mirror of
https://github.com/SuperBFG7/ympd
synced 2024-12-24 18:10:26 +00:00
Feat: track mpd status changes with idle protocol
This commit is contained in:
parent
15afb2a70e
commit
dd5db4f8a3
2
dist/htdocs/css/mpd.min.css
vendored
2
dist/htdocs/css/mpd.min.css
vendored
@ -1 +1 @@
|
||||
html{position:relative;min-height:100%}body{margin-bottom:60px}footer{position:absolute;bottom:0}body{padding-top:50px;padding-bottom:50px;background-color:#888}button{overflow:hidden}#BrowseBreadrumb{overflow:auto;white-space:nowrap}#BrowseBreadcrumb>li>a{cursor:pointer}#counter{font-size:22px;margin-left:10px;min-width:50px}.card{min-height:350px}@media only screen and (max-width:576px){.header-logo{display:none!important}}.clickable{cursor:pointer}.tblnum,.tblaction{width:30px}.album-cover{background-size:cover;border:1px solid black;border-radius:5px;overflow:hidden;margin-bottom:20px;width:240px;height:240px;background-color:#eee}.hide{display:none!important}.pull-right{float:right!important}.card-toolbar{margin-bottom:10px}.card-toolbar>div,.card-toolbar>form{margin-bottom:5px}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(/assets/MaterialIcons-Regular.eot);src:local('Material Icons'),local('MaterialIcons-Regular');src:url(/assets/MaterialIcons-Regular.woff2) format('woff2'),url(/assets/MaterialIcons-Regular.woff) format('woff'),url(/assets/MaterialIcons-Regular.ttf) format('truetype')}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:18px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;vertical-align:top;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}.material-icons-small{font-size:16px}main{padding-top:20px}.color-darkgrey{color:#6c757d}.color-darkgrey:hover{color:#6c757d!important}#btn-outputs-block>button{margin-bottom:10px}#btn-outputs-block>button:last-child{margin-bottom:0}.card-body{overflow-x:hidden}#progressBar{width:100%;margin-top:8px}#volumeBar{margin-top:2px;width:160px}.title-icon{float:left;margin-right:5px;font-size:1.8rem}.header-logo{font-size:2rem;float:left;margin-right:5px}.letters>button{width:28px;height:28px}.col-md{max-width:250px;min-width:250px}a.card-img-top{overflow:hidden;display:block}button.active{color:#fff;background-color:#28a745!important;border-color:#28a745!important}div#alertBox{position:fixed;top:50px;right:10px;width:80%;max-width:400px;z-index:1000;opacity:0;visibility:visible;transition:opacity .5s ease-in}div.alertBoxActive{opacity:1!important;visibility:visible!important;transition:opacity .5s ease-in}.popover-content{padding-top:4px;padding-bottom:4px}.opacity05{opacity:.5}caption{caption-side:top;font-size:120%;font-weight:bold;color:black}.dragover>td{border-top:25px solid transparent}[draggable]{-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;user-select:none;-khtml-user-drag:element;-webkit-user-drag:element}
|
||||
html{position:relative;min-height:100%}body{margin-bottom:60px;padding-top:50px;padding-bottom:50px;background-color:#888}main{padding-top:20px}footer{position:absolute;bottom:0}button{overflow:hidden}#BrowseBreadrumb{overflow:auto;white-space:nowrap}#BrowseBreadcrumb>li>a{cursor:pointer}#counter{font-size:22px;margin-left:10px;min-width:50px}.card{min-height:350px}@media only screen and (max-width:576px){.header-logo{display:none!important}}.clickable{cursor:pointer}.tblnum,.tblaction{width:30px}.album-cover{background-size:cover;border:1px solid black;border-radius:5px;overflow:hidden;margin-bottom:20px;width:240px;height:240px;background-color:#eee}.hide{display:none!important}.pull-right{float:right!important}.card-toolbar{margin-bottom:10px}.card-toolbar>div,.card-toolbar>form{margin-bottom:5px}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(/assets/MaterialIcons-Regular.eot);src:local('Material Icons'),local('MaterialIcons-Regular');src:url(/assets/MaterialIcons-Regular.woff2) format('woff2'),url(/assets/MaterialIcons-Regular.woff) format('woff'),url(/assets/MaterialIcons-Regular.ttf) format('truetype')}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:18px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;vertical-align:top;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}.material-icons-small{font-size:16px}.color-darkgrey,.color-darkgrey:hover{color:#6c757d!important}#btn-outputs-block>button{margin-bottom:10px}#btn-outputs-block>button:last-child{margin-bottom:0}.card-body{overflow-x:hidden}#progressBar{width:100%;margin-top:8px}#volumeBar{margin-top:2px;width:160px}.title-icon{float:left;margin-right:5px;font-size:1.8rem}.header-logo{font-size:2rem;float:left;margin-right:5px}.letters>button{width:28px;height:28px}.col-md{max-width:250px;min-width:250px}a.card-img-top{overflow:hidden;display:block}button.active{color:#fff;background-color:#28a745!important;border-color:#28a745!important}div#alertBox{position:fixed;top:50px;right:10px;width:80%;max-width:400px;z-index:1000;opacity:0;visibility:visible;transition:opacity .5s ease-in}div.alertBoxActive{opacity:1!important;visibility:visible!important;transition:opacity .5s ease-in}.popover-content{padding-top:4px;padding-bottom:4px}.opacity05{opacity:.5}caption{caption-side:top;font-size:120%;font-weight:bold;color:black}.dragover>td{border-top:25px solid transparent}[draggable]{-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;user-select:none;-khtml-user-drag:element;-webkit-user-drag:element}
|
132
htdocs/js/mpd.js
132
htdocs/js/mpd.js
@ -24,12 +24,13 @@
|
||||
*/
|
||||
|
||||
var socket;
|
||||
var last_song = '';
|
||||
var last_state;
|
||||
var current_song = new Object();
|
||||
var lastSong = '';
|
||||
var lastState;
|
||||
var currentSong = new Object();
|
||||
var playstate = '';
|
||||
var settings = {};
|
||||
var alertTimeout;
|
||||
var progressTimer;
|
||||
let deferredPrompt;
|
||||
var dragEl;
|
||||
|
||||
@ -299,9 +300,9 @@ function appInit() {
|
||||
|
||||
domCache.progressBar.value = 0;
|
||||
domCache.progressBar.addEventListener('change', function(event) {
|
||||
if (current_song && current_song.currentSongId >= 0) {
|
||||
var seekVal = Math.ceil(current_song.totalTime * (domCache.progressBar.value / 100));
|
||||
sendAPI({"cmd": "MPD_API_SET_SEEK", "data": {"songid": current_song.currentSongId, "seek": seekVal}});
|
||||
if (currentSong && currentSong.currentSongId >= 0) {
|
||||
var seekVal = Math.ceil(currentSong.totalTime * (domCache.progressBar.value / 100));
|
||||
sendAPI({"cmd": "MPD_API_SET_SEEK", "data": {"songid": currentSong.currentSongId, "seek": seekVal}});
|
||||
}
|
||||
}, false);
|
||||
|
||||
@ -648,7 +649,7 @@ function webSocketConnect() {
|
||||
}
|
||||
|
||||
socket.onmessage = function got_packet(msg) {
|
||||
if (msg.data === last_state || msg.data.length == 0)
|
||||
if (msg.data === lastState || msg.data.length == 0)
|
||||
return;
|
||||
|
||||
try {
|
||||
@ -674,9 +675,6 @@ function webSocketConnect() {
|
||||
case 'update_outputs':
|
||||
sendAPI({"cmd":"MPD_API_GET_OUTPUTS"}, parseOutputs);
|
||||
break;
|
||||
case 'song_change':
|
||||
songChange(obj);
|
||||
break;
|
||||
case 'error':
|
||||
showNotification(obj.data, '', '', 'danger');
|
||||
default:
|
||||
@ -825,8 +823,54 @@ function parseOutputs(obj) {
|
||||
domCache.outputs.innerHTML = btns;
|
||||
}
|
||||
|
||||
function setCounter(currentSongId, totalTime, elapsedTime) {
|
||||
currentSong.totalTime = totalTime;
|
||||
currentSong.elapsedTime = elapsedTime;
|
||||
currentSong.currentSongId = currentSongId;
|
||||
var total_minutes = Math.floor(totalTime / 60);
|
||||
var total_seconds = totalTime - total_minutes * 60;
|
||||
var elapsed_minutes = Math.floor(elapsedTime / 60);
|
||||
var elapsed_seconds = elapsedTime - elapsed_minutes * 60;
|
||||
|
||||
domCache.progressBar.value = Math.floor(100 * elapsedTime / totalTime);
|
||||
|
||||
var counterText = elapsed_minutes + ":" +
|
||||
(elapsed_seconds < 10 ? '0' : '') + elapsed_seconds + " / " +
|
||||
total_minutes + ":" + (total_seconds < 10 ? '0' : '') + total_seconds;
|
||||
domCache.counter.innerText = counterText;
|
||||
|
||||
//Set playing track in queue view
|
||||
if (lastState) {
|
||||
var tr = document.getElementById('queueTrackId' + lastState.data.currentSongId);
|
||||
if (tr) {
|
||||
var trtds = tr.getElementsByTagName('td');
|
||||
trtds[4].innerText = tr.getAttribute('data-duration');
|
||||
trtds[0].classList.remove('material-icons');
|
||||
trtds[0].innerText = tr.getAttribute('data-songpos');
|
||||
tr.classList.remove('font-weight-bold');
|
||||
}
|
||||
}
|
||||
var tr = document.getElementById('queueTrackId' + currentSongId);
|
||||
if (tr) {
|
||||
var trtds = tr.getElementsByTagName('td');
|
||||
trtds[4].innerText = counterText;
|
||||
trtds[0].classList.add('material-icons');
|
||||
trtds[0].innerText = 'play_arrow';
|
||||
tr.classList.add('font-weight-bold');
|
||||
}
|
||||
|
||||
if (progressTimer)
|
||||
clearTimeout(progressTimer);
|
||||
if (playstate == 'play') {
|
||||
progressTimer = setTimeout(function() {
|
||||
currentSong.elapsedTime ++;
|
||||
setCounter(currentSong.currentSongId, currentSong.totalTime, currentSong.elapsedTime);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function parseState(obj) {
|
||||
if (JSON.stringify(obj) === JSON.stringify(last_state))
|
||||
if (JSON.stringify(obj) === JSON.stringify(lastState))
|
||||
return;
|
||||
|
||||
//Set playstate
|
||||
@ -841,17 +885,17 @@ function parseState(obj) {
|
||||
playstate = 'pause';
|
||||
}
|
||||
|
||||
if (obj.data.nextsongpos == -1)
|
||||
if (obj.data.nextSongPos == -1)
|
||||
domCache.btnNext.setAttribute('disabled','disabled');
|
||||
else
|
||||
domCache.btnNext.removeAttribute('disabled');
|
||||
|
||||
if (obj.data.songpos <= 0)
|
||||
if (obj.data.songPos <= 0)
|
||||
domCache.btnPrev.setAttribute('disabled','disabled');
|
||||
else
|
||||
domCache.btnPrev.removeAttribute('disabled');
|
||||
|
||||
if (obj.data.queue_length == 0)
|
||||
if (obj.data.queueLength == 0)
|
||||
domCache.btnPlay.setAttribute('disabled','disabled');
|
||||
else
|
||||
domCache.btnPlay.removeAttribute('disabled');
|
||||
@ -873,55 +917,19 @@ function parseState(obj) {
|
||||
domCache.volumeBar.value = obj.data.volume;
|
||||
|
||||
//Set play counters
|
||||
current_song.totalTime = obj.data.totalTime;
|
||||
current_song.currentSongId = obj.data.currentsongid;
|
||||
var total_minutes = Math.floor(obj.data.totalTime / 60);
|
||||
var total_seconds = obj.data.totalTime - total_minutes * 60;
|
||||
var elapsed_minutes = Math.floor(obj.data.elapsedTime / 60);
|
||||
var elapsed_seconds = obj.data.elapsedTime - elapsed_minutes * 60;
|
||||
|
||||
domCache.progressBar.value = Math.floor(100 * obj.data.elapsedTime / obj.data.totalTime);
|
||||
|
||||
var counterText = elapsed_minutes + ":" +
|
||||
(elapsed_seconds < 10 ? '0' : '') + elapsed_seconds + " / " +
|
||||
total_minutes + ":" + (total_seconds < 10 ? '0' : '') + total_seconds;
|
||||
domCache.counter.innerText = counterText;
|
||||
|
||||
//Set playing track in queue view
|
||||
if (last_state) {
|
||||
var tr = document.getElementById('queueTrackId' + last_state.data.currentsongid);
|
||||
if (tr) {
|
||||
var trtds = tr.getElementsByTagName('td');
|
||||
trtds[4].innerText = tr.getAttribute('data-duration');
|
||||
trtds[0].classList.remove('material-icons');
|
||||
trtds[0].innerText = tr.getAttribute('data-songpos');
|
||||
tr.classList.remove('font-weight-bold');
|
||||
}
|
||||
}
|
||||
var tr = document.getElementById('queueTrackId' + obj.data.currentsongid);
|
||||
if (tr) {
|
||||
var trtds = tr.getElementsByTagName('td');
|
||||
trtds[4].innerText = counterText;
|
||||
trtds[0].classList.add('material-icons');
|
||||
trtds[0].innerText = 'play_arrow';
|
||||
tr.classList.add('font-weight-bold');
|
||||
}
|
||||
|
||||
//Get current song on queue change for http streams
|
||||
if (last_state == undefined || obj.data.queue_version != last_state.data.queue_version)
|
||||
sendAPI({"cmd": "MPD_API_GET_CURRENT_SONG"}, songChange);
|
||||
|
||||
|
||||
setCounter(obj.data.currentSongId, obj.data.totalTime, obj.data.elapsedTime);
|
||||
|
||||
//Get current song
|
||||
sendAPI({"cmd": "MPD_API_GET_CURRENT_SONG"}, songChange);
|
||||
//clear playback card if not playing
|
||||
if (obj.data.songpos == '-1') {
|
||||
if (obj.data.songPos == '-1') {
|
||||
domCache.currentTrack.innerText = 'Not playing';
|
||||
document.getElementById('currentAlbum').innerText = '';
|
||||
document.getElementById('currentArtist').innerText = '';
|
||||
document.getElementById('currentCover').style.backgroundImage = '';
|
||||
}
|
||||
|
||||
last_state = obj;
|
||||
lastState = obj;
|
||||
}
|
||||
|
||||
function getQueue() {
|
||||
@ -945,7 +953,7 @@ function parseQueue(obj) {
|
||||
|
||||
var nrItems = obj.data.length;
|
||||
var table = document.getElementById(app.current.app + 'List');
|
||||
table.setAttribute('data-version', obj.queue_version);
|
||||
table.setAttribute('data-version', obj.queueVersion);
|
||||
var tbody = table.getElementsByTagName('tbody')[0];
|
||||
var tr = tbody.getElementsByTagName('tr');
|
||||
for (var i = 0; i < nrItems; i++) {
|
||||
@ -1549,8 +1557,8 @@ function showMenu(el) {
|
||||
name = el.parentNode.parentNode.getAttribute('data-name');
|
||||
}
|
||||
|
||||
if (last_state)
|
||||
nextsongpos = last_state.data.nextsongpos;
|
||||
if (lastState)
|
||||
nextsongpos = lastState.data.nextsongpos;
|
||||
|
||||
var menu = '';
|
||||
if ((app.current.app == 'Browse' && app.current.tab == 'Filesystem') || app.current.app == 'Search' ||
|
||||
@ -1850,8 +1858,8 @@ function notificationsSupported() {
|
||||
function songChange(obj) {
|
||||
if (obj.type == 'error' || obj.type == 'result')
|
||||
return;
|
||||
var cur_song = obj.data.title + obj.data.artist + obj.data.album + obj.data.uri + obj.data.currentsongid;
|
||||
if (last_song == cur_song)
|
||||
var curSong = obj.data.title + obj.data.artist + obj.data.album + obj.data.uri + obj.data.currentSongId;
|
||||
if (lastSong == curSong)
|
||||
return;
|
||||
var textNotification = '';
|
||||
var htmlNotification = '';
|
||||
@ -1885,12 +1893,12 @@ function songChange(obj) {
|
||||
}
|
||||
document.title = pageTitle;
|
||||
//Update Artist in queue view for http streams
|
||||
var playingTr = document.getElementById('queueTrackId' + obj.data.currentsongid);
|
||||
var playingTr = document.getElementById('queueTrackId' + obj.data.currentSongId);
|
||||
if (playingTr)
|
||||
playingTr.getElementsByTagName('td')[1].innerText = obj.data.title;
|
||||
|
||||
showNotification(obj.data.title, textNotification, htmlNotification, 'success');
|
||||
last_song = cur_song;
|
||||
lastSong = curSong;
|
||||
}
|
||||
|
||||
function doSetFilterLetter(x) {
|
||||
|
@ -75,14 +75,10 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
|
||||
if (cmd_id == -1)
|
||||
cmd_id = get_cmd_id("MPD_API_UNKNOWN");
|
||||
|
||||
mpd_send_noidle(mpd.conn);
|
||||
mpd_send_noidle(mpd.conn);
|
||||
// mympd_parse_idle(mg_mgr);
|
||||
mpd_response_finish(mpd.conn);
|
||||
/*
|
||||
enum mpd_idle idle_event;
|
||||
mpd_connection_set_timeout(mpd.conn, 1);
|
||||
idle_event = mpd_recv_idle(mpd.conn, false);
|
||||
*/
|
||||
mpd_connection_set_timeout(mpd.conn, mpd.timeout);
|
||||
// mpd_connection_set_timeout(mpd.conn, mpd.timeout);
|
||||
|
||||
switch(cmd_id) {
|
||||
case MPD_API_UNKNOWN:
|
||||
@ -515,7 +511,7 @@ void mympd_poll(struct mg_mgr *s, int timeout) {
|
||||
switch (mpd.conn_state) {
|
||||
case MPD_DISCONNECTED:
|
||||
/* Try to connect */
|
||||
fprintf(stdout, "MPD Connecting to %s: %d\n", config.mpdhost, config.mpdport);
|
||||
fprintf(stdout, "MPD Connecting to %s:%d\n", config.mpdhost, config.mpdport);
|
||||
mpd.conn = mpd_connection_new(config.mpdhost, config.mpdport, mpd.timeout);
|
||||
if (mpd.conn == NULL) {
|
||||
fprintf(stderr, "Out of memory.");
|
||||
@ -585,9 +581,9 @@ void mympd_notify(struct mg_mgr *s) {
|
||||
}
|
||||
|
||||
void mympd_parse_idle(struct mg_mgr *s) {
|
||||
mpd_connection_set_timeout(mpd.conn, 60);
|
||||
// mpd_connection_set_timeout(mpd.conn, 60);
|
||||
enum mpd_idle idle_bitmask = mpd_recv_idle(mpd.conn, false);
|
||||
mpd_connection_set_timeout(mpd.conn, mpd.timeout);
|
||||
// mpd_connection_set_timeout(mpd.conn, mpd.timeout);
|
||||
int len;
|
||||
|
||||
for (unsigned j = 0;; ++j) {
|
||||
@ -668,11 +664,11 @@ int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, unsig
|
||||
}
|
||||
|
||||
len = json_printf(&out,"{type:state, data:{"
|
||||
"state:%d, volume:%d, songpos: %d, elapsedTime: %d, "
|
||||
"totalTime:%d, currentsongid: %d, kbitrate: %d, "
|
||||
"audioformat: { sample_rate: %d, bits: %d, channels: %d}, "
|
||||
"queue_length: %d, nextsongpos: %d, nextsongid: %d, "
|
||||
"queue_version: %d",
|
||||
"state:%d, volume:%d, songPos: %d, elapsedTime: %d, "
|
||||
"totalTime:%d, currentSongId: %d, kbitrate: %d, "
|
||||
"audioFormat: { sampleRate: %d, bits: %d, channels: %d}, "
|
||||
"queueLength: %d, nextSongPos: %d, nextSongId: %d, "
|
||||
"queueVersion: %d",
|
||||
mpd_status_get_state(status),
|
||||
mpd_status_get_volume(status),
|
||||
mpd_status_get_song_pos(status),
|
||||
@ -841,7 +837,7 @@ int mympd_put_current_song(char *buffer) {
|
||||
mympd_get_cover(mpd_song_get_uri(song),cover,500);
|
||||
|
||||
len = json_printf(&out,"{type: song_change, data: { pos: %d, title: %Q, "
|
||||
"artist: %Q, album: %Q, uri: %Q, currentsongid: %d, albumartist: %Q, "
|
||||
"artist: %Q, album: %Q, uri: %Q, currentSongId: %d, albumartist: %Q, "
|
||||
"duration: %d, cover: %Q}}",
|
||||
mpd_song_get_pos(song),
|
||||
mympd_get_tag(song, MPD_TAG_TITLE),
|
||||
|
19
src/mympd.c
19
src/mympd.c
@ -28,6 +28,7 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <pwd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "../dist/src/mongoose/mongoose.h"
|
||||
#include "../dist/src/inih/ini.h"
|
||||
@ -37,6 +38,7 @@
|
||||
static sig_atomic_t s_signal_received = 0;
|
||||
static struct mg_serve_http_opts s_http_server_opts;
|
||||
char s_redirect[250];
|
||||
struct mg_mgr mgr;
|
||||
|
||||
static void signal_handler(int sig_num) {
|
||||
signal(sig_num, signal_handler); // Reinstantiate signal handler
|
||||
@ -145,8 +147,14 @@ static int inihandler(void* user, const char* section, const char* name, const c
|
||||
return 1;
|
||||
}
|
||||
|
||||
void mympd_poll_loop() {
|
||||
while (s_signal_received == 0) {
|
||||
mympd_poll(&mgr, 200);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
struct mg_mgr mgr;
|
||||
|
||||
struct mg_connection *nc;
|
||||
struct mg_connection *nc_http;
|
||||
struct mg_bind_opts bind_opts;
|
||||
@ -254,10 +262,15 @@ int main(int argc, char **argv) {
|
||||
printf("myMPD started on http port %s\n", config.webport);
|
||||
if (config.ssl == true)
|
||||
printf("myMPD started on ssl port %s\n", config.sslport);
|
||||
|
||||
//Create thread for mpd idle connection
|
||||
//pthread_t tid;
|
||||
//pthread_create(&tid, NULL, &mympd_poll_loop, NULL);
|
||||
|
||||
//Main loop for http handling
|
||||
while (s_signal_received == 0) {
|
||||
mympd_poll(&mgr, 60);
|
||||
mg_mgr_poll(&mgr, 60);
|
||||
mg_mgr_poll(&mgr, 100);
|
||||
mympd_poll(&mgr, 1);
|
||||
}
|
||||
mg_mgr_free(&mgr);
|
||||
mympd_disconnect();
|
||||
|
Loading…
Reference in New Issue
Block a user