From 803c94a80c64d3e887bdc7c99658347c4a0778a5 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Tue, 12 Aug 2025 19:24:17 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20video=20resolution?= =?UTF-8?q?=20selector=20for=20publishing=20control?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../settings/components/tabs/VideoTab.tsx | 76 ++++++++++++++++++- src/frontend/src/locales/de/settings.json | 11 +++ src/frontend/src/locales/en/settings.json | 11 +++ src/frontend/src/locales/fr/settings.json | 11 +++ src/frontend/src/locales/nl/settings.json | 11 +++ 5 files changed, 118 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/features/settings/components/tabs/VideoTab.tsx b/src/frontend/src/features/settings/components/tabs/VideoTab.tsx index 7f18b7f0..5895fcec 100644 --- a/src/frontend/src/features/settings/components/tabs/VideoTab.tsx +++ b/src/frontend/src/features/settings/components/tabs/VideoTab.tsx @@ -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( 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) => { )} + {t('video.resolution.heading')} + +
+ { + await handleVideoResolutionChange(key as VideoResolution) + }} + style={{ + width: '100%', + }} + /> +
+
+ ) } diff --git a/src/frontend/src/locales/de/settings.json b/src/frontend/src/locales/de/settings.json index d4a367af..4254bb59 100644 --- a/src/frontend/src/locales/de/settings.json +++ b/src/frontend/src/locales/de/settings.json @@ -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": { diff --git a/src/frontend/src/locales/en/settings.json b/src/frontend/src/locales/en/settings.json index 4283a37b..f06ddd8b 100644 --- a/src/frontend/src/locales/en/settings.json +++ b/src/frontend/src/locales/en/settings.json @@ -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": { diff --git a/src/frontend/src/locales/fr/settings.json b/src/frontend/src/locales/fr/settings.json index b7d66269..c2bcb684 100644 --- a/src/frontend/src/locales/fr/settings.json +++ b/src/frontend/src/locales/fr/settings.json @@ -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": { diff --git a/src/frontend/src/locales/nl/settings.json b/src/frontend/src/locales/nl/settings.json index 32354ab7..bd539b51 100644 --- a/src/frontend/src/locales/nl/settings.json +++ b/src/frontend/src/locales/nl/settings.json @@ -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": {