nest、vue3实现webRTC实战(SDP会话描述协议)

167 阅读2分钟

一、流程简述

客户端A、客户端B与信令服务器建立socket连接用于交换SDP信息

信令服务(Nest.js)

信令服务主要使用socket实现房间的概念,对房间内的用户进行信息的交换

import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway({ cors: { origin: '*' } })
export class WsGateway {

  @WebSocketServer()
  server: Server;
  // 用于存储房间房间和用于id
  private users: Map<string, string> = new Map();

  // 加入房间
  @SubscribeMessage('joinRoom')
  joinRoom(
    @MessageBody() roomId: string,
    @ConnectedSocket() client: Socket,
  ): void {
    const previousRoom = this.users.get(client.id);
    // 判断是否存在之前的房间、如果之前进入且没退出则退出
    if (previousRoom) { 
      client.leave(previousRoom);
    }
    // 获取房间内信息
    const roomClients = this.server.sockets.adapter.rooms.get(roomId);
    // 限制一个房间不能超过两人,超出则加入失败
    if (roomClients?.size === 2) {
      return client.emit('error','加入失败')
    }
    client.join(roomId);
    this.users.set(client.id, roomId);
    // 通知房间内的用户,XX进入
    client.to(roomId).emit('userJoined', client.id);
  }
  // 断开连接通知房间内的用户
  disconnect(client: Socket): void {
    const roomId = this.users.get(client.id);
    if (roomId) {
      client.to(roomId).emit('userLeft', client.id);
      this.users.delete(client.id);
    }
  }
  // 信令交换
  @SubscribeMessage('signal')
  receptionOfferAnswer(
      @MessageBody() data: any, 
      @ConnectedSocket() client: Socket)
  {
    const roomClients = this.server.sockets.adapter.rooms.get(data.roomId);
    if (roomClients && roomClients.size > 1) {
         client.to(roomId).emit('reception', data.offer || data.answer);
    }
  }
}

客户端

客户端使用socket与信令服务建立连接,通过peerConnections.createOffer()peerConnections.createAnswer()创建offer信令、answer信令。再通过交换彼此信令,建立点对点管道通讯。

<script lang="ts" setup>
import { io } from 'socket.io-client'
import { ref } from 'vue'
const socket = io('http://xxxx:9988')

const roomId = 'test-room' // 房间 ID

// 加入房间
socket.emit('joinRoom', roomId)

// 监听其他用户加入
socket.on('userJoined', id => {
  // 其他用户加入创建offer
  createConnection()
})

socket.on('reception', data => {
  if (data.type === 'offer') {
    peerConnections
      .setRemoteDescription(new RTCSessionDescription(data))
      .then(() => peerConnections.createAnswer())
      .then(answer => peerConnections.setLocalDescription(answer))
      .then(() => {
        // 推送offer给房间内其他用户
        socket.emit('offer', { roomId, answer: peerConnections.localDescription })
      })
  } else if (data.type === 'answer') {
    // 根据answer信令建立连接
    peerConnections.setRemoteDescription(new RTCSessionDescription(data))
  }
})

// 创建 WebRTC 连接
const peerConnections = new RTCPeerConnection()
function createConnection() {
  // 在这里创建 WebRTC 连接
  peerConnections.createOffer().then(offer =>
    peerConnections.setLocalDescription(offer).then(() => {
      socket.emit('offer', {
        roomId,
        offer: peerConnections.localDescription
      })
    })
  )
}
</script>

使用navigator.mediaDevices .getDisplayMedia()获取媒体流,peerConnections.addTrack()推送媒体流

const startScreenShare = () => {
  navigator.mediaDevices
    .getDisplayMedia({
      audio: true,
      video: {
        width: { ideal: 1920 },
        height: { ideal: 1080 },
        frameRate: { ideal: 60 }
      }
    })
    .then(stream => {
      // 推送媒体流
      peerConnections.addTrack(stream.getVideoTracks()[0], stream)
    })
    .catch(e => {
      console.error('未能获取媒体', e)
    })
}