2012-05-14 13 views
22

Używam wspaniałej biblioteki reveal.js do tworzenia pokazu slajdów HTML. Moim jedynym problemem jest to, że potrzebuję go do synchronizacji na wielu urządzeniach.Synchronizacja czasu JS między wieloma urządzeniami

W tej chwili wysyłam żądanie AJAX do czasu z serwera i utrzymuję wewnętrzny zegar dla strony.

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 
} 

Podczas gdy to pozwala mi osiągnąć dokładność co najmniej 1 sekundy, muszę zrobić to lepiej. Różnica jest bardzo zauważalna, gdy pokaz slajdów rozwija się automatycznie.

Kod będzie działał wszystkie na tej samej platformie, więc zgodność z różnymi przeglądarkami nie ma się czym martwić.

Here's what I've managed to put together

Jakieś pomysły?

Odpowiedz

18

Co powiesz na inne podejście: kogo obchodzi czas? (Nie można niezawodnie synchronizować zegara systemowego z JavaScriptem.)

Zamiast tego należy użyć serwera Node z socket.io, aby zsynchronizować, kiedy klienci przesuwają pokaz slajdów. Zamiast klientów decydujących, kiedy przejść, serwer mówi im, aby to zrobili.

To podejście ma dodatkową zaletę, że można ręcznie obsługiwać pokaz slajdów podczas jego pracy. W poniższym przykładzie dodałem przycisk Next, który powoduje, że wszyscy podłączeni klienci natychmiast przechodzą do następnego slajdu.

aplikacja.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); 
} 

odsłon/index.html

Ten plik jest przetwarzany jako doT szablonu.

<!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> 

skopiować te dwa pliki do folderu, a następnie uruchomić

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

i przejdź do http://localhost:70 w kilku różnych oknach, a następnie zobaczyć magię.

+0

Uwielbiam tę odpowiedź! Ignorując moje pytanie, aby osiągnąć ten sam cel. Zamierzam przyjąć to podejście i zobaczyć, co mogę z tego wyciągnąć! –

+0

I kudos za link demo! –

+0

Mam kilka błędów z tym związanych z najnowszą wersją Express. Dopiero po przypięciu go do wersji 2.5.10 i ponownemu zainstalowaniu działało. Musiałem również uruchomić 'sudo node app.js' – skube

21

Zmierz czas, jaki upłynął od wysłania żądania do otrzymania odpowiedzi. Następnie podziel tę wartość przez 2. Daje to przybliżoną wartość opóźnienia w jedną stronę. Jeśli dodasz to do wartości czasu z serwera, będziesz bliżej prawdziwego czasu serwera.

Coś jak to powinno działać:

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

Ciekawe bok: John Resig ma good article na dokładność Javascript czasu.
Nie powinno to powodować problemów w tym przypadku, ponieważ martwisz się tylko o czas wyłączenia o ~ 1 sekundę. Różnica 15 ms nie powinna mieć dużego wpływu.

+0

Zadziwiająca odpowiedź! Jestem pewien, że jest to najlepsze dostępne rozwiązanie, ale zamierzam dodać nagrodę, aby sprawdzić, czy mogę przyciągnąć inne rozwiązania. –

+4

Tak to zrobię. Jedyna różnica polega na tym, że wykonam połączenie synchronizacyjne kilka razy (to jest 10 razy), a następnie wykorzystam czas systemowy z połączenia z najniższym opóźnieniem. Rzeczywiście, im większe opóźnienie, tym większy wpływ ma fakt, że rozkład między czasem między klientem a serwerem i czasem między serwerem a klientem nie wynosi dokładnie 1: 1. –

+0

Czy jest lepsza opcja niż dzielenie czasu odpowiedzi przez 2? W niektórych moich testach serwer blokuje się na chwilę, ale potem szybko wraca. Być może czas podróży wyniósł 200 ms, ale zwrócony czas to tylko 20 ms. Czy to ma sens? – jocull

0

Nie można naprawdę zsynchronizować się z serwerem. Pomiar czasu wymaganego przez serwer (zgodnie z sugestią MikeWyatt) nie jest dobrym wskaźnikiem opóźnienia.

Tylko twój serwer wie, kiedy odpowiada na żądanie. Dlatego powinien wysłać tę informację z powrotem z odpowiedzią. Dzięki Date.now() - new Date(timestringOfServerResponse) można dokładnie zmierzyć opóźnienie. Jednak nie jestem pewien, dlaczego potrzebowałbyś tej wartości.

Aby zsynchronizować aplikację między wieloma urządzeniami, serwer powinien wysłać do niej informację, które działanie należy wykonać, kiedy. "Kiedy" nie powinno być "jak tylko otrzymam moją odpowiedź", ale dokładny znacznik czasu. O ile zegary systemowe twoich urządzeń są dokładne i zsynchronizowane (zwykle są), aplikacja będzie uruchamiać swoje metody synchronicznie, ponieważ wie, co się stanie, kiedy (a przynajmniej: co wtedy powinno się stać, i może interpolować to, co zdarzyć się "teraz").

+0

Dokładnie to działa. Strona czeka przed właściwym czasem, aby upewnić się, że jest synchronizowana na różnych urządzeniach. Problem polega na tym, że mam obok siebie dwa iPady, które są ustawione na synchronizację czasu z serwerami czasu, ale są oddalone o 1 sekundę. –

+0

Więc chcesz zaimplementować serwer czasu w javascript, aby zsynchronizować "niestandardowe czasy systemowe"? – Bergi

+2

@Bergi, błędnie zakładasz, że zegar klienta pasuje do zegara na serwerze. Są praktycznie gwarantowane, że będą różne, prawdopodobnie przez kilka sekund lub minut. Dostosowanie się do opóźnień nie jest idealne, ale będzie ci bardzo blisko. W najgorszym przypadku będziesz miał wolne cały czas podróży w obie strony, jeśli jedna z podróży była natychmiastowa. – MikeWyatt

0

W dużym stopniu używam tutaj wzoru COMET dla mojej aplikacji internetowej w czasie rzeczywistym.

Aby użyć tego w twoim przypadku, potrzebujesz klientów, aby otworzyć żądanie AJAX na serwerze i czekać na odpowiedź. Tak szybko, jak to możliwe, klient musi zmienić slajdy.

Na serwerze musisz wstrzymać wszystkie odpowiedzi, aż nadejdzie czas zmiany slajdów. (Możesz być bardziej zaawansowany i później opóźnić klienta na ten sam czas, ale to najprawdopodobniej nie jest konieczne). Nie mogę tu pokazać przykładowego kodu, ponieważ nie wiem, co jest dla ciebie dostępne.

Skutecznie tworzysz orkiestrę, w której serwer gra dyrygenta, a wszyscy klienci go słuchają.

Czas jest określany na podstawie zdolności serwera do odpowiedzi na żądania w czasie (prawie) w tym samym czasie oraz opóźnienia sieci.
Zwykle klienci powinni znajdować się w tej samej części sieci, aby opóźnienie mogło być bardzo podobne - a wartość bezwzględna tutaj nie szkodzi, tylko wariancja.

Może być również dodatkowa sztuczka pomagająca: nie zmieniaj slajdów przy pomocy twardego zamiennika, wymieszaj je. Spowoduje to rozmycie zmiany, tak aby oko nie mogło złapać małych różnic czasowych, które zawsze będziesz mieć.

(Jeśli nie możesz korzystać z konwertera na serwerze, prawdopodobnie będziesz musiał użyć rozwiązania MikeWyatt - prawdopodobnie z kilkoma żądaniami i uśredniając wynik, w zależności od konfiguracji sieci. wystarczy, przejechanie całego internetu trochę ponad uśrednianie nie zaszkodzi ...)

10

Cieszę się, że znalazłeś zadowalającą odpowiedź na swoje pytanie. Miałem podobną potrzebę zsynchronizowania przeglądarki z zegarem serwera i postanowiłem ją osiągnąć z dokładnością lepszą niż 1 sekunda, tak jak Ty. Napisałem kod, aby to zrobić i publikuję tę odpowiedź tutaj, na wypadek, gdyby ktoś inny również potrzebował tego rozwiązania.

Kod nosi nazwę ServerDate i jest swobodnie dostępny do pobrania. Oto część README. Zauważ, że mogę osiągnąć precyzję 108 ms w moim przykładzie:

Można użyć ServerDate jak można użyć funkcji Date lub jedną z jej przypadkach, na przykład:

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

> ServerDate.now() 
1344900478753 

> ServerDate.getMilliseconds() 
22 

Jest też nowa metoda, aby uzyskać precyzję oszacowania ServerDate za zegar serwera (w milisekundach):

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

Widać różnicę między zegarem serwera i przeglądarek zegar, w milisekundach:

> ServerDate - new Date() 
39 
Powiązane problemy