1.配置 Freeswitch 支持websocket

        配置 external 中绑定 websocket 地址

  <param name="apply-candidate-acl" value="none"/>
  <param name="wss-binding" value="0.0.0.0:6075"/>
  <param name="ws-binding"  value="0.0.0.0:6076"/>

        修改 C:\Program Files\FreeSWITCH\conf\autoload_configs 下的 verto.conf.xml 文件中的default-v4

    <profile name="default-v4">
      <param name="bind-local" value="123.60.78.179:6076"/>
      <param name="bind-local" value="123.60.78.179:6075" secure="true"/>
      <param name="force-register-domain" value="$${domain}"/>
      <param name="secure-combined" value="$${certs_dir}/wss.pem"/>
      <param name="secure-chain" value="$${certs_dir}/wss.pem"/>
      <param name="userauth" value="true"/>
      <!-- setting this to true will allow anyone to register even with no account so use with care -->
      <param name="blind-reg" value="false"/>
      <param name="mcast-ip" value="224.1.1.1"/>
      <param name="mcast-port" value="1337"/>
      <param name="rtp-ip" value="$${local_ip_v4}"/>
      <param name="ext-rtp-ip" value="$${external_rtp_ip}"/>
      <param name="local-network" value="localnet.auto"/>
      <param name="outbound-codec-string" value="opus,h264,vp8"/>
      <param name="inbound-codec-string" value="opus,h264,vp8"/>

      <param name="apply-candidate-acl" value="localnet.auto"/>
      <param name="apply-candidate-acl" value="wan_v4.auto"/>
      <param name="apply-candidate-acl" value="rfc1918.auto"/>
      <param name="apply-candidate-acl" value="any_v4.auto"/>
      <param name="timer-name" value="soft"/>
    </profile>

        保存重启服务,或者刷新配置,在控制台中输入命令查看是否绑定成功

sofia status profile external

2.一个页面demo测试是否成功

<!DOCTYPE html>
<html>
<head>
    <title>JsSIP WebRTC Test</title>

    <script src="https://cdn.jsdelivr.net/npm/jssip@latest/dist/jssip.min.js"></script>

</head>
<body>
    <h1>JsSIP WebRTC Test</h1>
    <div id="status">Disconnected</div>

    <div>
        <label for="freeswitchIp">FreeSWITCH IP:</label>
        <input type="text" id="freeswitchIp" value="123.60.78.179" placeholder="FreeSWITCH IP">
    </div>
    <div>
        <label for="wsPort">WS Port:</label>
        <input type="number" id="wsPort" value="6076" placeholder="WS Port">
    </div>
    <div>
        <label for="stunServer">STUN Server:</label>
        <input type="text" id="stunServer" value="stun:123.60.78.179:6074" placeholder="STUN Server">
    </div>
    <div>
        <label for="sipUri">SIP URI:</label>
        <input type="text" id="sipUri" value="sip:1004@123.60.78.179" placeholder="sip:1004@domain">
    </div>
    <div>
        <label for="password">Password:</label>
        <input type="password" id="password" value="1004" placeholder="Password">
    </div>

    <button id="connectButton">Connect</button>

    <div>
        <label for="callee">Callee:</label>
        <input type="text" id="callee" value="sip:1003@123.60.78.179" placeholder="sip:callee@domain">
    </div>
    <button id="callButton" disabled>Call</button>

    <div id="localView"></div>
    <div id="remoteView"></div>

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const statusDiv = document.getElementById('status');
        const connectButton = document.getElementById('connectButton');
        const sipUriInput = document.getElementById('sipUri');
        const passwordInput = document.getElementById('password');
        const callButton = document.getElementById('callButton');
        const calleeInput = document.getElementById('callee');
        const localView = document.getElementById('localView');
        const remoteView = document.getElementById('remoteView');
        const freeswitchIpInput = document.getElementById('freeswitchIp');
        const wsPortInput = document.getElementById('wsPort');
        const stunServerInput = document.getElementById('stunServer');

        let ua = null;
        let session = null;

        connectButton.addEventListener('click', () => {
            const sipUri = sipUriInput.value;
            const password = passwordInput.value;
            const freeswitchIp = freeswitchIpInput.value;
            const wsPort = parseInt(wsPortInput.value) || 6076;

            const configuration = {
                uri: sipUri,
                password: password,
                sockets: [new JsSIP.WebSocketInterface(`ws://${freeswitchIp}:${wsPort}`, { protocol: 'sip' })],
                register: true,
                trace_sip: true
            };

            ua = new JsSIP.UA(configuration);
            console.log('UA object:', ua);
            console.log('UA Configuration:', ua.configuration); // 添加这行

            ua.on('registered', () => {
                statusDiv.innerText = 'Registered';
                callButton.disabled = false;
            });

            ua.on('unregistered', () => {
                statusDiv.innerText = 'Unregistered';
                callButton.disabled = true;
            });

            ua.on('registrationFailed', (e) => {
                statusDiv.innerText = 'Registration Failed: ' + e.cause;
                callButton.disabled = true;
            });

            ua.on('newRTCSession', (data) => {
                session = data.session;
                if (session.direction === 'incoming') {
                    session.answer()
                    .then(() => {
                        attachMedia(session);
                    })
                    .catch((error) => {
                        console.error('Error answering call:', error);
                    });
                } else if (session.direction === 'outgoing') {
                    attachMedia(session);
                }
            });

            ua.start();
            statusDiv.innerText = 'Connecting...';
            connectButton.disabled = true;
        });

        callButton.addEventListener('click', () => {
            console.log('Call button clicked');
            const callee = `sip:1004@${freeswitchIpInput.value}`; // 修改呼叫目标
            console.log('Callee:', callee);
            console.log('UA:', ua);
            if (ua && callee) {
                session = ua.call(callee, {
                    mediaConstraints: { audio: true, video: false },
                    sessionTimersExpires: 1800 
                });
                console.log('Session object:', session);
                console.log('Session status:', session ? session.status : null);
                if (session) {
                    session.on('failed', (e) => {
                        console.error('Call failed:', e);
                    });
                    session.on('connecting', () => {
                        console.log('Call connecting...');
                    });
                    session.on('progress', (response) => {
                        console.log('Call progress:', response);
                    });
                    session.on('accepted', (response) => {
                        console.log('Call accepted:', response);
                    });
                    session.on('ended', (response) => {
                        console.log('Call ended:', response);
                    });
                }
                attachMedia(session);
            } else {
                console.log('UA not initialized or callee is empty.');
            }
        });

        function attachMedia(session) {
            console.log("Not requesting local media.");
        }
    });
</script>
</body>
</html>
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐