Merge pull request #491 from getmaxun/browser-clarity

feat: enhance screencast quality
This commit is contained in:
Karishma Shukla
2025-03-21 01:13:33 +05:30
committed by GitHub
3 changed files with 60 additions and 60 deletions

View File

@@ -35,11 +35,11 @@ const SCREENCAST_CONFIG: {
compressionQuality: number; compressionQuality: number;
maxQueueSize: number; maxQueueSize: number;
} = { } = {
format: 'jpeg', format: 'png',
maxWidth: 1280, maxWidth: 1280,
maxHeight: 720, maxHeight: 720,
targetFPS: 30, targetFPS: 30,
compressionQuality: 0.8, compressionQuality: 0.95,
maxQueueSize: 2 maxQueueSize: 2
}; };
@@ -255,6 +255,8 @@ export class RemoteBrowser {
"--disable-extensions", "--disable-extensions",
"--no-sandbox", "--no-sandbox",
"--disable-dev-shm-usage", "--disable-dev-shm-usage",
"--force-color-profile=srgb",
"--force-device-scale-factor=2",
], ],
})); }));
const proxyConfig = await getDecryptedProxyConfig(userId); const proxyConfig = await getDecryptedProxyConfig(userId);
@@ -282,6 +284,7 @@ export class RemoteBrowser {
isMobile: false, isMobile: false,
hasTouch: false, hasTouch: false,
userAgent: this.getUserAgent(), userAgent: this.getUserAgent(),
deviceScaleFactor: 2,
}; };
if (proxyOptions.server) { if (proxyOptions.server) {
@@ -530,15 +533,18 @@ export class RemoteBrowser {
private async optimizeScreenshot(screenshot: Buffer): Promise<Buffer> { private async optimizeScreenshot(screenshot: Buffer): Promise<Buffer> {
try { try {
return await sharp(screenshot) return await sharp(screenshot)
.jpeg({ .png({
quality: Math.round(SCREENCAST_CONFIG.compressionQuality * 100), quality: Math.round(SCREENCAST_CONFIG.compressionQuality * 100),
progressive: true compressionLevel: 3,
adaptiveFiltering: true,
force: true
}) })
.resize({ .resize({
width: SCREENCAST_CONFIG.maxWidth, width: SCREENCAST_CONFIG.maxWidth,
height: SCREENCAST_CONFIG.maxHeight, height: SCREENCAST_CONFIG.maxHeight,
fit: 'inside', fit: 'inside',
withoutEnlargement: true withoutEnlargement: true,
kernel: sharp.kernel.mitchell
}) })
.toBuffer(); .toBuffer();
} catch (error) { } catch (error) {
@@ -700,6 +706,9 @@ export class RemoteBrowser {
try { try {
await this.client.send('Page.startScreencast', { await this.client.send('Page.startScreencast', {
format: SCREENCAST_CONFIG.format, 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 // Set flag to indicate screencast is active
this.isScreencastActive = true; this.isScreencastActive = true;
@@ -756,20 +765,34 @@ export class RemoteBrowser {
this.isProcessingScreenshot = true; this.isProcessingScreenshot = true;
try { try {
const optimizedScreenshot = await this.optimizeScreenshot(payload); const optimizationPromise = this.optimizeScreenshot(payload);
const base64Data = optimizedScreenshot.toString('base64');
const dataWithMimeType = `data:image/jpeg;base64,${base64Data}`; const timeoutPromise = new Promise<Buffer>((resolve) => {
setTimeout(() => resolve(payload), 100);
});
const optimizedScreenshot = await Promise.race([optimizationPromise, timeoutPromise]);
const base64Data = optimizedScreenshot.toString('base64');
const dataWithMimeType = `data:image/png;base64,${base64Data}`;
// Emit with user context to ensure the frontend can identify which browser's screenshot this is
this.socket.emit('screencast', { this.socket.emit('screencast', {
image: dataWithMimeType, image: dataWithMimeType,
userId: this.userId, userId: this.userId,
viewport: viewportSize || await this.currentPage?.viewportSize() || null viewport: viewportSize || await this.currentPage?.viewportSize() || null
}); });
logger.debug('Screenshot emitted');
} catch (error) { } catch (error) {
logger.error('Screenshot emission failed:', 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 { } finally {
this.isProcessingScreenshot = false; this.isProcessingScreenshot = false;

View File

@@ -42,27 +42,27 @@ export const getResponsiveDimensions = (): AppDimensions => {
const windowWidth = window.innerWidth; const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight; const windowHeight = window.innerHeight;
const browserWidth = windowWidth * 0.7; const browserWidth = windowWidth * 0.735;
const outputPreviewWidth = windowWidth * 0.716; const outputPreviewWidth = windowWidth * 0.743;
const heightBreakpoints = [ const heightBreakpoints = [
{ height: HEIGHT_BREAKPOINTS.xxxxxxxxxxxxxl, fraction: 0.82 }, { height: HEIGHT_BREAKPOINTS.xxxxxxxxxxxxxl, fraction: 0.84 },
{ height: HEIGHT_BREAKPOINTS.xxxxxxxxxxxxl, fraction: 0.81 }, { height: HEIGHT_BREAKPOINTS.xxxxxxxxxxxxl, fraction: 0.83 },
{ height: HEIGHT_BREAKPOINTS.xxxxxxxxxxxl, fraction: 0.80 }, { height: HEIGHT_BREAKPOINTS.xxxxxxxxxxxl, fraction: 0.82 },
{ height: HEIGHT_BREAKPOINTS.xxxxxxxxxxl, fraction: 0.79 }, { height: HEIGHT_BREAKPOINTS.xxxxxxxxxxl, fraction: 0.81 },
{ height: HEIGHT_BREAKPOINTS.xxxxxxxxxl, fraction: 0.78 }, { height: HEIGHT_BREAKPOINTS.xxxxxxxxxl, fraction: 0.80 },
{ height: HEIGHT_BREAKPOINTS.xxxxxxxxl, fraction: 0.77 }, { height: HEIGHT_BREAKPOINTS.xxxxxxxxl, fraction: 0.79 },
{ height: HEIGHT_BREAKPOINTS.xxxxxxxl, fraction: 0.76 }, { height: HEIGHT_BREAKPOINTS.xxxxxxxl, fraction: 0.78 },
{ height: HEIGHT_BREAKPOINTS.xxxxxxl, fraction: 0.75 }, { height: HEIGHT_BREAKPOINTS.xxxxxxl, fraction: 0.77 },
{ height: HEIGHT_BREAKPOINTS.xxxxxl, fraction: 0.74 }, { height: HEIGHT_BREAKPOINTS.xxxxxl, fraction: 0.76 },
{ height: HEIGHT_BREAKPOINTS.xxxxl, fraction: 0.73 }, { height: HEIGHT_BREAKPOINTS.xxxxl, fraction: 0.75 },
{ height: HEIGHT_BREAKPOINTS.xxxl, fraction: 0.72 }, { height: HEIGHT_BREAKPOINTS.xxxl, fraction: 0.741 },
{ height: HEIGHT_BREAKPOINTS.xxl, fraction: 0.71 }, { height: HEIGHT_BREAKPOINTS.xxl, fraction: 0.74 },
{ height: HEIGHT_BREAKPOINTS.xl, fraction: 0.70 }, { height: HEIGHT_BREAKPOINTS.xl, fraction: 0.72 },
{ height: HEIGHT_BREAKPOINTS.lg, fraction: 0.68 }, { height: HEIGHT_BREAKPOINTS.lg, fraction: 0.70 },
{ height: HEIGHT_BREAKPOINTS.md, fraction: 0.66 }, { height: HEIGHT_BREAKPOINTS.md, fraction: 0.68 },
{ height: HEIGHT_BREAKPOINTS.sm, fraction: 0.63 }, { height: HEIGHT_BREAKPOINTS.sm, fraction: 0.67 },
{ height: 0, fraction: 0.62 } { height: 0, fraction: 0.67 }
]; ];
const heightFraction = heightBreakpoints.find(bp => windowHeight >= bp.height)?.fraction ?? 0.62; const heightFraction = heightBreakpoints.find(bp => windowHeight >= bp.height)?.fraction ?? 0.62;

View File

@@ -60,9 +60,9 @@ code {
align-items: center; align-items: center;
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
width: calc(100% - 4rem); width: 100%;
height: calc(100vh - 4rem); height: calc(100vh - 2rem);
margin: 2rem 2rem 2rem 2rem; margin: 1rem;
overflow: hidden; overflow: hidden;
} }
@@ -81,7 +81,6 @@ code {
} }
.right-side-panel { .right-side-panel {
margin-left: 1.5rem;
transform: scale(1); transform: scale(1);
transform-origin: top left; transform-origin: top left;
overflow: hidden; overflow: hidden;
@@ -92,25 +91,3 @@ code {
bottom: 2rem !important; bottom: 2rem !important;
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
/* Consistent layout across all screen sizes */
@media screen and (min-width: 1024px) {
#browser-recorder {
width: calc(100% - 4rem);
height: calc(100vh - 4rem);
margin: 2rem 2rem 2rem 2rem;
}
}
/* Adjust for very small screens */
@media screen and (max-width: 1023px) {
#browser-recorder {
width: calc(100% - 2rem);
height: calc(100vh - 3rem);
margin: 1.5rem 1rem 1.5rem 1rem;
}
.right-side-panel {
margin-left: 1rem;
}
}