feat: smart codec defaults + mid-stream codec switching

- Gaming mode defaults to H.264 (NVENC hardware encoding, zero CPU)
- Text mode defaults to VP9 (better compression, CPU is free)
- Changing mode auto-updates codec to smart default
- User can manually override codec via UI pills
- Changing codec mid-stream auto-restarts the stream (~1s interruption)
This commit is contained in:
Jannis Braun
2026-03-23 23:35:33 +01:00
parent 894ce68194
commit 8897a5894c
2 changed files with 26 additions and 4 deletions
+17
View File
@@ -32,6 +32,7 @@ import {
import { getMediaStreamTrack } from '../utils/livekitInternals';
let _activeRoom: Room | null = null;
let _publishedScreenShareCodec: 'vp9' | 'h264' | null = null;
export function getActiveRoom(): Room | null {
return _activeRoom;
@@ -608,8 +609,21 @@ export function useLiveKit() {
const updateActiveTracks = async () => {
if (isScreenSharing) {
const opts = buildScreenShareOptions(screenShareConfig);
// Codec changed mid-stream — must restart (codec is baked into SDP negotiation)
if (_publishedScreenShareCodec && _publishedScreenShareCodec !== opts.publish.videoCodec) {
_publishedScreenShareCodec = null;
await stopScreenShare(room);
setTimeout(() => startScreenShare(room).catch(() => {}), 200);
return;
}
const screenPub = room.localParticipant.getTrackPublications().find(p => p.source === Track.Source.ScreenShare);
if (screenPub?.videoTrack) {
// Track the published codec for mid-stream change detection
if (!_publishedScreenShareCodec) {
_publishedScreenShareCodec = opts.publish.videoCodec;
}
const mediaTrack = getMediaStreamTrack(screenPub.videoTrack);
if (mediaTrack) {
if (opts.capture.width > 0 && opts.capture.height > 0) {
@@ -625,6 +639,9 @@ export function useLiveKit() {
resolveNativeOverdrive(mediaTrack ?? null, screenShareConfig, opts);
await applyOverdrive(room, Track.Source.ScreenShare, opts.overdrive);
}
} else {
// Screen share stopped — clear published codec tracker
_publishedScreenShareCodec = null;
}
if (isCameraOn) { await applyOverdrive(room, Track.Source.Camera, CAMERA_OVERDRIVE); }
};
+9 -4
View File
@@ -146,7 +146,7 @@ export const useVoiceStore = create<VoiceState>()(
inputDeviceId: 'default',
outputDeviceId: 'default',
focusedParticipantId: null,
screenShareConfig: { height: 720, fps: 60, mode: 'gaming', codec: 'vp9', customBitrateKbps: null, shareAudio: !isElectron() },
screenShareConfig: { height: 720, fps: 60, mode: 'gaming', codec: 'h264', customBitrateKbps: null, shareAudio: !isElectron() },
participantVolumes: new Map(),
setParticipantVolume: (userId, volume) => {
set((state) => {
@@ -326,9 +326,14 @@ export const useVoiceStore = create<VoiceState>()(
toggleScreenShare: () => set((state) => ({ isScreenSharing: !state.isScreenSharing })),
setFocusedParticipant: (id) => set({ focusedParticipantId: id }),
setScreenShareConfig: (config) => set((state) => ({
screenShareConfig: { ...state.screenShareConfig, ...config },
})),
setScreenShareConfig: (config) => set((state) => {
const merged = { ...state.screenShareConfig, ...config };
// When mode changes (without an explicit codec override), auto-set the smart default
if (config.mode && !config.codec) {
merged.codec = config.mode === 'gaming' ? 'h264' : 'vp9';
}
return { screenShareConfig: merged };
}),
noiseSuppression: true,
echoCancellation: true,
autoGainControl: true,