3 HTML躬行记——WebRTC视频通话( 三 )


1)媒体协商
在下图中 , Alice 和 Bob 通过信令服务器在交换 SDP 信息 。

3 HTML躬行记——WebRTC视频通话

文章插图
Alice 先调用 createOffer() 创建一个 Offer 类型的 SDP , 然后调用 setLocalDescription() 配置本地描述 。
Bob 接收发送过来的 Offer , 调用 setRemoteDescription() 配置远端描述 。
再调用 createAnswer() 创建一个 Answer 类型的 SDP , 最后调用 setLocalDescription() 配置本地描述 。
而 Bob 也会接收 Answer 并调用 setRemoteDescription() 配置远端描述 。后面的代码会实现上述过程 。
2)RTCPeerConnection
在 WebRTC 中创建连接 , 需要先初始化 RTCPeerConnection 类 , 其构造函数可以接收 STUN/TURN 服务器的配置信息 。
// STUN/TURN Serversconst pcConfig = {//'iceServers': [{//'urls': '',//'credential': "",//'username': ""//}]};// 实例化 RTCPeerConnectionconst pc = new RTCPeerConnection(pcConfig);然后注册 icecandidate 事件 , 将本机的网络信息发送给信令服务器 , sendMessage() 函数后面会介绍 。
pc.onicecandidate = function(e) {if(!e.candidate) {return;}// 发送 ICE CandidatesendMessage({type: 'candidate',label: e.candidate.sdpMLineIndex,id: e.candidate.sdpMid,candidate: e.candidate.candidate});};最后注册 track 事件 , 接收远端的音视频流 。
pc.ontrack = function(e) {remoteVideo.srcObject = e.streams[0];remoteVideo.play();};3)长连接
在客户端中 , 已经引入了 socket.io 库 , 所以只需要调用 io() 函数就能建立长连接 。
sendMessage() 函数就是发送信息给服务器的 message 事件 。
const socket = io("http://localhost:1234");// 发送消息function sendMessage(data){socket.emit('message', data);}本地也有个 message 事件 , 会接收从服务端发送来的消息 , 其实就是那些转发的消息 。
data 对象有个 type 属性 , 可创建和接收远端的 Answer 类型的 SDP 信息 , 以及接收远端的 ICE 候选者信息 。
socket.on("message", function (data) {switch (data.type) {case "offer":// 配置远端描述pc.setRemoteDescription(new RTCSessionDescription(data));// 创建 Answer 类型的 SDP 信息pc.createAnswer().then((desc) => {pc.setLocalDescription(desc);sendMessage(desc);});break;case "answer":// 接收远端的 Answer 类型的 SDP 信息pc.setRemoteDescription(new RTCSessionDescription(data));break;case "candidate":// 实例化 RTCIceCandidateconst candidate = new RTCIceCandidate({sdpMLineIndex: data.label,candidate: data.candidate});pc.addIceCandidate(candidate);break;}});在代码中 , 用 RTCSessionDescription 描述 SDP 信息 , 用 RTCIceCandidate 描述 ICE 候选者信息 。
4)开播
为开播按钮注册点击事件 , 在事件中 , 首先通过 getUserMedia() 获取本地的音视频流 。
btn.addEventListener("click", function (e) {// 获取音视频流navigator.mediaDevices.getUserMedia({video: {width: size,height: size},audio: true}).then((stream) => {localVideo.srcObject = stream;localStream = stream;// 将 Track 与 RTCPeerConnection 绑定stream.getTracks().forEach((track) => {pc.addTrack(track, stream);});// 创建 Offer 类型的 SDP 信息pc.createOffer({offerToRecieveAudio: 1,offerToRecieveVideo: 1}).then((desc) => {// 配置本地描述pc.setLocalDescription(desc);// 发送 Offer 类型的 SDP 信息sendMessage(desc);});localVideo.play();});btn.disabled = true;});然后在 then() 方法中 , 让 localVideo 接收音视频流 , 并且将 Track 与 RTCPeerConnection 绑定 。
这一步很关键 , 没有这一步就无法将音视频流推给远端 。
然后创建 Offer 类型的 SDP 信息 , 配置本地描述 , 并通过信令服务器发送给远端 。

经验总结扩展阅读