web-dev-qa-db-ja.com

複数のデバイス間で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秒程度以内になりますが、もっとうまくやる必要があります。スライドショーが自動進行している場合、違いは本当に顕著です。

コードはすべて同じプラットフォームで実行されるため、ブラウザー間の互換性について心配する必要はありません。

これが私が何とかまとめたものです

何か案は?

23
Josh Hunt

別のアプローチはどうですか:誰が時間を気にしますか? (システムクロックをJavaScriptと確実に同期させることはできません。)

代わりに、 Node サーバーと socket.io を使用して、クライアントがスライドショーを進めるときに同期します。クライアントがいつ進むかを決定する代わりに、サーバーはクライアントに指示します。

このアプローチには、スライドショーの実行中に手動でスライドショーをいじることができるという追加のボーナスがあります。次の例では、Nextボタンを追加して、接続されているすべてのクライアントをすぐに次のスライドに進めます。

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

views/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>

これらの2つのファイルをフォルダーにコピーしてから、

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

いくつかの異なるウィンドウでhttp://localhost:70に移動し、魔法を見てください。

19
josh3736

リクエストを送信してからレスポンスを返すまでの経過時間を測定します。次に、その値を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ミリ秒の違いはあまり効果がないはずです。

22
MikeWyatt

あなたの質問に対する満足のいく答えを見つけてくれてうれしいです。私はブラウザをサーバーの時計と同期させる必要があり、あなたと同じように1秒以上の精度でそれを達成することを決心しました。私はこれを行うためのコードを書き、他の誰かが解決策を必要とする場合に備えて、この回答をここに投稿しています。

コードは ServerDate と呼ばれ、無料でダウンロードできます。これがREADMEの一部です。私の例では108ミリ秒の精度を達成したことに注意してください。

ServerDate関数またはそのインスタンスの1つを使用するのと同じように、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
11
David Braun

ここでは、リアルタイムWebアプリケーションにCOMETパターンを幅広く使用しています。

あなたの場合にそれを使用するには、クライアントがサーバーへのAJAXリクエストを開き、応答を待つ必要があります。それが来るとすぐに、クライアントはスライドを変更する必要があります。

サーバーでは、スライドを変更するときまで、すべての回答を保留する必要があります。 (クライアントでそれぞれ同じ時間より進んで後で遅らせることもできますが、それはおそらく必要ではありません)。何が利用できるかわからないため、ここではそのサンプルコードを示すことはできません。

つまり、サーバーが指揮者を演じ、すべてのクライアントが指揮者の話を聞くオーケストラを効果的に作成していることになります。

次に、タイミングは、サーバーが(ほぼ)同時に要求に応答する能力とネットワーク遅延によって決定されます。
通常、クライアントはネットワークの同じ部分にある必要があるため、遅延は非常に似ている可能性があります。絶対値はここでは問題ではなく、変動のみです。

また、追加のトリックが役立つ場合もあります。ハードな交換でスライドを変更するのではなく、ブレンドします。これにより、変更がぼやけて、常に発生するわずかなタイミングの違いを目で捉えることができなくなります。

(サーバーにコンダクターを再生させることができない場合は、MikeWyattによるソリューションを使用する必要があります-ネットワーク設定に応じて、おそらくいくつかのリクエストと結果の平均化が必要です。LANでは1つのリクエストで十分かもしれません。フルインターネット上では、平均化を少し超えても問題はありません...)

0
Chris

サーバーと実際に同期することはできません。サーバーリクエストに必要な時間を測定することは(MikeWyattが提案したように)、レイテンシーの良い指標ではありません。

サーバーがいつリクエストに応答するかを知っているのはサーバーだけです。したがって、その情報を回答とともに返送する必要があります。 Date.now() - new Date(timestringOfServerResponse)を使用すると、レイテンシを正確に測定できます。しかし、なぜその値が必要になるのかわかりません。

複数のデバイス間でアプリを同期するには、サーバーはどのアクションをいつ実行するかをデバイスに送信する必要があります。 「いつ」は「応答を受け取ったらすぐに」ではなく、正確なタイムスタンプである必要があります。デバイスのシステムクロックが正確で同期されている限り(通常は同期されています)、アプリはメソッドを同期して実行します。これは、いつ何が起こるか(または少なくとも:そのとき何が起こったのか)を認識しており、何を補間できるためです。 「今」起こること)。

0
Bergi