diff --git a/component/index.tsx b/component/index.tsx index d49d87d53..e57482abe 100644 --- a/component/index.tsx +++ b/component/index.tsx @@ -72,7 +72,7 @@ import { useUrlParams, } from "../src/UrlParams"; import { MediaDevicesContext } from "../src/MediaDevicesContext"; -import { MediaDevices } from "../src/state/MediaDevices"; +import { AppViewModel } from "../src/state/AppViewModel"; import { ObservableScope } from "../src/state/ObservableScope"; import { BackgroundEffectsProvider } from "../src/livekit/BackgroundEffectsContext"; import { Config } from "../src/config/Config"; @@ -192,14 +192,12 @@ export const ElementCall: FC = ({ // observers running for the rest of the page's life. Null until then, which // is one render. const { controlledAudioDevices, callIntent } = params; - const [mediaDevices, setMediaDevices] = useState(null); + const [vm, setVm] = useState(null); useEffect(() => { const scope = new ObservableScope(); - setMediaDevices( - new MediaDevices(scope, { controlledAudioDevices, callIntent }), - ); + setVm(new AppViewModel(scope, { controlledAudioDevices, callIntent })); return (): void => { - setMediaDevices(null); + setVm(null); scope.end(); }; }, [controlledAudioDevices, callIntent]); @@ -220,8 +218,7 @@ export const ElementCall: FC = ({ // is told that Element Call has loaded, as the widget tells its client once // its own initialisation is over. Once per mount, however often the pieces // are later swapped out. - const ready = - container !== null && rtcSession !== null && mediaDevices !== null; + const ready = container !== null && rtcSession !== null && vm !== null; const announcedLoaded = useRef(false); useEffect(() => { if (!ready || announcedLoaded.current) return; @@ -251,8 +248,10 @@ export const ElementCall: FC = ({ - - + + = ({ vm, widget }) => { loaded && clientReady ? ( - + } > diff --git a/src/livekit/BackgroundEffectsContext.test.ts b/src/livekit/BackgroundEffectsContext.test.ts index 409b54afd..4fd4b67b9 100644 --- a/src/livekit/BackgroundEffectsContext.test.ts +++ b/src/livekit/BackgroundEffectsContext.test.ts @@ -21,7 +21,9 @@ import { syncBackgroundEffects, useBackgroundEffects, useSyncBackgroundEffects, + createAppBackgroundEffects, } from "./BackgroundEffectsContext"; +import { type BackgroundEffects } from "../state/BackgroundEffects"; import { backgroundBlur } from "../settings/settings"; import { constant } from "../state/Behavior"; import { flushPromises, testScope } from "../utils/test"; @@ -150,16 +152,17 @@ describe("BackgroundEffectsProvider", () => { }; // One component for every render, so a rerender updates the tree rather than // mounting a second provider with a pipeline of its own. + let effects: BackgroundEffects | undefined; const Surfaces: FC<{ tracks: (LocalVideoTrack | null)[] }> = ({ tracks }) => - createElement( - BackgroundEffectsProvider, - null, - createElement( + createElement(BackgroundEffectsProvider, { + // Made on first render, once a test has set up the platform. + effects: (effects ??= createAppBackgroundEffects(testScope())), + children: createElement( "div", null, ...tracks.map((track, key) => createElement(Surface, { key, track })), ), - ); + }); const surfaces = ( ...tracks: (LocalVideoTrack | null)[] ): ReturnType => createElement(Surfaces, { tracks }); @@ -173,6 +176,7 @@ describe("BackgroundEffectsProvider", () => { beforeEach(() => { seen = []; + effects = undefined; pipelines.built = 0; pipelines.destroyed = 0; pipelines.switches = []; diff --git a/src/livekit/BackgroundEffectsContext.tsx b/src/livekit/BackgroundEffectsContext.tsx index b9a0a4b98..05c8d4f20 100644 --- a/src/livekit/BackgroundEffectsContext.tsx +++ b/src/livekit/BackgroundEffectsContext.tsx @@ -10,14 +10,7 @@ import { type ProcessorWrapper, type BackgroundOptions, } from "@livekit/track-processors"; -import { - createContext, - type FC, - type JSX, - use, - useEffect, - useState, -} from "react"; +import { createContext, type FC, type JSX, use, useEffect } from "react"; import { type LocalVideoTrack } from "livekit-client"; import { logger } from "matrix-js-sdk/lib/logger"; import { combineLatest } from "rxjs"; @@ -27,7 +20,7 @@ import { BackgroundEffectTransformer } from "./BackgroundEffectTransformer"; import { OneStepPipeline } from "./OneStepPipeline"; import { supportsBackgroundProcessors } from "./backgroundProcessing"; import { type Behavior } from "../state/Behavior"; -import { ObservableScope } from "../state/ObservableScope"; +import { type ObservableScope } from "../state/ObservableScope"; import { type BackgroundEffects, createBackgroundEffects, @@ -118,31 +111,27 @@ export const useSyncBackgroundEffects = ( }, [processor, videoTrack]); }; +/** The app's one pipeline, shared by every camera track it opens. */ +export function createAppBackgroundEffects( + scope: ObservableScope, +): BackgroundEffects { + return createBackgroundEffects(scope, { + supported: supportsBackgroundProcessors(), + blur$: backgroundBlurSettings.value$, + pipeline: new OneStepPipeline( + new BackgroundEffectTransformer({ backgroundDisabled: true }), + "background-effect", + ), + }); +} + interface Props { + effects: BackgroundEffects; children: JSX.Element; } -export const BackgroundEffectsProvider: FC = ({ children }) => { - const [effects, setEffects] = useState(null); - useEffect(() => { - const scope = new ObservableScope(); - setEffects( - createBackgroundEffects(scope, { - supported: supportsBackgroundProcessors(), - blur$: backgroundBlurSettings.value$, - pipeline: new OneStepPipeline( - new BackgroundEffectTransformer({ backgroundDisabled: true }), - "background-effect", - ), - }), - ); - return (): void => scope.end(); - }, []); - - if (effects === null) return null; - return ( - - {children} - - ); -}; +export const BackgroundEffectsProvider: FC = ({ effects, children }) => ( + + {children} + +); diff --git a/src/room/CallView.test.tsx b/src/room/CallView.test.tsx index 1968222db..a2253609e 100644 --- a/src/room/CallView.test.tsx +++ b/src/room/CallView.test.tsx @@ -50,6 +50,7 @@ import { mockMediaDevices, mockRtcMembership, MockRTCSession, + testScope, } from "../utils/test"; import { CallView } from "./CallView"; import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary"; @@ -61,7 +62,10 @@ import { } from "../HostBridge"; import { type JoinCallData } from "../widget"; import { MatrixRTCTransportMissingError } from "../utils/errors"; -import { BackgroundEffectsProvider } from "../livekit/BackgroundEffectsContext"; +import { + BackgroundEffectsProvider, + createAppBackgroundEffects, +} from "../livekit/BackgroundEffectsContext"; import { MediaDevicesContext } from "../MediaDevicesContext"; import { constant } from "../state/Behavior"; @@ -193,7 +197,9 @@ function createCallView( - + {options.withErrorBoundary ? ( {callView} diff --git a/src/room/InCallView.test.tsx b/src/room/InCallView.test.tsx index 542880afd..c46164d96 100644 --- a/src/room/InCallView.test.tsx +++ b/src/room/InCallView.test.tsx @@ -31,6 +31,7 @@ import { mockRemoteParticipant, mockRtcMembership, type MockRTCSession, + testScope, } from "../utils/test"; import { E2eeType } from "../e2ee/e2eeType"; import { @@ -49,7 +50,10 @@ import { MediaDevicesContext } from "../MediaDevicesContext"; import { type MediaDevices as ECMediaDevices } from "../state/MediaDevices"; import { AppBar } from "../AppBar"; import { type MatrixInfo } from "./VideoPreview"; -import { BackgroundEffectsProvider } from "../livekit/BackgroundEffectsContext"; +import { + BackgroundEffectsProvider, + createAppBackgroundEffects, +} from "../livekit/BackgroundEffectsContext"; import { initializeWidget } from "../widget"; import { RootElementProvider } from "../RootElementContext"; @@ -242,7 +246,9 @@ describe("ActiveCall", () => { const { findByTestId } = render( - + { - +