Files
threadnet-call/src/settings/MediaQualitySettings.tsx
T
Thore Cimbal ac99cc6537
Build / build_full_element_call (push) Canceled after 0s
Build / build_embedded_element_call (push) Canceled after 0s
Build / build_sdk_element_call (push) Canceled after 0s
Build / Build Storybook (push) Canceled after 0s
Build & publish embedded packages for releases / Versioning (push) Canceled after 0s
Test / Run unit tests (push) Canceled after 0s
Test / Run end-to-end tests (push) Canceled after 0s
Upload translation files to Localazy / upload (push) Canceled after 0s
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Canceled after 0s
Build / deploy_develop (push) Canceled after 0s
Build / docker_for_develop (push) Canceled after 0s
Build & publish embedded packages for releases / build_element_call (push) Canceled after 0s
Build & publish embedded packages for releases / Publish tarball (push) Canceled after 0s
Build & publish embedded packages for releases / Publish NPM (push) Canceled after 0s
Build & publish embedded packages for releases / Publish Android AAR (push) Canceled after 0s
Build & publish embedded packages for releases / Publish SwiftPM Library (push) Canceled after 0s
Build & publish embedded packages for releases / Update release notes (push) Canceled after 0s
Build Element Call / Build Element Call (push) Canceled after 0s
fix: limit codec dropdown to codecs the SFU actually accepts
Live-verified via matrix-rtc-sfu negotiation logs (2026-07-28): a real
VP9 publish attempt got "falling back to alternative video codec" -
enabledPublishCodecs was [VP8, H264, H265], no VP9/AV1. The client's
backupCodec masked this (silent regression to VP8, no visible failure),
but offering VP9/AV1 in the dropdown was misleading - selecting them has
no real effect right now. Trimmed the dropdown to VP8/H264/H265 (added
h265 as a real option - livekit-client's own VideoCodec type already
supports it). Also fixed screenShareCodec's default, which was "vp9"
(meaning every user who enabled the toggle without touching the codec
dropdown was silently requesting an unsupported codec) - now "vp8" to
match camera's default.
2026-07-28 23:08:11 +02:00

146 lines
4.9 KiB
TypeScript

/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { type FC, type ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
import { FieldRow, InputField } from "../input/Input";
import { type Setting, useSetting, type VideoCodec } from "./settings";
import settingsStyles from "./SettingsModal.module.css";
import { Slider } from "../Slider";
export const MediaQualitySettings: FC<{
id: string;
header: string;
toggleLabel: string;
description: string;
toggleSetting: Setting<boolean>;
resolutionSetting: Setting<string>;
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 { t } = useTranslation();
const [advancedEnabled, setAdvancedEnabled] = useSetting(toggleSetting);
const [resolution, setResolution] = useSetting(resolutionSetting);
const [framerate, setFramerate] = useSetting(framerateSetting);
const [framerateRaw, setFramerateRaw] = useState(framerate);
const [bitrate, setBitrate] = useSetting(bitrateSetting);
const [bitrateRaw, setBitrateRaw] = useState(bitrate);
const [codec, setCodec] = useSetting(codecSetting);
return (
<>
<h4>{header}</h4>
<FieldRow>
<InputField
id={`${id}Toggle`}
label={toggleLabel}
description={description}
type="checkbox"
checked={advancedEnabled}
onChange={(e): void => setAdvancedEnabled(e.target.checked)}
/>
</FieldRow>
{advancedEnabled && (
<>
<div className={settingsStyles.volumeSlider}>
<label htmlFor={`${id}Resolution`}>
{t("settings.resolution_label", "Resolution")}
</label>
<select
id={`${id}Resolution`}
value={resolution}
onChange={(e): void => setResolution(e.target.value)}
>
{resolutionOptions.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
<div className={settingsStyles.volumeSlider}>
<label>
{t("settings.framerate_label", "Framerate")}
{": "}
<span className={settingsStyles.settingValue}>
{framerateRaw} fps
</span>
</label>
<Slider
label={t("settings.framerate_label", "Framerate")}
value={framerateRaw}
onValueChange={setFramerateRaw}
onValueCommit={setFramerate}
min={5}
max={60}
step={5}
tooltipFormatter={(v): string => `${v} fps`}
/>
</div>
<div className={settingsStyles.volumeSlider}>
<label>
{t("settings.bitrate_label", "Bitrate")}
{": "}
<span className={settingsStyles.settingValue}>
{(bitrateRaw / 1_000_000).toFixed(1)} Mbps
</span>
</label>
<Slider
label={t("settings.bitrate_label", "Bitrate")}
value={bitrateRaw}
onValueChange={setBitrateRaw}
onValueCommit={setBitrate}
min={bitrateRange.min}
max={bitrateRange.max}
step={bitrateRange.step}
tooltipFormatter={(v): string =>
`${(v / 1_000_000).toFixed(1)} Mbps`
}
/>
</div>
<div className={settingsStyles.volumeSlider}>
<label htmlFor={`${id}Codec`}>
{t("settings.codec_label", "Codec")}
</label>
{/* Only list codecs the SFU actually accepts right now (confirmed
live via matrix-rtc-sfu negotiation logs, 2026-07-28 -
enabledPublishCodecs was [VP8, H264, H265]). VP9/AV1 are not
currently negotiated by the SFU - selecting them silently
falls back to VP8 via the client's backupCodec, so leaving
them out avoids offering a choice that has no real effect. */}
<select
id={`${id}Codec`}
value={codec}
onChange={(e): void => setCodec(e.target.value as VideoCodec)}
>
<option value="vp8">VP8</option>
<option value="h264">H.264</option>
<option value="h265">H.265</option>
</select>
</div>
</>
)}
</>
);
};