From 33aebea4aac7bacc901de120b49df6cbabfb7d8c Mon Sep 17 00:00:00 2001 From: David Novicki Date: Fri, 21 Sep 2018 09:49:28 -0700 Subject: [PATCH] feat: added support for video bandwidth from getStats() --- src/js/rtc_session.js | 17 +++--- src/js/rtp-stats.js | 119 +++++++++++++++++++++++++++++++----------- 2 files changed, 100 insertions(+), 36 deletions(-) diff --git a/src/js/rtc_session.js b/src/js/rtc_session.js index ccce1b8..6b5b293 100644 --- a/src/js/rtc_session.js +++ b/src/js/rtc_session.js @@ -895,9 +895,9 @@ export default class RtcSession { var timestamp = new Date(); var impl = async (stream, streamType) => { - var tracks = []; + let tracks = []; - if (! stream) { + if (!stream) { return []; } @@ -910,13 +910,16 @@ export default class RtcSession { case 'video_output': tracks = stream.getVideoTracks(); break; + case 'video_bandwidth': + tracks = stream.getVideoTracks(); + break; default: throw new Error('Unsupported stream type while trying to get stats: ' + streamType); } return await Promise.all(tracks.map(async (track) => { - var rawStats = await this._pc.getStats(track); - var digestedStats = extractMediaStatsFromStats(timestamp, rawStats, streamType); + const rawStats = await this._pc.getStats(track); + const digestedStats = extractMediaStatsFromStats(timestamp, rawStats, streamType); if (! digestedStats) { throw new Error('Failed to extract MediaRtpStats from RTCStatsReport for stream type ' + streamType); } @@ -933,8 +936,10 @@ export default class RtcSession { video: { input: await impl(this._remoteVideoStream, 'video_input'), - output: await impl(this._localStream, 'video_output') - } + output: await impl(this._localStream, 'video_output'), + // Choose either stream as the underlying data is the same + bandwidth: await impl(this._remoteVideoStream || this._localStream, 'video_bandwidth'), + }, }; // For consistency's sake, coalesce rttMilliseconds into the output for audio and video. diff --git a/src/js/rtp-stats.js b/src/js/rtp-stats.js index 640aa53..7dea634 100644 --- a/src/js/rtp-stats.js +++ b/src/js/rtp-stats.js @@ -5,16 +5,17 @@ */ import { is_defined, when_defined } from './utils'; + export function extractMediaStatsFromStats(timestamp, stats, streamType) { - var extractedStats = null; + let extractedStats = null; - for (var key in stats) { + for (let key in stats) { var statsReport = stats[key]; if (statsReport) { if (statsReport.type === 'ssrc') { //chrome, opera case. chrome reports stats for all streams, not just the stream passed in. if (is_defined(statsReport.packetsSent) && statsReport.mediaType == 'audio' && streamType === 'audio_input') { - extractedStats = { + extractedStats = new MediaRtpStats({ timestamp: timestamp, packetsCount: statsReport.packetsSent, bytesSent: statsReport.bytesSent, @@ -22,10 +23,10 @@ export function extractMediaStatsFromStats(timestamp, stats, streamType) { packetsLost: is_defined(statsReport.packetsLost) ? Math.max(0, statsReport.packetsLost) : 0, procMilliseconds: is_defined(statsReport.googCurrentDelayMs), rttMilliseconds: when_defined(statsReport.googRtt) - }; + }, statsReport.type, streamType); } else if (is_defined(statsReport.packetsReceived) && statsReport.mediaType == 'audio' && streamType === 'audio_output') { - extractedStats = { + extractedStats = new MediaRtpStats({ timestamp: timestamp, packetsCount: statsReport.packetsReceived, bytesReceived: statsReport.bytesReceived, @@ -33,10 +34,10 @@ export function extractMediaStatsFromStats(timestamp, stats, streamType) { packetsLost: is_defined(statsReport.packetsLost) ? Math.max(0, statsReport.packetsLost) : 0, procMilliseconds: is_defined(statsReport.googCurrentDelayMs), jbMilliseconds: when_defined(statsReport.googJitterBufferMs) - }; + }, statsReport.type, streamType); } else if (is_defined(statsReport.packetsSent) && statsReport.mediaType == 'video' && streamType === 'video_input') { - extractedStats = { + extractedStats = new MediaRtpStats({ timestamp: timestamp, packetsCount: statsReport.packetsSent, bytesSent: statsReport.bytesSent, @@ -44,10 +45,10 @@ export function extractMediaStatsFromStats(timestamp, stats, streamType) { rttMilliseconds: when_defined(statsReport.googRtt), procMilliseconds: is_defined(statsReport.googCurrentDelayMs), frameRateSent: when_defined(statsReport.googFrameRateSent), - }; + }, statsReport.type, streamType); } else if (typeof statsReport.packetsReceived !== 'undefined' && statsReport.mediaType == 'video' && streamType === 'video_output') { - extractedStats = { + extractedStats = new MediaRtpStats({ timestamp: timestamp, packetsCount: statsReport.packetsSent, bytesReceived: statsReport.bytesReceived, @@ -55,36 +56,70 @@ export function extractMediaStatsFromStats(timestamp, stats, streamType) { frameRateReceived: when_defined(statsReport.googFrameRateReceived), procMilliseconds: is_defined(statsReport.googCurrentDelayMs), jbMilliseconds: when_defined(statsReport.googJitterBufferMs) - }; + }, statsReport.type, streamType); } } else if (statsReport.type === 'inboundrtp') { // Firefox case. Firefox reports packetsLost parameter only in inboundrtp type, and doesn't report in outboundrtp type. // So we only pull from inboundrtp. Firefox reports only stats for the stream passed in. if (is_defined(statsReport.packetsLost) && is_defined(statsReport.packetsReceived)) { - extractedStats = { + extractedStats = new MediaRtpStats({ packetsLost: statsReport.packetsLost, packetsCount: statsReport.packetsReceived, audioLevel: when_defined(statsReport.audioInputLevel), - rttMilliseconds: streamType === 'audio_ouptut' || streamType === 'video_output' ? when_defined(statsReport.roundTripTime) : null, + rttMilliseconds: streamType === 'audio_output' || streamType === 'video_output' ? when_defined(statsReport.roundTripTime) : null, jbMilliseconds: streamType === 'audio_output' || streamType === 'video_output' ? when_defined(statsReport.jitter, 0) * 1000 : null - }; + }, statsReport.type, streamType); + } + } else if (statsReport.type === 'VideoBwe') { + if (streamType === 'video_bandwidth') { + extractedStats = new MediaBWEForVideoStats({ + timestamp: timestamp, + encBitrate: statsReport.googActualEncBitrate, + availReceiveBW: statsReport.googAvailableReceiveBandwidth, + availSendBW: statsReport.googAvailableSendBandwidth, + bucketDelay: statsReport.googBucketDelay, + retransmitBitrate: statsReport.googRetransmitBitrate, + targetEncBitrate: statsReport.googTargetEncBitrate, + transmitBitrate: statsReport.googTransmitBitrate + }, statsReport.type, streamType); } } } } + return extractedStats ? extractedStats : null; +} - return extractedStats ? new MediaRtpStats(extractedStats, statsReport.type, streamType) : null; +/** +* Basic statistics object, represents core properties of any statistic +*/ +class BaseStats { + constructor(params = {}, statsReportType, streamType) { + this._timestamp = params.timestamp || new Date().getTime(); + this._statsReportType = statsReportType || params._statsReportType || "unknown"; + this._streamType = streamType || params.streamType || "unknown"; + } + /** Timestamp when stats are collected. */ + get timestamp() { + return this._timestamp; + } + /** {string} the type of the stats report */ + get statsReportType() { + return this._statsReportType; + } + /** {string} the type of the stream */ + get streamType() { + return this._streamType; + } } /** * Basic RTP statistics object, represents statistics of an audio or video stream. */ -class MediaRtpStats { - constructor(paramsIn, statsReportType, streamType) { - var params = paramsIn || {}; +class MediaRtpStats extends BaseStats { + constructor(params = {}, statsReportType, streamType) { + super(params, statsReportType, streamType); - this._timestamp = params.timestamp || new Date().getTime(); this._packetsLost = when_defined(params.packetsLost); this._packetsCount = when_defined(params.packetsCount); this._audioLevel = when_defined(params.audioLevel); @@ -96,8 +131,6 @@ class MediaRtpStats { this._framesDecoded = when_defined(params.framesDecoded); this._frameRateSent = when_defined(params.frameRateSent); this._frameRateReceived = when_defined(params.frameRateReceived); - this._statsReportType = statsReportType || params._statsReportType || "unknown"; - this._streamType = streamType || params.streamType || "unknown"; } /** {number} number of packets sent to the channel */ @@ -118,10 +151,6 @@ class MediaRtpStats { get audioLevel() { return this._audioLevel; } - /** Timestamp when stats are collected. */ - get timestamp() { - return this._timestamp; - } /** {number} Round trip time calculated with RTCP reports */ get rttMilliseconds() { return this._rttMilliseconds; @@ -154,12 +183,42 @@ class MediaRtpStats { get frameRateReceived() { return this._frameRateReceived; } - /** {string} the type of the stats report */ - get statsReportType() { - return this._statsReportType; +} + +/** +* Basic BWEForVideo statistics object, represents statistics of an audio or video stream. +*/ +class MediaBWEForVideoStats extends BaseStats { + constructor(params = {}, statsReportType, streamType) { + super(params, statsReportType, streamType); + + this._encBitrate = when_defined(params.encBitrate); + this._availReceiveBW = when_defined(params.availReceiveBW); + this._availSendBW = when_defined(params.availSendBW); + this._bucketDelay = when_defined(params.bucketDelay); + this._retransmitBitrate = when_defined(params.retransmitBitrate); + this._targetEncBitrate = when_defined(params.targetEncBitrate); + this._transmitBitrate = when_defined(params.transmitBitrate); } - /** {string} the type of the stream */ - get streamType() { - return this._streamType; + get encBitrate() { + return this._encBitrate; + } + get availReceiveBW() { + return this._availReceiveBW; + } + get availSendBW() { + return this._availSendBW; + } + get bucketDelay() { + return this._bucketDelay; + } + get retransmitBitrate() { + return this._retransmitBitrate; + } + get targetEncBitrate() { + return this._targetEncBitrate; + } + get transmitBitrate() { + return this._transmitBitrate; } }