Advertisement

基于WebRTC实现音视频通话

阅读量:

客户端采用 WebRTC 技术(推流),通讯用 websocket。

从技术实现角度来看,在WebRTC协议中,默认情况下参与者无法直接看到或听到对方的实时视频或音频数据。这与传统的互联网会议系统存在显著差异。当发起方(即拨打电话者)点击启动通话按钮时,在本地生成媒体流并实时推送到对方端口;与此同时,在线接通的一方也会发送相关媒体数据至本地服务器进行处理,并将接收到的数据通过DOM元素展示出来以供查看和操作;随后系统会自动检测双方是否存在有效的ice候选参数值,并根据检测结果自动生成一个会话请求报文发送出去以建立连接

复制代码
 //获取媒体流

    
 stream.value = await navigator.mediaDevices.getUserMedia({
    
     video: true,
    
     audio: true
    
 });
    
  
    
 // 初始化 PeerConnection
    
 peerConnection.value = new RTCPeerConnection({
    
     iceServers: [
    
     {
    
         urls: 'stun:stun.l.google.com:19302'
    
     }
    
     ]
    
 });
    
  
    
 // 推流给接收方
    
 stream.value.getTracks().forEach((track) => {
    
     peerConnection.value.addTrack(track, stream.value);
    
 });
    
  
    
 // 捕获接收方的流
    
 peerConnection.value.ontrack = (event) => {
    
     remoteStream.value = event.streams[0];
    
     if (callType.value === TypeVideo) {
    
     remoteVideo.value.srcObject = remoteStream.value;
    
     } else {
    
     remoteAudio.value.srcObject = remoteStream.value;
    
     }
    
 };
    
  
    
 // 监听ICE候选,确保 WebRTC 的点对点连接能够成功建立
    
 peerConnection.value.onicecandidate = (event) => {
    
     if (event.candidate) {
    
     //发送candidate
    
     ws.send(event.candidate);
    
     }
    
 };
    
 // 创建 offer
    
 const offer = await peerConnection.value.createOffer();
    
 await peerConnection.value.setLocalDescription(offer);
    
  
    
 //发送offer,这里发送的offer可以理解成是接收方用来捕获发起方流的一个凭证,接收方通过peerConnection.value.ontrack可以捕获到。
    
 ws.send(offer);
    
 //拉起等待接听界面
    
 showCall.value = true;
    
 //状态等待接听
    
 callStatus.value = 'wating';

第一步:接收方收到 offer 后,
第一阶段:打开来电显示界面,
第二阶段是决定接听或拒绝此份 offer。
1)打开来电显示界面

复制代码
 //拉起来电接听界面

    
 showCall.vue = true;
    
 //状态来电接听
    
 callStatus.value = 'coming';
    
 //初始化来电人信息等
    
 ....

关闭RTCDomain并完成配置设置后即可实现推流的终止;具体操作包括将RTC相关参数设置为已关闭状态,并将其DOM字段设为空以确保后续不再参与流媒体处理。

复制代码
 //接收方

    
 showCall.value = false;
    
 callStatus.value = 'closing';
    
 ws.send('reject');
    
  
    
 //发起方
    
 if (peerConnection.value) {
    
     peerConnection.value.close();
    
     peerConnection.value = null;
    
 }
    
 if (stream.value) {
    
     const tracks = stream.value.getTracks();
    
     tracks.forEach((track) => track.stop());
    
 }
    
 if (localVideo.value)
    
     localVideo.value.srcObject = null;
    
 if (remoteVideo.value)
    
     remoteVideo.value.srcObject = null;
    
 if (remoteAudio.value)
    
     remoteAudio.value.srcObject = null;
    
 showCall.value = false;
    
 callStatus.value = 'closing';

3)在接听时的操作与拨打流程相似,在具体实施中需要执行以下步骤:首先配置远端SDP参数(即发起方提供的 offer),然后加入 ICE 候选人(这里需要注意的是只有 far-end SDP 参数完成初始化后才允许设置冰(ice)。

复制代码
 // 获取本地媒体流

    
 ...同发起方
    
 // 初始化 PeerConnection
    
 ...同发起方
    
 // 推流给发起方
    
 ...同发起方
    
 // 捕获发起方的流
    
 ...同发起方
    
 // 监听ICE候选
    
 ...同发起方
    
  
    
 //设置远端SDP
    
 await peerConnection.value.setRemoteDescription(new RTCSessionDescription(caller.value.offer));
    
  
    
 // 添加发起方发过来的ice
    
 iceCandidateQueue.value.forEach(async (candidate) => {
    
 await  peerConnection.value.addIceCandidate(candidate);
    
 });
    
 iceCandidateQueue.value  = [];
    
  
    
 // 创建 answer
    
 const  answer  =  await  peerConnection.value.createAnswer();
    
 await  peerConnection.value.setLocalDescription(answer);
    
  
    
 //发送answer给发起方
    
 ws.send(answer);
    
 //状态通话中
    
 callStatus.value = 'calling';

在ice处理方面即为远端的SDP在初始化完成时可以直接被配置为有效状态,在尚未完成初始化的情况下则将被添加到iceCandidateQueue队列中待用

复制代码
 // 处理新的 ICE 候选

    
 const handleNewICECandidate = async (candidate) => {
    
     const iceCandidate = new RTCIceCandidate(candidate);
    
     if (peerConnection.value?.signalingState === 'have-remote-offer' || peerConnection.value?.signalingState === 'stable') {
    
     peerConnection.value.addIceCandidate(iceCandidate);
    
     } else {
    
     iceCandidateQueue.value.push(iceCandidate);
    
     }
    
 };

在最后一步中,在发起方接收到接收方的响应之后,在远端SDP上设置为接收方的回答,并在本地ICE上发送ice token作为确认。

复制代码
 //设置远端SDP

    
 await peerConnection.value.setRemoteDescription(new RTCSessionDescription(caller.value.answer));
    
  
    
 //添加ICE
    
 iceCandidateQueue.value.forEach(async (candidate) => {
    
     await peerConnection.value.addIceCandidate(candidate);
    
 });
    
 iceCandidateQueue.value = [];
    
 //状态接听中
    
 callStatus.value = 'calling';

这就是 WebRTC 视频通话的关键代码跟流程!

例图:

全部评论 (0)

还没有任何评论哟~