diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d891bae..eeae531a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to ## [Unreleased] +### Added + +- ✨(backend) make the LiveKit default video codec configurable + ### Changed - 📈(frontend) include LiveKit SIDs in the connection analytics event diff --git a/src/backend/core/api/__init__.py b/src/backend/core/api/__init__.py index 4faa20db..66dda030 100644 --- a/src/backend/core/api/__init__.py +++ b/src/backend/core/api/__init__.py @@ -71,6 +71,7 @@ def get_frontend_configuration(request): "force_wss_protocol": settings.LIVEKIT_FORCE_WSS_PROTOCOL, "enable_firefox_proxy_workaround": settings.LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND, "default_sources": settings.LIVEKIT_DEFAULT_SOURCES, + "default_video_codec": settings.LIVEKIT_DEFAULT_VIDEO_CODEC, }, "authenticated_users_can_edit_display_name": ( settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME diff --git a/src/backend/core/tests/test_settings_video_codec.py b/src/backend/core/tests/test_settings_video_codec.py new file mode 100644 index 00000000..290d5727 --- /dev/null +++ b/src/backend/core/tests/test_settings_video_codec.py @@ -0,0 +1,22 @@ +"""Unit tests for the LIVEKIT_DEFAULT_VIDEO_CODEC setting value.""" + +import pytest + +from meet.settings import VideoCodecValue + + +@pytest.mark.parametrize( + "raw,expected", + [("vp9", "vp9"), ("AV1", "av1"), (" h264 ", "h264")], +) +def test_video_codec_value_normalizes(raw, expected): + """Whitespace is trimmed and the name is lowercased before it is checked.""" + # environ=False keeps __new__ from resolving the value, so the instance survives. + assert VideoCodecValue(environ=False).to_python(raw) == expected + + +@pytest.mark.parametrize("raw", ["vp10", "", "h.264"]) +def test_video_codec_value_rejects_unsupported(raw): + """A name outside the accepted list stops the settings module loading.""" + with pytest.raises(ValueError, match="Unsupported video codec"): + VideoCodecValue(environ=False).to_python(raw) diff --git a/src/backend/meet/settings.py b/src/backend/meet/settings.py index 44e353ef..35137d7c 100755 --- a/src/backend/meet/settings.py +++ b/src/backend/meet/settings.py @@ -53,6 +53,26 @@ def get_release(): return "NA" # Default: not available +class VideoCodecValue(values.Value): + """ + A video codec name, normalized to lowercase and validated against the codecs + supported by the LiveKit client, so that a typo fails at startup instead of + silently downgrading every publisher to another codec. + """ + + codecs = frozenset(("vp8", "h264", "vp9", "av1")) + + def to_python(self, value): + """Normalize the codec name and ensure it is a supported one.""" + codec = super().to_python(value).strip().lower() + if codec not in self.codecs: + raise ValueError( + f"Unsupported video codec {value!r}, " + f"expected one of: {', '.join(sorted(self.codecs))}." + ) + return codec + + class Base(Configuration): """ This is the base configuration every configuration (aka environment) should inherit from. It @@ -678,6 +698,9 @@ class Base(Configuration): environ_prefix=None, default=False, ) + LIVEKIT_DEFAULT_VIDEO_CODEC = VideoCodecValue( + "vp9", environ_name="LIVEKIT_DEFAULT_VIDEO_CODEC", environ_prefix=None + ) CONNECTION_TEST_ENABLED = values.BooleanValue( environ_name="CONNECTION_TEST_ENABLED", environ_prefix=None, diff --git a/src/frontend/src/api/useConfig.ts b/src/frontend/src/api/useConfig.ts index 02293549..6bc0a812 100644 --- a/src/frontend/src/api/useConfig.ts +++ b/src/frontend/src/api/useConfig.ts @@ -3,7 +3,7 @@ import { keys } from './queryKeys' import { useQuery } from '@tanstack/react-query' import { RecordingMode } from '@/features/recording' import type { ApiAccessLevel } from '@/features/rooms/api/ApiRoom' -import type { Track } from 'livekit-client' +import type { Track, VideoCodec } from 'livekit-client' type Source = Track.Source export interface ApiConfig { @@ -62,6 +62,7 @@ export interface ApiConfig { force_wss_protocol: boolean enable_firefox_proxy_workaround: boolean default_sources: Source[] + default_video_codec: VideoCodec } transcription_destination?: string max_participants_for_sound: number diff --git a/src/frontend/src/features/rooms/components/Conference.tsx b/src/frontend/src/features/rooms/components/Conference.tsx index f471833c..ab33eca3 100644 --- a/src/frontend/src/features/rooms/components/Conference.tsx +++ b/src/frontend/src/features/rooms/components/Conference.tsx @@ -107,7 +107,7 @@ export const Conference = ({ adaptiveStream: true, dynacast: true, publishDefaults: { - videoCodec: 'vp9', + videoCodec: apiConfig?.livekit.default_video_codec ?? 'vp9', }, videoCaptureDefaults: { deviceId: userConfig.videoDeviceId ?? undefined, @@ -129,6 +129,7 @@ export const Conference = ({ userConfig.videoPublishResolution, userConfig.audioDeviceId, userConfig.audioOutputDeviceId, + apiConfig?.livekit.default_video_codec, ]) const room = useMemo(() => new Room(roomOptions), [roomOptions]) diff --git a/src/helm/env.d/common.yaml.gotmpl b/src/helm/env.d/common.yaml.gotmpl index 27342910..d73bd96d 100644 --- a/src/helm/env.d/common.yaml.gotmpl +++ b/src/helm/env.d/common.yaml.gotmpl @@ -150,6 +150,7 @@ backend: # Frontend LIVEKIT_FORCE_WSS_PROTOCOL: True LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND: True + LIVEKIT_DEFAULT_VIDEO_CODEC: vp9 FRONTEND_IDLE_DISCONNECT_WARNING_DELAY: 9000 FRONTEND_SILENCE_LIVEKIT_DEBUG: False FRONTEND_SUPPORT: "{'id': '58ea6697-8eba-4492-bc59-ad6562585041', 'help_article_transcript': 'https://lasuite.crisp.help/fr/article/visio-transcript-1sjq43x', 'help_article_recording': 'https://lasuite.crisp.help/fr/article/visio-enregistrement-wgc8o0', 'help_article_more_tools': 'https://lasuite.crisp.help/fr/article/visio-tools-bvxj23'}"