WebRTC接收端动态控制jitterBufferTarget
新版本的WebRTC支持在已经建立连接的情况下,接收端支持动态调整自己的jitterBufferTarget。在Chrome124版本中可以体验这个功能了。通过两个网页进行webrtc通信,具体效果见下面的视频。
什么是jitterBuffer
jitterBuffer是抖动缓冲器。简单的理解就是缓冲器越大,网络抖动的时候越不容易卡顿、延迟越大。反之缓冲器越小,网络抖动的时候越容易卡顿、延迟越低。
效果
上面的视频使用chrome 124(目前是chrome的beta版本)进行演示。使用nodejs创建了一个websocket server的服务,用来交换两个页面的offer和answer。具体可以参考:前端使用WebRTC———局域网内单向通信。
- 通过拉流页面中的输入框可以设置jitterBufferTarget。可以在chrome://webrtc-internals中可以看到设置的参数是可以动态生效的。
- 在视频的后半段,推流端使用了一个视频,订阅端将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地址。
推流页面 // 获取摄像头返回的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); }) }) }
订阅端
其中websoket server的ip写的是127.0.0.1,可以自行改成websocket server的局域网ip地址。
拉流页面jitterBufferTarget: 设置// 显示订阅视频的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 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; } } })
其他
如果你也是专注前端多媒体或者对前端多媒体感兴趣,可以关注微信公众号“前端多媒体”
还没有评论,来说两句吧...