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

news2024/9/24 7:17:03

本篇博客主要讲解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://新添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);
    }
}

answercandidate信令处理函数逻辑与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);
    }
}

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

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

相关文章

图像处理

图像处理 导入图片 导入io模块,读取文件所在位置,将生成的图像数据赋给变量img,显示图像 from skimage import ioimgio.imread(D:\工坊\图像处理\十个勤天2.png)io.imshow(img) 运行结果: 将图片进行灰度处理 from skimage i…

透明屏幕的透明度如何?能否实现真正的透明效果?

透明屏幕的透明度是一个关键的特性,其实际表现会因技术、材料和设计等因素而有所不同。目前,透明屏幕技术已经取得了显著的进步,尤其是在OLED(有机发光二极管)领域。 OLED透明屏幕由于其自发光的技术优势,能…

如何修改图片大小?调整图片大小的几个方法介绍

当我们在不同的应用场景中使用图片的时候,常常会需要去调整图片尺寸来适应不同的要求,还有图片体积大小也会有要求,这时候就需要用到我们今天分享的这款图片在线处理工具了,不管是图片改大小或者图片压缩它都能快速解决&#xff0…

Hadamard Product(点乘)、Matmul Product(矩阵相乘)和Concat Operation(拼接操作)在神经网络中的应用

Hadamard乘积(Hadamard Product),矩阵乘法(Matmul Product)和拼接操作(Concatenation Operation)在神经网络中的使用情况如下: Hadamard Product点乘、内积: Hadamard乘…

websevere服务器从零搭建到上线(二)|Linux上的五种IO模型

文章目录 阻塞 blocking非阻塞 non-blockingIO复用 IO multiplexing信号驱动 signal-driven异步 asynchronous拓展知识 看过上篇文章英国基本能理解本文五张图的内容websevere服务器从零搭建到上线(一)|阻塞、非阻塞、同步、异步 本文要能够在…

使用Python编写自动化测试代码规范整理

大家好,我们平时在写自动化测试脚本或者性能测试脚本时,需要注意代码规范,提高代码的可读性与维护性,之前给大家分享过pycharm的两个插件,大家可以参考:Pycharm代码规范与代码格式化插件安装 本文中主要从自…

突破销量瓶颈:亚马逊,速卖通,国际站销量提升实战技巧

1、精心选品:选品是亚马逊销售的第一步,也是至关重要的一步。卖家应该进行市场调研,了解消费者的需求和喜好,选择有市场潜力的产品。要注意产品的差异化,避免与竞争对手的产品过于相似。 2、优化产品详情页&#xff1…

BERT模型的网络结构解析 运行案例分析

整体结构 第一部分:嵌入层第二部分:编码层第三部分:输出层 对于一个m分类任务,输入n个词作为一次数据,单个批次输入t个数据,在BERT模型的不同部分,数据的形状信息如下: 注1&#x…

(ARM-Linux) ORACLE JDK 22 的下载安装及环境变量的配置

目录 获取JDK 安装JDK 配置JAVA环境变量 其他补充:JDK 22的新特征 1. 语法 2. 库 3. 性能 4. 工具 在今年的3月份,ORACLE 更新了的JDK 发行版 JDK 22,作为了一位ORACLE Primavera系列产品的研究者,其实对JDK的迭代完全不感…

信创基础软件之操作系统

操作系统概述 操作系统是计算机系统软硬件资源的纽带。操作系统是连接硬件和数据库、中间件、应用软件的纽带,是承载各种信息设备和软件应用的重要基础软件。操作系统控制和管理整个计算机系统的硬件、软件资源,组织和调度计算机工作和资源,…

linux部署java1.8(java17)

两种方式: 方式一 1.输入查找命令: yum -y list java*2.输入安装命令: yum install -y java-1.8.0-openjdk.x86_643.测试是否已经安装: java -version方式二: 点击链接进入官网:https://www.oracle.com/…

五一反向旅游,景区“AI+视频监控”将持续助力旅游业发展

一、建设背景 每年五一劳动节出去旅游都是人挤人状态,这导致景区的体验感极差。今年“五一反向旅游”的话题冲上了热搜,好多人选择了五一之后再出去旅游,避开拥挤的人群,这个时候景区的监管力度和感知能力就更要跟上去&#xff0…

前端 | 经典代办框实现(喵喵大王ver)

文章目录 📚实现效果📚模块实现解析🐇html🐇css🐇javascript 📚实现效果 输入框输入,点击Submit按钮提交,下方显示 设置事项上限6条(按照条数设置限制,默认每…

go-mysql-transfer 同步数据到es

同步数据需要注意的事项 前提条件 1 要同步的mysql 表必须包含主键 2 mysql binlog 必须是row 模式 3 不支持程序运行过程中修改表结构 4 要赋予连接mysql 账号的权限 reload, replication super 权限 如果是root 权限则不需要 安装 go-mysql-transfer ​ git clone…

MySQL从入门到高级 --- 5.DQL基本操作

文章目录 第五章:5.基本操作 - DQL5.1 运算符逻辑运算符位运算符算术运算符 5.2 条件查询5.3 排序查询5.4聚合查询5.4.1 聚合查询-NULL值处理 5.5 分组查询 - group by5.5.1 条件筛选 - having5.6 分页查询 - limit5.7 INSERT INTO SELECT 语句5.8 练习5.9 正则表达…

服务器端优化-Redis内存划分和内存配置

6、服务器端优化-Redis内存划分和内存配置 当Redis内存不足时,可能导致Key频繁被删除、响应时间变长、QPS不稳定等问题。当内存使用率达到90%以上时就需要我们警惕,并快速定位到内存占用的原因。 有关碎片问题分析 Redis底层分配并不是这个key有多大&…

单体服务-微服务-分布式 [三兄弟的区别]

大家好,我是晓星航。今天为大家带来的是 单体服务-微服务-分布式 [三兄弟的区别] 相关的讲解!😊 文章目录 1.单体服务1.1单体服务架构的基本介绍1.2单体服务的优缺点 2.微服务2.1微服务架构的基本介绍2.2微服务架构的优缺点 3.分布式4.三兄弟…

OpenCV4.9去运动模糊滤镜(68)

返回:OpenCV系列文章目录(持续更新中......) 上一篇:OpenCV4.9失焦去模糊滤镜(67) 下一篇:OpenCV如何通过梯度结构张量进行各向异性图像分割(69) 目标 在本教程中,您将学习: 运动模糊图像的 PSF 是多少如何恢复运动…

SQLI-labs-第十五关和第十六关

目录 知识点: 第十五关 1、判断注入点 2、判断当前数据库 3、判断表名 4、判断字段名 5、爆值 第十六关 1、判断注入点 知识点: POST方式的时间盲注 对应的函数利用,可参考SQLI-labs-第九关和第十关_sqllab第十关-CSDN博客 第十五…

Pycharm的安装配置

pycharm的安装配置 1.pycharm的安装 pycharm进入官网下载即可:官网。 想下载老版本的点这里。 pycharm分为专业版和社区版,专业版只能免费30天过了就需要💴,社区版是开源的,功能没有专业版全面但是也差不多够用。 …