Implement 1080p 60fps capture and WebSocket backpressure
This commit is contained in:
parent
b3f5902c44
commit
0167aeb7b9
3 changed files with 24 additions and 16 deletions
|
|
@ -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<any> {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue