WebRTC中,连接是核心内容,通过RTCPeerConnection接口可以将本地流MediaStream发送至远端,同时也可以远端媒体发送至本地,从而建立对等连接。本地与远端之间进行媒体协商及网络协商成功后,将本地媒体流发送到远端的过程称为连接建立。
七、建立连接
1、RTCConnection
RTCPeerConnection简称PC对象,即连接对象,本地为Local对象,远端为Remote对象,主要有以下API,如表所示:
2、连接建立
术语:
Peer-A : 简写为 A ,即本地
Peer-B : 简写为 B 即远端
RTCPeerConnection :简写为PC
RTCPeerConnection连接A端即为PC-A,连接B端即为PC-B
连接过程:
(1)A 获取媒体流MediaStream
代码如下:
navigator.mediaDevices.getUserMedia
(2) A(生成PC-A对象
RTCPeerConnection接口代表一个由本地计算机至远端的WebRTC连接。该接口提供了创建、保持、监控、关闭连接的方法,代码如下:
//可选参数包括ICE服务器等
pc=new RTCPeerConnection([RTCConfiguration dictionary])
ICE服务器的设置如下所示:
//设置ICE Server,使用Google服务器
let configuration = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] };
(3) A将流加入PC-A
代码如下:
//该方法已经不推荐使用,推荐使用addTrack方法
pc.addStream(stream);
//加入轨道
/循环迭代本地流的所有轨道
localStream.getTracks().forEach((track) => {
/把音视频轨道添加到连接里去
PC.addTrack(track, stream);
});
(4)A创建提议Offer
PC接口的CreateOffer()方法启动创建一个SDP offer ,目的是启动一个新的WebRTC去连接远程端点。代码如下:
offer=await pc.createOffer()
(5) 设置本地描述
A 创建提议Offer成功后,会生成RTCSessionDescription对象,然后调用PC-A的setLocalDescription方法设置本地描述,代码如下:
await pc.setLocalDescription(desc)
(6) A将Offer发送给B
A将Offer信息发送给B, 通常需要架设一个信令服务器转发Offer数据。WebSocket是一种常规的实现方式。
(7)B生成PC-B对象
B端也要生成一个RTCPeerConnection对象,用来进行应答Answer,发送流,接收等处理。
(8)B 设置远端描述
B收到信令服务器转发来的Offer信息后,调用PC-B的setRemoteDescription()方法设置远端描述。
(9) B生成应答Answer
PC-B的createAnswer()方法会生成一个应答SDP信息,应答Answer和提议Offer是成对出现。
(10) B 设置本地描述
B创建应答成功后,会生成RTCSessionDescription对象,然后调用PC-B的setLocalDescription()方法设置本地描述信息。
(11) B把Answer发送给 A
B将应答信息通过信令服务器转发给A。
(12)A设置远端描述
A收到信令服务器转发的应答信息后,调用PC-A的setRemoteDescription()方法设置远端描述。
(13)交换ICE候选地址
在建立连接的过程中,会回调onicecandidate事件,传递ICE候选地址,将其发送至另一端,并通过另一端的addiceCandidate()方法设置对方的候选地址。大致代码如下:
pc.addEventLisener('icecandidate',this.onIceCandidate);
onIceCandidate=async(event)=>{
if(event.candidate){
//发送Candidate至另一端
let iceinfo=event.candidate;
}
}
//另一端接收到Candidate
pc.addIceCandidate(new RTCIceCandidate);
理想情况下,现在已经建立连接了
(14)交换与使用媒体流
当一方执行addTrack后,另一方的PC会触 发track事件回调,通过事件参数可以获取对方轨道里的媒体流,代码如下:
pc.addEventListener('track',this.gotRemoteSteam);
//获取到远端媒体流
gotRemoteStream=(e)=>{
//远端媒体流
remoteVideo.srcObject=e.streams[0];
}
示例完整****代码
完整代码如下:
import React from "react";
import { Button } from "antd";
//本地视频
let localVideo;
//远端视频
let remoteVideo;
//本地流
let localStream;
//PeerA连接对象
let peerConnA;
//PeerB连接对象
let peerConnB;
/**
* 连接建立示例
*/
class PeerConnection extends React.Component {
componentDidMount() {
//初始化本地视频对象
localVideo = this.refs['localVideo'];
//初始化远端视频对象
remoteVideo = this.refs['remoteVideo'];
//获取本地视频尺寸
localVideo.addEventListener('loadedmetadata', () => {
console.log(`本地视频尺寸为: videoWidth: ${localVideo.videoWidth}px, videoHeight: ${localVideo.videoHeight}px`);
});
//获取远端视频尺寸
remoteVideo.addEventListener('loadedmetadata', () => {
console.log(`远端视频尺寸为: videoWidth: ${remoteVideo.videoWidth}px, videoHeight: ${remoteVideo.videoHeight}px`);
});
//监听远端视频尺寸大小变化
remoteVideo.addEventListener('resize', () => {
console.log(`远端视频尺寸为: ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);
});
}
//开始
start = async () => {
console.log('开始获取本地媒体流');
try {
//获取音视频流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
console.log('获取本地媒体流成功');
//本地视频获取流
localVideo.srcObject = stream;
localStream = stream;
} catch (e) {
console.log("getUserMedia错误:" + e);
}
}
//呼叫
call = async () => {
console.log('开始呼叫...');
//视频轨道
const videoTracks = localStream.getVideoTracks();
//音频轨道
const audioTracks = localStream.getAudioTracks();
//判断视频轨道是否有值
if (videoTracks.length > 0) {
//输出摄像头的名称
console.log(`使用的视频设备为: ${videoTracks[0].label}`);
}
//判断音频轨道是否有值
if (audioTracks.length > 0) {
//输出麦克风的名称
console.log(`使用的音频设备为: ${audioTracks[0].label}`);
}
//设置ICE Server,使用Google服务器
let configuration = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] };
//创建RTCPeerConnection对象
peerConnA = new RTCPeerConnection(configuration);
console.log('创建本地PeerConnection成功:peerConnA');
//监听返回的Candidate信息
peerConnA.addEventListener('icecandidate', this.onIceCandidateA);
//创建RTCPeerConnection对象
peerConnB = new RTCPeerConnection(configuration);
console.log('创建本地PeerConnection成功:peerConnB');
//监听返回的Candidate信息
peerConnB.addEventListener('icecandidate', this.onIceCandidateB);
//监听ICE状态变化
peerConnA.addEventListener('iceconnectionstatechange', this.onIceStateChangeA);
//监听ICE状态变化
peerConnB.addEventListener('iceconnectionstatechange', this.onIceStateChangeB);
//监听track事件,可以获取到远端视频流
peerConnB.addEventListener('track', this.gotRemoteStream);
//peerConnA.addStream(localStream);
//循环迭代本地流的所有轨道
localStream.getTracks().forEach((track) => {
//把音视频轨道添加到连接里去
peerConnA.addTrack(track, localStream);
});
console.log('将本地流添加到peerConnA里');
try {
console.log('peerConnA创建提议Offer开始');
//创建提议Offer
const offer = await peerConnA.createOffer();
//创建Offer成功
await this.onCreateOfferSuccess(offer);
} catch (e) {
//创建Offer失败
this.onCreateSessionDescriptionError(e);
}
}
//创建会话描述错误
onCreateSessionDescriptionError = (error) => {
console.log(`创建会话描述SD错误: ${error.toString()}`);
}
//创建提议Offer成功
onCreateOfferSuccess = async (desc) => {
//peerConnA创建Offer返回的SDP信息
console.log(`peerConnA创建Offer返回的SDP信息\n${desc.sdp}`);
console.log('设置peerConnA的本地描述start');
try {
//设置peerConnA的本地描述
await peerConnA.setLocalDescription(desc);
this.onSetLocalSuccess(peerConnA);
} catch (e) {
this.onSetSessionDescriptionError();
}
console.log('peerConnB开始设置远端描述');
try {
//设置peerConnB的远端描述
await peerConnB.setRemoteDescription(desc);
this.onSetRemoteSuccess(peerConnB);
} catch (e) {
//创建会话描述错误
this.onSetSessionDescriptionError();
}
console.log('peerConnB开始创建应答Answer');
try {
//创建应答Answer
const answer = await peerConnB.createAnswer();
//创建应答成功
await this.onCreateAnswerSuccess(answer);
} catch (e) {
//创建会话描述错误
this.onCreateSessionDescriptionError(e);
}
}
//设置本地描述完成
onSetLocalSuccess = (pc) => {
console.log(`${this.getName(pc)}设置本地描述完成:setLocalDescription`);
}
//设置远端描述完成
onSetRemoteSuccess = (pc) => {
console.log(`${this.getName(pc)}设置远端描述完成:setRemoteDescription`);
}
//设置描述SD错误
onSetSessionDescriptionError = (error) => {
console.log(`设置描述SD错误: ${error.toString()}`);
}
getName = (pc) => {
return (pc === peerConnA) ? 'peerConnA' : 'peerConnB';
}
//获取到远端视频流
gotRemoteStream = (e) => {
if (remoteVideo.srcObject !== e.streams[0]) {
//取集合第一个元素
remoteVideo.srcObject = e.streams[0];
console.log('peerConnB开始接收远端流');
}
}
//创建应答成功
onCreateAnswerSuccess = async (desc) => {
//输出SDP信息
console.log(`peerConnB的应答Answer数据:\n${desc.sdp}`);
console.log('peerConnB设置本地描述开始:setLocalDescription');
try {
//设置peerConnB的本地描述信息
await peerConnB.setLocalDescription(desc);
this.onSetLocalSuccess(peerConnB);
} catch (e) {
this.onSetSessionDescriptionError(e);
}
console.log('peerConnA设置远端描述开始:setRemoteDescription');
try {
//设置peerConnA的远端描述,即peerConnB的应答信息
await peerConnA.setRemoteDescription(desc);
this.onSetRemoteSuccess(peerConnA);
} catch (e) {
this.onSetSessionDescriptionError(e);
}
}
//Candidate事件回调方法
onIceCandidateA = async (event) => {
try {
if(event.candidate){
//将会peerConnA的Candidate添加至peerConnB里
await peerConnB.addIceCandidate(event.candidate);
this.onAddIceCandidateSuccess(peerConnB);
}
} catch (e) {
this.onAddIceCandidateError(peerConnB, e);
}
console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
}
//Candidate事件回调方法
onIceCandidateB = async (event) => {
try {
if(event.candidate){
//将会peerConnB的Candidate添加至peerConnA里
await peerConnA.addIceCandidate(event.candidate);
this.onAddIceCandidateSuccess(peerConnA);
}
} catch (e) {
this.onAddIceCandidateError(peerConnA, e);
}
console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
}
//添加Candidate成功
onAddIceCandidateSuccess = (pc) => {
console.log(`${this.getName(pc)}添加IceCandidate成功`);
}
//添加Candidate失败
onAddIceCandidateError = (pc, error) => {
console.log(`${this.getName(pc)}添加IceCandidate失败: ${error.toString()}`);
}
//监听ICE状态变化事件回调方法
onIceStateChangeA = (event) => {
console.log(`peerConnA连接的ICE状态: ${peerConnA.iceConnectionState}`);
console.log('ICE状态改变事件: ', event);
}
//监听ICE状态变化事件回调方法
onIceStateChangeB = (event) => {
console.log(`peerConnB连接的ICE状态: ${peerConnB.iceConnectionState}`);
console.log('ICE状态改变事件: ', event);
}
//断开连接
hangup = () => {
console.log('结束会话');
//关闭peerConnA
peerConnA.close();
//关闭peerConnB
peerConnB.close();
//peerConnA置为空
peerConnA = null;
//peerConnB置为空
peerConnB = null;
}
render() {
return (
<div className="container">
<h1>
<span>RTCPeerConnection示例</span>
</h1>
{/* 本地视频 */}
<video ref="localVideo" playsInline autoPlay muted></video>
{/* 远端视频 */}
<video ref="remoteVideo" playsInline autoPlay></video>
<div>
<Button ref="startButton" onClick={this.start} style={{marginRight:"10px"}}>开始</Button>
<Button ref="callButton" onClick={this.call} style={{marginRight:"10px"}}>呼叫</Button>
<Button ref="hangupButton" onClick={this.hangup} style={{marginRight:"10px"}}>挂断</Button>
</div>
</div>
);
}
}
//导出组件
export default PeerConnection;
版权归原作者 ch_s_t 所有, 如有侵权,请联系我们删除。