WebRTC 从入门到入土

212 阅读2分钟

简介

WebRTC是一个强大的框架,不仅提供了低延时的音视频实时通信,而且支持浏览器(Firefox、Chrome、safari)以及 iOS、Android 原生系统

重要概念

MediaStream

流媒体对象,音/视频数据的一种封装格式,挂载到 video 或 audio 标签上播放

RTCPeerConnection

会话控制,网络和媒体信息收发,作用类似 http 对象

SDP

主要用于两个会话实体之间的媒体协商,作用类似 http 中的配置项

结合下图类比会更容易理解: image.png

代码实例

创建数据源

localStream 作为发送端本地预览画面:

// 创建数据源
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
// 显示数据源,localVideo 是 html 中的 video 标签
localVideo.srcObject = localStream;

创建发送数据实例

用于发送步骤一中创建的数据:

// 本地实例
const pc1 = new RTCPeerConnection();
// 对端实例
const pc2 = new RTCPeerConnection();

配置实例

做这一步的目的是为了交换两端的信息:icecandidate 和 SDP

icecandidate:包含通信协议(TCP/UDP)和通信IPSTUNTURN协议中描述网络信息的格式规范,解决双方网络链接问题;
SDP:浏览器能力,包括不限于音视频编码格式,带宽,流控策略等;解决前置思考中,双方能力不匹配问题,通过交换双方 SDP 浏览器会自动选择双方都支持的视频编码格式。
// 告诉对端,本端地址
pc1.addEventListener('icecandidate', async (e) => {
// 发送给对端
// 对端添加本端地址
if (e.candidate) {
await pc2.addIceCandidate(e.candidate);
}
});
 
 
pc2.addEventListener('icecandidate', async (e) => {
// 发送给本端
// 本端添加对端地址
if (e.candidate) {
await pc1.addIceCandidate(e.candidate);
}
});
 
 
// 创建本端SDP,告诉本端浏览器支持哪些能力
const offer = await pc1.createOffer();
pc1.setLocalDescription(offer);
// 创建远端SDP,告诉远端浏览器支持哪些能力
const answer = await pc2.createAnswer();
pc2.setLocalDescription(answer);
// 。。。。发送远端SDP给本端
// 接收远端sdp,告诉远端浏览器支持哪些能力
pc1.setRemoteDescription(answer);
// 接收客户端sdp,告诉远端浏览器支持哪些能力
pc2.setRemoteDescription(offer);

发送数据

localStream.getTracks().forEach(
(track) => pc1.addTrack(track, localStream)
);

完整精简版Typescript代码

const pc1 = new RTCPeerConnection();
pc1.addEventListener('icecandidate', async (e) => {
if (e.candidate) {
await pc2.addIceCandidate(e.candidate);
}
});
pc1.addEventListener('iceconnectionstatechange', (e) => {
console.log('pc1: iceconnectionstatechange', e);
});
 
 
const pc2 = new RTCPeerConnection();
pc2.addEventListener('icecandidate', async (e) => {
if (e.candidate) {
await pc1.addIceCandidate(e.candidate);
}
});
 
 
pc2.addEventListener('iceconnectionstatechange', (e) => {
console.log('pc2: iceconnectionstatechange', e);
});
 
 
pc2.addEventListener('track', (e) => {
if (e.streams.length > 0) {
remoteVideo.srcObject = e.streams[0];
}
});
 
 
const remoteVideo = document.querySelector('#remoteVideo') as HTMLVideoElement;
const localVideo = document.querySelector('#localVideo') as HTMLVideoElement;
 
async function pushStream(answer: RTCSessionDescriptionInit) {
pc1.setRemoteDescription(answer);
}
 
async function pullStream(offer: RTCSessionDescriptionInit): Promise<void> {
pc2.setRemoteDescription(offer);
const answer = await pc2.createAnswer();
pc2.setLocalDescription(answer);
console.warn('answer', answer);
pushStream(answer);
}
 
 
window.onload = async () => {
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
 
 
localVideo.srcObject = localStream;
localStream.getTracks().forEach((track) => pc1.addTrack(track, localStream));
 
 
const offer = await pc1.createOffer();
pc1.setLocalDescription(offer);
console.warn('pc1 offer', offer);
pullStream(offer);
};