From afa35ac55c2a698857bce007f36dfbebf6475afe Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 6 Nov 2013 23:52:19 +0100 Subject: [PATCH 01/15] Don't assume max_h==max_w (a target aspect ratio of 1) when sending artwork - Remote 4 will send max_w=128 and max_h=160 for artwork in the Album tab --- src/artwork.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/artwork.c b/src/artwork.c index 95e7b883..421773c7 100644 --- a/src/artwork.c +++ b/src/artwork.c @@ -594,27 +594,21 @@ artwork_get(char *filename, int max_w, int max_h, int format, struct evbuffer *e need_rescale = 1; - /* Determine width/height -- assuming max_w == max_h */ - if ((src->width <= max_w) && (src->height <= max_h)) + if ((src->width <= max_w) && (src->height <= max_h)) /* Smaller than target */ { need_rescale = 0; target_w = src->width; target_h = src->height; } - else if (src->width > src->height) + else if (src->width * max_h > src->height * max_w) /* Wider aspect ratio than target */ { target_w = max_w; - target_h = (double)max_h * ((double)src->height / (double)src->width); + target_h = (double)max_w * ((double)src->height / (double)src->width); } - else if (src->height > src->width) + else /* Taller or equal aspect ratio */ { - target_h = max_h; - target_w = (double)max_w * ((double)src->width / (double)src->height); - } - else - { - target_w = max_w; + target_w = (double)max_h * ((double)src->width / (double)src->height); target_h = max_h; } From 0d5fef60e67d5c3b174a1c3249800638bcfeb8a2 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 7 Nov 2013 22:45:12 +0100 Subject: [PATCH 02/15] Add empty DACP property get for media-kind and extended-media-kind --- src/dacp_prop.gperf | 34 ++++++++++++++++++---------------- src/httpd_dacp.c | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/dacp_prop.gperf b/src/dacp_prop.gperf index 72049c49..44f28d3d 100644 --- a/src/dacp_prop.gperf +++ b/src/dacp_prop.gperf @@ -10,19 +10,21 @@ %omit-struct-type struct dacp_prop_map; %% -"dmcp.volume", dacp_propget_volume, dacp_propset_volume -"dacp.playerstate", dacp_propget_playerstate, NULL -"dacp.nowplaying", dacp_propget_nowplaying, NULL -"dacp.playingtime", dacp_propget_playingtime, dacp_propset_playingtime -"dacp.volumecontrollable", dacp_propget_volumecontrollable, NULL -"dacp.availableshufflestates", dacp_propget_availableshufflestates, NULL -"dacp.availablerepeatstates", dacp_propget_availablerepeatstates, NULL -"dacp.shufflestate", dacp_propget_shufflestate, dacp_propset_shufflestate -"dacp.repeatstate", dacp_propget_repeatstate, dacp_propset_repeatstate -"dacp.userrating", NULL, dacp_propset_userrating -"dacp.fullscreenenabled", dacp_propget_fullscreenenabled, NULL -"dacp.fullscreen", dacp_propget_fullscreen, NULL -"dacp.visualizerenabled", dacp_propget_visualizerenabled, NULL -"dacp.visualizer", dacp_propget_visualizer, NULL -"com.apple.itunes.itms-songid", dacp_propget_itms_songid, NULL -"com.apple.itunes.has-chapter-data", dacp_propget_haschapterdata, NULL +"dmcp.volume", dacp_propget_volume, dacp_propset_volume +"dacp.playerstate", dacp_propget_playerstate, NULL +"dacp.nowplaying", dacp_propget_nowplaying, NULL +"dacp.playingtime", dacp_propget_playingtime, dacp_propset_playingtime +"dacp.volumecontrollable", dacp_propget_volumecontrollable, NULL +"dacp.availableshufflestates", dacp_propget_availableshufflestates, NULL +"dacp.availablerepeatstates", dacp_propget_availablerepeatstates, NULL +"dacp.shufflestate", dacp_propget_shufflestate, dacp_propset_shufflestate +"dacp.repeatstate", dacp_propget_repeatstate, dacp_propset_repeatstate +"dacp.userrating", NULL, dacp_propset_userrating +"dacp.fullscreenenabled", dacp_propget_fullscreenenabled, NULL +"dacp.fullscreen", dacp_propget_fullscreen, NULL +"dacp.visualizerenabled", dacp_propget_visualizerenabled, NULL +"dacp.visualizer", dacp_propget_visualizer, NULL +"com.apple.itunes.itms-songid", dacp_propget_itms_songid, NULL +"com.apple.itunes.has-chapter-data", dacp_propget_haschapterdata, NULL +"com.apple.itunes.mediakind", dacp_propget_mediakind, NULL +"com.apple.itunes.extended-media-kind", dacp_propget_extendedmediakind, NULL diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 97e17a76..e62a3178 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -115,6 +115,10 @@ static void dacp_propget_itms_songid(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); static void dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +static void +dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); +static void +dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi); /* Forward - properties setters */ static void @@ -481,6 +485,17 @@ dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status // TODO } +static void +dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +{ + // TODO +} + +static void +dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) +{ + // TODO +} /* Properties setters */ static void From f71954eb43c5657b9832861b9c107a1832420f49 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 9 Nov 2013 23:51:36 +0100 Subject: [PATCH 03/15] Add dummy playqueue-contents support --- src/httpd_dacp.c | 86 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 19 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index e62a3178..5923a03b 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -689,27 +689,30 @@ dacp_propset_userrating(const char *value, struct evkeyvalq *query) static void dacp_reply_ctrlint(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { - dmap_add_container(evbuf, "caci", 127); /* 8 + len */ - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_char(evbuf, "muty", 0); /* 9 */ - dmap_add_int(evbuf, "mtco", 1); /* 12 */ - dmap_add_int(evbuf, "mrco", 1); /* 12 */ - dmap_add_container(evbuf, "mlcl", 125); /* 8 + len */ - dmap_add_container(evbuf, "mlit", 117); /* 8 + len */ - dmap_add_int(evbuf, "miid", 1); /* 12 */ /* Database ID */ - dmap_add_char(evbuf, "cmik", 1); /* 9 */ + /* /ctrl-int */ + /* If tags are added or removed container sizes should be adjusted too */ + dmap_add_container(evbuf, "caci", 194); /* 8, unknown dacp container - size of content */ + dmap_add_int(evbuf, "mstt", 200); /* 12, dmap.status */ + dmap_add_char(evbuf, "muty", 0); /* 9, dmap.updatetype */ + dmap_add_int(evbuf, "mtco", 1); /* 12, dmap.specifiedtotalcount */ + dmap_add_int(evbuf, "mrco", 1); /* 12, dmap.returnedcount */ + dmap_add_container(evbuf, "mlcl", 141); /* 8, dmap.listing - size of content */ + dmap_add_container(evbuf, "mlit", 133); /* 8, dmap.listingitem - size of content */ + dmap_add_int(evbuf, "miid", 1); /* 12, dmap.itemid - database ID */ + dmap_add_char(evbuf, "cmik", 1); /* 9, unknown */ - dmap_add_int(evbuf, "cmpr", (2 << 16 | 1)); /* 12 */ - dmap_add_int(evbuf, "capr", (2 << 16 | 2)); /* 12 */ + dmap_add_int(evbuf, "cmpr", (2 << 16 | 2)); /* 12, dmcp.protocolversion */ + dmap_add_int(evbuf, "capr", (2 << 16 | 5)); /* 12, dacp.protocolversion */ - dmap_add_char(evbuf, "cmsp", 1); /* 9 */ - dmap_add_char(evbuf, "aeFR", 0x64); /* 9 */ - dmap_add_char(evbuf, "cmsv", 1); /* 9 */ - dmap_add_char(evbuf, "cass", 1); /* 9 */ - dmap_add_char(evbuf, "caov", 1); /* 9 */ - dmap_add_char(evbuf, "casu", 1); /* 9 */ - dmap_add_char(evbuf, "ceSG", 1); /* 9 */ - dmap_add_char(evbuf, "cmrl", 1); /* 9 */ + dmap_add_char(evbuf, "cmsp", 1); /* 9, unknown */ + dmap_add_char(evbuf, "aeFR", 0x64); /* 9, unknown */ + dmap_add_char(evbuf, "cmsv", 1); /* 9, unknown */ + dmap_add_char(evbuf, "cass", 1); /* 9, unknown */ + dmap_add_char(evbuf, "caov", 1); /* 9, unknown */ + 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 */ httpd_send_reply(req, HTTP_OK, "OK", evbuf); } @@ -1124,6 +1127,47 @@ dacp_reply_playresume(struct evhttp_request *req, struct evbuffer *evbuf, char * evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf); } +static void +dacp_reply_playqueuecontents(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-contents?span=50&session-id=... */ + + s = daap_session_find(req, query, evbuf); + if (!s) + return; + + param = evhttp_find_header(query, "span"); + if (param) + { + ret = safe_atoi32(param, &span); + if (ret < 0) + 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_add_int(evbuf, "mtco", 0); /* 12 */ + dmap_add_char(evbuf, "ceQu", 0); /* 9 */ + dmap_add_container(evbuf, "mlcl", 67); /* 8 */ + dmap_add_container(evbuf, "ceQS", 59); /* 8 */ + dmap_add_container(evbuf, "mlit", 51); /* 8 */ + dmap_add_string(evbuf, "ceQk", "hist"); /* 12 */ + dmap_add_int(evbuf, "ceQi", 0xffffff38); /* 12 */ + dmap_add_int(evbuf, "ceQm", 200); /* 12 */ + dmap_add_string(evbuf, "ceQl", "History"); /* 27 */ + 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 */ + + httpd_send_reply(req, HTTP_OK, "OK", evbuf); +} + static void dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { @@ -1624,6 +1668,10 @@ static struct uri_map dacp_handlers[] = .regexp = "^/ctrl-int/[[:digit:]]+/playstatusupdate$", .handler = dacp_reply_playstatusupdate }, + { + .regexp = "^/ctrl-int/[[:digit:]]+/playqueue-contents$", + .handler = dacp_reply_playqueuecontents + }, { .regexp = "^/ctrl-int/[[:digit:]]+/nowplayingartwork$", .handler = dacp_reply_nowplayingartwork From 00a790c0c1ea287ed3c720acc4c42cf89fe09072 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 9 Nov 2013 23:58:18 +0100 Subject: [PATCH 04/15] Fix typo and add more comments --- src/httpd_dacp.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 5923a03b..0393ba35 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -1152,16 +1152,16 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, /* 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_add_int(evbuf, "mtco", 0); /* 12 */ - dmap_add_char(evbuf, "ceQu", 0); /* 9 */ - dmap_add_container(evbuf, "mlcl", 67); /* 8 */ - dmap_add_container(evbuf, "ceQS", 59); /* 8 */ - dmap_add_container(evbuf, "mlit", 51); /* 8 */ - dmap_add_string(evbuf, "ceQk", "hist"); /* 12 */ - dmap_add_int(evbuf, "ceQi", 0xffffff38); /* 12 */ - dmap_add_int(evbuf, "ceQm", 200); /* 12 */ - dmap_add_string(evbuf, "ceQl", "History"); /* 27 */ + 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 */ From 28b485c595ef42190273f4bdf3c22eb8f891c638 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 10 Nov 2013 12:35:24 +0100 Subject: [PATCH 05/15] Fix malformed DAAP packages --- src/httpd_daap.c | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/httpd_daap.c b/src/httpd_daap.c index a4e0542f..07bed0cf 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -438,11 +438,9 @@ daap_sort_build(struct sort_ctx *ctx, char *str) return 0; } -static int -daap_sort_finalize(struct sort_ctx *ctx, struct evbuffer *evbuf) +static void +daap_sort_finalize(struct sort_ctx *ctx) { - int ret; - /* Add current entry, if any */ if (ctx->mshc != -1) { @@ -461,13 +459,6 @@ daap_sort_finalize(struct sort_ctx *ctx, struct evbuffer *evbuf) dmap_add_short(ctx->headerlist, "mshc", '0'); /* 10 */ dmap_add_int(ctx->headerlist, "mshi", ctx->mshi); /* 12 */ dmap_add_int(ctx->headerlist, "mshn", ctx->misc_mshn); /* 12 */ - - dmap_add_container(evbuf, "mshl", EVBUFFER_LENGTH(ctx->headerlist)); - ret = evbuffer_add_buffer(evbuf, ctx->headerlist); - if (ret < 0) - return -1; - - return 0; } @@ -1174,14 +1165,17 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, /* Add header to evbuf, add songlist to evbuf */ if (sort_headers) - dmap_add_container(evbuf, tag, EVBUFFER_LENGTH(songlist) + EVBUFFER_LENGTH(sctx->headerlist) + 53); + { + daap_sort_finalize(sctx); + dmap_add_container(evbuf, tag, EVBUFFER_LENGTH(songlist) + EVBUFFER_LENGTH(sctx->headerlist) + 61); + } else dmap_add_container(evbuf, tag, EVBUFFER_LENGTH(songlist) + 53); dmap_add_int(evbuf, "mstt", 200); /* 12 */ dmap_add_char(evbuf, "muty", 0); /* 9 */ dmap_add_int(evbuf, "mtco", qp.results); /* 12 */ dmap_add_int(evbuf, "mrco", nsongs); /* 12 */ - dmap_add_container(evbuf, "mlcl", EVBUFFER_LENGTH(songlist)); + dmap_add_container(evbuf, "mlcl", EVBUFFER_LENGTH(songlist)); /* 8 */ db_query_end(&qp); @@ -1201,7 +1195,8 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, if (sort_headers) { - ret = daap_sort_finalize(sctx, evbuf); + dmap_add_container(evbuf, "mshl", EVBUFFER_LENGTH(sctx->headerlist)); /* 8 */ + ret = evbuffer_add_buffer(evbuf, sctx->headerlist); daap_sort_context_free(sctx); if (ret < 0) @@ -1730,7 +1725,10 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri /* Add header to evbuf, add grouplist to evbuf */ if (sort_headers) - dmap_add_container(evbuf, tag, EVBUFFER_LENGTH(grouplist) + EVBUFFER_LENGTH(sctx->headerlist) + 53); + { + daap_sort_finalize(sctx); + dmap_add_container(evbuf, tag, EVBUFFER_LENGTH(grouplist) + EVBUFFER_LENGTH(sctx->headerlist) + 61); + } else dmap_add_container(evbuf, tag, EVBUFFER_LENGTH(grouplist) + 53); @@ -1738,7 +1736,7 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri dmap_add_char(evbuf, "muty", 0); /* 9 */ dmap_add_int(evbuf, "mtco", qp.results); /* 12 */ dmap_add_int(evbuf,"mrco", ngrp); /* 12 */ - dmap_add_container(evbuf, "mlcl", EVBUFFER_LENGTH(grouplist)); + dmap_add_container(evbuf, "mlcl", EVBUFFER_LENGTH(grouplist)); /* 8 */ db_query_end(&qp); @@ -1758,12 +1756,13 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri if (sort_headers) { - ret = daap_sort_finalize(sctx, evbuf); + dmap_add_container(evbuf, "mshl", EVBUFFER_LENGTH(sctx->headerlist)); /* 8 */ + ret = evbuffer_add_buffer(evbuf, sctx->headerlist); daap_sort_context_free(sctx); if (ret < 0) { - DPRINTF(E_LOG, L_DAAP, "Could not add sort headers to DAAP browse reply\n"); + DPRINTF(E_LOG, L_DAAP, "Could not add sort headers to DAAP groups reply\n"); dmap_send_error(req, tag, "Out of memory"); return; @@ -1936,7 +1935,10 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri } if (sort_headers) - dmap_add_container(evbuf, "abro", EVBUFFER_LENGTH(itemlist) + EVBUFFER_LENGTH(sctx->headerlist) + 44); + { + daap_sort_finalize(sctx); + dmap_add_container(evbuf, "abro", EVBUFFER_LENGTH(itemlist) + EVBUFFER_LENGTH(sctx->headerlist) + 52); + } else dmap_add_container(evbuf, "abro", EVBUFFER_LENGTH(itemlist) + 44); @@ -1944,7 +1946,7 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri dmap_add_int(evbuf, "mtco", qp.results); /* 12 */ dmap_add_int(evbuf, "mrco", nitems); /* 12 */ - dmap_add_container(evbuf, tag, EVBUFFER_LENGTH(itemlist)); + dmap_add_container(evbuf, tag, EVBUFFER_LENGTH(itemlist)); /* 8 */ db_query_end(&qp); @@ -1964,7 +1966,8 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri if (sort_headers) { - ret = daap_sort_finalize(sctx, evbuf); + dmap_add_container(evbuf, "mshl", EVBUFFER_LENGTH(sctx->headerlist)); /* 8 */ + ret = evbuffer_add_buffer(evbuf, sctx->headerlist); daap_sort_context_free(sctx); if (ret < 0) From 7bb2fef25fb2b8a28639cea00c695d86a8f5cea8 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 14 Nov 2013 23:08:20 +0100 Subject: [PATCH 06/15] Lower log level to spam for DMAP investigation and add dmap_add_raw_uint32() for later use --- src/dmap_common.c | 18 ++++++++++++++++-- src/dmap_common.h | 3 +++ src/httpd_daap.c | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/dmap_common.c b/src/dmap_common.c index 7dfc7db2..421b74cb 100644 --- a/src/dmap_common.c +++ b/src/dmap_common.c @@ -167,6 +167,20 @@ dmap_add_literal(struct evbuffer *evbuf, char *tag, char *str, int len) evbuffer_add(evbuf, str, len); } +void +dmap_add_raw_uint32(struct evbuffer *evbuf, uint32_t val) +{ + unsigned char buf[4]; + + /* Value */ + buf[0] = (val >> 24) & 0xff; + buf[1] = (val >> 16) & 0xff; + buf[2] = (val >> 8) & 0xff; + buf[3] = val & 0xff; + + evbuffer_add(evbuf, buf, sizeof(buf)); +} + void dmap_add_string(struct evbuffer *evbuf, char *tag, const char *str) { @@ -427,7 +441,7 @@ dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, stru continue; } - DPRINTF(E_DBG, L_DAAP, "Investigating %s\n", df->desc); + DPRINTF(E_SPAM, L_DAAP, "Investigating %s\n", df->desc); strval = (char **) ((char *)dbmfi + dfm->mfi_offset); @@ -476,7 +490,7 @@ dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, stru dmap_add_field(song, df, *strval, val); - DPRINTF(E_DBG, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval); + DPRINTF(E_SPAM, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval); } if (sort_tags) diff --git a/src/dmap_common.h b/src/dmap_common.h index 67ab2649..fd936b52 100644 --- a/src/dmap_common.h +++ b/src/dmap_common.h @@ -67,6 +67,9 @@ dmap_add_char(struct evbuffer *evbuf, char *tag, char val); void dmap_add_literal(struct evbuffer *evbuf, char *tag, char *str, int len); +void +dmap_add_raw_uint32(struct evbuffer *evbuf, uint32_t val); + void dmap_add_string(struct evbuffer *evbuf, char *tag, const char *str); diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 07bed0cf..85b55990 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -1407,7 +1407,7 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char ** dmap_add_field(playlist, df, *strval, 0); - DPRINTF(E_DBG, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval); + DPRINTF(E_SPAM, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval); } /* Item count (mimc) */ From f42bbd37e12502c53d6eaa6c2e1f2b4d5ad84500 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 14 Nov 2013 23:14:58 +0100 Subject: [PATCH 07/15] 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); From 0576643ae563de46bb93161ece53dada894f553c Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 16 Nov 2013 22:33:12 +0100 Subject: [PATCH 08/15] Use artist in Up Next --- src/httpd_dacp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 93725fb0..dee14160 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -1184,7 +1184,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, 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, "ceQr", mfi->artist); dmap_add_string(song, "ceQa", mfi->album); dmap_add_string(song, "ceQg", mfi->genre); dmap_add_long(song, "asai", mfi->songalbumid); From d85eb171cbc42120f1e822dccfee460e4ccf8813 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 16 Nov 2013 22:52:53 +0100 Subject: [PATCH 09/15] Lower log level to spam for metadata message in httpd_daap.c --- src/httpd_daap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 85b55990..96510814 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -1653,7 +1653,7 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri dmap_add_field(group, df, *strval, 0); - DPRINTF(E_DBG, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval); + DPRINTF(E_SPAM, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval); } if (sort_headers) From ecd8b5d940e8c19f8d0f24da085dd1340078c586 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 17 Nov 2013 23:15:50 +0100 Subject: [PATCH 10/15] Misc work on httpd_dacp for support of playqueue-* - playqueue-edit still working poorly, but added documentation --- src/httpd_dacp.c | 132 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 29 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index dee14160..43f81197 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -158,31 +158,14 @@ static int seek_target; static void dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi) { - char canp[16]; - if ((status->status == PLAY_STOPPED) || !mfi) return; - memset(canp, 0, sizeof(canp)); - - canp[3] = 1; /* 0-3 database ID */ - - canp[4] = (status->plid >> 24) & 0xff; - canp[5] = (status->plid >> 16) & 0xff; - canp[6] = (status->plid >> 8) & 0xff; - canp[7] = status->plid & 0xff; - - canp[8] = (status->pos_pl >> 24) & 0xff; /* 8-11 position in playlist */ - canp[9] = (status->pos_pl >> 16) & 0xff; - canp[10] = (status->pos_pl >> 8) & 0xff; - canp[11] = status->pos_pl & 0xff; - - canp[12] = (status->id >> 24) & 0xff; /* 12-15 track ID */ - canp[13] = (status->id >> 16) & 0xff; - canp[14] = (status->id >> 8) & 0xff; - canp[15] = status->id & 0xff; - - dmap_add_literal(evbuf, "canp", canp, sizeof(canp)); + dmap_add_container(evbuf, "canp", 16); + dmap_add_raw_uint32(evbuf, 1); /* Database */ + dmap_add_raw_uint32(evbuf, status->plid); + dmap_add_raw_uint32(evbuf, status->pos_pl); + dmap_add_raw_uint32(evbuf, status->id); dmap_add_string(evbuf, "cann", mfi->title); dmap_add_string(evbuf, "cana", mfi->artist); @@ -1152,6 +1135,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, DPRINTF(E_DBG, L_DACP, "Fetching playqueue contents\n"); + span = 50; /* Default */ param = evhttp_find_header(query, "span"); if (param) { @@ -1172,7 +1156,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, /* 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)) + while ((ps = ps->pl_next) && (ps != head) && (i - status.pos_pl < abs(span))) { i++; song = evbuffer_new(); @@ -1180,8 +1164,8 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, 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, status.plid); + dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */ dmap_add_raw_uint32(song, mfi->id); dmap_add_string(song, "ceQn", mfi->title); dmap_add_string(song, "ceQr", mfi->artist); @@ -1258,19 +1242,109 @@ static void dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { struct daap_session *s; + struct player_status status; + struct player_source *ps; + const char *cuequery; + const char *sort; const char *param; - int span; + uint32_t id; + int mode; int ret; - /* /ctrl-int/1/playqueue-edit?command=add&query='dmap.itemid:...'&queuefilter=album:...&sort=album&mode=1&session-id=... */ + /* Variations of /ctrl-int/1/playqueue-edit and expected behaviour + User selected play (album or artist tab): + ?command=add&query='...'&sort=album&mode=1&session-id=... + -> clear queue, play query results + User selected track (album tab): + ?command=add&query='dmap.itemid:...'&queuefilter=album:...&sort=album&mode=1&session-id=... + -> clear queue, play itemid and the rest of album + User selected track (song tab): + ?command=add&query='dmap.itemid:...'&queuefilter=playlist:...&sort=name&mode=1&session-id=... + -> clear queue, play itemid and the rest of playlist + User selected track (playlist tab): + ?command=add&query='dmap.containeritemid:...'&queuefilter=playlist:...&sort=physical&mode=1&session-id=... + -> clear queue, play containeritemid and the rest of playlist + User selected track (artist tab): + ?command=add&query='dmap.itemid:...'&mode=1&session-id=... + -> clear queue, play itemid and the rest of artist tracks + User selected shuffle (artist tab): + ?command=add&query='...'&sort=album&mode=2&session-id=... + -> clear queue, play shuffled query results + User selected add item to queue: + ?command=add&query='...'&sort=album&mode=0&session-id=... + -> add query results to queue + User selected play next song (album tab) + ?command=add&query='daap.songalbumid:...'&sort=album&mode=3&session-id=... + -> replace queue from after current song with query results + User selected track in queue: + ?command=playnow&index=...&session-id=... + -> play index + */ s = daap_session_find(req, query, evbuf); if (!s) return; - // TODO + param = evhttp_find_header(query, "mode"); + if (param) + { + ret = safe_atoi32(param, &mode); + if (ret < 0) + DPRINTF(E_LOG, L_DACP, "Invalid mode value in playqueue-edit request\n"); + else if (mode == 1) + { + player_playback_stop(); - httpd_send_reply(req, HTTP_OK, "OK", evbuf); + player_queue_clear(); + } + } + + cuequery = evhttp_find_header(query, "query"); + if (cuequery) + { + sort = evhttp_find_header(query, "sort"); + + ps = player_queue_make_daap(cuequery, sort); + if (!ps) + { + DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); + + dmap_send_error(req, "cmst", "Could not build song queue"); + return; + } + + player_queue_add(ps); + } + else + { + player_get_status(&status); + + if (status.status != PLAY_STOPPED) + player_playback_stop(); + } + + /* + param = evhttp_find_header(query, "dacp.shufflestate"); + if (param) + dacp_propset_shufflestate(param, NULL);*/ + + id = 0; + param = evhttp_find_header(query, "index"); + if (param) + { + ret = safe_atou32(param, &id); + if (ret < 0) + DPRINTF(E_LOG, L_DACP, "Invalid index (%s) in playqueue-edit request\n", param); + } + + ret = player_playback_start(&id); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); + + dmap_send_error(req, "cmst", "Playback failed to start"); + return; + } } static void From a4c16741d9b88791025491fd8fa97f30a782cb94 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 21 Nov 2013 23:33:03 +0100 Subject: [PATCH 11/15] Better support for playqueue-edit (command add, mode 1) - reply message not fixed --- src/daap_query.gperf | 1 + src/httpd_dacp.c | 149 ++++++++++++++++++++++++++----------------- src/player.c | 149 ++++++++++++++++++++++++++++++++++++++++--- src/player.h | 5 +- 4 files changed, 232 insertions(+), 72 deletions(-) diff --git a/src/daap_query.gperf b/src/daap_query.gperf index fb61d635..89f4d6c3 100644 --- a/src/daap_query.gperf +++ b/src/daap_query.gperf @@ -12,6 +12,7 @@ struct dmap_query_field_map; %% "dmap.itemname", "f.title", 0 "dmap.itemid", "f.id", 1 +"dmap.containeritemid", "f.id", 1 "daap.songalbum", "f.album", 0 "daap.songalbumid", "f.songalbumid", 1 "daap.songartist", "f.album_artist", 0 diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 43f81197..431ab13c 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -733,8 +733,8 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u { sort = evhttp_find_header(query, "sort"); - ps = player_queue_make_daap(cuequery, sort); - if (!ps) + ret = player_queue_make_daap(&ps, cuequery, NULL, sort); + if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); @@ -1238,18 +1238,85 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, httpd_send_reply(req, HTTP_OK, "OK", evbuf); } +static void +dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +{ + struct player_source *ps; + const char *editquery; + const char *queuefilter; + const char *sort; + const char *param; + uint32_t idx; + int mode; + int ret; + + param = evhttp_find_header(query, "mode"); + if (param) + { + ret = safe_atoi32(param, &mode); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Invalid mode value in playqueue-edit request\n"); + + dmap_send_error(req, "cmst", "Invalid mode value in playqueue-edit request"); + return; + } + } + + if (mode == 1) + { + player_playback_stop(); + player_queue_clear(); + } + + editquery = evhttp_find_header(query, "query"); + if (editquery) + { + queuefilter = evhttp_find_header(query, "queuefilter"); + sort = evhttp_find_header(query, "sort"); + + ret = player_queue_make_daap(&ps, editquery, queuefilter, sort); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); + + dmap_send_error(req, "cmst", "Could not build song queue"); + return; + } + + idx = ret; + + player_queue_add(ps); + } + else + { + DPRINTF(E_LOG, L_DACP, "Could not add song queue, DACP query missing\n"); + + dmap_send_error(req, "cmst", "Could not add song queue, DACP query missing"); + return; + } + + /* + param = evhttp_find_header(query, "dacp.shufflestate"); + if (param) + dacp_propset_shufflestate(param, NULL);*/ + + DPRINTF(E_DBG, L_DACP, "Song queue built, playback starting at index %" PRIu32 "\n", idx); + ret = player_playback_start(&idx); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); + + dmap_send_error(req, "cmst", "Playback failed to start"); + return; + } +} + static void dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { struct daap_session *s; - struct player_status status; - struct player_source *ps; - const char *cuequery; - const char *sort; const char *param; - uint32_t id; - int mode; - int ret; /* Variations of /ctrl-int/1/playqueue-edit and expected behaviour User selected play (album or artist tab): @@ -1285,64 +1352,26 @@ dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, cha if (!s) return; - param = evhttp_find_header(query, "mode"); - if (param) + param = evhttp_find_header(query, "command"); + if (!param) { - ret = safe_atoi32(param, &mode); - if (ret < 0) - DPRINTF(E_LOG, L_DACP, "Invalid mode value in playqueue-edit request\n"); - else if (mode == 1) - { - player_playback_stop(); + DPRINTF(E_LOG, L_DACP, "No command in playqueue-edit request\n"); - player_queue_clear(); - } + dmap_send_error(req, "cmst", "No command in cue request"); + return; } - cuequery = evhttp_find_header(query, "query"); - if (cuequery) - { - sort = evhttp_find_header(query, "sort"); - - ps = player_queue_make_daap(cuequery, sort); - if (!ps) - { - DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); - - dmap_send_error(req, "cmst", "Could not build song queue"); - return; - } - - player_queue_add(ps); - } + if (strcmp(param, "clear") == 0) + dacp_reply_cue_clear(req, evbuf, uri, query); // TODO this might give wrong reply container + else if (strcmp(param, "playnow") == 0) + dacp_reply_cue_play(req, evbuf, uri, query); // TODO this might give wrong reply container + else if (strcmp(param, "add") == 0) + dacp_reply_playqueueedit_add(req, evbuf, uri, query); else { - player_get_status(&status); + DPRINTF(E_LOG, L_DACP, "Unknown playqueue-edit command %s\n", param); - if (status.status != PLAY_STOPPED) - player_playback_stop(); - } - - /* - param = evhttp_find_header(query, "dacp.shufflestate"); - if (param) - dacp_propset_shufflestate(param, NULL);*/ - - id = 0; - param = evhttp_find_header(query, "index"); - if (param) - { - ret = safe_atou32(param, &id); - if (ret < 0) - DPRINTF(E_LOG, L_DACP, "Invalid index (%s) in playqueue-edit request\n", param); - } - - ret = player_playback_start(&id); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); - - dmap_send_error(req, "cmst", "Playback failed to start"); + dmap_send_error(req, "cmst", "Unknown command in cue request"); return; } } diff --git a/src/player.c b/src/player.c index b82fe86a..68483cff 100644 --- a/src/player.c +++ b/src/player.c @@ -635,32 +635,163 @@ player_queue_make(struct query_params *qp, const char *sort) return q_head; } -/* Thread: httpd (DACP) */ -struct player_source * -player_queue_make_daap(const char *query, const char *sort) +static int +fetch_first_query_match(const char *query, struct db_media_file_info *dbmfi) { struct query_params qp; - struct player_source *ps; + uint32_t id; + int ret; memset(&qp, 0, sizeof(struct query_params)); qp.type = Q_ITEMS; + qp.idx_type = I_FIRST; + qp.sort = S_NONE; qp.offset = 0; - qp.limit = 0; - + qp.limit = 1; qp.filter = daap_query_parse_sql(query); if (!qp.filter) { DPRINTF(E_LOG, L_PLAYER, "Improper DAAP query!\n"); - return NULL; + return -1; + } + + ret = db_query_start(&qp); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Could not start query\n"); + + goto no_query_start; + } + + if (((ret = db_query_fetch_file(&qp, dbmfi)) == 0) && (dbmfi->id)) + { + ret = safe_atou32(dbmfi->id, &id); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n"); + + goto no_result; + } + + DPRINTF(E_DBG, L_PLAYER, "Found index song\n"); + ret = 1; + } + else + { + DPRINTF(E_LOG, L_PLAYER, "No song matches query (num %d): %s\n", qp.results, qp.filter); + + goto no_result; + } + + no_result: + db_query_end(&qp); + + no_query_start: + if (qp.filter) + free(qp.filter); + if (ret == 1) + return 0; + else + return -1; +} + + +/* Thread: httpd (DACP) */ +int +player_queue_make_daap(struct player_source **head, const char *query, const char *queuefilter, const char *sort) +{ + struct query_params qp; + struct player_source *ps; + struct db_media_file_info dbmfi; + uint32_t id; + int64_t albumid; + int plid; + int idx; + int ret; + char buf[200]; + + /* If query doesn't give even a single result give up */ + ret = fetch_first_query_match(query, &dbmfi); + if (ret < 0) + return -1; + + memset(&qp, 0, sizeof(struct query_params)); + + qp.offset = 0; + qp.limit = 0; + + id = 0; + + if (queuefilter) + { + safe_atou32(dbmfi.id, &id); + if ((strlen(queuefilter) > 6) && (strncmp(queuefilter, "album:", 6) == 0)) + { + qp.type = Q_ITEMS; + ret = safe_atoi64(strchr(queuefilter, ':') + 1, &albumid); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid album id in queuefilter: %s\n", queuefilter); + + return -1; + } + snprintf(buf, sizeof(buf), "f.songalbumid = %" PRIi64, albumid); + qp.filter = strdup(buf); + } + else if ((strlen(queuefilter) > 9) && (strncmp(queuefilter, "playlist:", 9) == 0)) + { + qp.type = Q_PLITEMS; + ret = safe_atoi32(strchr(queuefilter, ':') + 1, &plid); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid playlist id in queuefilter: %s\n", queuefilter); + + return -1; + } + qp.id = plid; + qp.filter = strdup("1 = 1"); + } + else + { + DPRINTF(E_LOG, L_PLAYER, "Unknown queuefilter: %s\n", queuefilter); + + return -1; + } + } + else if (strstr(query, "dmap.itemid:") && dbmfi.album_artist) + { + safe_atou32(dbmfi.id, &id); + qp.type = Q_ITEMS; + snprintf(buf, sizeof(buf), "f.album_artist = \"%s\"", dbmfi.album_artist); + qp.filter = strdup(buf); + } + else + { + id = 0; + qp.type = Q_ITEMS; + qp.filter = daap_query_parse_sql(query); } ps = player_queue_make(&qp, sort); - free(qp.filter); + if (qp.filter) + free(qp.filter); - return ps; + if (ps) + *head = ps; + else + return -1; + + idx = 0; + while (id && ps && ps->pl_next && (ps->id != id) && (ps->pl_next != *head)) + { + idx++; + ps = ps->pl_next; + } + + return idx; } struct player_source * diff --git a/src/player.h b/src/player.h index f477def0..bbdfa415 100644 --- a/src/player.h +++ b/src/player.h @@ -123,9 +123,8 @@ player_repeat_set(enum repeat_mode mode); int player_shuffle_set(int enable); - -struct player_source * -player_queue_make_daap(const char *query, const char *sort); +int +player_queue_make_daap(struct player_source **head, const char *query, const char *queuefilter, const char *sort); struct player_source * player_queue_make_pl(int plid, uint32_t *id); From 83a89edfd096fd65959550eeebc28f11ae14c3fb Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 21 Nov 2013 23:41:29 +0100 Subject: [PATCH 12/15] Small changes in playqueue-edit wrapper --- src/httpd_dacp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 431ab13c..4cad7ca4 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -1357,21 +1357,21 @@ dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, cha { DPRINTF(E_LOG, L_DACP, "No command in playqueue-edit request\n"); - dmap_send_error(req, "cmst", "No command in cue request"); + dmap_send_error(req, "cmst", "No command in playqueue-edit request"); return; } if (strcmp(param, "clear") == 0) dacp_reply_cue_clear(req, evbuf, uri, query); // TODO this might give wrong reply container else if (strcmp(param, "playnow") == 0) - dacp_reply_cue_play(req, evbuf, uri, query); // TODO this might give wrong reply container + dacp_reply_cue_play(req, evbuf, uri, query); // TODO index is wrong + this might give wrong reply container else if (strcmp(param, "add") == 0) dacp_reply_playqueueedit_add(req, evbuf, uri, query); else { DPRINTF(E_LOG, L_DACP, "Unknown playqueue-edit command %s\n", param); - dmap_send_error(req, "cmst", "Unknown command in cue request"); + dmap_send_error(req, "cmst", "Unknown command in playqueue-edit request"); return; } } From 4d542f9e224cd307c65afacfa95026fb08fcb3f5 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 21 Nov 2013 23:44:37 +0100 Subject: [PATCH 13/15] Set some dmap_send_error messages to "Invalid request" --- src/httpd_dacp.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 4cad7ca4..304369ce 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -1258,7 +1258,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Invalid mode value in playqueue-edit request\n"); - dmap_send_error(req, "cmst", "Invalid mode value in playqueue-edit request"); + dmap_send_error(req, "cmst", "Invalid request"); return; } } @@ -1280,7 +1280,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); - dmap_send_error(req, "cmst", "Could not build song queue"); + dmap_send_error(req, "cmst", "Invalid request"); return; } @@ -1292,7 +1292,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Could not add song queue, DACP query missing\n"); - dmap_send_error(req, "cmst", "Could not add song queue, DACP query missing"); + dmap_send_error(req, "cmst", "Invalid request"); return; } @@ -1357,7 +1357,7 @@ dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, cha { DPRINTF(E_LOG, L_DACP, "No command in playqueue-edit request\n"); - dmap_send_error(req, "cmst", "No command in playqueue-edit request"); + dmap_send_error(req, "cmst", "Invalid request"); return; } @@ -1371,7 +1371,7 @@ dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, cha { DPRINTF(E_LOG, L_DACP, "Unknown playqueue-edit command %s\n", param); - dmap_send_error(req, "cmst", "Unknown command in playqueue-edit request"); + dmap_send_error(req, "cmst", "Invalid request"); return; } } From afd25b79d95a62082a292e11aa2ca7cccd38abdd Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 22 Nov 2013 16:41:57 +0100 Subject: [PATCH 14/15] Support for playqueue-edit --- src/httpd_dacp.c | 64 ++++++++++++++++++++++++++++++++++-------------- src/player.c | 8 +++--- src/player.h | 2 +- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 304369ce..62271e58 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -223,22 +223,34 @@ make_playstatusupdate(struct evbuffer *evbuf) dmap_add_int(psu, "cmsr", current_rev); /* 12 */ - dmap_add_char(psu, "cavc", 1); /* 9 */ /* volume controllable */ - dmap_add_char(psu, "caps", status.status); /* 9 */ /* play status, 2 = stopped, 3 = paused, 4 = playing */ - dmap_add_char(psu, "cash", status.shuffle); /* 9 */ /* shuffle, true/false */ - dmap_add_char(psu, "carp", status.repeat); /* 9 */ /* repeat, 0 = off, 1 = repeat song, 2 = repeat (playlist) */ - - dmap_add_int(psu, "caas", 2); /* 12 */ /* available shuffle states */ - dmap_add_int(psu, "caar", 6); /* 12 */ /* available repeat states */ + dmap_add_char(psu, "caps", status.status); /* 9 */ /* play status, 2 = stopped, 3 = paused, 4 = playing */ + dmap_add_char(psu, "cash", status.shuffle); /* 9 */ /* shuffle, true/false */ + dmap_add_char(psu, "carp", status.repeat); /* 9 */ /* repeat, 0 = off, 1 = repeat song, 2 = repeat (playlist) */ + dmap_add_char(psu, "cafs", 0); /* 9 */ /* dacp.fullscreen */ + dmap_add_char(psu, "cavs", 0); /* 9 */ /* dacp.visualizer */ + dmap_add_char(psu, "cavc", 1); /* 9 */ /* volume controllable */ + dmap_add_int(psu, "caas", 2); /* 12 */ /* available shuffle states */ + dmap_add_int(psu, "caar", 6); /* 12 */ /* available repeat states */ + dmap_add_char(psu, "cafe", 0); /* 9 */ /* dacp.fullscreenenabled */ + dmap_add_char(psu, "cave", 0); /* 9 */ /* dacp.visualizerenabled */ if (mfi) { dacp_nowplaying(psu, &status, mfi); + + dmap_add_int(psu, "casa", 1); /* 12 */ /* unknown */ + dmap_add_int(psu, "astm", mfi->song_length); + dmap_add_char(psu, "casc", 1); /* Maybe an indication of extra data? */ + dmap_add_char(psu, "caks", 6); /* Unknown */ + dacp_playingtime(psu, &status, mfi); free_mfi(mfi, 0); } + dmap_add_char(psu, "casu", 1); /* 9 */ /* unknown */ + dmap_add_char(psu, "ceQu", 0); /* 9 */ /* unknown */ + dmap_add_container(evbuf, "cmst", EVBUFFER_LENGTH(psu)); /* 8 + len */ ret = evbuffer_add_buffer(evbuf, psu); @@ -733,7 +745,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u { sort = evhttp_find_header(query, "sort"); - ret = player_queue_make_daap(&ps, cuequery, NULL, sort); + ret = player_queue_make_daap(&ps, cuequery, NULL, sort, 0); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); @@ -765,6 +777,10 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u DPRINTF(E_LOG, L_DACP, "Invalid index (%s) in cue request\n", param); } + /* If selection was from Up Next queue (command will be playnow), then index is relative */ + if ((param = evhttp_find_header(query, "command")) && (strcmp(param, "playnow") == 0)) + id += status.pos_pl; + ret = player_playback_start(&id); if (ret < 0) { @@ -1249,6 +1265,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, uint32_t idx; int mode; int ret; + int quirkyquery; param = evhttp_find_header(query, "mode"); if (param) @@ -1258,7 +1275,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Invalid mode value in playqueue-edit request\n"); - dmap_send_error(req, "cmst", "Invalid request"); + dmap_send_error(req, "cacr", "Invalid request"); return; } } @@ -1272,15 +1289,18 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, editquery = evhttp_find_header(query, "query"); if (editquery) { + /* This query kind needs special treatment */ + quirkyquery = (mode == 1) && strstr(editquery, "dmap.itemid:"); + queuefilter = evhttp_find_header(query, "queuefilter"); sort = evhttp_find_header(query, "sort"); - ret = player_queue_make_daap(&ps, editquery, queuefilter, sort); + ret = player_queue_make_daap(&ps, editquery, queuefilter, sort, quirkyquery); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); - dmap_send_error(req, "cmst", "Invalid request"); + dmap_send_error(req, "cacr", "Invalid request"); return; } @@ -1292,7 +1312,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Could not add song queue, DACP query missing\n"); - dmap_send_error(req, "cmst", "Invalid request"); + dmap_send_error(req, "cacr", "Invalid request"); return; } @@ -1307,9 +1327,15 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); - dmap_send_error(req, "cmst", "Playback failed to start"); + dmap_send_error(req, "cacr", "Playback failed to start"); return; } + + dmap_add_container(evbuf, "cacr", 24); /* 8 + len */ + dmap_add_int(evbuf, "mstt", 200); /* 12 */ + dmap_add_int(evbuf, "miid", ps->id); /* 12 */ + + httpd_send_reply(req, HTTP_OK, "OK", evbuf); } static void @@ -1331,9 +1357,6 @@ dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, cha User selected track (playlist tab): ?command=add&query='dmap.containeritemid:...'&queuefilter=playlist:...&sort=physical&mode=1&session-id=... -> clear queue, play containeritemid and the rest of playlist - User selected track (artist tab): - ?command=add&query='dmap.itemid:...'&mode=1&session-id=... - -> clear queue, play itemid and the rest of artist tracks User selected shuffle (artist tab): ?command=add&query='...'&sort=album&mode=2&session-id=... -> clear queue, play shuffled query results @@ -1346,6 +1369,11 @@ dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, cha User selected track in queue: ?command=playnow&index=...&session-id=... -> play index + + And the quirky query - no sort and no queuefilter: + User selected track (artist tab): + ?command=add&query='dmap.itemid:...'&mode=1&session-id=... + -> clear queue, play itemid and the rest of artist tracks */ s = daap_session_find(req, query, evbuf); @@ -1362,9 +1390,9 @@ dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, cha } if (strcmp(param, "clear") == 0) - dacp_reply_cue_clear(req, evbuf, uri, query); // TODO this might give wrong reply container + dacp_reply_cue_clear(req, evbuf, uri, query); else if (strcmp(param, "playnow") == 0) - dacp_reply_cue_play(req, evbuf, uri, query); // TODO index is wrong + this might give wrong reply container + dacp_reply_cue_play(req, evbuf, uri, query); else if (strcmp(param, "add") == 0) dacp_reply_playqueueedit_add(req, evbuf, uri, query); else diff --git a/src/player.c b/src/player.c index 68483cff..a510c37a 100644 --- a/src/player.c +++ b/src/player.c @@ -553,7 +553,6 @@ player_queue_make(struct query_params *qp, const char *sort) int ret; qp->idx_type = I_NONE; - qp->sort = S_NONE; if (sort) { @@ -700,7 +699,7 @@ fetch_first_query_match(const char *query, struct db_media_file_info *dbmfi) /* Thread: httpd (DACP) */ int -player_queue_make_daap(struct player_source **head, const char *query, const char *queuefilter, const char *sort) +player_queue_make_daap(struct player_source **head, const char *query, const char *queuefilter, const char *sort, int quirk) { struct query_params qp; struct player_source *ps; @@ -721,6 +720,7 @@ player_queue_make_daap(struct player_source **head, const char *query, const cha qp.offset = 0; qp.limit = 0; + qp.sort = S_NONE; id = 0; @@ -760,9 +760,10 @@ player_queue_make_daap(struct player_source **head, const char *query, const cha return -1; } } - else if (strstr(query, "dmap.itemid:") && dbmfi.album_artist) + else if (quirk && dbmfi.album_artist) { safe_atou32(dbmfi.id, &id); + qp.sort = S_ALBUM; qp.type = Q_ITEMS; snprintf(buf, sizeof(buf), "f.album_artist = \"%s\"", dbmfi.album_artist); qp.filter = strdup(buf); @@ -808,6 +809,7 @@ player_queue_make_pl(int plid, uint32_t *id) qp.type = Q_PLITEMS; qp.offset = 0; qp.limit = 0; + qp.sort = S_NONE; ps = player_queue_make(&qp, NULL); diff --git a/src/player.h b/src/player.h index bbdfa415..29d39824 100644 --- a/src/player.h +++ b/src/player.h @@ -124,7 +124,7 @@ int player_shuffle_set(int enable); int -player_queue_make_daap(struct player_source **head, const char *query, const char *queuefilter, const char *sort); +player_queue_make_daap(struct player_source **head, const char *query, const char *queuefilter, const char *sort, int quirk); struct player_source * player_queue_make_pl(int plid, uint32_t *id); From 9760a43ccf81524d1430738274acb7b23477a4a1 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 22 Nov 2013 22:05:55 +0100 Subject: [PATCH 15/15] Add support for mode 2 (shuffle) in playqueue-edit and -contents --- src/httpd_dacp.c | 95 +++++++++++++++++++++++++++++++++++++++--------- src/player.c | 5 ++- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 62271e58..5b287758 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -1126,6 +1126,15 @@ dacp_reply_playresume(struct evhttp_request *req, struct evbuffer *evbuf, char * evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf); } +static struct player_source * +next_ps(struct player_source *ps, char shuffle) +{ + if (shuffle) + return ps->shuffle_next; + else + return ps->pl_next; +} + static void dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { @@ -1140,6 +1149,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, const char *param; int span; int i; + int n; int songlist_length; int ret; @@ -1162,21 +1172,38 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, songlist = NULL; i = 0; + n = 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; + /* Fast forward to song currently being played */ + ps = head; + while ((ps->id != status.id) && (ps = next_ps(ps, status.shuffle)) && (ps != head)) + i++; + /* 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 < abs(span))) + if (!songlist) + { + DPRINTF(E_LOG, L_DACP, "Could not allocate songlist evbuffer for playqueue-contents\n"); + + dmap_send_error(req, "ceQR", "Out of memory"); + return; + } + + while ((n < abs(span)) && (ps = next_ps(ps, status.shuffle)) && (ps != head)) { - i++; + n++; song = evbuffer_new(); - // TODO Out of memory check (song) + if (!song) + { + DPRINTF(E_LOG, L_DACP, "Could not allocate song evbuffer for playqueue-contents\n"); + + dmap_send_error(req, "ceQR", "Out of memory"); + return; + } + mfi = db_file_fetch_byid(ps->id); dmap_add_container(song, "ceQs", 16); dmap_add_raw_uint32(song, 1); /* Database */ @@ -1193,20 +1220,35 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, 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_int(song, "ceQI", n + 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) + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); + + dmap_send_error(req, "ceQR", "Out of memory"); + return; + } } } /* Playlists are hist, curr and main. Currently we don't support hist. */ playlists = evbuffer_new(); - // TODO Out of memory check (playlists) + if (!playlists) + { + DPRINTF(E_LOG, L_DACP, "Could not allocate playlists evbuffer for playqueue-contents\n"); + if (songlist) + evbuffer_free(songlist); + + dmap_send_error(req, "ceQR", "Out of memory"); + return; + } + dmap_add_container(playlists, "mlit", 61); dmap_add_string(playlists, "ceQk", "hist"); /* 12 */ dmap_add_int(playlists, "ceQi", -200); /* 12 */ @@ -1223,7 +1265,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, 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_int(playlists, "ceQm", n); /* 12 */ dmap_add_string(playlists, "ceQl", "Up Next"); /* 15 = 8 + 7 */ dmap_add_string(playlists, "ceQh", "from Music"); /* 18 = 8 + 10 */ @@ -1236,17 +1278,33 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, 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_int(evbuf, "mrco", n); /* 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 (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not add playlists to evbuffer for playqueue-contents\n"); + if (songlist) + evbuffer_free(songlist); + + dmap_send_error(req, "ceQR", "Out of memory"); + return; + } + if (songlist) { ret = evbuffer_add_buffer(evbuf, songlist); evbuffer_free(songlist); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not add songlist to evbuffer for playqueue-contents\n"); + + dmap_send_error(req, "ceQR", "Out of memory"); + return; + } } 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 */ @@ -1280,7 +1338,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, } } - if (mode == 1) + if ((mode == 1) || (mode == 2)) { player_playback_stop(); player_queue_clear(); @@ -1316,10 +1374,11 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, return; } - /* - param = evhttp_find_header(query, "dacp.shufflestate"); - if (param) - dacp_propset_shufflestate(param, NULL);*/ + if (mode == 2) + { + player_shuffle_set(1); + idx = 0; + } DPRINTF(E_DBG, L_DACP, "Song queue built, playback starting at index %" PRIu32 "\n", idx); ret = player_playback_start(&idx); diff --git a/src/player.c b/src/player.c index a510c37a..dd1b3a41 100644 --- a/src/player.c +++ b/src/player.c @@ -3566,7 +3566,10 @@ player_shuffle_set(int enable) struct player_source * player_queue_get(void) { - return source_head; + if (shuffle) + return shuffle_head; + else + return source_head; } int