diff --git a/src/frontend/public/assets/backgrounds/1.jpg b/src/frontend/public/assets/backgrounds/1.jpg new file mode 100644 index 00000000..7a449257 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/1.jpg differ diff --git a/src/frontend/public/assets/backgrounds/2.jpg b/src/frontend/public/assets/backgrounds/2.jpg new file mode 100644 index 00000000..a5afb860 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/2.jpg differ diff --git a/src/frontend/public/assets/backgrounds/3.jpg b/src/frontend/public/assets/backgrounds/3.jpg new file mode 100644 index 00000000..ac00c660 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/3.jpg differ diff --git a/src/frontend/public/assets/backgrounds/4.jpg b/src/frontend/public/assets/backgrounds/4.jpg new file mode 100644 index 00000000..4fab157a Binary files /dev/null and b/src/frontend/public/assets/backgrounds/4.jpg differ diff --git a/src/frontend/public/assets/backgrounds/5.jpg b/src/frontend/public/assets/backgrounds/5.jpg new file mode 100644 index 00000000..068de06f Binary files /dev/null and b/src/frontend/public/assets/backgrounds/5.jpg differ diff --git a/src/frontend/public/assets/backgrounds/6.jpg b/src/frontend/public/assets/backgrounds/6.jpg new file mode 100644 index 00000000..820dc556 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/6.jpg differ diff --git a/src/frontend/public/assets/backgrounds/7.jpg b/src/frontend/public/assets/backgrounds/7.jpg new file mode 100644 index 00000000..aa79b23d Binary files /dev/null and b/src/frontend/public/assets/backgrounds/7.jpg differ diff --git a/src/frontend/public/assets/backgrounds/8.jpg b/src/frontend/public/assets/backgrounds/8.jpg new file mode 100644 index 00000000..e0617e95 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/8.jpg differ diff --git a/src/frontend/public/assets/backgrounds/thumbnails/1.jpg b/src/frontend/public/assets/backgrounds/thumbnails/1.jpg new file mode 100644 index 00000000..4b7c8ee9 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/thumbnails/1.jpg differ diff --git a/src/frontend/public/assets/backgrounds/thumbnails/2.jpg b/src/frontend/public/assets/backgrounds/thumbnails/2.jpg new file mode 100644 index 00000000..a6dfa075 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/thumbnails/2.jpg differ diff --git a/src/frontend/public/assets/backgrounds/thumbnails/3.jpg b/src/frontend/public/assets/backgrounds/thumbnails/3.jpg new file mode 100644 index 00000000..bcb6f813 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/thumbnails/3.jpg differ diff --git a/src/frontend/public/assets/backgrounds/thumbnails/4.jpg b/src/frontend/public/assets/backgrounds/thumbnails/4.jpg new file mode 100644 index 00000000..46a8711b Binary files /dev/null and b/src/frontend/public/assets/backgrounds/thumbnails/4.jpg differ diff --git a/src/frontend/public/assets/backgrounds/thumbnails/5.jpg b/src/frontend/public/assets/backgrounds/thumbnails/5.jpg new file mode 100644 index 00000000..bc4ede10 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/thumbnails/5.jpg differ diff --git a/src/frontend/public/assets/backgrounds/thumbnails/6.jpg b/src/frontend/public/assets/backgrounds/thumbnails/6.jpg new file mode 100644 index 00000000..8ef981ad Binary files /dev/null and b/src/frontend/public/assets/backgrounds/thumbnails/6.jpg differ diff --git a/src/frontend/public/assets/backgrounds/thumbnails/7.jpg b/src/frontend/public/assets/backgrounds/thumbnails/7.jpg new file mode 100644 index 00000000..cbc06ca3 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/thumbnails/7.jpg differ diff --git a/src/frontend/public/assets/backgrounds/thumbnails/8.jpg b/src/frontend/public/assets/backgrounds/thumbnails/8.jpg new file mode 100644 index 00000000..e420b927 Binary files /dev/null and b/src/frontend/public/assets/backgrounds/thumbnails/8.jpg differ diff --git a/src/frontend/src/components/icons/BlurOn.tsx b/src/frontend/src/components/icons/BlurOn.tsx new file mode 100644 index 00000000..c4bd4ec0 --- /dev/null +++ b/src/frontend/src/components/icons/BlurOn.tsx @@ -0,0 +1,16 @@ +export const BlurOn = () => { + return ( + + + + ) +} diff --git a/src/frontend/src/components/icons/BlurOnStrong.tsx b/src/frontend/src/components/icons/BlurOnStrong.tsx new file mode 100644 index 00000000..cc42fd33 --- /dev/null +++ b/src/frontend/src/components/icons/BlurOnStrong.tsx @@ -0,0 +1,18 @@ +export const BlurOnStrong = () => { + return ( + + + + ) +} diff --git a/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx b/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx index 8003eb11..bf7b6f1d 100644 --- a/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx +++ b/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx @@ -2,12 +2,16 @@ import { LocalVideoTrack } from 'livekit-client' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { - BackgroundBlurFactory, - BackgroundBlurProcessorInterface, + BackgroundProcessorFactory, + BackgroundProcessorInterface, + ProcessorType, } from '../blur' import { css } from '@/styled-system/css' import { Text, P, ToggleButton, H } from '@/primitives' -import { HStack, styled } from '@/styled-system/jsx' +import { styled } from '@/styled-system/jsx' +import { BackgroundOptions } from '@livekit/track-processors' +import { BlurOn } from '@/components/icons/BlurOn' +import { BlurOnStrong } from '@/components/icons/BlurOnStrong' enum BlurRadius { NONE = 0, @@ -15,21 +19,20 @@ enum BlurRadius { NORMAL = 10, } -const isSupported = BackgroundBlurFactory.isSupported() +const isSupported = BackgroundProcessorFactory.isSupported() const Information = styled('div', { base: { backgroundColor: 'orange.50', borderRadius: '4px', padding: '0.75rem 0.75rem', - marginTop: '0.8rem', alignItems: 'start', }, }) export type EffectsConfigurationProps = { videoTrack: LocalVideoTrack - onSubmit?: (processor?: BackgroundBlurProcessorInterface) => void + onSubmit?: (processor?: BackgroundProcessorInterface) => void layout?: 'vertical' | 'horizontal' } @@ -55,46 +58,56 @@ export const EffectsConfiguration = ({ } }, [videoTrack, videoTrack?.isMuted]) - const toggleBlur = async (blurRadius: number) => { + const toggleEffect = async ( + type: ProcessorType, + options: BackgroundOptions + ) => { if (!videoTrack) return setProcessorPending(true) const processor = getProcessor() - const currentBlurRadius = getBlurRadius() try { - if (blurRadius == currentBlurRadius && processor) { + if (isSelected(type, options)) { + // Stop processor. await videoTrack.stopProcessor() onSubmit?.(undefined) - } else if (!processor) { - const newProcessor = BackgroundBlurFactory.getProcessor({ blurRadius })! + } else if (!processor || processor.serialize().type !== type) { + // Change processor. + const newProcessor = BackgroundProcessorFactory.getProcessor( + type, + options + )! await videoTrack.setProcessor(newProcessor) onSubmit?.(newProcessor) } else { - processor?.update({ blurRadius }) + // Update processor. + processor?.update(options) // We want to trigger onSubmit when options changes so the parent component is aware of it. onSubmit?.(processor) } } catch (error) { console.error('Error applying blur:', error) } finally { - setProcessorPending(false) + // Without setTimeout the DOM is not refreshing when updating the options. + setTimeout(() => setProcessorPending(false)) } } const getProcessor = () => { - return videoTrack?.getProcessor() as BackgroundBlurProcessorInterface + return videoTrack?.getProcessor() as BackgroundProcessorInterface } - const getBlurRadius = (): BlurRadius => { + const isSelected = (type: ProcessorType, options: BackgroundOptions) => { const processor = getProcessor() - return processor?.options.blurRadius || BlurRadius.NONE + const processorSerialized = processor?.serialize() + return ( + !!processor && + processorSerialized.type === type && + JSON.stringify(processorSerialized.options) === JSON.stringify(options) + ) } - const isSelected = (blurRadius: BlurRadius) => { - return getBlurRadius() == blurRadius - } - - const tooltipLabel = (blurRadius: BlurRadius) => { - return t(`blur.${isSelected(blurRadius) ? 'clear' : 'apply'}`) + const tooltipLabel = (type: ProcessorType, options: BackgroundOptions) => { + return t(`${type}.${isSelected(type, options) ? 'clear' : 'apply'}`) } return ( @@ -104,7 +117,7 @@ export const EffectsConfiguration = ({ ? { display: 'flex', flexDirection: 'column', - gap: '0.5rem', + gap: '1.5rem', } : { display: 'flex', @@ -112,6 +125,7 @@ export const EffectsConfiguration = ({ flexDirection: 'column', md: { flexDirection: 'row', + overflow: 'hidden', }, } )} @@ -164,49 +178,138 @@ export const EffectsConfiguration = ({ md: { borderLeft: '1px solid #dadce0', paddingLeft: '1.5rem', + width: '420px', + flexShrink: 0, }, } : {} )} > - - {t('heading')} - {isSupported ? ( - - await toggleBlur(BlurRadius.LIGHT)} - isSelected={isSelected(BlurRadius.LIGHT)} - > - {t('blur.light')} - - await toggleBlur(BlurRadius.NORMAL)} - isSelected={isSelected(BlurRadius.NORMAL)} - > - {t('blur.normal')} - - + <> +
+
+ + {t('blur.title')} + +
+ + await toggleEffect(ProcessorType.BLUR, { + blurRadius: BlurRadius.LIGHT, + }) + } + isSelected={isSelected(ProcessorType.BLUR, { + blurRadius: BlurRadius.LIGHT, + })} + > + + + + await toggleEffect(ProcessorType.BLUR, { + blurRadius: BlurRadius.NORMAL, + }) + } + isSelected={isSelected(ProcessorType.BLUR, { + blurRadius: BlurRadius.NORMAL, + })} + > + + +
+
+
+ + {t('virtual.title')} + +
+ {[...Array(8).keys()].map((i) => { + const imagePath = `/assets/backgrounds/${i + 1}.jpg` + const thumbnailPath = `/assets/backgrounds/thumbnails/${i + 1}.jpg` + return ( + + await toggleEffect(ProcessorType.VIRTUAL, { + imagePath, + }) + } + isSelected={isSelected(ProcessorType.VIRTUAL, { + imagePath, + })} + className={css({ + bgSize: 'cover', + })} + style={{ + backgroundImage: `url(${thumbnailPath})`, + }} + > + ) + })} +
+
+
+ + ⚠︎ {t('experimental')} + + ) : ( - {t('notAvailable')} + + {t('notAvailable')} + )} - - ⚠︎ {t('experimental')} - ) diff --git a/src/frontend/src/locales/de/rooms.json b/src/frontend/src/locales/de/rooms.json index 7d8dbc30..5812c476 100644 --- a/src/frontend/src/locales/de/rooms.json +++ b/src/frontend/src/locales/de/rooms.json @@ -19,6 +19,11 @@ "label": "" }, "heading": "", + "effects": { + "description": "", + "title": "", + "subTitle": "" + }, "joinLabel": "", "joinMeeting": "", "toggleOff": "", @@ -102,11 +107,17 @@ "notAvailable": "", "heading": "", "blur": { + "title": "", "light": "", "normal": "", "apply": "", "clear": "" }, + "virtual": { + "title": "", + "apply": "", + "clear": "" + }, "experimental": "" }, "sidePanel": { diff --git a/src/frontend/src/locales/en/rooms.json b/src/frontend/src/locales/en/rooms.json index b58ac8a3..3e7e19ce 100644 --- a/src/frontend/src/locales/en/rooms.json +++ b/src/frontend/src/locales/en/rooms.json @@ -20,7 +20,8 @@ }, "effects": { "description": "Apply effects", - "title": "Effects" + "title": "Effects", + "subTitle": "Configure your camera's effects." }, "heading": "Join the meeting", "joinLabel": "Join", @@ -102,14 +103,20 @@ }, "effects": { "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 for best performance or Firefox :(", + "notAvailable": "Video effects will be available soon on your browser. We're working on it! In the meantime, you can use Google Chrome for best performance or Firefox :(", "heading": "Blur", "blur": { + "title": "Background blur", "light": "Light blur", "normal": "Blur", "apply": "Enable blur", "clear": "Disable blur" }, + "virtual": { + "title": "Virtual background", + "apply": "Enable virtual background", + "clear": "Disable virtual background" + }, "experimental": "Experimental feature. A v2 is coming for full browser support and improved quality." }, "sidePanel": { diff --git a/src/frontend/src/locales/fr/rooms.json b/src/frontend/src/locales/fr/rooms.json index d99cae54..4583a618 100644 --- a/src/frontend/src/locales/fr/rooms.json +++ b/src/frontend/src/locales/fr/rooms.json @@ -21,7 +21,8 @@ "heading": "Rejoindre la réunion", "effects": { "description": "Appliquer des effets", - "title": "Effets" + "title": "Effets", + "subTitle": "Paramétrez les effets de votre caméra." }, "joinLabel": "Rejoindre", "joinMeeting": "Rejoindre la réjoindre", @@ -102,14 +103,20 @@ }, "effects": { "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 pour une meilleure performance ou Firefox :(", + "notAvailable": "Les effets vidéo seront bientôt disponible sur votre navigateur. Nous y travaillons ! En attendant, vous pouvez utiliser Google Chrome pour une meilleure performance ou Firefox :(", "heading": "Flou", "blur": { + "title": "Flou d'arrière-plan", "light": "Léger flou", "normal": "Flou", "apply": "Appliquer le flou", "clear": "Désactiver le flou" }, + "virtual": { + "title": "Arrière-plan virtuel", + "apply": "Appliquer l'arrière plan virtuel", + "clear": "Désactiver l'arrière plan virtuel" + }, "experimental": "Fonctionnalité expérimentale. Une v2 arrive pour un support complet sur tous les navigateurs et une meilleur qualité." }, "sidePanel": { diff --git a/src/frontend/src/primitives/ToggleButton.tsx b/src/frontend/src/primitives/ToggleButton.tsx index d398c725..ab844acb 100644 --- a/src/frontend/src/primitives/ToggleButton.tsx +++ b/src/frontend/src/primitives/ToggleButton.tsx @@ -27,7 +27,7 @@ export const ToggleButton = ({ <> {componentProps.children as ReactNode} diff --git a/src/frontend/src/primitives/buttonRecipe.ts b/src/frontend/src/primitives/buttonRecipe.ts index 1e82b7a4..52b3d9bb 100644 --- a/src/frontend/src/primitives/buttonRecipe.ts +++ b/src/frontend/src/primitives/buttonRecipe.ts @@ -86,6 +86,22 @@ export const buttonRecipe = cva({ backgroundColor: 'greyscale.100/50', }, }, + bigSquare: { + width: '56px', + height: '56px', + borderColor: 'greyscale.200', + borderRadius: '4px', + backgroundColor: 'greyscale.50', + padding: '0', + flexShrink: 0, + '&[data-hovered]': { + backgroundColor: 'greyscale.100', + }, + transition: 'box-shadow 0.2s ease-in-out', + '&[data-selected]': { + boxShadow: 'token(colors.primary.400) 0px 0px 0px 3px inset', + }, + }, tertiary: { backgroundColor: 'primary.100', color: 'primary.800',