socket实现视频通话-WebRTC

最近喜欢研究视频流,所以思考了双向通信socket,接下来我们就一起来看看本地如何实现双向视频通讯的功能吧~

客户端获取视频流

首先思考如何获取视频流呢?

其实跟录音的功能差不多,都是查询电脑上是否有媒体设备,如果有录音和录像的设备,首先就需要授权,然后将视频流通过socket传输给服务端。

获取媒体设备

const stream = await navigator.mediaDevices.getUserMedia({audio: true,video: true
})

因为是打视频的功能,那A客户端本身也希望看到A的摄像头,所以我们直接将其赋值给一个video标签,就能看到图像了.

<p>这是A页面</p><div class="local-stream-page"><video autoplay controls muted id="elA"></video><button onclick="onStart()">打视频给B页面</button>
</div><script>try {const stream = await navigator.mediaDevices.getUserMedia({audio: true,video: true})if (videoElA) {videoElA.srcObject = stream // 在 video 标签上播放媒体流}peerInit(stream) // 初始化连接} catch (error) {console.log('error:', error)}</script>

然后就是重要部分了,我们需要用到WebRTC的API

RTCPeerConnection

RTCPeerConnection是WebRTC API中的一个对象,用于建立和管理两个或多个用户之间的实时通信。它允许通过互联网进行音频和视频通话,以及共享数据流。

RTCPeerConnection对象提供了一系列的方法和事件,用于配置、管理和控制媒体流的传输。它支持使用不同的技术,如ICE(Interactive Connectivity Establishment)和STUN(Session Traversal Utilities for NAT)来解决网络地址转换(NAT)问题,以便在防火墙后面的不同设备之间建立连接。

使用RTCPeerConnection对象,您可以创建媒体流并将其发送到其他设备,也可以接收来自其他设备的媒体流。它还支持使用SDP(Session Description Protocol)描述媒体会话的配置,以及通过ICE和STUN协议协商和转发媒体数据包的路由。

const peerInit = stream => {// 1. 创建连接实例peerA = new RTCPeerConnection()// 2. 添加视频流轨道stream.getTracks().forEach(track => {peerA.addTrack(track, stream)})// peerA 端peerA.onicecandidate = event => {if (event.candidate) {socketA.send(JSON.stringify({ type: 'candid', data: event.candidate })) // socketA发送数据}}// 检测连接状态peerA.onconnectionstatechange = event => {if (peerA.connectionState === 'connected') {console.log('对等连接成功!')}}// 互换sdp认证transSDP()
}

到这里我们发送数据部分就是这样子啦,但是还不行,因为两者通视频,还需要SDP认证,什么是SDP认证呢?

SDP(Session Description Protocol)认证是指通过在SDP协议中添加特定的信息来验证身份或其他属性的方法。SDP协议是一种用于描述多媒体会话的信息协议,它包含了音频、视频等媒体的编码格式、分辨率、网络地址等信息,用于在通话双方之间建立和维护媒体连接。

在SDP认证中,通过在SDP协议中添加特定的信息,如用户名、会议ID等,双方可以互相验证身份。此外,还可以通过在SDP协议中包含数字签名或加密信息等技术来增强认证的安全性。

SDP认证通常用于多媒体通信、视频会议等应用场景中,以确保通信的安全性和可信度。在SDP认证中,需要使用相应的协议或算法来验证SDP信息的来源和完整性,以确认身份或其他属性的合法性。

互换SDP认证

// peerA 端
const transSDP = async () => {let offer = await peerA.createOffer()// 向 peerB 传输 offersocketA.send(JSON.stringify({ type: 'offer', data: offer }))// 接收 peerB 传来的 answersocketA.onmessage = async evt => {let reader = new FileReader()reader.readAsText(evt.data, 'utf-8')reader.onload = async function() {let { type, data } = JSON.parse(reader.result)console.log(JSON.parse(reader.result), 111)if (type == 'answer') {await peerA.setLocalDescription(offer)await peerA.setRemoteDescription(data)}}}
}

这就是A客户端的全部代码啦~

放心,全部代码文章末尾会给到.

node服务端socket传输

接下来我们来看看服务端是如何处理的.对了,这里必须说一下,两个socket之间的通信,必须要靠服务端管理,所以这就是为什么一定要学node的原因😂

const WebSocket = require('ws');// 创建一个 WebSocket 服务器,监听 8080 端口
const wss = new WebSocket.Server({ port: 8000 });// 当有客户端连接时,创建一个 WebSocket 并将其添加到客户端列表中
wss.on('connection', function connection(ws) {console.log('Client connected');// 当客户端发送消息时,将消息发送给所有客户端ws.on('message', function incoming(message) {console.log('Received message:', message.toString('utf8')); // 接受的对象,客户端发送的是字符串,Buffer// 将消息发送给所有客户端wss.clients.forEach(function each(client) {if (client !== ws && client.readyState === WebSocket.OPEN) {client.send(message); // 客户端接受的是blob格式数据}});});// 当客户端断开连接时,将其从客户端列表中删除ws.on('close', function close() {console.log('Client disconnected');});
});

服务端用到了ws依赖, 如何区分两个不同的socket客户端, 特别是在同一个服务器下,同一个端口,不同的页面下,我发现必须要给两个socket一个唯一的标识才能做到,所以这期就先出功能,后面再继续补一下ws的源码学习.

不过这里要区分清楚,这是将当前的client客户端发送给处理自己以外的,其他所以socket客户端,发送消息这里,就是一对多的关系哦.

客户端接受视频流

服务端处理完了,就进行下一个客户端如何接受视频流

刚刚的sdp认证,肯定不止止A页面的事情,都说了是认证,那肯定通信双方需要知晓.

这里有一个顺序问题

1.首先是A页面创建offer—createOffer

2.然后是B页面设置远程描述—setRemoteDescription

3.B页面生成发送到A页面的answer—createAnswer

4.B页面设置本地描述—setLocalDescription

5.A页面设置本地描述—setLocalDescription(传参是A页面的offer)

6.A页面设备远程描述–setRemoteDescription(传参是B页面的answer)

只要这上面6步都正常执行,B页面才能接收到A页面的视频流和音频流

const transSDP = async () => {// 1. 创建 offerlet offer = await peerA.createOffer()await peerB.setRemoteDescription(offer)// 2. 创建 answerlet answer = await peerB.createAnswer()await peerB.setLocalDescription(answer)// 3. 发送端设置 SDPawait peerA.setLocalDescription(offer)await peerA.setRemoteDescription(answer)
}

加上socket之后就是这样

不过既然是socket了,所以数据上要做转换处理,接收到的是blob数据

// B接收A的消息
// peerB 端,接收 peerA 传来的 offer
socketB.onmessage = evt => {// console.log(evt.data)handleBlobToText(evt.data)
}const handleBlobToText = (blob) => {let reader = new FileReader()reader.readAsText(blob, 'utf-8') // 接收到的是blob数据,先转成文本reader.onload = async function() {console.log(reader.result)let { type, data } = JSON.parse(reader.result) // 文本转对象console.log(JSON.parse(reader.result))if (type == 'offer') {await peerB.setRemoteDescription(data)console.log('2.然后是B页面设置远程描述', new Date().getTime())let answer = await peerB.createAnswer()console.log('3.B页面生成发送到A页面的answer', new Date().getTime())await peerB.setLocalDescription(answer)console.log('4.B页面设置本地描述', new Date().getTime())// 向 peerA 传输 answersocketB.send(JSON.stringify({ type: 'answer', data: answer }))}if (type == 'candid') {peerB.addIceCandidate(data)}}
}socketB.onerror = function() {console.log('WebSocket error. Ready state:', socketB.readyState);
};

根据时间戳,就能发现这六步的顺序.

将接收到的视频流渲染到B页面的video标签中,这就能接受的A页面的视频流了.

const socketB = new WebSocket('ws://localhost:8000');const peerB = new RTCPeerConnection()
const videoElB = document.getElementById('elB')// 监听数据传来
peerB.ontrack = async event => {const [remoteStream] = event.streamsvideoElB.srcObject = remoteStream
}

效果

这就是两个页面视频通讯的结果如下:

在这里插入图片描述

全部源码已经上传在GitHub上啦~

https://github.com/0522skylar/webRTC-video

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

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

相关文章

工业相机如何实现实时和本地Raw格式图像和Bitmap格式图像的保存和相互转换(C#代码,UI界面版)

工业相机如何实现实时和本地Raw图像和Bitmap图像的保存和相互转换&#xff08;C#代码&#xff0c;UI界面版&#xff09; 工业相机图像格式工业相机实现Raw图像和Bitmap图像的保存和转换的技术背景在相机SDK中获取图像转换图像的代码分析工业相机回调函数里保存Bitmap图像数据工…

太阳系三体模拟器

介绍 《三体》是刘慈欣创作的长篇科幻小说&#xff0c;文中提到的三体问题比较复杂和无解。 该项目代码就是利用 Python 来模拟三体的运行&#xff0c;此项目代码完全共享&#xff0c;欢迎下载。 我们可以自己通过调整天体的初始坐标、质量和矢量速度等等参数来自定义各种场景…

解决Golang WriteHeader设置后,Content-Type失效的问题

场景 最近笔者在研究web框架过程中&#xff0c;发现了一个响应类型的问题&#xff0c;困扰许久&#xff0c;原因就是设置了响应状态码后&#xff0c;然后设置响应类型为application/json。在实际请求后&#xff0c;响应类型变成了text/plain; charsetutf-8格式。 问题解决&…

RPA 培训前期准备指南——安装Uibot(RPA设计软件)

目录 一、电脑环境要求 二、所需的软件 2.1 办公软件 Office 版本&#xff1a; 2.2 Chrome 谷歌浏览器&#xff1a; 2.3 钉钉软件&#xff1a;官网下载 2.4 UiBot Creator 机器人流程自动化软件及 Chrome 扩展 三、UiBot Creator 安装与注册说明 &#xff08;1&#xf…

公网ip数字证书申请流程

SSL数字证书是一种安全通信协议&#xff0c;它使用加密技术来保护在网络上传输的数据的机密性和完整性。当一个网站使用SSL数字证书时&#xff0c;它可以在浏览器和网站之间建立一条加密通道&#xff0c;确保用户在网站上输入的信息&#xff0c;如用户名、密码、信用卡信息等&a…

Halcon顶帽运算top_hat

Halcon顶帽运算 顶帽运算的原理是用原始的二值图像减去开运算的图像。开运算的目的是“移除”某些局部像素&#xff0c;如去毛边、断开相邻的边缘等。而顶帽运算正是用来提取这些被移除的部分。图&#xff08;a&#xff09;为从二值图像中提取出的亮的区域&#xff0c;可见有一…

什么?想学Go语言的人有福了?

最新好多小伙伴想学习云原生&#xff0c;但是都不知道怎么去学&#xff0c;我建议他们先去学一下G语言&#xff0c;作为云原生基础语言。 但是告诉我说找不到Go语言学习资料&#xff1f;&#xff1f;&#xff1f; 好嘛&#xff0c;我连夜整理了几十本Go语言学习的pdf资料&…

【C#】知识点实践序列之Lock简单解决并发引起数据重复问题

欢迎来到《小5讲堂之知识点实践序列》文章&#xff0c;大家好&#xff0c;我是全栈小5。 这是2024年第3篇文章&#xff0c;此篇文章是C#知识点实践序列文章&#xff0c;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 本篇在Lock锁定代码块基…

.NET DevOps 接入指南 | 1. GitLab 安装

引言 容器、DevOps和微服务被称为驱动云原生快速发展的三架马车。而DevOps是其中非常重要的一环&#xff0c;DevOps 是由Developers&#xff08;Dev&#xff09;和Operations&#xff08;Ops&#xff09;两个单词简称组成&#xff0c;中文直译就是“开发运维一体化”。 DevOps…

SpringBoot+SSM项目实战 苍穹外卖(08) 用户下单支付 内网穿透cpolar软件 绕开微信支付实现

继续上一节的内容&#xff0c;本节导入地址簿功能代码&#xff0c;并实现用户下单和订单支付功能。 这里写目录标题 导入地址簿功能代码接口分析代码实现 用户下单接口分析代码实现 订单支付内网穿透——cpolar软件代码导入绕开微信支付实现 导入地址簿功能代码 地址簿&#x…

12.31信号位宽转换(整数,非整数),时钟分频(奇数,偶数,任意小数,占空比),自动售货机(1,2),游戏机

非整数倍数据位宽转换8to12 所谓非整数倍&#xff0c;就是利用一个cnt去周期性决定寄存器里怎么输出&#xff0c;这个cnt的值&#xff0c;是最小公倍数 寄存器就正常的寄存&#xff0c;怎么输入怎么寄存 timescale 1ns/1nsmodule width_8to12(input clk , input…

STM32L4

STM32L4系列超低功耗微控制器 意法半导体通过构建新型芯片架构实现了同类产品中最佳的超低功耗及性能&#xff0c;这得益于应用设计上的高度灵活性。 STM32L4系列可以根据微处理器运行时不同的应用需求来适时调整电压从而实现功耗的动态平衡。 该系列包含不同的产品线&#…