fix screen recording: resolve relative paths, buffer chunks, wait for encoder
This commit is contained in:
@@ -27,6 +27,44 @@ let tabGroupQueue: Promise<void> = Promise.resolve()
|
||||
// This ensures Playwright can build the iframe frame tree when connecting over CDP.
|
||||
let autoAttachParams: Protocol.Target.SetAutoAttachRequest | null = null
|
||||
|
||||
// Buffer for recording chunks when WebSocket isn't ready.
|
||||
// Chunks are keyed by tabId and flushed when WebSocket opens.
|
||||
interface BufferedChunk {
|
||||
tabId: number
|
||||
data?: number[]
|
||||
final?: boolean
|
||||
}
|
||||
const recordingChunkBuffer: BufferedChunk[] = []
|
||||
|
||||
/**
|
||||
* Flush buffered recording chunks to the WebSocket.
|
||||
* Called when WebSocket becomes ready.
|
||||
*/
|
||||
function flushRecordingChunkBuffer(ws: WebSocket): void {
|
||||
if (recordingChunkBuffer.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug(`Flushing ${recordingChunkBuffer.length} buffered recording chunks`)
|
||||
|
||||
while (recordingChunkBuffer.length > 0) {
|
||||
const chunk = recordingChunkBuffer.shift()!
|
||||
const { tabId, data, final } = chunk
|
||||
|
||||
// Send metadata message first
|
||||
ws.send(JSON.stringify({
|
||||
method: 'recordingData',
|
||||
params: { tabId, final },
|
||||
}))
|
||||
|
||||
// Then send binary data if not final
|
||||
if (data && !final) {
|
||||
const buffer = new Uint8Array(data)
|
||||
ws.send(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionManager {
|
||||
ws: WebSocket | null = null
|
||||
private connectionPromise: Promise<void> | null = null
|
||||
@@ -106,6 +144,10 @@ class ConnectionManager {
|
||||
settled = true
|
||||
logger.debug('WebSocket connected')
|
||||
clearTimeout(timeout)
|
||||
|
||||
// Flush any buffered recording chunks now that WebSocket is ready
|
||||
flushRecordingChunkBuffer(socket)
|
||||
|
||||
resolve()
|
||||
}
|
||||
|
||||
@@ -1486,6 +1528,11 @@ chrome.runtime.onMessage.addListener((message, _sender, _sendResponse) => {
|
||||
const buffer = new Uint8Array(data)
|
||||
connectionManager.ws.send(buffer)
|
||||
}
|
||||
} else {
|
||||
// Buffer chunks when WebSocket isn't ready - they'll be flushed when it opens.
|
||||
// This prevents data loss during brief disconnections or slow WebSocket startup.
|
||||
logger.debug(`Buffering recording chunk for tab ${tabId} (WebSocket not ready)`)
|
||||
recordingChunkBuffer.push({ tabId, data, final })
|
||||
}
|
||||
|
||||
return false // Sync response, no need to keep channel open
|
||||
|
||||
@@ -154,8 +154,22 @@ async function handleStartRecording(params: OffscreenStartRecordingMessage): Pro
|
||||
console.log(`MediaRecorder stopped for tab ${tabId}`)
|
||||
}
|
||||
|
||||
// Start with 1 second chunks
|
||||
recorder.start(1000)
|
||||
// Wait for MediaRecorder to actually start before returning.
|
||||
// This ensures the encoder is initialized and ready to capture frames.
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('MediaRecorder failed to start within 5 seconds'))
|
||||
}, 5000)
|
||||
|
||||
recorder.onstart = () => {
|
||||
clearTimeout(timeout)
|
||||
console.log(`MediaRecorder started for tab ${tabId}`)
|
||||
resolve()
|
||||
}
|
||||
|
||||
// Start with 1 second chunks
|
||||
recorder.start(1000)
|
||||
})
|
||||
|
||||
return { success: true, tabId, startedAt, mimeType: 'video/mp4' }
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import type { Page } from 'playwright-core'
|
||||
import type {
|
||||
StartRecordingResult,
|
||||
@@ -96,10 +97,14 @@ export async function startRecording(options: StartRecordingOptions): Promise<Re
|
||||
relayPort = 19988,
|
||||
} = options
|
||||
|
||||
// Resolve relative paths to absolute using the caller's cwd.
|
||||
// The relay server may have a different cwd, so we must resolve here.
|
||||
const absoluteOutputPath = path.resolve(outputPath)
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${relayPort}/recording/start`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId, frameRate, videoBitsPerSecond, audioBitsPerSecond, audio, outputPath }),
|
||||
body: JSON.stringify({ sessionId, frameRate, videoBitsPerSecond, audioBitsPerSecond, audio, outputPath: absoluteOutputPath }),
|
||||
})
|
||||
|
||||
const result = await response.json() as StartRecordingResult
|
||||
|
||||
Reference in New Issue
Block a user