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

本篇博客主要讲解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;
应从消息监听函数入手,完成对offeranswercandidate这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://新添1handleOffer(jsonMsg);break;case SIGNAL_TYPE_ANSWER://新添2handleAnswer(jsonMsg);break;                 case SIGNAL_TYPE_CANDIDATE://新添3handleCandidate(jsonMsg);break;}
});

首先完成offer信令处理函数:
当收到视频流 offer 消息时,它会提取房间ID、用户ID和远程用户ID,然后检查房间Map中是否存在该用户ID。如果存在用户ID,它会将消息发送给远程用户。
实现原理如下:

  1. 获取房间ID和用户ID。
  2. 获取房间Map。
  3. 检查用户ID是否存在于房间Map中。
  4. 如果远程用户存在,将消息发送给远程用户。
  5. 如果不存在,输出错误信息。
function handleOffer(message){// 获取房间ID和用户IDvar roomId = message.roomId;var uid = message.uid;var remoteUid = message.remoteUid;console.info("handleOffer uid:" + uid + " send offer to remoteUid: " + remoteUid);// 获取房间Mapvar 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);}
}

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

function handleAnswer(message){// 获取房间ID和用户IDvar roomId = message.roomId;var uid = message.uid;var remoteUid = message.remoteUid;console.info("handleAnswer uid:" + uid + " send answer to remoteUid: " + remoteUid);// 获取房间Mapvar 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和用户IDvar roomId = message.roomId;var uid = message.uid;var remoteUid = message.remoteUid;console.info("handleCandidate uid:" + uid + " send Candidate to remoteUid: " + remoteUid);// 获取房间Mapvar 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);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/7164.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自动驾驶主流芯片及平台架构(二)特斯拉自动驾驶芯片平台介绍

早期 对外采购mobileye EyeQ3 芯片摄像头半集成方案,主要是为了满足快速量产需求,且受制于研发资金不足限制; 中期 采用高算力NVIDIA 芯片平台其他摄像头供应商的特斯拉内部集成方案,mobileye开发节奏无法紧跟特斯拉需求&#xff…

如何取消xhr / fetch / axios请求

如何取消xhr请求 setTimeout(() > { xhr.abort() }, 1000)如何取消fetch请求 fetch()请求发送以后,如果中途想要取消,需要使用AbortController对象。 let controller new AbortController(); let signal controller.signal;fetch(url, {signal:…

【简单介绍下Debian常用命令】

🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…

基于51单片机的手动数字时钟设计

基于51单片机的手动数字时钟 (仿真+程序) 功能介绍 具体功能: 1.八位数码管显示时分秒,格式为XX-XX-XX; 2.六个按键控制时、分、秒的加减; 3.复位按键重新计时; ​演示视频&am…

ESG视角下的多期DID构建(2009-2022年)4.5万+数据

随着ESG信息越来越受到重视,一些第三方评级机构开始推出ESG评级产品,目前在第三方数据库能够查到华证、富时罗素、商道融绿、社会价值投资联盟以及Wind自有的ESG评级数据等。其中,商道融绿是中国最早发布ESG评级数据的机构,也是国…

产业空间集聚DO指数计算

1.前言 创始人 :Duranton and Overman(2005) 目前应用较多的产业集聚度量指数主要基于两类,一是根据不同空间地理单元中产业经济规模的均衡性进行构造,如空间基尼系数与EG指数;二是基于微观企业地理位置信息形成的产业…

商城数据库88张表结构完整示意图41~50(十二)

四十一: 四十二: 四十三: 四十四: 四十五: 四十六: 四十七: 四十八: 四十九: 五十:

【GameFi】链游 | Seraph | 区块链上的动作角色扮演 NFT 装备收集和掠夺游戏

官网下载 新赛季公告:https://www.seraph.game/#/news/357 开始时间:2024年4月19日 11:00 (UTC8) discard会有人发送一些激活码,或者有一些活动,只需要填表格关注账号,参与了就会将激活码发到你的邮箱 …

AI不只是技术,更是一种思维方式

一、AI思维 1.个人:提升自己的综合能力,成为一名懂技术、懂设计、懂硬件、懂市场运营等知识的综合型人才 2.数据:从全局视角看数据流向,挖掘数据价值 3.产品:运用新技术,发掘新需求点,探索产…

io (fscanf fprintf)

20 #include <sys/un.h>21 typedef struct stu22 {23 char name[16];24 int age;25 double score;26 }stu;27 int main(int argc, const char *argv[])28 {29 /* 有如下结构体30 31 申请该结构体数组&#xff0c;容量为5&#xff0c;初始化5个学生的信息32 …

奶爸预备 |《伯克毕生发展心理学.从0岁到青少年》 / (美) 劳拉·E. 伯克著——读书笔记

目录 引出第一篇 人的发展理论与研究第1章 历史、理论和研究方法 第二篇 发展的基础第2章 生物基础与环境基础第3章 孕期发育、分娩及新生儿 第三篇 婴儿期和学步期&#xff1a;0~2岁第4章 婴儿期和学步期的身体发育第5章 婴儿期和学步期的认知发展第6章 婴儿期和学步期的情绪与…

Oracle对空值(NULL)的 聚合函数 排序

除count之外sum、avg、max、min都为null&#xff0c;count为0 Null 不支持加减乘除&#xff0c;大小比较&#xff0c;相等比较&#xff0c;否则只能为空&#xff1b;只能用‘is [not] null’来进行判断&#xff1b; Max等聚合函数会自动“过滤null” null排序默认最大&#xf…

ADS基础教程9-理想模型和厂商模型实现及对比

目录 一、概要二、厂商库使用1.新建cell2.调用厂商库中元器件3.元器件替换及参数选择4.完成参数选择5.导入子图 三、仿真实现注意事项 一、概要 本文将介绍在ADS中调用厂商提供的库&#xff0c;来进行原理图仿真&#xff0c;并实现与ADS系统提供的理想元器件之间的比较。 二、…

KAN:Kolmogorov–Arnold Networks

KAN: Kolmogorov–Arnold Networks 论文链接&#xff1a;https://arxiv.org/abs/2404.19756 代码链接&#xff1a;https://github.com/KindXiaoming/pykan 项目链接&#xff1a;https://kindxiaoming.github.io/pykan/intro.html Abstract 受Kolmogorov-Arnold表示定理的启…

【微服务】网关(详细知识以及登录验证)

微服务网关 网关网关路由快速入门路由属性 路由断言网关登录校验自定义过滤器实现登录校验网关传递用户OpenFeign传递用户 网关 网络的关口&#xff0c;负责请求的路由&#xff0c;转发&#xff0c;身份校验 当我们把一个单体项目分成多个微服务并部署在多台服务器中&#xff…

MyBatis 使用 XML 文件映射

在MyBatis中 我们可以使用各种注解来配置我们Mapper 类中的方法 我们为什么要使用XML文件呢&#xff1f; 如果我们是一条非常长的SQL 语句 使用 注解配置的话&#xff0c; 会非常不利于阅读 如下 所以&#xff0c;就需要使用到一个XML文件来对SQL语句进行映射&#xff0c;那么 …

234234235

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

OSEK任务管理

1 前言 RTOS通过任务&#xff08;task&#xff09;来组织应用层程序框架&#xff08;framework&#xff09;&#xff0c;支持任务的并发和同步执行&#xff08;concurrent and asynchronous execution of tasks&#xff09;&#xff0c;并通过调度器&#xff08;scheduler&…

【Axure高保真原型】动态伸缩信息架构图

今天和大家分享动态伸缩信息架构图的原型模板&#xff0c;我们可以通过点击加减按钮来展开或收起子内容&#xff0c;具体效果可以点击下方视频观看或者打开预览地址来体验 【原型效果】 【Axure高保真原型】动态伸缩信息架构图 【原型预览含下载地址】 https://axhub.im/ax9/…

项目管理-项目采购管理2/2

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 本文承接 项目采购管理第二部分&#xff0c;详细讲解项目合同管理。 项目采购管理过程--重点&#xff1a; ①ITTO 输入&#xff0c;输出…