From 0366dea31fe6f8e312eee31a00ddb8a7ec014027 Mon Sep 17 00:00:00 2001 From: Kristian Martinoski Date: Fri, 25 Oct 2024 11:09:19 +0200 Subject: [PATCH] feat: add session and livestream call durations --- .../03-call-and-participant-state.mdx | 3 +- packages/client/src/store/CallState.ts | 105 ++++++++++++++---- .../src/hooks/callStateHooks.ts | 18 ++- .../03-core/03-call-and-participant-state.mdx | 3 +- .../03-call-and-participant-state.mdx | 2 + .../components/ActiveCallHeader.tsx | 7 +- 6 files changed, 107 insertions(+), 31 deletions(-) diff --git a/packages/client/docusaurus/docs/javascript/02-guides/03-call-and-participant-state.mdx b/packages/client/docusaurus/docs/javascript/02-guides/03-call-and-participant-state.mdx index eb6d16b948..55ad13a356 100644 --- a/packages/client/docusaurus/docs/javascript/02-guides/03-call-and-participant-state.mdx +++ b/packages/client/docusaurus/docs/javascript/02-guides/03-call-and-participant-state.mdx @@ -55,7 +55,8 @@ Here is an excerpt of the call state properties: | `createdBy$` | `createdBy` | The user who created the call. | | `custom$` | `custom` | Custom data attached to the call. | | `dominantSpeaker$` | `dominantSpeaker` | The participant that is the current dominant speaker of the call. | -| `duration$` | `duration` | The duration since the start of the call in seconds. | +| `sessionDuration$` | `sessionDuration` | The duration since the start of the call session in seconds. | +| `liveDuration$` | `liveDuration` | The duration since the start of the call livestream in seconds. | | `egress$` | `egress` | The egress data of the call (for broadcasting and livestreaming). | | `endedAt$` | `endedAt` | The time the call was ended. | | `endedBy$` | `endedBy` | The user who ended the call. | diff --git a/packages/client/src/store/CallState.ts b/packages/client/src/store/CallState.ts index 158fec2902..37d088725a 100644 --- a/packages/client/src/store/CallState.ts +++ b/packages/client/src/store/CallState.ts @@ -117,7 +117,8 @@ export class CallState { private callStatsReportSubject = new BehaviorSubject< CallStatsReport | undefined >(undefined); - private durationSubject = new BehaviorSubject(0); + private liveDurationSubject = new BehaviorSubject(0); + private sessionDurationSubject = new BehaviorSubject(0); // These are tracks that were delivered to the Subscriber's onTrack event // that we couldn't associate with a participant yet. @@ -287,10 +288,16 @@ export class CallState { thumbnails$: Observable; /** - * Will provide the count of seconds since the call started. + * Will provide the count of seconds since the session started. */ - duration$: Observable; - durationInterval: NodeJS.Timeout | undefined; + sessionDuration$: Observable; + sessionDurationInterval: NodeJS.Timeout | undefined; + + /** + * Will provide the count of seconds since the livestream started. + */ + liveDuration$: Observable; + liveDurationInterval: NodeJS.Timeout | undefined; readonly logger = getLogger(['CallState']); @@ -397,8 +404,10 @@ export class CallState { this.participantCount$ = duc(this.participantCountSubject); this.recording$ = duc(this.recordingSubject); this.transcribing$ = duc(this.transcribingSubject); - this.duration$ = duc(this.durationSubject); - this.durationInterval = undefined; + this.sessionDuration$ = duc(this.sessionDurationSubject); + this.sessionDurationInterval = undefined; + this.liveDuration$ = duc(this.liveDurationSubject); + this.liveDurationInterval = undefined; this.eventHandlers = { // these events are not updating the call state: @@ -433,6 +442,7 @@ export class CallState { 'call.hls_broadcasting_started': this.updateFromHLSBroadcastStarted, 'call.hls_broadcasting_stopped': this.updateFromHLSBroadcastStopped, 'call.live_started': (e) => this.updateFromCallResponse(e.call), + 'call.live_ended': (e) => this.updateFromCallResponse(e.call), // Needs to be implemented on backend 'call.member_added': this.updateFromMemberAdded, 'call.member_removed': this.updateFromMemberRemoved, 'call.member_updated_permission': this.updateMembers, @@ -477,9 +487,13 @@ export class CallState { * Runs the cleanup tasks. */ dispose = () => { - if (this.durationInterval) { - clearInterval(this.durationInterval); - this.durationInterval = undefined; + if (this.sessionDurationInterval) { + clearInterval(this.sessionDurationInterval); + this.sessionDurationInterval = undefined; + } + if (this.liveDurationInterval) { + clearInterval(this.liveDurationInterval); + this.liveDurationInterval = undefined; } }; @@ -551,6 +565,40 @@ export class CallState { return this.setCurrentValue(this.durationSubject, duration); }; + /** + * The number of seconds since the start of the call session. + */ + get sessionDuration() { + return this.getCurrentValue(this.sessionDuration$); + } + + /** + * Sets the number of seconds since the start of the call session. + * + * @internal + * @param sessionDuration the duration of the call session in seconds. + */ + setSessionDuration = (duration: Patch) => { + return this.setCurrentValue(this.sessionDurationSubject, duration); + }; + + /** + * The number of seconds since the start of the livestream. + */ + get liveDuration() { + return this.getCurrentValue(this.liveDuration$); + } + + /** + * Sets the number of seconds since the start of the livestream. + * + * @internal + * @param liveDuration the duration of the livestream in seconds. + */ + setLiveDuration = (duration: Patch) => { + return this.setCurrentValue(this.liveDurationSubject, duration); + }; + /** * The time the call session actually started. * Useful for displaying the call duration. @@ -1207,20 +1255,39 @@ export class CallState { this.setAnonymousParticipantCount(session.anonymous_participant_count || 0); }; + startDurationInterval = ( + startTime: string, + setDurationCallback: (seconds: Patch) => number, + ) => { + const startedAt = new Date(startTime).getTime(); + const elapsedSeconds = Math.floor((Date.now() - startedAt) / 1000); + setDurationCallback(elapsedSeconds); + return setInterval(() => setDurationCallback((prev) => prev + 1), 1000); + }; + private updateDuration = (session: CallSessionResponse | undefined) => { - if (session?.live_started_at && !this.durationInterval) { - const startedAt = new Date(session.live_started_at).getTime(); - const elapsedSeconds = Math.floor((Date.now() - startedAt) / 1000); - this.setDuration(elapsedSeconds); - this.durationInterval = setInterval( - () => this.setDuration((prev) => prev + 1), - 1000, + // session duration + if (session?.started_at && !this.sessionDurationInterval) { + this.sessionDurationInterval = this.startDurationInterval( + session.started_at, + this.setSessionDuration, ); } + if (session?.ended_at && this.sessionDurationInterval) { + clearInterval(this.sessionDurationInterval); + this.sessionDurationInterval = undefined; + } - if (session?.ended_at && this.durationInterval) { - clearInterval(this.durationInterval); - this.durationInterval = undefined; + // livestream duration + if (session?.live_started_at && !this.liveDurationInterval) { + this.liveDurationInterval = this.startDurationInterval( + session.live_started_at, + this.setDuration, + ); + } + if (session?.live_ended_at && this.liveDurationInterval) { + clearInterval(this.liveDurationInterval); + this.liveDurationInterval = undefined; } }; diff --git a/packages/react-bindings/src/hooks/callStateHooks.ts b/packages/react-bindings/src/hooks/callStateHooks.ts index b147451384..05a0ed6a4a 100644 --- a/packages/react-bindings/src/hooks/callStateHooks.ts +++ b/packages/react-bindings/src/hooks/callStateHooks.ts @@ -307,13 +307,23 @@ export const useParticipantCount = () => { }; /** - * Returns the duration of the call in seconds. + * Returns the duration of the call session in seconds. * * @category Call State */ -export const useCallDuration = () => { - const { duration$ } = useCallState(); - return useObservableValue(duration$); +export const useCallSessionDuration = () => { + const { sessionDuration$ } = useCallState(); + return useObservableValue(sessionDuration$); +}; + +/** + * Returns the duration of the livestream seconds. + * + * @category Call State + */ +export const useCallLiveDuration = () => { + const { liveDuration$ } = useCallState(); + return useObservableValue(liveDuration$); }; /** diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/03-call-and-participant-state.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/03-call-and-participant-state.mdx index 2c600bbc00..af94d96209 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/03-call-and-participant-state.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/03-call-and-participant-state.mdx @@ -69,7 +69,8 @@ Here is an excerpt of the available call state hooks: | `useCallCreatedAt` | The time the call was created. | | `useCallCreatedBy` | The user that created the call. | | `useCallCustomData` | The custom data attached to the call. | -| `useCallDuration` | The call duration since the start of the call in seconds. | +| `useCallSessionDuration` | The call duration since the start of the call in seconds. | +| `useCallLiveDuration` | The call duration since the start of the call in seconds. | | `useCallEgress` | The egress information of the call. | | `useCallEndedBy` | The user that ended the call. | | `useCallIngress` | The ingress information of the call. | diff --git a/packages/react-sdk/docusaurus/docs/React/02-guides/03-call-and-participant-state.mdx b/packages/react-sdk/docusaurus/docs/React/02-guides/03-call-and-participant-state.mdx index e329305b40..795ba09de4 100644 --- a/packages/react-sdk/docusaurus/docs/React/02-guides/03-call-and-participant-state.mdx +++ b/packages/react-sdk/docusaurus/docs/React/02-guides/03-call-and-participant-state.mdx @@ -70,6 +70,8 @@ Here is an excerpt of the available call state hooks: | `useCallCreatedAt` | The time the call was created. | | `useCallCreatedBy` | The user that created the call. | | `useCallCustomData` | The custom data attached to the call. | +| `useCallSessionDuration` | The duration since the start of the call session in seconds. | +| `useCallLiveDuration` | The duration since the start of the call livestream in seconds. | | `useCallEgress` | The egress information of the call. | | `useCallEndedBy` | The user that ended the call. | | `useCallIngress` | The ingress information of the call. | diff --git a/sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx b/sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx index acdcd85458..bbfa1e05c7 100644 --- a/sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx +++ b/sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx @@ -1,4 +1,3 @@ -import { useEffect, useMemo, useState } from 'react'; import { CallingState, CancelCallConfirmButton, @@ -49,13 +48,9 @@ const formatTime = (timeInSeconds: number) => { }; const Elapsed = () => { - const [elapsed, setElapsed] = useState(); const { useCallDuration } = useCallStateHooks(); const duration = useCallDuration(); - - useEffect(() => { - setElapsed(formatTime(duration)); - }, [duration]); + const elapsed = formatTime(duration); return (