diff --git a/src/frontend/src/features/rooms/components/Join.tsx b/src/frontend/src/features/rooms/components/Join.tsx index 0e333d47..674212da 100644 --- a/src/frontend/src/features/rooms/components/Join.tsx +++ b/src/frontend/src/features/rooms/components/Join.tsx @@ -1,25 +1,18 @@ import { useTranslation } from 'react-i18next' import { - ParticipantPlaceholder, usePersistentUserChoices, usePreviewTracks, type LocalUserChoices, } from '@livekit/components-react' import { css } from '@/styled-system/css' -import { log } from '@livekit/components-core' -import { defaultUserChoices } from '@livekit/components-core' import { Screen } from '@/layout/Screen' -import { useUser } from '@/features/auth' -import React from 'react' -import { - facingModeFromLocalTrack, - LocalVideoTrack, - Track, -} from 'livekit-client' +import { useMemo, useEffect, useRef, useState } from 'react' +import { LocalVideoTrack, Track } from 'livekit-client' import { H } from '@/primitives/H' import { SelectToggleDevice } from '../livekit/components/controls/SelectToggleDevice' import { Field } from '@/primitives/Field' -import { Button } from '@/primitives' +import { Form } from '@/primitives' +import { HStack, VStack } from '@/styled-system/jsx' const onError = (e: Error) => console.error('ERROR', e) @@ -28,92 +21,47 @@ export const Join = ({ }: { onSubmit: (choices: LocalUserChoices) => void }) => { - const { t } = useTranslation('rooms') - const { user } = useUser() - const defaults: Partial = { username: user?.full_name } - const persistUserChoices = true - const joinLabel = t('join.joinLabel') - const userLabel = t('join.usernameLabel') - - const [userChoices, setUserChoices] = React.useState(defaultUserChoices) - - // TODO: Remove and pipe `defaults` object directly into `usePersistentUserChoices` once we fully switch from type `LocalUserChoices` to `UserChoices`. - const partialDefaults: Partial = { - ...(defaults.audioDeviceId !== undefined && { - audioDeviceId: defaults.audioDeviceId, - }), - ...(defaults.videoDeviceId !== undefined && { - videoDeviceId: defaults.videoDeviceId, - }), - ...(defaults.audioEnabled !== undefined && { - audioEnabled: defaults.audioEnabled, - }), - ...(defaults.videoEnabled !== undefined && { - videoEnabled: defaults.videoEnabled, - }), - ...(defaults.username !== undefined && { username: defaults.username }), - } + const { t } = useTranslation('rooms', { keyPrefix: 'join' }) const { userChoices: initialUserChoices, saveAudioInputDeviceId, - saveAudioInputEnabled, saveVideoInputDeviceId, - saveVideoInputEnabled, saveUsername, - } = usePersistentUserChoices({ - defaults: partialDefaults, - preventSave: !persistUserChoices, - preventLoad: !persistUserChoices, - }) + } = usePersistentUserChoices({}) - // Initialize device settings - const [audioEnabled, setAudioEnabled] = React.useState( - initialUserChoices.audioEnabled - ) - const [videoEnabled, setVideoEnabled] = React.useState( - initialUserChoices.videoEnabled - ) - const [audioDeviceId, setAudioDeviceId] = React.useState( + const [audioDeviceId, setAudioDeviceId] = useState( initialUserChoices.audioDeviceId ) - const [videoDeviceId, setVideoDeviceId] = React.useState( + const [videoDeviceId, setVideoDeviceId] = useState( initialUserChoices.videoDeviceId ) - const [username, setUsername] = React.useState(initialUserChoices.username) + const [username, setUsername] = useState(initialUserChoices.username) - // Save user choices to persistent storage. - React.useEffect(() => { - saveAudioInputEnabled(audioEnabled) - }, [audioEnabled, saveAudioInputEnabled]) - React.useEffect(() => { - saveVideoInputEnabled(videoEnabled) - }, [videoEnabled, saveVideoInputEnabled]) - React.useEffect(() => { + useEffect(() => { saveAudioInputDeviceId(audioDeviceId) }, [audioDeviceId, saveAudioInputDeviceId]) - React.useEffect(() => { + + useEffect(() => { saveVideoInputDeviceId(videoDeviceId) }, [videoDeviceId, saveVideoInputDeviceId]) - React.useEffect(() => { + + useEffect(() => { saveUsername(username) }, [username, saveUsername]) + const [audioEnabled, setAudioEnabled] = useState(true) + const [videoEnabled, setVideoEnabled] = useState(true) + const tracks = usePreviewTracks( { - audio: audioEnabled - ? { deviceId: initialUserChoices.audioDeviceId } - : false, - video: videoEnabled - ? { deviceId: initialUserChoices.videoDeviceId } - : false, + audio: { deviceId: initialUserChoices.audioDeviceId }, + video: { deviceId: initialUserChoices.videoDeviceId }, }, onError ) - const videoEl = React.useRef(null) - - const videoTrack = React.useMemo( + const videoTrack = useMemo( () => tracks?.filter( (track) => track.kind === Track.Kind.Video @@ -121,7 +69,7 @@ export const Join = ({ [tracks] ) - const audioTrack = React.useMemo( + const audioTrack = useMemo( () => tracks?.filter( (track) => track.kind === Track.Kind.Audio @@ -129,59 +77,37 @@ export const Join = ({ [tracks] ) - const facingMode = React.useMemo(() => { - if (videoTrack) { - const { facingMode } = facingModeFromLocalTrack(videoTrack) - return facingMode - } else { - return 'undefined' - } - }, [videoTrack]) + const videoEl = useRef(null) - React.useEffect(() => { - if (videoEl.current && videoTrack) { + useEffect(() => { + const videoElement = videoEl.current as HTMLVideoElement | null + + const handleVideoLoaded = () => { + if (videoElement) { + videoElement.style.opacity = '1' + } + } + + if (videoElement && videoTrack && videoEnabled) { videoTrack.unmute() - videoTrack.attach(videoEl.current) + videoTrack.attach(videoElement) + videoElement.addEventListener('loadedmetadata', handleVideoLoaded) } return () => { videoTrack?.detach() + videoElement?.removeEventListener('loadedmetadata', handleVideoLoaded) } - }, [videoTrack]) - - const [isValid, setIsValid] = React.useState() - - const handleValidation = React.useCallback((values: LocalUserChoices) => { - return values.username !== '' - }, []) - - React.useEffect(() => { - const newUserChoices = { - username, - videoEnabled, - videoDeviceId, - audioEnabled, - audioDeviceId, - } - setUserChoices(newUserChoices) - setIsValid(handleValidation(newUserChoices)) - }, [ - username, - videoEnabled, - handleValidation, - audioEnabled, - audioDeviceId, - videoDeviceId, - ]) + }, [videoTrack, videoEnabled]) function handleSubmit() { - if (handleValidation(userChoices)) { - if (typeof onSubmit === 'function') { - onSubmit(userChoices) - } - } else { - log.warn('Validation failed with: ', userChoices) - } + onSubmit({ + audioEnabled, + videoEnabled, + audioDeviceId, + videoDeviceId, + username, + }) } return ( @@ -232,67 +158,60 @@ export const Join = ({ height: 'auto', aspectRatio: 16 / 9, '--tw-shadow': - '0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a', + '0 10px 15px -5px #00000010, 0 4px 5px -6px #00000010', '--tw-shadow-colored': - '0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color)', + '0 10px 15px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color)', boxShadow: 'var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)', + backgroundColor: 'black', })} > - {videoTrack && ( + {videoTrack && videoEnabled ? ( // eslint-disable-next-line jsx-a11y/media-has-caption