webrtc simple-peer socket.io-client socket.io 创建简易 p2p 音视频 demo

1,416 阅读2分钟
  1. 准备工作

    基于 webrtc 的前端 p2p 库 simple-peer

    客户端 socket.io socket.io-client

    服务端 socket.io socket.io

  2. 说明

    借鉴参考:本 demo 是基于此 例子 的变种,只是本人会分享一些自己踩过的坑。

  3. code

    server

    const express = require("express");
    const http = require("http");
    const app = express();
    const _ = require("lodash");
    const server = http.createServer(app);
    const socket = require("socket.io"); // 这里需要注意,socket.io 的版本需要和前端的 socket.io-client 大版本一致(比如是 2.x.x 则两端都必须是 2.x.x 版本),否则会导致无法通信。
    const io = socket(server, {
      cors: {
        origin: ["ws://10.173.6.13:9080/"], // 这里填写本机ip的地址
        allowedHeaders: ["my-custom-header"],
        credentials: true,
      },
    });
    
    /**
     * users: user: {socketId, userId, peerInfo}
     */
    let users = {};
    
    io.on("connection", function (socket) {
      const userId = socket.handshake.query.userId;
      const socketId = socket.id;
      users[userId] = {
        socketId: socketId,
        userId: userId,
      };
      /**
       * data: { userId: string, peerInfo: any }
       */
      socket.on("signal", (data) => {
        // 给被通知者发送消息
        console.log(users, [data.userId]);
        const socketId1 = users[data.userId].socketId;
        const informSocket = io.sockets.sockets.get(socketId1);
        if (!informSocket) {
          return;
        }
        informSocket.emit("signal", data);
      });
    });
    
    server.listen(process.env.PORT || 8000, () => {
      console.log("server is running on port 8000", server.address());
    });
    
    

    client

    // import Peer from 'peerjs';
    import Peer from 'simple-peer';
    import SocketIO from 'socket.io-client';
    
    import state from '@/state';
    
    import Basics from '../config/basics';
    
    const iceServers: { urls: string }[] = [
      { urls: 'stun:stun.l.google.com:19302' },
      { urls: 'stun:stun.ekiga.net' },
      { urls: 'stun:stun.ideasip.com' },
    ];
    
    export default class extends Basics {
      /**
       * 本地流
       */
      localStream?: TRTC.LocalStream | any;
      // 后端server
      socket: any;
      // peer 实例
      peer: any;
      constructor(props: any) {
        super(props);
      }
      socketInit() {
        // 后端socket.io
        this.socket = SocketIO('ws://10.173.6.13:8000/', {
          transports: ['websocket'], // 确认连接为 ws ,防止调用 http 请求
          query: {
            userId: state.user.info?.id,
          },
        });
        this.socket.on('connect', function () {
          console.log('Connected to signalling server, Peer ID: %s');
        });
      }
      init = async () => {
        this.socketInit();
        await this.createStream();
        if (Peer.WEBRTC_SUPPORT) {
          console.log('支持webrtc');
          // webrtc support!
        } else {
          console.log('此浏览器不支持webrtc');
          // fallback
        }
        if (state.user.info?.isStudent) {
          this.peer = new Peer({
            initiator: true, // 确认谁是发起者,当调用 peer.on('signal',() => {}) 方法时,发起者会发起第一个 sigal 连接信令
            config: {
              iceServers: iceServers,
            },
          });
          this.peer.addStream(this.localStream);
        } else {
          this.peer = new Peer({
            config: {
              iceServers: iceServers,
            },
          });
        }
    
        this.socket.on('signal', (data: { userId: string; signalData: any }) => {
          if (data.userId == state.user.info?.id) {
            console.log(
              'Received signalling data',
              data,
              'from Peer ID:',
              data.userId
            );
            this.peer.signal(data.signalData);
          }
        });
        if (state.user.info?.isStudent) {
          this.peer.on('signal', (data: any) => {
            this.socket.emit('signal', {
              signalData: data,
              userId: '3199361187342825',
            });
          });
        } else {
          this.peer.on('signal', (data: any) => {
            this.socket.emit('signal', {
              signalData: data,
              userId: '8889361187356611',
            });
          });
        }
    
        this.peer.on('stream', (stream: any) => {
          // got remote video stream, now let's show it in a video tag
          var video: any = document.getElementsByClassName('dailog-share-video')[0];
          if ('srcObject' in video) {
            video.srcObject = stream;
          } else {
            video.src = window.URL.createObjectURL(stream); // for older browsers
          }
          video.play();
        });
    
        this.isInitComplete.resolve(true);
        return;
      };
      /**
       * 创建共享屏幕本地流
       */
      async createStream() {
        if (!this.localStream) {
          this.localStream = await navigator.mediaDevices
            .getUserMedia({ video: true, audio: true })
            .catch((err) => console.log(err));
        } else {
          return this.localStream;
        }
      }
    }
    
    
    

再加上一些能够播放视频的html代码一般就能跑通了。在实际的应用场景中,可以创建多个实例