1V1音视频实时互动直播系统

李超老师的项目

先肯定分为两个两个端,一个是服务器端一个是客户端。客户端用于UI界面的显示,服务器端用于处理客户端发来的消息。

我们先搭建stun和turn服务器

首先介绍一下什么是stun协议,

它是用来干什么的?

stun协议存在的目的就是进行NAT穿越。stun是典型的客户端、服务器模式。客户端发送请求,服务器进行响应。

那么是什么NAT穿越呢?

首先我们先了解一下为什么要进行NAT穿越。下面举个例子,在两个浏览器之间进行实时的音视频互动,对于底层来说,这就是两个端点之间进行高效的网络传输。

为了解决音视频网络传输的问题,webrtc引入了一些网络传输协议。

1.NAT:那么此时我们介绍NAT,简单理解为将内网的地址转换为公网的地址,内网地址无法通讯,通过NAT转换为公网之后,才有通信的可能。

2.说到这里那么顺便介绍一下stun,这个stun充当的是中介的作用,在NAT的基础上,交换两个公网的信息,使得;两个公网之间可以建立连接。

3.turn,stun是有一定几率是不成功的,因此turn会在云端架设一个服务器,在p2p连接不成功的情况下,保证音视频的互通,它就相当于一个中转站。

4.ICE, 罗列所有通信可能性,选择最优解。

NAT又分为四种类型:

完全锥型

地址限制锥型

端口限制锥型

对称型

根据图中所示很好理解,caller与信令服务器连接,同时callee也跟信令服务器连接,第二个callee也跟信令服务器连接;caller向信令服务器发出join请求,信令服务器响应返回joined信息,第一个callee同理,但是第二个callee发送完join之后服务器发现成员已满,因此返回一个full信息,该callee不能join。然后caller和callee进行媒体协商,协商成功之后进行媒体流的数据传输。之后callee主动发出leave请求,服务器响应跟caller发出bye信息并返回callee leaved信息。

我们来看一下服务器端的代码。主要就是处理客户端发来的信令消息。也就是以上的流程转换为代码。

io.sockets.on('connection', (socket)=> {socket.on('message', (room, data)=>{socket.to(room).emit('message',room, data);});socket.on('join', (room)=>{socket.join(room);var myRoom = io.sockets.adapter.rooms[room]; var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;logger.debug('the user number of room is: ' + users);if(users < USERCOUNT){socket.emit('joined', room, socket.id); //发给自己if(users > 1){socket.to(room).emit('otherjoin', room, socket.id);//发给除自己之外的房间内的所有人}}else{socket.leave(room);	socket.emit('full', room, socket.id);}//socket.emit('joined', room, socket.id); //发给自己//socket.broadcast.emit('joined', room, socket.id); //发给除自己之外的这个节点上的所有人//io.in(room).emit('joined', room, socket.id); //发给房间内的所有人});socket.on('leave', (room)=>{var myRoom = io.sockets.adapter.rooms[room]; var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;logger.debug('the user number of room is: ' + (users-1));//socket.emit('leaved', room, socket.id);//socket.broadcast.emit('leaved', room, socket.id);socket.to(room).emit('bye', room, socket.id);socket.emit('leaved', room, socket.id);//io.in(room).emit('leaved', room, socket.id);});});

iceRestart是一个很好的方案,能够帮助我们重新选择数据传输的线路

下面了解一下状态机是什么这点蛮重要

下面介绍一下客户端代码编写的流程图

首先写函数start

start函数用于采集音视频数据

采集成功则与信令服务器连接,并注册信令函数

注册以上这些消息的处理函数

如果是joined则设置状态为joined,创建PC并绑定媒体流。

如果是otherjoin,则判断自身的状态是否为joined_unbind,如果是则需要重新创建PC并绑定媒体流并将状态设置为joined_conn,如果一开始状态为joined则直接将状态转换为joined_conn,接着开始媒体协商。

如果是full则状态设置为full并关闭PC,并关闭本地媒体流

客户端的实现需要注意的几点是

1.网络连接要在音视频数据获取到之后,否则可能导致绑定音视频流失败

2.当一端退出房间之后,另一端的PeerConnection要关闭重建,否则与新用户互通时媒体协商会失败。

3.所有的处理流程为异步处理

这里要了解一下什么是异步处理:

异步事件处理:要等待收到一个消息或事件后,才能做下一步的操作

同步处理:做完一步,直接做下一步

接下来我们介绍一下客户端的代码

首先先了解一个api

其中较为关键的是iceServers

第二个api

根据流程写以下代码:

首先start函数获取音视频数据

function start(){if(!navigator.mediaDevices ||!navigator.mediaDevices.getUserMedia){console.error('the getUserMedia is not supported!');return;}else {var constraints;if( shareDeskBox.checked && shareDesk()){constraints = {video: false,audio:  {echoCancellation: true,noiseSuppression: true,autoGainControl: true}}}else{constraints = {video: true,audio:  {echoCancellation: true,noiseSuppression: true,autoGainControl: true}}}navigator.mediaDevices.getUserMedia(constraints).then(getMediaStream).catch(handleError);}}

与信令服务器连接,注册处理函数

function conn(){socket = io.connect();socket.on('joined', (roomid, id) => {console.log('receive joined message!', roomid, id);state = 'joined'//如果是多人的话,第一个人不该在这里创建peerConnection//都等到收到一个otherjoin时再创建//所以,在这个消息里应该带当前房间的用户数////create conn and bind media trackcreatePeerConnection();bindTracks();btnConn.disabled = true;btnLeave.disabled = false;console.log('receive joined message, state=', state);});socket.on('otherjoin', (roomid) => {console.log('receive joined message:', roomid, state);//如果是多人的话,每上来一个人都要创建一个新的 peerConnection//if(state === 'joined_unbind'){createPeerConnection();bindTracks();}state = 'joined_conn';call();console.log('receive other_join message, state=', state);});socket.on('full', (roomid, id) => {console.log('receive full message', roomid, id);hangup();closeLocalMedia();state = 'leaved';console.log('receive full message, state=', state);alert('the room is full!');});socket.on('leaved', (roomid, id) => {console.log('receive leaved message', roomid, id);state='leaved'socket.disconnect();console.log('receive leaved message, state=', state);btnConn.disabled = false;btnLeave.disabled = true;});socket.on('bye', (room, id) => {console.log('receive bye message', roomid, id);//state = 'created';//当是多人通话时,应该带上当前房间的用户数//如果当前房间用户不小于 2, 则不用修改状态//并且,关闭的应该是对应用户的peerconnection//在客户端应该维护一张peerconnection表,它是//一个key:value的格式,key=userid, value=peerconnectionstate = 'joined_unbind';hangup();offer.value = '';answer.value = '';console.log('receive bye message, state=', state);});socket.on('disconnect', (socket) => {console.log('receive disconnect message!', roomid);if(!(state === 'leaved')){hangup();closeLocalMedia();}state = 'leaved';});socket.on('message', (roomid, data) => {console.log('receive message!', roomid, data);if(data === null || data === undefined){console.error('the message is invalid!');return;	}if(data.hasOwnProperty('type') && data.type === 'offer') {offer.value = data.sdp;pc.setRemoteDescription(new RTCSessionDescription(data));//create answerpc.createAnswer().then(getAnswer).catch(handleAnswerError);}else if(data.hasOwnProperty('type') && data.type == 'answer'){answer.value = data.sdp;pc.setRemoteDescription(new RTCSessionDescription(data));}else if (data.hasOwnProperty('type') && data.type === 'candidate'){var candidate = new RTCIceCandidate({sdpMLineIndex: data.label,candidate: data.candidate});pc.addIceCandidate(candidate);	}else{console.log('the message is invalid!', data);}});roomid = getQueryVariable('room');socket.emit('join', roomid);return true;
}

媒体协商call函数

function call(){if(state === 'joined_conn'){var offerOptions = {offerToRecieveAudio: 1,offerToRecieveVideo: 1}pc.createOffer(offerOptions).then(getOffer).catch(handleOfferError);}
}
function getOffer(desc){pc.setLocalDescription(desc);offer.value = desc.sdp;offerdesc = desc;//send offer sdpsendMessage(roomid, offerdesc);	}
function getAnswer(desc){pc.setLocalDescription(desc);answer.value = desc.sdp;//send answer sdpsendMessage(roomid, desc);
}

每个端都维护一个自己的peerconnection

var pcConfig = {'iceServers': [{'urls': 'turn:stun.al.learningrtc.cn:3478','credential': "mypasswd",'username': "garrylea"}]
};
function createPeerConnection(){//如果是多人的话,在这里要创建一个新的连接.//新创建好的要放到一个map表中。//key=userid, value=peerconnectionconsole.log('create RTCPeerConnection!');if(!pc){pc = new RTCPeerConnection(pcConfig);pc.onicecandidate = (e)=>{if(e.candidate) {sendMessage(roomid, {type: 'candidate',label:event.candidate.sdpMLineIndex, id:event.candidate.sdpMid, candidate: event.candidate.candidate});}else{console.log('this is the end candidate');}}pc.ontrack = getRemoteStream;}else {console.warning('the pc have be created!');}return;	
}

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

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

相关文章

Nginx网站服务【☆☆☆】

市面上常用Linux的web服务器&#xff1a;apache、Nginx。 apache与nginx的区别&#xff1f; 最核心的区别在于NGINX采用异步非阻塞机制&#xff0c;多个连接可以对应一个进程&#xff1b;apache采用的是同步阻塞多进程/线程模型&#xff0c;一个连接对应一个进程。apache美国…

MacOS 安装C语言版TensorFlow

文章目录 安装C语言版TensorFlow解压归档环境变量c_api.hC语言示例 安装C语言版TensorFlow 官方文档&#xff1a;https://tensorflow.google.cn/install/lang_c?hlzh-cnTensorFlow 提供了一个 C API&#xff0c;该 API 可用于为其他语言构建绑定。该 API 在 c_api.h 中定义&a…

从C到C++,C++入门(2)

在C入门篇&#xff08;1&#xff09;中&#xff0c;博主为大家简单介绍了什么是C&#xff0c;以及C中的关键字&#xff0c;命名空间&#xff0c;输入与输出和缺省参数的相关知识。今天就让我们继续一起学习C的基础知识点吧&#xff01;&#xff01; 1.函数重载 1.1函数重载的概…

经典的泡泡龙游戏源码免费下载

源码介绍 HTML5泡泡龙冒险小游戏是一款休闲网页游戏&#xff0c;游戏玩法是玩家从下方中央的弹珠发射台射出彩珠&#xff0c;多于3个同色珠相连则会消失。 源码下载 经典的泡泡龙游戏源码免费下载

C# WPF入门学习主线篇(六)—— TextBox常见属性和事件

欢迎回到C# WPF入门学习系列的第六篇。在前面的文章中&#xff0c;我们探讨了按钮&#xff08;Button&#xff09;的事件处理。今天&#xff0c;我们将继续学习另一个常用的WPF控件——TextBox。本文将介绍 TextBox 的常见属性和事件&#xff0c;并通过示例代码展示如何在实际应…

企业办公网安全管控挑战与解决方案

在数字化浪潮的推动下&#xff0c;企业正经历前所未有的变革。然而&#xff0c;随之而来的是一系列复杂的网络安全风险和挑战。我们的网络边界不再清晰&#xff0c;各种设备轻松接入企业网络&#xff0c;这不仅带来了便利&#xff0c;也极大地增加了安全风险。想象一下&#xf…

JavaScript 学习笔记 总结

回顾&#xff1a; Web页面标准 页面结构&#xff1a;HTML4、HTML5页面外观和布局&#xff1a;CSS页面行为&#xff1a;JavaScript强调三者的分离前后端分离开发模式 响应式设计Bootstrap框架入门 Bootstrap总结 基础 下载和使用基础样式&#xff1a;文本样式、图片样式、表格…

笔记 | 软件工程03:软件过程和软件开发方法

软件过程 1 何为软件过程模型 1.1 软件开发的特点 1.2 软件过程 1.3 软件过程模型 1.3.1 软件过程模型产生的背景 软件工程产生之前的软件开发——作坊式的个人创作&#xff1a;聚焦于编写代码&#xff1b;依靠个体技能&#xff0c;缺乏合作&#xff1b;关注时空利用&#x…

Threejs加载DOM+CSS到场景中,实现3D场景展示2D平面的效果

1. 前言 本篇文章主要实现了将DOM元素转换为Threejs可以使用的数据结构,使用CSS2DRenderer渲染器渲染这些DOMCSS的平面,使其可以作为一个物体添加到Threejs场景里 如下效果图: 2. 实现步骤 首先创建一个ThreejsVueVite的项目,作为本次的demo项目下载Threejs第三方库 yarn…

C++候捷stl-视频笔记4

一个万用的hash function 哈希函数的形式&#xff0c;一种是一般函数(右边)&#xff0c;一种是成员函数(左边)&#xff0c;类的对象将成为函数对象 具体做法例子。直接把属性的所有hash值加起来&#xff0c;会在hashtable中会产生很多的碰撞&#xff0c;放在同一个bucket中的元…

游戏UI设计秘诀:专家总结与实际案例解析!

随着游戏产业的不断发展&#xff0c;游戏UI界面设计变得越来越重要。一个好的游戏UI界面设计可以让玩家更容易理解游戏规则&#xff0c;提高游戏的可玩性&#xff0c;增加游戏的吸引力。在本文中&#xff0c;我们将讨论游戏UI界面设计的重要性和一些常见的设计原则。 1. 游戏U…

前端框架中的虚拟DOM和实际DOM之间的关系

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介前端框架中的虚拟DOM和实际DOM之间的关系1. 实际DOM&#xff08;Real DOM&#xff09;1.1 定义1.2 特点 2. 虚拟DOM&#xff08;Virtual DOM&#xff09;2.1 定义2.2 特点 3. 虚拟DOM的工作流程3.1 创建虚拟DOM3.2 比较虚拟DOM&…

Django redirect()函数实现页面重定向

1&#xff0c;通过路由反向解析进行重定向 1.1 添加视图函数 myshop/app2/views.py from django.http import HttpResponse from django.shortcuts import render from django.urls import reverse def index(request):return HttpResponse("app2 的index")# 反向…

PVE安装虚拟主机

本文记录PVE安装其他虚拟主机的步骤&#xff0c;以安装win-server为例。裸机安装PVE则不是本文主题。 准备文件 获取Windows系统镜像 win server镜像可以从官网获取普通Windows镜像可从MSDN获取此外&#xff0c;安装Windows系统还需要从PVE下载特殊驱动 获取Windows必要驱动 …

康谋技术 | 自动驾驶:揭秘高精度时间同步技术(二)

在自动驾驶中&#xff0c;对车辆外界环境进行感知需要用到很多传感器的数据&#xff08;Lidar&#xff0c;Camera&#xff0c;GPS/IMU&#xff09;&#xff0c;如果计算中心接收到的各传感器消息时间不统一&#xff0c;则会造成例如障碍物识别不准等问题。 为了对各类传感器进…

提高工作效率的神器有哪些?

在这个人工智能技术蓬勃发展的时代&#xff0c;很多AI工具极大地提升我们的工作效率&#xff0c;比如很多人在用的ChatGPT、Kimi&#xff0c;它们几乎无所不能&#xff0c;小编在日常工作也用它们&#xff0c;建议想抓住AI风口&#xff0c;或者是想用这些工具提升自己的朋友去学…

windows10 安装子linux系统(WSL安装方式)

在 windows 10 平台采用了WSL安装方式安装linux子系统 1 查找自己想要安装的linux子系统 wsl --list --online 2 在线安装 个人用Debian比较多&#xff0c;这里选择Debian&#xff0c;如下图&#xff1a; wsl --install -d Debian 安装完成&#xff0c;如下&#xff1a; 相关…

【JS重点知识03】定时器—间歇函数

一&#xff1a;间歇函数的应用场景 网页倒计时是需要每个一段时间需自动执行一段代码&#xff0c;而不需要手动去触发&#xff1b;间歇函数刚好满足了这一要求&#xff1b; 二&#xff1a;间歇函数的使用 1 开启定时器 语法规范&#xff1a; 1 setInterval(匿名函数,时间)…

Java版电商平台B2B2C:多商家直播商城系统特性解析

B2B2C平台&#xff0c;立足于传统电商领域&#xff0c;同时引入了创新的商业模式。该平台不仅支持商家入驻和平台自营&#xff0c;还积极构建了一个全新的市场环境&#xff0c;旨在为各行各业及互联网创业者提供更多收益机会。 该平台以消费者需求为中心&#xff0c;帮助企业构…

PyQt5+SQLlite3基于邮箱验证的登陆注册找回系统

本期教程投稿一篇实用性的基于邮箱登陆注册找回于一体的系统&#xff0c;在日常的开发和软件应用中非常常见&#xff0c;并且也使用了逻辑与界面分离的写法&#xff0c;那这个文章将详细的为大家介绍整个流程&#xff0c;但是细节的话还需要大家自己去完善&#xff0c;也欢迎大…