/* Copyright 2024-2025 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 { ProcessorWrapper, supportsBackgroundProcessors, type BackgroundOptions, } from "@livekit/track-processors"; import { createContext, type FC, type JSX, use, useEffect, useMemo, } from "react"; import { type LocalVideoTrack } from "livekit-client"; import { combineLatest, map, type Observable } from "rxjs"; import { useObservable } from "observable-hooks"; import { backgroundBlur as backgroundBlurSettings, useSetting, } from "../settings/settings"; import { BlurBackgroundTransformer } from "./BlurBackgroundTransformer"; import { type Behavior } from "../state/Behavior"; import { type ObservableScope } from "../state/ObservableScope"; //TODO-MULTI-SFU: This is not yet fully there. // it is a combination of exposing observable and react hooks. // preferably we should not make this a context anymore and instead just a vm? export type ProcessorState = { supported: boolean | undefined; processor: undefined | ProcessorWrapper; }; const ProcessorContext = createContext(undefined); export function useTrackProcessor(): ProcessorState { const state = use(ProcessorContext); if (state === undefined) throw new Error( "useTrackProcessor must be used within a ProcessorProvider", ); return state; } export function useTrackProcessorObservable$(): Observable { const state = use(ProcessorContext); if (state === undefined) throw new Error( "useTrackProcessor must be used within a ProcessorProvider", ); const state$ = useObservable( (init$) => init$.pipe(map(([init]) => init)), [state], ); return state$; } /** * Updates your video tracks to always use the given processor. */ export const trackProcessorSync = ( scope: ObservableScope, videoTrack$: Behavior, processor$: Behavior, ): void => { combineLatest([videoTrack$, processor$]) .pipe(scope.bind()) .subscribe(([videoTrack, processorState]) => { if (!processorState) return; if (!videoTrack) return; const { processor } = processorState; if (processor && !videoTrack.getProcessor()) { void videoTrack.setProcessor(processor); } if (!processor && videoTrack.getProcessor()) { void videoTrack.stopProcessor(); } }); }; export const useTrackProcessorSync = ( videoTrack: LocalVideoTrack | null, ): void => { const { processor } = useTrackProcessor(); useEffect(() => { if (!videoTrack) return; if (processor && !videoTrack.getProcessor()) { void videoTrack.setProcessor(processor); } if (!processor && videoTrack.getProcessor()) { void videoTrack.stopProcessor(); } }, [processor, videoTrack]); }; interface Props { children: JSX.Element; } export const ProcessorProvider: FC = ({ children }) => { // The setting the user wants to have const [blurActivated] = useSetting(backgroundBlurSettings); const supported = useMemo(() => supportsBackgroundProcessors(), []); const blur = useMemo( () => new ProcessorWrapper( new BlurBackgroundTransformer({ blurRadius: 15 }), "background-blur", ), [], ); // This is the actual state exposed through the context const processorState = useMemo( () => ({ supported, processor: supported && blurActivated ? blur : undefined, }), [supported, blurActivated, blur], ); return {children}; };