• 设置布局
<div class="bitrate"><div class="graph-container" id="bitrateGraph"><div>Bitrate</div><canvas id="bitrateCanvas"></canvas></div><div class="graph-container" id="packetGraph"><div>Packets sent per second</div><canvas id="packetCanvas"></canvas></div></div>
  • 引入第三方graph.js
<script src="js/third_party/graph.js"></script>
源码后附
  • 统计信息
//统计信息
window.setInterval(()=>{if (!pc) {return;}const sender = pc.getSenders()[0];if (!sender) {return;}sender.getStats().then(res=>{res.forEach(report =>{let bytes;let packets;if (report.type === 'outbound-rtp') {if (report.isRemote) {return;}const now = report.timestamp;bytes = report.bytesSent;packets = report.packetsSent;if (lastResult && lastResult.has(report.id)) {const bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /(now - lastResult.get(report.id).timestamp);    // append to chartbitrateSeries.addPoint(now, bitrate);bitrateGraph.setDataSeries([bitrateSeries]);bitrateGraph.updateEndDate();// calculate number of packets and app end to chartpacketSeries.addPoint(now, packets - lastResult.get(report.id).packetsSent);packetGraph.setDataSeries([packetSeries]);packetGraph.updateEndDate();}}});lastResult = res;}).catch(err=>{console.error(err);});},1000);
  • 效果


源码

  • html
<html><head><title>WebRTC PeerConnection</title><style>.preview,.bitrate{display: flex;}.remote{margin-left: 20px;}</style></head><body><div><div><button id="connserver">Connect Sig Server</button><button id="leave" disabled>Leave</button></div><div><label>BandWidth:</label><select id="bandwidth" disabled><option value="unlimited" selected>unlimited</option><option value="2000">2000</option><option value="1000">1000</option><option value="500">500</option><option value="250">250</option><option value="125">125</option></select>kps</div><div class="preview"><div><h2>Local:</h2><video id="localvideo" autoplay playsinline></video></div><div class="remote"><h2>Remote:</h2><video id="remotevideo" autoplay playsinline></video></div></div><div class="bitrate"><div class="graph-container" id="bitrateGraph"><div>Bitrate</div><canvas id="bitrateCanvas"></canvas></div><div class="graph-container" id="packetGraph"><div>Packets sent per second</div><canvas id="packetCanvas"></canvas></div></div></div><script src="js/third_party/graph.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script><script src="https://webrtc.github.io/adapter/adapter-latest.js"></script><script src="./js/main_bw.js"></script></body>
</html>
  • css

button {margin: 10px 20px 25px 0;vertical-align: top;width: 134px;
}table {margin: 200px (50% - 100) 0 0;
}textarea {color: #444;font-size: 0.9em;font-weight: 300;height: 20.0em;padding: 5px;width: calc(100% - 10px);
}div#getUserMedia {padding: 0 0 8px 0;
}div.input {display: inline-block;margin: 0 4px 0 0;vertical-align: top;width: 310px;
}div.input > div {margin: 0 0 20px 0;vertical-align: top;
}div.output {background-color: #eee;display: inline-block;font-family: 'Inconsolata', 'Courier New', monospace;font-size: 0.9em;padding: 10px 10px 10px 25px;position: relative;top: 10px;white-space: pre;width: 270px;
}div.label {display: inline-block;font-weight: 400;width: 120px;
}div.graph-container {background-color: #ccc;float: left;margin: 0.5em;width: calc(50%-1em);
}div#preview {border-bottom: 1px solid #eee;margin: 0 0 1em 0;padding: 0 0 0.5em 0;
}div#preview > div {display: inline-block;vertical-align: top;width: calc(50% - 12px);
}section#statistics div {display: inline-block;font-family: 'Inconsolata', 'Courier New', monospace;vertical-align: top;width: 308px;
}section#statistics div#senderStats {margin: 0 20px 0 0;
}section#constraints > div {margin: 0 0 20px 0;
}h2 {margin: 0 0 1em 0;
}section#constraints label {display: inline-block;width: 156px;
}section {margin: 0 0 20px 0;padding: 0 0 15px 0;
}video {background: #222;margin: 0 0 0 0;--width: 100%;width: var(--width);height: 225px;
}@media screen and (max-width: 720px) {button {font-weight: 500;height: 56px;line-height: 1.3em;width: 90px;}div#getUserMedia {padding: 0 0 40px 0;}section#statistics div {width: calc(50% - 14px);}}
  • graph.js
/**  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.**  Use of this source code is governed by a BSD-style license*  that can be found in the LICENSE file in the root of the source*  tree.*/
// taken from chrome://webrtc-internals with jshint adaptions'use strict';
/* exported TimelineDataSeries, TimelineGraphView */// The maximum number of data points bufferred for each stats. Old data points
// will be shifted out when the buffer is full.
const MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000;const TimelineDataSeries = (function() {/*** @constructor*/function TimelineDataSeries() {// List of DataPoints in chronological order.this.dataPoints_ = [];// Default color.  Should always be overridden prior to display.this.color_ = 'red';// Whether or not the data series should be drawn.this.isVisible_ = true;this.cacheStartTime_ = null;this.cacheStepSize_ = 0;this.cacheValues_ = [];}TimelineDataSeries.prototype = {/*** @override*/toJSON: function() {if (this.dataPoints_.length < 1) {return {};}let values = [];for (let i = 0; i < this.dataPoints_.length; ++i) {values.push(this.dataPoints_[i].value);}return {startTime: this.dataPoints_[0].time,endTime: this.dataPoints_[this.dataPoints_.length - 1].time,values: JSON.stringify(values),};},/*** Adds a DataPoint to |this| with the specified time and value.* DataPoints are assumed to be received in chronological order.*/addPoint: function(timeTicks, value) {let time = new Date(timeTicks);this.dataPoints_.push(new DataPoint(time, value));if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE) {this.dataPoints_.shift();}},isVisible: function() {return this.isVisible_;},show: function(isVisible) {this.isVisible_ = isVisible;},getColor: function() {return this.color_;},setColor: function(color) {this.color_ = color;},getCount: function() {return this.dataPoints_.length;},/*** Returns a list containing the values of the data series at |count|* points, starting at |startTime|, and |stepSize| milliseconds apart.* Caches values, so showing/hiding individual data series is fast.*/getValues: function(startTime, stepSize, count) {// Use cached values, if we can.if (this.cacheStartTime_ === startTime &&this.cacheStepSize_ === stepSize &&this.cacheValues_.length === count) {return this.cacheValues_;}// Do all the work.this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);this.cacheStartTime_ = startTime;this.cacheStepSize_ = stepSize;return this.cacheValues_;},/*** Returns the cached |values| in the specified time period.*/getValuesInternal_: function(startTime, stepSize, count) {let values = [];let nextPoint = 0;let currentValue = 0;let time = startTime;for (let i = 0; i < count; ++i) {while (nextPoint < this.dataPoints_.length &&this.dataPoints_[nextPoint].time < time) {currentValue = this.dataPoints_[nextPoint].value;++nextPoint;}values[i] = currentValue;time += stepSize;}return values;}};/*** A single point in a data series.  Each point has a time, in the form of* milliseconds since the Unix epoch, and a numeric value.* @constructor*/function DataPoint(time, value) {this.time = time;this.value = value;}return TimelineDataSeries;
})();const TimelineGraphView = (function() {// Maximum number of labels placed vertically along the sides of the graph.let MAX_VERTICAL_LABELS = 6;// Vertical spacing between labels and between the graph and labels.let LABEL_VERTICAL_SPACING = 4;// Horizontal spacing between vertically placed labels and the edges of the// graph.let LABEL_HORIZONTAL_SPACING = 3;// Horizintal spacing between two horitonally placed labels along the bottom// of the graph.// var LABEL_LABEL_HORIZONTAL_SPACING = 25;// Length of ticks, in pixels, next to y-axis labels.  The x-axis only has// one set of labels, so it can use lines instead.let Y_AXIS_TICK_LENGTH = 10;let GRID_COLOR = '#CCC';let TEXT_COLOR = '#000';let BACKGROUND_COLOR = '#FFF';let MAX_DECIMAL_PRECISION = 2;/*** @constructor*/function TimelineGraphView(divId, canvasId) {this.scrollbar_ = {position_: 0, range_: 0};this.graphDiv_ = document.getElementById(divId);this.canvas_ = document.getElementById(canvasId);// Set the range and scale of the graph.  Times are in milliseconds since// the Unix epoch.// All measurements we have must be after this time.this.startTime_ = 0;// The current rightmost position of the graph is always at most this.this.endTime_ = 1;this.graph_ = null;// Horizontal scale factor, in terms of milliseconds per pixel.this.scale_ = 1000;// Initialize the scrollbar.this.updateScrollbarRange_(true);}TimelineGraphView.prototype = {setScale: function(scale) {this.scale_ = scale;},// Returns the total length of the graph, in pixels.getLength_: function() {let timeRange = this.endTime_ - this.startTime_;// Math.floor is used to ignore the last partial area, of length less// than this.scale_.return Math.floor(timeRange / this.scale_);},/*** Returns true if the graph is scrolled all the way to the right.*/graphScrolledToRightEdge_: function() {return this.scrollbar_.position_ === this.scrollbar_.range_;},/*** Update the range of the scrollbar.  If |resetPosition| is true, also* sets the slider to point at the rightmost position and triggers a* repaint.*/updateScrollbarRange_: function(resetPosition) {let scrollbarRange = this.getLength_() - this.canvas_.width;if (scrollbarRange < 0) {scrollbarRange = 0;}// If we've decreased the range to less than the current scroll position,// we need to move the scroll position.if (this.scrollbar_.position_ > scrollbarRange) {resetPosition = true;}this.scrollbar_.range_ = scrollbarRange;if (resetPosition) {this.scrollbar_.position_ = scrollbarRange;this.repaint();}},/*** Sets the date range displayed on the graph, switches to the default* scale factor, and moves the scrollbar all the way to the right.*/setDateRange: function(startDate, endDate) {this.startTime_ = startDate.getTime();this.endTime_ = endDate.getTime();// Safety check.if (this.endTime_ <= this.startTime_) {this.startTime_ = this.endTime_ - 1;}this.updateScrollbarRange_(true);},/*** Updates the end time at the right of the graph to be the current time.* Specifically, updates the scrollbar's range, and if the scrollbar is* all the way to the right, keeps it all the way to the right.  Otherwise,* leaves the view as-is and doesn't redraw anything.*/updateEndDate: function(optDate) {this.endTime_ = optDate || (new Date()).getTime();this.updateScrollbarRange_(this.graphScrolledToRightEdge_());},getStartDate: function() {return new Date(this.startTime_);},/*** Replaces the current TimelineDataSeries with |dataSeries|.*/setDataSeries: function(dataSeries) {// Simply recreates the Graph.this.graph_ = new Graph();for (let i = 0; i < dataSeries.length; ++i) {this.graph_.addDataSeries(dataSeries[i]);}this.repaint();},/*** Adds |dataSeries| to the current graph.*/addDataSeries: function(dataSeries) {if (!this.graph_) {this.graph_ = new Graph();}this.graph_.addDataSeries(dataSeries);this.repaint();},/*** Draws the graph on |canvas_|.*/repaint: function() {this.repaintTimerRunning_ = false;let width = this.canvas_.width;let height = this.canvas_.height;let context = this.canvas_.getContext('2d');// Clear the canvas.context.fillStyle = BACKGROUND_COLOR;context.fillRect(0, 0, width, height);// Try to get font height in pixels.  Needed for layout.let fontHeightString = context.font.match(/([0-9]+)px/)[1];let fontHeight = parseInt(fontHeightString);// Safety check, to avoid drawing anything too ugly.if (fontHeightString.length === 0 || fontHeight <= 0 ||fontHeight * 4 > height || width < 50) {return;}// Save current transformation matrix so we can restore it later.context.save();// The center of an HTML canvas pixel is technically at (0.5, 0.5).  This// makes near straight lines look bad, due to anti-aliasing.  This// translation reduces the problem a little.context.translate(0.5, 0.5);// Figure out what time values to display.let position = this.scrollbar_.position_;// If the entire time range is being displayed, align the right edge of// the graph to the end of the time range.if (this.scrollbar_.range_ === 0) {position = this.getLength_() - this.canvas_.width;}let visibleStartTime = this.startTime_ + position * this.scale_;// Make space at the bottom of the graph for the time labels, and then// draw the labels.let textHeight = height;height -= fontHeight + LABEL_VERTICAL_SPACING;this.drawTimeLabels(context, width, height, textHeight, visibleStartTime);// Draw outline of the main graph area.context.strokeStyle = GRID_COLOR;context.strokeRect(0, 0, width - 1, height - 1);if (this.graph_) {// Layout graph and have them draw their tick marks.this.graph_.layout(width, height, fontHeight, visibleStartTime, this.scale_);this.graph_.drawTicks(context);// Draw the lines of all graphs, and then draw their labels.this.graph_.drawLines(context);this.graph_.drawLabels(context);}// Restore original transformation matrix.context.restore();},/*** Draw time labels below the graph.  Takes in start time as an argument* since it may not be |startTime_|, when we're displaying the entire* time range.*/drawTimeLabels: function(context, width, height, textHeight, startTime) {// Draw the labels 1 minute apart.let timeStep = 1000 * 60;// Find the time for the first label.  This time is a perfect multiple of// timeStep because of how UTC times work.let time = Math.ceil(startTime / timeStep) * timeStep;context.textBaseline = 'bottom';context.textAlign = 'center';context.fillStyle = TEXT_COLOR;context.strokeStyle = GRID_COLOR;// Draw labels and vertical grid lines.while (true) {let x = Math.round((time - startTime) / this.scale_);if (x >= width) {break;}let text = (new Date(time)).toLocaleTimeString();context.fillText(text, x, textHeight);context.beginPath();context.lineTo(x, 0);context.lineTo(x, height);context.stroke();time += timeStep;}},getDataSeriesCount: function() {if (this.graph_) {return this.graph_.dataSeries_.length;}return 0;},hasDataSeries: function(dataSeries) {if (this.graph_) {return this.graph_.hasDataSeries(dataSeries);}return false;},};/*** A Graph is responsible for drawing all the TimelineDataSeries that have* the same data type.  Graphs are responsible for scaling the values, laying* out labels, and drawing both labels and lines for its data series.*/const Graph = (function() {/*** @constructor*/function Graph() {this.dataSeries_ = [];// Cached properties of the graph, set in layout.this.width_ = 0;this.height_ = 0;this.fontHeight_ = 0;this.startTime_ = 0;this.scale_ = 0;// The lowest/highest values adjusted by the vertical label step size// in the displayed range of the graph. Used for scaling and setting// labels.  Set in layoutLabels.this.min_ = 0;this.max_ = 0;// Cached text of equally spaced labels.  Set in layoutLabels.this.labels_ = [];}/*** A Label is the label at a particular position along the y-axis.* @constructor*//*function Label(height, text) {this.height = height;this.text = text;}*/Graph.prototype = {addDataSeries: function(dataSeries) {this.dataSeries_.push(dataSeries);},hasDataSeries: function(dataSeries) {for (let i = 0; i < this.dataSeries_.length; ++i) {if (this.dataSeries_[i] === dataSeries) {return true;}}return false;},/*** Returns a list of all the values that should be displayed for a given* data series, using the current graph layout.*/getValues: function(dataSeries) {if (!dataSeries.isVisible()) {return null;}return dataSeries.getValues(this.startTime_, this.scale_, this.width_);},/*** Updates the graph's layout.  In particular, both the max value and* label positions are updated.  Must be called before calling any of the* drawing functions.*/layout: function(width, height, fontHeight, startTime, scale) {this.width_ = width;this.height_ = height;this.fontHeight_ = fontHeight;this.startTime_ = startTime;this.scale_ = scale;// Find largest value.let max = 0;let min = 0;for (let i = 0; i < this.dataSeries_.length; ++i) {let values = this.getValues(this.dataSeries_[i]);if (!values) {continue;}for (let j = 0; j < values.length; ++j) {if (values[j] > max) {max = values[j];} else if (values[j] < min) {min = values[j];}}}this.layoutLabels_(min, max);},/*** Lays out labels and sets |max_|/|min_|, taking the time units into* consideration.  |maxValue| is the actual maximum value, and* |max_| will be set to the value of the largest label, which* will be at least |maxValue|. Similar for |min_|.*/layoutLabels_: function(minValue, maxValue) {if (maxValue - minValue < 1024) {this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);return;}// Find appropriate units to use.let units = ['', 'k', 'M', 'G', 'T', 'P'];// Units to use for labels.  0 is '1', 1 is K, etc.// We start with 1, and work our way up.let unit = 1;minValue /= 1024;maxValue /= 1024;while (units[unit + 1] && maxValue - minValue >= 1024) {minValue /= 1024;maxValue /= 1024;++unit;}// Calculate labels.this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);// Append units to labels.for (let i = 0; i < this.labels_.length; ++i) {this.labels_[i] += ' ' + units[unit];}// Convert |min_|/|max_| back to unit '1'.this.min_ *= Math.pow(1024, unit);this.max_ *= Math.pow(1024, unit);},/*** Same as layoutLabels_, but ignores units.  |maxDecimalDigits| is the* maximum number of decimal digits allowed.  The minimum allowed* difference between two adjacent labels is 10^-|maxDecimalDigits|.*/layoutLabelsBasic_: function(minValue, maxValue, maxDecimalDigits) {this.labels_ = [];let range = maxValue - minValue;// No labels if the range is 0.if (range === 0) {this.min_ = this.max_ = maxValue;return;}// The maximum number of equally spaced labels allowed.  |fontHeight_|// is doubled because the top two labels are both drawn in the same// gap.let minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING;// The + 1 is for the top label.let maxLabels = 1 + this.height_ / minLabelSpacing;if (maxLabels < 2) {maxLabels = 2;} else if (maxLabels > MAX_VERTICAL_LABELS) {maxLabels = MAX_VERTICAL_LABELS;}// Initial try for step size between conecutive labels.let stepSize = Math.pow(10, -maxDecimalDigits);// Number of digits to the right of the decimal of |stepSize|.// Used for formating label strings.let stepSizeDecimalDigits = maxDecimalDigits;// Pick a reasonable step size.while (true) {// If we use a step size of |stepSize| between labels, we'll need://// Math.ceil(range / stepSize) + 1//// labels.  The + 1 is because we need labels at both at 0 and at// the top of the graph.// Check if we can use steps of size |stepSize|.if (Math.ceil(range / stepSize) + 1 <= maxLabels) {break;}// Check |stepSize| * 2.if (Math.ceil(range / (stepSize * 2)) + 1 <= maxLabels) {stepSize *= 2;break;}// Check |stepSize| * 5.if (Math.ceil(range / (stepSize * 5)) + 1 <= maxLabels) {stepSize *= 5;break;}stepSize *= 10;if (stepSizeDecimalDigits > 0) {--stepSizeDecimalDigits;}}// Set the min/max so it's an exact multiple of the chosen step size.this.max_ = Math.ceil(maxValue / stepSize) * stepSize;this.min_ = Math.floor(minValue / stepSize) * stepSize;// Create labels.for (let label = this.max_; label >= this.min_; label -= stepSize) {this.labels_.push(label.toFixed(stepSizeDecimalDigits));}},/*** Draws tick marks for each of the labels in |labels_|.*/drawTicks: function(context) {let x1;let x2;x1 = this.width_ - 1;x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH;context.fillStyle = GRID_COLOR;context.beginPath();for (let i = 1; i < this.labels_.length - 1; ++i) {// The rounding is needed to avoid ugly 2-pixel wide anti-aliased// lines.let y = Math.round(this.height_ * i / (this.labels_.length - 1));context.moveTo(x1, y);context.lineTo(x2, y);}context.stroke();},/*** Draws a graph line for each of the data series.*/drawLines: function(context) {// Factor by which to scale all values to convert them to a number from// 0 to height - 1.let scale = 0;let bottom = this.height_ - 1;if (this.max_) {scale = bottom / (this.max_ - this.min_);}// Draw in reverse order, so earlier data series are drawn on top of// subsequent ones.for (let i = this.dataSeries_.length - 1; i >= 0; --i) {let values = this.getValues(this.dataSeries_[i]);if (!values) {continue;}context.strokeStyle = this.dataSeries_[i].getColor();context.beginPath();for (let x = 0; x < values.length; ++x) {// The rounding is needed to avoid ugly 2-pixel wide anti-aliased// horizontal lines.context.lineTo(x, bottom - Math.round((values[x] - this.min_) * scale));}context.stroke();}},/*** Draw labels in |labels_|.*/drawLabels: function(context) {if (this.labels_.length === 0) {return;}let x = this.width_ - LABEL_HORIZONTAL_SPACING;// Set up the context.context.fillStyle = TEXT_COLOR;context.textAlign = 'right';// Draw top label, which is the only one that appears below its tick// mark.context.textBaseline = 'top';context.fillText(this.labels_[0], x, 0);// Draw all the other labels.context.textBaseline = 'bottom';let step = (this.height_ - 1) / (this.labels_.length - 1);for (let i = 1; i < this.labels_.length; ++i) {context.fillText(this.labels_[i], x, step * i);}}};return Graph;})();return TimelineGraphView;
})();
  • js
'use strict'var localVideo = document.querySelector('video#localvideo');
var remoteVideo = document.querySelector('video#remotevideo');var btnConn = document.querySelector('button#connserver');
var btnLeave = document.querySelector('button#leave');var optBw = document.querySelector('select#bandwidth')var localStream = null;var roomid = '444444';
var socket =null;var state = 'init';var pc = null;var lastResult;
var packetGraph;
var packetSeries;
var bitrateGraph;
var bitrateSeries;var pcConfig={'iceServers':[{'urls':'turn:121.41.76.43:3478','credential':'123456','username':'huang'}]}function sendMessage(roomid,data){if(socket){socket.emit('message',roomid,data);}
}function getAnswer(desc){pc.setLocalDescription(desc);optBw.disabled =false;sendMessage(roomid,desc);
}function handleAnswerError(err){console.error('Failed to get Answer!',err);
}function getOffer(desc){pc.setLocalDescription(desc);sendMessage(roomid,desc)
}
function handleOfferError(err){console.error('Failed to get Offer!',err);
}//接收远端流通道
function call(){if(state === 'joined_conn'){if(pc){var options = {offerToReceiveAudio:1,offerToReceiveVideo:1}pc.createOffer(options).then(getOffer).catch(handleOfferError);}}
}// 第一步:开始服务
function connSignalServer(){//开启本地视频start();return true;
}function conn(){//1 触发socke连接socket = io.connect();//2 加入房间后的回调socket.on('joined',(roomid,id)=>{state = 'joined';createPeerConnection();btnConn.disabled = true;btnLeave.disabled =false;console.log("reveive joined message:state=",state);    });socket.on('otherjoin',(roomid,id)=>{if (state === 'joined_unbind') {createPeerConnection();}state = 'joined_conn';//媒体协商call();console.log("reveive otherjoin message:state=",state);   });socket.on('full',(roomid,id)=>{console.log('receive full message ', roomid, id);closePeerConnection();closeLocalMedia();state = 'leaved';btnConn.disabled = false;btnLeave.disabled = true;console.log("reveive full message:state=",state);alert("the room is full!");});socket.on('leaved',(roomid,id)=>{state = 'leaved';socket.disconnect();btnConn.disabled = false;btnLeave.disabled = true;console.log("reveive leaved message:state=",state);});socket.on('bye',(roomid,id)=>{state = 'joined_unbind';closePeerConnection();console.log("reveive bye message:state=",state); });socket.on('disconnect', (socket) => {console.log('receive disconnect message!', roomid);if(!(state === 'leaved')){closePeerConnection();closeLocalMedia();}state = 'leaved';});socket.on('message',(roomid,id,data)=>{//媒体协商if(data){if(data.type === 'offer'){pc.setRemoteDescription(new RTCSessionDescription(data));pc.createAnswer().then(getAnswer).catch(handleAnswerError);}else if(data.type === 'answer'){console.log("reveive client message=====>",data);optBw.disabled = false;pc.setRemoteDescription(new RTCSessionDescription(data));}else if(data.type === 'candidate'){var candidate = new RTCIceCandidate({sdpMLineIndex:data.label,candidate:data.candidate});pc.addIceCandidate(candidate);}else{console.error('the message is invalid!',data)}}console.log("reveive client message",roomid,id,data);  });socket.emit('join',roomid);return;
}// 扑捉本地视频
function getMediaStream(stream){localStream =stream;//2 ===============显示本地视频===============localVideo.srcObject = localStream;//这个函数的调用时机特别重要 一定要在getMediaStream之后再调用,否则会出现绑定失败的情况conn();//信息统计bitrateSeries = new TimelineDataSeries();bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas');bitrateGraph.updateEndDate();packetSeries = new TimelineDataSeries();packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas');packetGraph.updateEndDate();
}function handleError(err){if(err){console.error("getUserMedia  error:",err); }
}// 第二步:采集本地视频
function start(){if (!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia) {           console.log("getUserMedia is not supported!")return;} else {//1 ===============配置音视频参数===============var constraints={video:true,audio: false}navigator.mediaDevices.getUserMedia(constraints).then(getMediaStream).catch(handleError)}
}//关闭流通道
function closeLocalMedia(){if (localStream&&localStream.getTracks()) {localStream.getTracks().forEach((track)=>{track.stop();   });}localStream = null;
}function leave(){if(socket){socket.emit('leave',roomid);}//释放资源closePeerConnection();closeLocalMedia();btnConn.disabled = false;btnLeave.disabled = true;
}//创建本地流媒体链接
function createPeerConnection(){console.log('create RTCPeerConnection!');if(!pc){pc = new RTCPeerConnection(pcConfig);pc.onicecandidate = (e) =>{if(e.candidate){sendMessage(roomid,{type:'candidate',label:e.candidate.sdpMLineIndex,id:e.candidate.sdpMid,candidate:e.candidate.candidate});}}pc.ontrack = (e)=>{remoteVideo.srcObject = e.streams[0];}}if(pc === null || pc === undefined){console.error('pc is null or undefined!');return;}if(localStream === null || localStream === undefined){console.error('localStream is null or undefined!');return;}if(localStream){localStream.getTracks().forEach((track)=>{pc.addTrack(track,localStream);})}
}//关闭本地媒体流链接
function closePeerConnection(){console.log('close RTCPeerConnection!');if(pc){pc.close();pc = null;}
}//控制传输速率
function chang_bw(){optBw.disabled =true;var bw = optBw.options[optBw.selectedIndex].value;var vsender = null;var senders = pc.getSenders();senders.forEach(sender=>{if(sender&&sender.track && sender.track.kind === 'video'){vsender = sender;} });var parameters= vsender.getParameters();if(!parameters.encodings){return;}if(bw === 'unlimited'){return;}parameters.encodings[0].maxBitrate = bw * 1000;vsender.setParameters(parameters).then(()=>{optBw.disabled =false;console.log('Successed to set parameters!');}).catch(err=>{console.error(err);})}//统计信息
window.setInterval(()=>{if (!pc) {return;}const sender = pc.getSenders()[0];if (!sender) {return;}sender.getStats().then(res=>{res.forEach(report =>{let bytes;let packets;if (report.type === 'outbound-rtp') {if (report.isRemote) {return;}const now = report.timestamp;bytes = report.bytesSent;packets = report.packetsSent;if (lastResult && lastResult.has(report.id)) {const bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /(now - lastResult.get(report.id).timestamp);    // append to chartbitrateSeries.addPoint(now, bitrate);bitrateGraph.setDataSeries([bitrateSeries]);bitrateGraph.updateEndDate();// calculate number of packets and app end to chartpacketSeries.addPoint(now, packets - lastResult.get(report.id).packetsSent);packetGraph.setDataSeries([packetSeries]);packetGraph.updateEndDate();}}});lastResult = res;}).catch(err=>{console.error(err);});},1000);
btnConn.onclick = connSignalServer;btnLeave.onclick = leave;optBw.onchange=chang_bw;

测试地址:https://www.huangxiaoguo.club/getstats/index.html

webRTC(十三):webrtc 统计信息相关推荐

  1. WebRTC[11]-WebRTC如何通过SDP信息设置音频码率

    目录 前言 正文 <WebRTC工作原理精讲>系列-总览_Data-Mining的博客-CSDN博客前言欢迎大家订阅Data-Mining 的<WebRTC工作原理精讲>专栏. ...

  2. WebRTC学习—WebRTC详解

    目录 一:WebRTC学习了解   (一)WebRTC应用场景   (二)WebRTC的难点   (三)学习流程   (四)学习目标 二:WebRTC介绍   (一)概述   (二)WebRTC可以实 ...

  3. 验证Oracle收集统计信息参数granularity数据分析的力度

    最近在学习Oracle的统计信息这一块,收集统计信息的方法如下: DBMS_STATS.GATHER_TABLE_STATS ( ownname VARCHAR2, ---所有者名字 tabname ...

  4. 当统计信息不准确时,CBO可能产生错误的执行计划,并在10053 trace中找到CBO出错的位置示例...

    一.本文说明: 操作系统:rhel 5.4 x32 数据库:oracle 11g r2 x32 二.实验内容: ----创建一张jack表,并创建索引jack_ind---- 1 SQL> cr ...

  5. Oracle执行计划突变诊断之统计信息收集问题

    Oracle执行计划突变诊断之统计信息收集问题 1.  情形描述 DB version:11.2.0.4 WITH SQL1 AS(SELECT LAC,CI,TO_NUMBER(C.LONGITUD ...

  6. 解决oracle11g安装导致数据库无法自动搜集统计信息-转

    近期发现个别11G数据库无法自动收集统计信息,部分视图查询结果如下: SQL> select client_name,status from dba_autotask_client where ...

  7. oracle 查看统计情报,Oracle 查看收集统计信息

    -- Start 统计信息相当于情报,对 Oracle 至关重要,如果统计信息不准确,Oracle 就会做出错误的判断.那如何查看统计信息呢?试一试下面的 SQL 吧. -- 查看表统计信息 sele ...

  8. Vertica的这些事lt;十二gt;—— vertica存储统计信息

    vertica存储统计信息: 表数量: select count(distinct table_name) FROM tables; 分区表数量: select count(distinct tabl ...

  9. R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、编写自定义三线表结构(将因子变量细粒度化重新构建三线图)、为不同的变量显示不同的统计信息

    R语言使用table1包绘制(生成)三线表.使用单变量分列构建三线表.编写自定义三线表结构(将因子变量细粒度化重新构建三线图).为不同的变量显示不同的统计信息 目录

最新文章

  1. jquery ui动态切换主题的一种实现方式
  2. JS函数表达的几种写法
  3. excel小写转大写公式_喂!这边居然有演示的Excel快捷键!
  4. Java大数据-Week2-Day4-IDEA安装
  5. SQL SERVER 和ACCESS/excel的数据导入导出
  6. element 表格数据过多时 进行鼠标移上去展示全部
  7. oracle查询所有表名_oracle删错数据了,要跑路吗,等一下,先抢救一下
  8. tomcat启动报错:Address already in use: JVM_Bind
  9. Apk打包-apk的解压和压缩实验
  10. Office Scan(OSCE)10.0客户端手动卸载
  11. 《21天学通Java(第7版)》—— 1.10 练习
  12. DoS攻击原理与实战(LOIC+Hping3)
  13. html5使用mescroll
  14. 新东方的负载均衡架构探索和实践
  15. 案例|工业物联网解决方案•空调系统智能监控运维云平台
  16. 解决使用feign调用服务时携带token
  17. winedit使用教程_latex及winedit入门指导教程.pdf
  18. with dlz mysql 条件_BIND+DLZ+MYSQL
  19. 古琴怎么学,初学者应该这么练(一)
  20. java for 获取索引_获取Java列表中的对象索引

热门文章

  1. 去掉Holo主题下Dialog的蓝色线
  2. 计算机应用技术英语音标,英语国际音标
  3. 帕金森病会引起哪些并发症
  4. Python3-豆瓣电影影片差评和影片封面照片的爬取
  5. html本地站点建立代码,实验目的通过编写一小网页熟练HTML语言书写方法;学会建立本地站点.doc...
  6. catia v5r25 百度网盘_CATIA v5R21软件下载(sp0,p2及p3版)百度网盘
  7. Scratch不仅适合小朋友,程序员和大学老师都应该广泛使用!!!
  8. Echarts-gl geo3D设置regions区域高度
  9. 视频教程-深入学习matlab免疫算法7讲-Matlab
  10. HTML京东快报页面,京东快报.html