Name the background effect pieces after background effects

- One name, as review asked: TrackProcessorContext becomes
  BackgroundEffectsContext, with BackgroundEffectsProvider,
  BackgroundEffectsState, useBackgroundEffects,
  useBackgroundEffectsState$ and useSyncBackgroundEffects.
- The state's Behavior is backgroundEffectsState$ wherever it is passed,
  in place of trackProcessorState$, trackerProcessorState$ and
  processorState$.
- Renames only: nothing changes in behaviour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
fkwp
2026-10-08 12:22:34 +02:00
co-authored by Claude Opus 5.5
parent cbb22a0094
commit e9365b85b5
22 changed files with 115 additions and 108 deletions
+3 -3
View File
@@ -74,7 +74,7 @@ import {
import { MediaDevicesContext } from "../src/MediaDevicesContext"; import { MediaDevicesContext } from "../src/MediaDevicesContext";
import { MediaDevices } from "../src/state/MediaDevices"; import { MediaDevices } from "../src/state/MediaDevices";
import { ObservableScope } from "../src/state/ObservableScope"; import { ObservableScope } from "../src/state/ObservableScope";
import { ProcessorProvider } from "../src/livekit/TrackProcessorContext"; import { BackgroundEffectsProvider } from "../src/livekit/BackgroundEffectsContext";
import { Config } from "../src/config/Config"; import { Config } from "../src/config/Config";
import { type ConfigOptions } from "../src/config/ConfigOptions"; import { type ConfigOptions } from "../src/config/ConfigOptions";
import { i18n } from "../src/utils/i18n"; import { i18n } from "../src/utils/i18n";
@@ -252,7 +252,7 @@ export const ElementCall: FC<ElementCallProps> = ({
<TooltipProvider> <TooltipProvider>
<ClientProvider client={client}> <ClientProvider client={client}>
<MediaDevicesContext value={mediaDevices}> <MediaDevicesContext value={mediaDevices}>
<ProcessorProvider> <BackgroundEffectsProvider>
<CallView <CallView
client={client} client={client}
rtcSession={rtcSession} rtcSession={rtcSession}
@@ -261,7 +261,7 @@ export const ElementCall: FC<ElementCallProps> = ({
preload={params.preload} preload={params.preload}
skipLobby={params.skipLobby} skipLobby={params.skipLobby}
/> />
</ProcessorProvider> </BackgroundEffectsProvider>
</MediaDevicesContext> </MediaDevicesContext>
</ClientProvider> </ClientProvider>
</TooltipProvider> </TooltipProvider>
+3 -3
View File
@@ -35,7 +35,7 @@ import { ErrorPage, LoadingPage } from "./FullScreenView";
import { Initializer } from "./initializer"; import { Initializer } from "./initializer";
import { type WidgetHelpers } from "./widget"; import { type WidgetHelpers } from "./widget";
import { useTheme } from "./useTheme"; import { useTheme } from "./useTheme";
import { ProcessorProvider } from "./livekit/TrackProcessorContext"; import { BackgroundEffectsProvider } from "./livekit/BackgroundEffectsContext";
import { type AppViewModel } from "./state/AppViewModel"; import { type AppViewModel } from "./state/AppViewModel";
import { MediaDevicesContext } from "./MediaDevicesContext"; import { MediaDevicesContext } from "./MediaDevicesContext";
import { import {
@@ -148,7 +148,7 @@ export const App: FC<Props> = ({ vm, widget }) => {
loaded && clientReady ? ( loaded && clientReady ? (
<ClientProvider client={widgetClient}> <ClientProvider client={widgetClient}>
<MediaDevicesContext value={vm.mediaDevices}> <MediaDevicesContext value={vm.mediaDevices}>
<ProcessorProvider> <BackgroundEffectsProvider>
<Sentry.ErrorBoundary <Sentry.ErrorBoundary
fallback={(error) => <ErrorPage error={error} />} fallback={(error) => <ErrorPage error={error} />}
> >
@@ -159,7 +159,7 @@ export const App: FC<Props> = ({ vm, widget }) => {
<SentryRoute path="*" element={<RoomPage />} /> <SentryRoute path="*" element={<RoomPage />} />
</Routes> </Routes>
</Sentry.ErrorBoundary> </Sentry.ErrorBoundary>
</ProcessorProvider> </BackgroundEffectsProvider>
</MediaDevicesContext> </MediaDevicesContext>
</ClientProvider> </ClientProvider>
) : ( ) : (
@@ -16,12 +16,12 @@ import {
import { import {
applyProcessor, applyProcessor,
ProcessorProvider, BackgroundEffectsProvider,
type ProcessorState, type BackgroundEffectsState,
trackProcessorSync, syncBackgroundEffects,
useTrackProcessor, useBackgroundEffects,
useTrackProcessorSync, useSyncBackgroundEffects,
} from "./TrackProcessorContext"; } from "./BackgroundEffectsContext";
import { backgroundBlur } from "../settings/settings"; import { backgroundBlur } from "../settings/settings";
import { constant } from "../state/Behavior"; import { constant } from "../state/Behavior";
import { flushPromises, testScope } from "../utils/test"; import { flushPromises, testScope } from "../utils/test";
@@ -112,10 +112,10 @@ describe("applyProcessor", () => {
}); });
}); });
describe("trackProcessorSync", () => { describe("syncBackgroundEffects", () => {
it("applies the processor to the current track", () => { it("applies the processor to the current track", () => {
const track = mockTrack("live"); const track = mockTrack("live");
trackProcessorSync( syncBackgroundEffects(
testScope(), testScope(),
constant(track), constant(track),
constant({ supported: true, processor }), constant({ supported: true, processor }),
@@ -124,7 +124,7 @@ describe("trackProcessorSync", () => {
}); });
}); });
describe("ProcessorProvider", () => { describe("BackgroundEffectsProvider", () => {
/** A camera track that holds whatever processor it is given. */ /** A camera track that holds whatever processor it is given. */
function cameraTrack(): LocalVideoTrack { function cameraTrack(): LocalVideoTrack {
let current: unknown; let current: unknown;
@@ -142,17 +142,17 @@ describe("ProcessorProvider", () => {
} as unknown as LocalVideoTrack; } as unknown as LocalVideoTrack;
} }
let seen: ProcessorState[]; let seen: BackgroundEffectsState[];
const Surface: FC<{ track: LocalVideoTrack | null }> = ({ track }) => { const Surface: FC<{ track: LocalVideoTrack | null }> = ({ track }) => {
seen.push(useTrackProcessor()); seen.push(useBackgroundEffects());
useTrackProcessorSync(track); useSyncBackgroundEffects(track);
return null; return null;
}; };
// One component for every render, so a rerender updates the tree rather than // One component for every render, so a rerender updates the tree rather than
// mounting a second provider with a pipeline of its own. // mounting a second provider with a pipeline of its own.
const Surfaces: FC<{ tracks: (LocalVideoTrack | null)[] }> = ({ tracks }) => const Surfaces: FC<{ tracks: (LocalVideoTrack | null)[] }> = ({ tracks }) =>
createElement( createElement(
ProcessorProvider, BackgroundEffectsProvider,
null, null,
createElement( createElement(
"div", "div",
@@ -169,7 +169,7 @@ describe("ProcessorProvider", () => {
await flushPromises(); await flushPromises();
}); });
}; };
const latest = (): ProcessorState => seen[seen.length - 1]; const latest = (): BackgroundEffectsState => seen[seen.length - 1];
beforeEach(() => { beforeEach(() => {
seen = []; seen = [];
@@ -35,24 +35,24 @@ import { useBehavior } from "../useBehavior";
// it is a combination of exposing observable and react hooks. // it is a combination of exposing observable and react hooks.
// preferably we should not make this a context anymore and instead just a vm? // preferably we should not make this a context anymore and instead just a vm?
export type ProcessorState = { export type BackgroundEffectsState = {
supported: boolean | undefined; supported: boolean | undefined;
processor: undefined | ProcessorWrapper<BackgroundOptions>; processor: undefined | ProcessorWrapper<BackgroundOptions>;
}; };
const ProcessorContext = createContext<BackgroundEffects | undefined>( const BackgroundEffectsContext = createContext<BackgroundEffects | undefined>(
undefined, undefined,
); );
export function useTrackProcessor(): ProcessorState { export function useBackgroundEffects(): BackgroundEffectsState {
return useBehavior(useTrackProcessorState$()); return useBehavior(useBackgroundEffectsState$());
} }
export function useTrackProcessorState$(): Behavior<ProcessorState> { export function useBackgroundEffectsState$(): Behavior<BackgroundEffectsState> {
const effects = use(ProcessorContext); const effects = use(BackgroundEffectsContext);
if (effects === undefined) if (effects === undefined)
throw new Error( throw new Error(
"useTrackProcessor must be used within a ProcessorProvider", "useBackgroundEffects must be used within a BackgroundEffectsProvider",
); );
return effects.state$; return effects.state$;
} }
@@ -91,24 +91,24 @@ export function applyProcessor(
/** /**
* Updates your video tracks to always use the given processor. * Updates your video tracks to always use the given processor.
*/ */
export const trackProcessorSync = ( export const syncBackgroundEffects = (
scope: ObservableScope, scope: ObservableScope,
videoTrack$: Behavior<LocalVideoTrack | null>, videoTrack$: Behavior<LocalVideoTrack | null>,
processor$: Behavior<ProcessorState>, backgroundEffectsState$: Behavior<BackgroundEffectsState>,
): void => { ): void => {
combineLatest([videoTrack$, processor$]) combineLatest([videoTrack$, backgroundEffectsState$])
.pipe(scope.bind()) .pipe(scope.bind())
.subscribe(([videoTrack, processorState]) => { .subscribe(([videoTrack, backgroundEffectsState]) => {
if (!processorState) return; if (!backgroundEffectsState) return;
if (!videoTrack) return; if (!videoTrack) return;
applyProcessor(videoTrack, processorState.processor); applyProcessor(videoTrack, backgroundEffectsState.processor);
}); });
}; };
export const useTrackProcessorSync = ( export const useSyncBackgroundEffects = (
videoTrack: LocalVideoTrack | null, videoTrack: LocalVideoTrack | null,
): void => { ): void => {
const { processor } = useTrackProcessor(); const { processor } = useBackgroundEffects();
useEffect(() => { useEffect(() => {
if (!videoTrack) return; if (!videoTrack) return;
applyProcessor(videoTrack, processor); applyProcessor(videoTrack, processor);
@@ -119,7 +119,7 @@ interface Props {
children: JSX.Element; children: JSX.Element;
} }
export const ProcessorProvider: FC<Props> = ({ children }) => { export const BackgroundEffectsProvider: FC<Props> = ({ children }) => {
const [effects, setEffects] = useState<BackgroundEffects | null>(null); const [effects, setEffects] = useState<BackgroundEffects | null>(null);
useEffect(() => { useEffect(() => {
const scope = new ObservableScope(); const scope = new ObservableScope();
@@ -137,5 +137,9 @@ export const ProcessorProvider: FC<Props> = ({ children }) => {
}, []); }, []);
if (effects === null) return null; if (effects === null) return null;
return <ProcessorContext value={effects}>{children}</ProcessorContext>; return (
<BackgroundEffectsContext value={effects}>
{children}
</BackgroundEffectsContext>
);
}; };
+3 -3
View File
@@ -61,7 +61,7 @@ import {
} from "../HostBridge"; } from "../HostBridge";
import { type JoinCallData } from "../widget"; import { type JoinCallData } from "../widget";
import { MatrixRTCTransportMissingError } from "../utils/errors"; import { MatrixRTCTransportMissingError } from "../utils/errors";
import { ProcessorProvider } from "../livekit/TrackProcessorContext"; import { BackgroundEffectsProvider } from "../livekit/BackgroundEffectsContext";
import { MediaDevicesContext } from "../MediaDevicesContext"; import { MediaDevicesContext } from "../MediaDevicesContext";
import { constant } from "../state/Behavior"; import { constant } from "../state/Behavior";
@@ -193,7 +193,7 @@ function createCallView(
<HostBridgeProvider value={hostBridge}> <HostBridgeProvider value={hostBridge}>
<TooltipProvider> <TooltipProvider>
<MediaDevicesContext value={mockMediaDevices({})}> <MediaDevicesContext value={mockMediaDevices({})}>
<ProcessorProvider> <BackgroundEffectsProvider>
{options.withErrorBoundary ? ( {options.withErrorBoundary ? (
<GroupCallErrorBoundary recoveryActionHandler={vi.fn()}> <GroupCallErrorBoundary recoveryActionHandler={vi.fn()}>
{callView} {callView}
@@ -201,7 +201,7 @@ function createCallView(
) : ( ) : (
callView callView
)} )}
</ProcessorProvider> </BackgroundEffectsProvider>
</MediaDevicesContext> </MediaDevicesContext>
</TooltipProvider> </TooltipProvider>
</HostBridgeProvider> </HostBridgeProvider>
+5 -5
View File
@@ -49,7 +49,7 @@ import { MediaDevicesContext } from "../MediaDevicesContext";
import { type MediaDevices as ECMediaDevices } from "../state/MediaDevices"; import { type MediaDevices as ECMediaDevices } from "../state/MediaDevices";
import { AppBar } from "../AppBar"; import { AppBar } from "../AppBar";
import { type MatrixInfo } from "./VideoPreview"; import { type MatrixInfo } from "./VideoPreview";
import { ProcessorProvider } from "../livekit/TrackProcessorContext"; import { BackgroundEffectsProvider } from "../livekit/BackgroundEffectsContext";
import { initializeWidget } from "../widget"; import { initializeWidget } from "../widget";
import { RootElementProvider } from "../RootElementContext"; import { RootElementProvider } from "../RootElementContext";
@@ -242,7 +242,7 @@ describe("ActiveCall", () => {
const { findByTestId } = render( const { findByTestId } = render(
<BrowserRouter> <BrowserRouter>
<MediaDevicesContext value={mediaDevices}> <MediaDevicesContext value={mediaDevices}>
<ProcessorProvider> <BackgroundEffectsProvider>
<TooltipProvider> <TooltipProvider>
<RoomContext value={mockLivekitRoom({ localParticipant })}> <RoomContext value={mockLivekitRoom({ localParticipant })}>
<ActiveCall <ActiveCall
@@ -257,7 +257,7 @@ describe("ActiveCall", () => {
/> />
</RoomContext> </RoomContext>
</TooltipProvider> </TooltipProvider>
</ProcessorProvider> </BackgroundEffectsProvider>
</MediaDevicesContext> </MediaDevicesContext>
</BrowserRouter>, </BrowserRouter>,
); );
@@ -292,7 +292,7 @@ describe("ActiveCall", () => {
<BrowserRouter> <BrowserRouter>
<RootElementProvider value={root}> <RootElementProvider value={root}>
<MediaDevicesContext value={mediaDevices}> <MediaDevicesContext value={mediaDevices}>
<ProcessorProvider> <BackgroundEffectsProvider>
<TooltipProvider> <TooltipProvider>
<RoomContext value={mockLivekitRoom({ localParticipant })}> <RoomContext value={mockLivekitRoom({ localParticipant })}>
<ActiveCall <ActiveCall
@@ -307,7 +307,7 @@ describe("ActiveCall", () => {
/> />
</RoomContext> </RoomContext>
</TooltipProvider> </TooltipProvider>
</ProcessorProvider> </BackgroundEffectsProvider>
</MediaDevicesContext> </MediaDevicesContext>
</RootElementProvider> </RootElementProvider>
</BrowserRouter>, </BrowserRouter>,
+5 -5
View File
@@ -79,7 +79,7 @@ import { useValueBehavior } from "../useValueBehavior.ts";
import { constant } from "../state/Behavior.ts"; import { constant } from "../state/Behavior.ts";
import { Toast } from "../Toast.tsx"; import { Toast } from "../Toast.tsx";
import overlayStyles from "../Overlay.module.css"; import overlayStyles from "../Overlay.module.css";
import { useTrackProcessorState$ } from "../livekit/TrackProcessorContext.tsx"; import { useBackgroundEffectsState$ } from "../livekit/BackgroundEffectsContext.tsx";
import { type Layout } from "../state/layout-types.ts"; import { type Layout } from "../state/layout-types.ts";
import { ObservableScope } from "../state/ObservableScope.ts"; import { ObservableScope } from "../state/ObservableScope.ts";
import { CallFooter, type FooterSnapshot } from "../components/CallFooter.tsx"; import { CallFooter, type FooterSnapshot } from "../components/CallFooter.tsx";
@@ -120,7 +120,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
const urlParams = useUrlParams(); const urlParams = useUrlParams();
const hostBridge = useHostBridge(); const hostBridge = useHostBridge();
const mediaDevices = useMediaDevices(); const mediaDevices = useMediaDevices();
const trackProcessorState$ = useTrackProcessorState$(); const backgroundEffectsState$ = useBackgroundEffectsState$();
// The element we have to draw the call in: the page, or the container a host // The element we have to draw the call in: the page, or the container a host
// gave us. Its size, not the window's, decides how the call is laid out. // gave us. Its size, not the window's, decides how the call is laid out.
const rootElement = useRootElement(); const rootElement = useRootElement();
@@ -150,7 +150,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
}, },
reactionsReader.raisedHands$, reactionsReader.raisedHands$,
reactionsReader.reactions$, reactionsReader.reactions$,
trackProcessorState$, backgroundEffectsState$,
); );
// TODO move this somewhere else once we use the callViewModel in the lobby as well! // TODO move this somewhere else once we use the callViewModel in the lobby as well!
vm.join(); vm.join();
@@ -170,7 +170,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
urlParams, urlParams,
hostBridge, hostBridge,
mediaDevices, mediaDevices,
trackProcessorState$, backgroundEffectsState$,
props.client, props.client,
rootElement, rootElement,
]); ]);
@@ -201,7 +201,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
props.onLeft, props.onLeft,
urlParams, urlParams,
mediaDevices, mediaDevices,
trackProcessorState$, backgroundEffectsState$,
props.client, props.client,
vm, vm,
]); ]);
+4 -4
View File
@@ -14,19 +14,19 @@ import { type MatrixClient, type RoomSummary } from "matrix-js-sdk";
import { KnockLobbyView } from "./KnockLobbyView"; import { KnockLobbyView } from "./KnockLobbyView";
import { LeaveToHomeProvider } from "../LeaveToHomeContext"; import { LeaveToHomeProvider } from "../LeaveToHomeContext";
import { MediaDevicesContext } from "../MediaDevicesContext"; import { MediaDevicesContext } from "../MediaDevicesContext";
import { type ProcessorState } from "../livekit/TrackProcessorContext"; import { type BackgroundEffectsState } from "../livekit/BackgroundEffectsContext";
import { mockMediaDevices } from "../utils/test"; import { mockMediaDevices } from "../utils/test";
vi.mock("@livekit/components-react", () => ({ vi.mock("@livekit/components-react", () => ({
usePreviewTracks: (): unknown[] => [], usePreviewTracks: (): unknown[] => [],
})); }));
vi.mock("../livekit/TrackProcessorContext", () => ({ vi.mock("../livekit/BackgroundEffectsContext", () => ({
useTrackProcessor: (): ProcessorState => ({ useBackgroundEffects: (): BackgroundEffectsState => ({
supported: false, supported: false,
processor: undefined, processor: undefined,
}), }),
useTrackProcessorSync: (): void => {}, useSyncBackgroundEffects: (): void => {},
})); }));
vi.mock("react-use-measure", () => ({ vi.mock("react-use-measure", () => ({
+4 -4
View File
@@ -28,7 +28,7 @@ import {
} from "../utils/test"; } from "../utils/test";
import { type MediaDevices } from "../state/MediaDevices"; import { type MediaDevices } from "../state/MediaDevices";
import { MediaDevicesContext } from "../MediaDevicesContext"; import { MediaDevicesContext } from "../MediaDevicesContext";
import { type ProcessorState } from "../livekit/TrackProcessorContext"; import { type BackgroundEffectsState } from "../livekit/BackgroundEffectsContext";
import { type EncryptionSystem } from "../e2ee/sharedKeyManagement"; import { type EncryptionSystem } from "../e2ee/sharedKeyManagement";
import lobbyStyles from "./LobbyView.module.css"; import lobbyStyles from "./LobbyView.module.css";
import headerStyles from "../Header.module.css"; import headerStyles from "../Header.module.css";
@@ -41,12 +41,12 @@ vi.mock("@livekit/components-react", () => ({
usePreviewTracks: (): unknown[] => [], usePreviewTracks: (): unknown[] => [],
})); }));
vi.mock("../livekit/TrackProcessorContext", () => ({ vi.mock("../livekit/BackgroundEffectsContext", () => ({
useTrackProcessor: (): ProcessorState => ({ useBackgroundEffects: (): BackgroundEffectsState => ({
supported: false, supported: false,
processor: undefined, processor: undefined,
}), }),
useTrackProcessorSync: (): void => {}, useSyncBackgroundEffects: (): void => {},
})); }));
vi.mock("react-use-measure", () => ({ vi.mock("react-use-measure", () => ({
+5 -5
View File
@@ -40,9 +40,9 @@ import { useMediaDevices } from "../MediaDevicesContext";
import { ObservableScope } from "../state/ObservableScope"; import { ObservableScope } from "../state/ObservableScope";
import { useInitial } from "../useInitial"; import { useInitial } from "../useInitial";
import { import {
useTrackProcessor, useBackgroundEffects,
useTrackProcessorSync, useSyncBackgroundEffects,
} from "../livekit/TrackProcessorContext"; } from "../livekit/BackgroundEffectsContext";
import { getValue } from "../utils/observable"; import { getValue } from "../utils/observable";
import { useBehavior } from "../useBehavior"; import { useBehavior } from "../useBehavior";
import { CallFooter, type FooterSnapshot } from "../components/CallFooter"; import { CallFooter, type FooterSnapshot } from "../components/CallFooter";
@@ -136,7 +136,7 @@ export const LobbyView: FC<Props> = ({
}, },
); );
const { processor } = useTrackProcessor(); const { processor } = useBackgroundEffects();
const initialProcessor = useInitial(() => processor); const initialProcessor = useInitial(() => processor);
const localTrackOptions = useMemo<CreateLocalTracksOptions>( const localTrackOptions = useMemo<CreateLocalTracksOptions>(
@@ -183,7 +183,7 @@ export const LobbyView: FC<Props> = ({
} }
}, [devices, videoInputId, videoTrack]); }, [devices, videoInputId, videoTrack]);
useTrackProcessorSync(videoTrack); useSyncBackgroundEffects(videoTrack);
const [footerVm, setFooterVm] = useState<ViewModel<FooterSnapshot> | null>( const [footerVm, setFooterVm] = useState<ViewModel<FooterSnapshot> | null>(
null, null,
+2 -2
View File
@@ -28,7 +28,7 @@ import {
import { PreferencesSettingsTab } from "./PreferencesSettingsTab"; import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
import { Slider } from "../Slider"; import { Slider } from "../Slider";
import { DeviceSelection } from "./DeviceSelection"; import { DeviceSelection } from "./DeviceSelection";
import { useTrackProcessor } from "../livekit/TrackProcessorContext"; import { useBackgroundEffects } from "../livekit/BackgroundEffectsContext";
import { import {
DeveloperSettingsTab, DeveloperSettingsTab,
type DeveloperSettingsSnapshot, type DeveloperSettingsSnapshot,
@@ -81,7 +81,7 @@ export const SettingsModal: FC<Props> = ({
// Generate a `Checkbox` input to turn blur on or off. // Generate a `Checkbox` input to turn blur on or off.
const BlurCheckbox: React.FC = (): ReactNode => { const BlurCheckbox: React.FC = (): ReactNode => {
const { supported } = useTrackProcessor(); const { supported } = useBackgroundEffects();
const [blurActive, setBlurActive] = useSetting(backgroundBlurSetting); const [blurActive, setBlurActive] = useSetting(backgroundBlurSetting);
+2 -2
View File
@@ -13,7 +13,7 @@ import {
BackgroundEffects, BackgroundEffects,
type BackgroundEffectsOptions, type BackgroundEffectsOptions,
} from "./BackgroundEffects"; } from "./BackgroundEffects";
import { type ProcessorState } from "../livekit/TrackProcessorContext"; import { type BackgroundEffectsState } from "../livekit/BackgroundEffectsContext";
import { flushPromises, testScope, withTestScheduler } from "../utils/test"; import { flushPromises, testScope, withTestScheduler } from "../utils/test";
/** A pipeline that records what it is switched to. */ /** A pipeline that records what it is switched to. */
@@ -45,7 +45,7 @@ function fakePipeline(): {
} }
/** One letter per state: idle, attached. */ /** One letter per state: idle, attached. */
function letter(state: ProcessorState): string { function letter(state: BackgroundEffectsState): string {
return state.processor === undefined ? "i" : "a"; return state.processor === undefined ? "i" : "a";
} }
+3 -3
View File
@@ -15,7 +15,7 @@ import { deepCompare } from "matrix-js-sdk/lib/utils";
import { type Behavior } from "./Behavior"; import { type Behavior } from "./Behavior";
import { type ObservableScope } from "./ObservableScope"; import { type ObservableScope } from "./ObservableScope";
import { type ProcessorState } from "../livekit/TrackProcessorContext"; import { type BackgroundEffectsState } from "../livekit/BackgroundEffectsContext";
const blurRadius = 15; const blurRadius = 15;
@@ -30,7 +30,7 @@ export interface BackgroundEffectsOptions {
/** The background effect pipeline, as the camera tracks and the menus see it. */ /** The background effect pipeline, as the camera tracks and the menus see it. */
export class BackgroundEffects { export class BackgroundEffects {
public readonly state$: Behavior<ProcessorState>; public readonly state$: Behavior<BackgroundEffectsState>;
public constructor( public constructor(
scope: ObservableScope, scope: ObservableScope,
@@ -38,7 +38,7 @@ export class BackgroundEffects {
) { ) {
this.state$ = scope.behavior( this.state$ = scope.behavior(
blur$.pipe( blur$.pipe(
scan<boolean, ProcessorState>( scan<boolean, BackgroundEffectsState>(
(previous, wanted) => { (previous, wanted) => {
// Attached the first time an effect is wanted and never detached // Attached the first time an effect is wanted and never detached
// after, so someone who never turns one on pays for none of it. // after, so someone who never turns one on pays for none of it.
+4 -4
View File
@@ -89,7 +89,7 @@ import { E2eeType } from "../../e2ee/e2eeType";
import { MatrixKeyProvider } from "../../e2ee/matrixKeyProvider"; import { MatrixKeyProvider } from "../../e2ee/matrixKeyProvider";
import { type MuteStates } from "../MuteStates"; import { type MuteStates } from "../MuteStates";
import { HeaderStyle, type UrlParams } from "../../UrlParams"; import { HeaderStyle, type UrlParams } from "../../UrlParams";
import { type ProcessorState } from "../../livekit/TrackProcessorContext"; import { type BackgroundEffectsState } from "../../livekit/BackgroundEffectsContext";
import { type HostBridge, nullHostBridge } from "../../HostBridge"; import { type HostBridge, nullHostBridge } from "../../HostBridge";
import { import {
layoutShallowEquals, layoutShallowEquals,
@@ -501,7 +501,7 @@ export function createCallViewModel$(
options: CallViewModelOptions, options: CallViewModelOptions,
handsRaisedSubject$: Observable<Record<string, RaisedHandInfo>>, handsRaisedSubject$: Observable<Record<string, RaisedHandInfo>>,
reactionsSubject$: Observable<Record<string, ReactionInfo>>, reactionsSubject$: Observable<Record<string, ReactionInfo>>,
trackProcessorState$: Behavior<ProcessorState>, backgroundEffectsState$: Behavior<BackgroundEffectsState>,
): CallViewModel { ): CallViewModel {
const logger = rootLogger.getChild("[CallViewModel]"); const logger = rootLogger.getChild("[CallViewModel]");
const client = matrixRoom.client; const client = matrixRoom.client;
@@ -590,7 +590,7 @@ export function createCallViewModel$(
client, client,
matrixRoom.roomId, matrixRoom.roomId,
mediaDevices, mediaDevices,
trackProcessorState$, backgroundEffectsState$,
livekitKeyProvider, livekitKeyProvider,
controlledAudioDevices, controlledAudioDevices,
options.livekitRoomFactory, options.livekitRoomFactory,
@@ -645,7 +645,7 @@ export function createCallViewModel$(
connection, connection,
mediaDevices, mediaDevices,
muteStates, muteStates,
trackProcessorState$, backgroundEffectsState$,
logger.getChild( logger.getChild(
"[Publisher " + connection.transport.livekit_service_url + "]", "[Publisher " + connection.transport.livekit_service_url + "]",
), ),
@@ -54,7 +54,7 @@ import {
localRtcMember, localRtcMember,
} from "../../utils/test-fixtures"; } from "../../utils/test-fixtures";
import { type Behavior, constant } from "../Behavior"; import { type Behavior, constant } from "../Behavior";
import { type ProcessorState } from "../../livekit/TrackProcessorContext"; import { type BackgroundEffectsState } from "../../livekit/BackgroundEffectsContext";
import { type MediaDevices } from "../MediaDevices"; import { type MediaDevices } from "../MediaDevices";
import { type MatrixRTCMode } from "../../config/ConfigOptions"; import { type MatrixRTCMode } from "../../config/ConfigOptions";
@@ -240,7 +240,7 @@ export function withCallViewModel(mode: MatrixRTCMode) {
}, },
raisedHands$, raisedHands$,
reactions$, reactions$,
new BehaviorSubject<ProcessorState>({ new BehaviorSubject<BackgroundEffectsState>({
processor: undefined, processor: undefined,
supported: undefined, supported: undefined,
}), }),
@@ -209,7 +209,7 @@ describe("LocalMembership", () => {
leave: vi.fn(), leave: vi.fn(),
} as unknown as MatrixRTCSession, } as unknown as MatrixRTCSession,
muteStates: mockMuteStates(), muteStates: mockMuteStates(),
trackProcessorState$: constant({ backgroundEffectsState$: constant({
supported: false, supported: false,
processor: undefined, processor: undefined,
}), }),
@@ -30,7 +30,7 @@ import {
mockMediaDevices, mockMediaDevices,
} from "../../../utils/test"; } from "../../../utils/test";
import { Publisher } from "./Publisher"; import { Publisher } from "./Publisher";
import { type ProcessorState } from "../../../livekit/TrackProcessorContext"; import { type BackgroundEffectsState } from "../../../livekit/BackgroundEffectsContext";
import { type Connection } from "../remoteMembers/Connection"; import { type Connection } from "../remoteMembers/Connection";
import { type MuteStates } from "../../MuteStates"; import { type MuteStates } from "../../MuteStates";
@@ -409,13 +409,13 @@ describe("Bug fix", () => {
describe("turning the camera on with an effect chosen", () => { describe("turning the camera on with an effect chosen", () => {
const processor = {} as ProcessorWrapper<BackgroundOptions>; const processor = {} as ProcessorWrapper<BackgroundOptions>;
let state$: BehaviorSubject<ProcessorState>; let state$: BehaviorSubject<BackgroundEffectsState>;
let publisher: Publisher; let publisher: Publisher;
/** The processor the SDK would give each camera track it made. */ /** The processor the SDK would give each camera track it made. */
let madeWith: unknown[]; let madeWith: unknown[];
beforeEach(() => { beforeEach(() => {
state$ = new BehaviorSubject<ProcessorState>({ state$ = new BehaviorSubject<BackgroundEffectsState>({
supported: true, supported: true,
processor: undefined, processor: undefined,
}); });
@@ -26,9 +26,9 @@ import type { Behavior } from "../../Behavior.ts";
import type { MediaDevices, SelectedDevice } from "../../MediaDevices.ts"; import type { MediaDevices, SelectedDevice } from "../../MediaDevices.ts";
import type { MuteStates } from "../../MuteStates.ts"; import type { MuteStates } from "../../MuteStates.ts";
import { import {
type ProcessorState, type BackgroundEffectsState,
trackProcessorSync, syncBackgroundEffects,
} from "../../../livekit/TrackProcessorContext.tsx"; } from "../../../livekit/BackgroundEffectsContext.tsx";
import { observeTrackReference$ } from "../../observeTrackReference"; import { observeTrackReference$ } from "../../observeTrackReference";
import { type Connection } from "../remoteMembers/Connection.ts"; import { type Connection } from "../remoteMembers/Connection.ts";
@@ -54,7 +54,7 @@ export class Publisher {
* @param connection - The connection to use for publishing. * @param connection - The connection to use for publishing.
* @param devices - The media devices to use for audio and video input. * @param devices - The media devices to use for audio and video input.
* @param muteStates - The mute states for audio and video. * @param muteStates - The mute states for audio and video.
* @param trackerProcessorState$ - The processor state for the video track processor (e.g. background blur). * @param backgroundEffectsState$ - The processor state for the video track processor (e.g. background blur).
* @param logger - The logger to use for logging :D. * @param logger - The logger to use for logging :D.
* @param controlledAudioDevices - Whether the app hosting Element Call * @param controlledAudioDevices - Whether the app hosting Element Call
* controls the audio output devices, rather than the browser. * controls the audio output devices, rather than the browser.
@@ -63,7 +63,7 @@ export class Publisher {
private connection: Pick<Connection, "livekitRoom" | "state$">, //setE2EEEnabled, private connection: Pick<Connection, "livekitRoom" | "state$">, //setE2EEEnabled,
devices: MediaDevices, devices: MediaDevices,
private readonly muteStates: MuteStates, private readonly muteStates: MuteStates,
private readonly trackerProcessorState$: Behavior<ProcessorState>, private readonly backgroundEffectsState$: Behavior<BackgroundEffectsState>,
private logger: Logger, private logger: Logger,
controlledAudioDevices: boolean, controlledAudioDevices: boolean,
) { ) {
@@ -73,8 +73,11 @@ export class Publisher {
this.logger.error("Failed to set E2EE enabled on room", e); this.logger.error("Failed to set E2EE enabled on room", e);
}); });
// Setup track processor syncing (blur) this.observeBackgroundEffects(
this.observeTrackProcessors(this.scope, room, this.trackerProcessorState$); this.scope,
room,
this.backgroundEffectsState$,
);
// Observe media device changes and update LiveKit active devices accordingly // Observe media device changes and update LiveKit active devices accordingly
this.observeMediaDevices(this.scope, devices, controlledAudioDevices); this.observeMediaDevices(this.scope, devices, controlledAudioDevices);
@@ -412,7 +415,7 @@ export class Publisher {
this.muteStates.video.setHandler(async (enable) => { this.muteStates.video.setHandler(async (enable) => {
try { try {
this.logger.debug(`handler: Setting LiveKit camera enabled: ${enable}`); this.logger.debug(`handler: Setting LiveKit camera enabled: ${enable}`);
const { processor } = this.trackerProcessorState$.value; const { processor } = this.backgroundEffectsState$.value;
if (enable && processor) await this.dropCameraWithoutEffect(lkRoom); if (enable && processor) await this.dropCameraWithoutEffect(lkRoom);
await lkRoom.localParticipant.setCameraEnabled(enable); await lkRoom.localParticipant.setCameraEnabled(enable);
// Unmute will restart the track if it was paused upstream, // Unmute will restart the track if it was paused upstream,
@@ -441,10 +444,10 @@ export class Publisher {
await lkRoom.localParticipant.unpublishTrack(track); await lkRoom.localParticipant.unpublishTrack(track);
} }
private observeTrackProcessors( private observeBackgroundEffects(
scope: ObservableScope, scope: ObservableScope,
room: LivekitRoom, room: LivekitRoom,
trackerProcessorState$: Behavior<ProcessorState>, backgroundEffectsState$: Behavior<BackgroundEffectsState>,
): void { ): void {
const track$ = scope.behavior( const track$ = scope.behavior(
observeTrackReference$(room.localParticipant, Track.Source.Camera).pipe( observeTrackReference$(room.localParticipant, Track.Source.Camera).pipe(
@@ -455,11 +458,11 @@ export class Publisher {
), ),
null, null,
); );
trackProcessorSync(scope, track$, trackerProcessorState$); syncBackgroundEffects(scope, track$, backgroundEffectsState$);
// Every camera track the SDK makes, on joining or on turning the camera // Every camera track the SDK makes, on joining or on turning the camera
// on, then starts with the pipeline on. One attached after publishing lets // on, then starts with the pipeline on. One attached after publishing lets
// the room through while it is built. // the room through while it is built.
trackerProcessorState$.pipe(scope.bind()).subscribe(({ processor }) => { backgroundEffectsState$.pipe(scope.bind()).subscribe(({ processor }) => {
room.options.videoCaptureDefaults = { room.options.videoCaptureDefaults = {
...room.options.videoCaptureDefaults, ...room.options.videoCaptureDefaults,
processor, processor,
@@ -655,7 +655,7 @@ describe("remote track logging", () => {
// function createSetup(): void { // function createSetup(): void {
// setUpPublishConnection(); // setUpPublishConnection();
// const fakeTrackProcessorSubject$ = new BehaviorSubject<ProcessorState>({ // const fakeTrackProcessorSubject$ = new BehaviorSubject<BackgroundEffectsState>({
// supported: true, // supported: true,
// processor: undefined, // processor: undefined,
// }); // });
@@ -26,7 +26,7 @@ import type {
} from "../../../livekit/openIDSFU.ts"; } from "../../../livekit/openIDSFU.ts";
import type { MediaDevices } from "../../MediaDevices.ts"; import type { MediaDevices } from "../../MediaDevices.ts";
import type { Behavior } from "../../Behavior.ts"; import type { Behavior } from "../../Behavior.ts";
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx"; import type { BackgroundEffectsState } from "../../../livekit/BackgroundEffectsContext.tsx";
import { getLiveKitOptions } from "../../../livekit/options.ts"; import { getLiveKitOptions } from "../../../livekit/options.ts";
import { import {
advancedCamera, advancedCamera,
@@ -60,7 +60,7 @@ export class ECConnectionFactory implements ConnectionFactory {
* @param client - The OpenID client parts for authentication, needed to get openID and JWT tokens. * @param client - The OpenID client parts for authentication, needed to get openID and JWT tokens.
* @param roomId - The current room ID. * @param roomId - The current room ID.
* @param devices - Used for video/audio out/in capture options. * @param devices - Used for video/audio out/in capture options.
* @param processorState$ - Effects like background blur (only for publishing connection?) * @param backgroundEffectsState$ - Effects like background blur (only for publishing connection?)
* @param livekitKeyProvider - Optional key provider for end-to-end encryption. * @param livekitKeyProvider - Optional key provider for end-to-end encryption.
* @param controlledAudioDevices - Option to indicate whether audio output device is controlled externally (native mobile app). * @param controlledAudioDevices - Option to indicate whether audio output device is controlled externally (native mobile app).
* @param livekitRoomFactory - Optional factory function (for testing) to create LivekitRoom instances. If not provided, a default factory is used. * @param livekitRoomFactory - Optional factory function (for testing) to create LivekitRoom instances. If not provided, a default factory is used.
@@ -69,7 +69,7 @@ export class ECConnectionFactory implements ConnectionFactory {
private client: OpenIDClientParts, private client: OpenIDClientParts,
private readonly roomId: string, private readonly roomId: string,
private devices: MediaDevices, private devices: MediaDevices,
private processorState$: Behavior<ProcessorState>, private backgroundEffectsState$: Behavior<BackgroundEffectsState>,
livekitKeyProvider: BaseKeyProvider | undefined, livekitKeyProvider: BaseKeyProvider | undefined,
private controlledAudioDevices: boolean, private controlledAudioDevices: boolean,
livekitRoomFactory?: () => LivekitRoom, livekitRoomFactory?: () => LivekitRoom,
@@ -77,7 +77,7 @@ export class ECConnectionFactory implements ConnectionFactory {
const defaultFactory = (): LivekitRoom => { const defaultFactory = (): LivekitRoom => {
const roomOptions = generateRoomOption({ const roomOptions = generateRoomOption({
devices: this.devices, devices: this.devices,
processorState: this.processorState$.value, backgroundEffectsState: this.backgroundEffectsState$.value,
e2eeLivekitOptions: livekitKeyProvider && { e2eeLivekitOptions: livekitKeyProvider && {
keyProvider: livekitKeyProvider, keyProvider: livekitKeyProvider,
// It's important that every room use a separate E2EE worker. // It's important that every room use a separate E2EE worker.
@@ -129,12 +129,12 @@ export class ECConnectionFactory implements ConnectionFactory {
*/ */
function generateRoomOption({ function generateRoomOption({
devices, devices,
processorState, backgroundEffectsState,
e2eeLivekitOptions, e2eeLivekitOptions,
controlledAudioDevices, controlledAudioDevices,
}: { }: {
devices: MediaDevices; devices: MediaDevices;
processorState: ProcessorState; backgroundEffectsState: BackgroundEffectsState;
e2eeLivekitOptions: e2eeLivekitOptions:
| E2EEManagerOptions | E2EEManagerOptions
| { e2eeManager: BaseE2EEManager } | { e2eeManager: BaseE2EEManager }
@@ -147,7 +147,7 @@ function generateRoomOption({
let videoCaptureDefaults = { let videoCaptureDefaults = {
...liveKitOptions.videoCaptureDefaults, ...liveKitOptions.videoCaptureDefaults,
deviceId: devices.videoInput.selected$.value?.id, deviceId: devices.videoInput.selected$.value?.id,
processor: processorState.processor, processor: backgroundEffectsState.processor,
}; };
let publishDefaults = liveKitOptions.publishDefaults; let publishDefaults = liveKitOptions.publishDefaults;
@@ -20,7 +20,7 @@ import {
mockMediaDevices, mockMediaDevices,
ownMemberMock, ownMemberMock,
} from "../../../utils/test.ts"; } from "../../../utils/test.ts";
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx"; import type { BackgroundEffectsState } from "../../../livekit/BackgroundEffectsContext.tsx";
import { constant } from "../../Behavior"; import { constant } from "../../Behavior";
import { import {
echoCancellationSetting, echoCancellationSetting,
@@ -80,7 +80,7 @@ describe("ECConnectionFactory - Audio inputs options", () => {
mockClient, mockClient,
"!roomid:example.org", "!roomid:example.org",
mockMediaDevices({}), mockMediaDevices({}),
new BehaviorSubject<ProcessorState>({ new BehaviorSubject<BackgroundEffectsState>({
supported: true, supported: true,
processor: undefined, processor: undefined,
}), }),
@@ -128,7 +128,7 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => {
select: () => {}, select: () => {},
}, },
}), }),
new BehaviorSubject<ProcessorState>({ new BehaviorSubject<BackgroundEffectsState>({
supported: true, supported: true,
processor: undefined, processor: undefined,
}), }),
@@ -163,7 +163,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
mockClient, mockClient,
"!roomid:example.org", "!roomid:example.org",
mockMediaDevices({}), mockMediaDevices({}),
new BehaviorSubject<ProcessorState>({ new BehaviorSubject<BackgroundEffectsState>({
supported: true, supported: true,
processor: undefined, processor: undefined,
}), }),
@@ -200,7 +200,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
mockClient, mockClient,
"!roomid:example.org", "!roomid:example.org",
mockMediaDevices({}), mockMediaDevices({}),
new BehaviorSubject<ProcessorState>({ new BehaviorSubject<BackgroundEffectsState>({
supported: true, supported: true,
processor: undefined, processor: undefined,
}), }),
@@ -238,7 +238,7 @@ describe("ECConnectionFactory - Camera quality settings", () => {
mockClient, mockClient,
"!roomid:example.org", "!roomid:example.org",
mockMediaDevices({}), mockMediaDevices({}),
new BehaviorSubject<ProcessorState>({ new BehaviorSubject<BackgroundEffectsState>({
supported: true, supported: true,
processor: undefined, processor: undefined,
}), }),
@@ -26,7 +26,7 @@ import {
ownMemberMock, ownMemberMock,
withTestScheduler, withTestScheduler,
} from "../../../utils/test.ts"; } from "../../../utils/test.ts";
import { type ProcessorState } from "../../../livekit/TrackProcessorContext.tsx"; import { type BackgroundEffectsState } from "../../../livekit/BackgroundEffectsContext.tsx";
import { import {
areLivekitTransportsEqual, areLivekitTransportsEqual,
createRemoteMatrixLivekitMembers$, createRemoteMatrixLivekitMembers$,
@@ -72,7 +72,7 @@ beforeEach(() => {
mockClient, mockClient,
"!roomid:example.org", "!roomid:example.org",
mockMediaDevices({}), mockMediaDevices({}),
new BehaviorSubject<ProcessorState>({ new BehaviorSubject<BackgroundEffectsState>({
supported: true, supported: true,
processor: undefined, processor: undefined,
}), }),