mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-18 00:29:42 +03:00
fix(web-test): sync video timeline with wall-clock during static pauses
CDP screencast doesn't send frames for static pages, causing video to be shorter than real time (gap-fill capped at 2s, smart pauses are 4-6s). - Add _flushFrames() helper on recorder to write duplicate frames on demand - Call _flushFrames() every 1s during smart TTS pauses in showCaption - Call _flushFrames() in wait() for long explicit pauses during recording - Call _flushFrames() in stopRecording for final gap before closing ffmpeg - Increase gap-fill cap from fps*2 to fps*30 as safety net Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2963,7 +2963,21 @@ export async function wait(seconds) {
|
|||||||
ms = Math.max(0, ms - credit);
|
ms = Math.max(0, ms - credit);
|
||||||
recorder.captionCredit = null;
|
recorder.captionCredit = null;
|
||||||
}
|
}
|
||||||
if (ms > 0) await page.waitForTimeout(ms);
|
if (ms > 0) {
|
||||||
|
// During recording, split long waits into chunks and flush frames
|
||||||
|
// to keep video timeline in sync (CDP may not send frames for static pages)
|
||||||
|
if (recorder?._flushFrames && ms > 1000) {
|
||||||
|
let remaining = ms;
|
||||||
|
while (remaining > 0) {
|
||||||
|
const chunk = Math.min(remaining, 1000);
|
||||||
|
await page.waitForTimeout(chunk);
|
||||||
|
remaining -= chunk;
|
||||||
|
recorder._flushFrames();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await page.waitForTimeout(ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getFormState();
|
return await getFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3039,7 +3053,7 @@ export async function startRecording(outputPath, opts = {}) {
|
|||||||
// Fill the gap with duplicates of the previous frame
|
// Fill the gap with duplicates of the previous frame
|
||||||
const gap = now - lastFrameTime;
|
const gap = now - lastFrameTime;
|
||||||
const dupes = Math.round(gap / frameDuration) - 1;
|
const dupes = Math.round(gap / frameDuration) - 1;
|
||||||
for (let i = 0; i < dupes && i < fps * 2; i++) {
|
for (let i = 0; i < dupes && i < fps * 30; i++) {
|
||||||
ffmpeg.stdin.write(lastFrameBuf);
|
ffmpeg.stdin.write(lastFrameBuf);
|
||||||
framesWritten++;
|
framesWritten++;
|
||||||
}
|
}
|
||||||
@@ -3062,7 +3076,23 @@ export async function startRecording(outputPath, opts = {}) {
|
|||||||
everyNthFrame: 1
|
everyNthFrame: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
recorder = { cdp, ffmpeg, startTime: Date.now(), outputPath: resolvedPath, ffmpegError: '', captions: [], videoTimeMs: 0 };
|
// Expose a frame-writing helper on the recorder object.
|
||||||
|
// During static periods (e.g. smart TTS pauses), CDP may not send screencast
|
||||||
|
// frames. Call _flushFrames() to fill the gap with duplicates of the last frame,
|
||||||
|
// keeping video timeline in sync with wall-clock time.
|
||||||
|
const _flushFrames = () => {
|
||||||
|
if (!lastFrameBuf || !lastFrameTime || ffmpeg.stdin.destroyed) return;
|
||||||
|
const now = Date.now();
|
||||||
|
const gap = now - lastFrameTime;
|
||||||
|
const dupes = Math.round(gap / frameDuration);
|
||||||
|
for (let i = 0; i < dupes; i++) {
|
||||||
|
ffmpeg.stdin.write(lastFrameBuf);
|
||||||
|
if (recorder) recorder.videoTimeMs += frameDuration;
|
||||||
|
}
|
||||||
|
if (dupes > 0) lastFrameTime = now;
|
||||||
|
};
|
||||||
|
|
||||||
|
recorder = { cdp, ffmpeg, startTime: Date.now(), outputPath: resolvedPath, ffmpegError: '', captions: [], videoTimeMs: 0, _flushFrames };
|
||||||
// Redirect stderr accumulation to the recorder object
|
// Redirect stderr accumulation to the recorder object
|
||||||
ffmpeg.stderr.removeAllListeners('data');
|
ffmpeg.stderr.removeAllListeners('data');
|
||||||
ffmpeg.stderr.on('data', d => { recorder.ffmpegError += d.toString(); });
|
ffmpeg.stderr.on('data', d => { recorder.ffmpegError += d.toString(); });
|
||||||
@@ -3077,6 +3107,9 @@ export async function stopRecording() {
|
|||||||
|
|
||||||
const { cdp, ffmpeg, startTime, outputPath } = recorder;
|
const { cdp, ffmpeg, startTime, outputPath } = recorder;
|
||||||
|
|
||||||
|
// Final frame flush: write remaining frames to cover the gap since the last screencast frame
|
||||||
|
if (recorder._flushFrames) recorder._flushFrames();
|
||||||
|
|
||||||
// Stop CDP screencast
|
// Stop CDP screencast
|
||||||
try { await cdp.send('Page.stopScreencast'); } catch {}
|
try { await cdp.send('Page.stopScreencast'); } catch {}
|
||||||
try { await cdp.detach(); } catch {}
|
try { await cdp.detach(); } catch {}
|
||||||
@@ -3172,9 +3205,17 @@ export async function showCaption(text, opts = {}) {
|
|||||||
el.textContent = text;
|
el.textContent = text;
|
||||||
}, { text, position, fontSize, bg, color });
|
}, { text, position, fontSize, bg, color });
|
||||||
|
|
||||||
// Smart TTS wait: pause for estimated speech duration so video has enough screen time
|
// Smart TTS wait: pause for estimated speech duration so video has enough screen time.
|
||||||
|
// Split into chunks and flush frames periodically — CDP doesn't send screencast frames
|
||||||
|
// for static pages, so we must write duplicate frames to keep video timeline in sync.
|
||||||
if (smartWaitMs > 0) {
|
if (smartWaitMs > 0) {
|
||||||
await page.waitForTimeout(smartWaitMs);
|
let remaining = smartWaitMs;
|
||||||
|
while (remaining > 0) {
|
||||||
|
const chunk = Math.min(remaining, 1000);
|
||||||
|
await page.waitForTimeout(chunk);
|
||||||
|
remaining -= chunk;
|
||||||
|
if (recorder?._flushFrames) recorder._flushFrames();
|
||||||
|
}
|
||||||
recorder.captionCredit = { waitedMs: smartWaitMs, at: Date.now() };
|
recorder.captionCredit = { waitedMs: smartWaitMs, at: Date.now() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user