wip refactor effects configuration
- add a button to explicitely enable camera when a room display selected option even if camera is disabled, and suggest to the user to enable its camera to allow her configure the effects - remove useless useSyncAfterDelay issue with rendering when enabling a processor, livekit is working on it, it's a bad approach to fix it with a deteministic delay it doesn't work and complexify the code I thought about listening to some html event however, we should fix the root cause instead
This commit is contained in:
+77
-36
@@ -8,13 +8,11 @@ import {
|
||||
BackgroundOptions,
|
||||
} from '../blur'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { Text, P, ToggleButton, H } from '@/primitives'
|
||||
import { Text, P, ToggleButton, H, Button } from '@/primitives'
|
||||
import { styled } from '@/styled-system/jsx'
|
||||
import { BlurOn } from '@/components/icons/BlurOn'
|
||||
import { BlurOnStrong } from '@/components/icons/BlurOnStrong'
|
||||
import { useTrackToggle } from '@livekit/components-react'
|
||||
import { Loader } from '@/primitives/Loader'
|
||||
import { useSyncAfterDelay } from '@/hooks/useSyncAfterDelay'
|
||||
import { RiProhibited2Line } from '@remixicon/react'
|
||||
import { FunnyEffects } from './FunnyEffects'
|
||||
import { useHasFunnyEffectsAccess } from '../../hooks/useHasFunnyEffectsAccess'
|
||||
@@ -50,10 +48,17 @@ export const EffectsConfiguration = ({
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'effects' })
|
||||
const { toggle, enabled } = useTrackToggle({ source: Track.Source.Camera })
|
||||
|
||||
const [processorPending, setProcessorPending] = useState(false)
|
||||
const processorPendingReveal = useSyncAfterDelay(processorPending)
|
||||
|
||||
const hasFunnyEffectsAccess = useHasFunnyEffectsAccess()
|
||||
|
||||
// Note: videoTrack.getProcessor() will return undefined during a transition
|
||||
// but our selectedProcessor state maintains the intended value
|
||||
const [selectedProcessor, setSelectedProcessor] = useState(
|
||||
videoTrack?.getProcessor()
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const videoElement = videoRef.current
|
||||
if (!videoElement) return
|
||||
@@ -69,6 +74,7 @@ export const EffectsConfiguration = ({
|
||||
|
||||
const clearEffect = async () => {
|
||||
await videoTrack.stopProcessor()
|
||||
setSelectedProcessor(undefined)
|
||||
onSubmit?.(undefined)
|
||||
}
|
||||
|
||||
@@ -92,7 +98,6 @@ export const EffectsConfiguration = ({
|
||||
await toggle(true, {
|
||||
processor: newProcessorTmp,
|
||||
})
|
||||
setTimeout(() => setProcessorPending(false))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,9 +105,10 @@ export const EffectsConfiguration = ({
|
||||
await toggle(true)
|
||||
}
|
||||
|
||||
const processor = getProcessor()
|
||||
const processor = videoTrack?.getProcessor() as BackgroundProcessorInterface
|
||||
try {
|
||||
if (isSelected(type, options)) {
|
||||
setSelectedProcessor(undefined)
|
||||
// Stop processor.
|
||||
await clearEffect()
|
||||
} else if (!processor || processor.serialize().type !== type) {
|
||||
@@ -118,28 +124,27 @@ export const EffectsConfiguration = ({
|
||||
if (!BackgroundProcessorFactory.hasModernApiSupport()) {
|
||||
await videoTrack.stopProcessor()
|
||||
}
|
||||
|
||||
await videoTrack.setProcessor(newProcessor)
|
||||
setSelectedProcessor(newProcessor)
|
||||
onSubmit?.(newProcessor)
|
||||
} else {
|
||||
// Update processor.
|
||||
processor?.update(options)
|
||||
// We want to trigger onSubmit when options changes so the parent component is aware of it.
|
||||
onSubmit?.(processor)
|
||||
setSelectedProcessor(processor)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error applying effect:', error)
|
||||
} finally {
|
||||
// Without setTimeout the DOM is not refreshing when updating the options.
|
||||
setTimeout(() => setProcessorPending(false))
|
||||
setProcessorPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
const getProcessor = () => {
|
||||
return videoTrack?.getProcessor() as BackgroundProcessorInterface
|
||||
}
|
||||
|
||||
const isSelected = (type: ProcessorType, options: BackgroundOptions) => {
|
||||
const processor = getProcessor()
|
||||
if (!selectedProcessor) return false
|
||||
const processor = selectedProcessor as BackgroundProcessorInterface
|
||||
const processorSerialized = processor?.serialize()
|
||||
return (
|
||||
!!processor &&
|
||||
@@ -165,7 +170,7 @@ export const EffectsConfiguration = ({
|
||||
display: 'flex',
|
||||
gap: '1.5rem',
|
||||
flexDirection: 'column',
|
||||
md: {
|
||||
lg: {
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
@@ -175,31 +180,65 @@ export const EffectsConfiguration = ({
|
||||
<div
|
||||
className={css({
|
||||
width: '100%',
|
||||
aspectRatio: 16 / 9,
|
||||
position: 'relative',
|
||||
})}
|
||||
>
|
||||
{videoTrack && !videoTrack.isMuted ? (
|
||||
<video
|
||||
ref={videoRef}
|
||||
width="100%"
|
||||
muted
|
||||
className={css(
|
||||
layout === 'vertical'
|
||||
? {
|
||||
height: '175px',
|
||||
width: '100%',
|
||||
}
|
||||
: {
|
||||
minHeight: '175px',
|
||||
maxWidth: '600px',
|
||||
width: '100%',
|
||||
objectFit: 'cover',
|
||||
sm: {
|
||||
aspectRatio: 16 / 9,
|
||||
},
|
||||
lg: {
|
||||
maxWidth: '100%',
|
||||
},
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
transform: 'rotateY(180deg)',
|
||||
[layout === 'vertical' ? 'height' : 'minHeight']: '175px',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
backgroundColor: 'black',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
className={css(
|
||||
layout === 'vertical'
|
||||
? {
|
||||
height: '175px',
|
||||
width: '100%',
|
||||
}
|
||||
: {
|
||||
minHeight: '175px',
|
||||
maxWidth: '600px',
|
||||
width: '100%',
|
||||
objectFit: 'cover',
|
||||
sm: {
|
||||
aspectRatio: 16 / 9,
|
||||
},
|
||||
lg: {
|
||||
maxWidth: '100%',
|
||||
},
|
||||
}
|
||||
)}
|
||||
>
|
||||
<P
|
||||
style={{
|
||||
@@ -211,17 +250,19 @@ export const EffectsConfiguration = ({
|
||||
>
|
||||
{t('activateCamera')}
|
||||
</P>
|
||||
</div>
|
||||
)}
|
||||
{processorPendingReveal && (
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
right: '8px',
|
||||
bottom: '8px',
|
||||
})}
|
||||
>
|
||||
<Loader />
|
||||
<Button
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
onPress={async () => await toggle()}
|
||||
aria-label={t('activateButton')}
|
||||
className={css({
|
||||
width: 'fit-content',
|
||||
marginX: 'auto',
|
||||
marginTop: '1rem',
|
||||
})}
|
||||
>
|
||||
{t('activateButton')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -242,7 +283,7 @@ export const EffectsConfiguration = ({
|
||||
{hasFunnyEffectsAccess && (
|
||||
<FunnyEffects
|
||||
videoTrack={videoTrack}
|
||||
isPending={processorPendingReveal}
|
||||
isPending={processorPending}
|
||||
onPending={setProcessorPending}
|
||||
/>
|
||||
)}
|
||||
@@ -270,8 +311,8 @@ export const EffectsConfiguration = ({
|
||||
onPress={async () => {
|
||||
await clearEffect()
|
||||
}}
|
||||
isSelected={!getProcessor()}
|
||||
isDisabled={processorPendingReveal}
|
||||
isSelected={!selectedProcessor}
|
||||
isDisabled={processorPending || videoTrack?.isMuted}
|
||||
>
|
||||
<RiProhibited2Line />
|
||||
</ToggleButton>
|
||||
@@ -283,7 +324,7 @@ export const EffectsConfiguration = ({
|
||||
tooltip={tooltipLabel(ProcessorType.BLUR, {
|
||||
blurRadius: BlurRadius.LIGHT,
|
||||
})}
|
||||
isDisabled={processorPendingReveal}
|
||||
isDisabled={processorPending || videoTrack?.isMuted}
|
||||
onChange={async () =>
|
||||
await toggleEffect(ProcessorType.BLUR, {
|
||||
blurRadius: BlurRadius.LIGHT,
|
||||
@@ -304,7 +345,7 @@ export const EffectsConfiguration = ({
|
||||
tooltip={tooltipLabel(ProcessorType.BLUR, {
|
||||
blurRadius: BlurRadius.NORMAL,
|
||||
})}
|
||||
isDisabled={processorPendingReveal}
|
||||
isDisabled={processorPending || videoTrack?.isMuted}
|
||||
onChange={async () =>
|
||||
await toggleEffect(ProcessorType.BLUR, {
|
||||
blurRadius: BlurRadius.NORMAL,
|
||||
@@ -352,7 +393,7 @@ export const EffectsConfiguration = ({
|
||||
tooltip={tooltipLabel(ProcessorType.VIRTUAL, {
|
||||
imagePath,
|
||||
})}
|
||||
isDisabled={processorPendingReveal}
|
||||
isDisabled={processorPending || videoTrack?.isMuted}
|
||||
onChange={async () =>
|
||||
await toggleEffect(ProcessorType.VIRTUAL, {
|
||||
imagePath,
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
/**
|
||||
* If value stays truthy for more than waitFor ms, syncValue takes the value of value.
|
||||
* @param value
|
||||
* @param waitFor
|
||||
* @returns
|
||||
*/
|
||||
export function useSyncAfterDelay<T>(value: T, waitFor: number = 300) {
|
||||
const valueRef = useRef(value)
|
||||
const timeoutRef = useRef<NodeJS.Timeout>()
|
||||
const [syncValue, setSyncValue] = useState<T>()
|
||||
|
||||
useEffect(() => {
|
||||
valueRef.current = value
|
||||
if (value) {
|
||||
if (!timeoutRef.current) {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setSyncValue(valueRef.current)
|
||||
timeoutRef.current = undefined
|
||||
}, waitFor)
|
||||
}
|
||||
} else {
|
||||
setSyncValue(value)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [value])
|
||||
|
||||
return syncValue
|
||||
}
|
||||
@@ -154,7 +154,8 @@
|
||||
}
|
||||
},
|
||||
"effects": {
|
||||
"activateCamera": "Ihre Kamera ist deaktiviert. Wählen Sie eine Option, um sie zu aktivieren.",
|
||||
"activateCamera": "Kamera ist aus.",
|
||||
"activateButton": "Kamera einschalten",
|
||||
"notAvailable": "Videoeffekte werden bald in Ihrem Browser verfügbar sein. Wir arbeiten daran! In der Zwischenzeit können Sie Google Chrome für die beste Leistung oder Firefox verwenden :(",
|
||||
"heading": "Unschärfe",
|
||||
"clear": "Effekt deaktivieren",
|
||||
|
||||
@@ -154,7 +154,8 @@
|
||||
}
|
||||
},
|
||||
"effects": {
|
||||
"activateCamera": "Your camera is disabled. Choose an option to enable it.",
|
||||
"activateCamera": "Your camera is disabled.",
|
||||
"activateButton": "Turn on camera",
|
||||
"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",
|
||||
"clear": "Disable effect",
|
||||
|
||||
@@ -154,7 +154,8 @@
|
||||
}
|
||||
},
|
||||
"effects": {
|
||||
"activateCamera": "Votre caméra est désactivée. Choisissez une option pour l'activer.",
|
||||
"activateCamera": "Caméra désactivée.",
|
||||
"activateButton": "Activer la caméra",
|
||||
"notAvailable": "Les effets vidéo seront bientôt disponibles sur votre navigateur. Nous y travaillons ! En attendant, vous pouvez utiliser Google Chrome pour de meilleures performances ou Firefox :(",
|
||||
"heading": "Flou",
|
||||
"clear": "Désactiver l'effect",
|
||||
|
||||
@@ -154,7 +154,8 @@
|
||||
}
|
||||
},
|
||||
"effects": {
|
||||
"activateCamera": "Uw camera is uitgeschakeld. Kies een optie om deze in te schakelen.",
|
||||
"activateCamera": "Camera is uit.",
|
||||
"activateButton": "Camera inschakelen",
|
||||
"notAvailable": "Video-effecten zijn binnenkort beschikbaar in uw browser. We werken eraan! In de tussentijd kunt u Google Chrome gebruiken voor de beste prestaties of Firefox :(",
|
||||
"heading": "Vervaging",
|
||||
"clear": "Effect uitschakelen",
|
||||
|
||||
Reference in New Issue
Block a user