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 */}
+
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',
};
}