WebRTC 实战:实现 P2P 实时视频互动

简介: 只有虽然说WebRTC支持P2P,但是需要有一台信令服务器来交换双方的SDP,现在我们就来用Node实现一个信令服务器。

服务端与信令


只有虽然说WebRTC支持P2P,但是需要有一台信令服务器来交换双方的SDP,现在我们就来用Node实现一个信令服务器。

这里选用阿里开源的MidwayJS服务端框架,你可以选择更加轻量级的Express或者Koa框架,都可以通过第三方中间件来实现相同的功能,我这里选择Midway完全是个人偏好。

如果感兴趣可以看一下Midway的文档,或者你钟爱Express可以使用Express+Express-ws来实现完全相同的效果。

客户端能力:

  • 提供页面HTML;
  • SDP交换;

使用ejs来进行页面渲染

使用模板引擎中间件来进行ejs模板的渲染工作,将路由根目录绑定到页面渲染

这一块相对简单,可以在使用的node框架的官网或者社区找到中间件的文档,根据文档进行配置,然后就可以开始编码工作了(代码非常简单,因为中间件帮我们处理了大部分事情)

@Provide()
@Controller('/')
export class HomeController {
  @Inject()
  ctx: Context;
  @Get('/')
  async home() {
    await this.ctx.render('home.ejs');
  }
}
复制代码

使用SocketIO作为socker-server来进行SDP交换

socketIO作为一个非常出名的socket框架,其内部提供了非常丰富的API,足够支撑起我们日常开发的大部分需要,这里具体的配置方法就不详写了,可以去官网了解,或许以后我会出相关的教程。

@Provide()
@WSController('/')
export class IndexSocketController {
  @Inject()
  ctx: Context;
  @App()
  socket: Application;
  @OnWSConnection()
  @WSEmit('connection')
  // 连接时触发,向客户端提交connection事件
  async onConnectionMethod() {
    this.ctx.logger.info('on client connect', this.ctx.id);
    return 'connection';
  }
  @OnWSMessage('join')
  // 收到消息标识为join时执行
  async joinRoom(roomId: string, user: string) {
    if ((await this.ctx.to(roomId).allSockets()).size >= 2) {
      // 如果房间内的用户数量大于等于二,不可以再加入新的成员
      // 并向客户端提交full事件
      this.ctx.emit('full', `${roomId} is full!`);
    } else {
      await this.ctx.join(roomId);
      this.ctx.to(roomId).emit('join', `user[ ${user} ] join this room!`);
      // 加入房间之后向房间内其他成员提交join事件
      // 向用户自身提交joined事件
      this.ctx.emit('joined', `join in ${roomId}!`);
    }
  }
  @OnWSMessage('quit')
  @WSEmit('result')
  async quitRoom(roomId: string, user: string) {
    await this.ctx.leave(roomId);
    this.ctx.to(roomId).emit('quit', `user[ ${user} ] quit this room!`);
    return 'quit success';
  }
  @OnWSMessage('call')
  async call(roomId, data) {
    this.ctx.to(roomId).emit('sdp', data);
  }
}
复制代码

(用注解开发真的爽“死”了)



客户端


了解了之前的WebRTC相关API,并且有了信令服务器的加持,我们就可以来进行视频互动的开发了。

为了简化,我们直接就使用模板引擎来渲染视图,不再去单独使用前端框架创建项目了。部分代码借鉴了github的一个开源项目

首先来列举一下客户端的能力

  1. 基本的音视频通话;
  2. 录屏;

音视频聊天

进行音视频聊天又分为几个步骤,首先需要初始化ICE

// 初始化ICE
const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
!PeerConnection && message.error('浏览器不支持WebRTC!');
const peer = new PeerConnection();
peer.ontrack = e => {
  if (e && e.streams) {
    // message 是自定义的日志工具
    message.log('收到对方音频/视频流数据...');
    remoteVideo.srcObject = e.streams[0];
  }
};
peer.onicecandidate = e => {
  if (e.candidate) {
    message.log('搜集并发送候选人');
    socket.emit('call', roomId, JSON.stringify({
      type: 'ice',
      iceCandidate: e.candidate
    }));
  } else {
    message.log('候选人收集完成!');
  }
};
复制代码

然后需要进行交换SDP,这里需要使用Socket来完成

const socket = io('http://localhost:7001')
socket.on('connect_error', () => {
  // 连接socket失败
  message.error('socket通道初始化失败')
})
// 消息处理
socket.on('connection', () => {
  // 连接之后加入房间
  socket.emit('join', roomId, username)
})
socket.on('sdp', e => {
  const { type, sdp, iceCandidate } = JSON.parse(e)
  if (type === 'answer') {
    peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
  } else if (type === 'ice') {
    peer.addIceCandidate(iceCandidate);
  } else if (type === 'offer') {
    const resolve = confirm('接到视频请求,是否接听')
    resolve && startLive(new RTCSessionDescription({ type, sdp }));
  }
})
复制代码

当双方都添加了对端的SDP之后就可以开始视频互动了,这里同一封装为startLive方法

async function startLive(offerSdp) {
  button.style.display = 'none'
  let stream;
  try {
    message.log('尝试调取本地摄像头/麦克风');
    stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    message.log('摄像头/麦克风获取成功!');
    localVideo.srcObject = stream;
  } catch {
    message.error('摄像头/麦克风获取失败!');
    return;
  }
  message.log(`------ WebRTC 流程开始 ------`);
  message.log('将媒体轨道添加到轨道集');
  stream.getTracks().forEach(track => {
    peer.addTrack(track, stream);
  });
  if (!offerSdp) {
    message.log('创建本地SDP');
    const offer = await peer.createOffer();
    await peer.setLocalDescription(offer);
    message.log(`传输发起方本地SDP`);
    socket.emit('call', roomId, JSON.stringify(offer));
  } else {
    message.log('接收到发送方SDP');
    await peer.setRemoteDescription(offerSdp);
    message.log('创建接收方(应答)SDP');
    const answer = await peer.createAnswer();
    message.log(`传输接收方(应答)SDP`);
    socket.emit('call', roomId, JSON.stringify(answer));
    await peer.setLocalDescription(answer);
  }
}
复制代码

录屏

实现了视频聊天之后我们来添加录屏的功能

之前我们已经介绍了录屏相关的API,这里我们将其添加进我们现有的代码中即可

let buffer, mediaRecorder;
//当该函数被触发后,将数据压入到blob中
function handleDataAvailable(e) {
  if (e && e.data && e.data.size > 0) {
    buffer.push(e.data);
  }
}
// 开始录制
function startRecord() {
  buffer = [];
  //设置录制下来的多媒体格式
  var options = {
    mimeType: 'video/webm;codecs=vp8'
  }
  //判断浏览器是否支持录制
  if (!MediaRecorder.isTypeSupported(options.mimeType)) {
    message.error(`${options.mimeType} is not supported!`);
    return;
  }
  try {
    //创建录制对象
    // stream 需要从将视频流保留到全局
    mediaRecorder = new MediaRecorder(stream, options);
  } catch (e) {
    message.error('Failed to create MediaRecorder:', e.message);
    return;
  }
  //当有音视频数据来了之后触发该事件
  mediaRecorder.ondataavailable = handleDataAvailable;
  //开始录制
  mediaRecorder.start(10);
}
// 停止录制
function stopRecord() {
  mediaRecorder.stop();
}
// 将录制的视频下载到本地
function downloadRecord() {
  var blob = new Blob(buffer, { type: 'video/webm' });
  var url = window.URL.createObjectURL(blob);
  var a = document.createElement('a');
  a.href = url;
  a.style.display = 'none';
  a.download = new Date().getTime() + '.webm';
  a.click();
}
复制代码

这里只是做了演示,需要根据需求定制录屏的内容

相关文章
|
7月前
|
Web App开发 编解码 安全
音视频绕不开的话题之WebRTC
闲来无事,我们今天探讨下音视频绕不开的一个话题:WebRTC。WebRTC之于音视频行业,无异于FFMpeg,可以说WebRTC的开源,让音视频行业大跨步进入发展快车道。
140 0
|
7月前
|
Web App开发 数据采集 物联网
Android平台基于RTMP或RTSP的一对一音视频互动技术方案探讨
随着智能门禁等物联网产品的普及,越来越多的开发者对音视频互动体验提出了更高的要求。目前市面上大多一对一互动都是基于WebRTC,优点不再赘述,我们这里先说说可能需要面临的问题:WebRTC的服务器部署非常复杂,可以私有部署,但是非常复杂。传输基于UDP,很难保证传输质量,由于UDP是不可靠的传输协议,在复杂的公网网络环境下,各种突发流量、偶尔的传输错误、网络抖动、超时等等都会引起丢包异常,都会在一定程度上影响音视频通信的质量,难以应对复杂的互联网环境,如跨区跨运营商、低带宽、高丢包等场景,行话说的好:从demo到实用,中间还差1万个WebRTC。
|
4月前
|
编解码 算法
网易云音乐音视频算法处理技术
网易云音乐音视频算法处理技术
67 0
|
4月前
|
编解码 算法 大数据
即时通讯技术文集(第25期):实时音视频基础入门 [共20篇]
​为了更好地分类阅读 52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第25 期。
35 1
|
9月前
|
编解码 监控 C++
H264音视频直播系统 服务器端+客户端源码 可用于视频聊天、视频会议
H264音视频直播系统 服务器端+客户端源码 可用于视频聊天、视频会议
107 0
|
10月前
|
Web App开发 安全 JavaScript
WebRTC:实时音视频通信的开发与应用
WebRTC(Web实时通信)是一种开放标准的实时通信技术,使开发者能够在Web浏览器中实现高质量的音视频通信。本文将介绍WebRTC的基本原理和用法,以及如何使用WebRTC构建实时音视频通信应用程序。
287 0
|
Web App开发 网络虚拟化
使用 WebRTC 构建简单的视频聊天室(1)
使用 WebRTC 构建简单的视频聊天室(1)
361 0
|
存储 数据采集 边缘计算
视频聊天源码以一对一直播为主,如何提高直播质量
视频聊天源码熟悉直播不仅要靠流媒体技术、服务器和CDN,还要使用多种功能机制,优化直播功能体验,比如减低直播延迟,提高直播间打开速度等。
|
Web App开发 编解码 边缘计算
基于WebRTC的互动直播实践
互动直播已经逐渐成为直播的主要形式。映客直播资深音视频工程师叶峰峰在LiveVideoStackCon 2018大会的演讲中详细介绍了INKE自研连麦整体设计思路、如何基于WebRTC搭建互动直播SDK以及针对用户体验进行优化。本文由LiveVideoStack整理而成。
798 0
基于WebRTC的互动直播实践
|
Web App开发 编解码 缓存
基于视频流传输 — 在线教育白板技术
在线教育不同于线下教育, 内容需要经过电子白板展现给用户,如何做出优秀的在线教育白板成为研究的重点。本文来自学而思网校客户端架构负责人赵文杰在 LiveVideoStackCon 2018 大会上的分享,并由 LiveVideoStack 整理而成。
基于视频流传输 — 在线教育白板技术