From 0167aeb7b9c8694a37681937b24e2f6d0f3c5979 Mon Sep 17 00:00:00 2001 From: srtk Date: Fri, 13 Feb 2026 22:29:20 +0530 Subject: [PATCH] Implement 1080p 60fps capture and WebSocket backpressure --- src/main/network.ts | 25 ++++++++++++++++--------- src/renderer/src/App.tsx | 5 +++-- src/renderer/src/utils/MediaEngine.ts | 10 +++++----- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/main/network.ts b/src/main/network.ts index 9ca19c7..41d170c 100644 --- a/src/main/network.ts +++ b/src/main/network.ts @@ -17,7 +17,6 @@ export enum MediaType { export class NetworkManager extends EventEmitter { private ws: WebSocket | null = null; private userId: number = 0; - private roomCode: string = ''; private videoSeq: number = 0; private audioSeq: number = 0; private screenSeq = 0; @@ -25,6 +24,7 @@ export class NetworkManager extends EventEmitter { private pingInterval: NodeJS.Timeout | null = null; private lastBinaryLog: number = 0; private binaryCount: number = 0; + private dropCount: number = 0; constructor(mainWindow: BrowserWindow) { super(); @@ -32,7 +32,6 @@ export class NetworkManager extends EventEmitter { } async connect(serverUrl: string, roomCode: string, displayName: string): Promise { - this.roomCode = roomCode; return new Promise((resolve, reject) => { // Determine Host and Protocol let host = serverUrl.trim().replace(/^wss?:\/\//, '').replace(/\/$/, ''); @@ -127,11 +126,7 @@ export class NetworkManager extends EventEmitter { this.safeSend('chat-message', msg.data); break; case 'UpdateStream': - // Ignore stream updates for self (we manage local state directly) - if (msg.data.user_id !== this.userId) { - console.log(`[Network] Peer Stream Update: User=${msg.data.user_id} Type=${msg.data.media_type} Active=${msg.data.active}`); - this.safeSend('peer-stream-update', msg.data); - } + this.safeSend('peer-stream-update', msg.data); break; case 'Error': console.error('WS Error Msg:', msg.data); @@ -226,7 +221,19 @@ export class NetworkManager extends EventEmitter { // --- New Encode Methods --- sendEncodedVideoChunk(chunk: any, isKeyFrame: boolean, timestamp: number, streamType: 'video' | 'screen' = 'video') { - const MAX_PAYLOAD = 1400; // WS can handle larger but keeping small for consistency + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; + + // Backpressure check: If we have > 2.5MB buffered, drop this frame. + // For video, it's better to drop than to lag. + if (this.ws.bufferedAmount > 2.5 * 1024 * 1024) { + this.dropCount++; + if (this.dropCount % 60 === 0) { + console.warn(`[Network] Backpressure! Dropped ${this.dropCount} video frames. Buffered: ${this.ws.bufferedAmount} bytes`); + } + return; + } + + const MAX_PAYLOAD = 16384; const totalSize = chunk.length; const seq = streamType === 'screen' ? this.screenSeq++ : this.videoSeq++; @@ -237,7 +244,7 @@ export class NetworkManager extends EventEmitter { const end = Math.min(start + MAX_PAYLOAD, totalSize); const slice = chunk.slice(start, end); - // Header (22 bytes) + // Header (24 bytes) const header = Buffer.alloc(HEADER_SIZE); header.writeUInt8(1, 0); // Version const mType = streamType === 'screen' ? MediaType.Screen : MediaType.Video; diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 83ae0c8..6c6741e 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -482,7 +482,7 @@ function App() { video: { width: 1280, height: 720, - frameRate: 30, + frameRate: 60, deviceId: selectedVideoDevice ? { exact: selectedVideoDevice } : undefined } }); @@ -566,7 +566,8 @@ function App() { chromeMediaSource: 'desktop', chromeMediaSourceId: sourceId, maxWidth: 1920, - maxHeight: 1080 + maxHeight: 1080, + maxFrameRate: 60 } } } as any); diff --git a/src/renderer/src/utils/MediaEngine.ts b/src/renderer/src/utils/MediaEngine.ts index 05bed94..7408bf4 100644 --- a/src/renderer/src/utils/MediaEngine.ts +++ b/src/renderer/src/utils/MediaEngine.ts @@ -51,8 +51,8 @@ export class MediaEngine extends SimpleEventEmitter { codec: 'avc1.42001f', // H.264 Baseline Profile Level 3.1 (720p safe) width: 1280, height: 720, - bitrate: 2_000_000, - framerate: 30, + bitrate: 4_000_000, + framerate: 60, latencyMode: 'realtime', avc: { format: 'annexb' } }; @@ -62,8 +62,8 @@ export class MediaEngine extends SimpleEventEmitter { codec: 'avc1.64002a', width: 1920, height: 1080, - bitrate: 2_000_000, // Reduced to 2 Mbps for better stability/FPS - framerate: 30, + bitrate: 8_000_000, // Reduced to 2 Mbps for better stability/FPS + framerate: 60, latencyMode: 'realtime', // Changed from 'quality' to 'realtime' for lower latency avc: { format: 'annexb' } }; @@ -193,7 +193,7 @@ export class MediaEngine extends SimpleEventEmitter { // Note: Decoders are usually more flexible, but giving a hint helps. // Screen share uses High Profile, Video uses Baseline. const config: VideoDecoderConfig = streamType === 'screen' - ? { codec: 'avc1.64002a', optimizeForLatency: false } + ? { codec: 'avc1.64002a', optimizeForLatency: true } : { codec: 'avc1.42001f', optimizeForLatency: true }; decoder.configure(config);