@t3lnet/sceneforge 1.0.27 → 1.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -182,7 +182,8 @@ export async function runRecordDemoCommand(argv) {
|
|
|
182
182
|
const hqVideo = hasFlag(args, "--hq-video");
|
|
183
183
|
const hqFormat = getFlagValue(args, "--hq-format") ?? "jpeg";
|
|
184
184
|
const hqQuality = parseInt(getFlagValue(args, "--hq-quality") ?? "100", 10);
|
|
185
|
-
const
|
|
185
|
+
const hqFpsArg = getFlagValue(args, "--hq-fps");
|
|
186
|
+
const hqFps = hqFpsArg ? parseInt(hqFpsArg, 10) : undefined; // undefined = use actual capture rate
|
|
186
187
|
|
|
187
188
|
if (!baseUrl) {
|
|
188
189
|
console.error("[error] --base-url is required");
|
|
@@ -262,6 +263,8 @@ export async function runRecordDemoCommand(argv) {
|
|
|
262
263
|
maxHeight: viewport.height,
|
|
263
264
|
});
|
|
264
265
|
await cdpRecorder.start();
|
|
266
|
+
// Wait for first frame to ensure recording is active before demo starts
|
|
267
|
+
await cdpRecorder.waitForFirstFrame();
|
|
265
268
|
}
|
|
266
269
|
|
|
267
270
|
const startUrl = resolveStartUrl(startPath, baseUrl);
|
|
@@ -305,7 +308,7 @@ export async function runRecordDemoCommand(argv) {
|
|
|
305
308
|
const finalVideoPath = path.join(outputPaths.videosDir, `${definition.name}.webm`);
|
|
306
309
|
try {
|
|
307
310
|
if (!demoError && result?.success) {
|
|
308
|
-
await cdpRecorder.assembleVideo(finalVideoPath, { fps: hqFps });
|
|
311
|
+
await cdpRecorder.assembleVideo(finalVideoPath, hqFps ? { fps: hqFps } : {});
|
|
309
312
|
console.log(`[record] High-quality video saved: ${finalVideoPath}`);
|
|
310
313
|
if (result.scriptPath) {
|
|
311
314
|
// Use the actual first frame time for alignment, not when we started the recorder
|
|
@@ -58,6 +58,7 @@ export function createCDPRecorder(page, options) {
|
|
|
58
58
|
let firstFrameTime = null; // Wall clock time when first frame was received
|
|
59
59
|
let writeQueue = Promise.resolve();
|
|
60
60
|
let nextFrameNumber = 0;
|
|
61
|
+
let recordingDuration = 0; // Duration in ms from start to stop
|
|
61
62
|
|
|
62
63
|
const frameHandler = (params) => {
|
|
63
64
|
if (!isRecording) return;
|
|
@@ -139,6 +140,24 @@ export function createCDPRecorder(page, options) {
|
|
|
139
140
|
console.log("[cdp-recorder] Started high-quality screen capture");
|
|
140
141
|
},
|
|
141
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Wait for the first frame to be captured.
|
|
145
|
+
* Call this after start() and before beginning the demo to ensure recording is active.
|
|
146
|
+
* @param {number} [timeoutMs=5000] - Maximum time to wait for first frame
|
|
147
|
+
* @returns {Promise<void>}
|
|
148
|
+
*/
|
|
149
|
+
async waitForFirstFrame(timeoutMs = 5000) {
|
|
150
|
+
const startWait = Date.now();
|
|
151
|
+
while (firstFrameTime === null) {
|
|
152
|
+
if (Date.now() - startWait > timeoutMs) {
|
|
153
|
+
console.warn("[cdp-recorder] Timeout waiting for first frame - proceeding anyway");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
157
|
+
}
|
|
158
|
+
console.log(`[cdp-recorder] First frame received after ${Date.now() - startWait}ms`);
|
|
159
|
+
},
|
|
160
|
+
|
|
142
161
|
/**
|
|
143
162
|
* Stop recording and return frame information
|
|
144
163
|
* @returns {Promise<{frameCount: number, duration: number, framesDir: string}>}
|
|
@@ -149,7 +168,7 @@ export function createCDPRecorder(page, options) {
|
|
|
149
168
|
}
|
|
150
169
|
|
|
151
170
|
isRecording = false;
|
|
152
|
-
|
|
171
|
+
recordingDuration = Date.now() - startTime;
|
|
153
172
|
|
|
154
173
|
try {
|
|
155
174
|
await cdpSession.send("Page.stopScreencast");
|
|
@@ -163,8 +182,12 @@ export function createCDPRecorder(page, options) {
|
|
|
163
182
|
await writeQueue;
|
|
164
183
|
|
|
165
184
|
const delayToFirstFrame = firstFrameTime ? firstFrameTime - startTime : 0;
|
|
185
|
+
const actualFps = frameTimestamps.length > 0 && recordingDuration > 0
|
|
186
|
+
? frameTimestamps.length / (recordingDuration / 1000)
|
|
187
|
+
: 30;
|
|
188
|
+
|
|
166
189
|
console.log(
|
|
167
|
-
`[cdp-recorder] Captured ${frameTimestamps.length} frames in ${(
|
|
190
|
+
`[cdp-recorder] Captured ${frameTimestamps.length} frames in ${(recordingDuration / 1000).toFixed(2)}s (${actualFps.toFixed(1)} fps)`
|
|
168
191
|
);
|
|
169
192
|
if (delayToFirstFrame > 100) {
|
|
170
193
|
console.log(`[cdp-recorder] First frame delay: ${delayToFirstFrame}ms`);
|
|
@@ -172,11 +195,12 @@ export function createCDPRecorder(page, options) {
|
|
|
172
195
|
|
|
173
196
|
return {
|
|
174
197
|
frameCount: frameTimestamps.length,
|
|
175
|
-
duration,
|
|
198
|
+
duration: recordingDuration,
|
|
176
199
|
framesDir,
|
|
177
200
|
frameTimestamps,
|
|
178
201
|
firstFrameTime, // Wall clock time when first frame was captured
|
|
179
202
|
startTime, // Wall clock time when recording was started
|
|
203
|
+
actualFps, // Actual capture framerate for real-time playback
|
|
180
204
|
};
|
|
181
205
|
},
|
|
182
206
|
|
|
@@ -195,10 +219,21 @@ export function createCDPRecorder(page, options) {
|
|
|
195
219
|
throw new Error("No frames captured - cannot assemble video");
|
|
196
220
|
}
|
|
197
221
|
|
|
198
|
-
//
|
|
199
|
-
|
|
222
|
+
// Calculate actual capture FPS for real-time playback
|
|
223
|
+
// This ensures video duration matches the actual recording duration
|
|
224
|
+
const calculatedFps = frameTimestamps.length > 0 && recordingDuration > 0
|
|
225
|
+
? frameTimestamps.length / (recordingDuration / 1000)
|
|
226
|
+
: 30;
|
|
227
|
+
|
|
228
|
+
// Use provided fps override or actual capture fps for real-time playback
|
|
229
|
+
const fps = fpsOverride || calculatedFps;
|
|
230
|
+
|
|
231
|
+
if (fpsOverride && Math.abs(fpsOverride - calculatedFps) > 1) {
|
|
232
|
+
console.log(`[cdp-recorder] Note: Using ${fps} fps (actual capture rate was ${calculatedFps.toFixed(1)} fps)`);
|
|
233
|
+
console.log(`[cdp-recorder] This will ${fpsOverride > calculatedFps ? 'speed up' : 'slow down'} playback`);
|
|
234
|
+
}
|
|
200
235
|
|
|
201
|
-
console.log(`[cdp-recorder] Assembling video at ${fps} fps`);
|
|
236
|
+
console.log(`[cdp-recorder] Assembling video at ${fps.toFixed(1)} fps`);
|
|
202
237
|
|
|
203
238
|
const frameExtension = format === "png" ? "png" : "jpg";
|
|
204
239
|
const framePattern = path.join(framesDir, `frame_%08d.${frameExtension}`);
|
|
@@ -263,5 +298,6 @@ High-Quality Recording Options:
|
|
|
263
298
|
Captures frames at maximum quality and assembles with FFmpeg
|
|
264
299
|
--hq-format <format> Frame format: jpeg (default) or png (lossless, larger files)
|
|
265
300
|
--hq-quality <1-100> JPEG quality (default: 100, ignored for PNG)
|
|
266
|
-
--hq-fps <fps> Output framerate (default:
|
|
301
|
+
--hq-fps <fps> Output framerate (default: actual capture rate for real-time)
|
|
302
|
+
Override only if you want to change playback speed`;
|
|
267
303
|
}
|
|
@@ -466,6 +466,7 @@ The CDP-based recorder captures frames directly from Chrome DevTools Protocol an
|
|
|
466
466
|
--hq-video # Enable CDP-based high-quality recording
|
|
467
467
|
--hq-format <format> # Frame format: jpeg (default) or png
|
|
468
468
|
--hq-quality <1-100> # JPEG quality (default: 100, ignored for PNG)
|
|
469
|
+
--hq-fps <fps> # Override output framerate (default: actual capture rate for real-time)
|
|
469
470
|
```
|
|
470
471
|
|
|
471
472
|
### Frame Formats
|
|
@@ -466,6 +466,7 @@ The CDP-based recorder captures frames directly from Chrome DevTools Protocol an
|
|
|
466
466
|
--hq-video # Enable CDP-based high-quality recording
|
|
467
467
|
--hq-format <format> # Frame format: jpeg (default) or png
|
|
468
468
|
--hq-quality <1-100> # JPEG quality (default: 100, ignored for PNG)
|
|
469
|
+
--hq-fps <fps> # Override output framerate (default: actual capture rate for real-time)
|
|
469
470
|
```
|
|
470
471
|
|
|
471
472
|
### Frame Formats
|