From e2c3b745ca1d3a87edf28e8404ad3b8e58d3c645 Mon Sep 17 00:00:00 2001
From: lebaudantoine
Date: Fri, 1 Aug 2025 17:16:07 +0200
Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9(frontend)=20avoid=20video=20glitch?=
=?UTF-8?q?=20on=20the=20prejoin=20while=20init=20a=20processor?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We've introduced simplifications that improve performance, enhance ux,
and contribute to the overall perception of experience quality.
Previously, our processor handling was overly complex. LiveKit allows us to set
a processor before starting the local video preview track, which eliminates
the black blink glitch that appeared when loading the join component
with a default processor.
This change prevents the unnecessary stopping and restarting
of the local video track.
I'm glad this issue is now resolved.
We also simplified component communication by avoiding props drilling.
Now, we use a single flag to indicate when the user is ready to enter the room.
This significantly reduces the complexity of props passed through components.
---
.../features/rooms/components/Conference.tsx | 11 +++-
.../src/features/rooms/components/Join.tsx | 62 +++++--------------
.../src/features/rooms/routes/Room.tsx | 13 +---
3 files changed, 25 insertions(+), 61 deletions(-)
diff --git a/src/frontend/src/features/rooms/components/Conference.tsx b/src/frontend/src/features/rooms/components/Conference.tsx
index 824f91eb..929e99c6 100644
--- a/src/frontend/src/features/rooms/components/Conference.tsx
+++ b/src/frontend/src/features/rooms/components/Conference.tsx
@@ -1,7 +1,10 @@
import { useEffect, useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
-import { LiveKitRoom } from '@livekit/components-react'
+import {
+ LiveKitRoom,
+ usePersistentUserChoices,
+} from '@livekit/components-react'
import {
DisconnectReason,
MediaDeviceFailure,
@@ -31,18 +34,20 @@ import { isFireFox } from '@/utils/livekit'
export const Conference = ({
roomId,
- userConfig,
initialRoomData,
mode = 'join',
}: {
roomId: string
- userConfig: LocalUserChoices
mode?: 'join' | 'create'
initialRoomData?: ApiRoom
}) => {
const posthog = usePostHog()
const { data: apiConfig } = useConfig()
+ const { userChoices: userConfig } = usePersistentUserChoices() as {
+ userChoices: LocalUserChoices
+ }
+
useEffect(() => {
posthog.capture('visit-room', { slug: roomId })
}, [roomId, posthog])
diff --git a/src/frontend/src/features/rooms/components/Join.tsx b/src/frontend/src/features/rooms/components/Join.tsx
index 81097654..2a7ff4bb 100644
--- a/src/frontend/src/features/rooms/components/Join.tsx
+++ b/src/frontend/src/features/rooms/components/Join.tsx
@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { usePreviewTracks } from '@livekit/components-react'
import { css } from '@/styled-system/css'
import { Screen } from '@/layout/Screen'
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { useEffect, useMemo, useRef, useState } from 'react'
import { LocalVideoTrack, Track } from 'livekit-client'
import { H } from '@/primitives/H'
import { SelectToggleDevice } from '../livekit/components/controls/SelectToggleDevice'
@@ -27,7 +27,6 @@ import { ApiLobbyStatus, ApiRequestEntry } from '../api/requestEntry'
import { Spinner } from '@/primitives/Spinner'
import { ApiAccessLevel } from '../api/ApiRoom'
import { useLoginHint } from '@/hooks/useLoginHint'
-import { LocalUserChoices } from '@/stores/userChoices'
const onError = (e: Error) => console.error('ERROR', e)
@@ -107,10 +106,10 @@ const Effects = ({
}
export const Join = ({
- onSubmit,
+ enterRoom,
roomId,
}: {
- onSubmit: (choices: LocalUserChoices) => void
+ enterRoom: () => void
roomId: string
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
@@ -132,23 +131,14 @@ export const Join = ({
saveProcessorSerialized,
} = usePersistentUserChoices()
- const [processor, setProcessor] = useState(
- BackgroundProcessorFactory.deserializeProcessor(processorSerialized)
- )
-
- useEffect(() => {
- saveProcessorSerialized(processor?.serialize())
- }, [
- processor,
- saveProcessorSerialized,
- // eslint-disable-next-line react-hooks/exhaustive-deps
- JSON.stringify(processor?.serialize()),
- ])
-
const tracks = usePreviewTracks(
{
audio: { deviceId: audioDeviceId },
- video: { deviceId: videoDeviceId },
+ video: {
+ deviceId: videoDeviceId,
+ processor:
+ BackgroundProcessorFactory.deserializeProcessor(processorSerialized),
+ },
},
onError
)
@@ -192,25 +182,6 @@ export const Join = ({
}
}, [videoTrack, videoEnabled])
- const enterRoom = useCallback(() => {
- onSubmit({
- audioEnabled,
- videoEnabled,
- audioDeviceId,
- videoDeviceId,
- username,
- processorSerialized: processor?.serialize(),
- })
- }, [
- onSubmit,
- audioEnabled,
- videoEnabled,
- audioDeviceId,
- videoDeviceId,
- username,
- processor,
- ])
-
// Room data strategy:
// 1. Initial fetch is performed to check access and get LiveKit configuration
// 2. Data remains valid for 6 hours to avoid unnecessary refetches
@@ -269,16 +240,6 @@ export const Join = ({
enterRoom()
}
- // This hook is used to setup the persisted user choice processor on initialization.
- // So it's on purpose that processor is not included in the deps.
- // We just want to wait for the videoTrack to be loaded to apply the default processor.
- useEffect(() => {
- if (processor && videoTrack && !videoTrack.getProcessor()) {
- videoTrack.setProcessor(processor)
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [videoTrack])
-
const renderWaitingState = () => {
switch (status) {
case ApiLobbyStatus.TIMEOUT:
@@ -453,7 +414,12 @@ export const Join = ({
)}
-
+
+ saveProcessorSerialized(processor?.serialize())
+ }
+ />
{
const { isLoggedIn } = useUser()
- const { userChoices: existingUserChoices } = usePersistentUserChoices()
- const [userConfig, setUserConfig] = useState(null)
+ const [hasSubmittedEntry, setHasSubmittedEntry] = useState(false)
const { roomId } = useParams()
const [location, setLocation] = useLocation()
@@ -48,10 +45,10 @@ export const Room = () => {
return
}
- if (!userConfig && !skipJoinScreen) {
+ if (!hasSubmittedEntry && !skipJoinScreen) {
return (
-
+ setHasSubmittedEntry(true)} roomId={roomId} />
)
}
@@ -62,10 +59,6 @@ export const Room = () => {
initialRoomData={initialRoomData}
roomId={roomId}
mode={mode}
- userConfig={{
- ...existingUserChoices,
- ...userConfig,
- }}
/>
)