diff --git a/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts index 179815a397..b0d16adb0c 100644 --- a/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts +++ b/packages/hms-video-store/src/analytics/stats/BaseStatsAnalytics.ts @@ -85,6 +85,7 @@ export abstract class RunningTrackAnalytics { protected samples: (LocalBaseSample | LocalVideoSample | RemoteAudioSample | RemoteVideoSample)[] = []; protected tempStats: TempPublishStats[] = []; + protected prevLatestStat?: TempPublishStats; constructor({ track, @@ -118,6 +119,7 @@ export abstract class RunningTrackAnalytics { } this.samples.push(this.collateSample()); + this.prevLatestStat = this.getLatestStat(); this.tempStats.length = 0; } @@ -160,7 +162,7 @@ export abstract class RunningTrackAnalytics { } protected calculateDifferenceForSample(key: keyof TempPublishStats) { - const firstValue = Number(this.tempStats[0][key]) || 0; + const firstValue = Number(this.prevLatestStat?.[key]) || 0; const latestValue = Number(this.getLatestStat()[key]) || 0; return latestValue - firstValue; diff --git a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts index 5c44720388..1ca7567d5f 100644 --- a/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts +++ b/packages/hms-video-store/src/analytics/stats/SubscribeStatsAnalytics.ts @@ -92,12 +92,16 @@ export class SubscribeStatsAnalytics extends BaseStatsAnalytics { // eslint-disable-next-line complexity private calculateAvSyncForStat(trackStats: HMSTrackStats, hmsStats: HMSWebrtcStats) { - if (!trackStats.peerID || !(trackStats.kind === 'video')) { + if (!trackStats.peerID || !trackStats.estimatedPlayoutTimestamp || trackStats.kind !== 'video') { return; } const peer = this.store.getPeerById(trackStats.peerID); const audioTrack = peer?.audioTrack; const videoTrack = peer?.videoTrack; + /** + * 1. Send value as MAX_SAFE_INTEGER when either audio or value track is muted for the entire window + * 2. When both audio and video are unmuted for a part of window , then divide the difference by those many number of samples only + */ const areBothTracksEnabled = audioTrack && videoTrack && audioTrack.enabled && videoTrack.enabled; if (!areBothTracksEnabled) { return MAX_SAFE_INTEGER; @@ -106,7 +110,12 @@ export class SubscribeStatsAnalytics extends BaseStatsAnalytics { if (!audioStats) { return MAX_SAFE_INTEGER; } - return audioStats.timestamp - trackStats.timestamp; + if (!audioStats.estimatedPlayoutTimestamp) { + return; + } + + // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-estimatedplayouttimestamp + return audioStats.estimatedPlayoutTimestamp - trackStats.estimatedPlayoutTimestamp; } } @@ -119,14 +128,9 @@ class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { const baseSample = { timestamp: Date.now(), - fec_packets_discarded: this.calculateDifferenceForSample('fecPacketsDiscarded'), - fec_packets_received: this.calculateDifferenceForSample('fecPacketsReceived'), - total_samples_duration: this.calculateDifferenceForSample('totalSamplesDuration'), - total_packets_received: this.calculateDifferenceForSample('packetsReceived'), - total_packets_lost: this.calculateDifferenceForSample('packetsLost'), total_pli_count: this.calculateDifferenceForSample('pliCount'), total_nack_count: this.calculateDifferenceForSample('nackCount'), - avg_jitter_buffer_delay: this.calculateAverage('calculatedJitterBufferDelay'), + avg_jitter_buffer_delay: this.calculateAverage('calculatedJitterBufferDelay', false), }; if (latestStat.kind === 'video') { @@ -155,6 +159,11 @@ class RunningRemoteTrackAnalytics extends RunningTrackAnalytics { audio_concealed_samples, audio_total_samples_received: this.calculateDifferenceForSample('totalSamplesReceived'), audio_concealment_events: this.calculateDifferenceForSample('concealmentEvents'), + fec_packets_discarded: this.calculateDifferenceForSample('fecPacketsDiscarded'), + fec_packets_received: this.calculateDifferenceForSample('fecPacketsReceived'), + total_samples_duration: this.calculateDifferenceForSample('totalSamplesDuration'), + total_packets_received: this.calculateDifferenceForSample('packetsReceived'), + total_packets_lost: this.calculateDifferenceForSample('packetsLost'), }); } }; diff --git a/packages/hms-video-store/src/interfaces/webrtc-stats.ts b/packages/hms-video-store/src/interfaces/webrtc-stats.ts index edcf6452d5..05d5613e25 100644 --- a/packages/hms-video-store/src/interfaces/webrtc-stats.ts +++ b/packages/hms-video-store/src/interfaces/webrtc-stats.ts @@ -57,6 +57,7 @@ export interface MissingInboundStats extends RTCInboundRtpStreamStats, MissingCo totalFreezesDuration?: number; jitterBufferDelay?: number; jitterBufferEmittedCount?: number; + estimatedPlayoutTimestamp?: DOMHighResTimeStamp; } export type PeerConnectionType = 'publish' | 'subscribe'; diff --git a/packages/roomkit-react/src/Prebuilt/common/utils.js b/packages/roomkit-react/src/Prebuilt/common/utils.js index f49820e37b..10f5981840 100644 --- a/packages/roomkit-react/src/Prebuilt/common/utils.js +++ b/packages/roomkit-react/src/Prebuilt/common/utils.js @@ -112,7 +112,7 @@ export const checkCorrectAnswer = (answer, localPeerResponse, type) => { } }; -export const isValidTextInput = (text, minLength = 1, maxLength = 100) => { +export const isValidTextInput = (text, minLength = 1, maxLength = 1024) => { return text && text.length >= minLength && text.length <= maxLength; }; diff --git a/packages/roomkit-react/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx b/packages/roomkit-react/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx index 4b01c78070..88eed72912 100644 --- a/packages/roomkit-react/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx +++ b/packages/roomkit-react/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx @@ -33,7 +33,7 @@ export const MultipleChoiceOptions = ({ {options.map(option => { return ( - + {!isStopped || !isQuiz ? ( handleCheckedChange(checked, option.index)} css={{ cursor: canRespond ? 'pointer' : 'not-allowed', + flexShrink: 0, }} > diff --git a/packages/roomkit-react/src/Prebuilt/components/Polls/common/OptionInputWithDelete.tsx b/packages/roomkit-react/src/Prebuilt/components/Polls/common/OptionInputWithDelete.tsx index 5f4f4b9bbf..d4b5416ae4 100644 --- a/packages/roomkit-react/src/Prebuilt/components/Polls/common/OptionInputWithDelete.tsx +++ b/packages/roomkit-react/src/Prebuilt/components/Polls/common/OptionInputWithDelete.tsx @@ -28,6 +28,7 @@ export const OptionInputWithDelete = ({ value={option?.text || ''} key={index} onChange={event => handleOptionTextChange(index, event.target.value.trimStart())} + maxLength={250} /> removeOption(index)} css={{ bg: 'transparent', border: 'none' }}> diff --git a/packages/roomkit-react/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx b/packages/roomkit-react/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx index f821e3a39d..172bf70aa5 100644 --- a/packages/roomkit-react/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx +++ b/packages/roomkit-react/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx @@ -23,7 +23,7 @@ export const SingleChoiceOptions = ({ {options.map(option => { return ( - + {!isStopped || !isQuiz ? ( - +