feat: move camera/screen share quality settings to Video tab
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
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
Previously these were only accessible under the "Developer" settings tab, gated behind an explicit "Developer mode" toggle in Preferences - functionally not developer-only at all (just resolution/framerate/ bitrate/codec pickers), just accidentally buried two clicks deep behind a checkbox most users would never find or think to enable. Extracted the MediaQualitySettings component out of DeveloperSettingsTab.tsx into its own file and moved both usages (camera + screen share) into the existing "Video" settings tab, visible to all users without any special mode. DeveloperSettingsTab keeps everything else (audio processing, connection stats, etc.) unchanged. Verified: full project typecheck clean, production embedded build succeeds.
This commit is contained in:
@@ -37,7 +37,6 @@ import { type Room as LivekitRoom } from "livekit-client";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { Config } from "../config/Config";
|
||||
import {
|
||||
type Setting,
|
||||
useSetting,
|
||||
duplicateTiles as duplicateTilesSetting,
|
||||
debugTileLayout as debugTileLayoutSetting,
|
||||
@@ -46,26 +45,13 @@ import {
|
||||
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
||||
matrixRTCMode as matrixRTCModeSetting,
|
||||
customLivekitUrl as customLivekitUrlSetting,
|
||||
advancedScreenShare as advancedScreenShareSetting,
|
||||
screenShareResolution as screenShareResolutionSetting,
|
||||
screenShareFramerate as screenShareFramerateSetting,
|
||||
screenShareBitrate as screenShareBitrateSetting,
|
||||
screenShareCodec as screenShareCodecSetting,
|
||||
advancedCamera as advancedCameraSetting,
|
||||
cameraResolution as cameraResolutionSetting,
|
||||
cameraFramerate as cameraFramerateSetting,
|
||||
cameraBitrate as cameraBitrateSetting,
|
||||
cameraCodec as cameraCodecSetting,
|
||||
echoCancellationSetting,
|
||||
noiseSuppressionSetting,
|
||||
autoGainControlSetting,
|
||||
type VideoCodec,
|
||||
enableExtendedLivekitLogs as enableExtendedLivekitLogsSetting,
|
||||
} from "./settings";
|
||||
import { MatrixRTCMode } from "../config/ConfigOptions";
|
||||
import styles from "./DeveloperSettingsTab.module.css";
|
||||
import settingsStyles from "./SettingsModal.module.css";
|
||||
import { Slider } from "../Slider";
|
||||
import { useUrlParams } from "../UrlParams";
|
||||
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
||||
|
||||
@@ -157,131 +143,6 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
||||
return null;
|
||||
}, [livekitRooms]);
|
||||
|
||||
const MediaQualitySettings: React.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 [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>
|
||||
<select
|
||||
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 AudioProcessingSettings: React.FC = (): ReactNode => {
|
||||
const [echoCancellation, setEchoCancellation] = useSetting(
|
||||
echoCancellationSetting,
|
||||
@@ -606,58 +467,6 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
||||
<p>{t("developer_mode.url_params")}</p>
|
||||
<pre>{JSON.stringify(urlParams, null, 2)}</pre>
|
||||
<Separator />
|
||||
<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 />
|
||||
<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 }}
|
||||
/>
|
||||
<Separator />
|
||||
<AudioProcessingSettings />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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>
|
||||
<select
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -24,12 +24,23 @@ import {
|
||||
soundEffectVolume as soundEffectVolumeSetting,
|
||||
backgroundBlur as backgroundBlurSetting,
|
||||
developerMode,
|
||||
advancedCamera as advancedCameraSetting,
|
||||
cameraResolution as cameraResolutionSetting,
|
||||
cameraFramerate as cameraFramerateSetting,
|
||||
cameraBitrate as cameraBitrateSetting,
|
||||
cameraCodec as cameraCodecSetting,
|
||||
advancedScreenShare as advancedScreenShareSetting,
|
||||
screenShareResolution as screenShareResolutionSetting,
|
||||
screenShareFramerate as screenShareFramerateSetting,
|
||||
screenShareBitrate as screenShareBitrateSetting,
|
||||
screenShareCodec as screenShareCodecSetting,
|
||||
} from "./settings";
|
||||
import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
|
||||
import { Slider } from "../Slider";
|
||||
import { DeviceSelection } from "./DeviceSelection";
|
||||
import { useTrackProcessor } from "../livekit/TrackProcessorContext";
|
||||
import { DeveloperSettingsTab } from "./DeveloperSettingsTab";
|
||||
import { MediaQualitySettings } from "./MediaQualitySettings";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { useSubmitRageshake } from "./submit-rageshake";
|
||||
import { useUrlParams } from "../UrlParams";
|
||||
@@ -189,6 +200,58 @@ export const SettingsModal: FC<Props> = ({
|
||||
</Form>
|
||||
<Separator />
|
||||
<BlurCheckbox />
|
||||
<Separator />
|
||||
<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 />
|
||||
<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 }}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user