diff --git a/packages/web/src/components/voice/VideoQualityPopover.js b/packages/web/src/components/voice/VideoQualityPopover.js index 362b299f..dfe713a9 100644 --- a/packages/web/src/components/voice/VideoQualityPopover.js +++ b/packages/web/src/components/voice/VideoQualityPopover.js @@ -3,13 +3,12 @@ import { useRef, useEffect } from 'react'; import { useVoiceStore } from '../../stores/voiceStore'; import { VideoPreset } from 'livekit-client'; const PRESETS = [ - { value: 'auto', label: 'Auto', desc: 'Adjusts to your connection' }, - { value: '1080p60', label: '1080p 60fps', desc: '1920x1080, 15000 kbps' }, - { value: '1080p', label: '1080p 30fps', desc: '1920x1080, 8000 kbps' }, - { value: '720p60', label: '720p 60fps', desc: '1280x720, 8000 kbps' }, - { value: '720p', label: '720p 30fps', desc: '1280x720, 5000 kbps' }, - { value: '540p', label: '540p 30fps', desc: '960x540, 2000 kbps' }, - { value: '360p', label: '360p 30fps', desc: '640x360, 1000 kbps' }, + { value: '1080p60', label: '1080p 60fps', desc: '1920x1080, 10000 kbps' }, + { value: '1080p', label: '1080p 30fps', desc: '1920x1080, 5000 kbps' }, + { value: '720p60', label: '720p 60fps', desc: '1280x720, 5000 kbps' }, + { value: '720p', label: '720p 30fps', desc: '1280x720, 3000 kbps' }, + { value: '540p', label: '540p 30fps', desc: '960x540, 1500 kbps' }, + { value: '360p', label: '360p 30fps', desc: '640x360, 800 kbps' }, ]; const QUALITY_MAP = { '1080p60': new VideoPreset(1920, 1080, 15_000_000, 60), diff --git a/packages/web/src/components/voice/VideoQualityPopover.tsx b/packages/web/src/components/voice/VideoQualityPopover.tsx index a7f73a9f..d774e71a 100644 --- a/packages/web/src/components/voice/VideoQualityPopover.tsx +++ b/packages/web/src/components/voice/VideoQualityPopover.tsx @@ -10,13 +10,12 @@ interface VideoQualityPopoverProps { } const PRESETS = [ - { value: 'auto' as const, label: 'Auto', desc: 'Adjusts to your connection' }, - { value: '1080p60' as const, label: '1080p 60fps', desc: '1920x1080, 15000 kbps' }, - { value: '1080p' as const, label: '1080p 30fps', desc: '1920x1080, 8000 kbps' }, - { value: '720p60' as const, label: '720p 60fps', desc: '1280x720, 8000 kbps' }, - { value: '720p' as const, label: '720p 30fps', desc: '1280x720, 5000 kbps' }, - { value: '540p' as const, label: '540p 30fps', desc: '960x540, 2000 kbps' }, - { value: '360p' as const, label: '360p 30fps', desc: '640x360, 1000 kbps' }, + { value: '1080p60' as const, label: '1080p 60fps', desc: '1920x1080, 10000 kbps' }, + { value: '1080p' as const, label: '1080p 30fps', desc: '1920x1080, 5000 kbps' }, + { value: '720p60' as const, label: '720p 60fps', desc: '1280x720, 5000 kbps' }, + { value: '720p' as const, label: '720p 30fps', desc: '1280x720, 3000 kbps' }, + { value: '540p' as const, label: '540p 30fps', desc: '960x540, 1500 kbps' }, + { value: '360p' as const, label: '360p 30fps', desc: '640x360, 800 kbps' }, ] as const; const QUALITY_MAP: Record = { diff --git a/packages/web/src/hooks/useLiveKit.js b/packages/web/src/hooks/useLiveKit.js index 80f2be5d..e5df8057 100644 --- a/packages/web/src/hooks/useLiveKit.js +++ b/packages/web/src/hooks/useLiveKit.js @@ -3,18 +3,17 @@ import { Room, RoomEvent, Track, ConnectionState, VideoPresets, VideoPreset, } f import { api } from '../api/client'; import { useVoiceStore } from '../stores/voiceStore'; /** - * OPENCORD NATIVE OVERDRIVE PIPELINE v16 (Golden Config) - * Restored exact v4 logic + Auto 720p60 default. + * OPENCORD NATIVE OVERDRIVE PIPELINE v18 (Golden Lock) + * Forces strict 3.5Mbps - 5Mbps window to prevent Chrome bandwidth panic. */ const QUALITY_MAP = { - '1080p60': new VideoPreset(1920, 1080, 10_000_000, 60), + '1080p60': new VideoPreset(1920, 1080, 8_000_000, 60), '1080p': new VideoPreset(1920, 1080, 5_000_000, 30), - '720p60': new VideoPreset(1280, 720, 5_000_000, 60), // v4 Golden Value + '720p60': new VideoPreset(1280, 720, 5_000_000, 60), // Golden Value '720p': new VideoPreset(1280, 720, 3_000_000, 30), '540p': new VideoPreset(960, 540, 1_500_000, 30), '360p': new VideoPreset(640, 360, 800_000, 30), }; -// AUTO defaults to the stable 720p60 preset const AUTO_PRESET = QUALITY_MAP['720p60']; let _activeRoom = null; export function getActiveRoom() { @@ -25,7 +24,6 @@ function parseIdentity(identity) { return { userId: parts[0] ?? identity, username: parts[1] ?? identity }; } let _connectGeneration = 0; -// The v4 "Triple-Kick" Hammer async function applyOverdriveHammer(room, source, preset) { try { const pub = room.localParticipant.getTrackPublications().find(p => p.source === source); @@ -39,9 +37,12 @@ async function applyOverdriveHammer(room, source, preset) { if (sender) { const params = sender.getParameters(); if (params.encodings && params.encodings[0]) { - console.log(`[Overdrive] Kicking ${source} to ${preset.encoding.maxBitrate}bps`); + console.log(`[Overdrive] Locking ${source} to 3.5M - ${preset.encoding.maxBitrate}bps`); params.encodings[0].maxBitrate = preset.encoding.maxBitrate; + // STRICT FLOOR: 3.5 Mbps. Prevents the 0.2 Mbps drop. + params.encodings[0].minBitrate = 3_500_000; params.encodings[0].maxFramerate = preset.encoding.maxFramerate; + params.encodings[0].networkPriority = 'high'; // @ts-ignore params.degradationPreference = 'maintain-framerate'; await sender.setParameters(params); @@ -115,6 +116,9 @@ export function useLiveKit() { roomRef.current = newRoom; const guardedUpdate = () => { if (roomRef.current === newRoom) updateParticipants(); }; + newRoom.on(RoomEvent.ParticipantConnected, guardedUpdate); + newRoom.on(RoomEvent.ParticipantDisconnected, guardedUpdate); + newRoom.on(RoomEvent.TrackSubscribed, guardedUpdate); newRoom.on(RoomEvent.ConnectionStateChanged, (state) => { if (roomRef.current === newRoom) { const connected = state === ConnectionState.Connected; @@ -250,7 +254,7 @@ export function useLiveKit() { if (roomRef.current) { if (!isScreenSharing) { const preset = QUALITY_MAP[videoQuality] || AUTO_PRESET; - console.log('[LiveKit] Starting v4 Golden screen share...'); + console.log('[LiveKit] Starting screen share (Golden Lock)...'); const track = await roomRef.current.localParticipant.setScreenShareEnabled(true, { resolution: preset.resolution, // @ts-ignore @@ -259,12 +263,18 @@ export function useLiveKit() { videoCodec: 'h264', videoEncoding: preset.encoding, simulcast: false, priority: 'very-high' }); if (track) { - const hammer = () => { if (roomRef.current) - applyOverdriveHammer(roomRef.current, Track.Source.ScreenShare, preset); }; - // Restore exact v4 timing: 0.5s, 2s, 5s - setTimeout(hammer, 500); - setTimeout(hammer, 2000); - setTimeout(hammer, 5000); + // Retry hammer to ensure parameters stick + let hits = 0; + const interval = setInterval(async () => { + if (roomRef.current) { + await applyOverdriveHammer(roomRef.current, Track.Source.ScreenShare, preset); + hits++; + if (hits > 10 || !isScreenSharing) + clearInterval(interval); + } + else + clearInterval(interval); + }, 500); } } else { diff --git a/packages/web/src/hooks/useLiveKit.ts b/packages/web/src/hooks/useLiveKit.ts index fbe8a2d7..a8438380 100644 --- a/packages/web/src/hooks/useLiveKit.ts +++ b/packages/web/src/hooks/useLiveKit.ts @@ -17,20 +17,19 @@ import { api } from '../api/client'; import { useVoiceStore } from '../stores/voiceStore'; /** - * OPENCORD NATIVE OVERDRIVE PIPELINE v16 (Golden Config) - * Restored exact v4 logic + Auto 720p60 default. + * OPENCORD NATIVE OVERDRIVE PIPELINE v18 (Golden Lock) + * Forces strict 3.5Mbps - 5Mbps window to prevent Chrome bandwidth panic. */ const QUALITY_MAP: Record = { - '1080p60': new VideoPreset(1920, 1080, 10_000_000, 60), + '1080p60': new VideoPreset(1920, 1080, 8_000_000, 60), '1080p': new VideoPreset(1920, 1080, 5_000_000, 30), - '720p60': new VideoPreset(1280, 720, 5_000_000, 60), // v4 Golden Value + '720p60': new VideoPreset(1280, 720, 5_000_000, 60), // Golden Value '720p': new VideoPreset(1280, 720, 3_000_000, 30), '540p': new VideoPreset(960, 540, 1_500_000, 30), '360p': new VideoPreset(640, 360, 800_000, 30), }; -// AUTO defaults to the stable 720p60 preset const AUTO_PRESET = QUALITY_MAP['720p60']!; let _activeRoom: Room | null = null; @@ -60,7 +59,6 @@ function parseIdentity(identity: string): { userId: string; username: string } { let _connectGeneration = 0; -// The v4 "Triple-Kick" Hammer async function applyOverdriveHammer(room: Room, source: Track.Source, preset: VideoPreset) { try { const pub = room.localParticipant.getTrackPublications().find(p => p.source === source); @@ -74,16 +72,22 @@ async function applyOverdriveHammer(room: Room, source: Track.Source, preset: Vi if (sender) { const params = sender.getParameters(); if (params.encodings && params.encodings[0]) { - console.log(`[Overdrive] Kicking ${source} to ${preset.encoding.maxBitrate}bps`); + console.log(`[Overdrive] Locking ${source} to 3.5M - ${preset.encoding.maxBitrate}bps`); + params.encodings[0].maxBitrate = preset.encoding.maxBitrate; + // STRICT FLOOR: 3.5 Mbps. Prevents the 0.2 Mbps drop. + (params.encodings[0] as any).minBitrate = 3_500_000; + params.encodings[0].maxFramerate = preset.encoding.maxFramerate; + params.encodings[0].networkPriority = 'high'; + // @ts-ignore params.degradationPreference = 'maintain-framerate'; + await sender.setParameters(params); } } } - if ((pub.track as any).mediaStreamTrack) { (pub.track as any).mediaStreamTrack.contentHint = 'motion'; } @@ -143,6 +147,9 @@ export function useLiveKit() { roomRef.current = newRoom; const guardedUpdate = () => { if (roomRef.current === newRoom) updateParticipants(); }; + newRoom.on(RoomEvent.ParticipantConnected, guardedUpdate); + newRoom.on(RoomEvent.ParticipantDisconnected, guardedUpdate); + newRoom.on(RoomEvent.TrackSubscribed, guardedUpdate); newRoom.on(RoomEvent.ConnectionStateChanged, (state) => { if (roomRef.current === newRoom) { const connected = state === ConnectionState.Connected; @@ -220,7 +227,7 @@ export function useLiveKit() { if (roomRef.current) { if (!isScreenSharing) { const preset = QUALITY_MAP[videoQuality] || AUTO_PRESET; - console.log('[LiveKit] Starting v4 Golden screen share...'); + console.log('[LiveKit] Starting screen share (Golden Lock)...'); const track = await roomRef.current.localParticipant.setScreenShareEnabled(true, { resolution: preset.resolution, // @ts-ignore @@ -228,12 +235,17 @@ export function useLiveKit() { }, { videoCodec: 'h264', videoEncoding: preset.encoding, simulcast: false, priority: 'very-high' } as any); + if (track) { - const hammer = () => { if (roomRef.current) applyOverdriveHammer(roomRef.current, Track.Source.ScreenShare, preset); }; - // Restore exact v4 timing: 0.5s, 2s, 5s - setTimeout(hammer, 500); - setTimeout(hammer, 2000); - setTimeout(hammer, 5000); + // Retry hammer to ensure parameters stick + let hits = 0; + const interval = setInterval(async () => { + if (roomRef.current) { + await applyOverdriveHammer(roomRef.current, Track.Source.ScreenShare, preset); + hits++; + if (hits > 10 || !isScreenSharing) clearInterval(interval); + } else clearInterval(interval); + }, 500); } } else { await roomRef.current.localParticipant.setScreenShareEnabled(false); } updateParticipants(); diff --git a/packages/web/src/stores/voiceStore.js b/packages/web/src/stores/voiceStore.js index 3179b314..1b814826 100644 --- a/packages/web/src/stores/voiceStore.js +++ b/packages/web/src/stores/voiceStore.js @@ -12,7 +12,7 @@ export const useVoiceStore = create((set, get) => ({ inputVolume: 100, outputVolume: 100, focusedParticipantId: null, - videoQuality: 'auto', + videoQuality: '720p60', participantVolumes: new Map(), setParticipantVolume: (userId, volume) => { set((state) => { diff --git a/packages/web/src/stores/voiceStore.ts b/packages/web/src/stores/voiceStore.ts index 532b5dad..55636a67 100644 --- a/packages/web/src/stores/voiceStore.ts +++ b/packages/web/src/stores/voiceStore.ts @@ -14,7 +14,7 @@ interface VoiceState { inputVolume: number; // 0-200 (100 = default) outputVolume: number; // 0-200 (100 = default) focusedParticipantId: string | null; - videoQuality: 'auto' | '1080p' | '1080p60' | '720p' | '720p60' | '540p' | '360p'; + videoQuality: '1080p' | '1080p60' | '720p' | '720p60' | '540p' | '360p'; // Per-participant volume (userId → 0-200, 100 = default) participantVolumes: Map; setParticipantVolume: (userId: string, volume: number) => void; @@ -40,7 +40,7 @@ interface VoiceState { toggleScreenShare: () => void; toggleDeafen: () => void; setFocusedParticipant: (id: string | null) => void; - setVideoQuality: (quality: 'auto' | '1080p' | '1080p60' | '720p' | '720p60' | '540p' | '360p') => void; + setVideoQuality: (quality: '1080p' | '1080p60' | '720p' | '720p60' | '540p' | '360p') => void; getVoiceUsers: (channelId: string) => string[]; clearAllVoiceUsers: () => void; leaveVoice: () => void; @@ -60,7 +60,7 @@ export const useVoiceStore = create((set, get) => ({ inputVolume: 100, outputVolume: 100, focusedParticipantId: null, - videoQuality: 'auto', + videoQuality: '720p60', participantVolumes: new Map(), setParticipantVolume: (userId, volume) => { set((state) => {