webrtc实现一对一音视频和类IM即时通讯

前面熟悉了WebRTC的核心对象PeerConnection,也了解了核心对象的一些基本方法;后面也分析了WebRTC的会话过程,以及中间需要调用哪些方法;最后通过 nodejs搭建了最基本的信令服务器。接下来就开始用代码还原前面的WebRTC的基本会话流程。

后续pc代表PeerConnectioncallerAcalleeB举例。

一对一音视频

再看看这个流程,回顾一下基本的通信思路
webrtc实现一对一音视频和类IM即时通讯

呼叫方A

localRtcPc为本地实例化后的PeerConnection实例,与前面整体流程有差异的地方是,现在在初始化 pc 后,直接同步获取本地摄像头和音频输入并添加到 pc 中。初始获取媒体流需要一定时间响应,如果在乎创建连接时间的,这一步可异步完成。

// 初始化呼叫者信息
const initCallerInfo = async (userId, targetUserId) => {
  mapSender.value = [];

  // 初始化 PC
  localRtcPc.value = new PeerConnection();

  // 初始化本地媒体信息
  const localStream = await getLocalUserMedia({
    video: true,
    audio: true,
  });

  // 添加轨道
  for (const track of localStream.getTracks()) {
    mapSender.value.push(localRtcPc.value.addTrack(track));
  }

  // 本地dom渲染  必须渲染完成后才能设置
  await setDomVideoStream("video", localStream);

  // 触发监听
  onPcEvent(localRtcPc.value, userId, targetUserId);

  // 创建offer
  const offer = await localRtcPc.value.createOffer({ iceRestart: true });

  // 设置offer为本地描述
  await localRtcPc.value.setLocalDescription(offer);

  // 发送offer给被呼叫者
  const params = {
    userId,
    targetUserId,
    offer,
  };
  linkSocket.value.emit("offer", params);
};
  1. A呼叫B后双方同意建立通信,A首先初始化PC,即代码中的localRtcPc
  2. 然后 A 初始化本地mediaStream,并添加到 pc 对象中,同时渲染在本地预览 DOM 元素。
  3. 初始化回调信息,比如 ontrack(监听B端媒体),onicecandidate(双方 ICE 候选信息)事件等
// 创建消息通道
const onPcEvent = (pc, userId, targetUserId) => {
  // 创建消息通道,建立webRTC通信之后,就可以直接 p2p 的直接发送消息, 无需中转服务器
  channel.value = pc.createDataChannel("chat");

  // 监听远程媒体轨道即远端音视频信息
  pc.ontrack = (e) => {
    setRemoteDomVideoStream("remoteVideo", e.track);
  };

  // 需要协商新的连接时会被触发  例如在添加新的数据流或者移除数据流时
  pc.onnegotiationneeded = (e) => {
    console.log("需要协商新的连接", e);
  };

  // 用于处理当数据通道被添加到连接时触发
  pc.ondatachannel = (e) => {
    e.channel.onopen = () => {
      console.log("通道打开");
    };
    e.channel.onclose = () => {
      console.log("通道关闭");
    };
    e.channel.onmessage = (data) => {
      console.log("收到消息", data);
      remoteMsg.value = data.data; // 接收到远端消息
    };
  };

  // 当 ICE 框架收集到新的 ICE 候选时触发,用于处理 ICE 候选的事件
  pc.onicecandidate = (e) => {
    if (e.candidate) {
      const params = {
        userId,
        targetUserId,
        candidate: e.candidate,
      };
      linkSocket.value.emit("candidate", params);
    } else {
      console.log("在此次协商中,没有收集到新的 ICE 候选");
    }
  };
};
  1. 创建offer信令设置为本地描述后发送给 B 。
  2. 等 B 创建应答信令之后,信令服务器会将其转发到 A 这边。
  3. A 接受 B 的 answer信令后,将其设置为 remoteDesc 
// 处理远端answer
const onRemoteAnswer = async (fromUserId, answer) => {
  await localRtcPc.value.setRemoteDescription(answer);
};

被呼叫方B

被呼叫端的过程和呼叫端类似,大体代码如下:

// 初始化被呼叫者的信息
const initCalleeInfo = async (targetUserId, userId) => {
  // 初始化 PC
  localRtcPc.value = new PeerConnection();

  // 初始化本地媒体信息
  const localStream = await getLocalUserMedia({
    video: true,
    audio: true,
  });

  // 添加轨道
  for (const track of localStream.getTracks()) {
    localRtcPc.value.addTrack(track);
  }

  // dom渲染
  await setDomVideoStream("video", localStream);

  // 触发监听
  onPcEvent(localRtcPc.value, targetUserId, userId);
};
  1. B 接听后同时初始化 pc。
  2. B 创建本地mediaStream,并添加到 pc 对象中,同时渲染在本地预览 Dom 元素。
  3. 同 A一样初始化回调监听。
  4. 当然此时 A 发送的offer信令通过信令服务器转发到 B 这边,B 将其设置为remoteDesc后,同时创建answer信令。
// 处理远端offer
const onRemoteOffer = async (fromUserId, offer) => {
  localRtcPc.value.setRemoteDescription(offer); // 保存远端发送给自己的信令

  const answer = await localRtcPc.value.createAnswer(); // 创建应答

  await localRtcPc.value.setLocalDescription(answer); // 保存应答
  const params = {
    userId: props.userId,
    targetUserId: fromUserId,
    answer,
  };
  linkSocket.value.emit("answer", params);
};

至此,所有的会话建立完成,在双方监听的 pc 核心方法ontrack中,就能拿到双方的音频和视频信息了,在文末有仓库地址,大家可自取。

webrtc实现一对一音视频和类IM即时通讯

webrtc实现一对一音视频和类IM即时通讯

注意:在真正的复杂网络环境中,需要考虑的还有很多,如果之前了解过WebRTC相关的知识,一定对stun 和 turn这几个词不陌生,但是这里暂时不考虑这个,从最简单的网络环境中开始,完成我们的目标。

通话过程中媒体流的变更

完成以上视频通话,再考虑一下,如何实现类似微信视频中,视频和音频之间随意切换,或摄像头前置后置切换呢?

这里就需要再学习一个知识点:RTCRtpSender对象。这个对象的接口支持变更你发送到对方的媒体,通过这个对象接口,你可以编辑更改流属性,从而达到控制远端媒体流的目的。

通过实例化后的PeerConnection对象调用getSenders方法,可获取每个媒体轨道对应RTCRtpSender对象。这里再解释下这个媒体轨道,我们在获取到媒体信息的时候,一般包含两部分,一部分音频信息(audiotrack),一部分视频信息(videotrack),因此这里的媒体轨道指的就是媒体信息。

  • 音频视频模式切换。
//获取发送到远端的具体媒体信息的发送方信息
const senders = this.localRtcPc.getSenders(); 
console.log(senders)
const send = senders.find((s) => s.track.kind === 'video') //找到视频发送方信息
send.track.enabled = !send.track.enabled //控制视频显示与否 即仅音频模式
  • 摄像头切换。
//这里web端因此只获取屏幕分享流 APP端则获取前置后置摄像头流即可
let stream = await this.getShareMedia()
const [videoTrack] = stream.getVideoTracks(); 
const send = senders.find((s) => s.track.kind === 'video')//找到视频类型发送方信息
send.replaceTrack(videoTrack) //替换视频媒体信息

类IM通讯实现

前面初始化回调流程中有个监听方法 onPcEvent(),内部你会发现有个函数createDataChannel,看名字就是创建了一个通道。是的,这就是的WebRTC中的datachannel可以实现无服务端 P2P 文本等富文本信息双向传输,只要完成WebRTC会话,即使视频通话过程中你的云服务器宕机了也没关系,P2P 的即时通讯还是可以正常进行的。

回看一下上面那两张图片,里面有相互发送消息。这就是类IM即时通讯。

官方描述

RTCPeerConnection 的 createDataChannel() 方法可以创建一个可以发送任意数据的数据通道, 常用于后台传输内容,例如:图像、文件传输、聊天文字等其他数据,当然除了后台,最常用的就是 P2P 中客户端的双向通信了。

基础语法和使用

下面的创建 datachannel 的前提是双方已经完成WebRTC的基础信令交换,pc变量为初始化后的RTCPeerConnection

let dataChannel = RTCPeerConnection.createDataChannel(label[, options]);

webrtc实现一对一音视频和类IM即时通讯

创建一个datachannel,发送并监听消息。

const channel =  pc.createDataChannel("channel", {
       protocol: "json",
       ordered: true,
});

-----------------监听消息------------------------------
pc.ondatachannel = function(ev) {
      console.log('Data channel is created!');
      ev.channel.onopen = function() {
        console.log('Data channel ------------open----------------');
      };
      ev.channel.onmessage = function(data) {
        console.log('Data channel ------------msg----------------',data);
      };
      ev.channel.onclose = function() {
        console.log('Data channel ------------close----------------');
      };
    };
    
 -------------发送消息--------------------------------------   
 channel.send(this.rtcmessage)

通过这种方式发送消息,你在浏览器的 NetWork 是看不到的哦,因此按照常规抓包逻辑直接抓HTTP或者WS协议包的话,也是抓不到的。想要深入了解原理,可以去看看SCTP协议 ,点击前往相关协议说明官方文档地址

项目使用指北

  1. 启动项目后,点击一对一视频
  2. 进去后,在url上带上参数,如下:
// 用户一
https://xx/#/demo3?userId=12345&roomId=xxx&nickName=Jack

// 用户二
https://xx/#/demo3?userId=1234&roomId=xxx&nickName=Rose
  1. 点击通话/切换,以及发送消息

代码地址: 前端 gitee.com/yoboom/webr… 后端 gitee.com/yoboom/webr…

另外, 推荐一下我的另一个开源项目:问卷平台

使用的技术栈为react + ts + echarts + 高德地图 + webrtc 目前正在持续开发中。有想要学习的小伙伴可以加入进来,一起交流学习

原文链接:https://juejin.cn/post/7340076087249436706 作者:cxyi7

(0)
上一篇 2024年2月28日 下午4:16
下一篇 2024年2月28日 下午4:26

相关推荐

发表评论

登录后才能评论