From 894ce68194fda0472784ea46154b778b8feb03dd Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Mon, 23 Mar 2026 22:44:31 +0100 Subject: [PATCH] feat: add manual codec selection (VP9/H.264) to stream settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VP9 is default — better quality per bit with H.264 SIMULCAST backup for Safari. H.264 option available for zero-CPU hardware encoding. Codec choice is independent of gaming/text mode, which still controls contentHint and degradationPreference. --- .../voice/ScreenShareSettingsPopover.tsx | 26 +++++++++++++++++++ packages/web/src/stores/voiceStore.ts | 3 ++- packages/web/src/utils/screenShare.ts | 20 +++++++------- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/web/src/components/voice/ScreenShareSettingsPopover.tsx b/packages/web/src/components/voice/ScreenShareSettingsPopover.tsx index 54df4ca1..9b8d7764 100644 --- a/packages/web/src/components/voice/ScreenShareSettingsPopover.tsx +++ b/packages/web/src/components/voice/ScreenShareSettingsPopover.tsx @@ -20,6 +20,11 @@ const MODES: { value: ScreenShareConfig['mode']; label: string }[] = [ { value: 'text', label: 'Text' }, ]; +const CODECS: { value: ScreenShareConfig['codec']; label: string; desc: string }[] = [ + { value: 'vp9', label: 'VP9', desc: 'Less bandwidth, more CPU' }, + { value: 'h264', label: 'H.264', desc: 'Less CPU, more bandwidth' }, +]; + function formatBitrate(bps: number): string { return `${(bps / 1_000_000).toFixed(bps % 1_000_000 === 0 ? 0 : 1)} Mbps`; } @@ -178,6 +183,27 @@ export function ScreenShareSettingsPopover({ open, onClose, anchorRef }: ScreenS + {/* Codec */} +
+
+ Codec +
+
+ {CODECS.map((c) => ( + + ))} +
+
+ {/* Bitrate Override */}
diff --git a/packages/web/src/stores/voiceStore.ts b/packages/web/src/stores/voiceStore.ts index f84ec393..449de192 100644 --- a/packages/web/src/stores/voiceStore.ts +++ b/packages/web/src/stores/voiceStore.ts @@ -10,6 +10,7 @@ export interface ScreenShareConfig { height: number | 'native'; fps: number; mode: 'gaming' | 'text'; + codec: 'vp9' | 'h264'; customBitrateKbps: number | null; shareAudio: boolean; } @@ -145,7 +146,7 @@ export const useVoiceStore = create()( inputDeviceId: 'default', outputDeviceId: 'default', focusedParticipantId: null, - screenShareConfig: { height: 720, fps: 60, mode: 'gaming', customBitrateKbps: null, shareAudio: !isElectron() }, + screenShareConfig: { height: 720, fps: 60, mode: 'gaming', codec: 'vp9', customBitrateKbps: null, shareAudio: !isElectron() }, participantVolumes: new Map(), setParticipantVolume: (userId, volume) => { set((state) => { diff --git a/packages/web/src/utils/screenShare.ts b/packages/web/src/utils/screenShare.ts index 46d6ce75..e75d6f3d 100644 --- a/packages/web/src/utils/screenShare.ts +++ b/packages/web/src/utils/screenShare.ts @@ -131,33 +131,33 @@ export function buildScreenShareOptions(config: ScreenShareConfig): ScreenShareB const bps = clampedKbps * 1000; const minBps = Math.round(bps * 0.25); - // Gaming mode: H.264 primary (hardware NVENC/QSV encoding, zero CPU impact on games) - // Text mode: VP9 primary (better compression for sharp text) with H.264 backup for Safari - const isGaming = mode === 'gaming'; + // VP9: better quality per bit, software-encoded (CPU). Best for text/static content. + // H.264: hardware-encoded via NVENC/QSV/VCE (zero CPU). Universal browser support. + // When VP9 is primary, H.264 SIMULCAST backup handles Safari/incompatible viewers. + // When H.264 is primary, no backup needed — universally supported. + const useVp9 = config.codec !== 'h264'; return { capture: { width: captureWidth, height: captureHeight, frameRate: fps }, publish: { - videoCodec: isGaming ? 'h264' : 'vp9', + videoCodec: useVp9 ? 'vp9' : 'h264', videoEncoding: { maxBitrate: bps, maxFramerate: fps }, simulcast: false, - // H.264 is universally supported — no backup needed for gaming mode. - // VP9 needs H.264 backup for Safari/incompatible viewers. - ...(isGaming ? {} : { + ...(useVp9 ? { backupCodec: { codec: 'h264' as const, encoding: { maxBitrate: bps, maxFramerate: fps }, }, backupCodecPolicy: BackupCodecPolicy.SIMULCAST, - }), + } : {}), }, overdrive: { maxBitrate: bps, maxFramerate: fps, minBitrate: minBps, - degradationPreference: isGaming ? 'balanced' : 'maintain-resolution', + degradationPreference: mode === 'text' ? 'maintain-resolution' : 'balanced', }, - contentHint: isGaming ? 'motion' : 'detail', + contentHint: mode === 'text' ? 'detail' : 'motion', }; }