* Internal core: prepare, screenshot, and track perf. * Shared by captureFrame (disk) and captureFrameToBuffer (buffer). * Returns the screenshot buffer, quantized time, and total capture time.
( session: CaptureSession, frameIndex: number, time: number, )
| 1684 | * Returns the screenshot buffer, quantized time, and total capture time. |
| 1685 | */ |
| 1686 | async function captureFrameCore( |
| 1687 | session: CaptureSession, |
| 1688 | frameIndex: number, |
| 1689 | time: number, |
| 1690 | ): Promise<{ buffer: Buffer; quantizedTime: number; captureTimeMs: number }> { |
| 1691 | const { page, options } = session; |
| 1692 | const startTime = Date.now(); |
| 1693 | |
| 1694 | // Static-frame dedup: this frame is byte-identical to its predecessor (predicted + |
| 1695 | // anchor-verified at init) → reuse the prior buffer, skip the seek + screenshot. |
| 1696 | // KEY: index by the ABSOLUTE composition frame (derived from `time`), NOT the |
| 1697 | // `frameIndex` arg — chunked/parallel/distributed callers pass a chunk-RELATIVE |
| 1698 | // frameIndex (captureStage passes the loop `i`, parallelCoordinator passes |
| 1699 | // `i-outputFrameOffset`) while staticFrames is keyed in absolute frames. Using `time` |
| 1700 | // is correct on every path (sequential, per-worker range, distributed chunk) because |
| 1701 | // `time` is always the absolute composition time for the frame. Each session captures |
| 1702 | // its range in ascending order, so lastFrameBuffer is the correct in-range anchor (and |
| 1703 | // since a static run is verified identical, reusing the run's first in-range capture |
| 1704 | // equals reusing the global anchor). Telemetry: count reuses separately; do NOT bump |
| 1705 | // capturePerf.frames (that would dilute the per-frame timing averages). |
| 1706 | // Use the SAME floor+epsilon idiom as quantizeTimeToFrame so the dedup lookup agrees |
| 1707 | // with the frame the seek actually lands on, even if `time` ever isn't exactly i/fps. |
| 1708 | const absFrameIndex = Math.floor(time * fpsToNumber(options.fps) + 1e-9); |
| 1709 | if (session.staticFrames?.has(absFrameIndex) && session.lastFrameBuffer) { |
| 1710 | session.staticDedupCount = (session.staticDedupCount ?? 0) + 1; |
| 1711 | return { |
| 1712 | buffer: session.lastFrameBuffer, |
| 1713 | quantizedTime: quantizeTimeToFrame(time, fpsToNumber(options.fps)), |
| 1714 | captureTimeMs: Date.now() - startTime, |
| 1715 | }; |
| 1716 | } |
| 1717 | |
| 1718 | try { |
| 1719 | const { quantizedTime, seekMs, beforeCaptureMs } = await prepareFrameForCapture( |
| 1720 | session, |
| 1721 | frameIndex, |
| 1722 | time, |
| 1723 | ); |
| 1724 | |
| 1725 | const screenshotStart = Date.now(); |
| 1726 | let screenshotBuffer: Buffer; |
| 1727 | |
| 1728 | if (session.captureMode === "beginframe") { |
| 1729 | const frameTimeTicks = |
| 1730 | session.beginFrameTimeTicks + frameIndex * session.beginFrameIntervalMs; |
| 1731 | const result = await beginFrameCapture( |
| 1732 | page, |
| 1733 | options, |
| 1734 | frameTimeTicks, |
| 1735 | session.beginFrameIntervalMs, |
| 1736 | ); |
| 1737 | if (result.hasDamage) session.beginFrameHasDamageCount++; |
| 1738 | else session.beginFrameNoDamageCount++; |
| 1739 | screenshotBuffer = result.buffer; |
| 1740 | } else { |
| 1741 | screenshotBuffer = await pageScreenshotCapture(page, options); |
| 1742 | } |
| 1743 |
no test coverage detected