diff --git a/packages/@webex/plugin-meetings/src/statsAnalyzer/index.ts b/packages/@webex/plugin-meetings/src/statsAnalyzer/index.ts index 7339e5d4e4d..84322630ba8 100644 --- a/packages/@webex/plugin-meetings/src/statsAnalyzer/index.ts +++ b/packages/@webex/plugin-meetings/src/statsAnalyzer/index.ts @@ -103,7 +103,7 @@ export class StatsAnalyzer extends EventsScope { this.networkQualityMonitor = networkQualityMonitor; this.correlationId = config.correlationId; this.mqaSentCount = -1; - this.lastMqaDataSent = {}; + this.lastMqaDataSent = {resolutions: {}}; this.lastEmittedStartStopEvent = {}; this.receiveSlotCallback = receiveSlotCallback; this.successfulCandidatePair = {}; @@ -152,6 +152,21 @@ export class StatsAnalyzer extends EventsScope { const newMqa = cloneDeep(emptyMqaInterval); Object.keys(this.statsResults).forEach((mediaType) => { + if (!this.lastMqaDataSent[mediaType]) { + this.lastMqaDataSent[mediaType] = {}; + this.lastMqaDataSent.resolutions[mediaType] = {}; + } + + if (!this.lastMqaDataSent[mediaType].send && mediaType.includes('-send')) { + this.lastMqaDataSent[mediaType].send = {}; + this.lastMqaDataSent.resolutions[mediaType].send = {}; + } + + if (!this.lastMqaDataSent[mediaType].recv && mediaType.includes('-recv')) { + this.lastMqaDataSent[mediaType].recv = {}; + this.lastMqaDataSent.resolutions[mediaType].recv = {}; + } + if (mediaType.includes('audio-send') || mediaType.includes('audio-share-send')) { const audioSender = cloneDeep(emptyAudioTransmit); @@ -162,6 +177,8 @@ export class StatsAnalyzer extends EventsScope { mediaType, }); newMqa.audioTransmit.push(audioSender); + + this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send); } else if (mediaType.includes('audio-recv') || mediaType.includes('audio-share-recv')) { const audioReceiver = cloneDeep(emptyAudioReceive); @@ -172,6 +189,8 @@ export class StatsAnalyzer extends EventsScope { mediaType, }); newMqa.audioReceive.push(audioReceiver); + + this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv); } else if (mediaType.includes('video-send') || mediaType.includes('video-share-send')) { const videoSender = cloneDeep(emptyVideoTransmit); @@ -182,6 +201,11 @@ export class StatsAnalyzer extends EventsScope { mediaType, }); newMqa.videoTransmit.push(videoSender); + + this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send); + this.lastMqaDataSent.resolutions[mediaType].send = cloneDeep( + this.statsResults.resolutions[mediaType].send + ); } else if (mediaType.includes('video-recv') || mediaType.includes('video-share-recv')) { const videoReceiver = cloneDeep(emptyVideoReceive); @@ -192,6 +216,11 @@ export class StatsAnalyzer extends EventsScope { mediaType, }); newMqa.videoReceive.push(videoReceiver); + + this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv); + this.lastMqaDataSent.resolutions[mediaType].recv = cloneDeep( + this.statsResults.resolutions[mediaType].recv + ); } }); diff --git a/packages/@webex/plugin-meetings/src/statsAnalyzer/mqaUtil.ts b/packages/@webex/plugin-meetings/src/statsAnalyzer/mqaUtil.ts index 7192d272bc8..90858bcfb48 100644 --- a/packages/@webex/plugin-meetings/src/statsAnalyzer/mqaUtil.ts +++ b/packages/@webex/plugin-meetings/src/statsAnalyzer/mqaUtil.ts @@ -134,9 +134,12 @@ export const getVideoReceiverMqa = ({videoReceiver, statsResults, lastMqaDataSen const lastPacketsReceived = lastMqaDataSent[mediaType]?.[sendrecvType].totalPacketsReceived || 0; const lastPacketsLost = lastMqaDataSent[mediaType]?.[sendrecvType].totalPacketsLost || 0; const lastBytesReceived = lastMqaDataSent[mediaType]?.[sendrecvType].totalBytesReceived || 0; - const lastFramesReceived = lastMqaDataSent[mediaType]?.[sendrecvType].framesReceived || 0; - const lastFramesDecoded = lastMqaDataSent[mediaType]?.[sendrecvType].framesDecoded || 0; - const lastFramesDropped = lastMqaDataSent[mediaType]?.[sendrecvType].framesDropped || 0; + const lastFramesReceived = + lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].framesReceived || 0; + const lastFramesDecoded = + lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].framesDecoded || 0; + const lastFramesDropped = + lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].framesDropped || 0; const lastKeyFramesDecoded = lastMqaDataSent[mediaType]?.[sendrecvType].keyFramesDecoded || 0; const lastPliCount = lastMqaDataSent[mediaType]?.[sendrecvType].totalPliCount || 0; @@ -190,12 +193,12 @@ export const getVideoReceiverMqa = ({videoReceiver, statsResults, lastMqaDataSen const totalFrameDecodedInaMin = statsResults.resolutions[mediaType][sendrecvType].framesDecoded - lastFramesDecoded; - videoReceiver.streams[0].common.receivedFrameRate = totalFrameReceivedInaMin - ? (totalFrameReceivedInaMin * 100) / 60 - : 0; - videoReceiver.streams[0].common.renderedFrameRate = totalFrameDecodedInaMin - ? (totalFrameDecodedInaMin * 100) / 60 - : 0; + videoReceiver.streams[0].common.receivedFrameRate = Math.round( + totalFrameReceivedInaMin ? totalFrameReceivedInaMin / 60 : 0 + ); + videoReceiver.streams[0].common.renderedFrameRate = Math.round( + totalFrameDecodedInaMin ? totalFrameDecodedInaMin / 60 : 0 + ); videoReceiver.streams[0].common.framesDropped = statsResults.resolutions[mediaType][sendrecvType].framesDropped - lastFramesDropped; @@ -220,9 +223,9 @@ export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, m lastMqaDataSent[mediaType]?.[sendrecvType].totalPacketsLostOnReceiver || 0; const lastBytesSent = lastMqaDataSent[mediaType]?.[sendrecvType].totalBytesSent || 0; const lastKeyFramesEncoded = - lastMqaDataSent[mediaType]?.[sendrecvType].totalKeyFramesEncoded || 0; + lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].totalKeyFramesEncoded || 0; const lastFirCount = lastMqaDataSent[mediaType]?.[sendrecvType].totalFirCount || 0; - const lastFramesSent = lastMqaDataSent[mediaType]?.[sendrecvType].framesSent || 0; + const lastFramesSent = lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].framesSent || 0; const {csi} = statsResults[mediaType]; if (csi && !videoSender.streams[0].common.csi.includes(csi)) { videoSender.streams[0].common.csi.push(csi); @@ -280,9 +283,9 @@ export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, m const totalFrameSentInaMin = statsResults.resolutions[mediaType][sendrecvType].framesSent - (lastFramesSent || 0); - videoSender.streams[0].common.transmittedFrameRate = totalFrameSentInaMin - ? (totalFrameSentInaMin * 100) / 60 - : 0; + videoSender.streams[0].common.transmittedFrameRate = Math.round( + totalFrameSentInaMin ? totalFrameSentInaMin / 60 : 0 + ); videoSender.streams[0].transmittedHeight = statsResults.resolutions[mediaType][sendrecvType].height || 0; videoSender.streams[0].transmittedWidth = diff --git a/packages/@webex/plugin-meetings/test/unit/spec/stats-analyzer/index.js b/packages/@webex/plugin-meetings/test/unit/spec/stats-analyzer/index.js index f9bac273083..5f0e0ead2d6 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/stats-analyzer/index.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/stats-analyzer/index.js @@ -179,7 +179,7 @@ describe('plugin-meetings', () => { report: [ { type: 'outbound-rtp', - framesSent: 0, + framesSent: 1500, bytesSent: 1, }, { @@ -209,7 +209,7 @@ describe('plugin-meetings', () => { bytesReceived: 1, frameHeight: 720, frameWidth: 1280, - framesReceived: 1, + framesReceived: 1500, }, { type: 'candidate-pair', @@ -437,6 +437,16 @@ describe('plugin-meetings', () => { assert.strictEqual(mqeData.intervalMetadata.peripherals.find((val) => val.name === MEDIA_DEVICES.MICROPHONE).information, _UNKNOWN_); assert.strictEqual(mqeData.intervalMetadata.peripherals.find((val) => val.name === MEDIA_DEVICES.CAMERA).information, _UNKNOWN_); }); + + it('emits the correct frameRate', async () => { + await startStatsAnalyzer({expected: {receiveVideo: true}}); + + await progressTime(); + assert.strictEqual(mqeData.videoReceive[0].streams[0].common.receivedFrameRate, 25); + fakeStats.video.receivers[0].framesReceived = 3000; + await progressTime(); + assert.strictEqual(mqeData.videoReceive[0].streams[0].common.receivedFrameRate, 25); + }); }); }); });