目录
媒体捕获
首先,我们需要获取用户的媒体设备(通常是摄像头和麦克风)的视频和音频流。
async function getMediaStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
return stream;
} catch (error) {
console.error('Error accessing media devices', error);
}
}
创建RTCPeerConnection
接下来,创建RTCPeerConnection实例,这是WebRTC的核心,用于管理连接的生命周期和媒体流的传输。
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] // 使用Google STUN服务器
});
// 当远程视频流被添加时,将其显示在页面上
pc.ontrack = event => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
// 处理ICE候选
pc.onicecandidate = event => {
if (event.candidate) {
// 通过信令发送ICE候选给对方
sendToServer({ type: 'candidate', candidate: event.candidate });
}
};
添加本地媒体流
将获取到的本地媒体流添加到RTCPeerConnection实例中。
const localStream = await getMediaStream();
document.getElementById('localVideo').srcObject = localStream;
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
信令交互
WebRTC应用需要自定义信令机制来交换SDP信息和ICE候选。这里以一个简化的示例说明如何处理offer和answer的交换。
发起呼叫方(Offerer)
async function createOffer() {
try {
await pc.setLocalDescription(await pc.createOffer());
// 通过信令发送offer给对方
sendToServer({ type: 'offer', sdp: pc.localDescription });
} catch (error) {
console.error('Error creating offer', error);
}
}
接收呼叫方(Answerer)
function handleOffer(offer) {
pc.setRemoteDescription(new RTCSessionDescription(offer));
pc.createAnswer()
.then(answer => {
pc.setLocalDescription(answer);
// 通过信令发送answer给对方
sendToServer({ type: 'answer', sdp: answer });
})
.catch(error => console.error('Error creating answer', error));
}
function handleCandidate(candidate) {
pc.addIceCandidate(new RTCIceCandidate(candidate));
}
信令服务的实现
这里假设使用WebSocket作为信令通道,实际应用中需要根据具体服务端逻辑实现。
let socket; // 假设WebSocket已连接
function sendToServer(message) {
socket.send(JSON.stringify(message));
}
socket.addEventListener('message', event => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'offer':
handleOffer(data.sdp);
break;
case 'answer':
pc.setRemoteDescription(new RTCSessionDescription(data.sdp));
break;
case 'candidate':
handleCandidate(data.candidate);
break;
default:
console.log('Unknown message', data);
}
});
错误处理
在WebRTC应用中,全面的错误处理机制是必不可少的,以确保用户能够获得良好的体验并及时了解问题所在。以下是一些关键环节的错误处理示例:
pc.onnegotiationneeded = async e => {
try {
await pc.setLocalDescription(await pc.createOffer());
sendToServer({ type: 'offer', sdp: pc.localDescription });
} catch (err) {
console.error("Error during negotiation", err);
}
};
pc.oniceconnectionstatechange = e => {
switch(pc.iceConnectionState) {
case "disconnected":
case "failed":
console.log("Connection failed or disconnected");
// 可能需要重连逻辑或提示用户
break;
case "closed":
console.log("Connection closed");
break;
default:
// 其他状态,如checking, connected等
break;
}
};
// 示例:处理getUserMedia的错误
async function getMediaStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
return stream;
} catch (error) {
console.error('Error accessing media devices', error);
alert("无法访问您的媒体设备,请检查权限设置。");
}
}
连接状态监测
WebRTC提供了多种事件监听器来监测连接状态,如oniceconnectionstatechange、onnegotiationneeded、onsignalingstatechange等。通过监听这些事件,开发者可以实时响应连接的变化,比如在连接断开时尝试重连或向用户展示错误信息。
媒体流的停止和重连
停止本地媒体流可以通过调用MediaStreamTrack.stop()方法实现,而重连则通常涉及到重新创建RTCPeerConnection、重新获取媒体流和重新交换SDP信息。
// 停止本地媒体流
localStream.getTracks().forEach(track => track.stop());
// 重连逻辑可能需要重新初始化RTCPeerConnection、获取媒体流、交换offer-answer等
async function reconnect() {
pc.close(); // 关闭现有连接
pc = new RTCPeerConnection(config);
// 重复之前的设置过程,如添加track、设置事件监听器等
}
使用成熟库简化开发
-
Adapter.js
:这是一个官方推荐的库,用于解决浏览器之间的WebRTC API兼容性问题。只需在项目中引入Adapter.js,它会自动修补API差异,使得开发者可以使用统一的API编写代码。 -
SimpleWebRTC
:这是一个更高级的库,提供了更高层次的抽象,简化了WebRTC的很多复杂操作,如自动处理信令、媒体流管理、房间管理等。使用SimpleWebRTC,开发者可以用更少的代码快速搭建视频聊天应用。