From f42bbd37e12502c53d6eaa6c2e1f2b4d5ad84500 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 14 Nov 2013 23:14:58 +0100 Subject: [PATCH] Add basic support for playqueue-contents (real reply) and add placeholder for playqueue-edit --- src/httpd_dacp.c | 141 +++++++++++++++++++++++++++++++++++++++++------ src/player.c | 28 ++-------- src/player.h | 23 +++++++- 3 files changed, 151 insertions(+), 41 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 0393ba35..93725fb0 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -712,7 +712,7 @@ dacp_reply_ctrlint(struct evhttp_request *req, struct evbuffer *evbuf, char **ur dmap_add_char(evbuf, "casu", 1); /* 9, unknown */ dmap_add_char(evbuf, "ceSG", 1); /* 9, unknown */ dmap_add_char(evbuf, "cmrl", 1); /* 9, unknown */ - dmap_add_long(evbuf, "ceSX", 3); /* 16, unknown dacp - announce support for playqueue-contents */ + dmap_add_long(evbuf, "ceSX", (1 << 1 | 1)); /* 16, unknown dacp - lowest bit announces support for playqueue-contents/-edit */ httpd_send_reply(req, HTTP_OK, "OK", evbuf); } @@ -1131,8 +1131,17 @@ static void dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { struct daap_session *s; + struct evbuffer *song; + struct evbuffer *songlist; + struct evbuffer *playlists; + struct media_file_info *mfi; + struct player_source *ps; + struct player_source *head; + struct player_status status; const char *param; int span; + int i; + int songlist_length; int ret; /* /ctrl-int/1/playqueue-contents?span=50&session-id=... */ @@ -1141,6 +1150,8 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, if (!s) return; + DPRINTF(E_DBG, L_DACP, "Fetching playqueue contents\n"); + param = evhttp_find_header(query, "span"); if (param) { @@ -1149,21 +1160,115 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, DPRINTF(E_LOG, L_DACP, "Invalid span value in playqueue-contents request\n"); } - /* Dummy reply */ - dmap_add_container(evbuf, "ceQR", 138); /* 8, size of contents */ - dmap_add_int(evbuf, "mstt", 200); /* 12, dmap.status */ - dmap_add_int(evbuf, "mtco", span); /* 12, dmap.specifiedtotalcount */ - dmap_add_int(evbuf, "mrco", 0); /* 12, dmap.returnedcount */ - dmap_add_char(evbuf, "ceQu", 0); /* 9, unknown dacp */ - dmap_add_container(evbuf, "mlcl", 67); /* 8, size of contents */ - dmap_add_container(evbuf, "ceQS", 59); /* 8, size of contents */ - dmap_add_container(evbuf, "mlit", 51); /* 8, size of contents */ - dmap_add_string(evbuf, "ceQk", "hist"); /* 12, unknown dacp - either hist or curr (or next?) */ - dmap_add_int(evbuf, "ceQi", 0xffffff38); /* 12, unknown dacp */ - dmap_add_int(evbuf, "ceQm", 200); /* 12, unknown dacp - status code? */ - dmap_add_string(evbuf, "ceQl", "History"); /* X - should be full localised name of hist/curr/next*/ - dmap_add_char(evbuf, "apsm", 0); /* 9, daap.playlistshufflemode - not part of mlcl container */ - dmap_add_char(evbuf, "aprm", 0); /* 9, daap.playlistrepeatmode - not part of mlcl container */ + songlist = NULL; + i = 0; + player_get_status(&status); + /* Get queue and make songlist only if playing or paused */ + if ((status.status != PLAY_STOPPED) && (head = player_queue_get())) + { + /* Fast forward to current playlist position */ + for (ps = head; (ps && i < status.pos_pl); i++) + ps = ps->pl_next; + /* Make song list for Up Next, begin with first song after playlist position */ + // TODO support for shuffle + songlist = evbuffer_new(); + while ((ps = ps->pl_next) && (ps != head) && (i - status.pos_pl < span)) + { + i++; + song = evbuffer_new(); + // TODO Out of memory check (song) + mfi = db_file_fetch_byid(ps->id); + dmap_add_container(song, "ceQs", 16); + dmap_add_raw_uint32(song, 1); /* Database */ + dmap_add_raw_uint32(song, 0); /* Unknown */ + dmap_add_raw_uint32(song, 0); /* Unknown */ + dmap_add_raw_uint32(song, mfi->id); + dmap_add_string(song, "ceQn", mfi->title); + dmap_add_string(song, "ceQr", mfi->album_artist); + dmap_add_string(song, "ceQa", mfi->album); + dmap_add_string(song, "ceQg", mfi->genre); + dmap_add_long(song, "asai", mfi->songalbumid); + dmap_add_int(song, "cmmk", mfi->media_kind); + dmap_add_int(song, "casa", 1); /* Unknown */ + dmap_add_int(song, "astm", mfi->song_length); + dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */ + dmap_add_char(song, "caks", 6); /* Unknown */ + dmap_add_int(song, "ceQI", i + 1); + + dmap_add_container(songlist, "mlit", EVBUFFER_LENGTH(song)); + ret = evbuffer_add_buffer(songlist, song); + evbuffer_free(song); + if (mfi) + free_mfi(mfi, 0); + // TODO Out of memory check (songlist) + } + } + + /* Playlists are hist, curr and main. Currently we don't support hist. */ + playlists = evbuffer_new(); + // TODO Out of memory check (playlists) + dmap_add_container(playlists, "mlit", 61); + dmap_add_string(playlists, "ceQk", "hist"); /* 12 */ + dmap_add_int(playlists, "ceQi", -200); /* 12 */ + dmap_add_int(playlists, "ceQm", 200); /* 12 */ + dmap_add_string(playlists, "ceQl", "Previously Played"); /* 25 = 8 + 17 */ + + if (songlist) + { + dmap_add_container(playlists, "mlit", 36); + dmap_add_string(playlists, "ceQk", "curr"); /* 12 */ + dmap_add_int(playlists, "ceQi", 0); /* 12 */ + dmap_add_int(playlists, "ceQm", 1); /* 12 */ + + dmap_add_container(playlists, "mlit", 69); + dmap_add_string(playlists, "ceQk", "main"); /* 12 */ + dmap_add_int(playlists, "ceQi", 1); /* 12 */ + dmap_add_int(playlists, "ceQm", i); /* 12 */ + dmap_add_string(playlists, "ceQl", "Up Next"); /* 15 = 8 + 7 */ + dmap_add_string(playlists, "ceQh", "from Music"); /* 18 = 8 + 10 */ + + songlist_length = EVBUFFER_LENGTH(songlist); + } + else + songlist_length = 0; + + /* Final construction of reply */ + dmap_add_container(evbuf, "ceQR", 79 + EVBUFFER_LENGTH(playlists) + songlist_length); /* size of entire container */ + dmap_add_int(evbuf, "mstt", 200); /* 12, dmap.status */ + dmap_add_int(evbuf, "mtco", abs(span)); /* 12 */ + dmap_add_int(evbuf, "mrco", i); /* 12 */ + dmap_add_char(evbuf, "ceQu", 0); /* 9 */ + dmap_add_container(evbuf, "mlcl", 8 + EVBUFFER_LENGTH(playlists) + songlist_length); /* 8 */ + dmap_add_container(evbuf, "ceQS", EVBUFFER_LENGTH(playlists)); /* 8 */ + ret = evbuffer_add_buffer(evbuf, playlists); + // TODO check ret value & memcheck evbuf + evbuffer_free(playlists); + if (songlist) + { + ret = evbuffer_add_buffer(evbuf, songlist); + evbuffer_free(songlist); + } + dmap_add_char(evbuf, "apsm", status.shuffle); /* 9, daap.playlistshufflemode - not part of mlcl container */ + dmap_add_char(evbuf, "aprm", status.repeat); /* 9, daap.playlistrepeatmode - not part of mlcl container */ + + httpd_send_reply(req, HTTP_OK, "OK", evbuf); +} + +static void +dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +{ + struct daap_session *s; + const char *param; + int span; + int ret; + + /* /ctrl-int/1/playqueue-edit?command=add&query='dmap.itemid:...'&queuefilter=album:...&sort=album&mode=1&session-id=... */ + + s = daap_session_find(req, query, evbuf); + if (!s) + return; + + // TODO httpd_send_reply(req, HTTP_OK, "OK", evbuf); } @@ -1672,6 +1777,10 @@ static struct uri_map dacp_handlers[] = .regexp = "^/ctrl-int/[[:digit:]]+/playqueue-contents$", .handler = dacp_reply_playqueuecontents }, + { + .regexp = "^/ctrl-int/[[:digit:]]+/playqueue-edit$", + .handler = dacp_reply_playqueueedit + }, { .regexp = "^/ctrl-int/[[:digit:]]+/nowplayingartwork$", .handler = dacp_reply_nowplayingartwork diff --git a/src/player.c b/src/player.c index de2e20e2..b82fe86a 100644 --- a/src/player.c +++ b/src/player.c @@ -114,26 +114,6 @@ struct player_command int raop_pending; }; -struct player_source -{ - uint32_t id; - - uint64_t stream_start; - uint64_t output_start; - uint64_t end; - - struct transcode_ctx *ctx; - - struct player_source *pl_next; - struct player_source *pl_prev; - - struct player_source *shuffle_next; - struct player_source *shuffle_prev; - - struct player_source *play_next; -}; - - /* Keep in sync with enum raop_devtype */ static const char *raop_devtype[] = { @@ -560,7 +540,6 @@ metadata_send(struct player_source *ps, int startup) raop_metadata_send(ps->id, rtptime, offset, startup); } - /* Audio sources */ /* Thread: httpd (DACP) */ static struct player_source * @@ -3163,7 +3142,6 @@ sync_command(struct player_command *cmd) return ret; } - /* Player API executed in the httpd (DACP) thread */ int player_get_status(struct player_status *status) @@ -3452,6 +3430,12 @@ player_shuffle_set(int enable) return ret; } +struct player_source * +player_queue_get(void) +{ + return source_head; +} + int player_queue_add(struct player_source *ps) { diff --git a/src/player.h b/src/player.h index 4f2d4b47..f477def0 100644 --- a/src/player.h +++ b/src/player.h @@ -55,8 +55,24 @@ struct player_status { typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg); typedef void (*player_status_handler)(void); -struct player_source; +struct player_source +{ + uint32_t id; + uint64_t stream_start; + uint64_t output_start; + uint64_t end; + + struct transcode_ctx *ctx; + + struct player_source *pl_next; + struct player_source *pl_prev; + + struct player_source *shuffle_next; + struct player_source *shuffle_prev; + + struct player_source *play_next; +}; int player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit); @@ -67,7 +83,6 @@ player_get_status(struct player_status *status); int player_now_playing(uint32_t *id); - void player_speaker_enumerate(spk_enum_cb cb, void *arg); @@ -115,6 +130,9 @@ player_queue_make_daap(const char *query, const char *sort); struct player_source * player_queue_make_pl(int plid, uint32_t *id); +struct player_source * +player_queue_get(void); + int player_queue_add(struct player_source *ps); @@ -124,7 +142,6 @@ player_queue_clear(void); void player_queue_plid(uint32_t plid); - void player_set_update_handler(player_status_handler handler);