27

我正在使用美妙的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 秒左右的准确度内获得准确率,但我需要做得更好。当幻灯片自动前进时,差异非常明显。

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

这是我设法整理的内容

有任何想法吗?

4

5 回答 5

25

测量发送请求和返回响应之间经过的时间。然后,将该值除以 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 计时准确性的好文章
在这种情况下,它不应该引起问题,因为您只关心您的时间会减少约 1 秒。15 毫秒的差异应该不会有太大影响。

于 2012-05-14T15:17:08.890 回答
21

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

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

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

应用程序.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

该文件被处理为一个模板。

<!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几个不同的窗口,然后看到魔术。

于 2012-05-19T04:16:57.790 回答
11

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

该代码称为ServerDate,可免费下载。这是自述文件的一部分。请注意,在我的示例中,我达到了 108 毫秒的精度:

您可以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
于 2012-08-14T15:57:29.073 回答
0

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

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

在服务器上,您必须保留所有答案,直到需要更换幻灯片。(您可以更先进,之后每次在客户端上延迟相同的时间,但这很可能没有必要)。我无法在这里向您展示示例代码,因为我不知道您可以使用什么。

因此,您实际上是在创建一个管弦乐队,其中服务器播放指挥,所有客户端都在听他。

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

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

(如果您不能让服务器播放指挥,您可能不得不使用 MikeWyatt 的解决方案 - 可能有几个请求并平均结果,具体取决于网络设置。在 LAN 中,一个请求可能就足够了,去在整个互联网上,平均水平有点过分不会受到伤害......)

于 2012-05-19T12:20:38.770 回答
0

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

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

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

于 2012-05-14T23:14:49.710 回答