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();
}
复制代码

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

相关文章
|
Web App开发 编解码 安全
音视频绕不开的话题之WebRTC
闲来无事,我们今天探讨下音视频绕不开的一个话题:WebRTC。WebRTC之于音视频行业,无异于FFMpeg,可以说WebRTC的开源,让音视频行业大跨步进入发展快车道。
184 0
|
Web App开发 数据采集 物联网
Android平台基于RTMP或RTSP的一对一音视频互动技术方案探讨
随着智能门禁等物联网产品的普及,越来越多的开发者对音视频互动体验提出了更高的要求。目前市面上大多一对一互动都是基于WebRTC,优点不再赘述,我们这里先说说可能需要面临的问题:WebRTC的服务器部署非常复杂,可以私有部署,但是非常复杂。传输基于UDP,很难保证传输质量,由于UDP是不可靠的传输协议,在复杂的公网网络环境下,各种突发流量、偶尔的传输错误、网络抖动、超时等等都会引起丢包异常,都会在一定程度上影响音视频通信的质量,难以应对复杂的互联网环境,如跨区跨运营商、低带宽、高丢包等场景,行话说的好:从demo到实用,中间还差1万个WebRTC。
150 0
|
18天前
|
Web App开发 网络协议 算法
WebRTC 和一些常见的直播方案
【10月更文挑战第25天】
|
2月前
|
安全 CDN
基于surging 如何利用peerjs进行语音视频通话
【9月更文挑战第5天】该内容介绍了如何在基于 Surging 框架的应用中集成 PeerJS 以实现语音视频通话功能。首先需安装 Surging 并引入 PeerJS 库,接着创建 Peer 对象并处理连接事件,然后在 Surging 中创建与 PeerJS 交互的逻辑,最后实现获取媒体设备及建立连接共享媒体流的功能。整个过程需根据具体需求进行调整和优化,并确保通信安全。
67 14
|
11月前
|
编解码 算法 大数据
即时通讯技术文集(第25期):实时音视频基础入门 [共20篇]
​为了更好地分类阅读 52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第25 期。
58 1
|
6月前
|
编解码 算法
网易云音乐音视频算法处理技术
网易云音乐音视频算法处理技术
147 0
|
11月前
|
Web App开发 编解码 小程序
即时通讯技术文集(第24期):音视频WebRTC好文合集 [共20篇]
为了更好地分类阅读 52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第 24 期。
75 0
|
JSON 编解码 监控
天猫精灵音视频质保框架介绍
背景音视频做为天猫精灵的重要业务,可支持多态终端的爱家看护监控、音视频通话等场景,志在打造陪伴类心智,为用户生活增添便捷和美好。近一年业务经历了手机/音箱/云端/服务商等整体换血,在维持40万日活访问的基础上,打造新品卖点,提升性能耗时。音视频业务的质量保证,除了要考虑通用音视频指标,还要结合业务架构实际,从功能/稳定/性能/QoS等方面提升用户体验,了解行业位置。现将质保框架总结如下,希望能抛砖
天猫精灵音视频质保框架介绍
|
Web App开发 编解码 关系型数据库
互动直播之WebRTC服务器Kurento实战
先介绍Kurento的主要模块及Kurento的Docker安装方式,接着介绍了基于coturn项目的打洞服务器的安装及调试,最后介绍Kurento的demo调试。
3163 0
互动直播之WebRTC服务器Kurento实战
|
Web App开发 编解码 JavaScript
互动直播之WebRTC服务开源技术选型
介绍了直播的基础知识,对比几种传输标得出WebRTC的优势,常见的WebRTC架构及开源方案。
4336 0
互动直播之WebRTC服务开源技术选型