Skip to content

Commit

Permalink
Update publish-alpha (#2594)
Browse files Browse the repository at this point in the history
* fix: active speaker sorting in role prominence

* fix: update selected device css, mute all label

* fix: use prerelease (#2596)

* feat: add subscriber video stats to analytics

---------

Co-authored-by: Ravi theja <[email protected]>
Co-authored-by: Kaustubh Kumar <[email protected]>
Co-authored-by: Eswar Prasad Clinton. A <[email protected]>
  • Loading branch information
4 people authored Feb 27, 2024
1 parent 9eade91 commit 0dea2d8
Show file tree
Hide file tree
Showing 11 changed files with 95 additions and 19 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/alpha-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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({
Expand All @@ -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);
}
}
Expand All @@ -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 {
Expand All @@ -96,7 +122,21 @@ class RunningRemoteTrackAnalytics extends RunningTrackAnalytics {
};

if (latestStat.kind === 'video') {
return removeUndefinedFromObject<RemoteAudioSample | RemoteVideoSample>(baseSample);
return removeUndefinedFromObject<RemoteAudioSample | RemoteVideoSample>(
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) -
Expand Down Expand Up @@ -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;
}
}
7 changes: 7 additions & 0 deletions packages/hms-video-store/src/analytics/stats/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
7 changes: 7 additions & 0 deletions packages/hms-video-store/src/interfaces/webrtc-stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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';
Expand Down
8 changes: 4 additions & 4 deletions packages/hms-video-store/src/rtc-stats/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
);
};

Expand Down
2 changes: 2 additions & 0 deletions packages/hms-video-store/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ export const Options = ({
<Dropdown.Item
key={option.label}
css={{
backgroundColor: selectedDeviceId === option.deviceId ? '$surface_bright' : '$surface_dim',
backgroundColor: '$surface_dim',
p: '$4 $8',
h: '$15',
fontSize: '$xs',
justifyContent: 'space-between',
color: selectedDeviceId === option.deviceId ? '$primary_bright' : '',
}}
onClick={() => {
onClick(option.deviceId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam
<Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', false)}>
<MicOffIcon />
<Text variant="sm" css={optionTextCSS}>
Mute Audio
Mute Audio for All
</Text>
</Dropdown.Item>
) : null}
Expand All @@ -72,7 +72,7 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam
<Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', true)}>
<MicOnIcon />
<Text variant="sm" css={optionTextCSS}>
Unmute Audio
Unmute Audio for All
</Text>
</Dropdown.Item>
) : null}
Expand All @@ -85,7 +85,7 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam
<Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', false)}>
<VideoOffIcon />
<Text variant="sm" css={optionTextCSS}>
Mute Video
Mute Video for All
</Text>
</Dropdown.Item>
) : null}
Expand All @@ -94,7 +94,7 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam
<Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', true)}>
<VideoOnIcon />
<Text variant="sm" css={optionTextCSS}>
Unmute Video
Unmute Video for All
</Text>
</Dropdown.Item>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function RoleProminence({
edgeToEdge={edgeToEdge}
hasSidebar={layoutMode === LayoutMode.SIDEBAR}
/>
{isInsetEnabled && localPeer && !prominentPeers.includes(localPeer) && <InsetTile />}
{isInsetEnabled && localPeer && prominentPeers.length > 0 && !prominentPeers.includes(localPeer) && <InsetTile />}
</ProminenceLayout.Root>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ 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 || '')) {
acc[0].push(peer);
} else {
acc[1].push(peer);
}

return acc;
},
[[], []],
Expand Down

0 comments on commit 0dea2d8

Please sign in to comment.