(frontend) add video resolution selector for publishing control

Introduce select option allowing users to set maximum publishing
resolution that instantly changes video track resolution for other
participants.

Essential for low bandwidth networks and follows common patterns across
major videoconferencing solutions. Users can optimize their video
quality based on network conditions without leaving the call.
This commit is contained in:
lebaudantoine
2025-08-12 19:24:17 +02:00
committed by aleb_the_flash
parent fd90d0b830
commit 803c94a80c
5 changed files with 118 additions and 2 deletions

View File

@@ -7,7 +7,14 @@ import { HStack } from '@/styled-system/jsx'
import { usePersistentUserChoices } from '@/features/rooms/livekit/hooks/usePersistentUserChoices'
import { ReactNode, useCallback, useEffect, useState } from 'react'
import { css } from '@/styled-system/css'
import { createLocalVideoTrack, LocalVideoTrack } from 'livekit-client'
import {
createLocalVideoTrack,
LocalVideoTrack,
Track,
VideoPresets,
} from 'livekit-client'
import { BackgroundProcessorFactory } from '@/features/rooms/livekit/components/blur'
import { VideoResolution } from '@/stores/userChoices'
type RowWrapperProps = {
heading: string
@@ -57,8 +64,9 @@ export const VideoTab = ({ id }: VideoTabProps) => {
const { localParticipant } = useRoomContext()
const {
userChoices: { videoDeviceId },
userChoices: { videoDeviceId, processorSerialized, videoPublishResolution },
saveVideoInputDeviceId,
saveVideoPublishResolution,
} = usePersistentUserChoices()
const [videoElement, setVideoElement] = useState<HTMLVideoElement | null>(
null
@@ -88,6 +96,22 @@ export const VideoTab = ({ id }: VideoTabProps) => {
isDisabled: true,
}
const handleVideoResolutionChange = async (key: 'h720' | 'h360' | 'h180') => {
const videoPublication = localParticipant.getTrackPublication(
Track.Source.Camera
)
const videoTrack = videoPublication?.track
if (videoTrack) {
saveVideoPublishResolution(key)
await videoTrack.restartTrack({
resolution: VideoPresets[key].resolution,
deviceId: { exact: videoDeviceId },
processor:
BackgroundProcessorFactory.deserializeProcessor(processorSerialized),
})
}
}
useEffect(() => {
let videoTrack: LocalVideoTrack | null = null
@@ -165,6 +189,54 @@ export const VideoTab = ({ id }: VideoTabProps) => {
)}
</div>
</RowWrapper>
<H lvl={2}>{t('video.resolution.heading')}</H>
<HStack
gap={0}
style={{
flexWrap: 'wrap',
}}
>
<div
style={{
flex: '1 1 215px',
minWidth: 0,
}}
>
<Field
type="select"
label={t('video.resolution.publish.label')}
items={[
{
value: 'h720',
label: `${t('video.resolution.publish.items.high')} (720p)`,
},
{
value: 'h360',
label: `${t('video.resolution.publish.items.medium')} (360p)`,
},
{
value: 'h180',
label: `${t('video.resolution.publish.items.low')} (180p)`,
},
]}
selectedKey={videoPublishResolution}
onSelectionChange={async (key) => {
await handleVideoResolutionChange(key as VideoResolution)
}}
style={{
width: '100%',
}}
/>
</div>
<div
style={{
width: '10rem',
justifyContent: 'center',
display: 'flex',
paddingLeft: '1.5rem',
}}
/>
</HStack>
</TabPanel>
)
}

View File

@@ -40,6 +40,17 @@
"disabled": "Videovorschau deaktiviert"
}
},
"resolution": {
"heading": "Auflösung",
"publish": {
"label": "Wählen Sie Ihre maximale Sendeauflösung",
"items": {
"high": "Hohe Auflösung",
"medium": "Standardauflösung",
"low": "Niedrige Auflösung"
}
}
},
"permissionsRequired": "Berechtigungen erforderlich"
},
"notifications": {

View File

@@ -40,6 +40,17 @@
"disabled": "Video preview disabled"
}
},
"resolution": {
"heading": "Resolution",
"publish": {
"label": "Select your sending resolution (maximum)",
"items": {
"high": "High definition",
"medium": "Standard definition",
"low": "Low definition"
}
}
},
"permissionsRequired": "Permissions required"
},
"notifications": {

View File

@@ -40,6 +40,17 @@
"disabled": "Aperçu vidéo désactivé"
}
},
"resolution": {
"heading": "Résolution",
"publish": {
"label": "Sélectionner votre résolution d'envoi (maximum)",
"items": {
"high": "Haute définition",
"medium": "Définition standard",
"low": "Basse définition"
}
}
},
"permissionsRequired": "Autorisations nécessaires"
},
"notifications": {

View File

@@ -40,6 +40,17 @@
"disabled": "Videovoorbeeld uitgeschakeld"
}
},
"resolution": {
"heading": "Resolutie",
"publish": {
"label": "Selecteer uw verzendresolutie (maximum)",
"items": {
"high": "Hoge definitie",
"medium": "Standaarddefinitie",
"low": "Lage definitie"
}
}
},
"permissionsRequired": "Machtigingen vereist"
},
"notifications": {