javascriptnode.jssynchronizationmp3

How to synchronize a music player to multiple clients in NodeJS


i've written a script to basicly synchronize a song to multiple clients. The problem is, client 1 will always be a tiny bit further into the song than client 2.

when client 1 is playing a song, and client 2 presses a button, client 1 sends the current time to client 2 so that client 2 can pick up the time and play the mp3.

I have tried to pre-load the mp3, so that the client 2 wont lose time loading the file when the song starts. Yet it is still +- 0.5seconds too late.

//Client

$(document).ready(function() {
    var currentId = "";

    document.getElementById("audio").load();

    $.ajax({
        url: '/retrieveId',
        type: 'post',
        success: function(data) {
        currentId = data;
        console.log(currentId);
        }
    });


    $('#host').click(function () {
        $('#audio')[0].play();
        var interval2 = setInterval(function() {
            var currTime = document.getElementById('audio').currentTime;
            $.ajax({
            url: '/startedPlaying',
            type: 'post',
            data: { time: currTime, minute: '10' },
            success: function(data) {
            }
            });
        },10);
    });

    $('#client').click(function() {
    document.getElementById("audio").play();
        $.ajax({
            url: '/retrieveTime',
            type: 'post',
            success: function(data) {
                document.getElementById("audio").currentTime = data;
            }
        });
    })
});

//Server

app.use(bodyParser.urlencoded({ extended: true }));

app.get("/", function(req, res) {
res.sendFile(path.join(__dirname + "/index.html"));
}).listen(12340);


app.post("/startedPlaying", function(req, res) {
currTime = req.body.time;
res.end();
});

app.post("/retrieveTime", function(req, res) {
res.send(currTime);
res.end();
});

app.post("/retrieveId", function(req, res) {
id++;
res.send(id.toString());
res.end();
});

So basicly, i need help on how i should let them run exactly at the same time.

Thank you in advance.


Solution

  • The first thing you need to do is set up a channel of communication between the two clients that will be synchronized. Given your latency requirements, I'm guessing that these clients are physically near each other and are likely to be on the same network. In that case, you would benefit from creating a peer-to-peer WebRTC connection with data channel between them. Generally, the clients can negotiate a connection across the network they share rather than having to send data out to the server and back, which reduces the latency of this data channel.

    Next, buffer the media like you normally would. You can use .buffered to determine what time ranges have been buffered. You can generally rely on the canplaythrough event to determine when the player has buffered enough to play without having to rebuffer again.

    Then, decide which end will be "master", from where all the synchronization data will come from. Start playback on both sides.

    Regularly, as playback is progressing, the master should send its currentTime to the other clients. Upon receiving, those clients should check their own currentTime to see how far away they are. From there, they should make a calculation as to how to catch up. For example, if the master is at 100.100 and a slave client is at 100.000, then it needs to make up 100 milliseconds. If you set the playbackRate to 1.01, you'll make up that time over 10 seconds. Or, 1.02 and you'll make it up in 5 seconds.

    You'll want to come up with some formula that determines the severity of the desynchronization and eases into fixing it. You will also want some sane limits on that playback speed. (For example, you probably don't want to go faster/slower by more than 10% to synchronize.) Additionally, if the two are separated badly enough, you might choose to pause one, set the currentTime on the other (which usually causes some rebuffering) and start again.

    An improvement you can make is to also determine the latency between the master/slave. If the master says "now is 12.345s", and you receive that 100 milliseconds later, this timestamp is actually 12.445. You can implement a "ping" of sorts where the latency is continuously measured. Better yet, piggy back this on each timing message.

    This should get you really close in timing, sufficient for most use cases.