WebRTC接收端动态控制jitterBufferTarget

新版本的WebRTC支持在已经建立连接的情况下,接收端支持动态调整自己的jitterBufferTarget。在Chrome124版本中可以体验这个功能了。通过两个网页进行webrtc通信,具体效果见下面的视频。

什么是jitterBuffer

jitterBuffer是抖动缓冲器。简单的理解就是缓冲器越大,网络抖动的时候越不容易卡顿、延迟越大。反之缓冲器越小,网络抖动的时候越容易卡顿、延迟越低。

效果

在这里插入图片描述

上面的视频使用chrome 124(目前是chrome的beta版本)进行演示。使用nodejs创建了一个websocket server的服务,用来交换两个页面的offer和answer。具体可以参考:前端使用WebRTC———局域网内单向通信

  1. 通过拉流页面中的输入框可以设置jitterBufferTarget。可以在chrome://webrtc-internals中可以看到设置的参数是可以动态生效的。
  2. 在视频的后半段,推流端使用了一个视频,订阅端将jitterBufferTarget设置为2000毫秒,来演示接收端的延迟效果。可以看到视频中每秒会增加一个小圆圈,设置为2000毫秒的jitterBufferTarget后,拉流端的视频中的小圆圈和推流视频中小圆圈一直保持2个的差距。表示设置是生效的。

实现方法

核心代码

拉流端通过RTCRtpReceiver.jitterBufferTarget方法,可以支持动态调整jitterBufferTarget的大小(最大4000ms)。

const receivers = pc_sub.getReceivers();
if (arr.length > 0) {
  // demo多次刷新会复用同一个pc,导致存在多个receiver,所以此处用最后一个
  const receiver = arr[arr.length-1];
  // 兼容性判断,低版本浏览器不支持此方法
  if (receiver.jitterBufferTarget !== undefined) {
    receiver.jitterBufferTarget = jitterBufferTarget;
  }
}       

实际应用

在使用WebRTC进行通信时,不同的业务场景,我们对于延迟和卡顿有不同的需求,比如在网络好的情况下,我们希望延迟尽可能的小一些,就可以将jitterBufferTarget的延迟设置的比较小,反之用户的网络不好的情况下,就把jitterBufferTarget的延迟设置的大一些,尽量减少卡顿。或者在云游戏的场景,也可以尽可能的减小jitterBufferTarget来减小端到端延迟,获得更好的游戏体验。

代码

可以直接从gittee获取源代码,也可以直接使用下面的代码

服务端

要注意server依赖了nodejs-websocket模块。

var ws = require("nodejs-websocket");


var pub_ws = null;
var sub_ws = null;


function start() {
  var msg = JSON.stringify({ type: "start" });
  pub_ws.send(msg);
}

var server = ws.createServer(function (conn) {
  // 收到websocket连接
  conn.on("text", function (str) {
    if (pub_ws === conn) {
      if (sub_ws) {
        sub_ws.send(str);
      }
    } else if (sub_ws === conn) {
      if (pub_ws) {
        pub_ws.send(str);
      }
    } else {
      let obj = JSON.parse(str);
      if (obj.type === 'publish') {
        pub_ws = conn;
        if (sub_ws) {
          start();
        }
      } else if (obj.type === 'subscribe') {
        sub_ws = conn;
        if (pub_ws) {
          start();
        }
      }
    }
  })

  conn.on("error", function (event) {

  });

  conn.on("close", function (code, reason) {
    if (conn === pub_ws) {
      console.log("remove pub")
      pub_ws = null;
    } else if (conn === sub_ws) {
      console.log("remove sub")
      sub_ws = null;
    }
  })
}).listen(9000);
推流端

推流页面需要用localhost访问,因为获取设备需要https或者localhost(127.0.0.1)才可以。获取设备部分可以参考# 获取浏览器麦克风、摄像头和屏幕共享其中websoket server的ip写的是127.0.0.1,可以自行改成websocket server的局域网ip地址。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>推流页面</title>
  </head>

  <body>
    <video id="localStream" style="width: 320px; height: 240px;" autoplay muted></video>
    <script>
      // 获取摄像头返回的MediaStream
      let localStream = null;

      // 显示本地画面的VideoElement
      let localVideo = document.getElementById("localStream");

      // 推流用的MediaStream
      let pc_pub = new RTCPeerConnection();

      let ws = new WebSocket('ws://127.0.0.1:9000');
      ws.addEventListener('open', () => {
        // 通知server pub已经上线
        ws.send(JSON.stringify({
          type: "publish"
        }))
      })

      ws.addEventListener('message', (event) => {
        let msg = JSON.parse(event.data);
        switch (msg.type) {
          case "start":
            start();
            break;

          case "answer":
            pc_pub.setRemoteDescription(msg).then(() => {

            }).catch((err) => {

            })
            break;

          default:
            pc_pub.addIceCandidate(msg);
            break;
        }
      })

      pc_pub.addEventListener('icecandidate', (event) => {
        if (event.candidate) {
          ws.send(JSON.stringify(event.candidate));
        }
      })

      function start() {
        getDevice().then((mediaStream) => {
          pc_pub.addTrack(mediaStream.getVideoTracks()[0], mediaStream);
          pc_pub.createOffer().then((offer) => {
            pc_pub.setLocalDescription(offer).then(() => {
              ws.send(JSON.stringify(offer));
            }).catch((err) => {
              console.error('setLocalDescription error', err);
            })
          }).catch((err) => {
            console.error("create offer error", err);
          })
        }).catch((err) => {
          console.error("getDevice error", err);
        })
      }

      function getDevice() {
        return new Promise((resolve, reject) => {
          navigator.mediaDevices.getUserMedia({ video: true }).then((mediaStream) => {
            localVideo.srcObject = mediaStream;
            resolve(mediaStream);
          }).catch((err) => {
            reject(err);
          })
        })
      }
    </script>
  </body>
</html>
订阅端

其中websoket server的ip写的是127.0.0.1,可以自行改成websocket server的局域网ip地址。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>拉流页面</title>
  </head>

  <body>
    <video id="remoteStream" style="width: 320px; height: 240px;" autoplay muted></video>
    <div>
      jitterBufferTarget:<input id="jitterBufferTarget_input" />
      <button id="jitterBufferTarget_button">设置</button>
    </div>
    
    <script>
      // 显示订阅视频的VideoElement
      let remoteStream = document.getElementById("remoteStream");

      // 订阅流用的Peerconnection
      let pc_sub = new RTCPeerConnection();

      const jitterBufferTarget_button = document.getElementById("jitterBufferTarget_button");
      const jitterBufferTarget_input = document.getElementById("jitterBufferTarget_input");

      let ws = new WebSocket('ws://127.0.0.1:9000');
      ws.addEventListener('open', () => {
        // 通知server pub已经上线
        ws.send(JSON.stringify({
          type: "subscribe"
        })) 
      })

      ws.addEventListener('message', (event) => {
        let msg = JSON.parse(event.data);
        switch (msg.type) {
          case "start":
            break;

          case "offer":
            pc_sub.setRemoteDescription(msg).then(() => {
              pc_sub.createAnswer().then((answer) => {
                pc_sub.setLocalDescription(answer).then(() => {
                  ws.send(JSON.stringify(answer));
                }).catch((err) => {
                })
              }).catch((err) => {
                console.error('create answer error', err);
              })
            }).catch((err) => {
              console.error('setRemoteDescription error', err);
            })
            break;

          default:
            pc_sub.addIceCandidate(msg);
            break;
        }
      })

      pc_sub.addEventListener('icecandidate', (event) => {
        if (event.candidate) {
          ws.send(JSON.stringify(event.candidate));
        }
      })

      pc_sub.addEventListener('track', (event) => {
        remoteStream.srcObject = event.streams[0];
      })

      jitterBufferTarget_button.addEventListener("click", () => {
        const jitterBufferTarget = parseInt(jitterBufferTarget_input.value);
        if (jitterBufferTarget < 0 || jitterBufferTarget > 4000) {
          return;
        }
        const receivers = pc_sub.getReceivers();
        if (arr.length > 0) {
          // demo多次刷新会复用同一个pc,导致存在多个receiver,所以此处用最后一个
          const receiver = arr[arr.length-1];
          // 兼容性判断,低版本浏览器不支持此方法
          if (receiver.jitterBufferTarget !== undefined) {
            receiver.jitterBufferTarget = jitterBufferTarget;
          }
          
        }
      })

      
    </script>
  </body>

</html>

其他

如果你也是专注前端多媒体或者对前端多媒体感兴趣,可以关注微信公众号“前端多媒体”

  • 28
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

豆包啊啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值