IrosTheBeggar / mStream

The easiest music streaming server available
http://mstream.io
GNU General Public License v3.0
2.21k stars 186 forks source link

add option to save current playlist to prevent sudden close of browser #373

Open aroundmyroom opened 2 years ago

aroundmyroom commented 2 years ago

I would likt to preserve the history of played songs in the left side of the screen (Now Playing) including the queue of the songs about to play.

it happens often I suddenly close (by accident) the browser window with mstream playing. When restarting again I start over and need to add the songs again what were in the Now Playing screen

Would be nice to allow a user to save the Now Playing locally (if set) so that this will not happen

SachiaLanlus commented 2 years ago

Agree. I also have this demand for my use case. I think it can be implemented by using local storage and resume. But there might have the synchronization issues in multi-tab and multi-client scenarios. Therefore, we might need the server to do this job. This would be painful work...

SachiaLanlus commented 2 years ago

Here is the solution I implemented. We need two things to make this come true. First, we need a server to store the playlist data we have currently. I chose cloudflare worker kv as a key-value db. You can follow the link below to setup. CloudflareDB Then the second one is a client-side script to synchronize playlist between client and server. The code below is the client-side script I wrote today, and it worked great. You need tampermonkey to load this script. And change the MSTREAM_DOMAIN, KV_DOMAIN, TOKEN, and KEY according to your setup.

// ==UserScript==
// @name         mStream Music Playlist Sync
// @version      0.0.1
// @description  A script to sync the playlist among clients
// @match        https://MSTREAM_DOMAIN/
// @connect      KV_DOMAIN
// @grant        GM.xmlHttpRequest
// @grant        GM_notification
// @grant        GM.notification
// @grant        GM.getValue
// @grant        GM.setValue
// @run-at       document-end
// ==/UserScript==

var GM_notification = GM_notification || GM.notification;

var check_interval = 1;
const kv_token = TOKEN;
var status = [];
const key = KEY;

function push_status(){
    console.log('INFO: Sending state');
    var payload = {'_id': key, status};
    GM.xmlHttpRequest(
        {
            method: 'POST',
            timeout: 30000,
            url: 'https://KV_DOMAIN/?key=' + kv_token,
            onerror: function(){
                GM_notification('ERROR: Server respond with error','mStream Music Playlist Sync');
                console.error('ERROR: Server respond with error');
                check_interval = 60;
            },
            ontimeout: function(){
                GM_notification('ERROR: Server not respond','mStream Music Playlist Sync');
                console.error('ERROR: Server not respond');
                check_interval = 60;
            },
            onload: function(response){
                if(response.status == 200){
                    check_interval = 5;
                }
            },
            data: JSON.stringify(payload),
        }
    );
}

function pull_status(){
    console.log('INFO: Getting state');
    GM.xmlHttpRequest(
        {
            method: 'GET',
            timeout: 30000,
            url: 'https://KV_DOMAIN/' + key,
            onerror: function(){
                GM_notification('ERROR: Server respond with error','mStream Music Playlist Sync');
                console.error('ERROR: Server respond with error');
                check_interval = 60;
            },
            ontimeout: function(){
                GM_notification('ERROR: Server not respond','mStream Music Playlist Sync');
                console.error('ERROR: Server not respond');
                check_interval = 60;
            },
            onload: function(response){
                if(response.status == 200){
                    check_interval = 5;
                    var return_dict = JSON.parse(response.responseText);
                    var remote_status = return_dict.status;
                    status = remote_status;
                    for(let m_path of remote_status){
                        console.log(m_path);
                        VUEPLAYERCORE.addSongWizard(m_path, {}, true);
                    }
                }
            },
        }
    );
}

async function sync_timer(){
    let current_staus = get_current_status();
    if(!arraysEqual(current_staus, status)){
        status = current_staus;
        push_status();
    }
}

function arraysEqual(a, b) {
    if (a === b) return true;
    if (a == null || b == null) return false;
    if (a.length !== b.length) return false;

    // If you don't care about the order of the elements inside
    // the array, you should sort both arrays here.
    // Please note that calling sort on an array will modify that array.
    // you might want to clone your array first.

    for (var i = 0; i < a.length; ++i) {
        if (a[i] !== b[i]) return false;
    }
    return true;
}

function get_current_status(){
    let current_staus = [];
    for(let p of MSTREAMPLAYER.playlist){
        current_staus.push(p.rawFilePath);
    }
    return current_staus;
}

pull_status();
setInterval(sync_timer, check_interval * 1000);