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; } 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/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/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 a4e0542f..96510814 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) @@ -1412,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) */ @@ -1658,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) @@ -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) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 97e17a76..5b287758 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 @@ -154,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); @@ -236,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); @@ -481,6 +480,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 @@ -674,27 +684,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", (1 << 1 | 1)); /* 16, unknown dacp - lowest bit announces support for playqueue-contents/-edit */ httpd_send_reply(req, HTTP_OK, "OK", evbuf); } @@ -732,8 +745,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, 0); + if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); @@ -764,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) { @@ -1109,6 +1126,343 @@ 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) +{ + 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 n; + int songlist_length; + int ret; + + /* /ctrl-int/1/playqueue-contents?span=50&session-id=... */ + + s = daap_session_find(req, query, evbuf); + if (!s) + return; + + DPRINTF(E_DBG, L_DACP, "Fetching playqueue contents\n"); + + span = 50; /* Default */ + 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"); + } + + 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 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 */ + songlist = evbuffer_new(); + 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)) + { + n++; + song = evbuffer_new(); + 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 */ + 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); + 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", 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); + 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(); + 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 */ + 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", n); /* 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", 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); + 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 */ + + 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; + int quirkyquery; + + 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, "cacr", "Invalid request"); + return; + } + } + + if ((mode == 1) || (mode == 2)) + { + player_playback_stop(); + player_queue_clear(); + } + + 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, quirkyquery); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); + + dmap_send_error(req, "cacr", "Invalid request"); + 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, "cacr", "Invalid request"); + return; + } + + 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); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); + + 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 +dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +{ + struct daap_session *s; + const char *param; + + /* 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 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 + + 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); + if (!s) + return; + + param = evhttp_find_header(query, "command"); + if (!param) + { + DPRINTF(E_LOG, L_DACP, "No command in playqueue-edit request\n"); + + dmap_send_error(req, "cmst", "Invalid request"); + return; + } + + if (strcmp(param, "clear") == 0) + dacp_reply_cue_clear(req, evbuf, uri, query); + else if (strcmp(param, "playnow") == 0) + dacp_reply_cue_play(req, evbuf, uri, query); + 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", "Invalid request"); + return; + } +} + static void dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { @@ -1609,6 +1963,14 @@ 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:]]+/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..dd1b3a41 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 * @@ -574,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) { @@ -656,32 +634,165 @@ 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, int quirk) +{ + 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; + qp.sort = S_NONE; + + 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 (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); + } + 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 * @@ -698,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); @@ -3163,7 +3275,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 +3563,15 @@ player_shuffle_set(int enable) return ret; } +struct player_source * +player_queue_get(void) +{ + if (shuffle) + return shuffle_head; + else + return source_head; +} + int player_queue_add(struct player_source *ps) { diff --git a/src/player.h b/src/player.h index 4f2d4b47..29d39824 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); @@ -108,13 +123,15 @@ 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, int quirk); 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 +141,6 @@ player_queue_clear(void); void player_queue_plid(uint32_t plid); - void player_set_update_handler(player_status_handler handler);