make ring$ a behavior and add code comments to justify/explain the change.

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo K
2025-09-19 14:11:22 +02:00
parent 0774d18f92
commit 5dfad5ec1e

View File

@@ -880,60 +880,68 @@ export class CallViewModel extends ViewModel {
? this.allOthersLeft$ ? this.allOthersLeft$
: NEVER; : NEVER;
private readonly didSendCallNotification$ = fromEvent(
this.matrixRTCSession,
MatrixRTCSessionEvent.DidSendCallNotification,
) as Observable<
Parameters<
MatrixRTCSessionEventHandlerMap[MatrixRTCSessionEvent.DidSendCallNotification]
>
>;
/** /**
* Whenever the RTC session tells us that it intends to ring the remote * Whenever the RTC session tells us that it intends to ring the remote
* participant's devices, this emits an Observable tracking the current state of * participant's devices, this emits an Observable tracking the current state of
* that ringing process. * that ringing process.
*/ */
private readonly ring$: Observable< // This is a behavior since we need to store the latest state for when we subscribe to this after `didSendCallNotification$`
Observable<"ringing" | "timeout" | "decline"> // has already emitted but we still need the latest observable with a timeout timer that only gets created on after receiving `notificationEvent`.
> = ( // A behavior will emit the latest observable with the running timer to new subscribers.
fromEvent( // see also: callPickupState$ and in particular the line: `return this.ring$.pipe(mergeAll());` here we otherwise might get an EMPTY observable if
this.matrixRTCSession, // `ring$` would not be a behavior.
MatrixRTCSessionEvent.DidSendCallNotification, private readonly ring$: Behavior<
) as Observable< Observable<"ringing" | "timeout" | "decline"> | Observable<never>
Parameters< > = this.scope.behavior(
MatrixRTCSessionEventHandlerMap[MatrixRTCSessionEvent.DidSendCallNotification] this.didSendCallNotification$.pipe(
> filter(
> ([notificationEvent]) => notificationEvent.notification_type === "ring",
).pipe( ),
filter( map(([notificationEvent]) => {
([notificationEvent]) => notificationEvent.notification_type === "ring", const lifetimeMs = notificationEvent?.lifetime ?? 0;
), return concat(
map(([notificationEvent]) => { lifetimeMs === 0
const lifetimeMs = notificationEvent?.lifetime ?? 0; ? // If no lifetime, skip the ring state
return concat( EMPTY
lifetimeMs === 0 : // Ring until lifetime ms have passed
? // If no lifetime, skip the ring state timer(lifetimeMs).pipe(
EMPTY ignoreElements(),
: // Ring until lifetime ms have passed startWith("ringing" as const),
timer(lifetimeMs).pipe( ),
ignoreElements(), // The notification lifetime has timed out, meaning ringing has likely
startWith("ringing" as const), // stopped on all receiving clients.
), of("timeout" as const),
// The notification lifetime has timed out, meaning ringing has likely NEVER,
// stopped on all receiving clients. ).pipe(
of("timeout" as const), takeUntil(
NEVER, (
).pipe( fromEvent(this.matrixRoom, RoomEvent.Timeline) as Observable<
takeUntil( Parameters<EventTimelineSetHandlerMap[RoomEvent.Timeline]>
( >
fromEvent(this.matrixRoom, RoomEvent.Timeline) as Observable< ).pipe(
Parameters<EventTimelineSetHandlerMap[RoomEvent.Timeline]> filter(
> ([event]) =>
).pipe( event.getType() === EventType.RTCDecline &&
filter( event.getRelation()?.rel_type === "m.reference" &&
([event]) => event.getRelation()?.event_id ===
event.getType() === EventType.RTCDecline && notificationEvent.event_id &&
event.getRelation()?.rel_type === "m.reference" && event.getSender() !== this.userId,
event.getRelation()?.event_id === notificationEvent.event_id && ),
event.getSender() !== this.userId,
), ),
), ),
), endWith("decline" as const),
endWith("decline" as const), );
); }),
}), ),
EMPTY,
); );
/** /**
@@ -972,6 +980,8 @@ export class CallViewModel extends ViewModel {
return of("success" as const); return of("success" as const);
} }
// Show the ringing state of the most recent ringing attempt. // Show the ringing state of the most recent ringing attempt.
// ring$ is a behavior so it will emit the latest observable which very well might already have a running timer.
// this is important in case livekitConnectionState$ and someoneElseJoined$ emit after didSendCallNotification$ has already emitted.
return this.ring$.pipe(switchAll()); return this.ring$.pipe(switchAll());
}), }),
// The state starts as 'unknown' because we don't know if the RTC // The state starts as 'unknown' because we don't know if the RTC