WebRTC 开发环境搭建 - Web端

一、开发环境准备

一、开发环境准备

  1. 浏览器选择

WebRTC 开发推荐使用最新版本的浏览器:

  • 推荐 Chrome(版本 90 以上)或 Firefox(版本 85 以上)
  • Edge(基于 Chromium 的新版本)和 Safari 14+ 也可支持

浏览器兼容性验证:

  • 访问 https://caniuse.com/webrtc 查看当前浏览器支持情况
  • 现代浏览器均已原生支持 WebRTC 标准

实验性功能配置(Chrome 为例):

  1. 地址栏输入 chrome://flags

  2. 搜索 "WebRTC" 相关选项:

    • Enable WebRTC STUN origin header
    • Enable WebRTC-H264 with OpenH264 FFmpeg
  3. 启用后需重启浏览器生效

  4. 开发工具配置

代码编辑器推荐:

  • VS Code(推荐安装 WebRTC 相关插件)
    • WebRTC Code Snippets
    • JavaScript (ES6) code snippets
  • WebStorm(内置 WebRTC 智能提示)

调试工具:

  • Chrome DevTools
    • 使用 Console 调试信令
    • Network 面板查看 ICE 候选交换
    • 使用 WebRTC 专用调试面板(chrome://webrtc-internals)
  • Firefox 开发者工具
    • WebRTC 调试面板

本地开发服务器:

  • Node.js + Express
    const express = require('express');
    const app = express();
    app.use(express.static('public'));
    app.listen(3000);
    

  • Python SimpleHTTPServer
    python -m http.server 8000
    

  1. HTTPS 环境配置

开发环境 HTTPS 解决方案:

  1. 自签名证书

    • 使用 OpenSSL 生成:
      openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
      

    • Node.js 配置示例:
      const https = require('https');
      const fs = require('fs');
      const options = {
        key: fs.readFileSync('key.pem'),
        cert: fs.readFileSync('cert.pem')
      };
      https.createServer(options, app).listen(443);
      

  2. 内网穿透工具

    • ngrok 基础使用:
      ngrok http 80
      

    • 付费版本支持自定义域名和固定子域名
  3. Let's Encrypt

    • 适用于生产环境
    • 使用 certbot 工具自动获取证书:
      sudo apt-get install certbot
      sudo certbot certonly --standalone -d yourdomain.com
      

注意事项:

  • 本地开发时(localhost/127.0.0.1)可豁免 HTTPS 要求
  • 跨设备测试必须使用 HTTPS
  • iOS 设备强制要求 HTTPS 环境

二、基本项目搭建步骤

  1. 创建项目结构:

    webrtc-demo/
    ├── index.html
    ├── script.js
    ├── style.css
    └── server.js
    

  2. HTML 基础结构:

    <!DOCTYPE html>
    <html>
    <head>
      <title>WebRTC Demo</title>
      <link rel="stylesheet" href="style.css">
    </head>
    <body>
      <video id="localVideo" autoplay muted></video>
      <video id="remoteVideo" autoplay></video>
      <button id="startButton">开始</button>
      <button id="callButton">呼叫</button>
      <button id="hangupButton">挂断</button>
      <script src="script.js"></script>
    </body>
    </html>
    

三、核心 API 使用

  1. 获取媒体设备:

    // 获取用户媒体流
    async function getLocalStream() {
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          audio: true,
          video: true
        });
        document.getElementById('localVideo').srcObject = stream;
        return stream;
      } catch (err) {
        console.error('获取媒体设备失败:', err);
      }
    }
    

  2. RTCPeerConnection 创建:

    // 创建 PeerConnection
    const configuration = {
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        // 可添加 TURN 服务器配置
      ]
    };
    
    const peerConnection = new RTCPeerConnection(configuration);
    

  3. 信令交换实现:

    // 创建 offer
    async function createOffer() {
      try {
        const offer = await peerConnection.createOffer();
        await peerConnection.setLocalDescription(offer);
        // 通过信令服务器发送 offer 给对端
        sendSignalingMessage({ type: 'offer', sdp: offer.sdp });
      } catch (err) {
        console.error('创建 offer 失败:', err);
      }
    }
    
    // 处理 answer
    async function handleAnswer(answer) {
      try {
        await peerConnection.setRemoteDescription(
          new RTCSessionDescription(answer)
        );
      } catch (err) {
        console.error('处理 answer 失败:', err);
      }
    }
    

四、完整示例代码

  1. 信令服务器实现(使用 Socket.io):

    // server.js
    const express = require('express');
    const app = express();
    const server = require('http').createServer(app);
    const io = require('socket.io')(server);
    
    app.use(express.static('public'));
    
    io.on('connection', socket => {
      socket.on('message', message => {
        socket.broadcast.emit('message', message);
      });
    });
    
    server.listen(3000, () => {
      console.log('信令服务器运行在 http://localhost:3000');
    });
    

  2. 客户端完整实现:

    // script.js
    document.addEventListener('DOMContentLoaded', () => {
      const startButton = document.getElementById('startButton');
      const callButton = document.getElementById('callButton');
      const hangupButton = document.getElementById('hangupButton');
      let localStream;
      let peerConnection;
      const socket = io();
      
      startButton.addEventListener('click', start);
      callButton.addEventListener('click', call);
      hangupButton.addEventListener('click', hangup);
      
      async function start() {
        try {
          localStream = await getLocalStream();
          startButton.disabled = true;
          callButton.disabled = false;
        } catch (err) {
          console.error('启动失败:', err);
        }
      }
      
      async function call() {
        callButton.disabled = true;
        hangupButton.disabled = false;
        
        // 创建 RTCPeerConnection
        peerConnection = new RTCPeerConnection(configuration);
        
        // 添加本地流
        localStream.getTracks().forEach(track => {
          peerConnection.addTrack(track, localStream);
        });
        
        // 监听远程流
        peerConnection.ontrack = event => {
          document.getElementById('remoteVideo').srcObject = event.streams[0];
        };
        
        // 监听ICE候选项
        peerConnection.onicecandidate = event => {
          if (event.candidate) {
            socket.emit('message', {
              type: 'candidate',
              candidate: event.candidate
            });
          }
        };
        
        // 监听信令消息
        socket.on('message', async message => {
          if (message.type === 'offer') {
            await peerConnection.setRemoteDescription(
              new RTCSessionDescription(message)
            );
            const answer = await peerConnection.createAnswer();
            await peerConnection.setLocalDescription(answer);
            socket.emit('message', answer);
          } else if (message.type === 'answer') {
            await peerConnection.setRemoteDescription(
              new RTCSessionDescription(message)
            );
          } else if (message.type === 'candidate') {
            try {
              await peerConnection.addIceCandidate(
                new RTCIceCandidate(message.candidate)
              );
            } catch (err) {
              console.error('添加ICE候选项失败:', err);
            }
          }
        });
        
        // 创建offer
        const offer = await peerConnection.createOffer();
        await peerConnection.setLocalDescription(offer);
        socket.emit('message', offer);
      }
      
      function hangup() {
        peerConnection.close();
        peerConnection = null;
        hangupButton.disabled = true;
        callButton.disabled = false;
        document.getElementById('remoteVideo').srcObject = null;
      }
    });
    

五、调试与优化建议

  1. 常见问题排查:

    • 检查浏览器控制台是否有错误
    • 使用 chrome://webrtc-internals 进行详细调试
    • 确保媒体权限已授权
  2. 性能优化:

    • 根据网络状况调整视频分辨率
    // 调整视频约束
    const constraints = {
      video: {
        width: { ideal: 1280 },
        height: { ideal: 720 },
        frameRate: { ideal: 30, max: 60 }
      },
      audio: true
    };
    

    • 实现带宽自适应
    • 使用 VP8 或 H.264 编解码器
  3. 安全考虑:

    • 实现身份验证机制
    • 使用 TURN 服务器绕过对称 NAT
    • 保护信令服务器通信安全

六、进阶功能扩展

  1. 屏幕共享:

    async function shareScreen() {
      try {
        const stream = await navigator.mediaDevices.getDisplayMedia({
          video: true,
          audio: true
        });
        document.getElementById('localVideo').srcObject = stream;
      } catch (err) {
        console.error('屏幕共享失败:', err);
      }
    }
    

  2. 多人会议:

    • 使用 Mesh 架构(每个对等点相互连接)
    • 或使用 SFU 架构(通过媒体服务器转发)
  3. 数据通道:

    // 创建数据通道
    const dataChannel = peerConnection.createDataChannel('chat');
    
    dataChannel.onopen = () => {
      console.log('数据通道已打开');
      dataChannel.send('Hello!');
    };
    
    dataChannel.onmessage = event => {
      console.log('收到消息:', event.data);
    };
    

  4. 录制功能:

    let mediaRecorder;
    let recordedChunks = [];
    
    function startRecording() {
      recordedChunks = [];
      const stream = document.getElementById('localVideo').srcObject;
      mediaRecorder = new MediaRecorder(stream);
      
      mediaRecorder.ondataavailable = event => {
        if (event.data.size > 0) {
          recordedChunks.push(event.data);
        }
      };
      
      mediaRecorder.start();
    }
    
    function stopRecording() {
      mediaRecorder.stop();
      mediaRecorder.onstop = () => {
        const blob = new Blob(recordedChunks, { type: 'video/webm' });
        const url = URL.createObjectURL(blob);
        // 提供下载链接
      };
    }
    

Logo

邀请您加入社区

更多推荐