diff --git a/src/frontend/src/features/rooms/livekit/components/Effects.tsx b/src/frontend/src/features/rooms/livekit/components/Effects.tsx index 4037d069..eb954167 100644 --- a/src/frontend/src/features/rooms/livekit/components/Effects.tsx +++ b/src/frontend/src/features/rooms/livekit/components/Effects.tsx @@ -1,24 +1,72 @@ -import { useEffect, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' import { useLocalParticipant } from '@livekit/components-react' import { LocalVideoTrack } from 'livekit-client' -import { Div, P } from '@/primitives' +import { Text, P, ToggleButton } from '@/primitives' import { useTranslation } from 'react-i18next' +import { HStack, VStack } from '@/styled-system/jsx' +import { + BackgroundBlur, + BackgroundOptions, + ProcessorWrapper, + BackgroundTransformer, +} from '@livekit/track-processors' + +enum BlurRadius { + NONE = 0, + LIGHT = 5, + NORMAL = 10, +} export const Effects = () => { const { t } = useTranslation('rooms', { keyPrefix: 'effects' }) - const { isCameraEnabled, cameraTrack } = useLocalParticipant() + const { isCameraEnabled, cameraTrack, localParticipant } = + useLocalParticipant() const videoRef = useRef(null) + const [processorPending, setProcessorPending] = useState(false) const localCameraTrack = cameraTrack?.track as LocalVideoTrack - useEffect(() => { - const videoElement = videoRef.current + const getProcessor = () => { + return localCameraTrack?.getProcessor() as ProcessorWrapper + } - const attachVideoTrack = async () => { - if (!videoElement) return - localCameraTrack?.attach(videoElement) + const getBlurRadius = (): BlurRadius => { + const processor = getProcessor() + return ( + (processor?.transformer as BackgroundTransformer)?.options?.blurRadius || + BlurRadius.NONE + ) + } + + const toggleBlur = async (blurRadius: number) => { + if (!isCameraEnabled) await localParticipant.setCameraEnabled(true) + if (!localCameraTrack) return + + setProcessorPending(true) + + const processor = getProcessor() + const currentBlurRadius = getBlurRadius() + + try { + if (blurRadius == currentBlurRadius && processor) { + await localCameraTrack.stopProcessor() + } else if (!processor) { + await localCameraTrack.setProcessor(BackgroundBlur(blurRadius)) + } else { + await processor?.updateTransformerOptions({ blurRadius }) + } + } catch (error) { + console.error('Error applying blur:', error) + } finally { + setProcessorPending(false) } + } + useEffect(() => { + const videoElement = videoRef.current + if (!videoElement) return + + const attachVideoTrack = async () => localCameraTrack?.attach(videoElement) attachVideoTrack() return () => { @@ -27,8 +75,16 @@ export const Effects = () => { } }, [localCameraTrack, isCameraEnabled]) + const isSelected = (blurRadius: BlurRadius) => { + return isCameraEnabled && getBlurRadius() == blurRadius + } + + const tooltipLabel = (blurRadius: BlurRadius) => { + return t(`blur.${isSelected(blurRadius) ? 'clear' : 'apply'}`) + } + return ( -
+ {localCameraTrack && isCameraEnabled ? (
)} - + {ProcessorWrapper.isSupported ? ( + + await toggleBlur(BlurRadius.LIGHT)} + isSelected={isSelected(BlurRadius.LIGHT)} + > + {t('blur.light')} + + await toggleBlur(BlurRadius.NORMAL)} + isSelected={isSelected(BlurRadius.NORMAL)} + > + {t('blur.normal')} + + + ) : ( + {t('notAvailable')} + )} + ) } diff --git a/src/frontend/src/locales/de/rooms.json b/src/frontend/src/locales/de/rooms.json index 386e268f..09fe50ea 100644 --- a/src/frontend/src/locales/de/rooms.json +++ b/src/frontend/src/locales/de/rooms.json @@ -74,7 +74,14 @@ } }, "effects": { - "activateCamera": "" + "activateCamera": "", + "notAvailable": "", + "blur": { + "light": "", + "normal": "", + "apply": "", + "clear": "" + } }, "sidePanel": { "heading": { diff --git a/src/frontend/src/locales/en/rooms.json b/src/frontend/src/locales/en/rooms.json index 2b24508f..ca12f331 100644 --- a/src/frontend/src/locales/en/rooms.json +++ b/src/frontend/src/locales/en/rooms.json @@ -72,7 +72,14 @@ } }, "effects": { - "activateCamera": "Camera is disabled" + "activateCamera": "Your camera is disabled. Choose an option to enable it.", + "notAvailable": "The blur effect will be available soon on your browser. We're working on it! In the meantime, you can use Google Chrome :(", + "blur": { + "light": "Light blur", + "normal": "Blur", + "apply": "Enable blur", + "clear": "Disable blur" + } }, "sidePanel": { "heading": { diff --git a/src/frontend/src/locales/fr/rooms.json b/src/frontend/src/locales/fr/rooms.json index f9e7af56..1378b7f2 100644 --- a/src/frontend/src/locales/fr/rooms.json +++ b/src/frontend/src/locales/fr/rooms.json @@ -72,7 +72,14 @@ } }, "effects": { - "activateCamera": "Votre camera est désactivée" + "activateCamera": "Votre camera est désactivée. Choisissez une option pour l'activer.", + "notAvailable": "L'effet de flou sera bientôt disponible sur votre navigateur. Nous y travaillons ! En attendant, vous pouvez utiliser Google Chrome :(", + "blur": { + "light": "Léger flou", + "normal": "Flou", + "apply": "Appliquer le flou", + "clear": "Désactiver le flou" + } }, "sidePanel": { "heading": {