Implement 1080p 60fps capture and WebSocket backpressure

This commit is contained in:
srtk 2026-02-13 22:29:20 +05:30
parent b3f5902c44
commit 0167aeb7b9
3 changed files with 24 additions and 16 deletions

View file

@ -17,7 +17,6 @@ export enum MediaType {
export class NetworkManager extends EventEmitter { export class NetworkManager extends EventEmitter {
private ws: WebSocket | null = null; private ws: WebSocket | null = null;
private userId: number = 0; private userId: number = 0;
private roomCode: string = '';
private videoSeq: number = 0; private videoSeq: number = 0;
private audioSeq: number = 0; private audioSeq: number = 0;
private screenSeq = 0; private screenSeq = 0;
@ -25,6 +24,7 @@ export class NetworkManager extends EventEmitter {
private pingInterval: NodeJS.Timeout | null = null; private pingInterval: NodeJS.Timeout | null = null;
private lastBinaryLog: number = 0; private lastBinaryLog: number = 0;
private binaryCount: number = 0; private binaryCount: number = 0;
private dropCount: number = 0;
constructor(mainWindow: BrowserWindow) { constructor(mainWindow: BrowserWindow) {
super(); super();
@ -32,7 +32,6 @@ export class NetworkManager extends EventEmitter {
} }
async connect(serverUrl: string, roomCode: string, displayName: string): Promise<any> { async connect(serverUrl: string, roomCode: string, displayName: string): Promise<any> {
this.roomCode = roomCode;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Determine Host and Protocol // Determine Host and Protocol
let host = serverUrl.trim().replace(/^wss?:\/\//, '').replace(/\/$/, ''); let host = serverUrl.trim().replace(/^wss?:\/\//, '').replace(/\/$/, '');
@ -127,11 +126,7 @@ export class NetworkManager extends EventEmitter {
this.safeSend('chat-message', msg.data); this.safeSend('chat-message', msg.data);
break; break;
case 'UpdateStream': 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; break;
case 'Error': case 'Error':
console.error('WS Error Msg:', msg.data); console.error('WS Error Msg:', msg.data);
@ -226,7 +221,19 @@ export class NetworkManager extends EventEmitter {
// --- New Encode Methods --- // --- New Encode Methods ---
sendEncodedVideoChunk(chunk: any, isKeyFrame: boolean, timestamp: number, streamType: 'video' | 'screen' = 'video') { 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 totalSize = chunk.length;
const seq = streamType === 'screen' ? this.screenSeq++ : this.videoSeq++; 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 end = Math.min(start + MAX_PAYLOAD, totalSize);
const slice = chunk.slice(start, end); const slice = chunk.slice(start, end);
// Header (22 bytes) // Header (24 bytes)
const header = Buffer.alloc(HEADER_SIZE); const header = Buffer.alloc(HEADER_SIZE);
header.writeUInt8(1, 0); // Version header.writeUInt8(1, 0); // Version
const mType = streamType === 'screen' ? MediaType.Screen : MediaType.Video; const mType = streamType === 'screen' ? MediaType.Screen : MediaType.Video;

View file

@ -482,7 +482,7 @@ function App() {
video: { video: {
width: 1280, width: 1280,
height: 720, height: 720,
frameRate: 30, frameRate: 60,
deviceId: selectedVideoDevice ? { exact: selectedVideoDevice } : undefined deviceId: selectedVideoDevice ? { exact: selectedVideoDevice } : undefined
} }
}); });
@ -566,7 +566,8 @@ function App() {
chromeMediaSource: 'desktop', chromeMediaSource: 'desktop',
chromeMediaSourceId: sourceId, chromeMediaSourceId: sourceId,
maxWidth: 1920, maxWidth: 1920,
maxHeight: 1080 maxHeight: 1080,
maxFrameRate: 60
} }
} }
} as any); } as any);

View file

@ -51,8 +51,8 @@ export class MediaEngine extends SimpleEventEmitter {
codec: 'avc1.42001f', // H.264 Baseline Profile Level 3.1 (720p safe) codec: 'avc1.42001f', // H.264 Baseline Profile Level 3.1 (720p safe)
width: 1280, width: 1280,
height: 720, height: 720,
bitrate: 2_000_000, bitrate: 4_000_000,
framerate: 30, framerate: 60,
latencyMode: 'realtime', latencyMode: 'realtime',
avc: { format: 'annexb' } avc: { format: 'annexb' }
}; };
@ -62,8 +62,8 @@ export class MediaEngine extends SimpleEventEmitter {
codec: 'avc1.64002a', codec: 'avc1.64002a',
width: 1920, width: 1920,
height: 1080, height: 1080,
bitrate: 2_000_000, // Reduced to 2 Mbps for better stability/FPS bitrate: 8_000_000, // Reduced to 2 Mbps for better stability/FPS
framerate: 30, framerate: 60,
latencyMode: 'realtime', // Changed from 'quality' to 'realtime' for lower latency latencyMode: 'realtime', // Changed from 'quality' to 'realtime' for lower latency
avc: { format: 'annexb' } avc: { format: 'annexb' }
}; };
@ -193,7 +193,7 @@ export class MediaEngine extends SimpleEventEmitter {
// Note: Decoders are usually more flexible, but giving a hint helps. // Note: Decoders are usually more flexible, but giving a hint helps.
// Screen share uses High Profile, Video uses Baseline. // Screen share uses High Profile, Video uses Baseline.
const config: VideoDecoderConfig = streamType === 'screen' const config: VideoDecoderConfig = streamType === 'screen'
? { codec: 'avc1.64002a', optimizeForLatency: false } ? { codec: 'avc1.64002a', optimizeForLatency: true }
: { codec: 'avc1.42001f', optimizeForLatency: true }; : { codec: 'avc1.42001f', optimizeForLatency: true };
decoder.configure(config); decoder.configure(config);