mpd.fm
Version:
A MPD web server and client to listen to your favorite online radio stations
248 lines (223 loc) • 9.02 kB
JavaScript
;
var socket = null;
const DefaultSongText = 'Select a station';
const DefaultMpdErrorText = 'Trying to reconnect...';
var lastMpdReconnectAttempt = 0;
var timer = {
// All in ms
mpdLastUpdate: 0,
lastMpdUpdateTimestamp: 0,
displayedTime: 0,
lastDisplayTimestamp: 0
};
Vue.component('radio-station', {
props: ['station']
})
var app = new Vue({
el: '#app',
data: {
stationList: [ ],
status: 'loading', // playing, stopped, paused
elapsed: '0:00',
song: DefaultSongText,
currentStation: null,
errorState: {
wssDisconnect: true,
mpdServerDisconnect: true
}
},
created: function () {
this.connectWSS();
this.updateElapsed();
},
methods: {
connectWSS: function() {
var self = this;
// Connect to WebSocket server
var url = 'ws://'+location.hostname+(location.port ? ':'+location.port: '');
socket = new ReconnectingWebSocket(url, null, {reconnectInterval: 3000});
socket.onopen = function () {
self.errorState.wssDisconnect = false;
self.sendWSSMessage('REQUEST_STATION_LIST', null);
self.sendWSSMessage('REQUEST_STATUS', null);
};
socket.onmessage = function (message) {
self.errorState.wssDisconnect = false;
var msg = JSON.parse(message.data);
switch(msg.type) {
case "STATION_LIST":
self.stationList = msg.data;
break;
case "STATUS":
timer.lastDisplayTimestamp = 0;
self.setPlayState(msg.data.state);
self.setCurrentStation(msg.data.file);
self.setSongName(msg.data.title, msg.data.album, msg.data.artist);
self.setElapsedTime(msg.data.elapsed);
break;
case "ELAPSED":
self.setElapsedTime(msg.data.elapsed);
break;
case "MPD_OFFLINE":
self.status = 'loading';
self.currentStation = null;
self.elapsed = '0:00';
self.song = DefaultMpdErrorText;
self.errorState.mpdServerDisconnect = true;
setTimeout(() => {
if((Date.now()-lastMpdReconnectAttempt) >= 2500) {
lastMpdReconnectAttempt = Date.now();
self.sendWSSMessage('REQUEST_STATUS', null);
}
}, 3000);
return;
}
self.errorState.mpdServerDisconnect = false;
};
socket.onerror = socket.onclose = function(err) {
self.errorState.wssDisconnect = true;
};
},
onPlayButton: function(event) {
var self = this;
switch(self.status) {
case 'playing':
self.status = 'loading';
self.sendWSSMessage('PAUSE', null);
break;
case 'stopped':
case 'paused':
self.status = 'loading';
self.sendWSSMessage('PLAY', null);
break;
default:
self.sendWSSMessage('REQUEST_STATUS', null);
break;
}
},
onPlayStation: function(stream) {
var self = this;
self.status = 'loading';
self.currentStation = null;
self.elapsed = '0:00';
self.song = "";
self.sendWSSMessage('PLAY', { stream: stream });
},
updateElapsed: function() {
var self = this;
var timeout = 1000;
if(self.status === 'playing') {
// Last MPD update + the time passed since then
var bestGuessOnMpdTime = (timer.mpdLastUpdate + Date.now() - timer.lastMpdUpdateTimestamp);
if(timer.lastDisplayTimestamp <= 0) {
// Initialize display to latest MPD update + the time passed since then
timer.displayedTime = bestGuessOnMpdTime;
timer.lastDisplayTimestamp = Date.now();
}
// Advance displayed timer by the time passed since it has been last updated for the user
timer.displayedTime += Math.max(Date.now() - timer.lastDisplayTimestamp, 0);
// Calculate difference to best guess
var delta = timer.displayedTime - bestGuessOnMpdTime;
if(Math.abs(delta) > 3000) {
timer.displayedTime = bestGuessOnMpdTime;
} else {
var timeoutShorterToRecoverIn10Secs = delta / 10;
timeout = Math.min(Math.max(timeout - timeoutShorterToRecoverIn10Secs, 0), 2000);
timer.displayedTime -= timeoutShorterToRecoverIn10Secs;
}
} else if(self.status === 'paused') {
timer.displayedTime = timer.mpdLastUpdate;
} else {
timer.displayedTime = 0;
}
self.changeDisplayTimer(timer.displayedTime);
timer.lastDisplayTimestamp = Date.now();
setTimeout(() => {
self.updateElapsed();
}, timeout);
if(self.status === 'playing' && (Date.now() - timer.lastMpdUpdateTimestamp) > 10000) {
self.sendWSSMessage('REQUEST_ELAPSED', null);
}
},
setElapsedTime: function(elapsed) {
if(!isNaN(parseFloat(elapsed)) && isFinite(elapsed)) {
timer.mpdLastUpdate = elapsed * 1000;
} else {
timer.mpdLastUpdate = 0;
}
timer.lastMpdUpdateTimestamp = Date.now();
},
setPlayState: function(state) {
switch(state) {
case 'play':
this.status = 'playing';
break;
case 'stop':
this.status = 'stopped';
break;
case 'pause':
this.status = 'paused';
break;
default:
this.status = 'loading';
break;
}
},
setCurrentStation: function(file) {
var self = this;
var found = false;
self.stationList.forEach(station => {
if(station.stream === file) {
found = true;
// Don't do anything if the station did not chnage
if(!self.currentStation || self.currentStation.stream !== file)
self.currentStation = station;
return;
}
});
if(!found) {
self.song = DefaultSongText;
self.currentStation = null;
}
},
setSongName: function(title, album, artist) {
if(!title && !album && !artist && !this.currentStation) {
this.song = DefaultSongText;
} else {
var text = '';
if(typeof artist != 'undefined' && artist.length > 0) {
text = artist;
}
if(typeof album != 'undefined' && album.length > 0) {
text += ((text.length > 0) ? ' - ' : '') + album;
}
if(typeof title != 'undefined' && title.length > 0) {
text += ((text.length > 0) ? ' - ' : '') + title;
}
this.song = text;
}
},
changeDisplayTimer: function(ms) {
var timeInSec = ms/1000;
var hours = Math.floor(timeInSec / 3600);
var minutes = Math.floor((timeInSec / 60) - (hours * 60));
var seconds = Math.floor(timeInSec - (hours * 3600) - (minutes * 60));
var strToDisplay = (hours > 0) ? (hours+':') : '';
strToDisplay += (hours > 0 && minutes < 10) ? ('0' + minutes + ':') : (minutes + ':');
strToDisplay += (seconds < 10 ? '0' : '') + seconds;
this.elapsed = strToDisplay;
},
sendWSSMessage: function(type, data) {
var self = this;
var msg = {
type: type,
data: (data) ? data : {}
}
try {
socket.send(JSON.stringify(msg));
} catch (error) {
self.errorState.wssDisconnect = true;
}
}
}
})