从不准确的时钟中获取更精确的时钟

时间:2013-12-03 01:14:09

标签: javascript

问题

我需要能够尽可能地将一些JavaScript事件同步到YouTube视频中的特定时间。虽然我知道有一些limitations to how accurate timers can be in browsers,但我认为应该可以做得比我从视频播放器获得的更好。我在开始播放YouTube视频时使用了以下代码。

startTime = new Date();
setInterval(function () {
    samples.push({player: player.getCurrentTime(), jstime: new Date() - startTime});
}, 20);

此代码获取视频播放器认为与player.getCurrentTime()处于同一时间的当前时间,并将其与常规时钟时间一起记录到播放开始时间。以下结果可让您了解准确性:

{"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},

在网上进行的一些挖掘表明,YouTube视频计时器的准确性直接来自底层播放器(在我的情况下通常是HTML5),并且不应该依赖于更精确的几百毫秒。

可能的解决方案

一些观察/假设(以及一些明显的说明):

  • player.getCurrentTime()检索的时间将接近不变的时间。
  • 玩家时间过去的速度应该与墙上的时钟几乎相同。 (虽然视频帧通常与音频时钟相关,但机器之间的音频时钟频率会有几赫兹的变化,但它可能会略微漂移。)
  • 如果我观察两个时钟,我应该能够确定它们之间的速率差异(应该接近0)。

一旦知道了错误率并且随着时间的推移采样,就应该可以得到一个接近两者中最准确的定时器(提供给JavaScript的时钟)的准确度的定时器。这个假设是否正确?

如何实施?

考虑到播放器时间和JavaScript时钟播放时间的输入,我如何得到一个可以调用每个动画帧的计时器,以便为我提供最高精度?

这样的派生时间有多精确?

2 个答案:

答案 0 :(得分:1)

我之前已经做过这样的事情(诚然使用ActionScript并以AVM为目标),但原则仍然存在。

不要依赖浏览器(或应用)来获取时间信息。

想一想,在视频方面稍微缓冲一下,或偶尔使用沙滩球/旋转小时玻璃,突然间你的同步是错误的。

您要做的是挂钩媒体播放器事件,然后对视频时间作出反应。

W3 HTML5视频演示页面[http://www.w3.org/2010/05/video/mediaevents.html]显示了一大堆属性和值 - 我认为您想要查看的是timeUpdate事件和currentTime属性。

答案 1 :(得分:0)

我想我已经想出如何实现这一目标。现在有点乱,但你明白了。

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;
    }
}