timecut icon indicating copy to clipboard operation
timecut copied to clipboard

Wait for web fonts to finish loading before recording a page

Open frizurd opened this issue 4 years ago • 8 comments

I'm trying to load external web fonts before Timecut starts recording. I use Webfontloader to load multiple web fonts from Google. The problem is that I can't find a way to load the fonts before or during the initial page render, the fonts will always start loading when Timecut is recording. Which results in the text suddenly changing from font-family during the render.

Webfontloader has a "Loaded" event callback but I can't find a way to wait for it in the "preparePage" function of Puppeteer. I also tried using setInterval to check if the fonts have finished loading but this just results in the same issue. setInterval, setTimeout and the callbacks never get fired during the preparePage stage.

frizurd avatar Sep 04 '20 08:09 frizurd

I understand that setInterval gets overwritten by Timecut. Would it be possible to ignore this for a specific time or function?

frizurd avatar Sep 09 '20 10:09 frizurd

Unfortunately, there currently isn't a way to access the overwritten setInterval or to prevent overwriting it. In a future version of timecut, overwritten time functions will be available.

You might have some success by trying to create a promise that resolves when the callback is called. I think this might work, though it's a little clunky. In your web page:

var loadResolve;
var preparePagePromise = new Promise(function (resolve) {
  loadResolve = resolve;
});
WebFontConfig = {
  active: loadResolve
};

Then in your node file:

config.preparePage = function () {
  return preparePagePromise;
}

If the font loading times are predictable, there is also config.startDelay that waits a certain number of seconds before the process starts.

Hopefully this gets a little bit easier when the old setInterval is accessible.

tungs avatar Sep 09 '20 16:09 tungs

First of all, thanks for the reply, I appreciate it a lot.

I've tried changing startDelay before, without success. The function does not get resolved during the delay, no matter the amount of time. It will always get executed and resolved during the render.

Tried your other method also but the same happens. I can't seem to find a way to resolve the WebFont promise before the render. If I strictly wait for a return it will just result in a Timecut timeout.

frizurd avatar Sep 10 '20 07:09 frizurd

I got the same use case to work by writing a custom function that awaits a promise and is executed before timers are overriden: https://github.com/MediaBits-io/timesnap/commit/403e70d6313d1362898ee54ca486aa20a236ff6b

Just have timecut use this version of timesnap.

vincaslt avatar Dec 21 '20 17:12 vincaslt

I got the same use case to work by writing a custom function that awaits a promise and is executed before timers are overriden: MediaBits-io/timesnap@403e70d

Just have timecut use this version of timesnap.

Awesome thank you so much.

I can successfully delay and render a video but it always shows the following error after capturing 10 to 20 frames.

This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason: Error: Protocol error (Runtime.callFunctionOn): Promise was collected at Promise (/Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/node_modules/puppeteer/lib/Connection.js:183:56) at new Promise (<anonymous>) at CDPSession.send (/Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/node_modules/puppeteer/lib/Connection.js:182:12) at ExecutionContext._evaluateInternal (/Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/node_modules/puppeteer/lib/ExecutionContext.js:107:44) at ExecutionContext.evaluate (/Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/node_modules/puppeteer/lib/ExecutionContext.js:48:23) at ExecutionContext.<anonymous> (/Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/node_modules/puppeteer/lib/helper.js:112:23) at DOMWorld.evaluate (/Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/node_modules/puppeteer/lib/DOMWorld.js:112:20) -- ASYNC -- at Frame.<anonymous> (/Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/node_modules/puppeteer/lib/helper.js:111:15) at /Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/lib/overwrite-time.js:222:18 at Array.map (<anonymous>) at Object.goToTimeAndAnimate (/Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/lib/overwrite-time.js:221:36) at Timeout.setInterval (/Users/frizurd/Google Drive/Projects/vokal/api/node_modules/timesnap/index.js:194:51) at ontimeout (timers.js:436:11) at tryOnTimeout (timers.js:300:5) at listOnTimeout (timers.js:263:5) at Timer.processTimers (timers.js:223:10)

Am I implementing it correctly with the following code:

preparePageRealtime: async(page) => { await page.waitFor(1000) }

frizurd avatar Dec 31 '20 16:12 frizurd

Hmm, it seems like something has thrown an error in preparePageRealtime. I think I didn't set it up to handle errors or rethrow them. I'll make the change in the same fork, hopefully it will help with at least being able to try-catch it. You must be trying to load fonts before rendering?

vincaslt avatar Jan 01 '21 13:01 vincaslt

You must be trying to load fonts before rendering?

Yes that's right

frizurd avatar Jan 01 '21 14:01 frizurd

I pushed some changes that will enable rethrowing. You will be able to catch the error outside of puppeteer now, but you may still be getting the same error though.

I found I had the same issue, it's likely related to how you handle promises with webfontloader (I assume you're using that). I had my resolve bound to active, which was never called on error, and load() never threw an error so my promise was never resolving or being rejected, hence the garbage collection error.

I now have something like this, let me know if it's relevant:

    try {
      WebFont.load({
        google: {
          families,
        },
        active: resolve,
        inactive: () => {
          reject('No fonts were loaded!');
        },
        loading: () => {
          console.log('Loading fonts', families.join(', '));
        },
      });
    } catch (e) {
      reject(e);
    }

vincaslt avatar Jan 01 '21 16:01 vincaslt