Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@positlabs
Created February 18, 2019 20:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save positlabs/8771dace9df61a92b699d4a66a834ac5 to your computer and use it in GitHub Desktop.
Save positlabs/8771dace9df61a92b699d4a66a834ac5 to your computer and use it in GitHub Desktop.
/*
The client lib for the generator will override some browser methods in order to
generate video or gifs of the page that it runs on. This guarantees that frames
won't be dropped, and the framerate will be consistent.
Global method overrides
- setTimeout
- setInterval
- requestAnimationFrame
Set currentTime of videos
*/
const queries = new URLSearchParams(window.location.search);
// cache originals just in case
const _requestAnimationFrame = window.requestAnimationFrame.bind(window);
const _setTimeout = window.setTimeout.bind(window);
const _setInterval = window.setInterval.bind(window);
const _clearTimeout = window.clearTimeout.bind(window);
const _clearInterval = window.clearInterval.bind(window);
const _dateNow = Date.now.bind(window);
// let contentIsReady = false
const state = {
done: false,
frameAvailable: false,
};
let currentTime = 0;
let now = _dateNow();
let options = {
fps: 24,
};
// let videos = []
// let frameAvailable = false
let rafCallbacks = [];
let timeouts = [];
let intervals = [];
class Timeout {
constructor(cb, dur) {
this.callback = cb;
this.duration = dur;
// use the native setTimeout to generate ids
this.id = _setTimeout(() => {}, 1);
this._timeRemaining = dur;
}
advance(timeDiff) {
this._timeRemaining -= timeDiff;
if (this._timeRemaining <= 0) {
this.callback();
return true;
}
return false;
}
}
class Interval {
constructor(cb, dur) {
this.callback = cb;
this.duration = dur;
// use the native setInterval to generate ids
this.id = _setInterval(() => {}, 1000);
_clearInterval(this.id);
this._timeRemaining = dur;
}
advance(timeDiff) {
this._timeRemaining -= timeDiff;
if (this._timeRemaining <= 0) {
this.callback();
this._timeRemaining = this.duration;
return true;
}
return false;
}
}
let nextFramePromise = Promise.resolve();
const nextFrame = async () => {
if (state.done) {
state.frameAvailable = false;
return state;
}
// ensure a new frame is available since last call
await nextFramePromise;
nextFramePromise = newFrame();
const frameDur = 1000 / options.fps;
currentTime += frameDur;
now += 1000 / options.fps;
// remove all of the callbacks and run them
rafCallbacks.splice(0, rafCallbacks.length).forEach(cb => cb(currentTime));
// advance timeouts and prune dead ones
timeouts = timeouts.filter(timeout => {
const complete = timeout.advance(frameDur);
// keep unfinished timeouts, discard finished timeouts
return !complete;
});
// advance intervals
intervals.forEach(interval => interval.advance(frameDur));
state.frameAvailable = true;
return state;
};
const newFrame = () => {
return new Promise(resolve => {
_requestAnimationFrame(resolve);
});
};
/*
*/
const start = config => {
videos = [].slice.call(document.querySelectorAll('video'));
now = _dateNow();
window.addEventListener('message', nextFrame);
if (config.dryRun) {
console.log('dryRun', config.dryRun);
_setInterval(nextFrame, config.dryRun);
}
};
/**
* overrides global timing methods (requestAnimationFrame, setTimeout, setInterval)
*/
const init = () => {
window.setTimeout = (cb, dur) => {
// console.log('setTimeout', cb, dur)
const timeout = new Timeout(cb, dur);
timeouts.push(timeout);
return timeout.id;
};
window.clearTimeout = id => {
// remove the timeout from the array
const target = timeouts.filter(timeout => timeout.id === id)[0];
timeouts.splice(timeouts.indexOf(target), 1);
};
window.setInterval = (cb, dur) => {
const interval = new Interval(cb, dur);
intervals.push(interval);
return interval.id;
};
window.clearInterval = id => {
// remove the interval from the array
const target = intervals.filter(interval => interval.id === id)[0];
intervals.splice(intervals.indexOf(target), 1);
};
window.requestAnimationFrame = cb => {
rafCallbacks.push(cb);
};
window.Date.now = () => now;
};
const stop = () => {
state.done = true;
};
window.rendererClient = {
init,
start,
nextFrame,
stop,
original: {
setTimeout: _setTimeout,
setInterval: _setInterval,
clearTimeout: _clearTimeout,
clearInterval: _clearInterval,
requestAnimationFrame: _requestAnimationFrame,
dateNow: _dateNow,
},
};
init();
start({ dryRun: parseInt(queries.get('dry-run')) });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment