网站首页 > 技术文章 正文
WebRTC对等通信
在之前的工作中,我们使用了区块链技术来实时共享客户端模块,本次我们用RTCPeerConnection建立了一个对等连接。
点击此处查看GitHub代码
自iOS11之后,WebRTC可以在所有浏览器中工作了,用户可以实时使用。
点击此处运行代码
我嵌入了一个HTML标签,浏览器的安全保护机制不允许这样做。
WebRTC提供了三种API:
从设备中获取音频和视频(mediastream)
建立了一个对等连接(RTCPeerConnection)
传递任意数据(RTCDataChannel)
本文使用了mediastream和PeerConnection.
无服务器的实时对等连接通信客户端
按照如下步骤在同一网页不同客户端之间建立连接
1.实例化两个RTCPeerConnection对象。
2.添加彼此为ICE candidates。
3.对第一个对象使用createoffer建立请求。
4.对两个对象设置本地/远程’描述’。
5.对第二个对象createAnswer。
6.对两个对象设置远程/本地’描述’。
7.进行直接交流。
以下是代码实现
我们从一个react Component出发, 它可以传达两个视频和三个按钮, 并且有一些默认状态可供操作。
class WebRTCPeerConnection extends React.Component {
state = {
startDisabled: false,
callDisabled: true,
hangUpDisabled: true,
servers: null,
pc1: null,
pc2: null,
localStream: null
};
localVideoRef = React.createRef();
remoteVideoRef = React.createRef();
start = () => {
// start media devices
};
call = () => {
// initiate a call
};
hangUp = () => {
// hang up connection
};
render() {
const { startDisabled, callDisabled, hangUpDisabled } = this.state;
return (
<div>
<video
ref={this.localVideoRef}
autoPlay
muted
style={{ width: "240px", height: "180px" }}
/>
<video
ref={this.remoteVideoRef}
autoPlay
style={{ width: "240px", height: "180px" }}
/>
<div>
<button onClick={this.start} disabled={startDisabled}>
Start
</button>
<button onClick={this.call} disabled={callDisabled}>
Call
</button>
<button onClick={this.hangUp} disabled={hangUpDisabled}>
Hang Up
</button>
</div>
</div>
);
}
}
我们使用三个布尔值来控制按钮: null 、 pc1 、 pc2。
第一步:开始
当点击Start开始按钮时,请求视频/音频许可并且开始一个localstream本地流。
start = () => {
this.setState({
startDisabled: true
});
navigator.mediaDevices
.getUserMedia({
audio: true,
video: true
})
.then(this.gotStream)
.catch(e => alert("getUserMedia() error:" + e.name));
};
gotStream = stream => {
this.localVideoRef.current.srcObject = stream;
this.setState({
callDisabled: false,
localStream: stream
});
};
使用this.setState来禁止开始按钮,使用navigator.getUserMedia来进入媒体.如果允许,我们在localVideo中开始数据流并且把它加入到状态中。
第二步:调用
现在你可以按Call按钮,这样就启动了两点连接,pc1和pc2,使它们可以相互交流。
1.call开始请求。
2.onCreateOfferSuccess更新pc1,pc2,并且初始化应答。
3.onCreateAnswerSuccess结束连接。
4.gotRemoteStream激发建立第二个视频。
call = () => {
this.setState({
callDisabled: true,
hangUpDisabled: false
});
let { localStream } = this.state;
let servers = null,
pc1 = new RTCPeerConnection(servers),
pc2 = new RTCPeerConnection(servers);
pc1.onicecandidate = e => this.onIceCandidate(pc1, e);
pc1.oniceconnectionstatechange = e => this.onIceStateChange(pc1, e);
pc2.onicecandidate = e => this.onIceCandidate(pc2, e);
pc2.oniceconnectionstatechange = e => this.onIceStateChange(pc2, e);
pc2.ontrack = this.gotRemoteStream;
localStream
.getTracks()
.forEach(track => pc1.addTrack(track, localStream));
pc1
.createOffer({
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
})
.then(this.onCreateOfferSuccess, error =>
console.error(
"Failed to create session description",
error.toString()
)
);
this.setState({
servers,
pc1,
pc2,
localStream
});
};
这段代码几乎是前端模板。
我们开始或禁用相应的按钮,从状态中得到本地流localStream,并且初始化servers, pc1和pc2。
两个pc*都有许多事件听众,onIceCandidate会将其连接,并且通过onIceStateChange打印故障信息,gotRemoteStream会将其添加到正确的video组件。
接着我们把localStream添加到第一个客户端,pc1会产生一个接收视频和音频的请求,当完成这些之后,就更新了组分状态。
onCreateOfferSuccess
当pc1成功请求之后,我们更新客户端中本地和远程的描述。我不确定这些描述包括什么,但是这些资料很重要。
onCreateOfferSuccess = desc => {
let { pc1, pc2 } = this.state;
pc1
.setLocalDescription(desc)
.then(
() =>
console.log("pc1 setLocalDescription complete createOffer"),
error =>
console.error(
"pc1 Failed to set session description in createOffer",
error.toString()
)
);
pc2.setRemoteDescription(desc).then(
() => {
console.log("pc2 setRemoteDescription complete createOffer");
pc2
.createAnswer()
.then(this.onCreateAnswerSuccess, error =>
console.error(
"pc2 Failed to set session description in createAnswer",
error.toString()
)
);
},
error =>
console.error(
"pc2 Failed to set session description in createOffer",
error.toString()
)
);
};
pc1更新本地描述,pc2更新远程描述,pc2同样产生了一个回复,就像这样:好,我接受你的请求,我们开始吧。
onCreateAnswerSuccess
当pc2成功恢复后,我们开始建立另一轮描述,只不过这次顺序正好相反。
onCreateAnswerSuccess = desc => {
let { pc1, pc2 } = this.state;
pc1
.setRemoteDescription(desc)
.then(
() =>
console.log(
"pc1 setRemoteDescription complete createAnswer"
),
error =>
console.error(
"pc1 Failed to set session description in onCreateAnswer",
error.toString()
)
);
pc2
.setLocalDescription(desc)
.then(
() =>
console.log(
"pc2 setLocalDescription complete createAnswer"
),
error =>
console.error(
"pc2 Failed to set session description in onCreateAnswer",
error.toString()
)
);
};
pc1建立远程描述而pc2建立本地描述。我想这就像是:从pc1视角看来,对它是本地的,对pc2是远程的,反过来对pc2也一样。
现在,我们就有了两个视频流,可以在同一个网页中相互交流。
onIceCandidate
在这一步,两个pc都说他们得到了ICE candidate。我不知道实际发生了什么,但是这给了我们一个来区分这两个客户端分别在对谁交流的机会。
onIceCandidate = (pc, event) => {
let { pc1, pc2 } = this.state;
let otherPc = pc === pc1 ? pc2 : pc1;
otherPc
.addIceCandidate(event.candidate)
.then(
() => console.log("addIceCandidate success"),
error =>
console.error(
"failed to add ICE Candidate",
error.toString()
)
);
};
我们猜测其它客户端,把它添加成为一个candidate。 如果我们的客户端多于两个,这会很有意思。
第三步:结束
结束很简单,只需要关闭两个客户端。
hangUp = () => {
let { pc1, pc2 } = this.state;
pc1.close();
pc2.close();
this.setState({
pc1: null,
pc2: null,
hangUpDisabled: true,
callDisabled: false
});
};
有趣的地方
第一部分连接,也就是两个客户端互相找到对方,成为发射信号。WebRTC spec没有提到发信号。
发信号在同一个网页两个客户端之间是很简单的,两个客户端就在内存中,只需要启动两个客户端。
但是在现实世界中,你需要这些客户端可以在不同计算机的不同浏览器中运行,如何做才能使它们能互相找到对方呢?如果有上千个客户端呢?
你需要一些通信频道,这些频道知道所有客户端在哪儿,并且说:嗨!你,连接我这里,或者是,你,离开我这里。
这对分布式去中心化区块链不起作用。
猜你喜欢
- 2025-05-27 新式炫富 “土豪摔”请了解一下!假如你摔倒,会摔出什么呢?
- 2025-05-27 如何使用WebRTC建立一个视频会议App(二)
- 2025-05-27 谈谈 WebRTC 的 SDP Unified Plan
- 2025-05-27 freeswitch的ACL规则
- 2025-05-27 WebRTC入门篇:WebRTC以及测试驱动开发(TDD)指导
- 2025-05-27 处理 WebRTC ICE重启应对NAT变化
- 2025-05-27 webrtc原理及相关api使用逻辑
- 2025-05-27 pion/webrtc v4.1.0重磅升级,AV1全支持+多编解码协商,实时通!
- 2025-05-27 街拍达人不仅要美,还要懂摄影!
- 2025-05-27 零基础快入门WebRTC:基本概念、关键技术、与WebSocket的区别等
- 08-06中等生如何学好初二数学函数篇
- 08-06C#构造函数
- 08-06初中数学:一次函数学习要点和方法
- 08-06仓颉编程语言基础-数据类型—结构类型
- 08-06C++实现委托机制
- 08-06初中VS高中三角函数:从"固定镜头"到"360°全景",数学视野升级
- 08-06一文讲透PLC中Static和Temp变量的区别
- 08-06类三剑客:一招修改所有对象!类方法与静态方法的核心区别!
- 1524℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 657℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 528℃MySQL service启动脚本浅析(r12笔记第59天)
- 494℃启用MySQL查询缓存(mysql8.0查询缓存)
- 493℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 480℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 462℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 461℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- windowsscripthost (69)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (70)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)