mStream
mStream copied to clipboard
add option to save current playlist to prevent sudden close of browser
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
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...
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.
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);