2012-05-14 16 views
22

Estoy usando la maravillosa biblioteca reveal.js para crear una presentación de diapositivas HTML. Mi único problema es que lo necesito para sincronizar en varios dispositivos.sincronizar el tiempo JS entre varios dispositivos

Por el momento estoy haciendo una solicitud AJAX al tiempo del servidor y tengo un reloj interno para la página.

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 
} 

Si bien esto me da 1 o más segundos de precisión, necesito hacerlo mejor. La diferencia es realmente notable cuando la presentación de diapositivas avanza automáticamente.

El código se ejecutará en la misma plataforma, por lo que no hay que preocuparse por la compatibilidad entre navegadores.

Here's what I've managed to put together

¿Alguna idea?

Respuesta

18

¿Qué tal un enfoque diferente: a quién le importa el tiempo? (No va a sincronizar de manera confiable el reloj del sistema con JavaScript.)

En su lugar, utilice un servidor Node con socket.io para sincronizar cuando sus clientes avancen la presentación de diapositivas. En lugar de que los clientes decidan cuándo avanzar, el servidor les dice que lo hagan.

Este enfoque viene con la ventaja añadida de poder manipular manualmente la presentación de diapositivas mientras se está ejecutando. En el ejemplo que sigue, he agregado un botón Next que hace que todos los clientes conectados avancen inmediatamente a la siguiente diapositiva.

aplicación.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); 
} 

vistas/index.html

Este archivo se procesa como una plantilla 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> 

copia estos dos archivos en una carpeta, a continuación, ejecute

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

y vaya a http://localhost:70 en varias ventanas diferentes, a continuación, ver la magia.

+0

¡Me encanta esta respuesta! Ignorando mi pregunta para llegar al mismo objetivo. Voy a tomar este enfoque y ver qué puedo sacar de eso. –

+0

¡Y felicitaciones por el enlace de demostración! –

+0

Tengo un par de errores al ejecutar esto con la última versión de Express. Solo cuando lo fijé en la versión 2.5.10 y lo reinstalé, funcionó. Además, tuve que ejecutar 'sudo node app.js' – skube

21

Mida el tiempo transcurrido entre el envío de la solicitud y el regreso de la respuesta. Luego, divide ese valor por 2. Eso te da un valor aproximado de latencia unidireccional. Si agrega eso al valor de tiempo del servidor, estará más cerca de la hora verdadera del servidor.

Algo como esto debería funcionar:

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

aparte interesante: John Resig tiene una good article sobre la exactitud de la sincronización Javascript.
En este caso, no debería causar un problema, ya que solo le preocupa que su tiempo se apague en ~ 1 segundo. Una diferencia de 15 ms no debería tener mucho efecto.

+0

Una respuesta increíble! Estoy bastante seguro de que esta es la mejor solución disponible, pero voy a agregar una recompensa para ver si puedo atraer otras soluciones. –

+4

Así es como lo haría. La única diferencia es que haría una llamada de sincronización varias veces (es decir, 10 veces) y luego utilizaré el tiempo de sistema de la llamada con la latencia más baja. De hecho, cuanto mayor sea la latencia, mayor será el impacto del hecho de que la distribución entre el tiempo del cliente al servidor y el del servidor al cliente no es exactamente 1: 1. –

+0

¿Existe una mejor opción que dividir el tiempo de respuesta entre 2? En algunas de mis pruebas el servidor bloquea por un tiempo pero luego regresa rápidamente. Así que tal vez el tiempo de viaje fue de 200 ms, pero el tiempo devuelto es de solo 20 ms. ¿Tiene sentido? – jocull

0

No se puede sincronizar realmente con el servidor. Medir el tiempo que necesita su servidor (como sugirió MikeWyatt) no es un buen indicador de la latencia.

Solo su servidor sabe cuándo responde una solicitud. Por lo tanto, debe enviar esa información con la respuesta. Con Date.now() - new Date(timestringOfServerResponse) puede medir la latencia exactamente. Sin embargo, no estoy seguro de por qué necesitarías ese valor.

Para sincronizar una aplicación entre múltiples dispositivos, el servidor debe enviarles qué acción realizar cuando. El "cuándo" no debería ser "tan pronto como reciba mi respuesta", sino una marca de tiempo exacta. En cuanto a que los relojes del sistema de sus dispositivos son precisos y sincronizados (generalmente lo son), la aplicación ejecutará sus métodos de forma sincronizada, porque sabe qué suceder cuando (o al menos: lo que debería haber sucedido en ese momento, y puede interpolar suceder "ahora").

+0

Así es exactamente como está funcionando esto. La página espera hasta la hora correcta antes de realizar una acción para asegurarse de que esté sincronizada en todos los dispositivos. El problema se reduce al hecho de que tengo dos iPads uno al lado del otro que están configurados para sincronizar el tiempo con los servidores de tiempo, pero están separados por 1 segundo. –

+0

¿Desea implementar un servidor horario en javascript para sincronizar "tiempos de sistema personalizados"? – Bergi

+2

@Bergi, está asumiendo erróneamente que el reloj del cliente coincide con el reloj del servidor. Prácticamente se garantiza que serán diferentes, probablemente por varios segundos o minutos. Ajustarse a la latencia no es perfecto, pero te acercará bastante. En el peor de los casos, se perderá el tiempo completo de ida y vuelta, si uno de los viajes fue instantáneo. – MikeWyatt

0

Estoy usando extensamente el patrón COMET aquí para mi aplicación web en tiempo real.

Para usar eso en su caso, necesitaría que los clientes abran una solicitud de AJAX al servidor y esperen una respuesta. Tan pronto como se presente, el cliente debe cambiar las diapositivas.

En el servidor, debe retener todas las respuestas hasta que sea el momento de cambiar las diapositivas. (Usted podría estar más avanzado y retrasar después en el cliente por el mismo tiempo cada uno, pero eso probablemente no sea necesario). No puedo mostrar el código de muestra aquí porque no sé qué hay disponible para ti.

Así que está creando una orquesta en la que el servidor es el conductor y todos los clientes lo están escuchando.

El tiempo se determina por la capacidad del servidor para responder a las solicitudes en (casi) el mismo tiempo más la latencia de la red.
Normalmente los clientes deben estar en la misma parte de la red, por lo que la latencia puede ser muy similar, y el valor absoluto no duele aquí, solo la variación.

Y también podría haber un truco adicional para ayudar: no cambie las diapositivas con un reemplazo duro, mezcle. Esto borrará el cambio para que el ojo no pueda captar las pequeñas diferencias de tiempo que siempre tendrás.

(Si no puede tener el servidor interpretando al conductor, es probable que tenga que usar la solución de MikeWyatt, probablemente con unas pocas solicitudes y promediando el resultado, dependiendo de la configuración de la red. basta, ir a través de Internet un poco sobre el promedio no va a doler ...)

10

Me alegro de que hayas encontrado una respuesta satisfactoria a tu pregunta. Tenía una necesidad similar de sincronizar el navegador con el reloj del servidor y estaba decidido a lograrlo con una precisión superior a 1 segundo. He escrito el código para hacer esto y estoy publicando esta respuesta aquí en caso de que alguien más también necesite la solución. El código se llama ServerDate y está disponible para su descarga. Aquí hay una parte del README. Nótese que he conseguido 108 ms de precisión en mi ejemplo:

Puede utilizar ServerDate de igual modo que la función Date o uno de sus casos, por ejemplo,:

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

> ServerDate.now() 
1344900478753 

> ServerDate.getMilliseconds() 
22 

También hay un nuevo método para obtener la precisión de la estimación del reloj del servidor de ServerDate (en milisegundos):

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

Se puede ver la diferencia entre el reloj del servidor y los navegadores reloj, en milisegundos:

> ServerDate - new Date() 
39 
Cuestiones relacionadas