* Refactor media devices to live outside React as Observables This moves the media devices state out of React to further our transition to a MVVM architecture in which we can more easily model and store complex application state. I have created an AppViewModel to act as the overarching state holder for any future non-React state we end up creating, and the MediaDevices reside within this. We should move more application logic (including the CallViewModel itself) there in the future. * Address review feedback * Fixes from ios debugging session: (#3342) - dont use preferred vs selected concept in controlled media. Its not needed since we dont use the id for actual browser media devices (the id's are not even actual browser media devices) - add more logging - add more conditions to not accidently set a deviceId that is not a browser deviceId but one provided via controlled. --------- Co-authored-by: Timo <16718859+toger5@users.noreply.github.com>
56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
/*
|
|
Copyright 2023, 2024 New Vector Ltd.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import { type Observable, defer, finalize, scan, startWith, tap } from "rxjs";
|
|
|
|
const nothing = Symbol("nothing");
|
|
|
|
/**
|
|
* RxJS operator that invokes a callback when the Observable is finalized,
|
|
* passing the most recently emitted value. If no value was emitted, the
|
|
* callback will not be invoked.
|
|
*/
|
|
export function finalizeValue<T>(callback: (finalValue: T) => void) {
|
|
return (source$: Observable<T>): Observable<T> =>
|
|
defer(() => {
|
|
let finalValue: T | typeof nothing = nothing;
|
|
return source$.pipe(
|
|
tap((value) => (finalValue = value)),
|
|
finalize(() => {
|
|
if (finalValue !== nothing) callback(finalValue);
|
|
}),
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* RxJS operator that accumulates a state from a source of events. This is like
|
|
* scan, except it emits an initial value immediately before any events arrive.
|
|
*/
|
|
export function accumulate<State, Event>(
|
|
initial: State,
|
|
update: (state: State, event: Event) => State,
|
|
) {
|
|
return (events$: Observable<Event>): Observable<State> =>
|
|
events$.pipe(scan(update, initial), startWith(initial));
|
|
}
|
|
|
|
/**
|
|
* Reads the current value of a state Observable without reacting to future
|
|
* changes.
|
|
*
|
|
* This function exists to help with certain cases of bridging Observables into
|
|
* React, where an initial value is needed. You should never use it to create an
|
|
* Observable derived from another Observable; use reactive operators instead.
|
|
*/
|
|
export function getValue<T>(state$: Observable<T>): T {
|
|
let value: T | typeof nothing = nothing;
|
|
state$.subscribe((x) => (value = x)).unsubscribe();
|
|
if (value === nothing) throw new Error("Not a state Observable");
|
|
return value;
|
|
}
|