Подтвердить что ты не робот

Синхронизация времени JS между несколькими устройствами

Я использую замечательную библиотеку 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 или около секунды секунд, мне нужно сделать лучше. Разница действительно заметна, когда слайд-шоу автоматически продвигается вперед.

Код будет работать на одной платформе, поэтому совместимость с кросс-браузером не о чем беспокоиться.

Вот что мне удалось собрать

Любые идеи?

4b9b3361

Ответ 1

Как насчет другого подхода: кто заботится о времени? (Вы не будете надежно синхронизировать системные часы с JavaScript.)

Вместо этого используйте сервер Node с socket.io для синхронизации, когда ваши клиенты продвигают слайд-шоу. Вместо того, чтобы клиенты решали, когда нужно продвигаться, сервер сообщает им.

Этот подход имеет дополнительный бонус, позволяющий вручную возиться со слайд-шоу во время его работы. В следующем примере я добавил кнопку "Далее", которая заставит всех подключенных клиентов немедленно перейти к следующему слайду.

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

Измерьте прошедшее время между отправкой запроса и возвратом ответа. Затем разделите это значение на 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);
}

Интересно, что у Джона Ресига хорошая статья о точности времени Javascript.
Это не должно вызывать проблемы в этом случае, так как вы беспокоитесь о том, что ваше время отключено на ~ 1 секунду. Разница в 15 мс не должна иметь большого эффекта.

Ответ 3

Я рад, что вы нашли удовлетворительный ответ на свой вопрос. У меня была аналогичная потребность в синхронизации браузера с серверными часами, и он решил добиться этого с точностью более 1 секунды, как и вы. Я написал код, чтобы сделать это, и опубликую этот ответ здесь, если кому-то еще нужно решение.

Код называется ServerDate и свободно доступен для загрузки. Здесь часть README. Обратите внимание, что в моем примере я достиг точности 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

Ответ 4

Вы не можете синхронизировать с сервером. Измерение времени, которое требует ваш запрос на сервер (как предположил Майк Уиатт), не является хорошим индикатором задержки.

Только ваш сервер знает, когда он отвечает на запрос. Поэтому он должен отправить эту информацию обратно с ответом. С помощью Date.now() - new Date(timestringOfServerResponse) вы можете точно измерить задержку. Но я не уверен, зачем вам нужна эта ценность.

Чтобы синхронизировать приложение между несколькими устройствами, сервер должен отправить им какое действие для выполнения, когда. "Когда" не должно быть "как только вы получите мой ответ", но точную метку времени. Поскольку системные часы ваших устройств точны и синхронизированы (они обычно есть), приложение будет запускать свои методы синхронно, потому что оно знает, что произойдет, когда (или, по крайней мере: что должно было произойти тогда, и оно может интерполировать то, что "сейчас" ).

Ответ 5

Я широко использую шаблон COMET для моего веб-приложения в реальном времени.

Чтобы использовать это в своем случае, вам потребуются клиенты, чтобы открыть запрос AJAX на сервер и дождаться ответа. Как только клиент приходит на смену слайдам.

На сервере вам нужно сдержать все ответы, пока не настанет время для изменения слайдов. (Вы могли бы быть более продвинутыми и задерживаться потом на клиенте за одно и то же время, но это, скорее всего, не нужно). Я не могу показать вам пример кода для этого здесь, поскольку я не знаю, что доступно вам.

Итак, вы эффективно создаете оркестр, где сервер играет проводника, и все его слушают.

Затем время определяется способностью сервера отвечать на запросы (почти) в то же время плюс латентность сети.
Обычно клиенты должны находиться в одной и той же части сети, поэтому латентность может быть очень похожей - и абсолютная ценность здесь не повредит, только вариация.

И также может быть полезен дополнительный трюк: не меняйте слайды с жесткой заменой, смешайте их. Это размывает изменение, так что глаз не сможет уловить небольшие временные различия, которые вы всегда будете иметь.

(Если вы не можете подключить проводник к серверу, вам, вероятно, придется использовать решение MikeWyatt - возможно, с несколькими запросами и усреднением результата в зависимости от настройки сети. В локальной сети может быть один запрос достаточно, перейдя через весь интернет немного по усреднению, не повредит...)