在多个设备之间同步JS时间

时间:2012-05-14 15:02:27

标签: javascript time synchronization

我正在使用精彩的reveal.js库来创建HTML幻灯片。我唯一的问题是我需要它来跨多个设备进行同步。

目前我正在从服务器发出一个AJAX请求,并保留页面的内部时钟。

function syncTime() {
    // Set up our time object, synced by the HTTP DATE header
    // Fetch the page over JS to get just the headers
    console.log("syncing time")
    var r = new XMLHttpRequest();
    r.open('HEAD', document.location, false);
    r.send(null);
    var timestring = r.getResponseHeader("DATE");

    systemtime = new Date(timestring); // Set the time to the date sent from the server
}

虽然这让我在1秒左右的准确度,但我需要做得更好。幻灯片显示自动前进时,差异非常明显。

代码将在同一平台上运行,因此无需担心跨浏览器兼容性。

Here's what I've managed to put together

有什么想法吗?

5 个答案:

答案 0 :(得分:22)

衡量发送请求和取回响应之间经过的时间。然后,将该值除以2.这为您提供了单向延迟的粗略值。如果将其添加到服务器的时间值,您将更接近真实的服务器时间。

这样的事情应该有效:

function syncTime() {
    // Set up our time object, synced by the HTTP DATE header
    // Fetch the page over JS to get just the headers
    console.log("syncing time")
    var r = new XMLHttpRequest();
    var start = (new Date).getTime();

    r.open('HEAD', document.location, false);
    r.onreadystatechange = function()
    {
        if (r.readyState != 4)
        {
            return;
        }
        var latency = (new Date).getTime() - start;
        var timestring = r.getResponseHeader("DATE");

        // Set the time to the **slightly old** date sent from the 
        // server, then adjust it to a good estimate of what the
        // server time is **right now**.
        systemtime = new Date(timestring);
        systemtime.setMilliseconds(systemtime.getMilliseconds() + (latency / 2))
    };
    r.send(null);
}

有趣的是:John Resig对Javascript计时的准确性有good article
  在这种情况下它不应该引起问题,因为你只关心你的休息时间约1秒钟。 15毫秒的差异不应该产生太大影响。

答案 1 :(得分:19)

另一种方法怎么样:谁在乎时间? (你不能用JavaScript可靠地同步系统时钟。)

相反,当客户推进幻灯片放映时,使用Node服务器与socket.io同步。而不是客户决定何时前进,服务器告诉他们。

这种方法带来了额外的好处,即能够在幻灯片播放时手动摆弄幻灯片。在下面的示例中,我添加了一个 Next 按钮,该按钮会使所有连接的客户端立即前进到下一张幻灯片。

app.js

var express = require('express')
    , app = express.createServer()
    , io = require('socket.io').listen(app)
    , doT = require('dot')
    , slide = 0
    , slides = [
        'http://placekitten.com/700/400?image=13',
        'http://placekitten.com/700/400?image=14',
        'http://placekitten.com/700/400?image=15',
        'http://placekitten.com/700/400?image=16',
        'http://placekitten.com/700/400?image=1',
        'http://placekitten.com/700/400?image=2',
        'http://placekitten.com/700/400?image=3',
        'http://placekitten.com/700/400?image=4',
        'http://placekitten.com/700/400?image=5',
        'http://placekitten.com/700/400?image=6',
        'http://placekitten.com/700/400?image=7',
        'http://placekitten.com/700/400?image=8',
        'http://placekitten.com/700/400?image=9',
        'http://placekitten.com/700/400?image=10',
        'http://placekitten.com/700/400?image=11',
        'http://placekitten.com/700/400?image=12',
    ];

app.listen(70); // listen on port 70

app.register('.html', doT); // use doT to render templates
app.set('view options', {layout:false}); // keep it simple
doT.templateSettings.strip=false; // don't strip line endings from template file

app.get('/', function(req, res) {
    res.render('index.html', { slide: slide, slides: slides });
});

app.post('/next', function(req, res) {
    next();
    res.send(204); // No Content
});

setInterval(next, 4000); // advance slides every 4 seconds

function next() {
    if (++slide >= slides.length) slide = 0;
    io.sockets.emit('slide', slide);
}

的观点/ index.html中

此文件将作为doT模板处理。

<!DOCTYPE html>
<html>
<head>
<title>Synchronized Slideshow</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
var curslide = {{=it.slide}}; // the slide the server is currently on.

$(function() {
    $('#slide' + curslide).css('left',0);

    $('#next').click(function() {
        $.post('/next');
    });
});

var socket = io.connect('http://localhost:70');
socket.on('slide', function(slide) {
    $('#slide' + curslide).animate({left:-700}, 400);
    $('#slide' + slide).css('left',700).show().animate({left:0}, 400);
    curslide = slide;
});
</script>
<style>
#slideshow, .slide { width:700px; height:400px; overflow:hidden; position:relative; }
.slide { position:absolute; top:0px; left:700px; }
</style>
</head>
<body>
    <div id="slideshow">
        {{~it.slides :url:i}}
            <div id="slide{{=i}}" class="slide"><img src="{{=url}}"></div>
        {{~}}
    </div>
    <button id="next">Next &gt;</button>
</body>
</html>

将这两个文件复制到一个文件夹中,然后运行

$ npm install express socket.io dot
$ node app.js

并在几个不同的窗口中导航到http://localhost:70,然后看看魔术。

答案 2 :(得分:11)

我很高兴你找到了一个满意的答案。我有类似的需要将浏览器与服务器的时钟同步,并决心以超过1秒的精度实现它,就像你一样。我已编写代码来执行此操作,并在此处发布此答案,以防其他人也需要解决方案。

代码名为ServerDate,可免费下载。这是自述文件的一部分。请注意,我在我的示例中实现了108 ms的精度:

您可以像使用ServerDate函数或其中一个函数一样使用Date 实例,例如:

> ServerDate()
"Mon Aug 13 2012 20:26:34 GMT-0300 (ART)"

> ServerDate.now()
1344900478753

> ServerDate.getMilliseconds()
22

还有一种新方法可以获得ServerDate估计的精确度 服务器的时钟(以毫秒为单位):

> ServerDate.toLocaleString() + " ± " + ServerDate.getPrecision() + " ms"
"Tue Aug 14 01:01:49 2012 ± 108 ms"

您可以看到服务器时钟和浏览器时钟之间的差异,以毫秒为单位:

> ServerDate - new Date()
39

答案 3 :(得分:0)

您无法真正与服务器同步。衡量服务器请求所需的时间(正如MikeWyatt建议的那样)并不是延迟的良好指标。

只有您的服务器知道他何时回复请求。因此,它应该将该信息与答案一起发回。使用Date.now() - new Date(timestringOfServerResponse),您可以准确地测量延迟。但我不确定你为什么需要那个价值。

要在多个设备之间同步应用程序,服务器应该向他们发送要执行的操作。 “何时”不应该“一旦得到我的回复”,而是一个确切的时间戳。至于你的设备的系统时钟是准确和同步的(它们通常是),应用程序将同步运行它的方法,因为它知道什么时候发生(或至少:那时应该发生什么,它可以插入什么发生“现在”)。

答案 4 :(得分:0)

我在这里广泛使用COMET模式用于我的实时Web应用程序。

要在您的情况下使用它,您需要客户端向服务器打开AJAX请求并等待答案。一旦它到来,客户必须更换幻灯片。

在服务器上,您必须保留所有答案,直到更改幻灯片为止。 (你可能会更高级,并在客户端上延迟相同的时间,但这很可能没有必要)。我不能在这里向您展示示例代码,因为我不知道您可以使用哪些代码。

因此,您正在有效地创建一个管弦乐队,其中服务器扮演指挥,所有客户都在倾听他。

然后,时间由服务器在(几乎)同时响应请求以及网络延迟的能力决定。
通常客户端应该位于网络的同一部分,因此延迟可能非常相似 - 绝对值在这里不会受到影响,只有变化。

还有一个额外的技巧可以帮助:不要用硬替换来改变幻灯片,混合它们。这将模糊变化,使眼睛无法捕捉到您将永远存在的微小时间差异。

(如果您不能让服务器播放指挥,您可能不得不使用MikeWyatt的解决方案 - 可能需要一些请求并平均结果,具体取决于网络设置。在LAN中,一个请求可能是足够的,超过平均水平超过平均值不会伤害...)