From 6d34209cacc7951e132feef3499ab59030036b3d Mon Sep 17 00:00:00 2001 From: eswarclynn Date: Thu, 22 Feb 2024 12:21:05 +0530 Subject: [PATCH 1/6] fix: add subscriber video stats to analyics --- .../src/analytics/stats/BaseStatsAnalytics.ts | 5 +++++ .../src/analytics/stats/SubscribeStatsAnalytics.ts | 10 +++++++++- .../hms-video-store/src/analytics/stats/interfaces.ts | 7 +++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts index df5ac55f2b..936c249a91 100644 --- a/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts +++ b/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts @@ -158,6 +158,11 @@ export abstract class RunningTrackAnalytics { return latestValue - firstValue; } + protected calculateDifferenceAverage(key: keyof TempPublishStats, round = true) { + const avg = this.calculateDifferenceForSample(key) / this.tempStats.length; + return round ? Math.round(avg) : avg; + } + protected calculateInstancesOfHigh(key: keyof TempPublishStats, threshold: number) { const checkStat = this.getLatestStat()[key]; if (typeof checkStat !== 'number') { diff --git a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts index 181e77aeaf..e1bbf5cfa7 100644 --- a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts +++ b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts @@ -96,7 +96,15 @@ class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { }; if (latestStat.kind === 'video') { - return removeUndefinedFromObject(baseSample); + return removeUndefinedFromObject( + Object.assign(baseSample, { + avg_frames_received_per_sec: this.calculateDifferenceAverage('framesReceived'), + avg_frames_dropped_per_sec: this.calculateDifferenceAverage('framesDropped'), + avg_frames_decoded_per_sec: this.calculateDifferenceAverage('framesDecoded'), + frame_width: this.calculateAverage('frameWidth'), + frame_height: this.calculateAverage('frameHeight'), + }), + ); } else { const audio_concealed_samples = (latestStat.concealedSamples || 0) - diff --git a/packages/hms-video-store/src/analytics/stats/interfaces.ts b/packages/hms-video-store/src/analytics/stats/interfaces.ts index 6d3519ad9b..ad2d239582 100644 --- a/packages/hms-video-store/src/analytics/stats/interfaces.ts +++ b/packages/hms-video-store/src/analytics/stats/interfaces.ts @@ -87,4 +87,11 @@ export interface RemoteVideoSample extends RemoteBaseSample { avg_frames_decoded_per_sec?: number; total_pli_count?: number; total_nack_count?: number; + frame_width?: number; + frame_height?: number; + pause_count?: number; + pause_duration_seconds?: number; + freeze_count?: number; + freeze_duration_seconds?: number; + avg_jitter_buffer_delay?: number; } From 17811c5bd50c8e2d9c21cb51f38aafe5b72a93ff Mon Sep 17 00:00:00 2001 From: eswarclynn Date: Fri, 23 Feb 2024 14:44:48 +0530 Subject: [PATCH 2/6] feat: pause count, freeze count, jitter buffer delay --- .../src/analytics/stats/BaseStatsAnalytics.ts | 2 +- .../src/analytics/stats/SubscribeStatsAnalytics.ts | 13 +++++++++++-- .../hms-video-store/src/interfaces/webrtc-stats.ts | 6 ++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts index 936c249a91..480ffba9f5 100644 --- a/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts +++ b/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts @@ -68,7 +68,7 @@ export abstract class BaseStatsAnalytics { protected abstract handleStatsUpdate(hmsStats: HMSWebrtcStats): void; } -type TempPublishStats = HMSTrackStats & { availableOutgoingBitrate?: number }; +type TempPublishStats = HMSTrackStats & { availableOutgoingBitrate?: number; calculatedJitterBufferDelay?: number }; export abstract class RunningTrackAnalytics { readonly sampleWindowSize: number; diff --git a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts index e1bbf5cfa7..fe84d5230b 100644 --- a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts +++ b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts @@ -49,8 +49,12 @@ export class SubscribeStatsAnalytics extends BaseStatsAnalytics { Object.keys(remoteTracksStats).forEach(trackID => { const trackStats = remoteTracksStats[trackID]; const track = this.store.getTrackById(trackID); + const calculatedJitterBufferDelay = + trackStats.jitterBufferDelay && + trackStats.jitterBufferEmittedCount && + trackStats.jitterBufferDelay / trackStats.jitterBufferEmittedCount; if (this.trackAnalytics.has(trackID)) { - this.trackAnalytics.get(trackID)?.pushTempStat({ ...trackStats }); + this.trackAnalytics.get(trackID)?.pushTempStat({ ...trackStats, calculatedJitterBufferDelay }); } else { if (track) { const trackAnalytics = new RunningRemoteTrackAnalytics({ @@ -59,7 +63,7 @@ export class SubscribeStatsAnalytics extends BaseStatsAnalytics { ssrc: trackStats.ssrc.toString(), kind: trackStats.kind, }); - trackAnalytics.pushTempStat({ ...trackStats }); + trackAnalytics.pushTempStat({ ...trackStats, calculatedJitterBufferDelay }); this.trackAnalytics.set(trackID, trackAnalytics); } } @@ -103,6 +107,11 @@ class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { avg_frames_decoded_per_sec: this.calculateDifferenceAverage('framesDecoded'), frame_width: this.calculateAverage('frameWidth'), frame_height: this.calculateAverage('frameHeight'), + pause_count: this.calculateDifferenceForSample('pauseCount'), + pause_duration_seconds: this.calculateDifferenceForSample('totalPausesDuration'), + freeze_count: this.calculateDifferenceForSample('freezeCount'), + freeze_duration_seconds: this.calculateDifferenceForSample('totalFreezesDuration'), + avg_jitter_buffer_delay: this.calculateAverage('calculatedJitterBufferDelay'), }), ); } else { diff --git a/packages/hms-video-store/src/interfaces/webrtc-stats.ts b/packages/hms-video-store/src/interfaces/webrtc-stats.ts index 44b8631c1e..b1487169aa 100644 --- a/packages/hms-video-store/src/interfaces/webrtc-stats.ts +++ b/packages/hms-video-store/src/interfaces/webrtc-stats.ts @@ -50,6 +50,12 @@ export interface MissingInboundStats extends RTCInboundRtpStreamStats, MissingCo fecPacketsDiscarded?: number; fecPacketsReceived?: number; totalSamplesDuration?: number; + pauseCount?: number; + totalPausesDuration?: number; + freezeCount?: number; + totalFreezesDuration?: number; + jitterBufferDelay?: number; + jitterBufferEmittedCount?: number; } export type PeerConnectionType = 'publish' | 'subscribe'; From 9e6ef7f2acc2548b74a35ea566533e8091a17d31 Mon Sep 17 00:00:00 2001 From: eswarclynn Date: Mon, 26 Feb 2024 09:03:38 +0530 Subject: [PATCH 3/6] fix: add missing prop framesReceived to stats interface --- packages/hms-video-store/src/interfaces/webrtc-stats.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/hms-video-store/src/interfaces/webrtc-stats.ts b/packages/hms-video-store/src/interfaces/webrtc-stats.ts index b1487169aa..edcf6452d5 100644 --- a/packages/hms-video-store/src/interfaces/webrtc-stats.ts +++ b/packages/hms-video-store/src/interfaces/webrtc-stats.ts @@ -37,6 +37,7 @@ interface MissingOutboundStats extends RTCOutboundRtpStreamStats, MissingCommonS export interface MissingInboundStats extends RTCInboundRtpStreamStats, MissingCommonStats { bytesReceived?: number; + framesReceived?: number; framesDropped?: number; jitter?: number; packetsLost?: number; From a58090125cc7944352fac77df52c966f52ee838f Mon Sep 17 00:00:00 2001 From: eswarclynn Date: Mon, 26 Feb 2024 15:11:09 +0530 Subject: [PATCH 4/6] feat: avg_av_sync_ms prop in subscriber.stats --- .../src/analytics/stats/BaseStatsAnalytics.ts | 6 ++- .../stats/SubscribeStatsAnalytics.ts | 40 +++++++++++++++++-- .../hms-video-store/src/rtc-stats/utils.ts | 10 +++-- .../hms-video-store/src/utils/constants.ts | 2 + 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts index 480ffba9f5..6a0dc26cc3 100644 --- a/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts +++ b/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts @@ -68,7 +68,11 @@ export abstract class BaseStatsAnalytics { protected abstract handleStatsUpdate(hmsStats: HMSWebrtcStats): void; } -type TempPublishStats = HMSTrackStats & { availableOutgoingBitrate?: number; calculatedJitterBufferDelay?: number }; +type TempPublishStats = HMSTrackStats & { + availableOutgoingBitrate?: number; + calculatedJitterBufferDelay?: number; + avSync?: number; +}; export abstract class RunningTrackAnalytics { readonly sampleWindowSize: number; diff --git a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts index fe84d5230b..a52a316478 100644 --- a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts +++ b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts @@ -12,8 +12,9 @@ import { RemoteVideoTrackAnalytics, SubscribeAnalyticPayload, } from './interfaces'; +import { HMSTrackStats } from '../../interfaces'; import { HMSWebrtcStats } from '../../rtc-stats'; -import { SUBSCRIBE_STATS_SAMPLE_WINDOW } from '../../utils/constants'; +import { JAVA_INTEGER_MAX_VALUE, SUBSCRIBE_STATS_SAMPLE_WINDOW } from '../../utils/constants'; import AnalyticsEventFactory from '../AnalyticsEventFactory'; export class SubscribeStatsAnalytics extends BaseStatsAnalytics { @@ -53,8 +54,10 @@ export class SubscribeStatsAnalytics extends BaseStatsAnalytics { trackStats.jitterBufferDelay && trackStats.jitterBufferEmittedCount && trackStats.jitterBufferDelay / trackStats.jitterBufferEmittedCount; + + const avSync = this.calculateAvSyncForStat(trackStats, hmsStats); if (this.trackAnalytics.has(trackID)) { - this.trackAnalytics.get(trackID)?.pushTempStat({ ...trackStats, calculatedJitterBufferDelay }); + this.trackAnalytics.get(trackID)?.pushTempStat({ ...trackStats, calculatedJitterBufferDelay, avSync }); } else { if (track) { const trackAnalytics = new RunningRemoteTrackAnalytics({ @@ -63,7 +66,7 @@ export class SubscribeStatsAnalytics extends BaseStatsAnalytics { ssrc: trackStats.ssrc.toString(), kind: trackStats.kind, }); - trackAnalytics.pushTempStat({ ...trackStats, calculatedJitterBufferDelay }); + trackAnalytics.pushTempStat({ ...trackStats, calculatedJitterBufferDelay, avSync }); this.trackAnalytics.set(trackID, trackAnalytics); } } @@ -79,6 +82,25 @@ export class SubscribeStatsAnalytics extends BaseStatsAnalytics { }); } } + + // eslint-disable-next-line complexity + private calculateAvSyncForStat(trackStats: HMSTrackStats, hmsStats: HMSWebrtcStats) { + if (!trackStats.peerID || !(trackStats.kind === 'video')) { + return; + } + const peer = this.store.getPeerById(trackStats.peerID); + const audioTrack = peer?.audioTrack; + const videoTrack = peer?.videoTrack; + const areBothTracksEnabled = audioTrack && videoTrack && audioTrack.enabled && videoTrack.enabled; + if (!areBothTracksEnabled) { + return JAVA_INTEGER_MAX_VALUE; + } + const audioStats = hmsStats.getRemoteTrackStats(audioTrack.trackId); + if (!audioStats) { + return JAVA_INTEGER_MAX_VALUE; + } + return audioStats.timestamp - trackStats.timestamp; + } } class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { @@ -102,6 +124,7 @@ class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { if (latestStat.kind === 'video') { return removeUndefinedFromObject( Object.assign(baseSample, { + avg_av_sync_ms: this.calculateAvgAvSyncForSample(), avg_frames_received_per_sec: this.calculateDifferenceAverage('framesReceived'), avg_frames_dropped_per_sec: this.calculateDifferenceAverage('framesDropped'), avg_frames_decoded_per_sec: this.calculateDifferenceAverage('framesDecoded'), @@ -152,4 +175,15 @@ class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { samples: this.samples, }; }; + + private calculateAvgAvSyncForSample() { + const avSyncValues = this.tempStats.map(stat => stat.avSync); + const validAvSyncValues: number[] = avSyncValues.filter( + (value): value is number => value !== undefined && value !== JAVA_INTEGER_MAX_VALUE, + ); + if (validAvSyncValues.length === 0) { + return JAVA_INTEGER_MAX_VALUE; + } + return validAvSyncValues.reduce((a, b) => a + b, 0) / validAvSyncValues.length; + } } diff --git a/packages/hms-video-store/src/rtc-stats/utils.ts b/packages/hms-video-store/src/rtc-stats/utils.ts index 35d63a3f7f..608e46ca4c 100644 --- a/packages/hms-video-store/src/rtc-stats/utils.ts +++ b/packages/hms-video-store/src/rtc-stats/utils.ts @@ -59,6 +59,7 @@ export const getLocalTrackStats = async ( trackStats[stat] = { ...out, bitrate: computeBitrate('bytesSent', out, prevTrackStats?.[stat]), + trackIdentifier: inStats?.trackIdentifier, packetsLost: inStats?.packetsLost, jitter: inStats?.jitter, roundTripTime: inStats?.roundTripTime, @@ -116,15 +117,16 @@ export const getTrackStats = async ( } return ( - trackStats && - Object.assign(trackStats, { + trackStats && { + ...trackStats, + trackIdentifier: track.trackId, bitrate, packetsLostRate, - peerId: track.peerId, + peerID: track.peerId, enabled: track.enabled, peerName, codec: trackStats.codec, - }) + } ); }; diff --git a/packages/hms-video-store/src/utils/constants.ts b/packages/hms-video-store/src/utils/constants.ts index 03c3756d7b..f821d05f31 100644 --- a/packages/hms-video-store/src/utils/constants.ts +++ b/packages/hms-video-store/src/utils/constants.ts @@ -34,6 +34,8 @@ export const PUBLISH_STATS_PUSH_INTERVAL = 300; export const SUBSCRIBE_STATS_SAMPLE_WINDOW = 10; export const SUBSCRIBE_STATS_PUSH_INTERVAL = 60; +export const JAVA_INTEGER_MAX_VALUE = 2147483647; + export const HMSEvents = { DEVICE_CHANGE: 'device-change', LOCAL_AUDIO_ENABLED: 'local-audio-enabled', From 02b4dd18ceafde3e9d8641a89299ddf49b16e802 Mon Sep 17 00:00:00 2001 From: eswarclynn Date: Mon, 26 Feb 2024 15:21:36 +0530 Subject: [PATCH 5/6] fix: webrtc stats type revert --- packages/hms-video-store/src/rtc-stats/utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/hms-video-store/src/rtc-stats/utils.ts b/packages/hms-video-store/src/rtc-stats/utils.ts index 608e46ca4c..8e6c1a9232 100644 --- a/packages/hms-video-store/src/rtc-stats/utils.ts +++ b/packages/hms-video-store/src/rtc-stats/utils.ts @@ -59,7 +59,6 @@ export const getLocalTrackStats = async ( trackStats[stat] = { ...out, bitrate: computeBitrate('bytesSent', out, prevTrackStats?.[stat]), - trackIdentifier: inStats?.trackIdentifier, packetsLost: inStats?.packetsLost, jitter: inStats?.jitter, roundTripTime: inStats?.roundTripTime, @@ -119,7 +118,6 @@ export const getTrackStats = async ( return ( trackStats && { ...trackStats, - trackIdentifier: track.trackId, bitrate, packetsLostRate, peerID: track.peerId, From e065090a68d428aa7eb1d0ab2b4899326c446f35 Mon Sep 17 00:00:00 2001 From: eswarclynn Date: Tue, 27 Feb 2024 13:51:06 +0530 Subject: [PATCH 6/6] fix: use math.pow for max_safe_integer --- .../src/analytics/stats/SubscribeStatsAnalytics.ts | 10 +++++----- packages/hms-video-store/src/utils/constants.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts index a52a316478..cc807977a2 100644 --- a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts +++ b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts @@ -14,7 +14,7 @@ import { } from './interfaces'; import { HMSTrackStats } from '../../interfaces'; import { HMSWebrtcStats } from '../../rtc-stats'; -import { JAVA_INTEGER_MAX_VALUE, SUBSCRIBE_STATS_SAMPLE_WINDOW } from '../../utils/constants'; +import { MAX_SAFE_INTEGER, SUBSCRIBE_STATS_SAMPLE_WINDOW } from '../../utils/constants'; import AnalyticsEventFactory from '../AnalyticsEventFactory'; export class SubscribeStatsAnalytics extends BaseStatsAnalytics { @@ -93,11 +93,11 @@ export class SubscribeStatsAnalytics extends BaseStatsAnalytics { const videoTrack = peer?.videoTrack; const areBothTracksEnabled = audioTrack && videoTrack && audioTrack.enabled && videoTrack.enabled; if (!areBothTracksEnabled) { - return JAVA_INTEGER_MAX_VALUE; + return MAX_SAFE_INTEGER; } const audioStats = hmsStats.getRemoteTrackStats(audioTrack.trackId); if (!audioStats) { - return JAVA_INTEGER_MAX_VALUE; + return MAX_SAFE_INTEGER; } return audioStats.timestamp - trackStats.timestamp; } @@ -179,10 +179,10 @@ class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { private calculateAvgAvSyncForSample() { const avSyncValues = this.tempStats.map(stat => stat.avSync); const validAvSyncValues: number[] = avSyncValues.filter( - (value): value is number => value !== undefined && value !== JAVA_INTEGER_MAX_VALUE, + (value): value is number => value !== undefined && value !== MAX_SAFE_INTEGER, ); if (validAvSyncValues.length === 0) { - return JAVA_INTEGER_MAX_VALUE; + return MAX_SAFE_INTEGER; } return validAvSyncValues.reduce((a, b) => a + b, 0) / validAvSyncValues.length; } diff --git a/packages/hms-video-store/src/utils/constants.ts b/packages/hms-video-store/src/utils/constants.ts index f821d05f31..2d3f72c608 100644 --- a/packages/hms-video-store/src/utils/constants.ts +++ b/packages/hms-video-store/src/utils/constants.ts @@ -34,7 +34,7 @@ export const PUBLISH_STATS_PUSH_INTERVAL = 300; export const SUBSCRIBE_STATS_SAMPLE_WINDOW = 10; export const SUBSCRIBE_STATS_PUSH_INTERVAL = 60; -export const JAVA_INTEGER_MAX_VALUE = 2147483647; +export const MAX_SAFE_INTEGER = Math.pow(2, 31) - 1; export const HMSEvents = { DEVICE_CHANGE: 'device-change',