diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml index bbe49b9924..f51b1ea521 100644 --- a/.github/workflows/alpha-release.yml +++ b/.github/workflows/alpha-release.yml @@ -22,12 +22,10 @@ jobs: run: yarn install --frozen-lockfile - name: Update versions - env: - BUMP: ${{ github.event.inputs.versionBump }} run: | yarn global add lerna@5 lerna -v - echo $(lerna version $BUMP --no-git-tag-version --exact --yes --no-private) + echo $(lerna version prerelease --no-git-tag-version --exact --yes --no-private) lerna add @100mslive/hms-video-store --peer --scope=@100mslive/hms-virtual-background --exact lerna add @100mslive/roomkit-react --scope=prebuilt-react-integration --exact diff --git a/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts index df5ac55f2b..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 }; +type TempPublishStats = HMSTrackStats & { + availableOutgoingBitrate?: number; + calculatedJitterBufferDelay?: number; + avSync?: number; +}; export abstract class RunningTrackAnalytics { readonly sampleWindowSize: number; @@ -158,6 +162,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..cc807977a2 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 { MAX_SAFE_INTEGER, SUBSCRIBE_STATS_SAMPLE_WINDOW } from '../../utils/constants'; import AnalyticsEventFactory from '../AnalyticsEventFactory'; export class SubscribeStatsAnalytics extends BaseStatsAnalytics { @@ -49,8 +50,14 @@ 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; + + const avSync = this.calculateAvSyncForStat(trackStats, hmsStats); if (this.trackAnalytics.has(trackID)) { - this.trackAnalytics.get(trackID)?.pushTempStat({ ...trackStats }); + this.trackAnalytics.get(trackID)?.pushTempStat({ ...trackStats, calculatedJitterBufferDelay, avSync }); } else { if (track) { const trackAnalytics = new RunningRemoteTrackAnalytics({ @@ -59,7 +66,7 @@ export class SubscribeStatsAnalytics extends BaseStatsAnalytics { ssrc: trackStats.ssrc.toString(), kind: trackStats.kind, }); - trackAnalytics.pushTempStat({ ...trackStats }); + trackAnalytics.pushTempStat({ ...trackStats, calculatedJitterBufferDelay, avSync }); this.trackAnalytics.set(trackID, trackAnalytics); } } @@ -75,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 MAX_SAFE_INTEGER; + } + const audioStats = hmsStats.getRemoteTrackStats(audioTrack.trackId); + if (!audioStats) { + return MAX_SAFE_INTEGER; + } + return audioStats.timestamp - trackStats.timestamp; + } } class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { @@ -96,7 +122,21 @@ class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { }; if (latestStat.kind === 'video') { - return removeUndefinedFromObject(baseSample); + 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'), + 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 { const audio_concealed_samples = (latestStat.concealedSamples || 0) - @@ -135,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 !== MAX_SAFE_INTEGER, + ); + if (validAvSyncValues.length === 0) { + return MAX_SAFE_INTEGER; + } + return validAvSyncValues.reduce((a, b) => a + b, 0) / validAvSyncValues.length; + } } 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; } diff --git a/packages/hms-video-store/src/interfaces/webrtc-stats.ts b/packages/hms-video-store/src/interfaces/webrtc-stats.ts index 44b8631c1e..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; @@ -50,6 +51,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'; diff --git a/packages/hms-video-store/src/rtc-stats/utils.ts b/packages/hms-video-store/src/rtc-stats/utils.ts index 35d63a3f7f..8e6c1a9232 100644 --- a/packages/hms-video-store/src/rtc-stats/utils.ts +++ b/packages/hms-video-store/src/rtc-stats/utils.ts @@ -116,15 +116,15 @@ export const getTrackStats = async ( } return ( - trackStats && - Object.assign(trackStats, { + trackStats && { + ...trackStats, 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..2d3f72c608 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 MAX_SAFE_INTEGER = Math.pow(2, 31) - 1; + export const HMSEvents = { DEVICE_CHANGE: 'device-change', LOCAL_AUDIO_ENABLED: 'local-audio-enabled', diff --git a/packages/roomkit-react/src/Prebuilt/components/AudioVideoToggle.tsx b/packages/roomkit-react/src/Prebuilt/components/AudioVideoToggle.tsx index 6184ca6aa9..8fc403db76 100644 --- a/packages/roomkit-react/src/Prebuilt/components/AudioVideoToggle.tsx +++ b/packages/roomkit-react/src/Prebuilt/components/AudioVideoToggle.tsx @@ -57,11 +57,12 @@ export const Options = ({ { onClick(option.deviceId); diff --git a/packages/roomkit-react/src/Prebuilt/components/Footer/RoleOptions.tsx b/packages/roomkit-react/src/Prebuilt/components/Footer/RoleOptions.tsx index 1e1a15585f..3234fb4e45 100644 --- a/packages/roomkit-react/src/Prebuilt/components/Footer/RoleOptions.tsx +++ b/packages/roomkit-react/src/Prebuilt/components/Footer/RoleOptions.tsx @@ -63,7 +63,7 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam setTrackEnabled('audio', false)}> - Mute Audio + Mute Audio for All ) : null} @@ -72,7 +72,7 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam setTrackEnabled('audio', true)}> - Unmute Audio + Unmute Audio for All ) : null} @@ -85,7 +85,7 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam setTrackEnabled('video', false)}> - Mute Video + Mute Video for All ) : null} @@ -94,7 +94,7 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam setTrackEnabled('video', true)}> - Unmute Video + Unmute Video for All ) : null} diff --git a/packages/roomkit-react/src/Prebuilt/components/VideoLayouts/RoleProminence.tsx b/packages/roomkit-react/src/Prebuilt/components/VideoLayouts/RoleProminence.tsx index 516d212806..ce1a98e487 100644 --- a/packages/roomkit-react/src/Prebuilt/components/VideoLayouts/RoleProminence.tsx +++ b/packages/roomkit-react/src/Prebuilt/components/VideoLayouts/RoleProminence.tsx @@ -67,7 +67,7 @@ export function RoleProminence({ edgeToEdge={edgeToEdge} hasSidebar={layoutMode === LayoutMode.SIDEBAR} /> - {isInsetEnabled && localPeer && !prominentPeers.includes(localPeer) && } + {isInsetEnabled && localPeer && prominentPeers.length > 0 && !prominentPeers.includes(localPeer) && } ); } diff --git a/packages/roomkit-react/src/Prebuilt/components/hooks/useRoleProminencePeers.tsx b/packages/roomkit-react/src/Prebuilt/components/hooks/useRoleProminencePeers.tsx index 7b058098b9..4ff5bfecaa 100644 --- a/packages/roomkit-react/src/Prebuilt/components/hooks/useRoleProminencePeers.tsx +++ b/packages/roomkit-react/src/Prebuilt/components/hooks/useRoleProminencePeers.tsx @@ -17,7 +17,7 @@ export const useRoleProminencePeers = (prominentRoles: string[], peers: HMSPeer[ } return acc; } - if (peer.isLocal && isInsetEnabled) { + if (peer.isLocal && isInsetEnabled && !prominentRoles?.includes(peer.roleName || '')) { return acc; } if (prominentRoles?.includes(peer.roleName || '')) { @@ -25,6 +25,7 @@ export const useRoleProminencePeers = (prominentRoles: string[], peers: HMSPeer[ } else { acc[1].push(peer); } + return acc; }, [[], []],