feat: configurable media quality via config.json

Add a `media_quality` section to config.json that allows self-hosters
to configure video codec, resolution, bitrate, framerate, and simulcast
layers for both camera and screen sharing.

This addresses the long-standing request in #249 for configurable media
quality settings. The LiveKit SDK already supports all of these options;
this change exposes them through the existing config system.

New config.json fields:
- media_quality.video_codec: preferred codec (vp8/vp9/h264/av1)
- media_quality.video: camera resolution, bitrate, framerate, simulcast layers
- media_quality.screen_share: screen share resolution, bitrate, framerate,
  simulcast layers (enables 3+ layer simulcast for screen sharing)

All fields are optional and fall back to the existing defaults (VP8,
720p camera, 1080p screen share) when not specified.

Signed-off-by: Ryan Emmick <ryanemmick4@gmail.com>
This commit is contained in:
Ryan Emmick
2026-02-11 01:43:56 -06:00
parent 90ba38d2bc
commit 390dc22c96
4 changed files with 212 additions and 36 deletions

View File

@@ -12,6 +12,8 @@ import {
type ScreenShareCaptureOptions,
RoomEvent,
MediaDeviceFailure,
type ScreenSharePreset,
VideoPreset as VideoPresetClass,
} from "livekit-client";
import { observeParticipantEvents } from "@livekit/components-core";
import {
@@ -661,6 +663,7 @@ export const createLocalMembership$ = ({
!getUrlParams().hideScreensharing
) {
toggleScreenSharing = (): void => {
const screenConf = Config.get().media_quality?.screen_share;
const screenshareSettings: ScreenShareCaptureOptions = {
// Screen share audio shouldn't have any filtering.
// "echoCancellation" is purposely excluded, as setting it to
@@ -674,6 +677,13 @@ export const createLocalMembership$ = ({
selfBrowserSurface: "include",
surfaceSwitching: "include",
systemAudio: "include",
...(screenConf?.max_resolution && {
resolution: {
width: Math.round((screenConf.max_resolution * 16) / 9),
height: screenConf.max_resolution,
frameRate: screenConf.max_framerate ?? 30,
},
}),
};
const targetScreenshareState = !sharingScreen$.value;
logger.info(

View File

@@ -27,7 +27,10 @@ import type {
import type { MediaDevices } from "../../MediaDevices.ts";
import type { Behavior } from "../../Behavior.ts";
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
import { defaultLiveKitOptions } from "../../../livekit/options.ts";
import {
defaultLiveKitOptions,
getLiveKitOptions,
} from "../../../livekit/options.ts";
// TODO evaluate if this should be done like the Publisher Factory
export interface ConnectionFactory {
@@ -138,15 +141,16 @@ function generateRoomOption({
echoCancellation: boolean;
noiseSuppression: boolean;
}): RoomOptions {
const liveKitOptions = getLiveKitOptions();
return {
...defaultLiveKitOptions,
...liveKitOptions,
videoCaptureDefaults: {
...defaultLiveKitOptions.videoCaptureDefaults,
...liveKitOptions.videoCaptureDefaults,
deviceId: devices.videoInput.selected$.value?.id,
processor: processorState.processor,
},
audioCaptureDefaults: {
...defaultLiveKitOptions.audioCaptureDefaults,
...liveKitOptions.audioCaptureDefaults,
deviceId: devices.audioInput.selected$.value?.id,
echoCancellation,
noiseSuppression,