From 2186235d659fd2c7c0f24c0b88d8000d3f50615c Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Thu, 26 Feb 2026 00:28:20 +0100 Subject: [PATCH] feat: manual bitrate override slider for screen share settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add customBitrateKbps to ScreenShareConfig (null = auto matrix lookup) - Slider in Stream Settings popover: 500 kbps–20 Mbps, step 500 kbps - "Reset to Auto" clears override back to preset-derived bitrate - Live updates via existing applyOverdrive() pipeline on active streams - Persist version 5 → 6 with migration --- .../voice/ScreenShareSettingsPopover.tsx | 50 +++++++++++++++++++ packages/web/src/stores/voiceStore.ts | 10 +++- packages/web/src/utils/screenShare.ts | 6 ++- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/web/src/components/voice/ScreenShareSettingsPopover.tsx b/packages/web/src/components/voice/ScreenShareSettingsPopover.tsx index 9267e835..d81683c3 100644 --- a/packages/web/src/components/voice/ScreenShareSettingsPopover.tsx +++ b/packages/web/src/components/voice/ScreenShareSettingsPopover.tsx @@ -3,6 +3,10 @@ import { useVoiceStore } from '../../stores/voiceStore'; import type { ScreenShareConfig } from '../../stores/voiceStore'; import { buildScreenShareOptions } from '../../utils/screenShare'; +const BITRATE_MIN = 500; // kbps +const BITRATE_MAX = 20000; // kbps +const BITRATE_STEP = 500; // kbps + interface ScreenShareSettingsPopoverProps { open: boolean; onClose: () => void; @@ -38,6 +42,12 @@ function formatDegradation(pref: RTCDegradationPreference): string { } } +function formatKbps(kbps: number): string { + return kbps >= 1000 + ? `${(kbps / 1000).toFixed(kbps % 1000 === 0 ? 0 : 1)} Mbps` + : `${kbps} kbps`; +} + export function ScreenShareSettingsPopover({ open, onClose }: ScreenShareSettingsPopoverProps) { const popoverRef = useRef(null); const config = useVoiceStore((s) => s.screenShareConfig); @@ -125,6 +135,46 @@ export function ScreenShareSettingsPopover({ open, onClose }: ScreenShareSetting ))} + + {/* Bitrate Override */} +
+
+
+ Bitrate +
+ {config.customBitrateKbps != null && ( + + )} +
+
+ setConfig({ customBitrateKbps: Number(e.target.value) })} + className="flex-1 h-1.5 accent-discord-blurple cursor-pointer appearance-none bg-[#4e5058] rounded-full + [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3.5 [&::-webkit-slider-thumb]:h-3.5 + [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md + [&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:border-0 + [&::-moz-range-thumb]:w-3.5 [&::-moz-range-thumb]:h-3.5 [&::-moz-range-thumb]:rounded-full + [&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:cursor-pointer" + /> + + {config.customBitrateKbps != null + ? formatKbps(config.customBitrateKbps) + : `Auto`} + +
+
{/* Footer — computed stats */} diff --git a/packages/web/src/stores/voiceStore.ts b/packages/web/src/stores/voiceStore.ts index 969e76f8..88896804 100644 --- a/packages/web/src/stores/voiceStore.ts +++ b/packages/web/src/stores/voiceStore.ts @@ -7,6 +7,7 @@ export interface ScreenShareConfig { height: 1080 | 720 | 540; fps: 60 | 45 | 30; mode: 'gaming' | 'text'; + customBitrateKbps: number | null; } interface VoiceState { @@ -109,7 +110,7 @@ export const useVoiceStore = create()( inputDeviceId: 'default', outputDeviceId: 'default', focusedParticipantId: null, - screenShareConfig: { height: 720, fps: 60, mode: 'gaming' }, + screenShareConfig: { height: 720, fps: 60, mode: 'gaming', customBitrateKbps: null }, participantVolumes: new Map(), setParticipantVolume: (userId, volume) => { set((state) => { @@ -323,7 +324,7 @@ export const useVoiceStore = create()( }), { name: 'opencord-voice-settings', - version: 5, + version: 6, migrate: (persistedState: any, version: number) => { if (version === 0) { persistedState.streamAttenuationEnabled = false; @@ -348,6 +349,11 @@ export const useVoiceStore = create()( persistedState.screenShareConfig = { height, fps, mode: 'gaming' }; delete persistedState.videoQuality; } + if (version < 6) { + if (persistedState.screenShareConfig) { + persistedState.screenShareConfig.customBitrateKbps = null; + } + } return persistedState; }, storage: createJSONStorage(() => localStorage), diff --git a/packages/web/src/utils/screenShare.ts b/packages/web/src/utils/screenShare.ts index 0aac3914..4c7f19a4 100644 --- a/packages/web/src/utils/screenShare.ts +++ b/packages/web/src/utils/screenShare.ts @@ -53,9 +53,11 @@ const BITRATE_MATRIX: Record> = { const WIDTH_MAP: Record = { 1080: 1920, 720: 1280, 540: 960 }; export function buildScreenShareOptions(config: ScreenShareConfig): ScreenShareBuildResult { - const { height, fps, mode } = config; + const { height, fps, mode, customBitrateKbps } = config; const width = WIDTH_MAP[height]!; - const maxBitrate = BITRATE_MATRIX[height]![fps]!; + const maxBitrate = customBitrateKbps != null + ? customBitrateKbps * 1000 + : BITRATE_MATRIX[height]![fps]!; const minBitrate = Math.round(maxBitrate * 0.25); return {