mirror of
https://github.com/vector-im/element-call.git
synced 2026-11-07 00:52:28 +00:00
Create the background effects with the app's view model
- AppViewModel creates them, next to its media devices, as review asked; BackgroundEffectsProvider takes them as a prop rather than building them in an effect. - The component build, which has no AppViewModel of its own, now makes one in the effect that made its media devices, so both builds get their state the same way. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
Timo K.
co-authored by
Claude Opus 5.5
parent
f52d2b7952
commit
813f1e6f53
+9
-10
@@ -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/tracks/BackgroundEffectsContext";
|
||||
import { Config } from "../src/config/Config";
|
||||
@@ -194,14 +194,12 @@ export const ElementCall: FC<ElementCallProps> = ({
|
||||
// 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<MediaDevices | null>(null);
|
||||
const [vm, setVm] = useState<AppViewModel | null>(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]);
|
||||
@@ -222,8 +220,7 @@ export const ElementCall: FC<ElementCallProps> = ({
|
||||
// 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;
|
||||
@@ -254,8 +251,10 @@ export const ElementCall: FC<ElementCallProps> = ({
|
||||
<Decoration>
|
||||
<TooltipProvider>
|
||||
<ClientProvider client={client}>
|
||||
<MediaDevicesContext value={mediaDevices}>
|
||||
<BackgroundEffectsProvider>
|
||||
<MediaDevicesContext value={vm.mediaDevices}>
|
||||
<BackgroundEffectsProvider
|
||||
effects={vm.backgroundEffects}
|
||||
>
|
||||
<CallView
|
||||
client={client}
|
||||
rtcSession={rtcSession}
|
||||
|
||||
+1
-1
@@ -148,7 +148,7 @@ export const App: FC<Props> = ({ vm, widget }) => {
|
||||
loaded && clientReady ? (
|
||||
<ClientProvider client={widgetClient}>
|
||||
<MediaDevicesContext value={vm.mediaDevices}>
|
||||
<BackgroundEffectsProvider>
|
||||
<BackgroundEffectsProvider effects={vm.backgroundEffects}>
|
||||
<Sentry.ErrorBoundary
|
||||
fallback={(error) => <ErrorPage error={error} />}
|
||||
>
|
||||
|
||||
@@ -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 "../tracks/BackgroundEffectsContext";
|
||||
import {
|
||||
BackgroundEffectsProvider,
|
||||
createAppBackgroundEffects,
|
||||
} from "../tracks/BackgroundEffectsContext";
|
||||
import { MediaDevicesContext } from "../MediaDevicesContext";
|
||||
import { constant } from "../state/Behavior";
|
||||
|
||||
@@ -193,7 +197,9 @@ function createCallView(
|
||||
<HostBridgeProvider value={hostBridge}>
|
||||
<TooltipProvider>
|
||||
<MediaDevicesContext value={mockMediaDevices({})}>
|
||||
<BackgroundEffectsProvider>
|
||||
<BackgroundEffectsProvider
|
||||
effects={createAppBackgroundEffects(testScope())}
|
||||
>
|
||||
{options.withErrorBoundary ? (
|
||||
<GroupCallErrorBoundary recoveryActionHandler={vi.fn()}>
|
||||
{callView}
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
type MockRTCSession,
|
||||
mockRTCSlot,
|
||||
mockRTCMember,
|
||||
testScope,
|
||||
} from "../utils/test";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import {
|
||||
@@ -48,7 +49,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 "../tracks/BackgroundEffectsContext";
|
||||
import {
|
||||
BackgroundEffectsProvider,
|
||||
createAppBackgroundEffects,
|
||||
} from "../tracks/BackgroundEffectsContext";
|
||||
import { initializeWidget } from "../widget";
|
||||
import { RootElementProvider } from "../RootElementContext";
|
||||
|
||||
@@ -234,7 +238,9 @@ describe("ActiveCall", () => {
|
||||
const { findByTestId } = render(
|
||||
<BrowserRouter>
|
||||
<MediaDevicesContext value={mediaDevices}>
|
||||
<BackgroundEffectsProvider>
|
||||
<BackgroundEffectsProvider
|
||||
effects={createAppBackgroundEffects(testScope())}
|
||||
>
|
||||
<TooltipProvider>
|
||||
<ActiveCall
|
||||
client={matrixRoom.client}
|
||||
@@ -282,7 +288,9 @@ describe("ActiveCall", () => {
|
||||
<BrowserRouter>
|
||||
<RootElementProvider value={root}>
|
||||
<MediaDevicesContext value={mediaDevices}>
|
||||
<BackgroundEffectsProvider>
|
||||
<BackgroundEffectsProvider
|
||||
effects={createAppBackgroundEffects(testScope())}
|
||||
>
|
||||
<TooltipProvider>
|
||||
<ActiveCall
|
||||
client={matrixRoom.client}
|
||||
|
||||
@@ -6,6 +6,7 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type AudioOutputOptions, MediaDevices } from "./MediaDevices";
|
||||
import { createAppBackgroundEffects } from "../tracks/BackgroundEffectsContext";
|
||||
import { type ObservableScope } from "./ObservableScope";
|
||||
|
||||
/**
|
||||
@@ -16,6 +17,7 @@ export class AppViewModel {
|
||||
this.scope,
|
||||
this.audioOutputOptions,
|
||||
);
|
||||
public readonly backgroundEffects = createAppBackgroundEffects(this.scope);
|
||||
|
||||
// TODO: Move more application logic here. The CallViewModel, at the very
|
||||
// least, ought to be accessible from this object.
|
||||
|
||||
@@ -26,7 +26,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";
|
||||
@@ -196,16 +198,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<typeof createElement> => createElement(Surfaces, { tracks });
|
||||
@@ -219,6 +222,7 @@ describe("BackgroundEffectsProvider", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
seen = [];
|
||||
effects = undefined;
|
||||
pipelines.built = 0;
|
||||
pipelines.destroyed = 0;
|
||||
pipelines.switches = [];
|
||||
|
||||
@@ -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, Track } from "livekit-client";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import { combineLatest } from "rxjs";
|
||||
@@ -31,7 +24,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,
|
||||
@@ -149,31 +142,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<Props> = ({ children }) => {
|
||||
const [effects, setEffects] = useState<BackgroundEffects | null>(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 (
|
||||
<BackgroundEffectsContext value={effects}>
|
||||
{children}
|
||||
</BackgroundEffectsContext>
|
||||
);
|
||||
};
|
||||
export const BackgroundEffectsProvider: FC<Props> = ({ effects, children }) => (
|
||||
<BackgroundEffectsContext value={effects}>
|
||||
{children}
|
||||
</BackgroundEffectsContext>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user