Clean up: deduplicate settings UI, fix backupCodec, remove dead export

Signed-off-by: Ryan Emmick <ryanemmick4@gmail.com>
This commit is contained in:
Ryan Emmick
2026-02-11 12:26:16 -06:00
parent 297470ac2e
commit 98f63c7172
6 changed files with 113 additions and 162 deletions

View File

@@ -108,7 +108,10 @@ function buildPublishOptions(
stopMicTrackOnMute: false, stopMicTrackOnMute: false,
videoCodec: codec, videoCodec: codec,
videoEncoding, videoEncoding,
backupCodec: { codec: "vp8", encoding: videoEncoding }, backupCodec: {
codec: "vp8",
encoding: VideoPresets.h720.encoding,
},
} as TrackPublishDefaults; } as TrackPublishDefaults;
} }
@@ -156,6 +159,3 @@ export function getLiveKitOptions(): RoomOptions {
return buildLiveKitOptions(); return buildLiveKitOptions();
} }
} }
// Keep backward-compatible export for existing consumers
export const defaultLiveKitOptions: RoomOptions = buildLiveKitOptions();

View File

@@ -20,6 +20,7 @@ import { iosDeviceMenu$ } from "../state/MediaDevices";
import { useMediaDevices } from "../MediaDevicesContext"; import { useMediaDevices } from "../MediaDevicesContext";
import { widget } from "../widget"; import { widget } from "../widget";
import { import {
type Setting,
useSetting, useSetting,
soundEffectVolume as soundEffectVolumeSetting, soundEffectVolume as soundEffectVolumeSetting,
backgroundBlur as backgroundBlurSetting, backgroundBlur as backgroundBlurSetting,
@@ -112,33 +113,47 @@ export const SettingsModal: FC<Props> = ({
); );
}; };
const ScreenShareSettings: React.FC = (): ReactNode => { const MediaQualitySettings: React.FC<{
const [advancedEnabled, setAdvancedEnabled] = useSetting( id: string;
advancedScreenShareSetting, header: string;
); toggleLabel: string;
const [resolution, setResolution] = useSetting( description: string;
screenShareResolutionSetting, toggleSetting: Setting<boolean>;
); resolutionSetting: Setting<string>;
const [framerate, setFramerate] = useSetting(screenShareFramerateSetting); framerateSetting: Setting<number>;
bitrateSetting: Setting<number>;
codecSetting: Setting<VideoCodec>;
resolutionOptions: { value: string; label: string }[];
bitrateRange: { min: number; max: number; step: number };
}> = ({
id,
header,
toggleLabel,
description,
toggleSetting,
resolutionSetting,
framerateSetting,
bitrateSetting,
codecSetting,
resolutionOptions,
bitrateRange,
}): ReactNode => {
const [advancedEnabled, setAdvancedEnabled] = useSetting(toggleSetting);
const [resolution, setResolution] = useSetting(resolutionSetting);
const [framerate, setFramerate] = useSetting(framerateSetting);
const [framerateRaw, setFramerateRaw] = useState(framerate); const [framerateRaw, setFramerateRaw] = useState(framerate);
const [bitrate, setBitrate] = useSetting(screenShareBitrateSetting); const [bitrate, setBitrate] = useSetting(bitrateSetting);
const [bitrateRaw, setBitrateRaw] = useState(bitrate); const [bitrateRaw, setBitrateRaw] = useState(bitrate);
const [codec, setCodec] = useSetting(screenShareCodecSetting); const [codec, setCodec] = useSetting(codecSetting);
return ( return (
<> <>
<h4>{t("settings.screen_share_header", "Screen sharing")}</h4> <h4>{header}</h4>
<FieldRow> <FieldRow>
<InputField <InputField
id="advancedScreenShare" id={`${id}Toggle`}
label={t( label={toggleLabel}
"settings.advanced_screen_share_label", description={description}
"Advanced screen share settings",
)}
description={t(
"settings.advanced_screen_share_description",
"Configure resolution, framerate, bitrate, and codec for screen sharing",
)}
type="checkbox" type="checkbox"
checked={advancedEnabled} checked={advancedEnabled}
onChange={(e): void => setAdvancedEnabled(e.target.checked)} onChange={(e): void => setAdvancedEnabled(e.target.checked)}
@@ -147,29 +162,29 @@ export const SettingsModal: FC<Props> = ({
{advancedEnabled && ( {advancedEnabled && (
<> <>
<div className={styles.volumeSlider}> <div className={styles.volumeSlider}>
<label htmlFor="screenShareResolution"> <label htmlFor={`${id}Resolution`}>
{t("settings.screen_share_resolution_label", "Resolution")} {t("settings.resolution_label", "Resolution")}
</label> </label>
<select <select
id="screenShareResolution" id={`${id}Resolution`}
value={resolution} value={resolution}
onChange={(e): void => setResolution(e.target.value)} onChange={(e): void => setResolution(e.target.value)}
> >
<option value="1024x576">576p</option> {resolutionOptions.map((opt) => (
<option value="1280x720">720p</option> <option key={opt.value} value={opt.value}>
<option value="1920x1080">1080p</option> {opt.label}
<option value="2560x1440">1440p</option> </option>
<option value="3840x2160">4K</option> ))}
</select> </select>
</div> </div>
<div className={styles.volumeSlider}> <div className={styles.volumeSlider}>
<label> <label>
{t("settings.screen_share_framerate_label", "Framerate")} {t("settings.framerate_label", "Framerate")}
{": "} {": "}
<span className={styles.settingValue}>{framerateRaw} fps</span> <span className={styles.settingValue}>{framerateRaw} fps</span>
</label> </label>
<Slider <Slider
label={t("settings.screen_share_framerate_label", "Framerate")} label={t("settings.framerate_label", "Framerate")}
value={framerateRaw} value={framerateRaw}
onValueChange={setFramerateRaw} onValueChange={setFramerateRaw}
onValueCommit={setFramerate} onValueCommit={setFramerate}
@@ -181,142 +196,31 @@ export const SettingsModal: FC<Props> = ({
</div> </div>
<div className={styles.volumeSlider}> <div className={styles.volumeSlider}>
<label> <label>
{t("settings.screen_share_bitrate_label", "Bitrate")} {t("settings.bitrate_label", "Bitrate")}
{": "} {": "}
<span className={styles.settingValue}> <span className={styles.settingValue}>
{(bitrateRaw / 1_000_000).toFixed(1)} Mbps {(bitrateRaw / 1_000_000).toFixed(1)} Mbps
</span> </span>
</label> </label>
<Slider <Slider
label={t("settings.screen_share_bitrate_label", "Bitrate")} label={t("settings.bitrate_label", "Bitrate")}
value={bitrateRaw} value={bitrateRaw}
onValueChange={setBitrateRaw} onValueChange={setBitrateRaw}
onValueCommit={setBitrate} onValueCommit={setBitrate}
min={500_000} min={bitrateRange.min}
max={15_000_000} max={bitrateRange.max}
step={500_000} step={bitrateRange.step}
tooltipFormatter={(v): string => tooltipFormatter={(v): string =>
`${(v / 1_000_000).toFixed(1)} Mbps` `${(v / 1_000_000).toFixed(1)} Mbps`
} }
/> />
</div> </div>
<div className={styles.volumeSlider}> <div className={styles.volumeSlider}>
<label htmlFor="screenShareCodec"> <label htmlFor={`${id}Codec`}>
{t("settings.screen_share_codec_label", "Codec")} {t("settings.codec_label", "Codec")}
</label> </label>
<select <select
id="screenShareCodec" id={`${id}Codec`}
value={codec}
onChange={(e): void =>
setCodec(e.target.value as VideoCodec)
}
>
<option value="vp8">VP8</option>
<option value="vp9">VP9</option>
<option value="h264">H.264</option>
<option value="av1">AV1</option>
</select>
</div>
</>
)}
</>
);
};
const CameraSettings: React.FC = (): ReactNode => {
const [advancedEnabled, setAdvancedEnabled] = useSetting(
advancedCameraSetting,
);
const [resolution, setResolution] = useSetting(cameraResolutionSetting);
const [framerate, setFramerate] = useSetting(cameraFramerateSetting);
const [framerateRaw, setFramerateRaw] = useState(framerate);
const [bitrate, setBitrate] = useSetting(cameraBitrateSetting);
const [bitrateRaw, setBitrateRaw] = useState(bitrate);
const [codec, setCodec] = useSetting(cameraCodecSetting);
return (
<>
<h4>{t("settings.camera_header", "Camera quality")}</h4>
<FieldRow>
<InputField
id="advancedCamera"
label={t(
"settings.advanced_camera_label",
"Advanced camera settings",
)}
description={t(
"settings.advanced_camera_description",
"Configure resolution, framerate, bitrate, and codec for camera video. Changes apply on next call join.",
)}
type="checkbox"
checked={advancedEnabled}
onChange={(e): void => setAdvancedEnabled(e.target.checked)}
/>
</FieldRow>
{advancedEnabled && (
<>
<div className={styles.volumeSlider}>
<label htmlFor="cameraResolution">
{t("settings.camera_resolution_label", "Resolution")}
</label>
<select
id="cameraResolution"
value={resolution}
onChange={(e): void => setResolution(e.target.value)}
>
<option value="640x360">360p</option>
<option value="960x540">540p</option>
<option value="1280x720">720p</option>
<option value="1920x1080">1080p</option>
<option value="2560x1440">1440p</option>
</select>
</div>
<div className={styles.volumeSlider}>
<label>
{t("settings.camera_framerate_label", "Framerate")}
{": "}
<span className={styles.settingValue}>
{framerateRaw} fps
</span>
</label>
<Slider
label={t("settings.camera_framerate_label", "Framerate")}
value={framerateRaw}
onValueChange={setFramerateRaw}
onValueCommit={setFramerate}
min={5}
max={60}
step={5}
tooltipFormatter={(v): string => `${v} fps`}
/>
</div>
<div className={styles.volumeSlider}>
<label>
{t("settings.camera_bitrate_label", "Bitrate")}
{": "}
<span className={styles.settingValue}>
{(bitrateRaw / 1_000_000).toFixed(1)} Mbps
</span>
</label>
<Slider
label={t("settings.camera_bitrate_label", "Bitrate")}
value={bitrateRaw}
onValueChange={setBitrateRaw}
onValueCommit={setBitrate}
min={200_000}
max={8_000_000}
step={100_000}
tooltipFormatter={(v): string =>
`${(v / 1_000_000).toFixed(1)} Mbps`
}
/>
</div>
<div className={styles.volumeSlider}>
<label htmlFor="cameraCodec">
{t("settings.camera_codec_label", "Codec")}
</label>
<select
id="cameraCodec"
value={codec} value={codec}
onChange={(e): void => onChange={(e): void =>
setCodec(e.target.value as VideoCodec) setCodec(e.target.value as VideoCodec)
@@ -488,9 +392,45 @@ export const SettingsModal: FC<Props> = ({
<Separator /> <Separator />
<BlurCheckbox /> <BlurCheckbox />
<Separator /> <Separator />
<CameraSettings /> <MediaQualitySettings
id="camera"
header={t("settings.camera_header", "Camera quality")}
toggleLabel={t("settings.advanced_camera_label", "Advanced camera settings")}
description={t("settings.advanced_camera_description", "Configure resolution, framerate, bitrate, and codec for camera video. Changes apply on next call join.")}
toggleSetting={advancedCameraSetting}
resolutionSetting={cameraResolutionSetting}
framerateSetting={cameraFramerateSetting}
bitrateSetting={cameraBitrateSetting}
codecSetting={cameraCodecSetting}
resolutionOptions={[
{ value: "640x360", label: "360p" },
{ value: "960x540", label: "540p" },
{ value: "1280x720", label: "720p" },
{ value: "1920x1080", label: "1080p" },
{ value: "2560x1440", label: "1440p" },
]}
bitrateRange={{ min: 200_000, max: 8_000_000, step: 100_000 }}
/>
<Separator /> <Separator />
<ScreenShareSettings /> <MediaQualitySettings
id="screenShare"
header={t("settings.screen_share_header", "Screen sharing")}
toggleLabel={t("settings.advanced_screen_share_label", "Advanced screen share settings")}
description={t("settings.advanced_screen_share_description", "Configure resolution, framerate, bitrate, and codec for screen sharing")}
toggleSetting={advancedScreenShareSetting}
resolutionSetting={screenShareResolutionSetting}
framerateSetting={screenShareFramerateSetting}
bitrateSetting={screenShareBitrateSetting}
codecSetting={screenShareCodecSetting}
resolutionOptions={[
{ value: "1024x576", label: "576p" },
{ value: "1280x720", label: "720p" },
{ value: "1920x1080", label: "1080p" },
{ value: "2560x1440", label: "1440p" },
{ value: "3840x2160", label: "4K" },
]}
bitrateRange={{ min: 500_000, max: 15_000_000, step: 500_000 }}
/>
</> </>
), ),
}; };

View File

@@ -166,6 +166,14 @@ export const customLivekitUrl = new Setting<string | null>(
export type VideoCodec = "vp8" | "vp9" | "h264" | "av1"; export type VideoCodec = "vp8" | "vp9" | "h264" | "av1";
/**
* Parse a "WIDTHxHEIGHT" resolution string into numeric width and height.
*/
export function parseResolution(res: string): { width: number; height: number } {
const [w, h] = res.split("x").map(Number);
return { width: w, height: h };
}
export const advancedScreenShare = new Setting<boolean>( export const advancedScreenShare = new Setting<boolean>(
"advanced-screen-share", "advanced-screen-share",
false, false,

View File

@@ -61,6 +61,7 @@ import {
screenShareFramerate, screenShareFramerate,
screenShareBitrate, screenShareBitrate,
screenShareCodec, screenShareCodec,
parseResolution,
} from "../../../settings/settings.ts"; } from "../../../settings/settings.ts";
import { Config } from "../../../config/Config.ts"; import { Config } from "../../../config/Config.ts";
import { import {
@@ -688,9 +689,7 @@ export const createLocalMembership$ = ({
if (advancedScreenShare.getValue()) { if (advancedScreenShare.getValue()) {
// User has advanced screen share settings enabled // User has advanced screen share settings enabled
const resParts = screenShareResolution.getValue().split("x"); const { width, height } = parseResolution(screenShareResolution.getValue());
const width = Number(resParts[0]);
const height = Number(resParts[1]);
const fps = screenShareFramerate.getValue(); const fps = screenShareFramerate.getValue();
const bps = screenShareBitrate.getValue(); const bps = screenShareBitrate.getValue();
const codec = screenShareCodec.getValue(); const codec = screenShareCodec.getValue();

View File

@@ -34,6 +34,7 @@ import {
cameraFramerate, cameraFramerate,
cameraBitrate, cameraBitrate,
cameraCodec, cameraCodec,
parseResolution,
echoCancellationSetting, echoCancellationSetting,
noiseSuppressionSetting, noiseSuppressionSetting,
autoGainControlSetting, autoGainControlSetting,
@@ -150,9 +151,7 @@ function generateRoomOption({
let publishDefaults = liveKitOptions.publishDefaults; let publishDefaults = liveKitOptions.publishDefaults;
if (advancedCamera.getValue()) { if (advancedCamera.getValue()) {
const resParts = cameraResolution.getValue().split("x"); const { width, height } = parseResolution(cameraResolution.getValue());
const width = Number(resParts[0]);
const height = Number(resParts[1]);
const fps = cameraFramerate.getValue(); const fps = cameraFramerate.getValue();
const bps = cameraBitrate.getValue(); const bps = cameraBitrate.getValue();
const codec = cameraCodec.getValue(); const codec = cameraCodec.getValue();

View File

@@ -25,6 +25,7 @@ import { constant } from "../../Behavior";
import { import {
echoCancellationSetting, echoCancellationSetting,
noiseSuppressionSetting, noiseSuppressionSetting,
autoGainControlSetting,
} from "../../../settings/settings.ts"; } from "../../../settings/settings.ts";
// At the top of your test file, after imports // At the top of your test file, after imports
@@ -105,9 +106,13 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => {
test.each([{ controlled: true }, { controlled: false }])( test.each([{ controlled: true }, { controlled: false }])(
"it sets controlledAudioDevice=$controlled then uses deviceId accordingly", "it sets controlledAudioDevice=$controlled then uses deviceId accordingly",
({ controlled }) => { ({ controlled }) => {
// test("it sets echoCancellation and noiseSuppression based on constructor parameters", () => {
const RoomConstructor = vi.mocked(LivekitRoom); const RoomConstructor = vi.mocked(LivekitRoom);
// Explicitly set audio settings so the test doesn't depend on defaults
echoCancellationSetting.setValue(true);
noiseSuppressionSetting.setValue(true);
autoGainControlSetting.setValue(true);
const ecConnectionFactory = new ECConnectionFactory( const ecConnectionFactory = new ECConnectionFactory(
mockClient, mockClient,
"!roomid:example.org", "!roomid:example.org",