WebRTC + JsSIP + freeSWITCH一对一视频聊天
来源:互联网 发布:数据库建表语句 编辑:程序博客网 时间:2024/05/12 18:16
之前几篇文件介绍了 freeSWITCH 和 WebRTC 结合在一起需要的各种环境,现在到了最关键的一篇,使用 JsSIP 来创建一个 DEMO 。这次我们需要写点 JS 代码。
准备 JsSIP 库文件
可以从 http://www.jssip.net/download/ 下载一个 min 版的 js 文件,我用的是 3.0.13 ,文件名是 jssip-3.0.13.min.js ,把它放在我们之前用 Node.js 建立的 https 服务器的 public/js 目录下,我们将在 html 文件内引用它。类似:
<script src="js/jssip-3.0.13.min.js" type="text/javascript"></script>
配置 freeSWITCH
我们之前下载的 freeSWITCH ,默认是不处理音视频编解码的,所以,要设置它采用 media proxy 模式来代理转发 WebRTC 的音视频,这样就可以基于 JsSIP 、 WebRTC 、 freeSWITCH 来一对一视频聊天。
修改vars.xml,加入:
<X-PRE-PROCESS cmd=="set" data="proxy_media=true"/>
修改sip_profiles/internal.xml,设置inbound-proxy-media和inbound-late-negotiation为true,类似下面:
<!--Uncomment to set all inbound calls to proxy media mode--> <param name="inbound-proxy-media" value="true"/> <!-- Let calls hit the dialplan before selecting codec for the a-leg --> <param name="inbound-late-negotiation" value="true"/>
这样配置之后,freeSWITCH 会进入代理模式,不对media 做任何处理,直接在两个 end peer 之间转发(RTP包)。
JsSIP DEMO
JsSIP 的 API 文档参考下面链接:
- http://www.jssip.net/documentation/3.0.x/api/
- http://www.jssip.net/documentation/3.0.x/api/session/
- http://www.jssip.net/documentation/3.0.x/api/ua/
注意, JsSIP 对 SIP 和 WebRTC 做了封装,比如你不需要自己调用 getUserMedia 来捕获音视频了, JsSIP 会根据你传给JsSIP.UA.call方法的参数来自己调用,用起来比较方便。
但是,你还是要了解 SIP 呼叫的流程和WebRTC的各种限制以及如何处理 RTCPeerConnection 发过来的音视频流。
关于 WebRTC JS 侧 API,看这里好了:http://w3c.github.io/webrtc-pc/。
想看更多资料,可以看我搜集的这些链接:WebRTC学习资料大全。
关于 SIP 的流程,参考《freeSWITCH权威指南》这本书吧,讲得很明白。
网上关于 JsSIP + freeSWITCH 的 demo 很少,而且基本跑不起来……我这个是验证过的啦!
直接给出我们的 demo.html 的所有代码:
<!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="js/jssip-3.0.13.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 URI (i.e: sip:alice@example.com)"/></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"/></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 (i.e: wss://example.com)"/></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:3000@192.168.40.96:5060"></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="videoView" width="420px" height="320px" autoplay ></video> </div></body> <script type="text/javascript"> var outgoingSession = null; var incomingSession = null; var currentSession = null; var videoView = document.getElementById('videoView'); 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; videoView.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_, password: sip_password_, register: true, session_timers: false }; userAgent = new JsSIP.UA(configuration); userAgent.on('registered', function(data){ console.info("registered: ", data.response.status_code, ",", data.response.reason_phrase); }); userAgent.on('registrationFailed', function(data){ console.log("registrationFailed, ", data); //console.warn("registrationFailed, ", data.response.status_code, ",", data.response.reason_phrase, " cause - ", data.cause); }); userAgent.on('registrationExpiring', function(){ console.warn("registrationExpiring"); }); 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, mandatory: { maxWidth: 640, maxHeight: 360 } }, '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); } }); 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); }); data.session.on('progress', function(data){ console.info('onProgress - ', data.originator); if(data.originator == 'remote'){ console.info('onProgress, response - ', data.response); } }); data.session.on('peerconnection', function(data){ console.info('onPeerconnection - ', data.peerconnection); data.peerconnection.onaddstream = function(ev){ console.info('onaddstream from remote - ', ev); videoView.src = URL.createObjectURL(ev.stream); }; }); }); 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"); 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': true , mandatory: { maxWidth: 640, maxHeight: 360 } }, 'mediaStream': localStream }; //outgoingSession = userAgent.call('sip:3000@192.168.40.96:5060', options); outgoingSession = userAgent.call(sip_phone_number_, options); } </script></html>
关于 JsSIP.UA 和 JsSIP.RTCSession 等类和 API 的使用,参考代码,对照 JsSIP 的官方文档,即可理解。
注意我在代码里调用 RTCSession 的 answer 方法做了自动接听。实际开发中,你需要弹出一个提示框,让用户选择是否接听。
一对一视频聊天的效果:
先运行 freeSWITCH , 验证启动正常(参看freeSWITCH安装、配置与局域网测试),再运行 npm start
启动 https 服务器,最后 Chrome 内访问 https://192.168.40.131:8080/demo.html ,看到下面的结果:
注意,必须填写有效信息,然后先点击 Initialize 来初始化,代码中会到 freeSWITCH 那里注册 SIP 号码,然后才可以呼叫别人。
被呼叫方只需要填写信息,点击 Initialize 按钮,不需要点击 Call 按钮。
填写所有参数,点击 Initialize 按钮,可以注册一个 SIP 号码 1000(使用开发者工具可查看我输出的日志)。
然后到另一台电脑访问同一个链接,注册一个不同的账号 1002。
再回到初始的那台电脑,在 SIP Phone Info 后输入 sip:1002@192.168.40.131:5060
,然后点击 Call 按钮,即可呼叫 1002 ,接通后,可以看到对方视频。效果如下:
我测试的摄像头有问题,出来的是抽象视频……
这是我们使用 JsSIP 和 freeSWITCH 构建视频聊天的简单 DEMO 。
想起来一点非常重要的:freeSWITCH 和 nodejs 实现的 https 服务器,使用的证书应该是一个,否则会报错哦。可以先跑 freeSWITCH ,然后把 cert/wss.pem 文件的内容分拆给 nodejs 使用。
相关阅读:
- freeSWITCH安装、配置与局域网测试
- 使用nodejs为WebRTC+freeSWITCH搭建https服务
- freeSWITCH + WebRTC 音视频会议
- 使用Zoiper与freeSWITCH开视频会议
- Windows下编译freeSWITCH
- 使用freeSWITCH和Yate进行VoIP通话
- WebRTC + JsSIP + freeSWITCH一对一视频聊天
- JsSIP + WebRTC + freeSWITCH视频会议
- 基于WebRtc实现安卓视频一对一聊天
- webrtc/apprtc视频聊天
- Chrome引入WebRTC支持视频聊天
- 在网站中内置WebRTC视频聊天
- FreeSWITCH学习笔记 第二场 第一个镜头 JsSIP初识
- freeSWITCH + WebRTC 音视频会议
- FreeSWITCH:WebRTC 配置
- 能进行一对一视频聊天的网站有哪些?
- 120行代码实现 浏览器WebRTC视频聊天
- Nodejs + azure +webrtc 实现android 和web视频聊天步骤
- 聊天 一对一 Socket
- Socket一对一聊天实例
- WebRTC 聊天Demo
- FreeSWITCH 视频直播
- 5分钟搭建一个HTML5视频聊天Demo(WebRTC+NodeJS)
- 5分钟搭建一个HTML5视频聊天Demo(WebRTC+NodeJS)
- oracle-64位的Windows系统如何与32位的PL/SQL建立连接
- java设计模式--设配器adapter
- 中级任务笔记
- C#之网页开发基础
- TreeView---利用RecyclerView打造高性能树形控件
- WebRTC + JsSIP + freeSWITCH一对一视频聊天
- 使用Java编写Excel传统课表转日历格式
- 零拷贝原理-数据的收发-软中断和DMA
- 业界首部安卓热修复宝典出炉!你想知道的一切都在这里了
- 主成分分析(PCA)
- Selenium+java分层(三)
- Spring MVC之最简项目配置(全注解)
- 自定义控件之自定义开关
- 使用webpack创建vue项目,安装vue-router和不安装vue-router的区别