fix: disable camera and remove Y4M fake video to prevent browser crash when audio flows
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
8f0b739308
commit
8487e723d4
1 changed files with 18 additions and 47 deletions
|
|
@ -3,7 +3,6 @@ import { Logger } from 'winston';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import os from 'os';
|
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
import { config } from '../config';
|
import { config } from '../config';
|
||||||
|
|
@ -17,37 +16,8 @@ import { ChatProcedure, ChatMessageEntry } from './chatProcedure';
|
||||||
import { AuthProcedure } from './authProcedure';
|
import { AuthProcedure } from './authProcedure';
|
||||||
import { isValidMeetingUrl } from './meetingUrlParser';
|
import { isValidMeetingUrl } from './meetingUrlParser';
|
||||||
|
|
||||||
/**
|
// Camera / fake video injection is disabled for now to focus on stability.
|
||||||
* Generate a solid-white Y4M video file for use as fake camera input.
|
// The Y4M fake video file was causing browser crashes when audio started flowing.
|
||||||
* Chromium loops this single frame at 30fps, so participants see a static white image.
|
|
||||||
* Later this can be replaced with a custom image (avatar/background).
|
|
||||||
*/
|
|
||||||
function _generateFakeVideoFile(): string {
|
|
||||||
const width = 1280;
|
|
||||||
const height = 720;
|
|
||||||
const filePath = path.join(os.tmpdir(), 'bot-video-white.y4m');
|
|
||||||
|
|
||||||
if (fs.existsSync(filePath)) return filePath;
|
|
||||||
|
|
||||||
const header = `YUV4MPEG2 W${width} H${height} F30:1 Ip A0:0 C420jpeg\n`;
|
|
||||||
const frameHeader = 'FRAME\n';
|
|
||||||
|
|
||||||
// White in YUV: Y=235, U=128, V=128
|
|
||||||
const yPlane = Buffer.alloc(width * height, 235);
|
|
||||||
const uvSize = (width / 2) * (height / 2);
|
|
||||||
const uPlane = Buffer.alloc(uvSize, 128);
|
|
||||||
const vPlane = Buffer.alloc(uvSize, 128);
|
|
||||||
|
|
||||||
const fd = fs.openSync(filePath, 'w');
|
|
||||||
fs.writeSync(fd, header);
|
|
||||||
fs.writeSync(fd, frameHeader);
|
|
||||||
fs.writeSync(fd, yPlane);
|
|
||||||
fs.writeSync(fd, uPlane);
|
|
||||||
fs.writeSync(fd, vPlane);
|
|
||||||
fs.closeSync(fd);
|
|
||||||
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OrchestratorCallbacks {
|
export interface OrchestratorCallbacks {
|
||||||
onStateChange: (state: BotState, message?: string) => void;
|
onStateChange: (state: BotState, message?: string) => void;
|
||||||
|
|
@ -189,9 +159,6 @@ export class BotOrchestrator {
|
||||||
// Dismiss any post-join permission modals (e.g. "Manage windows on all displays")
|
// Dismiss any post-join permission modals (e.g. "Manage windows on all displays")
|
||||||
await this._joinProcedure!.dismissBrowserPermissionModals();
|
await this._joinProcedure!.dismissBrowserPermissionModals();
|
||||||
|
|
||||||
// Verify camera is on in the meeting
|
|
||||||
await this._ensureCameraOnInMeeting();
|
|
||||||
|
|
||||||
// Initialize audio playback
|
// Initialize audio playback
|
||||||
await this._audioProcedure!.initialize();
|
await this._audioProcedure!.initialize();
|
||||||
|
|
||||||
|
|
@ -309,8 +276,8 @@ export class BotOrchestrator {
|
||||||
await this._takeScreenshot('auth-no-join-now');
|
await this._takeScreenshot('auth-no-join-now');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate camera toggle if it's off (so background image is visible)
|
// Camera stays OFF — no video injection, focus on communication stability
|
||||||
await this._ensureCameraOn();
|
this._logger.info('Camera left OFF (video disabled for stability)');
|
||||||
|
|
||||||
await this._page!.waitForTimeout(2000);
|
await this._page!.waitForTimeout(2000);
|
||||||
|
|
||||||
|
|
@ -347,9 +314,6 @@ export class BotOrchestrator {
|
||||||
// Start keepalive to prevent idle disconnect
|
// Start keepalive to prevent idle disconnect
|
||||||
this._startKeepAlive();
|
this._startKeepAlive();
|
||||||
|
|
||||||
// Verify camera is on in the meeting
|
|
||||||
await this._ensureCameraOnInMeeting();
|
|
||||||
|
|
||||||
// Initialize audio playback
|
// Initialize audio playback
|
||||||
await this._audioProcedure!.initialize();
|
await this._audioProcedure!.initialize();
|
||||||
|
|
||||||
|
|
@ -803,22 +767,15 @@ export class BotOrchestrator {
|
||||||
private async _launchBrowser(authMode: boolean = false): Promise<void> {
|
private async _launchBrowser(authMode: boolean = false): Promise<void> {
|
||||||
this._logger.info(`Launching browser (authMode=${authMode})...`);
|
this._logger.info(`Launching browser (authMode=${authMode})...`);
|
||||||
|
|
||||||
// Generate a solid white Y4M video file so participants see a clean image
|
|
||||||
const fakeVideoPath = _generateFakeVideoFile();
|
|
||||||
this._logger.info(`Fake video file: ${fakeVideoPath}`);
|
|
||||||
|
|
||||||
const args = authMode
|
const args = authMode
|
||||||
? [
|
? [
|
||||||
// Chromium Minimal: only --no-sandbox + fake media (proven to work for authenticated join)
|
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
'--use-fake-ui-for-media-stream',
|
'--use-fake-ui-for-media-stream',
|
||||||
'--use-fake-device-for-media-stream',
|
'--use-fake-device-for-media-stream',
|
||||||
`--use-file-for-fake-video-capture=${fakeVideoPath}`,
|
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
'--use-fake-ui-for-media-stream',
|
'--use-fake-ui-for-media-stream',
|
||||||
'--use-fake-device-for-media-stream',
|
'--use-fake-device-for-media-stream',
|
||||||
`--use-file-for-fake-video-capture=${fakeVideoPath}`,
|
|
||||||
'--disable-web-security',
|
'--disable-web-security',
|
||||||
'--disable-features=IsolateOrigins,site-per-process',
|
'--disable-features=IsolateOrigins,site-per-process',
|
||||||
'--autoplay-policy=no-user-gesture-required',
|
'--autoplay-policy=no-user-gesture-required',
|
||||||
|
|
@ -933,6 +890,20 @@ export class BotOrchestrator {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle browser renderer crash (Chromium process segfault)
|
||||||
|
this._page.on('crash', () => {
|
||||||
|
this._logger.error('BROWSER CRASH: Chromium renderer process crashed!');
|
||||||
|
this._setState('error', 'Browser crashed');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle browser disconnection (entire browser process dies)
|
||||||
|
this._browser.on('disconnected', () => {
|
||||||
|
if (!this._isShuttingDown) {
|
||||||
|
this._logger.error('BROWSER DISCONNECTED: Browser process died unexpectedly');
|
||||||
|
this._setState('error', 'Browser process died');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this._logger.info('Browser launched');
|
this._logger.info('Browser launched');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue