Refactor ring$ observable

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo K
2025-09-19 16:44:03 +02:00
parent 317b2dc796
commit c3f49fa73b
+19 -26
View File
@@ -898,19 +898,19 @@ export class CallViewModel extends ViewModel {
// A behavior will emit the latest observable with the running timer to new subscribers. // A behavior will emit the latest observable with the running timer to new subscribers.
// see also: callPickupState$ and in particular the line: `return this.ring$.pipe(mergeAll());` here we otherwise might get an EMPTY observable if // see also: callPickupState$ and in particular the line: `return this.ring$.pipe(mergeAll());` here we otherwise might get an EMPTY observable if
// `ring$` would not be a behavior. // `ring$` would not be a behavior.
private readonly ring$: Behavior< private readonly ring$: Behavior<"ringing" | "timeout" | "decline" | null> =
Observable<"ringing" | "timeout" | "decline"> | Observable<never> this.scope.behavior(
> = this.scope.behavior(
this.didSendCallNotification$.pipe( this.didSendCallNotification$.pipe(
filter( filter(
([notificationEvent]) => notificationEvent.notification_type === "ring", ([notificationEvent]) =>
notificationEvent.notification_type === "ring",
), ),
map(([notificationEvent]) => { switchMap(([notificationEvent]) => {
const lifetimeMs = notificationEvent?.lifetime ?? 0; const lifetimeMs = notificationEvent?.lifetime ?? 0;
return concat( return concat(
lifetimeMs === 0 lifetimeMs === 0
? // If no lifetime, skip the ring state ? // If no lifetime, skip the ring state
EMPTY of(null)
: // Ring until lifetime ms have passed : // Ring until lifetime ms have passed
timer(lifetimeMs).pipe( timer(lifetimeMs).pipe(
ignoreElements(), ignoreElements(),
@@ -919,6 +919,7 @@ export class CallViewModel extends ViewModel {
// The notification lifetime has timed out, meaning ringing has likely // The notification lifetime has timed out, meaning ringing has likely
// stopped on all receiving clients. // stopped on all receiving clients.
of("timeout" as const), of("timeout" as const),
// This makes sure we will not drop into the `endWith("decline" as const)` state
NEVER, NEVER,
).pipe( ).pipe(
takeUntil( takeUntil(
@@ -941,7 +942,7 @@ export class CallViewModel extends ViewModel {
); );
}), }),
), ),
EMPTY, null,
); );
/** /**
@@ -949,7 +950,7 @@ export class CallViewModel extends ViewModel {
*/ */
private readonly someoneElseJoined$ = this.memberships$.pipe( private readonly someoneElseJoined$ = this.memberships$.pipe(
map((ms) => ms.some((m) => m.sender !== this.userId)), map((ms) => ms.some((m) => m.sender !== this.userId)),
); ) as Behavior<boolean>;
/** /**
* The current call pickup state of the call. * The current call pickup state of the call.
@@ -968,27 +969,19 @@ export class CallViewModel extends ViewModel {
? this.scope.behavior< ? this.scope.behavior<
"unknown" | "ringing" | "timeout" | "decline" | "success" "unknown" | "ringing" | "timeout" | "decline" | "success"
>( >(
combineLatest([ combineLatest(
this.livekitConnectionState$, [this.livekitConnectionState$, this.someoneElseJoined$, this.ring$],
this.someoneElseJoined$, (livekitConnectionState, someoneElseJoined, ring) => {
]).pipe(
switchMap(([livekitConnectionState, someoneElseJoined]) => {
if (livekitConnectionState === ConnectionState.Disconnected) { if (livekitConnectionState === ConnectionState.Disconnected) {
// Do not ring until we're connected. // Do not ring until we're connected.
return of("unknown" as const); return "unknown" as const;
} else if (someoneElseJoined) { } else if (someoneElseJoined && ring !== null) {
return of("success" as const); return "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. // as long as we have not yet sent an RTC notification event, ring will be null -> callPickupState$ = unknown.
// this is important in case livekitConnectionState$ after didSendCallNotification$ has already emitted. return ring ?? ("unknown" as const);
return this.ring$.pipe(switchAll()); },
}),
// The state starts as 'unknown' because we don't know if the RTC
// session will actually send a notify event yet. It will only be
// known once we send our own membership and see that we were the
// first one to join.
startWith("unknown" as const),
), ),
) )
: constant(null); : constant(null);
@@ -1700,7 +1693,7 @@ export class CallViewModel extends ViewModel {
private readonly livekitRoom: LivekitRoom, private readonly livekitRoom: LivekitRoom,
private readonly mediaDevices: MediaDevices, private readonly mediaDevices: MediaDevices,
private readonly options: CallViewModelOptions, private readonly options: CallViewModelOptions,
public readonly livekitConnectionState$: Observable<ECConnectionState>, public readonly livekitConnectionState$: Behavior<ECConnectionState>,
private readonly handsRaisedSubject$: Observable< private readonly handsRaisedSubject$: Observable<
Record<string, RaisedHandInfo> Record<string, RaisedHandInfo>
>, >,