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:
@@ -32,6 +32,7 @@ import {
|
|||||||
import { getMediaStreamTrack } from '../utils/livekitInternals';
|
import { getMediaStreamTrack } from '../utils/livekitInternals';
|
||||||
|
|
||||||
let _activeRoom: Room | null = null;
|
let _activeRoom: Room | null = null;
|
||||||
|
let _publishedScreenShareCodec: 'vp9' | 'h264' | null = null;
|
||||||
|
|
||||||
export function getActiveRoom(): Room | null {
|
export function getActiveRoom(): Room | null {
|
||||||
return _activeRoom;
|
return _activeRoom;
|
||||||
@@ -608,8 +609,21 @@ export function useLiveKit() {
|
|||||||
const updateActiveTracks = async () => {
|
const updateActiveTracks = async () => {
|
||||||
if (isScreenSharing) {
|
if (isScreenSharing) {
|
||||||
const opts = buildScreenShareOptions(screenShareConfig);
|
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);
|
const screenPub = room.localParticipant.getTrackPublications().find(p => p.source === Track.Source.ScreenShare);
|
||||||
if (screenPub?.videoTrack) {
|
if (screenPub?.videoTrack) {
|
||||||
|
// Track the published codec for mid-stream change detection
|
||||||
|
if (!_publishedScreenShareCodec) {
|
||||||
|
_publishedScreenShareCodec = opts.publish.videoCodec;
|
||||||
|
}
|
||||||
const mediaTrack = getMediaStreamTrack(screenPub.videoTrack);
|
const mediaTrack = getMediaStreamTrack(screenPub.videoTrack);
|
||||||
if (mediaTrack) {
|
if (mediaTrack) {
|
||||||
if (opts.capture.width > 0 && opts.capture.height > 0) {
|
if (opts.capture.width > 0 && opts.capture.height > 0) {
|
||||||
@@ -625,6 +639,9 @@ export function useLiveKit() {
|
|||||||
resolveNativeOverdrive(mediaTrack ?? null, screenShareConfig, opts);
|
resolveNativeOverdrive(mediaTrack ?? null, screenShareConfig, opts);
|
||||||
await applyOverdrive(room, Track.Source.ScreenShare, opts.overdrive);
|
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); }
|
if (isCameraOn) { await applyOverdrive(room, Track.Source.Camera, CAMERA_OVERDRIVE); }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
inputDeviceId: 'default',
|
inputDeviceId: 'default',
|
||||||
outputDeviceId: 'default',
|
outputDeviceId: 'default',
|
||||||
focusedParticipantId: null,
|
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(),
|
participantVolumes: new Map(),
|
||||||
setParticipantVolume: (userId, volume) => {
|
setParticipantVolume: (userId, volume) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
@@ -326,9 +326,14 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
toggleScreenShare: () => set((state) => ({ isScreenSharing: !state.isScreenSharing })),
|
toggleScreenShare: () => set((state) => ({ isScreenSharing: !state.isScreenSharing })),
|
||||||
|
|
||||||
setFocusedParticipant: (id) => set({ focusedParticipantId: id }),
|
setFocusedParticipant: (id) => set({ focusedParticipantId: id }),
|
||||||
setScreenShareConfig: (config) => set((state) => ({
|
setScreenShareConfig: (config) => set((state) => {
|
||||||
screenShareConfig: { ...state.screenShareConfig, ...config },
|
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,
|
noiseSuppression: true,
|
||||||
echoCancellation: true,
|
echoCancellation: true,
|
||||||
autoGainControl: true,
|
autoGainControl: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user