diff --git a/htdocs/css/starter-template.css b/htdocs/css/mpd.css similarity index 75% rename from htdocs/css/starter-template.css rename to htdocs/css/mpd.css index a61fb1c..46244c7 100644 --- a/htdocs/css/starter-template.css +++ b/htdocs/css/mpd.css @@ -20,3 +20,11 @@ body { margin-top: 0px; margin-bottom: 0px; } + +#counter { + font-size: 24px; + margin-top: -6px; + margin-left: 10px; + min-width: 50px; +} + diff --git a/htdocs/index.html b/htdocs/index.html index b8ccf21..1220c19 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -14,7 +14,7 @@ - + @@ -80,12 +80,13 @@
+
-
Playlist
+
Playlist
-

Playing track xyz

-

  2:13/4:12

+

+

  

- +
@@ -111,16 +112,16 @@
- - - -
diff --git a/htdocs/js/mpd.js b/htdocs/js/mpd.js index ab48494..cbe05af 100644 --- a/htdocs/js/mpd.js +++ b/htdocs/js/mpd.js @@ -5,7 +5,34 @@ $('#volumeslider').slider().on('slide', function(event) { socket.send("MPD_API_SET_VOLUME,"+event.value); }); -webSocketConnect(); +$(window).bind('hashchange', function(e) { + $('#nav_links > li').each(function(value) { + if(window.location.hash === $(this).children().attr('href')) + $(this).addClass('active'); + else + $(this).removeClass('active'); + }); + + switch(window.location.hash) { + case "#browse": + $('#panel-heading').text("Browse database"); + socket.send('MPD_API_GET_BROWSE,/'); + break; + case "#about": + $('#panel-heading').text("About"); + break; + default: + $('#panel-heading').text("Playlist"); + $('#nav_links > li:first-child').addClass('active'); + socket.send("MPD_API_GET_PLAYLIST"); + } + socket.send("MPD_API_GET_TRACK_INFO"); +}); + +$(document).ready(function(){ + webSocketConnect(); +}); + function webSocketConnect() { @@ -18,18 +45,19 @@ function webSocketConnect() { try { socket.onopen = function() { console.log("Connected"); - socket.send("MPD_API_GET_PLAYLIST"); - socket.send("MPD_API_GET_STATE"); + $(window).trigger( 'hashchange' ); } socket.onmessage =function got_packet(msg) { - console.log(msg.data); if(msg.data === last_state) return; var obj = JSON.parse(msg.data); switch (obj.type) { case "playlist": + if(window.location.hash) + break; + $('#salamisandwich').find("tr:gt(0)").remove(); for (var song in obj.data) { var minutes = Math.floor(obj.data[song].duration / 60); @@ -38,17 +66,65 @@ function webSocketConnect() { $('#salamisandwich tr:last').after( "
" + "" + - ""); + ""); } + break; + case "browse": + if(window.location.hash !== '#browse') + break; + $('#salamisandwich').find("tr:gt(0)").remove(); + + for (var item in obj.data) { + switch(obj.data[item].type) { + case "directory": + $('#salamisandwich tr:last').after( + "" + + "" + + ""); + break; + case "song": + var minutes = Math.floor(obj.data[item].duration / 60); + var seconds = obj.data[item].duration - minutes * 60; + + $('#salamisandwich tr:last').after( + "" + + "" + + ""); + break; + case "playlist": + break; + } + } + $('#salamisandwich td:eq(1)').click(function(){ + socket.send('MPD_API_GET_BROWSE,'+$(this).text()); + }); + break; case "state": if(JSON.stringify(obj) === JSON.stringify(last_state)) break; + 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; + $('#volumeslider').slider('setValue', obj.data.volume); var progress = Math.floor(100*obj.data.elapsedTime/obj.data.totalTime) + "%"; $('#progressbar').width(progress); + + $('#counter') + .text(elapsed_minutes + ":" + + (elapsed_seconds < 10 ? '0' : '') + elapsed_seconds + " / " + + total_minutes + ":" + (total_seconds < 10 ? '0' : '') + total_seconds); + + $('#salamisandwich > thead > tr').each(function(value) { + $(this).removeClass('success'); + }); $('#playlist_'+obj.data.currentsongid).addClass('success'); + if(obj.data.random) $('#btnrandom').addClass("active") else @@ -73,11 +149,20 @@ function webSocketConnect() { updatePlayIcon(obj.data.state); if(last_state && (obj.data.volume !== last_state.data.volume)) updateVolumeIcon(obj.data.volume); + + if(obj.data.elapsedTime <= 1) + socket.send("MPD_API_GET_TRACK_INFO"); + break; case "disconnected": - $('#alert').text("Error: Connection to MPD failed."); - $('#alert').removeClass("hide alert-info").addclass("alert-danger"); - break; + $('#alert') + .text("Server lost connection to MPD Host.") + .removeClass("hide alert-info") + .addClass("alert-danger"); + break; + + case "current_song": + $('#currenttrack').text(" " + obj.data.title); default: break; @@ -188,21 +273,18 @@ function updateDB() }, 5000); } -function toggleButton(id) { - switch (obj.type) { - case "btnrandom": - socket.send("MPD_API_TOGGLE_RANDOM"); - break; - case "btnconsume": - socket.send("MPD_API_TOGGLE_CONSUME"); - break; - case "btnsingle": - socket.send("MPD_API_TOGGLE_SINGLE"); - break; - case "btnrepeat": - socket.send("MPD_API_TOGGLE_REPEAT"); - break; - default: - break; - } -} + +$('#btnrandom').on('click', function (e) { + socket.send("MPD_API_TOGGLE_RANDOM," + ($(this).hasClass('active') ? 0 : 1)); + +}); +$('#btnconsume').on('click', function (e) { + socket.send("MPD_API_TOGGLE_CONSUME," + ($(this).hasClass('active') ? 0 : 1)); + +}); +$('#btnsingle').on('click', function (e) { + socket.send("MPD_API_TOGGLE_SINGLE," + ($(this).hasClass('active') ? 0 : 1)); +}); +$('#btnrepeat').on('click', function (e) { + socket.send("MPD_API_TOGGLE_REPEAT," + ($(this).hasClass('active') ? 0 : 1)); +}); diff --git a/src/http_server.c b/src/http_server.c index 638653f..453a276 100644 --- a/src/http_server.c +++ b/src/http_server.c @@ -14,8 +14,8 @@ struct serveable { static const struct serveable whitelist[] = { { "/css/bootstrap.css", "text/css" }, - { "/css/starter-template.css", "text/css" }, { "/css/slider.css", "text/css" }, + { "/css/mpd.css", "text/css" }, { "/js/bootstrap.min.js", "text/javascript" }, { "/js/mpd.js", "text/javascript" }, diff --git a/src/main.c b/src/main.c index d378291..a6b7f52 100644 --- a/src/main.c +++ b/src/main.c @@ -19,7 +19,7 @@ struct libwebsocket_protocols protocols[] = { "ympd-client", callback_ympd, sizeof(struct per_session_data__ympd), - 10, + 255, }, { NULL, NULL, 0, 0 } /* terminator */ diff --git a/src/mpd_client.c b/src/mpd_client.c index c2a97a2..500640c 100644 --- a/src/mpd_client.c +++ b/src/mpd_client.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "mpd_client.h" @@ -47,26 +48,29 @@ int callback_ympd(struct libwebsocket_context *context, if(mpd_conn_state != MPD_CONNECTED) { n = snprintf(p, MAX_SIZE, "{\"type\":\"disconnected\"}"); } - else if(pss->queue_version != queue_version) { + else if((pss->queue_version != queue_version) || (pss->do_send & DO_SEND_PLAYLIST)) { n = mpd_put_playlist(p); pss->queue_version = queue_version; - } - else if(pss->do_send & DO_SEND_STATE) { - n = mpd_put_state(p); - pss->do_send &= ~DO_SEND_STATE; - } - else if(pss->do_send & DO_SEND_PLAYLIST) { - n = mpd_put_playlist(p); pss->do_send &= ~DO_SEND_PLAYLIST; } - else if(pss->do_send & DO_SEND_TRACK_INFO) + else if(pss->do_send & DO_SEND_TRACK_INFO) { n = mpd_put_current_song(p); + pss->do_send &= ~DO_SEND_TRACK_INFO; + } + else if(pss->do_send & DO_SEND_BROWSE) { + n = mpd_put_browse(p, pss->browse_path); + pss->do_send &= ~DO_SEND_BROWSE; + free(pss->browse_path); + } else { n = mpd_put_state(p); } if(n > 0) - m = libwebsocket_write(wsi, (unsigned char*)p, n, LWS_WRITE_TEXT); + m = libwebsocket_write(wsi, (unsigned char *)p, n, LWS_WRITE_TEXT); + + if(p != NULL) + printf("Sending out: %s\n", p); if (m < n) { lwsl_err("ERROR %d writing to socket\n", n, m); @@ -79,9 +83,7 @@ int callback_ympd(struct libwebsocket_context *context, case LWS_CALLBACK_RECEIVE: printf("Got %s\n", (char *)in); - if(!strcmp((const char *)in, MPD_API_GET_STATE)) - pss->do_send |= DO_SEND_STATE; - else if(!strcmp((const char *)in, MPD_API_GET_PLAYLIST)) + if(!strcmp((const char *)in, MPD_API_GET_PLAYLIST)) pss->do_send |= DO_SEND_PLAYLIST; else if(!strcmp((const char *)in, MPD_API_GET_TRACK_INFO)) pss->do_send |= DO_SEND_TRACK_INFO; @@ -101,11 +103,40 @@ int callback_ympd(struct libwebsocket_context *context, mpd_send_next(conn); mpd_response_finish(conn); } + else if(!strncmp((const char *)in, MPD_API_TOGGLE_RANDOM, sizeof(MPD_API_TOGGLE_RANDOM)-1)) { + unsigned random; + if(sscanf(in, "MPD_API_TOGGLE_RANDOM,%d", &random)) + mpd_run_random(conn, random); + } + else if(!strncmp((const char *)in, MPD_API_TOGGLE_REPEAT, sizeof(MPD_API_TOGGLE_REPEAT)-1)) { + unsigned repeat; + if(sscanf(in, "MPD_API_TOGGLE_REPEAT,%d", &repeat)) + mpd_run_repeat(conn, repeat); + } + else if(!strncmp((const char *)in, MPD_API_TOGGLE_CONSUME, sizeof(MPD_API_TOGGLE_CONSUME)-1)) { + unsigned consume; + if(sscanf(in, "MPD_API_TOGGLE_CONSUME,%d", &consume)) + mpd_run_consume(conn, consume); + } + else if(!strncmp((const char *)in, MPD_API_TOGGLE_SINGLE, sizeof(MPD_API_TOGGLE_SINGLE)-1)) { + unsigned single; + if(sscanf(in, "MPD_API_TOGGLE_SINGLE,%d", &single)) + mpd_run_single(conn, single); + } else if(!strncmp((const char *)in, MPD_API_SET_VOLUME, sizeof(MPD_API_SET_VOLUME)-1)) { unsigned int volume; if(sscanf(in, "MPD_API_SET_VOLUME,%ud", &volume) && volume < 100) mpd_run_set_volume(conn, volume); } + else if(!strncmp((const char *)in, MPD_API_GET_BROWSE, sizeof(MPD_API_GET_BROWSE)-1)) { + char *dir; + if(sscanf(in, "MPD_API_GET_BROWSE,%m[^\t\n]", &dir) && dir != NULL) { + printf("sending '%s'\n", dir); + pss->do_send |= DO_SEND_BROWSE; + pss->browse_path = dir; + } + } + break; default: @@ -166,19 +197,19 @@ char* mpd_get_title(struct mpd_song const *song) ptr = str; while(*ptr++ != '\0') if(*ptr=='"') - *ptr=' '; + *ptr='\''; - return str; + return basename(str); } -int mpd_put_state(char* buffer) +int mpd_put_state(char *buffer) { struct mpd_status *status; int len; status = mpd_run_status(conn); if (!status) { - lwsl_err("MPD status: %s\n", mpd_connection_get_error_message(conn)); + lwsl_err("MPD mpd_run_status: %s\n", mpd_connection_get_error_message(conn)); mpd_conn_state = MPD_FAILURE; return 0; } @@ -202,43 +233,48 @@ int mpd_put_state(char* buffer) mpd_status_get_song_id(status)); queue_version = mpd_status_get_queue_version(status); - printf("buffer: %s\n", buffer); mpd_status_free(status); return len; } -int mpd_put_current_song(char* buffer) +int mpd_put_current_song(char *buffer) { struct mpd_song *song; int len; song = mpd_run_current_song(conn); - if (song != NULL) { - len = snprintf(buffer, MAX_SIZE, "{\"type\": \"current_song\", \"data\": {" - "{\"id\":%d, \"duration\":%d, \"title\":\"%s\"},", - mpd_song_get_id(song), - mpd_song_get_duration(song), - mpd_get_title(song) - ); - mpd_song_free(song); - } + if(song == NULL) + return 0; + + len = snprintf(buffer, MAX_SIZE, "{\"type\": \"current_song\", \"data\":" + "{\"id\":%d, \"pos\":%d, \"duration\":%d, \"title\":\"%s\"}}", + mpd_song_get_id(song), + mpd_song_get_pos(song), + mpd_song_get_duration(song), + mpd_get_title(song) + ); + mpd_song_free(song); mpd_response_finish(conn); return len; } -int mpd_put_playlist(char* buffer) +int mpd_put_playlist(char *buffer) { char *cur = buffer; const char *end = buffer + MAX_SIZE; struct mpd_entity *entity; - struct mpd_song const *song; - mpd_send_list_queue_meta(conn); + if (!mpd_send_list_queue_meta(conn)) { + lwsl_err("MPD mpd_send_list_queue_meta: %s\n", mpd_connection_get_error_message(conn)); + mpd_conn_state = MPD_FAILURE; + return 0; + } cur += snprintf(cur, end - cur, "{\"type\": \"playlist\", \"data\": [ "); - for(entity = mpd_recv_entity(conn); entity; entity = mpd_recv_entity(conn)) { + while((entity = mpd_recv_entity(conn)) != NULL) { + const struct mpd_song *song; if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) { song = mpd_entity_get_song(entity); @@ -250,13 +286,74 @@ int mpd_put_playlist(char* buffer) mpd_get_title(song) ); } - mpd_entity_free(entity); } /* remove last ',' */ cur--; cur += snprintf(cur, end - cur, "] }"); - printf("buffer: %s\n", buffer); + return cur - buffer; +} + +int mpd_put_browse(char *buffer, char *path) +{ + char *cur = buffer; + const char *end = buffer + MAX_SIZE; + struct mpd_entity *entity; + + if (!mpd_send_list_meta(conn, path)) { + lwsl_err("MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(conn)); + mpd_conn_state = MPD_FAILURE; + return 0; + } + cur += snprintf(cur, end - cur, "{\"type\":\"browse\",\"data\":[ "); + + while((entity = mpd_recv_entity(conn)) != NULL) { + const struct mpd_song *song; + const struct mpd_directory *dir; + const struct mpd_playlist *pl; + + switch (mpd_entity_get_type(entity)) { + case MPD_ENTITY_TYPE_UNKNOWN: + break; + + case MPD_ENTITY_TYPE_SONG: + song = mpd_entity_get_song(entity); + cur += snprintf(cur, end - cur, + "{\"type\":\"song\",\"uri\":\"%s\",\"duration\":%d,\"title\":\"%s\"},", + mpd_song_get_uri(song), + mpd_song_get_duration(song), + mpd_get_title(song) + ); + break; + + case MPD_ENTITY_TYPE_DIRECTORY: + dir = mpd_entity_get_directory(entity); + cur += snprintf(cur, end - cur, + "{\"type\":\"directory\",\"dir\":\"%s\"},", + mpd_directory_get_path(dir) + ); + break; + + case MPD_ENTITY_TYPE_PLAYLIST: + pl = mpd_entity_get_playlist(entity); + cur += snprintf(cur, end - cur, + "{\"type\":\"playlist\",\"plist\":\"%s\"},", + mpd_playlist_get_path(pl) + ); + break; + } + mpd_entity_free(entity); + } + + if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(conn)) { + lwsl_err("MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(conn)); + mpd_conn_state = MPD_FAILURE; + return 0; + } + + /* remove last ',' */ + cur--; + cur += snprintf(cur, end - cur, "] }"); return cur - buffer; } diff --git a/src/mpd_client.h b/src/mpd_client.h index b411231..1fe6313 100644 --- a/src/mpd_client.h +++ b/src/mpd_client.h @@ -3,12 +3,15 @@ #define DO_SEND_STATE (1 << 0) #define DO_SEND_PLAYLIST (1 << 1) #define DO_SEND_TRACK_INFO (1 << 2) +#define DO_SEND_BROWSE (1 << 3) + struct libwebsocket_protocols *protocol_array; struct per_session_data__ympd { int do_send; unsigned queue_version; + char *browse_path; }; enum mpd_conn_states { @@ -17,11 +20,11 @@ enum mpd_conn_states { MPD_CONNECTED }; -#define MPD_API_GET_STATE "MPD_API_GET_STATE" #define MPD_API_GET_SEEK "MPD_API_GET_SEEK" #define MPD_API_GET_PLAYLIST "MPD_API_GET_PLAYLIST" #define MPD_API_GET_TRACK_INFO "MPD_API_GET_TRACK_INFO" -#define MPD_API_ADD_TRACK "MPD_API_GET_STATE" +#define MPD_API_GET_BROWSE "MPD_API_GET_BROWSE" +#define MPD_API_ADD_TRACK "MPD_API_ADD_TRACK" #define MPD_API_SET_VOLUME "MPD_API_SET_VOLUME" #define MPD_API_SET_PAUSE "MPD_API_SET_PAUSE" #define MPD_API_SET_PLAY "MPD_API_SET_PLAY" @@ -30,6 +33,10 @@ enum mpd_conn_states { #define MPD_API_SET_NEXT "MPD_API_SET_PREV" #define MPD_API_SET_PREV "MPD_API_SET_NEXT" #define MPD_API_UPDATE_DB "MPD_API_UPDATE_DB" +#define MPD_API_TOGGLE_RANDOM "MPD_API_TOGGLE_RANDOM" +#define MPD_API_TOGGLE_CONSUME "MPD_API_TOGGLE_CONSUME" +#define MPD_API_TOGGLE_SINGLE "MPD_API_TOGGLE_SINGLE" +#define MPD_API_TOGGLE_REPEAT "MPD_API_TOGGLE_REPEAT" @@ -39,6 +46,7 @@ int callback_ympd(struct libwebsocket_context *context, void *user, void *in, size_t len); void mpd_loop(); -int mpd_put_state(char* buffer); -int mpd_put_current_song(char* buffer); -int mpd_put_playlist(char* buffer); +int mpd_put_state(char *buffer); +int mpd_put_current_song(char *buffer); +int mpd_put_playlist(char *buffer); +int mpd_put_browse(char *buffer, char *path); \ No newline at end of file
#
" + obj.data[song].pos + ""+ obj.data[song].title +""+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +"
"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds + + "
" + obj.data[item].dir +"
" + obj.data[item].title +""+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +"