小工具知识网

当前位置: 首页 > 行业知识 > 一对一WebRTC视频通话系列(四)——offer、answer、candidate信令实现

行业知识

一对一WebRTC视频通话系列(四)——offer、answer、candidate信令实现

2026-11-15 16:42:14 来源:互联网转载

本篇博客主要讲解offer、answer、candidate信令实现,涵盖了媒体协商和网络协商相关实现。

本系列博客主要记录一对一WebRTC视频通话实现过程中的一些重点,代码全部进行了注释,便于理解WebRTC整体实现。


一对一WebRTC视频通话系列往期博客

一对一WebRTC视频通话系列(一)—— 创建页面并显示摄像头画面

一对一WebRTC视频通话系列(二)——websocket和join信令实现

一对一WebRTC视频通话系列(三)——leave和peer-leave信令实现


offer、answer、candidate信令实现

  • 整体实现思路
    • 1. 客户端
    • 2. 服务端

      整体实现思路

      整体实现思路(红色部分为客户端,蓝色为服务端):

      (1)收到new­peer (handleRemoteNewPeer处理),作为发起者创建RTCPeerConnection,绑定事件响应函数,加入本地流;

      (2)创建offer sdp,设置本地sdp,并将offer sdp发送到服务器;

      (3)服务器收到offer sdp 转发给指定的remoteClient;

      (4)接收者收到offer,也创建RTCPeerConnection,绑定事件响应函数,加入本地流;

      (5)接收者设置远程sdp,并创建answer sdp,然后设置本地sdp并将answer sdp发送到服务器;

      (6)服务器收到answer sdp 转发给指定的remoteClient;

      (7)发起者收到answer sdp,则设置远程sdp;

      (8)发起者和接收者都收到ontrack回调事件,获取到对方码流的对象句柄;

      (9)发起者和接收者都开始请求打洞,通过onIceCandidate获取到打洞信息(candidate)并发送给对方

      (10)如果P2P能成功则进行P2P通话,如果P2P不成功则进行中继转发通话。

      1. 客户端

      (1)创建RTCPeerConnection,绑定事件响应函数,加入本地流

      handleRemoteNewPeer->doOffer->ceratePeerConnection()

      function doOffer() {    //创建RTCPeerConnection对象    if(pc == null)        ceratePeerConnection();    pc.createOffer().then(createOfferAndSendMessage).catch(handleCreateOfferError);}
      function ceratePeerConnection() {    //创建RTCPeerConnection对象    pc = new RTCPeerConnection(null);    pc.onicecandidate = handleIceCandidate;    pc.ontrack = handleRemoteStreamAdd;    localStream.getTracks().forEach(track => {        pc.addTrack(track, localStream);    });}

      (2)创建offer sdp,设置本地sdp,并将offer sdp发送到服务器

      handleRemoteNewPeer->doOffer->

      pc.createOffer().then(createOfferAndSendMessage).catch(handleCreateOfferError);

      function createOfferAndSendMessage(session){    pc.setLocalDescription(session).then(function(){        var jsonMsg = {            'cmd': 'offer',            'roomId': roomId,            'uid': localUserId,            'remoteUid':remoteUserId,            'msg': JSON.stringify(session)        };        var message = JSON.stringify(jsonMsg); //将json对象转换为字符串        zeroRTCEngine.sendMessage(message);   //设计方法:用实现方法而不是直接用变量        console.info("send offer message: " + message);            }).catch(function(error){        console.error('offer setLocalDiscription failed: ' + error.toString());    });}

      (4)接收者收到offer,也创建RTCPeerConnection,绑定事件响应函数,加入本地流

      ZeroRTCEngine.prototype.onmessage()解析收到信息。

      当信令为SIGNAL_TYPE_OFFER时,调用handleRemoteOffer()进行处理。

      function handleRemoteOffer(message) {    console.info("handleRemoteOffer");    if(pc == null){        ceratePeerConnection();    }    var desc = JSON.parse(message.msg);    pc.setRemoteDescription(desc);    doAnswer();}

      (5)接收者设置远程sdp,并创建answer sdp,然后设置本地sdp并将answer sdp发送到服务器;

      在(4)完成后,调用doAnswer()函数实现。

      function doAnswer() {    pc.createAnswer().then(createAnswerAndSendMessage).catch(handleCreateAnswerError);}
      function createAnswerAndSendMessage(session){    pc.setLocalDescription(session).then(function(){        var jsonMsg = {            'cmd': 'answer',            'roomId': roomId,            'uid': localUserId,            'remoteUid':remoteUserId,            'msg': JSON.stringify(session)        };        var message = JSON.stringify(jsonMsg); //将json对象转换为字符串        zeroRTCEngine.sendMessage(message);   //设计方法:用实现方法而不是直接用变量        console.info("send answer message: " + message);            }).catch(function(error){        console.error('answer setLocalDiscription failed: ' + error.toString());    });}

      (7)发起者收到answer sdp,则设置远程sdp;

      ZeroRTCEngine.prototype.onmessage()解析收到信息。

      当信令为SIGNAL_TYPE_ANSWER时,调用handleRemoteAnswer()进行处理。

      function handleRemoteAnswer(message) {    console.info("handleRemoteAnswer");    var desc = JSON.parse(message.msg);    pc.setRemoteDescription(desc);}

      (8)发起者和接收者都收到ontrack回调事件,获取到对方码流的对象句柄; ???

      (9)发起者和接收者都开始请求打洞,通过onIceCandidate获取到打洞信息(candidate)并发送给对方

      function createPeerConnection() {    pc = new RTCPeerConnection(null);    pc.onicecandidate = handleIceCandidate;    pc.ontrack = handleRemoteStreamAdd;    localStream.getTracks().forEach((track) => pc.addTrack(track, localStream));}
      function handleIceCandidate(event) {    console.info("handleIceCandidate");    if (event.candidate) {        var jsonMsg = {            'cmd': 'candidate',            'roomId': roomId,            'uid': localUserId,            'remoteUid': remoteUserId,            'msg': JSON.stringify(event.candidate)        };        var message = JSON.stringify(jsonMsg);        zeroRTCEngine.sendMessage(message);        console.info("send candidate message");    } else {        console.warn("End of candidates");    }}
      function handleRemoteCandidate(message) {    console.info("handleRemoteCandidate");    var candidate = JSON.parse(message.msg);    pc.addIceCandidate(candidate).catch(e => {        console.error("addIceCandidate failed:" + e.name);    });}

      2. 服务端

      主要完成以下两点:

      (3)服务器收到offer sdp 转发给指定的remoteClient;

      (6)服务器收到answer sdp 转发给指定的remoteClient;

      应从消息监听函数入手,完成对offer、answer、candidate这3种情况的处理。

      // 监听客户端发送的消息conn.on("text", function (str) {    console.info("Received msg:"+str);    var jsonMsg = JSON.parse(str);    switch(jsonMsg.cmd){        case SIGNAL_TYPE_JOIN:            handleJoin(jsonMsg, conn);             break;        case SIGNAL_TYPE_LEAVE:            handleLeave(jsonMsg);            break;        case SIGNAL_TYPE_OFFER://新添1            handleOffer(jsonMsg);            break;        case SIGNAL_TYPE_ANSWER://新添2            handleAnswer(jsonMsg);            break;                         case SIGNAL_TYPE_CANDIDATE://新添3            handleCandidate(jsonMsg);        break;    }});

      首先完成offer信令处理函数:

      当收到视频流 offer 消息时,它会提取房间ID、用户ID和远程用户ID,然后检查房间Map中是否存在该用户ID。如果存在用户ID,它会将消息发送给远程用户。

      实现原理如下:

      1. 获取房间ID和用户ID。
      2. 获取房间Map。
      3. 检查用户ID是否存在于房间Map中。
      4. 如果远程用户存在,将消息发送给远程用户。
      5. 如果不存在,输出错误信息。
      function handleOffer(message){    // 获取房间ID和用户ID    var roomId = message.roomId;    var uid = message.uid;    var remoteUid = message.remoteUid;    console.info("handleOffer uid:" + uid + " send offer to remoteUid: " + remoteUid);    // 获取房间Map    var roomMap = roomTableMap.get(roomId);    if(roomMap == null){        console.error("roomId:" + roomId + " is not exist");        return;    }    if(roomMap.get(uid) == null){        console.error("uid:" + uid + " is not exist in roomId:" + roomId);        return;    }    var remoteClient = roomMap.get(remoteUid);    if(remoteClient){        var msg = JSON.stringify(message);        remoteClient.conn.sendText(msg);    }else{        console.error("remoteUid:" + remoteUid + " is not exist in roomId:" + roomId);    }}

      answer和candidate信令处理函数逻辑与offer几乎一样,简单修改函数名称和打印信息即可:

      function handleAnswer(message){    // 获取房间ID和用户ID    var roomId = message.roomId;    var uid = message.uid;    var remoteUid = message.remoteUid;    console.info("handleAnswer uid:" + uid + " send answer to remoteUid: " + remoteUid);    // 获取房间Map    var roomMap = roomTableMap.get(roomId);    if(roomMap == null){        console.error("roomId:" + roomId + " is not exist");        return;    }    if(roomMap.get(uid) == null){        console.error("uid:" + uid + " is not exist in roomId:" + roomId);        return;    }    var remoteClient = roomMap.get(remoteUid);    if(remoteClient){        var msg = JSON.stringify(message);        remoteClient.conn.sendText(msg);    }else{        console.error("remoteUid:" + remoteUid + " is not exist in roomId:" + roomId);    }}function handleCandidate(message){    // 获取房间ID和用户ID    var roomId = message.roomId;    var uid = message.uid;    var remoteUid = message.remoteUid;    console.info("handleCandidate uid:" + uid + " send Candidate to remoteUid: " + remoteUid);    // 获取房间Map    var roomMap = roomTableMap.get(roomId);    if(roomMap == null){        console.error("roomId:" + roomId + " is not exist");        return;    }    if(roomMap.get(uid) == null){        console.error("uid:" + uid + " is not exist in roomId:" + roomId);        return;    }    var remoteClient = roomMap.get(remoteUid);    if(remoteClient){        var msg = JSON.stringify(message);        remoteClient.conn.sendText(msg);    }else{        console.error("remoteUid:" + remoteUid + " is not exist in roomId:" + roomId);    }}

上一篇:dhcp服务器配置怎么做?

下一篇:idea服务器怎么破解?