refactor: move media settings to developer tab, centralize config defaults

This commit is contained in:
Ryan Emmick
2026-04-23 09:49:14 -05:00
parent 8642646fa7
commit dedf4e158e
8 changed files with 332 additions and 289 deletions
+8 -1
View File
@@ -77,7 +77,14 @@ export const Slider: FC<Props> = ({
<Range className={styles.highlight} />
</Track>
{/* Note: This is expected not to be visible on mobile.*/}
<Tooltip placement="top" label={tooltipFormatter ? tooltipFormatter(value) : Math.round(value * 100).toString() + "%"}>
<Tooltip
placement="top"
label={
tooltipFormatter
? tooltipFormatter(value)
: Math.round(value * 100).toString() + "%"
}
>
<Thumb className={styles.handle} aria-label={label} />
</Tooltip>
</Root>
+31
View File
@@ -211,6 +211,24 @@ export interface ResolvedConfigOptions extends ConfigOptions {
};
};
ssla: string;
media_quality: Required<
Pick<NonNullable<ConfigOptions["media_quality"]>, "video_codec">
> & {
video: Required<
Pick<
NonNullable<NonNullable<ConfigOptions["media_quality"]>["video"]>,
"max_resolution" | "max_bitrate" | "max_framerate"
>
>;
screen_share: Required<
Pick<
NonNullable<
NonNullable<ConfigOptions["media_quality"]>["screen_share"]
>,
"max_resolution" | "max_bitrate" | "max_framerate"
>
>;
};
}
export const DEFAULT_CONFIG: ResolvedConfigOptions = {
@@ -224,4 +242,17 @@ export const DEFAULT_CONFIG: ResolvedConfigOptions = {
feature_use_device_session_member_events: true,
},
ssla: "https://static.element.io/legal/element-software-and-services-license-agreement-uk-1.pdf",
media_quality: {
video_codec: "vp8",
video: {
max_resolution: 720,
max_bitrate: 1_700_000,
max_framerate: 30,
},
screen_share: {
max_resolution: 1080,
max_bitrate: 5_000_000,
max_framerate: 30,
},
},
};
+5 -5
View File
@@ -75,14 +75,15 @@ describe("buildLiveKitOptions", () => {
});
});
it("uses 720p screen share preset for low resolution", () => {
it("uses DEFAULT_CONFIG defaults when only resolution is set", () => {
const opts = buildLiveKitOptions({
screen_share: {
max_resolution: 720,
},
});
// Bitrate and framerate fall back to DEFAULT_CONFIG, not the preset
expect(opts.publishDefaults?.screenShareEncoding).toEqual({
maxBitrate: 2_000_000,
maxBitrate: 5_000_000,
maxFramerate: 30,
});
});
@@ -155,11 +156,10 @@ describe("getLiveKitOptions", () => {
expect(opts.publishDefaults?.videoCodec).toBe("h264");
});
it("falls back to defaults when Config throws", () => {
it("throws when Config is not initialized", () => {
vi.mocked(Config.get).mockImplementation(() => {
throw new Error("Config not initialized");
});
const opts = getLiveKitOptions();
expect(opts.publishDefaults?.videoCodec).toBe("vp8");
expect(() => getLiveKitOptions()).toThrow("Config not initialized");
});
});
+13 -22
View File
@@ -9,7 +9,6 @@ import {
AudioPresets,
DefaultReconnectPolicy,
type RoomOptions,
ScreenSharePresets,
type TrackPublishDefaults,
type VideoPreset,
VideoPresets,
@@ -17,7 +16,7 @@ import {
} from "livekit-client";
import { Config } from "../config/Config";
import type { ConfigOptions } from "../config/ConfigOptions";
import { DEFAULT_CONFIG, type ConfigOptions } from "../config/ConfigOptions";
/**
* Find the closest matching VideoPreset for a given height.
@@ -38,16 +37,15 @@ function videoPresetForHeight(height: number): VideoPreset {
function buildPublishOptions(
mediaQuality: ConfigOptions["media_quality"],
): TrackPublishDefaults {
const defaults = DEFAULT_CONFIG.media_quality;
const videoConf = mediaQuality?.video;
const screenConf = mediaQuality?.screen_share;
const codec = mediaQuality?.video_codec ?? "vp8";
const codec = mediaQuality?.video_codec ?? defaults.video_codec;
// Camera video encoding
const videoHeight = videoConf?.max_resolution ?? 720;
const basePreset = videoPresetForHeight(videoHeight);
const videoEncoding = {
maxBitrate: videoConf?.max_bitrate ?? basePreset.encoding.maxBitrate,
maxFramerate: videoConf?.max_framerate ?? basePreset.encoding.maxFramerate,
maxBitrate: videoConf?.max_bitrate ?? defaults.video.max_bitrate,
maxFramerate: videoConf?.max_framerate ?? defaults.video.max_framerate,
};
// Camera simulcast layers
@@ -59,7 +57,7 @@ function buildPublishOptions(
Math.round((layer.height * 16) / 9),
layer.height,
layer.bitrate,
videoConf?.max_framerate ?? 30,
videoConf?.max_framerate ?? defaults.video.max_framerate,
),
);
} else {
@@ -67,15 +65,10 @@ function buildPublishOptions(
}
// Screen share encoding
const screenHeight = screenConf?.max_resolution ?? 1080;
const screenBasePreset =
screenHeight <= 720
? ScreenSharePresets.h720fps30
: ScreenSharePresets.h1080fps30;
const screenShareEncoding = {
maxBitrate: screenConf?.max_bitrate ?? screenBasePreset.encoding.maxBitrate,
maxBitrate: screenConf?.max_bitrate ?? defaults.screen_share.max_bitrate,
maxFramerate:
screenConf?.max_framerate ?? screenBasePreset.encoding.maxFramerate,
screenConf?.max_framerate ?? defaults.screen_share.max_framerate,
};
// Screen share simulcast layers
@@ -122,7 +115,9 @@ function buildPublishOptions(
export function buildLiveKitOptions(
mediaQuality?: ConfigOptions["media_quality"],
): RoomOptions {
const videoHeight = mediaQuality?.video?.max_resolution ?? 720;
const videoHeight =
mediaQuality?.video?.max_resolution ??
DEFAULT_CONFIG.media_quality.video.max_resolution;
const basePreset = videoPresetForHeight(videoHeight);
return {
@@ -150,12 +145,8 @@ export function buildLiveKitOptions(
/**
* Get LiveKit options, reading from the loaded Config singleton.
* Falls back to defaults if Config is not yet initialized.
* Requires Config.init() to have resolved first.
*/
export function getLiveKitOptions(): RoomOptions {
try {
return buildLiveKitOptions(Config.get().media_quality);
} catch {
return buildLiveKitOptions();
}
return buildLiveKitOptions(Config.get().media_quality);
}
+252
View File
@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
import {
type ChangeEvent,
type FC,
type ReactNode,
useCallback,
useEffect,
useMemo,
@@ -29,11 +30,13 @@ import {
InlineField,
Label,
RadioControl,
Separator,
} from "@vector-im/compound-web";
import { type Room as LivekitRoom } from "livekit-client";
import { FieldRow, InputField } from "../input/Input";
import {
type Setting,
useSetting,
duplicateTiles as duplicateTilesSetting,
debugTileLayout as debugTileLayoutSetting,
@@ -43,8 +46,24 @@ import {
matrixRTCMode as matrixRTCModeSetting,
customLivekitUrl as customLivekitUrlSetting,
MatrixRTCMode,
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,
} from "./settings";
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";
@@ -127,6 +146,185 @@ 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,
);
const [noiseSuppression, setNoiseSuppression] = useSetting(
noiseSuppressionSetting,
);
const [autoGainControl, setAutoGainControl] = useSetting(
autoGainControlSetting,
);
return (
<>
<h4>{t("settings.audio_processing_header", "Audio processing")}</h4>
<p>
{t(
"settings.audio_processing_description",
"Changes apply on next call join.",
)}
</p>
<FieldRow>
<InputField
id="echoCancellation"
label={t("settings.echo_cancellation_label", "Echo cancellation")}
type="checkbox"
checked={echoCancellation}
onChange={(e): void => setEchoCancellation(e.target.checked)}
/>
</FieldRow>
<FieldRow>
<InputField
id="noiseSuppression"
label={t("settings.noise_suppression_label", "Noise suppression")}
type="checkbox"
checked={noiseSuppression}
onChange={(e): void => setNoiseSuppression(e.target.checked)}
/>
</FieldRow>
<FieldRow>
<InputField
id="autoGainControl"
label={t(
"settings.auto_gain_control_label",
"Automatic gain control",
)}
type="checkbox"
checked={autoGainControl}
onChange={(e): void => setAutoGainControl(e.target.checked)}
/>
</FieldRow>
</>
);
};
return (
<>
<p>
@@ -379,6 +577,60 @@ export const DeveloperSettingsTab: FC<Props> = ({
<pre>{JSON.stringify(env, null, 2)}</pre>
<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 />
</>
);
};
-242
View File
@@ -20,25 +20,10 @@ import { iosDeviceMenu$ } from "../state/MediaDevices";
import { useMediaDevices } from "../MediaDevicesContext";
import { widget } from "../widget";
import {
type Setting,
useSetting,
soundEffectVolume as soundEffectVolumeSetting,
backgroundBlur as backgroundBlurSetting,
developerMode,
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,
} from "./settings";
import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
import { Slider } from "../Slider";
@@ -113,191 +98,6 @@ export const SettingsModal: FC<Props> = ({
);
};
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={styles.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={styles.volumeSlider}>
<label>
{t("settings.framerate_label", "Framerate")}
{": "}
<span className={styles.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={styles.volumeSlider}>
<label>
{t("settings.bitrate_label", "Bitrate")}
{": "}
<span className={styles.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={styles.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,
);
const [noiseSuppression, setNoiseSuppression] = useSetting(
noiseSuppressionSetting,
);
const [autoGainControl, setAutoGainControl] = useSetting(
autoGainControlSetting,
);
return (
<>
<h4>{t("settings.audio_processing_header", "Audio processing")}</h4>
<p>
{t(
"settings.audio_processing_description",
"Changes apply on next call join.",
)}
</p>
<FieldRow>
<InputField
id="echoCancellation"
label={t(
"settings.echo_cancellation_label",
"Echo cancellation",
)}
type="checkbox"
checked={echoCancellation}
onChange={(e): void => setEchoCancellation(e.target.checked)}
/>
</FieldRow>
<FieldRow>
<InputField
id="noiseSuppression"
label={t(
"settings.noise_suppression_label",
"Noise suppression",
)}
type="checkbox"
checked={noiseSuppression}
onChange={(e): void => setNoiseSuppression(e.target.checked)}
/>
</FieldRow>
<FieldRow>
<InputField
id="autoGainControl"
label={t(
"settings.auto_gain_control_label",
"Automatic gain control",
)}
type="checkbox"
checked={autoGainControl}
onChange={(e): void => setAutoGainControl(e.target.checked)}
/>
</FieldRow>
</>
);
};
const devices = useMediaDevices();
useEffect(() => {
if (open) devices.requestDeviceNames();
@@ -371,8 +171,6 @@ export const SettingsModal: FC<Props> = ({
/>
</div>
</Form>
<Separator />
<AudioProcessingSettings />
</>
),
};
@@ -391,46 +189,6 @@ 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 }}
/>
</>
),
};
+20 -18
View File
@@ -169,7 +169,10 @@ 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 } {
export function parseResolution(res: string): {
width: number;
height: number;
} {
const [w, h] = res.split("x").map(Number);
return { width: w, height: h };
}
@@ -209,10 +212,7 @@ export const cameraResolution = new Setting<string>(
export const cameraFramerate = new Setting<number>("camera-framerate", 30);
export const cameraBitrate = new Setting<number>(
"camera-bitrate",
1_700_000,
);
export const cameraBitrate = new Setting<number>("camera-bitrate", 1_700_000);
export const cameraCodec = new Setting<VideoCodec>("camera-codec", "vp8");
@@ -238,19 +238,21 @@ export const autoGainControlSetting = new Setting<boolean>(
* Only updates settings that the user hasn't explicitly set in localStorage.
*/
export function seedSettingsFromConfig(
mediaQuality: {
video_codec?: VideoCodec;
video?: {
max_resolution?: number;
max_bitrate?: number;
max_framerate?: number;
};
screen_share?: {
max_resolution?: number;
max_bitrate?: number;
max_framerate?: number;
};
} | undefined,
mediaQuality:
| {
video_codec?: VideoCodec;
video?: {
max_resolution?: number;
max_bitrate?: number;
max_framerate?: number;
};
screen_share?: {
max_resolution?: number;
max_bitrate?: number;
max_framerate?: number;
};
}
| undefined,
): void {
if (!mediaQuality) return;
@@ -689,7 +689,9 @@ export const createLocalMembership$ = ({
if (advancedScreenShare.getValue()) {
// User has advanced screen share settings enabled
const { width, height } = parseResolution(screenShareResolution.getValue());
const { width, height } = parseResolution(
screenShareResolution.getValue(),
);
const fps = screenShareFramerate.getValue();
const bps = screenShareBitrate.getValue();
const codec = screenShareCodec.getValue();