freeswitch + webRtc +jssip 实现web端语音通话

版权声明:本文为CSDN博主「foruok」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/foruok/article/details/74321214

上一篇文章我们已经介绍了freeswitch 的安装,接下来介绍freeswitch的使用。
今天主要介绍freeswitch + webRtc +jssip 实现web端语音通话。
原文中会做freeswitch的视频配置更改,但我只做语音通话,所有忽略部分步骤。

准备 JsSIP 库文件

可以从 http://www.jssip.net/download/ 下载一个 min 版的 js 文件,我用的是 3.1.2 ,文件名是 jssip-3.1.2 .min.js ,把它放在我们web服务器(我用的是tomcat)的 js 目录下,我们将在 html 文件内引用它。类似:

<script src="js/jssip-3.1.2.min.js" type="text/javascript"></script> 

Demo

本文基本采用原文的demo,新建一个html,并引入上文的jssip-3.1.2.min.js,具体代码如下。

<!DOCTYPE html>
<html>
<head>
    <title>JsSIP + WebRTC + freeSWITCH</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="Author" content="foruok" />
    <meta name="description" content="JsSIP based example web application." />
    <script src="./jssip-3.1.2.min.js" type="text/javascript"></script>
    <style type="text/css">
    </style>
</head>


<body>

<div id="login-page" style="width: 424px; height: 260px; background-color: #f2f4f4; border: 1px solid grey; padding-top: 4px">
    <table border="0" frame="void" width="418px">
        <tr>
            <td class="td_label" width="160px" align="right"><label for="sip_uri">SIP URI:</label></td>
            <td width="258px"><input style="width:250px" id="sip_uri" type="text" placeholder="sip:1001@10.0.0.0.com" value=""/></td>
        </tr>
        <tr>
            <td class="td_label"  align="right"><label for="sip_password">SIP Password:</label></td>
            <td><input style="width:250px" id="sip_password" type="password" placeholder="SIP password" value=""/></td>
        </tr>
        <tr>
            <td class="td_label" align="right"><label for="ws_uri">WSS URI:</label></td>
            <td><input style="width:250px" id="ws_uri" class="last unset" type="text" placeholder="WSS URI (wss://example.com)" value=""/></td>
        </tr>
        <tr>
            <td class="td_label"  align="right"><label class="input_label" for="sip_phone_number">SIP Phone Info:</label></td>
            <td><input style="width:250px" id="sip_phone_number" type="text" placeholder="sip:1001@10.0.0.0:5060" value=""></td>
        </tr>
        <tr>
            <td colspan="2" align="center"><button onclick="testStart()"> Initialize </button></td>
        </tr>
        <tr>
            <td colspan="2" align="center"><button onclick="testCall()"> Call </button></td>
        </tr>
        <tr>
            <td  colspan="2" align="center"><button onclick="captureLocalMedia()"> Capture Local Media</button></td>
        </tr>
    </table>
</div>

<div style="width: 424px; height: 324px;background-color: #333333; border: 2px solid blue; padding:0px; margin-top: 4px;">
    <video id="video" width="420px" height="320px" autoplay ></video>
    <audio id="audio" controls></audio>
</div>

</body>
<script type="text/javascript">
  var outgoingSession = null;
  var incomingSession = null;
  var currentSession = null;
  var audio = document.getElementById('audio');

  var constraints = {
    audio: true,
    video: true,
    mandatory: {
      maxWidth: 640,
      maxHeight: 360
    }
  };
  URL = window.URL || window.webkitURL;

  var localStream = null;
  var userAgent = null;

  function gotLocalMedia(stream) {
    console.info('Received local media stream');
    localStream = stream;
    audio.src = URL.createObjectURL(stream);
  }

  function captureLocalMedia() {
    console.info('Requesting local video & audio');
    navigator.webkitGetUserMedia(constraints, gotLocalMedia, function(e){
      alert('getUserMedia() error: ' + e.name);
    });
  }

  function testStart(){

    var sip_uri_ = document.getElementById("sip_uri").value.toString();
    var sip_password_ = document.getElementById("sip_password").value.toString();
    var ws_uri_ = document.getElementById("ws_uri").value.toString();

    console.info("get input info: sip_uri = ", sip_uri_, " sip_password = ", sip_password_, " ws_uri = ", ws_uri_);

    var socket = new JsSIP.WebSocketInterface(ws_uri_);
    var configuration = {
      sockets: [ socket ],
      outbound_proxy_set: ws_uri_,
      uri: sip_uri_,//与用户代理关联的SIP URI(字符串)。这是您的提供商提供给您的SIP地址
      password: sip_password_,//SIP身份验证密码
      register: true,//指示启动时JsSIP用户代理是否应自动注册
      session_timers: false//启用会话计时器(根据RFC 4028)
    };

    userAgent = new JsSIP.UA(configuration);

    //成功注册成功,data:Response JsSIP.IncomingResponse收到的SIP 2XX响应的实例
    userAgent.on('registered', function(data){
      console.info("registered: ", data.response.status_code, ",", data.response.reason_phrase);
    });
    //由于注册失败而被解雇,data:Response JsSIP.IncomingResponse接收到的SIP否定响应的实例,如果失败是由这样的响应的接收产生的,否则为空
    userAgent.on('registrationFailed', function(data){
      console.log("registrationFailed, ", data);
      //console.warn("registrationFailed, ", data.response.status_code, ",", data.response.reason_phrase, " cause - ", data.cause);
    });

    //1.在注册到期之前发射几秒钟。如果应用程序没有为这个事件设置任何监听器,JsSIP将像往常一样重新注册。
    // 2.如果应用程序订阅了这个事件,它负责ua.register()在registrationExpiring事件中调用(否则注册将过期)。
    // 3.此事件使应用程序有机会在重新注册之前执行异步操作。对于那些在REGISTER请求中的自定义SIP头中使用外部获得的“令牌”的环境很有用。
    userAgent.on('registrationExpiring', function(){
      console.warn("registrationExpiring");
    });

    //为传入或传出会话/呼叫激发。data:
    //     originator:'remote',新消息由远程对等方生成;'local',新消息由本地用户生成。
    //      session:JsSIP.RTCSession 实例。
    //      request:JsSIP.IncomingRequest收到的MESSAGE请求的实例;JsSIP.OutgoingRequest传出MESSAGE请求的实例
    userAgent.on('newRTCSession', function(data){
      console.info('onNewRTCSession: ', data);
      if(data.originator == 'remote'){ //incoming call
        console.info("incomingSession, answer the call");
        incomingSession = data.session;
        //回答传入会话。此方法仅适用于传入会话。
        data.session.answer({'mediaConstraints' : { 'audio': true, 'video': true },
         // 'mediaStream': localStream
        });
      }else{
        console.info("outgoingSession");
        outgoingSession = data.session;
        outgoingSession.on('connecting', function(data){
          console.info('onConnecting - ', data.request);
          currentSession = outgoingSession;
          outgoingSession = null;
        });
      }
      //接受呼叫时激发
      data.session.on('accepted', function(data){
        console.info('onAccepted - ', data);
        if(data.originator == 'remote' && currentSession == null){
          currentSession = incomingSession;
          incomingSession = null;
          console.info("setCurrentSession - ", currentSession);
        }
      });
      //确认呼叫后激发
      data.session.on('confirmed', function(data){
        console.info('onConfirmed - ', data);
        if(data.originator == 'remote' && currentSession == null){
          currentSession = incomingSession;
          incomingSession = null;
          console.info("setCurrentSession - ", currentSession);
        }
      });
      //在将远程SDP传递到RTC引擎之前以及在发送本地SDP之前激发。此事件提供了修改传入和传出SDP的机制。
      data.session.on('sdp', function(data){
        console.info('onSDP, type - ', data.type, ' sdp - ', data.sdp);
        //data.sdp = data.sdp.replace('UDP/TLS/RTP/SAVPF', 'RTP/SAVPF');
        //console.info('onSDP, changed sdp - ', data.sdp);
      });
      //接收或生成对邀请请求的1XX SIP类响应(>100)时激发。该事件在SDP处理之前触发(如果存在),以便在需要时对其进行微调,甚至通过删除数据对象中响应参数的主体来删除它
      data.session.on('progress', function(data){
        console.info('onProgress - ', data.originator);
        if(data.originator == 'remote'){
          console.info('onProgress, response - ', data.response);
        }
      });
      //创建基础RTCPeerConnection后激发。应用程序有机会通过在peerconnection上添加RTCDataChannel或设置相应的事件侦听器来更改peerconnection。
      data.session.on('peerconnection', function(data){
        console.info('onPeerconnection - ', data.peerconnection);
        data.peerconnection.onaddstream = function(ev){
          console.info('onaddstream from remote - ', ev);
          audio.src = URL.createObjectURL(ev.stream);
          audio.onloadstart = () => {
           audio.play();
          };
          audio.onerror = () => {
           alert('录音加载失败...');
          };
        };
      });
    });
    //为传入或传出消息请求激发。data:
    //     originator:'remote',新消息由远程对等方生成;'local',新消息由本地用户生成。
    //      message:JsSIP.Message 实例。
    //      request:JsSIP.IncomingRequest收到的MESSAGE请求的实例;JsSIP.OutgoingRequest传出MESSAGE请求的实例
    userAgent.on('newMessage', function(data){
      if(data.originator == 'local'){
        console.info('onNewMessage , OutgoingRequest - ', data.request);
      }else{
        console.info('onNewMessage , IncomingRequest - ', data.request);
      }
    });

    console.info("call register");
    //连接到信令服务器,并恢复以前的状态,如果以前停止。重新开始时,如果UA配置中的参数设置为register:true,则向SIP域注册。
    userAgent.start();
  }

  // Register callbacks to desired call events
  var eventHandlers = {
    'progress': function(e) {
      console.log('call is in progress');
    },
    'failed': function(e) {
      console.log('call failed: ', e);
    },
    'ended': function(e) {
      console.log('call ended : ', e);
    },
    'confirmed': function(e) {
      console.log('call confirmed');
    }
  };

  function testCall(){
    var sip_phone_number_ = document.getElementById("sip_phone_number").value.toString();

    var options = {
      'eventHandlers'    : eventHandlers,
      'mediaConstraints' : { 'audio': true, 'video': false ,
      },
      //'mediaStream': localStream
    };

    //outgoingSession = userAgent.call('sip:3000@192.168.40.96:5060', options);
    /*
           * 拨打多媒体电话。不需要自己调用 getUserMedia 来捕获音视频了, JsSIP 会根据你传给JsSIP.UA.call方法的参数来自己调用

               参数

               Target 通话的目的地。String表示目标用户名或完整的SIP URI或JsSIP.URI实例。

               Options 可选Object附加参数(见下文)。
                   options对象中的字段;
                   mediaConstraints Object有两个有效的字段(audio和video)指示会话是否打算使用音频和/或视频以及要使用的约束。默认值是audio并且video设置为true。
                   mediaStream MediaStream 传送到另一端。
                   eventHandlers Object事件处理程序的可选项将被注册到每个呼叫事件。为每个要通知的事件定义事件处理程序。
               */
    outgoingSession = userAgent.call(sip_phone_number_, options);
  }
</script>
</html>

运行

启动我们的web服务(tomcat),访问我们添加的这个html页面,出现如下页面:
在这里插入图片描述
启动freeswitch,然后填写SIP信息:

这里使用了FreeSwitch 中默认配置的用户(1000-1019).
SIP URL : sip:1004@10.0.0.0 / sip:1004@example.com
SIP Password: FreeSwitch 中默认用户的密码为1234
WSS URL:ws://10.0.0.0:5066 / wss://10.0.7.69:7443(安全认证)
SIP Phone Info:被叫用户的SIP

点击Initialize 进行用户注册
Call:呼叫用户

到这一步我们的Demo就搭建完成了,因为jssip需要在本地或者安全认证的环境中使用,所以我们的服务是本地服务,也就是locahost。

本地Initialize是没有问题的,

Call的话会有点问题:我们是本地服务,那么如何实现语音通话呢?

自己跟自己通话?开两个页面,分别注册一个用户,然后呼叫?
显然是行不通的主叫方和被叫方都需要调用音频设备,同一台机器显然是不允许的。但其他机器访问我们的demo就需要IP,这时候发现报错了,jssip不允许访问音频设备。

那么我们怎么测试呢?
可以注册一个用户,然后呼叫随意一个用户,我们会听到正常呼叫时的“嘟嘟”声和一段时间后的呼叫失败提示。
这样是否可以证明我们的测试没有问题,服务是可用的了?

仅仅用来测试,到这一步也许可以了。但作为一个技术方案或者演示Demo显然这样还差点意思。

那怎么办呢

1、找另外一台机器,部署一套tomcat服务,两个机器分别访问本地的demo页面,注册一个用户,然后这两个用户就可以正常语音通话了。
2、部署到服务器,使用https和wss,这样就可以两台机器分别访问注册,然后测试通话了。

但这个针对测试或这调研来说,不太可能或申请域名或者认证的。所以我们只能搞一搞自签证书试试了。
下一篇文章重点介绍自签证书下使用https和wss实现我们的语音通话。

参考资料:
https://www.cnblogs.com/gxp69/articles/12028002.html
参考资料:
https://blog.csdn.net/foruok/article/details/74321214

### 回答1: SIP.js 是一个用于浏览器中实现实时通信的开源 JavaScript 库。它支持与 SIP 服务器进行呼入和呼出通话,并且还支持呼叫转移和保持功能。 通过 Sip.js,我们可以通过浏览器进行 WebRTC 电话呼入和呼出。这意味着我们可以使用浏览器作为我们的电话终,并通过互联网连接进行语音通话。我们只需要一个支持 WebRTC 的浏览器,如 Google Chrome 或 Mozilla Firefox,以及一个配置良好的 FreeSWITCH 服务器。 首先,我们可以使用 Sip.js 建立与 FreeSWITCH 服务器的连接,并注册一个用户。通过这个用户,我们可以发送一个呼出请求来拨打电话到另一个用户,或者接收一个呼入请求来接听电话。 当我们接听了电话后,我们可以选择将电话转移到另一个用户或者电话号码。这可以通过 Sip.js 中提供的呼叫转移功能来实现。我们可以指定转移的目标用户或电话号码,并且可以选择在转移时将呼叫保持或取消保持。 另外一个非常有用的功能是呼叫保持。当我们在通话过程中需要暂时中断通话时,我们可以使用 Sip.js 提供的呼叫保持功能。这个功能允许我们将通话保持在后台,并在需要时重新恢复通话。这对于需要处理多个呼叫或需要暂时离开的用户非常有用。 总之,使用 Sip.js 和 FreeSWITCH,我们可以实现 WebRTC 电话的呼入、呼出、转移和保持功能。这使得我们可以通过浏览器进行实时语音通话,并且可以灵活地控制电话的转移和保持状态。 ### 回答2: SIP.js是一个基于WebRTC技术的开源JavaScript库,用于在网页实现电话呼入、呼出、转移和保持等功能。它与FreeSWITCH一起使用,可以创建一个强大的Web电话应用。 呼入功能指的是当有电话呼叫进入系统时,SIP.js与FreeSWITCH配合使用,可以在网页接受并处理这些呼叫。用户可以通过网页界面接听和拒绝呼叫,并可以显示来电号码等相关信息。 呼出功能允许用户通过网页发起呼叫。用户可以在网页中输入电话号码,并点击拨号按钮,SIP.js将与FreeSWITCH建立连接并将呼叫发出。用户可以直接在网页上进行语音通话,无需使用传统的电话设备。 转移功能指的是将呼叫从一个用户转移至另一个用户。使用SIP.js和FreeSWITCH,用户可以在网页上进行呼叫转移操作。例如,当用户接听电话后,可以选择将呼叫转移到其他用户或号码。 保持功能允许用户在进行通话时暂停通话并将其保持。使用SIP.js和FreeSWITCH,用户可以通过网页界面上的按钮实现保持和取消保持操作。当通话暂停时,用户之间的语音通信将被暂停,但通话仍然保持连接。取消保持后,通话将恢复正常。 总而言之,SIP.js与FreeSWITCH结合使用可实现在网页创建强大的Web电话应用,包括呼入、呼出、转移和保持等功能。这使得用户可以方便地通过网页进行语音通话,提高了通信的灵活性和便利性。 ### 回答3: SIP.js是一个用于实现SIP(Session Initiation Protocol,会话初始化协议)通信的JavaScript库,而FreeSWITCH是一个开源的电话交换软件平台。它们结合使用可以实现WebRTC电话的呼入、呼出、转移和保持等功能。 WebRTC是一种实时通信的标准,可以在支持WebRTC的浏览器中实现点对点的音视频通话。而SIP是一种常用的语音和多媒体会话协议,用于建立、修改和终止多媒体会话。 使用SIP.js和FreeSWITCH,可以在网页实现电话呼入功能,即用户可以通过在网页上输入电话号码并发起呼叫,FreeSWITCH将收到的呼叫转发到SIP.js,并通过WebRTC建立与目标号码的通信。 同样地,可以实现电话呼出功能。用户在网页上选择拨打的目标号码,SIP.js将呼叫请求发送给FreeSWITCHFreeSWITCH通过拨号规则将呼叫转发到外部电话网络,从而实现电话呼出功能。 此外,SIP.js和FreeSWITCH还可以实现电话转移功能。用户可以在通话中将电话转移到其他电话号码,通过SIP.js向FreeSWITCH发送转移请求,FreeSWITCH通话转移到指定的目标电话。 另外,还可以通过SIP.js和FreeSWITCH实现电话保持功能。当用户需要将通话暂时保持时,SIP.js可以向FreeSWITCH发送保持请求,FreeSWITCH通话暂停,并播放保持音乐。当用户想要恢复通话时,再次调用SIP.js的相应函数,FreeSWITCH将取消保持,恢复通话。 总结来说,SIP.js结合FreeSWITCH可以方便地实现WebRTC电话的呼入、呼出、转移和保持等功能。这些功能使得在网页进行实时语音通信变得简单和便捷。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值