WebRTC 快速入门:从零开始建立实时音视频通信

677 阅读4分钟

以前开发前端项目的时候参与过多个涉及实时通信的项目。其中一个项目特别让我印象深刻,那就是使用 WebRTC 实现视频通话功能。WebRTC是一个开源项目,允许网页浏览器进行实时通信,无需任何插件或中间件。通过 WebRTC,我们可以实现音视频通话、屏幕共享、文件传输等多种实时通信功能。

在我初次接触 WebRTC 时,虽然文档和示例很多,但实际操作中还是遇到了不少挑战。从环境搭建、权限处理到信令服务器的实现,每一步都需要仔细研究和调试。然而,一旦掌握了基本的流程和技巧,WebRTC 的强大功能就会展现在眼前。


媒体捕获

首先,我们需要获取用户的媒体设备(通常是摄像头和麦克风)的视频和音频流。

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,开发者可以用更少的代码快速搭建视频聊天应用。