美文网首页这事情急不得
WebRTC概念与实战速通(实战篇)

WebRTC概念与实战速通(实战篇)

作者: 这事情急不得 | 来源:发表于2019-04-10 03:55 被阅读61次

上一篇说了WebRTC的基本概念,这一篇我们来看一下在Web浏览器中怎么样使用WebRTC的API来建立视频通话。

Step 1:

首先,要能让对方看到视频,你必须获取你local的视频流,你的local视频流哪里来呢,可以是来自你的机器上的camera。所以第一步是要能打开你的camera。但是你的手机如果有前后双摄的话,那么你可能想指定把前摄像头的视频让对方看到而不是后摄像头的。或者你有多个摄像头的话,你可能想知道要打开哪个摄像头。

navigator.mediaDevices.enumerateDevices() 这个方法可以帮你列出机器上所有的device,每个device都有kind,label和deviceId这3个属性,你可以通过他们判断哪个deviceId是你要打开的摄像头。

navigator.mediaDevices.getUserMedia(constraints) 这个方法可以打开一个local的视频流,constraints这个对象里面可以对这个视频流做出许多的配置,比如要不要音频,视频长宽比,理想的FPS,是手机的前还是后置摄像头,等等。默认他会打开系统默认的摄像头,当然也可以把上面的deviceId直接设置给这个constraints的audio或video对象。constrants可以配置的东西可谓眼花缭乱。

这个网页详细的介绍了constraints以及怎样在视频进行过程中更改constraints:

https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints

Step 2:

现在我们得到了一个local视频的stream,接下来我们需要把这个stream attach到负责进行P2P通讯的PeerConnection对象上。

首先使用 peerConnection = new RTCPeerConnection(configuration) 这个方法来创建一个peerConnection,注意这里的configuration参数的内容指的就是上一篇说到的ICE server的地址,可以像这样:

const configuration = {iceServers: [{urls: 'stuns:stun.example.org'}]};

注意这里的iceServers是个数组,所以你可以提供不止一个ice server。当然你也可以只提供一个空数组,这代表你在局域网内进行 WebRTC,此时你local的ICE candidate会以广播的方式发送到整个局域网,你也会收到来自局域网的很多设备的ICE candidate response,只要那些设备都支持WebRTC。当然最终你连接的设备只能是接受你SDP offer并给你发SDP answer的那一个设备。

接下来,在ICE candidate交换过程中peerConnection会trigger很多的events,你需要对这些events注册回调,当然这里面其实你只需要关注那么一两个关键的events就行了。

这里你至少需要处理onicecandidateonsignalingstatechange这两个事件的回调。

onicecandidate表明peerConnection在你local生成了一个ice candidate需要送到remote signal server那里去,所以在这个回调里一般你会通过websocket把这个ice candiate发送给signal server,然后在signal server那里再写code把ice candidate发送给另一个peer。

onsignalingstatechange 表明peerConnection在ice candidate协商和SDP offer/answer过程中改变自己的状态,只有当signalingState状态变为stable时,你才可以把你从remote signal server拿到的ice candidates加入到peerConnection中。

所以首先你的websocket接收到来自signal server的别人发给你的ice candidates,把他们存在一个queue里面,然后在这个回调中,你检测当signalingState === “stable”时,从queue里取出所有的ice candidates并且把他们一个一个的添加给peerConnection。代码类似这样:

接下来,你有一个optional的操作,是可以创建DataChannel,WebRTC的DataChannel可以用来传输一些文字信息,比如视频字幕,弹幕,当然这个DataChannel是optional的,用不到的话可以不创建。

可以通过peerConnection.CreateDataChannel(name)来创建一个DataChannel,创建完以后,你需要设置dataChannle对象的onmessage,onopen等回调,在此就不细说了。

最后一步,调用peerConnection.addStream(stream)来把从Step 1得到的local stream attach到peerConnection上,这样peerConnection才知道要发送哪个stream给对方。

Step 3:

接下来要开始进行SDP offer/answer交换的过程。首先使用

offer = peerConnection.createOffer(options)来创建要发送给signal server的SDP offer。这里options这个对象里你可以指定你只希望收到对方的视频还是只希望收到对方的音频还是两个都要。

然后调用peerConnection.setLocalDescription(offer)来把offer塞到peerConnection的本地描述符里去,媒体面数据传输中需要用到这个信息。最后,调用websocket或者其他自己写的方法把offer发送给signal server并确保另一方能接收到这个offer。

另一方接收到offer后,同样的,他那头需要调用

peerConnection.createAnswer(options)来创建answer,并通过signal server发送回给我们的local。此时我们的local需要调用

peerConnection.SetRemoteDescription(answer)来把SDP answer塞到peerConnection的远端描述符里去。当你有了这个远端描述符以后,紧接着你就可以调用peerConnection.getRemoteStreams()来得到对方的remote streams,一般里面只有一个stream,所以只要取第0个就行了。注意这里peerConnection.getRemoteStreams()已经deprecated,新的方法是peerConnection.getReceivers()然后直接得到stream里面的video/audio tracks,但我没用过。

最后,当你有了remote stream以后,你只要把这个stream attach到html video tag,就能看到对方发来的视频了。

注意:

第一,navigator.mediaDevices.getUserMedia() 这个方法如果是在浏览器load的某个网站前端里调用,那么必须是在https环境下才能调用成功。只有在localhost环境下,浏览器才不会检测任何security,所以在localhost环境下怎么弄都可以。

第二,以上流程并不是唯一的流程。特别是ICE candidates交换和SDP offer/answer交换这两个过程并没有什么内在的联系,所以是可以并行互不影响的。有些实现把peerConnection.createOffer()的时机放在peerConnection的onicecandidate回调里面,这个其实是没有必要的。

但由于这两个过程的先后顺序未定义的原因,你有可能先收到SDP answer,也有可能先收到另一头发给你的ice candidate,这里需要小心处理,你的code里决不能假设有一个先后顺序。

其实协议里也并不是每个细节都定义的十分清楚的。这让我想到了很多年以前我修改一个ssh的linux library,改了一些代码让它能在windows下也能cmake build成功,并且fix了它的一些bug。弄完以后我用它做了一个ssh client去连接我们的一个基于RTOSVxWorks的3G通讯的某个刀片server,发现死活连不上,然而用putty是一下就连上了。然而用我的client去连接另一个linux server却是好的能work的。

后来经过仔细研究library的code,发现ssh握手中有一步是双方互相发送各自支持的加密算法的列表给对方,这样就可以用双方都支持的加密算法来加密解密消息,不至于加密完后传到你那里你没法解密。然而在rfc中到底哪一方先把自己支持的加密算法的列表发送给对方,这个行为是没有定义的。而这个library里永远是假设等到接收到了对方发来的列表后才把自己的列表发出去,当我把这个code改成主动发送列表后,问题就解决了。由此可见我们的刀片server搭载的ssh server的逻辑也是要等到接收到了别人的才发送自己的,这样就变成了互相等待。而putty肯定是采用了主动发送的策略。

第三,这次只是讨论了1对1的WebRTC,有可能你需要1对多,多对多的WebRTC,比如直播或者在线会议室,此时你可能需要建立很多的PeerConnection,并管理很多个remote streams,这就十分的复杂了。

第四,WebRTC目前只在谷歌自家的chrome浏览器上支持的最好,Android自带的浏览器应该也支持的很好。但苹果家现在也可以支持了,只是需要safari 12或者ios 11.3以上。根据这个网站显示:

http://gs.statcounter.com/ios-version-market-share/mobile-tablet/worldwide

目前 ios 低于11.3的至少也有10%,你使用了WebRTC的app是否要放弃这10%的用户也成了一个很大的考虑。

另一个解决方案是直接使用WebRTC的编译好的library,毕竟浏览器也是直接使用了WebRTC的编译好了的library然后把library的API用js的形式提供给了前端而已。但问题是对于WebRTC的底层library的API并没有什么文档,所以直接用的effort会非常大。目前已知的是Cordova和react-native都有对应的封装成js API的项目,但Cordova的项目好几年没有更新了,react-native的项目里面有一些很久都没解决的issue。

第五,一般不会WebRTC的两端都是浏览器,两端都是用js API来写。一般情况下只有一端是浏览器,另一端是一个能同时接收很多WebRTC连接的server,此时在server端就有一些open source可以选择了,这些open source提供的API和标准的js API可能会完全不一样。

相关文章

网友评论

    本文标题:WebRTC概念与实战速通(实战篇)

    本文链接:https://www.haomeiwen.com/subject/pxdiiqtx.html