diff --git a/packages/web/src/hooks/useLiveKit.ts b/packages/web/src/hooks/useLiveKit.ts index 1c2c80e7..08a1dbd6 100644 --- a/packages/web/src/hooks/useLiveKit.ts +++ b/packages/web/src/hooks/useLiveKit.ts @@ -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); } }; diff --git a/packages/web/src/stores/voiceStore.ts b/packages/web/src/stores/voiceStore.ts index 449de192..a4471d93 100644 --- a/packages/web/src/stores/voiceStore.ts +++ b/packages/web/src/stores/voiceStore.ts @@ -146,7 +146,7 @@ export const useVoiceStore = create()( 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()( 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,