diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 5d71905e..33fc8228 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -39,7 +39,7 @@ const SCREENCAST_CONFIG: { maxWidth: 1280, maxHeight: 720, targetFPS: 30, - compressionQuality: 0.8, + compressionQuality: 0.95, maxQueueSize: 2 }; @@ -255,6 +255,8 @@ export class RemoteBrowser { "--disable-extensions", "--no-sandbox", "--disable-dev-shm-usage", + "--force-color-profile=srgb", + "--force-device-scale-factor=2", ], })); const proxyConfig = await getDecryptedProxyConfig(userId); @@ -532,14 +534,17 @@ export class RemoteBrowser { try { return await sharp(screenshot) .png({ - quality: Math.round(SCREENCAST_CONFIG.compressionQuality * 100), - progressive: true + quality: Math.round(SCREENCAST_CONFIG.compressionQuality * 100), + compressionLevel: 3, + adaptiveFiltering: true, + force: true }) .resize({ width: SCREENCAST_CONFIG.maxWidth, height: SCREENCAST_CONFIG.maxHeight, fit: 'inside', - withoutEnlargement: true + withoutEnlargement: true, + kernel: sharp.kernel.mitchell }) .toBuffer(); } catch (error) { @@ -701,6 +706,9 @@ export class RemoteBrowser { try { await this.client.send('Page.startScreencast', { format: SCREENCAST_CONFIG.format, + quality: Math.round(SCREENCAST_CONFIG.compressionQuality * 100), + maxWidth: SCREENCAST_CONFIG.maxWidth, + maxHeight: SCREENCAST_CONFIG.maxHeight, }); // Set flag to indicate screencast is active this.isScreencastActive = true; @@ -753,27 +761,41 @@ export class RemoteBrowser { } return; } - + this.isProcessingScreenshot = true; - + try { - const optimizedScreenshot = await this.optimizeScreenshot(payload); + const optimizationPromise = this.optimizeScreenshot(payload); + + const timeoutPromise = new Promise((resolve) => { + setTimeout(() => resolve(payload), 100); + }); + + const optimizedScreenshot = await Promise.race([optimizationPromise, timeoutPromise]); const base64Data = optimizedScreenshot.toString('base64'); - const dataWithMimeType = `data:image/jpeg;base64,${base64Data}`; - - // Emit with user context to ensure the frontend can identify which browser's screenshot this is + const dataWithMimeType = `data:image/png;base64,${base64Data}`; + this.socket.emit('screencast', { image: dataWithMimeType, userId: this.userId, viewport: viewportSize || await this.currentPage?.viewportSize() || null }); - - logger.debug('Screenshot emitted'); } catch (error) { logger.error('Screenshot emission failed:', error); + try { + const base64Data = payload.toString('base64'); + const dataWithMimeType = `data:image/png;base64,${base64Data}`; + this.socket.emit('screencast', { + image: dataWithMimeType, + userId: this.userId, + viewport: viewportSize || await this.currentPage?.viewportSize() || null + }); + } catch (e) { + logger.error('Fallback screenshot emission also failed:', e); + } } finally { this.isProcessingScreenshot = false; - + if (this.screenshotQueue.length > 0) { const nextScreenshot = this.screenshotQueue.shift(); if (nextScreenshot) {