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的开源,让音视频行业大跨步进入发展快车道。
196 0
|
Web App开发 数据采集 物联网
Android平台基于RTMP或RTSP的一对一音视频互动技术方案探讨
随着智能门禁等物联网产品的普及,越来越多的开发者对音视频互动体验提出了更高的要求。目前市面上大多一对一互动都是基于WebRTC,优点不再赘述,我们这里先说说可能需要面临的问题:WebRTC的服务器部署非常复杂,可以私有部署,但是非常复杂。传输基于UDP,很难保证传输质量,由于UDP是不可靠的传输协议,在复杂的公网网络环境下,各种突发流量、偶尔的传输错误、网络抖动、超时等等都会引起丢包异常,都会在一定程度上影响音视频通信的质量,难以应对复杂的互联网环境,如跨区跨运营商、低带宽、高丢包等场景,行话说的好:从demo到实用,中间还差1万个WebRTC。
165 0
|
1月前
|
Web App开发 网络协议 算法
WebRTC 和一些常见的直播方案
【10月更文挑战第25天】
|
3月前
|
Web App开发 网络协议 Android开发
Android平台一对一音视频通话方案大比拼:WebRTC VS RTMP VS RTSP,谁才是王者?
【9月更文挑战第4天】本文详细对比了在Android平台上实现一对一音视频通话时常用的WebRTC、RTMP及RTSP三种技术方案。从技术原理、性能表现与开发难度等方面进行了深入分析,并提供了示例代码。WebRTC适合追求低延迟和高质量的场景,但开发成本较高;RTMP和RTSP则在简化开发流程的同时仍能保持较好的传输效果,适用于不同需求的应用场景。
195 1
|
4月前
|
编解码 Dart 网络协议
"震撼揭秘!Flutter如何玩转超低延迟RTSP/RTMP播放,跨平台视频流体验大升级,让你的应用秒变直播神器!"
【8月更文挑战第15天】Flutter作为跨平台UI框架,以其高效性和丰富生态著称。本文详述如何利用flutter_vlc_player等插件在Flutter中实现低延迟RTSP/RTMP播放,并提供代码示例。通过优化播放器设置,如禁用缓冲、启用帧丢弃等,可进一步减少延迟,提升用户观看体验,展现了Flutter在视频流媒体应用中的强大潜力。
123 0
|
4月前
|
Web App开发 网络协议 Android开发
### 惊天对决!Android平台一对一音视频通话方案大比拼:WebRTC VS RTMP VS RTSP,谁才是王者?
【8月更文挑战第14天】随着移动互联网的发展,实时音视频通信已成为移动应用的关键部分。本文对比分析了Android平台上WebRTC、RTMP与RTSP三种主流技术方案。WebRTC提供端到端加密与直接数据传输,适于高质量低延迟通信;RTMP适用于直播场景,但需服务器中转;RTSP支持实时流播放,但在复杂网络下稳定性不及WebRTC。三种方案各有优劣,WebRTC功能强大但集成复杂,RTMP和RTSP实现较简单但需额外编码支持。本文还提供了示例代码以帮助开发者更好地理解和应用这些技术。
158 0
|
7月前
|
编解码 算法
网易云音乐音视频算法处理技术
网易云音乐音视频算法处理技术
172 0
|
缓存 网络协议 应用服务中间件
流媒体及直播相关知识
流媒体及直播相关知识
193 0
|
Web App开发 编解码 关系型数据库
互动直播之WebRTC服务器Kurento实战
先介绍Kurento的主要模块及Kurento的Docker安装方式,接着介绍了基于coturn项目的打洞服务器的安装及调试,最后介绍Kurento的demo调试。
3181 0
互动直播之WebRTC服务器Kurento实战
|
Web App开发 编解码 JavaScript
互动直播之WebRTC服务开源技术选型
介绍了直播的基础知识,对比几种传输标得出WebRTC的优势,常见的WebRTC架构及开源方案。
4350 0
互动直播之WebRTC服务开源技术选型

热门文章

最新文章