WebRTC提供了非常精细化的管理。大家除了可以使用非常方便的上层接口来使用 WebRTC之外,还可以通过对 Sender/Receiver的控制,对网络流量进行控制。另外还可以通过 WebRTC统计数据进行网络质量分析。这些知识你都可以通过本章的内容学习到。…

13-1 【基础铺垫,学前有概念】RTPRReceiver发送器

image.png
image.png
RTCRtpReceiver.track:返回与当前RTCRtpReceiver实例关联的MediaStreamTrack
通过媒体轨属性可以获取当前轨的类型,是audio/video

RTCRtpReceiver.transport:返回接收到的接收者媒体轨的RTCDTLTransport实例
存放着媒体数据传输相关的属性,其中trnasport用于媒体数据的传输,媒体流通过底层的transport进行传输。transport可以进行复用,多个媒体轨复用一个transport传输!

RTCRtpReceiver.rtcpTransport:返回发送和接收RTCP的RTCDTLTransport实例
与rtcp传输相关的属性,比如传输抖动,丢包数量、延迟….。接受方进行统计,反馈给发送端,发送方根据这些数据进行网络质量的评估,适当调整网络流量的发送,这就是流量控制

https://www.cnblogs.com/ssyfj/p/14823861.html
image.png

13-2 【基础铺垫,学前有概念】RTPSender发送器

image.png

这里的setParameters可以设置最大码率等
image.png
image.png

13-3-4 【来点实战】传输速率的控制

image.png

room_bw.html

  1. <html>
  2. <head>
  3. <title>WebRTC PeerConnection</title>
  4. <link href="./css/main.css" rel="stylesheet" />
  5. </head>
  6. <body>
  7. <div>
  8. <div>
  9. <button id="connserver">Connect Sig Server</button>
  10. <button id="leave" disabled>Leave</button>
  11. </div>
  12. <div>
  13. <label>Bandwidth:</label>
  14. <select id = "bandwidth" disabled>
  15. <option value="unlimited" selected>unlimited</option>
  16. <option value="2000">2000</option>
  17. <option value="1000">1000</option>
  18. <option value="500">500</option>
  19. <option value="250">250</option>
  20. <option value="125">125</option>
  21. </select>
  22. kbps
  23. </div>
  24. <div id="preview">
  25. <div >
  26. <h2>Local:</h2>
  27. <video id="localvideo" autoplay playsinline muted></video>
  28. </div>
  29. <div>
  30. <h2>Remote:</h2>
  31. <video id="remotevideo" autoplay playsinline></video>
  32. </div>
  33. </div>
  34. </div>
  35. <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>
  36. <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  37. <script src="js/main_bw.js"></script>
  38. </body>
  39. </html>

main_bw.js

  1. 'use strict'
  2. var localVideo = document.querySelector('video#localvideo');
  3. var remoteVideo = document.querySelector('video#remotevideo');
  4. var btnConn = document.querySelector('button#connserver');
  5. var btnLeave = document.querySelector('button#leave');
  6. var offer = document.querySelector('textarea#offer');
  7. var answer = document.querySelector('textarea#answer');
  8. var shareDeskBox = document.querySelector('input#shareDesk');
  9. var bandwidth = document.querySelector('select#bandwidth');
  10. var pcConfig = {
  11. 'iceServers': [{
  12. 'urls': 'turn:stun.al.learningrtc.cn:3478',
  13. 'credential': "mypasswd",
  14. 'username': "garrylea"
  15. }]
  16. };
  17. var localStream = null;
  18. var remoteStream = null;
  19. var pc = null;
  20. var roomid;
  21. var socket = null;
  22. var offerdesc = null;
  23. var state = 'init';
  24. function sendMessage(roomid, data){
  25. console.log('send message to other end', roomid, data);
  26. if(!socket){
  27. console.log('socket is null');
  28. }
  29. socket.emit('message', roomid, data);
  30. }
  31. function conn(){
  32. socket = io.connect();
  33. socket.on('joined', (roomid, id) => {
  34. console.log('receive joined message!', roomid, id);
  35. state = 'joined'
  36. //如果是多人的话,第一个人不该在这里创建peerConnection
  37. //都等到收到一个otherjoin时再创建
  38. //所以,在这个消息里应该带当前房间的用户数
  39. //
  40. //create conn and bind media track
  41. createPeerConnection();
  42. bindTracks();
  43. btnConn.disabled = true;
  44. btnLeave.disabled = false;
  45. console.log('receive joined message, state=', state);
  46. });
  47. socket.on('otherjoin', (roomid) => {
  48. console.log('receive joined message:', roomid, state);
  49. //如果是多人的话,每上来一个人都要创建一个新的 peerConnection
  50. //
  51. if(state === 'joined_unbind'){
  52. createPeerConnection();
  53. bindTracks();
  54. }
  55. state = 'joined_conn';
  56. call();
  57. console.log('receive other_join message, state=', state);
  58. });
  59. socket.on('full', (roomid, id) => {
  60. console.log('receive full message', roomid, id);
  61. socket.disconnect();
  62. hangup();
  63. closeLocalMedia();
  64. state = 'leaved';
  65. console.log('receive full message, state=', state);
  66. alert('the room is full!');
  67. });
  68. socket.on('leaved', (roomid, id) => {
  69. console.log('receive leaved message', roomid, id);
  70. state='leaved'
  71. socket.disconnect();
  72. console.log('receive leaved message, state=', state);
  73. btnConn.disabled = false;
  74. btnLeave.disabled = true;
  75. });
  76. socket.on('bye', (room, id) => {
  77. console.log('receive bye message', roomid, id);
  78. //state = 'created';
  79. //当是多人通话时,应该带上当前房间的用户数
  80. //如果当前房间用户不小于 2, 则不用修改状态
  81. //并且,关闭的应该是对应用户的peerconnection
  82. //在客户端应该维护一张peerconnection表,它是
  83. //一个key:value的格式,key=userid, value=peerconnection
  84. state = 'joined_unbind';
  85. hangup();
  86. console.log('receive bye message, state=', state);
  87. });
  88. socket.on('disconnect', (socket) => {
  89. console.log('receive disconnect message!', roomid);
  90. if(!(state === 'leaved')){
  91. hangup();
  92. closeLocalMedia();
  93. }
  94. state = 'leaved';
  95. });
  96. socket.on('message', (roomid, data) => {
  97. console.log('receive message!', roomid, data);
  98. if(data === null || data === undefined){
  99. console.error('the message is invalid!');
  100. return;
  101. }
  102. if(data.hasOwnProperty('type') && data.type === 'offer') {
  103. pc.setRemoteDescription(new RTCSessionDescription(data));
  104. //create answer
  105. pc.createAnswer()
  106. .then(getAnswer)
  107. .catch(handleAnswerError);
  108. }else if(data.hasOwnProperty('type') && data.type === 'answer'){
  109. pc.setRemoteDescription(new RTCSessionDescription(data));
  110. bandwidth.disabled = false;
  111. }else if (data.hasOwnProperty('type') && data.type === 'candidate'){
  112. var candidate = new RTCIceCandidate({
  113. sdpMLineIndex: data.label,
  114. candidate: data.candidate
  115. });
  116. pc.addIceCandidate(candidate)
  117. .then(()=>{
  118. console.log('Successed to add ice candidate');
  119. })
  120. .catch(err=>{
  121. console.error(err);
  122. });
  123. }else{
  124. console.log('the message is invalid!', data);
  125. }
  126. });
  127. roomid = '111111';
  128. socket.emit('join', roomid);
  129. return true;
  130. }
  131. function connSignalServer(){
  132. //开启本地视频
  133. start();
  134. return true;
  135. }
  136. function getMediaStream(stream){
  137. localStream = stream;
  138. localVideo.srcObject = localStream;
  139. //这个函数的位置特别重要,
  140. //一定要放到getMediaStream之后再调用
  141. //否则就会出现绑定失败的情况
  142. //setup connection
  143. conn();
  144. }
  145. function getDeskStream(stream){
  146. localStream = stream;
  147. }
  148. function handleError(err){
  149. console.error('Failed to get Media Stream!', err);
  150. }
  151. function shareDesk(){
  152. if(IsPC()){
  153. navigator.mediaDevices.getDisplayMedia({video: true})
  154. .then(getDeskStream)
  155. .catch(handleError);
  156. return true;
  157. }
  158. return false;
  159. }
  160. function start(){
  161. if(!navigator.mediaDevices ||
  162. !navigator.mediaDevices.getUserMedia){
  163. console.error('the getUserMedia is not supported!');
  164. return;
  165. }else {
  166. var constraints = {
  167. video: true,
  168. audio: false
  169. }
  170. navigator.mediaDevices.getUserMedia(constraints)
  171. .then(getMediaStream)
  172. .catch(handleError);
  173. }
  174. }
  175. function getRemoteStream(e){
  176. remoteStream = e.streams[0];
  177. remoteVideo.srcObject = e.streams[0];
  178. }
  179. function handleOfferError(err){
  180. console.error('Failed to create offer:', err);
  181. }
  182. function handleAnswerError(err){
  183. console.error('Failed to create answer:', err);
  184. }
  185. function getAnswer(desc){
  186. pc.setLocalDescription(desc);
  187. bandwidth.disabled = false;
  188. //send answer sdp
  189. sendMessage(roomid, desc);
  190. }
  191. function getOffer(desc){
  192. pc.setLocalDescription(desc);
  193. offerdesc = desc;
  194. //send offer sdp
  195. sendMessage(roomid, offerdesc);
  196. }
  197. function createPeerConnection(){
  198. //如果是多人的话,在这里要创建一个新的连接.
  199. //新创建好的要放到一个map表中。
  200. //key=userid, value=peerconnection
  201. console.log('create RTCPeerConnection!');
  202. if(!pc){
  203. pc = new RTCPeerConnection(pcConfig);
  204. pc.onicecandidate = (e)=>{
  205. if(e.candidate) {
  206. sendMessage(roomid, {
  207. type: 'candidate',
  208. label:event.candidate.sdpMLineIndex,
  209. id:event.candidate.sdpMid,
  210. candidate: event.candidate.candidate
  211. });
  212. }else{
  213. console.log('this is the end candidate');
  214. }
  215. }
  216. pc.ontrack = getRemoteStream;
  217. }else {
  218. console.log('the pc have be created!');
  219. }
  220. return;
  221. }
  222. //绑定永远与 peerconnection在一起,
  223. //所以没必要再单独做成一个函数
  224. function bindTracks(){
  225. console.log('bind tracks into RTCPeerConnection!');
  226. if( pc === null && localStream === undefined) {
  227. console.error('pc is null or undefined!');
  228. return;
  229. }
  230. if(localStream === null && localStream === undefined) {
  231. console.error('localstream is null or undefined!');
  232. return;
  233. }
  234. //add all track into peer connection
  235. localStream.getTracks().forEach((track)=>{
  236. pc.addTrack(track, localStream);
  237. });
  238. }
  239. function call(){
  240. if(state === 'joined_conn'){
  241. var offerOptions = {
  242. offerToRecieveAudio: 1,
  243. offerToRecieveVideo: 1
  244. }
  245. pc.createOffer(offerOptions)
  246. .then(getOffer)
  247. .catch(handleOfferError);
  248. }
  249. }
  250. function hangup(){
  251. if(!pc) {
  252. return;
  253. }
  254. offerdesc = null;
  255. pc.close();
  256. pc = null;
  257. }
  258. function closeLocalMedia(){
  259. if(!(localStream === null || localStream === undefined)){
  260. localStream.getTracks().forEach((track)=>{
  261. track.stop();
  262. });
  263. }
  264. localStream = null;
  265. }
  266. function leave() {
  267. socket.emit('leave', roomid); //notify server
  268. hangup();
  269. closeLocalMedia();
  270. btnConn.disabled = false;
  271. btnLeave.disabled = true;
  272. bandwidth.disabled = true;
  273. }
  274. function change_bw(){
  275. bandwidth.disabled = true;
  276. var bw = bandwidth.options[bandwidth.selectedIndex].value;
  277. var vsender = null;
  278. var senders = pc.getSenders();
  279. //获取一个RTCRtpSender
  280. senders.forEach(sender => {
  281. if(sender && sender.track.kind === 'video'){
  282. vsender = sender;
  283. }
  284. });
  285. //获取RTCRtpSender参数,返回一个RTCRtpParameters对象
  286. var parameters = vsender.getParameters();
  287. if(!parameters.encodings){
  288. parameters.encodings=[{}];
  289. }
  290. //设置传输码率
  291. if(bw === 'unlimited'){
  292. delete parameters.encodings[0].maxBitrate;
  293. }else{
  294. parameters.encodings[0].maxBitrate = bw * 1000;
  295. }
  296. //更新RTP传输的配置以及WebRTC连接上特定传出媒体轨的编码配置
  297. vsender.setParameters(parameters)
  298. .then(()=>{
  299. bandwidth.disabled = false;
  300. })
  301. .catch(err => {
  302. console.error(err)
  303. });
  304. return;
  305. }
  306. btnConn.onclick = connSignalServer
  307. btnLeave.onclick = leave;
  308. bandwidth.onchange = change_bw;

效果

需要打开两个网页,然后都连接信号服务器,才能设置码率。
image.png

查看网络连接状态 chrome://webrtc-internals
image.png

旧的版本
image.png

13-5-6 【来点实战】WebRTC统计信息

index.html

  1. <html>
  2. <head>
  3. <title>WebRTC PeerConnection</title>
  4. <link href="./css/main.css" rel="stylesheet" />
  5. </head>
  6. <body>
  7. <div>
  8. <div>
  9. <button id="connserver">Connect Sig Server</button>
  10. <button id="leave" disabled>Leave</button>
  11. </div>
  12. <div>
  13. <label>BandWidth:</label>
  14. <select id="bandwidth" disabled>
  15. <option value="unlimited" selected>unlimited</option>
  16. <option value="2000">2000</option>
  17. <option value="1000">1000</option>
  18. <option value="500">500</option>
  19. <option value="250">250</option>
  20. <option value="125">125</option>
  21. </select>
  22. kbps
  23. </div>
  24. <div id="preview">
  25. <div >
  26. <h2>Local:</h2>
  27. <video id="localvideo" autoplay playsinline muted></video>
  28. </div>
  29. <div>
  30. <h2>Remote:</h2>
  31. <video id="remotevideo" autoplay playsinline></video>
  32. </div>
  33. </div>
  34. <div class="graph-container" id="bitrateGraph">
  35. <div>Bitrate</div>
  36. <canvas id="bitrateCanvas"></canvas>
  37. </div>
  38. <div class="graph-container" id="packetGraph">
  39. <div>Packets sent per second</div>
  40. <canvas id="packetCanvas"></canvas>
  41. </div>
  42. </div>
  43. <script src="js/third_party/graph.js"></script>
  44. <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>
  45. <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  46. <script src="js/main_bw.js"></script>
  47. </body>
  48. </html>

main.js

  1. 'use strict'
  2. var localVideo = document.querySelector('video#localvideo');
  3. var remoteVideo = document.querySelector('video#remotevideo');
  4. var btnConn = document.querySelector('button#connserver');
  5. var btnLeave = document.querySelector('button#leave');
  6. var optBw = document.querySelector('select#bandwidth');
  7. var bitrateGraph;
  8. var bitrateSeries;
  9. var packetGraph;
  10. var packetSeries;
  11. var lastResult;
  12. var pcConfig = {
  13. 'iceServers': [{
  14. 'urls': 'turn:stun.al.learningrtc.cn:3478',
  15. 'credential': "mypasswd",
  16. 'username': "garrylea"
  17. }]
  18. };
  19. var localStream = null;
  20. var remoteStream = null;
  21. var pc = null;
  22. var roomid;
  23. var socket = null;
  24. var offerdesc = null;
  25. var state = 'init';
  26. function sendMessage(roomid, data){
  27. console.log('send message to other end', roomid, data);
  28. if(!socket){
  29. console.log('socket is null');
  30. }
  31. socket.emit('message', roomid, data);
  32. }
  33. function conn(){
  34. socket = io.connect();
  35. socket.on('joined', (roomid, id) => {
  36. console.log('receive joined message!', roomid, id);
  37. state = 'joined'
  38. //如果是多人的话,第一个人不该在这里创建peerConnection
  39. //都等到收到一个otherjoin时再创建
  40. //所以,在这个消息里应该带当前房间的用户数
  41. //
  42. //create conn and bind media track
  43. createPeerConnection();
  44. bindTracks();
  45. btnConn.disabled = true;
  46. btnLeave.disabled = false;
  47. console.log('receive joined message, state=', state);
  48. });
  49. socket.on('otherjoin', (roomid) => {
  50. console.log('receive joined message:', roomid, state);
  51. //如果是多人的话,每上来一个人都要创建一个新的 peerConnection
  52. //
  53. if(state === 'joined_unbind'){
  54. createPeerConnection();
  55. bindTracks();
  56. }
  57. state = 'joined_conn';
  58. call();
  59. console.log('receive other_join message, state=', state);
  60. });
  61. socket.on('full', (roomid, id) => {
  62. console.log('receive full message', roomid, id);
  63. socket.disconnect();
  64. hangup();
  65. closeLocalMedia();
  66. state = 'leaved';
  67. console.log('receive full message, state=', state);
  68. alert('the room is full!');
  69. });
  70. socket.on('leaved', (roomid, id) => {
  71. console.log('receive leaved message', roomid, id);
  72. state='leaved'
  73. socket.disconnect();
  74. console.log('receive leaved message, state=', state);
  75. btnConn.disabled = false;
  76. btnLeave.disabled = true;
  77. optBw.disabled = true;
  78. });
  79. socket.on('bye', (room, id) => {
  80. console.log('receive bye message', roomid, id);
  81. //state = 'created';
  82. //当是多人通话时,应该带上当前房间的用户数
  83. //如果当前房间用户不小于 2, 则不用修改状态
  84. //并且,关闭的应该是对应用户的peerconnection
  85. //在客户端应该维护一张peerconnection表,它是
  86. //一个key:value的格式,key=userid, value=peerconnection
  87. state = 'joined_unbind';
  88. hangup();
  89. console.log('receive bye message, state=', state);
  90. });
  91. socket.on('disconnect', (socket) => {
  92. console.log('receive disconnect message!', roomid);
  93. if(!(state === 'leaved')){
  94. hangup();
  95. closeLocalMedia();
  96. }
  97. state = 'leaved';
  98. btnConn.disabled = false;
  99. btnLeave.disabled = true;
  100. optBw.disabled = true;
  101. });
  102. socket.on('message', (roomid, data) => {
  103. console.log('receive message!', roomid, data);
  104. if(data === null || data === undefined){
  105. console.error('the message is invalid!');
  106. return;
  107. }
  108. if(data.hasOwnProperty('type') && data.type === 'offer') {
  109. pc.setRemoteDescription(new RTCSessionDescription(data));
  110. //create answer
  111. pc.createAnswer()
  112. .then(getAnswer)
  113. .catch(handleAnswerError);
  114. }else if(data.hasOwnProperty('type') && data.type === 'answer'){
  115. optBw.disabled = false
  116. pc.setRemoteDescription(new RTCSessionDescription(data));
  117. }else if (data.hasOwnProperty('type') && data.type === 'candidate'){
  118. var candidate = new RTCIceCandidate({
  119. sdpMLineIndex: data.label,
  120. candidate: data.candidate
  121. });
  122. pc.addIceCandidate(candidate)
  123. .then(()=>{
  124. console.log('Successed to add ice candidate');
  125. })
  126. .catch(err=>{
  127. console.error(err);
  128. });
  129. }else{
  130. console.log('the message is invalid!', data);
  131. }
  132. });
  133. roomid = '111111';
  134. socket.emit('join', roomid);
  135. return true;
  136. }
  137. function connSignalServer(){
  138. //开启本地视频
  139. start();
  140. return true;
  141. }
  142. function getMediaStream(stream){
  143. localStream = stream;
  144. localVideo.srcObject = localStream;
  145. //这个函数的位置特别重要,
  146. //一定要放到getMediaStream之后再调用
  147. //否则就会出现绑定失败的情况
  148. //setup connection
  149. conn();
  150. bitrateSeries = new TimelineDataSeries();
  151. bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas');
  152. bitrateGraph.updateEndDate();
  153. packetSeries = new TimelineDataSeries();
  154. packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas');
  155. packetGraph.updateEndDate();
  156. }
  157. function getDeskStream(stream){
  158. localStream = stream;
  159. }
  160. function handleError(err){
  161. console.error('Failed to get Media Stream!', err);
  162. }
  163. function shareDesk(){
  164. if(IsPC()){
  165. navigator.mediaDevices.getDisplayMedia({video: true})
  166. .then(getDeskStream)
  167. .catch(handleError);
  168. return true;
  169. }
  170. return false;
  171. }
  172. function start(){
  173. if(!navigator.mediaDevices ||
  174. !navigator.mediaDevices.getUserMedia){
  175. console.error('the getUserMedia is not supported!');
  176. return;
  177. }else {
  178. var constraints = {
  179. video: true,
  180. audio: false
  181. }
  182. navigator.mediaDevices.getUserMedia(constraints)
  183. .then(getMediaStream)
  184. .catch(handleError);
  185. }
  186. }
  187. function getRemoteStream(e){
  188. remoteStream = e.streams[0];
  189. remoteVideo.srcObject = e.streams[0];
  190. }
  191. function handleOfferError(err){
  192. console.error('Failed to create offer:', err);
  193. }
  194. function handleAnswerError(err){
  195. console.error('Failed to create answer:', err);
  196. }
  197. function getAnswer(desc){
  198. pc.setLocalDescription(desc);
  199. optBw.disabled = false;
  200. //send answer sdp
  201. sendMessage(roomid, desc);
  202. }
  203. function getOffer(desc){
  204. pc.setLocalDescription(desc);
  205. offerdesc = desc;
  206. //send offer sdp
  207. sendMessage(roomid, offerdesc);
  208. }
  209. function createPeerConnection(){
  210. //如果是多人的话,在这里要创建一个新的连接.
  211. //新创建好的要放到一个map表中。
  212. //key=userid, value=peerconnection
  213. console.log('create RTCPeerConnection!');
  214. if(!pc){
  215. pc = new RTCPeerConnection(pcConfig);
  216. pc.onicecandidate = (e)=>{
  217. if(e.candidate) {
  218. sendMessage(roomid, {
  219. type: 'candidate',
  220. label:event.candidate.sdpMLineIndex,
  221. id:event.candidate.sdpMid,
  222. candidate: event.candidate.candidate
  223. });
  224. }else{
  225. console.log('this is the end candidate');
  226. }
  227. }
  228. pc.ontrack = getRemoteStream;
  229. }else {
  230. console.log('the pc have be created!');
  231. }
  232. return;
  233. }
  234. //绑定永远与 peerconnection在一起,
  235. //所以没必要再单独做成一个函数
  236. function bindTracks(){
  237. console.log('bind tracks into RTCPeerConnection!');
  238. if( pc === null && localStream === undefined) {
  239. console.error('pc is null or undefined!');
  240. return;
  241. }
  242. if(localStream === null && localStream === undefined) {
  243. console.error('localstream is null or undefined!');
  244. return;
  245. }
  246. //add all track into peer connection
  247. localStream.getTracks().forEach((track)=>{
  248. pc.addTrack(track, localStream);
  249. });
  250. }
  251. function call(){
  252. if(state === 'joined_conn'){
  253. var offerOptions = {
  254. offerToRecieveAudio: 1,
  255. offerToRecieveVideo: 1
  256. }
  257. pc.createOffer(offerOptions)
  258. .then(getOffer)
  259. .catch(handleOfferError);
  260. }
  261. }
  262. function hangup(){
  263. if(!pc) {
  264. return;
  265. }
  266. offerdesc = null;
  267. pc.close();
  268. pc = null;
  269. }
  270. function closeLocalMedia(){
  271. if(!(localStream === null || localStream === undefined)){
  272. localStream.getTracks().forEach((track)=>{
  273. track.stop();
  274. });
  275. }
  276. localStream = null;
  277. }
  278. function leave() {
  279. socket.emit('leave', roomid); //notify server
  280. hangup();
  281. closeLocalMedia();
  282. btnConn.disabled = false;
  283. btnLeave.disabled = true;
  284. optBw.disabled = true;
  285. }
  286. function chang_bw()
  287. {
  288. optBw.disabled = true;
  289. var bw = optBw.options[optBw.selectedIndex].value;
  290. var vsender = null;
  291. var senders = pc.getSenders();
  292. senders.forEach( sender => {
  293. if(sender && sender.track.kind === 'video'){
  294. vsender = sender;
  295. }
  296. });
  297. var parameters = vsender.getParameters();
  298. if(!parameters.encodings){
  299. return;
  300. }
  301. if(bw === 'unlimited'){
  302. return;
  303. }
  304. parameters.encodings[0].maxBitrate = bw * 1000;
  305. vsender.setParameters(parameters)
  306. .then(()=>{
  307. optBw.disabled = false;
  308. console.log('Successed to set parameters!');
  309. })
  310. .catch(err => {
  311. console.error(err);
  312. })
  313. }
  314. // query getStats every second
  315. window.setInterval(() => {
  316. if (!pc) {
  317. return;
  318. }
  319. const sender = pc.getSenders()[0];
  320. if (!sender) {
  321. return;
  322. }
  323. sender.getStats().then(res => {
  324. res.forEach(report => {
  325. let bytes;
  326. let packets;
  327. if (report.type === 'outbound-rtp') {
  328. if (report.isRemote) {
  329. return;
  330. }
  331. const now = report.timestamp;
  332. bytes = report.bytesSent;
  333. packets = report.packetsSent;
  334. if (lastResult && lastResult.has(report.id)) {
  335. // calculate bitrate
  336. const bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /
  337. (now - lastResult.get(report.id).timestamp);
  338. // append to chart
  339. bitrateSeries.addPoint(now, bitrate);
  340. bitrateGraph.setDataSeries([bitrateSeries]);
  341. bitrateGraph.updateEndDate();
  342. // calculate number of packets and append to chart
  343. packetSeries.addPoint(now, packets -
  344. lastResult.get(report.id).packetsSent);
  345. packetGraph.setDataSeries([packetSeries]);
  346. packetGraph.updateEndDate();
  347. }
  348. }
  349. });
  350. lastResult = res;
  351. });
  352. }, 1000);
  353. btnConn.onclick = connSignalServer
  354. btnLeave.onclick = leave;
  355. optBw.onchange = chang_bw;

重点代码部分

  1. // query getStats every second
  2. window.setInterval(() => {
  3. if (!pc) {
  4. return;
  5. }
  6. const sender = pc.getSenders()[0];
  7. if (!sender) {
  8. return;
  9. }
  10. sender.getStats().then(res => {
  11. res.forEach(report => {
  12. let bytes;
  13. let packets;
  14. if (report.type === 'outbound-rtp') {
  15. if (report.isRemote) {
  16. return;
  17. }
  18. const now = report.timestamp;
  19. bytes = report.bytesSent;
  20. packets = report.packetsSent;
  21. if (lastResult && lastResult.has(report.id)) {
  22. // calculate bitrate
  23. const bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /
  24. (now - lastResult.get(report.id).timestamp);
  25. // append to chart
  26. bitrateSeries.addPoint(now, bitrate);
  27. bitrateGraph.setDataSeries([bitrateSeries]);
  28. bitrateGraph.updateEndDate();
  29. // calculate number of packets and append to chart
  30. packetSeries.addPoint(now, packets -
  31. lastResult.get(report.id).packetsSent);
  32. packetGraph.setDataSeries([packetSeries]);
  33. packetGraph.updateEndDate();
  34. }
  35. }
  36. });
  37. lastResult = res;
  38. });
  39. }, 1000);

这里依赖了graph.js绘制码率图。

效果

初始化效果
image.png

连接后的效果

image.png
这里是设置了码率为1000kbps.显示码率,和每秒的包大小。

image.png

如果同时传输视频和音频,则在统计时,需要这么做

  1. window.setInterval(() => {
  2. if (!pc) {
  3. return;
  4. }
  5. const senders = pc.getSenders();
  6. if (!senders) {
  7. return;
  8. }
  9. var vsender;
  10. senders.forEach(sender => {
  11. if(sender && sender.track.kind === 'video')
  12. { vsender = sender;}
  13. });
  14. if(!vsender){
  15. return;
  16. }
  17. vsender.getStats().then(res => {
  18. //******省略

相关代码

13chapter.zip