diff --git a/index.js b/index.js index 41f2675..6df94f6 100644 --- a/index.js +++ b/index.js @@ -77,7 +77,7 @@ const ERRORS = { // the different capture modes const CAPTURE_MODES = ["CANVAS", "VIEWPORT"] // the list of accepted trigger modes -const TRIGGER_MODES = ["DELAY", "FN_TRIGGER", "FN_TRIGGER_GIF"] +const TRIGGER_MODES = ["DELAY", "FN_TRIGGER"] // // UTILITY FUNCTIONS @@ -94,7 +94,7 @@ function isUrlValid(url) { } // is a trigger valid ? looks at the trigger mode and trigger settings -function isTriggerValid(triggerMode, delay, playbackFps) { +function isTriggerValid(triggerMode, delay) { if (!TRIGGER_MODES.includes(triggerMode)) { return false } @@ -106,15 +106,8 @@ function isTriggerValid(triggerMode, delay, playbackFps) { delay >= DELAY_MIN && delay <= DELAY_MAX ) - } else if (triggerMode === "FN_TRIGGER_GIF") { - return ( - typeof playbackFps !== undefined && - !isNaN(playbackFps) && - playbackFps >= GIF_DEFAULTS.MIN_FPS && - playbackFps <= GIF_DEFAULTS.MAX_FPS - ) } else if (triggerMode === "FN_TRIGGER") { - // fn trigger and fn trigger gif don't need any params + // fn trigger doesn't need any param return true } } @@ -330,7 +323,6 @@ const resizeCanvas = async (image, resX, resY) => { } const performCapture = async ( mode, - triggerMode, page, canvasSelector, resX, @@ -345,14 +337,7 @@ const performCapture = async ( // if viewport mode, use the native puppeteer page.screenshot if (mode === "VIEWPORT") { // we simply take a capture of the viewport - return captureViewport( - page, - triggerMode, - gif, - frameCount, - captureInterval, - playbackFps - ) + return captureViewport(page, gif, frameCount, captureInterval, playbackFps) } // if the mode is canvas, we need to execute som JS on the client to select // the canvas and generate a dataURL to bridge it in here @@ -360,7 +345,6 @@ const performCapture = async ( const canvas = await captureCanvas( page, canvasSelector, - triggerMode, gif, frameCount, captureInterval, @@ -435,7 +419,7 @@ const validateParams = ({ if (!url || !mode) throw ERRORS.MISSING_PARAMETERS if (!isUrlValid(url)) throw ERRORS.UNSUPPORTED_URL if (!CAPTURE_MODES.includes(mode)) throw ERRORS.INVALID_PARAMETERS - if (!isTriggerValid(triggerMode, delay, playbackFps)) + if (!isTriggerValid(triggerMode, delay)) throw ERRORS.INVALID_TRIGGER_PARAMETERS if (gif && !validateGifParams(frameCount, captureInterval, playbackFps)) @@ -472,11 +456,17 @@ const validateParams = ({ } } -async function captureFramesWithTiming( - captureFrameFunction, +async function captureViewport( + page, + isGif, frameCount, - captureInterval + captureInterval, + playbackFps ) { + if (!isGif) { + return await page.screenshot() + } + const frames = [] let lastCaptureStart = performance.now() @@ -484,9 +474,11 @@ async function captureFramesWithTiming( // Record start time of screenshot operation const captureStart = performance.now() - // Use the provided capture function to get the frame - const frame = await captureFrameFunction() - frames.push(frame) + // Capture raw pixels + const frameBuffer = await page.screenshot({ + encoding: "binary", + }) + frames.push(frameBuffer) // Calculate how long the capture took const captureDuration = performance.now() - captureStart @@ -510,88 +502,6 @@ async function captureFramesWithTiming( lastCaptureStart = performance.now() } - return frames -} - -async function captureFramesProgrammatically(page, captureFrameFunction) { - const frames = [] - - page.on("console", msg => { - console.log("BROWSER:", msg.text()) - }) - - // set up the event listener and capture loop - await page.exposeFunction("captureFrame", async () => { - const frame = await captureFrameFunction() - frames.push(frame) - console.log(`programmatic frame ${frames.length} captured`) - return frames.length - }) - - // wait for events in browser context - await page.evaluate( - function (maxFrames, delayMax) { - return new Promise(function (resolve) { - const handleFrameCapture = async event => { - const frameCount = await window.captureFrame() - - console.log(JSON.stringify(event)) - console.log(JSON.stringify({ frameCount, maxFrames })) - console.log( - JSON.stringify({ isLastFrame: event.detail?.isLastFrame }) - ) - if (event.detail?.isLastFrame || frameCount >= maxFrames) { - window.removeEventListener( - "fxhash-capture-frame", - handleFrameCapture - ) - resolve() - } - } - - window.addEventListener("fxhash-capture-frame", handleFrameCapture) - - // timeout fallback - setTimeout(() => { - window.removeEventListener("fxhash-capture-frame", handleFrameCapture) - resolve() - }, delayMax) - }) - }, - GIF_DEFAULTS.MAX_FRAMES, - DELAY_MAX - ) - - return frames -} - -async function captureViewport( - page, - triggerMode, - isGif, - frameCount, - captureInterval, - playbackFps -) { - if (!isGif) { - return await page.screenshot() - } - - const captureViewportFrame = async () => { - return await page.screenshot({ - encoding: "binary", - }) - } - - const frames = - triggerMode === "FN_TRIGGER_GIF" - ? await captureFramesProgrammatically(page, captureViewportFrame) - : await captureFramesWithTiming( - captureViewportFrame, - frameCount, - captureInterval - ) - const viewport = page.viewport() return await captureFramesToGif( frames, @@ -604,7 +514,6 @@ async function captureViewport( async function captureCanvas( page, canvasSelector, - triggerMode, isGif, frameCount, captureInterval, @@ -623,24 +532,36 @@ async function captureCanvas( return Buffer.from(pureBase64, "base64") } - const captureCanvasFrame = async () => { + const frames = [] + let lastCaptureStart = Date.now() + + for (let i = 0; i < frameCount; i++) { + const captureStart = Date.now() + // Get raw pixel data from canvas const base64 = await page.$eval(canvasSelector, el => { if (!el || el.tagName !== "CANVAS") return null return el.toDataURL() }) - if (!base64) throw new Error("Canvas capture failed") - return base64 - } + if (!base64) throw null + frames.push(base64) - const frames = - triggerMode === "FN_TRIGGER_GIF" - ? await captureFramesProgrammatically(page, captureCanvasFrame) - : await captureFramesWithTiming( - captureCanvasFrame, - frameCount, - captureInterval - ) + // Calculate timing adjustments + const captureDuration = Date.now() - captureStart + const adjustedInterval = Math.max(0, captureInterval - captureDuration) + + console.log(`Frame ${i + 1}/${frameCount}:`, { + captureDuration, + adjustedInterval, + totalFrameTime: Date.now() - lastCaptureStart, + }) + + if (adjustedInterval > 0) { + await sleep(adjustedInterval) + } + + lastCaptureStart = Date.now() + } const dimensions = await page.$eval(canvasSelector, el => ({ width: el.width, @@ -750,7 +671,6 @@ exports.handler = async (event, context) => { const processCapture = async () => { const capture = await performCapture( mode, - triggerMode, page, canvasSelector, resX, @@ -767,16 +687,10 @@ exports.handler = async (event, context) => { return upload } - if (triggerMode === "FN_TRIGGER_GIF") { - // for FN_TRIGGER_GIF mode, skip preview waiting entirely - // the capture functions will handle event listening internally - console.log("Using FN_TRIGGER_GIF mode - skipping preview wait") + if (useFallbackCaptureOnTimeout) { + await waitPreviewWithFallback(context, triggerMode, page, delay) } else { - if (useFallbackCaptureOnTimeout) { - await waitPreviewWithFallback(context, triggerMode, page, delay) - } else { - await waitPreview(triggerMode, page, delay) - } + await waitPreview(triggerMode, page, delay) } httpResponse = await processCapture()