♻️(front) refactor Effects
We need to have an agnostic component to apply effects on any video track, enters EffectsConfiguration. It takes in input the video track and outputs the processor applied. This is gonna be used in order to use the same component on the pre join screen and the in room customization side panel.
This commit is contained in:
@@ -8,9 +8,9 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { ParticipantsList } from './controls/Participants/ParticipantsList'
|
import { ParticipantsList } from './controls/Participants/ParticipantsList'
|
||||||
import { useSidePanel } from '../hooks/useSidePanel'
|
import { useSidePanel } from '../hooks/useSidePanel'
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import { Effects } from './Effects'
|
|
||||||
import { Chat } from '../prefabs/Chat'
|
import { Chat } from '../prefabs/Chat'
|
||||||
import { Transcript } from './Transcript'
|
import { Transcript } from './Transcript'
|
||||||
|
import { Effects } from './effects/Effects'
|
||||||
|
|
||||||
type StyledSidePanelProps = {
|
type StyledSidePanelProps = {
|
||||||
title: string
|
title: string
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { useLocalParticipant } from '@livekit/components-react'
|
||||||
|
import { LocalVideoTrack } from 'livekit-client'
|
||||||
|
import { css } from '@/styled-system/css'
|
||||||
|
import { EffectsConfiguration } from './EffectsConfiguration'
|
||||||
|
|
||||||
|
export const Effects = () => {
|
||||||
|
const { cameraTrack } = useLocalParticipant()
|
||||||
|
const localCameraTrack = cameraTrack?.track as LocalVideoTrack
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
padding: '0 1.5rem',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<EffectsConfiguration videoTrack={localCameraTrack} layout="vertical" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,13 +1,21 @@
|
|||||||
|
import { LocalVideoTrack, Track, TrackProcessor } from 'livekit-client'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useLocalParticipant } from '@livekit/components-react'
|
|
||||||
import { LocalVideoTrack } from 'livekit-client'
|
|
||||||
import { Text, P, ToggleButton, Div, H } from '@/primitives'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { HStack, styled, VStack } from '@/styled-system/jsx'
|
|
||||||
import {
|
import {
|
||||||
BackgroundBlurFactory,
|
BackgroundBlurFactory,
|
||||||
BackgroundBlurProcessorInterface,
|
BackgroundBlurProcessorInterface,
|
||||||
} from './blur/index'
|
} from '../blur'
|
||||||
|
import { css } from '@/styled-system/css'
|
||||||
|
import { Text, P, ToggleButton, H } from '@/primitives'
|
||||||
|
import { HStack, styled } from '@/styled-system/jsx'
|
||||||
|
|
||||||
|
enum BlurRadius {
|
||||||
|
NONE = 0,
|
||||||
|
LIGHT = 5,
|
||||||
|
NORMAL = 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSupported = BackgroundBlurFactory.isSupported()
|
||||||
|
|
||||||
const Information = styled('div', {
|
const Information = styled('div', {
|
||||||
base: {
|
base: {
|
||||||
@@ -19,48 +27,45 @@ const Information = styled('div', {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
enum BlurRadius {
|
export const EffectsConfiguration = ({
|
||||||
NONE = 0,
|
videoTrack,
|
||||||
LIGHT = 5,
|
onSubmit,
|
||||||
NORMAL = 10,
|
layout = 'horizontal',
|
||||||
}
|
}: {
|
||||||
|
videoTrack: LocalVideoTrack
|
||||||
const isSupported = BackgroundBlurFactory.isSupported()
|
onSubmit?: (processor?: TrackProcessor<Track.Kind.Video>) => void
|
||||||
|
layout?: 'vertical' | 'horizontal'
|
||||||
export const Effects = () => {
|
}) => {
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'effects' })
|
|
||||||
const { isCameraEnabled, cameraTrack, localParticipant } =
|
|
||||||
useLocalParticipant()
|
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
|
const { t } = useTranslation('rooms', { keyPrefix: 'effects' })
|
||||||
const [processorPending, setProcessorPending] = useState(false)
|
const [processorPending, setProcessorPending] = useState(false)
|
||||||
|
|
||||||
const localCameraTrack = cameraTrack?.track as LocalVideoTrack
|
useEffect(() => {
|
||||||
|
const videoElement = videoRef.current
|
||||||
|
if (!videoElement) return
|
||||||
|
|
||||||
const getProcessor = () => {
|
const attachVideoTrack = async () => videoTrack?.attach(videoElement)
|
||||||
return localCameraTrack?.getProcessor() as BackgroundBlurProcessorInterface
|
attachVideoTrack()
|
||||||
}
|
|
||||||
|
|
||||||
const getBlurRadius = (): BlurRadius => {
|
return () => {
|
||||||
const processor = getProcessor()
|
if (!videoElement) return
|
||||||
return processor?.options.blurRadius || BlurRadius.NONE
|
videoTrack.detach(videoElement)
|
||||||
}
|
}
|
||||||
|
}, [videoTrack, videoTrack?.isMuted])
|
||||||
|
|
||||||
const toggleBlur = async (blurRadius: number) => {
|
const toggleBlur = async (blurRadius: number) => {
|
||||||
if (!isCameraEnabled) await localParticipant.setCameraEnabled(true)
|
if (!videoTrack) return
|
||||||
if (!localCameraTrack) return
|
|
||||||
|
|
||||||
setProcessorPending(true)
|
setProcessorPending(true)
|
||||||
|
|
||||||
const processor = getProcessor()
|
const processor = getProcessor()
|
||||||
const currentBlurRadius = getBlurRadius()
|
const currentBlurRadius = getBlurRadius()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (blurRadius == currentBlurRadius && processor) {
|
if (blurRadius == currentBlurRadius && processor) {
|
||||||
await localCameraTrack.stopProcessor()
|
await videoTrack.stopProcessor()
|
||||||
|
onSubmit?.(undefined)
|
||||||
} else if (!processor) {
|
} else if (!processor) {
|
||||||
await localCameraTrack.setProcessor(
|
const newProcessor = BackgroundBlurFactory.getProcessor({ blurRadius })!
|
||||||
BackgroundBlurFactory.getProcessor({ blurRadius })!
|
await videoTrack.setProcessor(newProcessor)
|
||||||
)
|
onSubmit?.(newProcessor)
|
||||||
} else {
|
} else {
|
||||||
processor?.update({ blurRadius })
|
processor?.update({ blurRadius })
|
||||||
}
|
}
|
||||||
@@ -71,21 +76,17 @@ export const Effects = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
const getProcessor = () => {
|
||||||
const videoElement = videoRef.current
|
return videoTrack?.getProcessor() as BackgroundBlurProcessorInterface
|
||||||
if (!videoElement) return
|
}
|
||||||
|
|
||||||
const attachVideoTrack = async () => localCameraTrack?.attach(videoElement)
|
const getBlurRadius = (): BlurRadius => {
|
||||||
attachVideoTrack()
|
const processor = getProcessor()
|
||||||
|
return processor?.options.blurRadius || BlurRadius.NONE
|
||||||
return () => {
|
}
|
||||||
if (!videoElement) return
|
|
||||||
localCameraTrack.detach(videoElement)
|
|
||||||
}
|
|
||||||
}, [localCameraTrack, isCameraEnabled])
|
|
||||||
|
|
||||||
const isSelected = (blurRadius: BlurRadius) => {
|
const isSelected = (blurRadius: BlurRadius) => {
|
||||||
return isCameraEnabled && getBlurRadius() == blurRadius
|
return getBlurRadius() == blurRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltipLabel = (blurRadius: BlurRadius) => {
|
const tooltipLabel = (blurRadius: BlurRadius) => {
|
||||||
@@ -93,55 +94,82 @@ export const Effects = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack padding="0 1.5rem" overflowY="scroll">
|
<div
|
||||||
{localCameraTrack && isCameraEnabled ? (
|
className={css(
|
||||||
<video
|
layout === 'vertical'
|
||||||
ref={videoRef}
|
? {
|
||||||
width="100%"
|
display: 'flex',
|
||||||
muted
|
flexDirection: 'column',
|
||||||
style={{
|
gap: '0.5rem',
|
||||||
transform: 'rotateY(180deg)',
|
}
|
||||||
minHeight: '175px',
|
: {
|
||||||
borderRadius: '8px',
|
display: 'flex',
|
||||||
}}
|
gap: '1.5rem',
|
||||||
/>
|
flexDirection: 'column',
|
||||||
) : (
|
md: {
|
||||||
<div
|
flexDirection: 'row',
|
||||||
style={{
|
},
|
||||||
width: '100%',
|
}
|
||||||
height: '174px',
|
)}
|
||||||
display: 'flex',
|
>
|
||||||
backgroundColor: 'black',
|
<div
|
||||||
justifyContent: 'center',
|
className={css({
|
||||||
flexDirection: 'column',
|
width: '100%',
|
||||||
}}
|
aspectRatio: 16 / 9,
|
||||||
>
|
})}
|
||||||
<P
|
>
|
||||||
|
{videoTrack && !videoTrack.isMuted ? (
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
width="100%"
|
||||||
|
muted
|
||||||
style={{
|
style={{
|
||||||
color: 'white',
|
transform: 'rotateY(180deg)',
|
||||||
textAlign: 'center',
|
minHeight: '175px',
|
||||||
textWrap: 'balance',
|
borderRadius: '8px',
|
||||||
marginBottom: 0,
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
backgroundColor: 'black',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('activateCamera')}
|
<P
|
||||||
</P>
|
style={{
|
||||||
</div>
|
color: 'white',
|
||||||
)}
|
textAlign: 'center',
|
||||||
<Div
|
textWrap: 'balance',
|
||||||
alignItems={'left'}
|
marginBottom: 0,
|
||||||
width={'100%'}
|
}}
|
||||||
style={{
|
>
|
||||||
border: '1px solid #dadce0',
|
{t('activateCamera')}
|
||||||
borderRadius: '8px',
|
</P>
|
||||||
margin: '0 .625rem',
|
</div>
|
||||||
padding: '0.5rem 1rem',
|
)}
|
||||||
}}
|
</div>
|
||||||
|
<div
|
||||||
|
className={css(
|
||||||
|
layout === 'horizontal'
|
||||||
|
? {
|
||||||
|
md: {
|
||||||
|
borderLeft: '1px solid #dadce0',
|
||||||
|
paddingLeft: '1.5rem',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<H
|
<H
|
||||||
lvl={3}
|
lvl={3}
|
||||||
style={{
|
style={{
|
||||||
marginBottom: '0.4rem',
|
marginBottom: '0.4rem',
|
||||||
|
fontWeight: 'bold',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('heading')}
|
{t('heading')}
|
||||||
@@ -173,16 +201,9 @@ export const Effects = () => {
|
|||||||
<Text variant="sm">{t('notAvailable')}</Text>
|
<Text variant="sm">{t('notAvailable')}</Text>
|
||||||
)}
|
)}
|
||||||
<Information>
|
<Information>
|
||||||
<Text
|
<Text variant="sm">⚠︎ {t('experimental')}</Text>
|
||||||
variant="sm"
|
|
||||||
style={{
|
|
||||||
textWrap: 'balance',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
⚠︎ {t('experimental')}
|
|
||||||
</Text>
|
|
||||||
</Information>
|
</Information>
|
||||||
</Div>
|
</div>
|
||||||
</VStack>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user