WebRTC(Web Real-Time Communication)是一种支持浏览器之间实时音视频通信的技术。本章将介绍WebRTC的基本概念和使用方法。
const webrtcOverview = {
definition: 'WebRTC是一种支持浏览器之间点对点实时通信的开放标准',
features: [
'点对点通信,无需服务器中转',
'支持音视频实时传输',
'支持任意数据传输',
'跨平台支持',
'内置加密(DTLS/SRTP)'
],
components: {
getUserMedia: '获取本地媒体流(摄像头、麦克风)',
RTCPeerConnection: '建立点对点连接',
RTCDataChannel: '数据通道,传输任意数据'
}
};
async function getMediaDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
devices.forEach(device => {
console.log(`${device.kind}: ${device.label} (${device.deviceId})`);
});
return {
cameras: devices.filter(d => d.kind === 'videoinput'),
microphones: devices.filter(d => d.kind === 'audioinput'),
speakers: devices.filter(d => d.kind === 'audiooutput')
};
}
async function checkPermissions() {
const permissions = await navigator.permissions.query({ name: 'camera' });
console.log('摄像头权限:', permissions.state);
const micPermissions = await navigator.permissions.query({ name: 'microphone' });
console.log('麦克风权限:', micPermissions.state);
}
async function getLocalStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
const videoElement = document.querySelector('video');
videoElement.srcObject = stream;
videoElement.play();
return stream;
} catch (error) {
console.error('获取媒体流失败:', error);
throw error;
}
}
async function getSpecificDevice(deviceId) {
const stream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: deviceId } },
audio: true
});
return stream;
}
const constraints = {
video: {
width: { ideal: 1920 },
height: { ideal: 1080 },
frameRate: { ideal: 30 },
facingMode: 'user'
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
function stopStream(stream) {
stream.getTracks().forEach(track => {
track.stop();
});
}
function toggleVideo(stream, enabled) {
stream.getVideoTracks().forEach(track => {
track.enabled = enabled;
});
}
function toggleAudio(stream, enabled) {
stream.getAudioTracks().forEach(track => {
track.enabled = enabled;
});
}
function getTrackSettings(track) {
return track.getSettings();
}
function applyConstraints(track, constraints) {
return track.applyConstraints(constraints);
}
const configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'username',
credential: 'password'
}
]
};
const peerConnection = new RTCPeerConnection(configuration);
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log('ICE候选:', event.candidate);
sendToRemote({ candidate: event.candidate });
}
};
peerConnection.onconnectionstatechange = () => {
console.log('连接状态:', peerConnection.connectionState);
};
peerConnection.oniceconnectionstatechange = () => {
console.log('ICE连接状态:', peerConnection.iceConnectionState);
};
peerConnection.ontrack = (event) => {
console.log('收到远程轨道:', event.track);
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
async function addLocalTracks(peerConnection, stream) {
stream.getTracks().forEach(track => {
peerConnection.addTrack(track, stream);
});
}
const localStream = await getLocalStream();
addLocalTracks(peerConnection, localStream);
async function createOffer() {
const offer = await peerConnection.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await peerConnection.setLocalDescription(offer);
console.log('本地描述已设置:', offer);
return offer;
}
async function handleOffer(offer) {
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
return answer;
}
async function handleAnswer(answer) {
await peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
}
async function addIceCandidate(candidate) {
try {
await peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
} catch (error) {
console.error('添加ICE候选失败:', error);
}
}
class SignalingClient {
constructor(serverUrl) {
this.ws = new WebSocket(serverUrl);
this.handlers = new Map();
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};
}
on(type, handler) {
this.handlers.set(type, handler);
}
handleMessage(message) {
const handler = this.handlers.get(message.type);
if (handler) {
handler(message.data);
}
}
send(type, data) {
this.ws.send(JSON.stringify({ type, data }));
}
join(roomId) {
this.send('join', { roomId });
}
sendOffer(offer) {
this.send('offer', offer);
}
sendAnswer(answer) {
this.send('answer', answer);
}
sendCandidate(candidate) {
this.send('candidate', candidate);
}
}
class WebRTCClient {
constructor(signalingUrl) {
this.signaling = new SignalingClient(signalingUrl);
this.peerConnection = null;
this.localStream = null;
this.configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
};
this.setupSignaling();
}
setupSignaling() {
this.signaling.on('offer', async (offer) => {
await this.handleOffer(offer);
});
this.signaling.on('answer', async (answer) => {
await this.handleAnswer(answer);
});
this.signaling.on('candidate', async (candidate) => {
await this.addIceCandidate(candidate);
});
}
async startLocalMedia() {
this.localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = this.localStream;
return this.localStream;
}
createPeerConnection() {
this.peerConnection = new RTCPeerConnection(this.configuration);
this.localStream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, this.localStream);
});
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.signaling.sendCandidate(event.candidate);
}
};
this.peerConnection.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
}
async createCall(roomId) {
this.createPeerConnection();
const offer = await this.peerConnection.createOffer();
await this.peerConnection.setLocalDescription(offer);
this.signaling.join(roomId);
this.signaling.sendOffer(offer);
}
async handleOffer(offer) {
this.createPeerConnection();
await this.peerConnection.setRemoteDescription(
new RTCSessionDescription(offer)
);
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
this.signaling.sendAnswer(answer);
}
async handleAnswer(answer) {
await this.peerConnection.setRemoteDescription(
new RTCSessionDescription(answer)
);
}
async addIceCandidate(candidate) {
await this.peerConnection.addIceCandidate(
new RTCIceCandidate(candidate)
);
}
hangup() {
if (this.peerConnection) {
this.peerConnection.close();
this.peerConnection = null;
}
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop());
this.localStream = null;
}
}
}
const dataChannel = peerConnection.createDataChannel('chat', {
ordered: true
});
dataChannel.onopen = () => {
console.log('数据通道已打开');
};
dataChannel.onclose = () => {
console.log('数据通道已关闭');
};
dataChannel.onmessage = (event) => {
console.log('收到消息:', event.data);
};
dataChannel.onerror = (error) => {
console.error('数据通道错误:', error);
};
function sendMessage(message) {
if (dataChannel.readyState === 'open') {
dataChannel.send(JSON.stringify(message));
}
}
peerConnection.ondatachannel = (event) => {
const receiveChannel = event.channel;
receiveChannel.onmessage = (event) => {
console.log('收到消息:', event.data);
};
};
📞 WebRTC开发建议
- STUN/TURN服务器:生产环境需要配置TURN服务器解决NAT穿透问题
- 信令服务器:可以使用WebSocket、Socket.io等实现
- 错误处理:处理各种连接失败和媒体获取失败的情况
- 安全性:WebRTC强制使用加密,确保数据安全
🔗 连接流程
- 获取本地媒体流
- 创建RTCPeerConnection
- 添加本地轨道
- 创建并发送Offer/Answer
- 交换ICE候选
- 建立连接
下一章将探讨 [Geolocation API](file:///e:/db-w.cn/md_data/javascript/78_Geolocation API.md),学习如何在Web应用中获取地理位置信息。