Синхронизация времени JS между несколькими устройствами
Я использую замечательную библиотеку reve.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
}
Хотя это дает мне около одной секунды точности, мне нужно сделать лучше. Разница действительно заметна, когда слайд-шоу происходит автоматически.
Код будет работать на одной и той же платформе, поэтому не стоит беспокоиться о совместимости между браузерами.
Есть идеи?
5 ответов
Как насчет другого подхода: кого волнует время? (Вы не собираетесь надежно синхронизировать системные часы с JavaScript.)
Вместо этого используйте Node- сервер с http://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 ></button>
</body>
</html>
Скопируйте эти два файла в папку, затем запустите
$ npm install express socket.io dot
$ node app.js
и перейдите к http://localhost:70
в нескольких разных окнах, затем увидите магию.
Измерьте время, прошедшее между отправкой запроса и получением ответа. Затем разделите это значение на 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 мс не должна иметь большого эффекта.
Я рад, что вы нашли удовлетворительный ответ на свой вопрос. У меня была аналогичная необходимость синхронизировать браузер с часами сервера, и я решил добиться этого с точностью лучше 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
Я широко использую здесь шаблон COMET для своего веб-приложения в реальном времени.
Чтобы использовать это в вашем случае, вам нужно, чтобы клиенты открывали AJAX-запрос к серверу и ждали ответа. Как только он приходит, клиент должен сменить слайды.
На сервере вы должны отложить все ответы, пока не пришло время менять слайды. (Вы можете быть более продвинутым и впоследствии откладывать на клиенте одно и то же время, но это, скорее всего, не обязательно). Я не могу показать вам пример кода для этого здесь, так как я не знаю, что доступно для вас.
Таким образом, вы эффективно создаете оркестр, в котором сервер играет дирижера, а все клиенты слушают его.
Время определяется в зависимости от способности сервера отвечать на запросы (почти) в одно и то же время плюс задержка в сети.
Обычно клиенты должны находиться в одной и той же части сети, поэтому задержка может быть очень похожей - и здесь абсолютное значение не влияет, только на вариацию.
Также может помочь дополнительная хитрость: не меняйте слайды жесткой заменой, смешивайте их. Это размывает изменения, так что глаз не сможет уловить небольшие временные различия, которые у вас всегда будут.
(Если у вас нет проводника, играющего на сервере, вам, вероятно, придется использовать решение от MikeWyatt - возможно, с несколькими запросами и усреднением результата, в зависимости от настроек сети. В локальной сети одного запроса может быть достаточно, по всему интернету немного по усреднению не повредит...)
Вы не можете синхронизироваться с сервером. Измерение времени, которое требуется вашему серверу (как предложил MikeWyatt), не является хорошим показателем задержки.
Только ваш сервер знает, когда он отвечает на запрос. Поэтому он должен отправить эту информацию обратно с ответом. С Date.now() - new Date(timestringOfServerResponse)
Вы можете точно измерить задержку. Тем не менее, я не уверен, зачем вам эта ценность.
Чтобы синхронизировать приложение между несколькими устройствами, сервер должен отправить им, какое действие выполнить когда. "Когда" не должно быть "как только вы получите мой ответ", но точная временная метка. Поскольку системные часы ваших устройств точны и синхронизированы (как правило, они есть), приложение будет запускать свои методы синхронно, потому что оно знает, что произойдет, когда (или, по крайней мере, что должно было произойти, и может интерполировать произойти "сейчас").