Deriving a more-accurate clock from an inaccurate clock - javascript

The Problem
I need to be able to synchronize some JavaScript events to specific timing in YouTube videos as closely as possible. While I understand there are some limitations to how accurate timers can be in browsers, I think it should be possible to do better than what I'm getting from the video player. I used the following code on starting playback of the YouTube video.
startTime = new Date();
setInterval(function () {
samples.push({player: player.getCurrentTime(), jstime: new Date() - startTime});
}, 20);
This code gets the current time that the video player thinks it is at with player.getCurrentTime(), and logs it along with regular clock time against the time that playback was started. The following results give you an idea of the accuracy:
{"player":0.188,"jstime":109},
{"player":0.676,"jstime":125},
{"player":0.676,"jstime":140},
{"player":0.676,"jstime":171},
{"player":0.676,"jstime":203},
{"player":0.676,"jstime":218},
{"player":0.676,"jstime":234},
{"player":0.676,"jstime":265},
{"player":0.676,"jstime":296},
{"player":0.676,"jstime":312},
{"player":0.676,"jstime":327},
{"player":0.676,"jstime":577},
{"player":1.012,"jstime":624},
{"player":1.187,"jstime":655},
{"player":1.187,"jstime":671},
{"player":1.187,"jstime":686},
{"player":1.187,"jstime":717},
{"player":1.187,"jstime":733},
{"player":1.187,"jstime":749},
{"player":1.187,"jstime":780},
{"player":1.187,"jstime":811},
{"player":1.447,"jstime":842},
{"player":1.447,"jstime":858},
{"player":1.447,"jstime":873},
{"player":1.447,"jstime":905},
{"player":1.447,"jstime":936},
{"player":1.447,"jstime":951},
{"player":1.447,"jstime":998},
{"player":1.605,"jstime":1029},
{"player":1.605,"jstime":1061},
Some digging online reveals that the accuracy of the YouTube video timer comes directly from the underlying player (which will usually be HTML5 in my case), and should not be relied upon for anything more accurate than a few hundred milliseconds.
A Possible Solution
A few observations/assumptions (and some stating the obvious):
The time retrieved from player.getCurrentTime() will be approximating something constant.
The rate at which player time passes should be nearly the same for the clock-on-the-wall. (It may drift slightly though due to the fact that video frames are usually tied to an audio clock which always varies from machine to machine by a few Hz.)
If I observe both clocks over time, I should be able to determine the difference in rate between them (which should be close to 0).
Once the error rates are known and samples are taken over time, it should be possible to derive a timer that is close to the accuracy of the most accurate timer of the two (the clock provided to JavaScript). Is this assumption correct?
How to implement?
Given the inputs of player time and JavaScript clock-on-the-wall time, how can I derive a timer that I can call every animation frame to give me the highest accuracy possible?
How accurate could such a derived time be?

I've done this sort of thing before (admittedly using ActionScript and targeting the AVM) but the principles still stand.
Don't rely on the browser (or app) for timing info.
Think about it, a little bit of buffering on the video side, or the occasional beach-ball / spinning hour glass, and suddenly your synchronisation is wrong.
What you want to do is hook into media player events, and then react to the time of the video.
The W3 HTML5 Video demo page [ http://www.w3.org/2010/05/video/mediaevents.html ] shows a whole bunch of properties and values - the ones I think you'd be wanting to take a look at are the timeUpdate event and the currentTime property.

I think I've figured out how to accomplish this. A bit messy for now, but you get the idea.
function youTubeTimer (player) {
var startTime;
var totalTime = 0;
var lastPlayerTime;
//Array of offsets between the JS timer and reported video player time
var deltas = [];
// Amount of time to correct
var correction;
player.addEventListener('onStateChange', function (e) {
if (e.data === YT.PlayerState.PLAYING) {
startTime = new Date();
} else if (e.data === YT.PlayerState.PAUSED || e.data === YT.PlayerState.BUFFERING || e.data === YT.PlayerState.ENDED) {
totalTime = ((new Date() - startTime)/1000) + totalTime;
}
if (e.data === YT.PlayerState.ENDED) {
console.log(totalTime);
}
});
function getJsTime() {
if (player.getPlayerState() === YT.PlayerState.PLAYING) {
return ((new Date() - startTime)/1000) + totalTime;
} else {
return totalTime;
}
}
// Call this function frequently!
this.getHighResPlayerTime = function getHighResPlayerTime() {
if (!player.getCurrentTime) {
return 0;
}
var playerTime = player.getCurrentTime(); // Seconds of playback, reported by the video player
var jsTime = getJsTime(); // Seconds from the clock time when the video started
// Has the player time been updated? Adjust the correction offset.
if (playerTime !== lastPlayerTime) {
lastPlayerTime = playerTime;
// Store up to 20 samples of offsets
if (deltas.length >= 500) {
deltas.shift();
}
deltas.push(jsTime - playerTime);
// Calculate a new correction value
correction = 0;
for (var x = 0; x < deltas.length; x ++)
{
correction += deltas[x];
}
correction = correction / x;
}
return jsTime - correction;
}
}

Related

How can I get an event when an audio file reaches a certain point?

I am experimenting with interactive audio applications in HTML, and I would like to be able to seamlessly start playing one audio file just as another audio file reaches a particular playback location; for example, if audio file A is playing, then I would like to receive an event when it reaches 16 seconds in exactly.
I have tried using audio.play(); setTimeout(myCallback, 16000); for this, but I am finding it to be incredibly unstable in some browsers (Safari especially, particularly when the window is in the background), and will sometimes fire slightly early, and other times fire very late. This remains the case even if I explicitly stop, rewind, and .load() the upcoming audio segment when scheduling the callback.
My current (simple) looper that simply toggles between two players every 16 seconds is below:
audio = []
for (var i = 0; i < 2; i++) {
audio[i] = new Audio("mew" + i + ".mp3");
}
var which = 0;
var pump = function() {
audio[which].play();
which = (which + 1) % audio.length;
window.setTimeout(pump, 16000);
}
audio[which].addEventListener('canplaythrough', function() {
pump();
});
for (i in audio) {
audio[i].load();
}
Also, note that while in this case I could use setInterval(), that will not always be the case (and anyway, setInterval() suffers from the same stability problem).

Web Audio API and real current time when playing an audio file

I am having problems when I want to know the current time of a file playing using the Web Audio API. My code plays the file nicely and the current time returned by the getCurrentTime() function is accurate enough when it comes to short files which load fast.
But when I try to load big files, sometimes the current time returned by the getCurrentTime() function is accurate and sometimes not. Sometimes, after waiting for example for 20 seconds to hear the file playing, when it starts playing it says that the current time is about 20 seconds (which is not true because it is just playing the beginning of the file). It happens with any audio format (OGG, MP3, WAV...) but only sometimes.
I am using a slow system (Asus EEE PC 901 with an Intel Atom 1.60 Ghz and 2 GB RAM with Windows XP Home Edition and SP3) and Firefox 41.0.1.
I am not sure, but it seems that the source.start() method starts playing the sound way too late, so the line after calling that method, where I set the value for the startTime variable, is not the real starting time.
Here is the code (simplified):
var context, buffer, startTime, source;
var stopped = true;
function load(file, startAt)
{
//Here creates the AudioContext and loads the file through XHR (AJAX) and gets the buffer. All works fine.
//When it gots the buffer through XHR (AJAX) and all is fine, it calls play(startAt) function immediately.
//Note: normally, startAt is 0.
}
function play(startAt)
{
source = context.createBufferSource(); //Context created before.
source.buffer = buffer; //Buffer got before from XHR (AJAX).
//Creates a gain node to be able to set the volume later:
var gainNode = context.createGain();
source.connect(gainNode);
gainNode.connect(context.destination);
//Plays the sound:
source.loop = false;
source.start(startAt, 0, buffer.duration - 3); //I don't want the last 3 seconds.
//Stores the start time (useful for pause/resume):
startTime = context.currentTime - startAt; //Here I store the startTime but maybe the file has still not begun to play (should it be just startTime = context.currentTime?).
stopped = false;
}
function stop()
{
source.stop(0);
stopped = true;
}
function getCurrentTime()
{
return (stopped) ? 0 : context.currentTime - startTime;
}
How can I detect when exactly the source.start() method starts playing the file? So I can set the startTime variable value just at that moment, and never before.
Thank you very much in advance. I would really appreciate any kind of help.
From MDN (https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/start), about the first parameter of the start() function:
when (Optional)
The time, in seconds, at which the sound should begin to play, in the same time coordinate system used by the AudioContext. If when is less than (AudioContext.currentTime, or if it's 0, the sound begins to play at once. The default value is 0.
There is no evident issue with your code (although there is no example a call to play()): if you call play(0) or play(context.currentTime + someDelayInSeconds), start() should behave as expected. Unfortunately here the issue is that AudioBufferSource is not meant for big files. Again from the MDN doc of AudioBuffer (https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer):
Objects of these types are designed to hold small audio snippets, typically less than 45 s.
I suspect that for big something doesn't work very well with the "sound begins play at once" assumption (I also experienced it, although 20 seconds seems way too much...). Unfortunately there is no way to get the exact start time of AudioBufferSource in WebAudio yet.
If you don't have any real reason to load this big file with AudioBufferSource, I suggest you use a MediaElementSourceNode (https://developer.mozilla.org/en-US/docs/Web/API/MediaElementAudioSourceNode): as you can see from the example on the linked doc, it allows you to plug a simple HTML5 Audio element into the AudioContext. You then can have all usual control over the element itself, i.e. you also have access to the audioElement.currentTime property, which tells you the current playout time (in this case of the file itself, which is what you need). Additionally, you don't have to handle loading of the file in memory and could start playing as soon as some data is available.
context.currentTime starts counting the second you create the context object. That means if it takes 20 seconds for your audio to load, context.currentTime == 20.
To account for this delay, you can set a simple timer from the time that you create the context to the time that audio loading completes.
var context; //Create your context here
var audioLoadStart = new Date();
//Do audio load
var audioLoadOffset = (new Date() - audioLoadStart) / 1000;
currentTime = context.currentTime - audioLoadOffset - startTime;

HTML5 canvas game won't pause rendering when browser is not in focus

My friends and I are making a game, and from some earlier tests, requestAnimationFrame() would make sure that the game would pause when it wasn't in focus. In our current game, here, you can see that it keeps rendering everything even when not in focus. This is what we use to deal with the rendering:
//main game loop, updates and renders the game
var main = function(){
var now = Date.now();
var delta = now - then;
update(delta / 1000);
render();
then = now;
requestAnimationFrame(main);
};
//updates the positions of the target and enemy
var update = function(delta){
target.update(delta, gamecanvas);
planet.update(delta);
enemies.forEach(function(enemy){
enemy.update(delta, gamecanvas)
});
defenders.forEach(function(enemy){
enemy.update(delta, gamecanvas)
});
};
//clears the screen
var clearScreen = function(){
gamectx.clearRect(0,0,gamecanvas.width, gamecanvas.height);
};
//clears the screen, and redraws the objects
var render = function(){
clearScreen();
planet.draw(gamectx);
enemies.forEach(function(enemy){
enemy.draw(gamectx)
});
defenders.forEach(function(enemy){
enemy.draw(gamectx)
});
target.draw(gamectx);
};
//updates the time, runs the main loop
var then = Date.now();
main();
Anyone have an idea what's going wrong?
I think you're looking in the wrong direction.
• RequestAnimationFrame does stop triggering when your application is tabbed out.
But the 'real' time keeps on running. So your delta, when Browser resumes, will be huge, most probably making your movements/physic/.... go to dust.
• So first thing : have the delta clamped. In both ways. Say if it's above 50ms, you must have been tabbed out, so consider, say, 16ms elapsed. But for very fast display (like 120Hz, some people have this), a too small delta will drive the computer's fan crazy, so check it against, say 14ms to avoid overheat/fan turning/battery sinking. You might want to handle a 'game time' that would be the time elapsed within your game.
var gameTime = 0;
var typicalFrame = 16;
var smallestFrame = 14;
var longestFrame = 50;
var main = function(){
var now = Date.now();
var delta = now - then;
if (delta<smallestFrame) return;
if (delta>longestFrame) delta = typicalFrame;
gameTime += delta;
then = now;
...
• Second thing : Even if the advice above might be enough, in fact you might want to pause the game properly when you loose focus. First example that comes to mind is the music, that you might want to stop (do it ! it's so boring when you don't even know which tab is playing music !!). Second example is a network game where the server will like to know the player isn't playing any more.
This is not that difficult to do : just handle window.onblur and window.onfocus events and do what you think appropriate here to stop your game clock, music, ...

Make HTML5 video stop at indicated time

I have a HTML5 video element in my page. The video I want to play is having a duration of 10 minutes.
I have to play the part of the video from minute 1 to minute 5.
I can start it from a particular time by setting its currentTime property.
But how can I stop the video at a particular time jQuery or JavaScript?
TL;DR: Simply listen on "timeupdate":
video.addEventListener("timeupdate", function(){
if(this.currentTime >= 5 * 60) {
this.pause();
}
});
The usual way to wait for something in JavaScript is to wait for an event or a timeout. A timeout is out of question in this case, the user might pause the video on his own. In this case the stop wouldn't be on your specific time, but earlier.
Checking the time regularly is also too costly: you either check too often (and therefore waste precious processing power) or not often enough and therefore you won't stop at the correct time.
However currentTime is a checkable property, and to our luck, there's the timeupdate event for media elements, which is described as follows:
The current playback position changed as part of normal playback or in an especially interesting way, for example discontinuously.
This concludes that you can simply listen on timeupdate and then check whether you've passed the mark:
// listen on the event
video.addEventListener("timeupdate", function(){
// check whether we have passed 5 minutes,
// current time is given in seconds
if(this.currentTime >= 5 * 60) {
// pause the playback
this.pause();
}
});
Keep in mind that this will pause whenever the user tries to skip past 5 minutes. If you want to allow skips and only initially pause the video beyond the 5 minute mark, either remove the event listener or introduce some kind of flag:
var pausing_function = function(){
if(this.currentTime >= 5 * 60) {
this.pause();
// remove the event listener after you paused the playback
this.removeEventListener("timeupdate",pausing_function);
}
};
video.addEventListener("timeupdate", pausing_function);
The timeupdate event is what you are looking for, but it only fires at about 2 fps which is too slow to stop at precise times.
For those cases I used requestAnimationFrame which fires at 60 fps and decreased the endTime a little which fixes small "lag hops":
const onTimeUpdate = () => {
if (video.currentTime >= (endTime - 0.05)) {
video.pause()
} else {
window.requestAnimationFrame(onTimeUpdate)
}
}
window.requestAnimationFrame(onTimeUpdate)
Not sure of an inbuilt way, but one way would be to use the setInterval Function and check the currentTime for the video and then stop playback
var myVid=document.getElementById("video1");
var timer= setInterval(function(){myTimer()},1000);
function myTimer()
{
if(myVid.currentTime == 5* 60)
{
myVid.pause();
window.clearInterval(timer);
}
}
So as below
<video id="myVid">
<source></source> <!--Whatever source here -->
</video>
using Above HTML attach an Event
var vid = document.getElementById("myVid");
vid.addEventListener("timeupdate", function(){
// Check you time here and
if(t >= 300000) //Where t = CurrentTime
{
vid.stop();// Stop the Video
}
});
This is the right way of doing it.

Flash Player stops animating when out of viewport

I have several flash movies on a site. They all loop through an animation which lasts for approximately 15 seconds (but this is not set in stone). They all begin to play at the same time and are in sync.
However, when I resize the browser window, or scroll the page, if a flash player instance is not in the viewport, it stops playing. When it returns to the viewport, it resumes and subsequently is out of sync with the other flash player instances.
I am under the impression this is a flash player optimisation. Is there anyway of disabling this behaviour, possibly through JS / AS3? It appears to happen in Firefox and Chrome on Windows.
UPDATE
Just to clarify. I have methodologies in place using local connection and ExternalInterface to sync the ads. What I am looking for is a method to disable the "optimisation" of FlashPlayer which results in the frame rate being drastically reduced.
You can't disable this feature. It's put in place to lower the memory and CPU use when the flash application isn't visible.
What is available for you though is something called the Throttle API. This is a dedicated API created to allow creators of Flash applications the ability to be notified exactly when their application is going to be slowed down/throttled.
Here's an example.
addEventListener(ThrottleEvent.THROTTLE, onThrottleEventHandler);
function onThrottleEventHandler(e:ThrottleEvent):void
{
if(e.state == ThrottleType.THROTTLE)
{
/**
* the player is about to be slowed down to a frame rate
* set in e.targetFrameRate
*/
}
else if(e.state == ThrottleType.PAUSE)
{
/**
* the player is going to be paused completely, AKA 0 fps
*/
}
else if(e.state == ThrottleType.RESUME)
{
/**
* the player is now back to normal as it has resumed
* from the paused or throttled state
*/
}
}
Now you can figure out a way that works best for you but my suggestion is to store the current time that has passed whenever being throttled or paused via:
currentTime = getTimer();
Then calculate how much time has passed once your application has resumed using:
passedTime = getTimer() - currentTime;
Then do what you like with this information.
Hopefully this has helped, should offer you a greater degree of control now that you're familiar with the Throttle API. For more information on it, check it out in the documentation here: ThrottleEvent
I belive this kind of behavior its normal, its kind of a bug of flash that has never been fixed.
I belive inha may got u a solution, but not on an enter_frame event. that just to brutal.
what I would do is:
create a timer event.. each X seconds.. so it will call a checkFunction,
in my checkFunction(). I would check if all my movieClips are syncronized.
and if I found 1 that is not.. ill put a simple gotoAndPlay(xFrame);
var aMovieClips:Array; //get all ur movieclips into this array.
var timeToCheck = 1000; //time to check for a unsincronized movieclip
var time:Timer=new Timer(timeToCheck,0);//do inifinite call each 1000 seconds
time.start();
time.addEventListener(TimerEvent.TIMER, checkFunction);
public function checkFunction(e:TimerEvent):void
{
var aCurrentFrames:Array;
for (var n:int = 0;n<aMovieClips.length();n++)
aCurrentFrames.push(aMovieClips[n].currentFrame);
//so now u have all current frames of all movie clips.. so u can add a
//gotoAndPlay(x); to any unsyncronized movieclip
}
If it is very important that each SWF progresses simultaneously, I would control it by JavaScript.
Assuming each SWF file is a simple MovieClip, something like this is how I would go about it:
Set the Document Class of each FLA file to this:
package {
import flash.display.MovieClip;
import flash.external.ExternalInterface;
public class ExternalInterfaceControlledMC extends MovieClip {
public function ExternalInterfaceControlledMC() {
this.stop();
if (ExternalInterface.available) {
try {
ExternalInterface.addCallback("setFrame", jsSetFrame);
} catch (error:Error) {
trace("An Error occurred:", error.message);
}
} else {
trace("ExternalInterface is not available");
}
}
private function jsSetFrame(value:String):void {
var frameNumber = int(value) % this.totalFrames;
this.gotoAndStop(frameNumber);
}
}
}
In the JavaScript, you would add a reference of each instance of the SWFs into an array, then use a timer to tell each SWF to progress to a frame number.
var swfElements; //Array
var currFrame = 1;
function onPageLoad() {
//Init timer
setInterval("progressFrame", 1000 / 30); //30 fps
}
function progressFrame() {
for (var i = 0; i < swfElements.length; i++) {
swfElements[i].setFrame(currFrame);
}
currFrame++;
}
Please beware that nothing about this code is tested and is only meant to be used to illustrate my train of thought.

Categories

Resources