diff --git a/src/Makefile.am b/src/Makefile.am index fac6e0b7..8a908089 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -102,6 +102,7 @@ forked_daapd_SOURCES = main.c \ rsp_query.c rsp_query.h \ daap_query.c daap_query.h \ player.c player.h \ + queue.c queue.h \ worker.c worker.h \ $(ALSA_SRC) $(OSS4_SRC) \ laudio_dummy.c \ diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index b51ba4fc..19a2b4da 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -50,6 +50,7 @@ #include "db.h" #include "daap_query.h" #include "player.h" +#include "queue.h" #include "listener.h" /* httpd event base, from httpd.c */ @@ -757,6 +758,7 @@ dacp_reply_ctrlint(struct evhttp_request *req, struct evbuffer *evbuf, char **ur static int find_first_song_id(const char *query) { + //TODO [refactor][performance] Unnecessary query, it is enough to extract the item id from the query string. Accessing the db to verify the item exists is not needed. struct db_media_file_info dbmfi; struct query_params qp; int id; @@ -821,11 +823,11 @@ find_first_song_id(const char *query) static int -make_queue_for_query(struct player_source **head, const char *query, const char *queuefilter, const char *sort, int quirk) +make_queue_for_query(struct queue_item **head, const char *query, const char *queuefilter, const char *sort, int quirk) { struct media_file_info *mfi; struct query_params qp; - struct player_source *ps; + struct queue_item *items; int64_t albumid; int64_t artistid; int plid; @@ -941,22 +943,18 @@ make_queue_for_query(struct player_source **head, const char *query, const char qp.sort = S_ARTIST; } - ps = player_queue_make(&qp); + items = queue_make(&qp); if (qp.filter) free(qp.filter); - if (ps) - *head = ps; + if (items) + *head = items; else return -1; - idx = 0; - while (id && ps && ps->pl_next && (ps->id != id) && (ps->pl_next != *head)) - { - idx++; - ps = ps->pl_next; - } + // Get the position (0-based) of the first item + idx = queueitem_pos(items, id); return idx; } @@ -965,7 +963,7 @@ static void dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { struct player_status status; - struct player_source *ps; + struct queue_item *items; const char *sort; const char *cuequery; const char *param; @@ -997,7 +995,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u { sort = evhttp_find_header(query, "sort"); - ret = make_queue_for_query(&ps, cuequery, NULL, sort, 0); + ret = make_queue_for_query(&items, cuequery, NULL, sort, 0); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); @@ -1006,7 +1004,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u return; } - player_queue_add(ps); + player_queue_add(items); } else { @@ -1063,9 +1061,9 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u /* If playing from history queue, the pos holds the id of the item to play */ if (hist) - ret = player_playback_startid(id, &id); + ret = player_playback_start_byitemid(id, &id); else - ret = player_playback_startpos(pos, &id); + ret = player_playback_start_bypos(pos, &id); if (ret < 0) { @@ -1134,7 +1132,7 @@ static void dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { struct player_status status; - struct player_source *ps; + struct queue_item *items; struct daap_session *s; const char *param; const char *shuffle; @@ -1216,8 +1214,8 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u DPRINTF(E_DBG, L_DACP, "Playspec request for playlist %d, start song id %d%s\n", plid, pos, (shuffle) ? ", shuffle" : ""); - ps = player_queue_make_pl(plid, &pos); - if (!ps) + items = queue_make_pl(plid); //TODO [queue] get queue-item-id or pos for first song to play (dacp) --- , &pos); + if (!items) { DPRINTF(E_LOG, L_DACP, "Could not build song queue from playlist %d\n", plid); @@ -1232,13 +1230,13 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u player_playback_stop(); player_queue_clear(); - player_queue_add(ps); + player_queue_add(items); player_queue_plid(plid); if (shuffle) dacp_propset_shufflestate(shuffle, NULL); - ret = player_playback_startpos(pos, &id); + ret = player_playback_start_bypos(pos, &id); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); @@ -1472,7 +1470,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, struct evbuffer *playlists; struct player_status status; struct player_history *history; - struct player_queue *queue; + struct queue_info *queue; const char *param; size_t songlist_length; size_t playlist_length; @@ -1544,13 +1542,13 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, /* Get queue and make songlist only if playing or paused */ if (status.status != PLAY_STOPPED) { - queue = player_queue_get_relative(abs(span)); + queue = player_queue_get_bypos(abs(span)); if (queue) { i = queue->start_pos; for (n = 0; (n < queue->count) && (n < abs(span)); n++) { - ret = playqueuecontents_add_source(songlist, queue->queue[n], (n + i + 1), status.plid); + ret = playqueuecontents_add_source(songlist, queue->queue[n].dbmfi_id, (n + i + 1), status.plid); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); @@ -1559,8 +1557,8 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, return; } } + queue_info_free(queue); } - queue_free(queue); } } @@ -1644,9 +1642,7 @@ static void dacp_reply_playqueueedit_clear(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) { const char *param; - int clear_hist; - clear_hist = 0; param = evhttp_find_header(query, "mode"); /* @@ -1655,9 +1651,9 @@ dacp_reply_playqueueedit_clear(struct evhttp_request *req, struct evbuffer *evbu * otherwise the current playlist. */ if (strcmp(param,"0x68697374") == 0) - clear_hist = 1; - - player_queue_empty(clear_hist); + player_queue_clear_history(); + else + player_queue_clear(); dmap_add_container(evbuf, "cacr", 24); /* 8 + len */ dmap_add_int(evbuf, "mstt", 200); /* 12 */ @@ -1678,7 +1674,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, //?command=add&query='dmap.itemid:2'&query-modifier=containers&sort=name&mode=2&session-id=100 // -> mode 2: stop playblack, clear playqueue, add shuffled songs from playlist=itemid to playqueue - struct player_source *ps; + struct queue_item *items; const char *editquery; const char *queuefilter; const char *querymodifier; @@ -1730,7 +1726,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, if (!querymodifier || (strcmp(querymodifier, "containers") != 0)) { quirkyquery = (mode == 1) && strstr(editquery, "dmap.itemid:") && ((!queuefilter) || strstr(queuefilter, "(null)")); - ret = make_queue_for_query(&ps, editquery, queuefilter, sort, quirkyquery); + ret = make_queue_for_query(&items, editquery, queuefilter, sort, quirkyquery); } else { @@ -1745,7 +1741,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, } snprintf(modifiedquery, sizeof(modifiedquery), "playlist:%d", plid); - ret = make_queue_for_query(&ps, NULL, modifiedquery, sort, 0); + ret = make_queue_for_query(&items, NULL, modifiedquery, sort, 0); } if (ret < 0) @@ -1760,11 +1756,11 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, if (mode == 3) { - player_queue_add_next(ps); + player_queue_add_next(items); } else { - player_queue_add(ps); + player_queue_add(items); } } else @@ -1782,7 +1778,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, } DPRINTF(E_DBG, L_DACP, "Song queue built, playback starting at index %" PRIu32 "\n", idx); - ret = player_playback_startpos(idx, NULL); + ret = player_playback_start_bypos(idx, NULL); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); @@ -1833,7 +1829,7 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf return; } - player_queue_move(src, dst); + player_queue_move_bypos(src, dst); } /* 204 No Content is the canonical reply */ @@ -1865,7 +1861,7 @@ dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evb return; } - player_queue_remove_pos_relative(item_index); + player_queue_remove_bypos(item_index); } /* 204 No Content is the canonical reply */ diff --git a/src/mpd.c b/src/mpd.c index cff31642..39b23191 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -57,6 +57,7 @@ #include "artwork.h" #include "player.h" +#include "queue.h" #include "filescanner.h" @@ -1210,7 +1211,7 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } if (songpos > 0) - ret = player_playback_startpos(songpos, NULL); + ret = player_playback_start_byindex(songpos, NULL); else ret = player_playback_start(NULL); @@ -1263,7 +1264,7 @@ mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } if (id > 0) - ret = player_playback_startid(id, NULL); + ret = player_playback_start_byitemid(id, NULL); else ret = player_playback_start(NULL); @@ -1506,11 +1507,11 @@ mpd_command_stop(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return 0; } -static struct player_source * +static struct queue_item * make_queue_for_path(char *path, int recursive) { struct query_params qp; - struct player_source *ps; + struct queue_item *items; memset(&qp, 0, sizeof(struct query_params)); @@ -1531,10 +1532,10 @@ make_queue_for_path(char *path, int recursive) DPRINTF(E_DBG, L_PLAYER, "Out of memory\n"); } - ps = player_queue_make(&qp); + items = queue_make(&qp); sqlite3_free(qp.filter); - return ps; + return items; } /* @@ -1545,7 +1546,7 @@ make_queue_for_path(char *path, int recursive) static int mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct player_source *ps; + struct queue_item *items; int ret; if (argc < 2) @@ -1556,9 +1557,9 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_ARG; } - ps = make_queue_for_path(argv[1], 1); + items = make_queue_for_path(argv[1], 1); - if (!ps) + if (!items) { ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]); if (ret < 0) @@ -1566,7 +1567,7 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_UNKNOWN; } - player_queue_add(ps); + player_queue_add(items); ret = player_playback_start(NULL); if (ret < 0) @@ -1586,7 +1587,7 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) static int mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct player_source *ps; + struct queue_item *items; int ret; if (argc < 2) @@ -1603,9 +1604,9 @@ mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) DPRINTF(E_LOG, L_MPD, "Adding at a specified position not supported for 'addid', adding songs at end of queue.\n"); } - ps = make_queue_for_path(argv[1], 0); + items = make_queue_for_path(argv[1], 0); - if (!ps) + if (!items) { ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]); if (ret < 0) @@ -1614,13 +1615,14 @@ mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) } - player_queue_add(ps); + player_queue_add(items); + //TODO [queue] Get queue-item-id for mpd-command addid evbuffer_add_printf(evbuf, "addid: %s\n" "Id: %d\n", argv[1], - ps->id); + 0); //ps->id); ret = player_playback_start(NULL); if (ret < 0) @@ -1668,7 +1670,7 @@ mpd_command_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) // If argv[1] is ommited clear the whole queue except the current playing one if (argc < 2) { - player_queue_empty(0); + player_queue_clear(); return 0; } @@ -1695,7 +1697,7 @@ mpd_command_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_ARG; } - ret = player_queue_remove_pos_relative(pos); + ret = player_queue_remove_bypos(pos); if (ret < 0) { ret = asprintf(errmsg, "Failed to remove song at position '%d'", pos); @@ -1734,7 +1736,7 @@ mpd_command_deleteid(struct evbuffer *evbuf, int argc, char **argv, char **errms return ACK_ERROR_ARG; } - ret = player_queue_remove_queueitemid(songid); + ret = player_queue_remove_byitemid(songid); if (ret < 0) { ret = asprintf(errmsg, "Failed to remove song with id '%s'", argv[1]); @@ -1756,7 +1758,7 @@ mpd_command_deleteid(struct evbuffer *evbuf, int argc, char **argv, char **errms static int mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct player_queue *queue; + struct queue_info *queue; uint32_t songid; int pos_pl; int i; @@ -1777,7 +1779,7 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err } // Get the whole queue (start_pos = 0, end_pos = -1) - queue = player_queue_get(0, 0); + queue = player_queue_get_byindex(0, 0); if (!queue) { @@ -1788,14 +1790,14 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err pos_pl = queue->start_pos; for (i = 0; i < queue->count; i++) { - if (songid == 0 || songid == queue->queue[i]) + if (songid == 0 || songid == queue->queue[i].item_id) { - ret = mpd_add_mediainfo_byid(evbuf, queue->queue[i], pos_pl); + ret = mpd_add_mediainfo_byid(evbuf, queue->queue[i].dbmfi_id, pos_pl); if (ret < 0) { - ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue->queue[i]); + ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue->queue[i].dbmfi_id); - queue_free(queue); + queue_info_free(queue); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); @@ -1806,7 +1808,7 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err pos_pl++; } - queue_free(queue); + queue_info_free(queue); return 0; } @@ -1822,7 +1824,7 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err static int mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct player_queue *queue; + struct queue_info *queue; int start_pos; int end_pos; int count; @@ -1854,7 +1856,7 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e count = 0; } - queue = player_queue_get(start_pos, count); + queue = player_queue_get_byindex(start_pos, count); if (!queue) { @@ -1865,12 +1867,12 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e pos_pl = queue->start_pos; for (i = 0; i < queue->count; i++) { - ret = mpd_add_mediainfo_byid(evbuf, queue->queue[i], pos_pl); + ret = mpd_add_mediainfo_byid(evbuf, queue->queue[i].dbmfi_id, pos_pl); if (ret < 0) { - ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue->queue[i]); + ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue->queue[i].dbmfi_id); - queue_free(queue); + queue_info_free(queue); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); @@ -1880,7 +1882,7 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e pos_pl++; } - queue_free(queue); + queue_info_free(queue); return 0; } @@ -1892,7 +1894,7 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e static int mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { - struct player_queue *queue; + struct queue_info *queue; int pos_pl; int i; int ret; @@ -1901,7 +1903,7 @@ mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errm * forked-daapd does not keep track of changes in the queue based on the playlist version, * therefor plchanges returns all songs in the queue as changed ignoring the given version. */ - queue = player_queue_get(0, 0); + queue = player_queue_get_byindex(0, 0); if (!queue) { @@ -1912,12 +1914,12 @@ mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errm pos_pl = queue->start_pos; for (i = 0; i < queue->count; i++) { - ret = mpd_add_mediainfo_byid(evbuf, queue->queue[i], pos_pl); + ret = mpd_add_mediainfo_byid(evbuf, queue->queue[i].dbmfi_id, pos_pl); if (ret < 0) { - ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue->queue[i]); + ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue->queue[i].dbmfi_id); - queue_free(queue); + queue_info_free(queue); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); @@ -1927,7 +1929,7 @@ mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errm pos_pl++; } - queue_free(queue); + queue_info_free(queue); return 0; } @@ -2141,8 +2143,7 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { char path[PATH_MAX]; struct playlist_info *pli; - struct player_source *ps; - uint32_t pos; + struct queue_item *items; int ret; if (argc < 2) @@ -2173,9 +2174,9 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) //TODO If a second parameter is given only add the specified range of songs to the playqueue - ps = player_queue_make_pl(pli->id, &pos); + items = queue_make_pl(pli->id); - if (!ps) + if (!items) { free_pli(pli, 0); @@ -2185,7 +2186,7 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_UNKNOWN; } - player_queue_add(ps); + player_queue_add(items); ret = player_playback_start(NULL); if (ret < 0) @@ -2422,7 +2423,7 @@ static int mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { struct query_params qp; - struct player_source *ps; + struct queue_item *items; int ret; if (argc < 3 || ((argc - 1) % 2) != 0) @@ -2441,9 +2442,9 @@ mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg mpd_get_query_params_find(argc - 1, argv + 1, &qp); - ps = player_queue_make(&qp); + items = queue_make(&qp); - if (!ps) + if (!items) { ret = asprintf(errmsg, "Failed to add songs to playlist"); if (ret < 0) @@ -2451,7 +2452,7 @@ mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg return ACK_ERROR_UNKNOWN; } - player_queue_add(ps); + player_queue_add(items); ret = player_playback_start(NULL); if (ret < 0) @@ -2896,7 +2897,7 @@ static int mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { struct query_params qp; - struct player_source *ps; + struct queue_item *items; int ret; if (argc < 3 || ((argc - 1) % 2) != 0) @@ -2915,9 +2916,9 @@ mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errm mpd_get_query_params_search(argc - 1, argv + 1, &qp); - ps = player_queue_make(&qp); + items = queue_make(&qp); - if (!ps) + if (!items) { ret = asprintf(errmsg, "Failed to add songs to playlist"); if (ret < 0) @@ -2925,7 +2926,7 @@ mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errm return ACK_ERROR_UNKNOWN; } - player_queue_add(ps); + player_queue_add(items); ret = player_playback_start(NULL); if (ret < 0) diff --git a/src/player.c b/src/player.c index bed0974d..431fabb8 100644 --- a/src/player.c +++ b/src/player.c @@ -82,6 +82,25 @@ // Default volume (must be from 0 - 100) #define PLAYER_DEFAULT_VOLUME 50 +struct player_source +{ + uint32_t id; + uint32_t queueitem_id; + uint32_t len_ms; + + enum data_kind data_kind; + enum media_kind media_kind; + int setup_done; + + uint64_t stream_start; + uint64_t output_start; + uint64_t end; + + struct transcode_ctx *ctx; + + struct player_source *play_next; +}; + enum player_sync_source { PLAYER_SYNC_CLOCK, @@ -110,25 +129,10 @@ enum range_type RANGEARG_RANGE }; -/* - * Identifies an item or a range of items - * - * Depending on item_range.type the item(s) are identified by: - * - item id (type = RANGEARG_ID) given in item_range.id - * - item position (type = RANGEARG_POS) given in item_range.start_pos - * - start and end position (type = RANGEARG_RANGE) given in item_range.start_pos to item_range.end_pos - * - * The pointer id_ptr may be set to an item id by the called function. - */ -struct item_range +struct playback_start_param { - enum range_type type; - uint32_t id; - int start_pos; - int end_pos; - - char shuffle; + int pos; uint32_t *id_ptr; }; @@ -138,7 +142,13 @@ struct playerqueue_get_param int pos; int count; - struct player_queue *queue; + struct queue_info *queue; +}; + +struct playerqueue_add_param +{ + struct queue_item *items; + int pos; }; struct icy_artwork @@ -181,9 +191,10 @@ struct player_command uint32_t id; int intval; int ps_pos[2]; - struct item_range item_range; struct icy_artwork icy; + struct playback_start_param playback_start_param; struct playerqueue_get_param queue_get_param; + struct playerqueue_add_param queue_add_param; } arg; int ret; @@ -260,14 +271,15 @@ static int master_volume; struct rng_ctx shuffle_rng; /* Audio source */ -static struct player_source *source_head; -static struct player_source *shuffle_head; static struct player_source *cur_playing; static struct player_source *cur_streaming; static uint32_t cur_plid; static uint32_t cur_plversion; static struct evbuffer *audio_buf; +/* Play queue */ +static struct queue *queue; + /* Play history */ static struct player_history *history; @@ -776,166 +788,18 @@ metadata_check_icy(void) /* Audio sources */ -/* Helper */ static struct player_source * -next_ps(struct player_source *ps, char shuffle) +source_new(struct queue_item_info *item) { - if (shuffle) - return ps->shuffle_next; - else - return ps->pl_next; -} - -/* Thread: httpd (DACP) */ -struct player_source * -player_queue_make(struct query_params *qp) -{ - struct db_media_file_info dbmfi; - struct player_source *q_head; - struct player_source *q_tail; struct player_source *ps; - uint32_t id; - uint32_t song_length; - int ret; - ret = db_query_start(qp); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not start query\n"); + ps = (struct player_source *)calloc(1, sizeof(struct player_source)); - return NULL; - } - - DPRINTF(E_DBG, L_PLAYER, "Player queue query returned %d items\n", qp->results); - - q_head = NULL; - q_tail = NULL; - while (((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"); - - continue; - } - - ret = safe_atou32(dbmfi.song_length, &song_length); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n"); - - continue; - } - - ps = (struct player_source *)malloc(sizeof(struct player_source)); - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Out of memory for struct player_source\n"); - - ret = -1; - break; - } - - memset(ps, 0, sizeof(struct player_source)); - - ps->id = id; - ps->len_ms = song_length; - - if (!q_head) - q_head = ps; - - if (q_tail) - { - q_tail->pl_next = ps; - ps->pl_prev = q_tail; - - q_tail->shuffle_next = ps; - ps->shuffle_prev = q_tail; - } - - q_tail = ps; - - DPRINTF(E_DBG, L_PLAYER, "Added song id %d (%s)\n", id, dbmfi.title); - } - - db_query_end(qp); - - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Error fetching results\n"); - - return NULL; - } - - if (!q_head) - return NULL; - - q_head->pl_prev = q_tail; - q_tail->pl_next = q_head; - q_head->shuffle_prev = q_tail; - q_tail->shuffle_next = q_head; - - return q_head; -} - -struct player_source * -player_queue_make_pl(int plid, uint32_t *id) -{ - struct query_params qp; - struct player_source *ps; - struct player_source *p; - uint32_t i; - char buf[124]; - - memset(&qp, 0, sizeof(struct query_params)); - - if (plid) - { - qp.id = plid; - qp.type = Q_PLITEMS; - qp.offset = 0; - qp.limit = 0; - qp.sort = S_NONE; - } - else if (*id) - { - qp.id = 0; - qp.type = Q_ITEMS; - qp.offset = 0; - qp.limit = 0; - qp.sort = S_NONE; - snprintf(buf, sizeof(buf), "f.id = %" PRIu32, *id); - qp.filter = strdup(buf); - } - else - return NULL; - - qp.idx_type = I_NONE; - - ps = player_queue_make(&qp); - - if (qp.filter) - free(qp.filter); - - /* Shortcut for shuffled playlist */ - if (*id == 0) - return ps; - - p = ps; - i = 0; - do - { - if (p->id == *id) - { - *id = i; - break; - } - - p = p->pl_next; - i++; - } - while (p != ps); + ps->id = item->dbmfi_id; + ps->queueitem_id = item->item_id; + ps->data_kind = item->data_kind; + ps->media_kind = item->media_kind; + ps->len_ms = item->len_ms; return ps; } @@ -1001,133 +865,6 @@ source_stop(struct player_source *ps) } } -/* - * Shuffles the items between head and tail (excluding head and tail) - */ -static void -source_shuffle(struct player_source *head, struct player_source *tail) -{ - struct player_source *ps; - struct player_source **ps_array; - int nitems; - int i; - - if (!head) - return; - - if (!tail) - return; - - if (!shuffle) - { - ps = head; - do - { - ps->shuffle_next = ps->pl_next; - ps->shuffle_prev = ps->pl_prev; - ps = ps->pl_next; - } - while (ps != head); - } - - // Count items in queue (excluding head and tail) - ps = head->shuffle_next; - if (!cur_streaming) - nitems = 1; - else - nitems = 0; - while (ps != tail) - { - nitems++; - ps = ps->shuffle_next; - } - - // Do not reshuffle queue with one item - if (nitems < 1) - return; - - // Construct array for number of items in queue - ps_array = (struct player_source **)malloc(nitems * sizeof(struct player_source *)); - if (!ps_array) - { - DPRINTF(E_LOG, L_PLAYER, "Could not allocate memory for shuffle array\n"); - return; - } - - // Fill array with items in queue (excluding head and tail) - if (cur_streaming) - ps = head->shuffle_next; - else - ps = head; - i = 0; - do - { - ps_array[i] = ps; - - ps = ps->shuffle_next; - i++; - } - while (ps != tail); - - shuffle_ptr(&shuffle_rng, (void **)ps_array, nitems); - - for (i = 0; i < nitems; i++) - { - ps = ps_array[i]; - - if (i > 0) - ps->shuffle_prev = ps_array[i - 1]; - - if (i < (nitems - 1)) - ps->shuffle_next = ps_array[i + 1]; - } - - // Insert shuffled items between head and tail - if (cur_streaming) - { - ps_array[0]->shuffle_prev = head; - ps_array[nitems - 1]->shuffle_next = tail; - head->shuffle_next = ps_array[0]; - tail->shuffle_prev = ps_array[nitems - 1]; - } - else - { - ps_array[0]->shuffle_prev = ps_array[nitems - 1]; - ps_array[nitems - 1]->shuffle_next = ps_array[0]; - shuffle_head = ps_array[0]; - } - - free(ps_array); - - return; -} - -static void -source_reshuffle(void) -{ - struct player_source *head; - struct player_source *tail; - - if (cur_streaming) - head = cur_streaming; - else if (shuffle) - head = shuffle_head; - else - head = source_head; - - if (repeat == REPEAT_ALL) - tail = head; - else if (shuffle) - tail = shuffle_head; - else - tail = source_head; - - source_shuffle(head, tail); - - if (repeat == REPEAT_ALL) - shuffle_head = head; -} - /* Helper */ static int source_open(struct player_source *ps, int no_md, int seek) @@ -1223,110 +960,80 @@ source_open(struct player_source *ps, int no_md, int seek) static int source_next(int force) { - struct player_source *ps; - struct player_source *head; - struct player_source *limit; + struct player_source *ps_next; + struct queue_item_info *item; enum repeat_mode r_mode; + uint32_t cur_queueitem_id; int ret; - head = (shuffle) ? shuffle_head : source_head; - limit = head; r_mode = repeat; /* Force repeat mode at user request */ if (force && (r_mode == REPEAT_SONG)) r_mode = REPEAT_ALL; - /* Playlist has only one file, treat REPEAT_ALL as REPEAT_SONG */ - if ((r_mode == REPEAT_ALL) && (source_head == source_head->pl_next)) - r_mode = REPEAT_SONG; - /* Playlist has only one file, not a user action, treat as REPEAT_ALL - * and source_check() will stop playback - */ - else if (!force && (r_mode == REPEAT_OFF) && (source_head == source_head->pl_next)) - r_mode = REPEAT_SONG; + cur_queueitem_id = 0; + if (cur_streaming) + cur_queueitem_id = cur_streaming->queueitem_id; - if (!cur_streaming) - ps = head; - else - ps = next_ps(cur_streaming, shuffle); + item = queue_next(queue, cur_queueitem_id, shuffle, r_mode); - switch (r_mode) + if (!item) { - case REPEAT_SONG: - if (!cur_streaming) - break; + DPRINTF(E_DBG, L_PLAYER, "End of playlist reached and repeat is OFF\n"); - if ((cur_streaming->data_kind == DATA_KIND_FILE) && cur_streaming->ctx) - { - ret = transcode_seek(cur_streaming->ctx, 0); + //playback_abort(); + cur_streaming = NULL; + return 0; + } - /* source_open() takes care of sending metadata, but we don't - * call it when repeating a song as we just seek back to 0 - * so we have to handle metadata ourselves here - */ - if (ret >= 0) - metadata_trigger(cur_streaming, 0); - } - else - ret = source_open(cur_streaming, force, 0); + /* + * Check if the next item is the same as the current streaming (REPEAT SINGLE or REPEAT ALL + * with only one item in the playlist) + */ + if (item->item_id == cur_streaming->queueitem_id) + { + if ((cur_streaming->data_kind == DATA_KIND_FILE) && cur_streaming->ctx) + { + ret = transcode_seek(cur_streaming->ctx, 0); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Failed to restart song for song repeat\n"); + /* source_open() takes care of sending metadata, but we don't + * call it when repeating a song as we just seek back to 0 + * so we have to handle metadata ourselves here + */ + if (ret >= 0) + metadata_trigger(cur_streaming, 0); + } + else + ret = source_open(cur_streaming, force, 0); - return -1; - } + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Failed to restart song for song repeat\n"); - return 0; + return -1; + } - case REPEAT_ALL: - if (!shuffle) - { - limit = ps; - break; - } - - /* Reshuffle before repeating playlist */ - if (cur_streaming && (ps == shuffle_head)) - { - source_reshuffle(); - ps = shuffle_head; - } - - limit = shuffle_head; - - break; - - case REPEAT_OFF: - limit = head; - - if (force && (ps == limit)) - { - DPRINTF(E_DBG, L_PLAYER, "End of playlist reached and repeat is OFF\n"); - - playback_abort(); - return 0; - } - break; + return 0; } do { - ret = source_open(ps, force, 0); + ps_next = source_new(item); + //TODO [queue] reshuffle if repeat all and end of playlist reached + ret = source_open(ps_next, 1, 0); if (ret < 0) { - if (shuffle) - ps = ps->shuffle_next; - else - ps = ps->pl_next; - + // Failed to open source, try next + source_free(ps_next); + item = queue_next(queue, item->item_id, shuffle, repeat); //TODO performance optimization, call to queue_next always iterates from the beginning of the queue continue; } + // Successfully opened the next source, break out of the loop break; } - while (ps != limit); + while (item); /* Couldn't open any of the files in our queue */ if (ret < 0) @@ -1337,9 +1044,9 @@ source_next(int force) } if (!force && cur_streaming) - cur_streaming->play_next = ps; + cur_streaming->play_next = ps_next; - cur_streaming = ps; + cur_streaming = ps_next; return 0; } @@ -1347,19 +1054,16 @@ source_next(int force) static int source_prev(void) { - struct player_source *ps; - struct player_source *head; - struct player_source *limit; + struct queue_item_info *item; + struct player_source *ps_prev; int ret; if (!cur_streaming) return -1; - head = (shuffle) ? shuffle_head : source_head; - ps = (shuffle) ? cur_streaming->shuffle_prev : cur_streaming->pl_prev; - limit = ps; + item = queue_prev(queue, cur_streaming->queueitem_id, shuffle, repeat); - if ((repeat == REPEAT_OFF) && (cur_streaming == head)) + if (!item) { DPRINTF(E_DBG, L_PLAYER, "Start of playlist reached and repeat is OFF\n"); @@ -1367,24 +1071,21 @@ source_prev(void) return 0; } - /* We are not reshuffling on prev calls in the shuffle case - should we? */ - do { - ret = source_open(ps, 1, 0); + ps_prev = source_new(item); + ret = source_open(ps_prev, 1, 0); if (ret < 0) - { - if (shuffle) - ps = ps->shuffle_prev; - else - ps = ps->pl_prev; - - continue; - } + { + // Failed to open source, try next + source_free(ps_prev); + item = queue_prev(queue, item->item_id, shuffle, repeat); //TODO performance optimization, call to queue_prev always iterates from the beginning of the queue + continue; + } break; } - while (ps != limit); + while (item); /* Couldn't open any of the files in our queue */ if (ret < 0) @@ -1394,65 +1095,11 @@ source_prev(void) return -1; } - cur_streaming = ps; + cur_streaming = ps_prev; return 0; } -/* - * Returns the position of the given player_source (ps) in the playqueue or shufflequeue. - * First song in the queue has position 0. Depending on the 'shuffle' argument, - * the position is either determined in the playqueue or shufflequeue. - * - * @param ps the source to search in the queue - * @param shuffle 0 search in the playqueue, 1 search in the shufflequeue - * @return position 0-based in the queue (-1 if not found) - */ -static int -source_position(struct player_source *source, char shuffle) -{ - struct player_source *ps; - struct player_source *head; - int ret; - - head = shuffle ? shuffle_head : source_head; - if (!head) - return -1; - - if (source == head) - return 0; - - ret = 0; - for (ps = next_ps(head, shuffle); ps != head; ps = next_ps(ps, shuffle)) - { - ret++; - if (ps == source) - return ret; - } - - DPRINTF(E_LOG, L_PLAYER, "Bug! source_position was given non-existent source\n"); - - return -1; -} - -static uint32_t -source_count() -{ - struct player_source *ps; - uint32_t ret; - - ret = 0; - - if (source_head) - { - ret++; - for (ps = source_head->pl_next; ps != source_head; ps = ps->pl_next) - ret++; - } - - return ret; -} - /* * Updates cur_playing and notifies remotes and raop devices about * changes. @@ -1462,15 +1109,14 @@ source_check(void) { struct timespec ts; struct player_source *ps; - struct player_source *head; uint64_t pos; enum repeat_mode r_mode; int i; int id; int ret; - if (!cur_streaming) - return 0; +// if (!cur_streaming) +// return 0; ret = player_get_current_pos(&pos, &ts, 0); if (ret < 0) @@ -1502,11 +1148,10 @@ source_check(void) and initialize stream_start and output_start values. */ r_mode = repeat; - /* Playlist has only one file, treat REPEAT_ALL as REPEAT_SONG */ - if ((r_mode == REPEAT_ALL) && (source_head == source_head->pl_next)) - r_mode = REPEAT_SONG; - if (r_mode == REPEAT_SONG) + /* If repeat mode is REPEAT_SONG or playlist has only one file and repeat mode is REPEAT_ALL, + source_next() does not open a new source but instead seeks to position 0. */ + if (r_mode == REPEAT_SONG || (r_mode == REPEAT_ALL && !cur_playing->play_next)) { ps = cur_playing; @@ -1541,8 +1186,6 @@ source_check(void) return pos; } - head = (shuffle) ? shuffle_head : source_head; - i = 0; while (cur_playing && (cur_playing->end != 0) && (pos > cur_playing->end)) { @@ -1558,8 +1201,7 @@ source_check(void) * - at end of playlist (NULL) * - repeat OFF and at end of playlist (wraparound) */ - if (!cur_playing->play_next - || ((r_mode == REPEAT_OFF) && (cur_playing->play_next == head))) + if (!cur_playing->play_next) { playback_abort(); @@ -1595,6 +1237,15 @@ source_check(void) return pos; } +static struct player_source * +source_now_playing(void) +{ + if (cur_playing) + return cur_playing; + + return cur_streaming; +} + struct player_history * player_history_get(void) { @@ -1636,6 +1287,7 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) int ret; int nbytes; int icy_timer; + char *silence_buf; if (!cur_streaming) return 0; @@ -1660,31 +1312,43 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) if (evbuffer_get_length(audio_buf) == 0) { - switch (cur_streaming->data_kind) + if (cur_streaming) { - case DATA_KIND_HTTP: - ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer); + switch (cur_streaming->data_kind) + { + case DATA_KIND_HTTP: + ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer); - if (icy_timer) - metadata_check_icy(); - break; + if (icy_timer) + metadata_check_icy(); + break; - case DATA_KIND_FILE: - ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer); - break; + case DATA_KIND_FILE: + ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer); + break; #ifdef HAVE_SPOTIFY_H - case DATA_KIND_SPOTIFY: - ret = spotify_audio_get(audio_buf, len - nbytes); - break; + case DATA_KIND_SPOTIFY: + ret = spotify_audio_get(audio_buf, len - nbytes); + break; #endif - case DATA_KIND_PIPE: - ret = pipe_audio_get(audio_buf, len - nbytes); - break; + case DATA_KIND_PIPE: + ret = pipe_audio_get(audio_buf, len - nbytes); + break; - default: - ret = -1; + default: + ret = -1; + } + } + else + { + // Reached end of playlist (cur_playing is NULL) send silence and source_check will abort playback if the last item was played + DPRINTF(E_DBG, L_PLAYER, "End of playlist reached, stream silence until playback of last item ends\n"); + silence_buf = (char *)calloc((len - nbytes), sizeof(char)); + evbuffer_add(audio_buf, silence_buf, (len - nbytes)); + free(silence_buf); + ret = len - nbytes; } if (ret <= 0) @@ -2275,6 +1939,9 @@ device_restart_cb(struct raop_device *dev, struct raop_session *rs, enum raop_se static void playback_abort(void) { + struct player_source *ps_playing; + struct player_source *ps_temp; + if (laudio_status != LAUDIO_CLOSED) laudio_close(); @@ -2283,16 +1950,22 @@ playback_abort(void) pb_timer_stop(); - if (cur_playing) - source_stop(cur_playing); - else - source_stop(cur_streaming); + ps_playing = source_now_playing(); + source_stop(ps_playing); - playerqueue_clear(NULL); + // Free all player_source items + while (ps_playing) + { + ps_temp = ps_playing; + ps_playing = ps_playing->play_next; + source_free(ps_temp); + } cur_playing = NULL; cur_streaming = NULL; + playerqueue_clear(NULL); + evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf)); status_update(PLAY_STOPPED); @@ -2307,6 +1980,7 @@ get_status(struct player_command *cmd) struct timespec ts; struct player_source *ps; struct player_status *status; + struct queue_item_info *item_next; uint64_t pos; int ret; @@ -2340,7 +2014,7 @@ get_status(struct player_command *cmd) status->pos_ms = (pos * 1000) / 44100; status->len_ms = cur_streaming->len_ms; - status->pos_pl = source_position(cur_streaming, 0); + status->pos_pl = queue_index_byitemid(queue, cur_streaming->queueitem_id, 0); break; @@ -2380,13 +2054,22 @@ get_status(struct player_command *cmd) status->len_ms = ps->len_ms; status->id = ps->id; - status->pos_pl = source_position(ps, 0); + status->pos_pl = queue_index_byitemid(queue, ps->queueitem_id, 0); - ps = next_ps(ps, shuffle); - status->next_id = ps->id; - status->next_pos_pl = source_position(ps, 0); + item_next = queue_next(queue, ps->queueitem_id, shuffle, repeat); + if (item_next) + { + status->next_id = item_next->dbmfi_id; + status->next_pos_pl = queue_index_byitemid(queue, item_next->item_id, 0); + } + else + { + //TODO [queue/mpd] Check how mpd sets the next-id/-pos if the last song is playing + status->next_id = 0; + status->next_pos_pl = 0; + } - status->playlistlength = source_count(); + status->playlistlength = queue_count(queue); break; } @@ -2397,13 +2080,14 @@ static int now_playing(struct player_command *cmd) { uint32_t *id; + struct player_source *ps_playing; id = cmd->arg.id_ptr; - if (cur_playing) - *id = cur_playing->id; - else if (cur_streaming) - *id = cur_streaming->id; + ps_playing = source_now_playing(); + + if (ps_playing) + *id = ps_playing->id; else return -1; @@ -2436,6 +2120,9 @@ artwork_url_get(struct player_command *cmd) static int playback_stop(struct player_command *cmd) { + struct player_source *ps_playing; + struct player_source *ps_temp; + if (laudio_status != LAUDIO_CLOSED) laudio_close(); @@ -2447,15 +2134,19 @@ playback_stop(struct player_command *cmd) pb_timer_stop(); - if (cur_playing) + ps_playing = source_now_playing(); + if (ps_playing) { - history_add(cur_playing->id); - source_stop(cur_playing); + history_add(ps_playing->id); + source_stop(ps_playing); } - else if (cur_streaming) + + // Free all player_source items + while (ps_playing) { - history_add(cur_streaming->id); - source_stop(cur_streaming); + ps_temp = ps_playing; + ps_playing = ps_playing->play_next; + source_free(ps_temp); } cur_playing = NULL; @@ -2539,55 +2230,19 @@ playback_start_bh(struct player_command *cmd) return -1; } -static struct player_source * -playerqueue_get_source_byid(uint32_t id) -{ - struct player_source *ps; - - if (!source_head) - return NULL; - - ps = source_head->pl_next; - while (ps->id != id && ps != source_head) - { - ps = ps->pl_next; - } - - return ps; -} - -static struct player_source * -playerqueue_get_source_bypos(int pos) -{ - struct player_source *ps; - int i; - - if (!source_head) - return NULL; - - ps = source_head; - for (i = pos; i > 0; i--) - ps = ps->pl_next; - - return ps; -} - static int -playback_start(struct player_command *cmd) +playback_start_item(struct player_command *cmd, struct queue_item_info *qii) { + uint32_t *dbmfi_id; struct raop_device *rd; - uint32_t *idx_id; + struct player_source *ps_playing; + struct player_source *ps_temp; struct player_source *ps; int ret; - if (!source_head) - { - DPRINTF(E_LOG, L_PLAYER, "Nothing to play!\n"); + dbmfi_id = cmd->arg.playback_start_param.id_ptr; - return -1; - } - - idx_id = cmd->arg.item_range.id_ptr; + ps_playing = source_now_playing(); if (player_state == PLAY_PLAYING) { @@ -2596,12 +2251,9 @@ playback_start(struct player_command *cmd) * and do not change player state (ignores given arguments for playing a * specified song by pos or id). */ - if (idx_id) + if (dbmfi_id && ps_playing) { - if (cur_playing) - *idx_id = cur_playing->id; - else - *idx_id = cur_streaming->id; + *dbmfi_id = ps_playing->id; } status_update(player_state); @@ -2616,10 +2268,8 @@ playback_start(struct player_command *cmd) * If either an item id or an item position is given, get the corresponding * player_source from the queue. */ - if (cmd->arg.item_range.type == RANGEARG_ID) - ps = playerqueue_get_source_byid(cmd->arg.item_range.id); - else if (cmd->arg.item_range.type == RANGEARG_POS) - ps = playerqueue_get_source_bypos(cmd->arg.item_range.start_pos); + if (qii) + ps = source_new(qii); else ps = NULL; @@ -2636,21 +2286,20 @@ playback_start(struct player_command *cmd) * * Stop playback (if it was paused) and prepare to start playback on ps. */ - if (cur_playing) - source_stop(cur_playing); - else if (cur_streaming) - source_stop(cur_streaming); + source_stop(ps_playing); + + // Free all player_source items + while (ps_playing) + { + ps_temp = ps_playing; + ps_playing = ps_playing->play_next; + source_free(ps_temp); + } cur_playing = NULL; cur_streaming = NULL; - if (shuffle) - { - source_reshuffle(); - cur_streaming = shuffle_head; - } - else - cur_streaming = ps; + cur_streaming = ps; ret = source_open(cur_streaming, 0, 1); if (ret < 0) @@ -2661,8 +2310,8 @@ playback_start(struct player_command *cmd) return -1; } - if (idx_id) - *idx_id = cur_streaming->id; + if (dbmfi_id) + *dbmfi_id = cur_streaming->id; cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000; cur_streaming->output_start = cur_streaming->stream_start; @@ -2673,7 +2322,7 @@ playback_start(struct player_command *cmd) * Player was stopped, start playing the queue */ if (shuffle) - source_reshuffle(); + queue_shuffle(queue, 0); ret = source_next(0); if (ret < 0) @@ -2762,6 +2411,61 @@ playback_start(struct player_command *cmd) return playback_start_bh(cmd); } +static int +playback_start(struct player_command *cmd) +{ + return playback_start_item(cmd, NULL); +} + +static int +playback_start_byitemid(struct player_command *cmd) +{ + int item_id; + struct queue_item_info *qii; + + item_id = cmd->arg.playback_start_param.id; + + qii = queue_get_byitemid(queue, item_id); + + return playback_start_item(cmd, qii); +} + +static int +playback_start_byindex(struct player_command *cmd) +{ + int pos; + struct queue_item_info *qii; + + pos = cmd->arg.playback_start_param.pos; + + qii = queue_get_byindex(queue, pos, 0); + + return playback_start_item(cmd, qii); +} + +static int +playback_start_bypos(struct player_command *cmd) +{ + int offset; + struct player_source *ps_playing; + struct queue_item_info *qii; + + offset = cmd->arg.playback_start_param.pos; + + ps_playing = source_now_playing(); + + if (ps_playing) + { + qii = queue_get_bypos(queue, ps_playing->queueitem_id, offset, shuffle); + } + else + { + qii = queue_get_byindex(queue, offset, shuffle); + } + + return playback_start_item(cmd, qii); +} + static int playback_prev_bh(struct player_command *cmd) { @@ -2979,7 +2683,9 @@ playback_pause_bh(struct player_command *cmd) static int playback_pause(struct player_command *cmd) { + struct player_source *ps_playing; struct player_source *ps; + struct player_source *ps_temp; uint64_t pos; pos = source_check(); @@ -2995,13 +2701,10 @@ playback_pause(struct player_command *cmd) if (player_state == PLAY_STOPPED) return -1; - if (cur_playing) - ps = cur_playing; - else - ps = cur_streaming; + ps_playing = source_now_playing(); /* Store pause position */ - ps->end = pos; + ps_playing->end = pos; cmd->raop_pending = raop_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); @@ -3010,11 +2713,20 @@ playback_pause(struct player_command *cmd) pb_timer_stop(); - if (ps->play_next) - source_stop(ps->play_next); + ps = ps_playing->play_next; + if (ps) + source_stop(ps); + + // Free all player_source items in the play_next list except the now playing source (becomes the new cur_streaming source) + while (ps) + { + ps_temp = ps; + ps = ps->play_next; + source_free(ps_temp); + } cur_playing = NULL; - cur_streaming = ps; + cur_streaming = ps_playing; cur_streaming->play_next = NULL; evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf)); @@ -3521,11 +3233,16 @@ repeat_set(struct player_command *cmd) static int shuffle_set(struct player_command *cmd) { + uint32_t cur_id; + switch (cmd->arg.intval) { case 1: if (!shuffle) - source_reshuffle(); + { + cur_id = cur_streaming ? cur_streaming->queueitem_id : 0; + queue_shuffle(queue, cur_id); + } /* FALLTHROUGH*/ case 0: shuffle = cmd->arg.intval; @@ -3541,108 +3258,44 @@ shuffle_set(struct player_command *cmd) return 0; } -static unsigned int -playerqueue_count() -{ - struct player_source *ps; - int count; - - if (!source_head) - return 0; - - count = 1; - ps = source_head->pl_next; - while (ps != source_head) - { - count++; - ps = ps->pl_next; - } - - return count; -} - -static struct player_queue * -playerqueue_get(int pos, int count, char shuffle) -{ - struct player_queue *queue; - uint32_t *ids; - unsigned int qlength; - struct player_source *ps; - int nitems; - int i; - - queue = malloc(sizeof(struct player_queue)); - - qlength = playerqueue_count(); - - if (count > 0) - nitems = count; - else - nitems = qlength - pos; - - ids = malloc(nitems * sizeof(uint32_t)); - - pos = 0; - ps = shuffle ? shuffle_head : source_head; - for (i = 0; i < pos && ps; i++) - { - ps = shuffle ? ps->shuffle_next : ps->pl_next; - } - - for (i = 0; i < nitems && ps; i++) - { - ids[pos] = ps->id; - pos++; - - ps = shuffle ? ps->shuffle_next : ps->pl_next; - } - - queue->start_pos = pos; - queue->count = nitems; - queue->queue = ids; - - queue->length = qlength; - queue->playingid = 0; - if (cur_playing) - queue->playingid = cur_playing->id; - else if (cur_streaming) - queue->playingid = cur_streaming->id; - - return queue; -} - static int -playerqueue_get_relative(struct player_command *cmd) +playerqueue_get_bypos(struct player_command *cmd) { - int pos; int count; - struct player_queue *queue; + struct queue_info *qi; struct player_source *ps; + int item_id; count = cmd->arg.queue_get_param.count; // Set pos to the position of the current item + 1 - ps = cur_playing ? cur_playing : cur_streaming; - pos = ps ? source_position(ps, shuffle) + 1 : 0; + ps = source_now_playing(); - queue = playerqueue_get(pos, count, shuffle); - cmd->arg.queue_get_param.queue = queue; + item_id = 0; + if (ps) + { + item_id = ps->queueitem_id; + } + + qi = queue_info_new_bypos(queue, item_id, count, shuffle); + + cmd->arg.queue_get_param.queue = qi; return 0; } static int -playerqueue_get_absolute(struct player_command *cmd) +playerqueue_get_byindex(struct player_command *cmd) { int pos; int count; - struct player_queue *queue; + struct queue_info *qi; pos = cmd->arg.queue_get_param.pos; count = cmd->arg.queue_get_param.count; - queue = playerqueue_get(pos, count, shuffle); - cmd->arg.queue_get_param.queue = queue; + qi = queue_info_new_byindex(queue, pos, count, 0); + cmd->arg.queue_get_param.queue = qi; return 0; } @@ -3657,45 +3310,20 @@ playerqueue_free(struct player_queue *queue) static int playerqueue_add(struct player_command *cmd) { - struct player_source *ps; - struct player_source *ps_shuffle; - struct player_source *source_tail; - struct player_source *ps_tail; + struct queue_item *items; + uint32_t cur_id; - ps = cmd->arg.ps; - ps_shuffle = ps; + items = cmd->arg.queue_add_param.items; - if (source_head) - { - /* Playlist order */ - source_tail = source_head->pl_prev; - ps_tail = ps->pl_prev; - - source_tail->pl_next = ps; - ps_tail->pl_next = source_head; - - source_head->pl_prev = ps_tail; - ps->pl_prev = source_tail; - - /* Shuffle */ - source_tail = shuffle_head->shuffle_prev; - ps_tail = ps_shuffle->shuffle_prev; - - source_tail->shuffle_next = ps_shuffle; - ps_tail->shuffle_next = shuffle_head; - - shuffle_head->shuffle_prev = ps_tail; - ps_shuffle->shuffle_prev = source_tail; - } - else - { - source_head = ps; - shuffle_head = ps_shuffle; - } + queue_add(queue, items); if (shuffle) - source_reshuffle(); + { + cur_id = cur_streaming ? cur_streaming->queueitem_id : 0; + queue_shuffle(queue, cur_id); + } + //TODO [refactor] Unnecessary if, always set plid to 0 after adding items if (cur_plid != 0) cur_plid = 0; cur_plversion++; @@ -3708,38 +3336,19 @@ playerqueue_add(struct player_command *cmd) static int playerqueue_add_next(struct player_command *cmd) { - struct player_source *ps; - struct player_source *ps_shuffle; - struct player_source *ps_playing; + struct queue_item *items; + uint32_t cur_id; - ps = cmd->arg.ps; - ps_shuffle = ps; + items = cmd->arg.queue_add_param.items; - if (source_head && cur_streaming) - { - ps_playing = cur_streaming; + cur_id = cur_streaming ? cur_streaming->queueitem_id : 0; - // Insert ps after ps_playing - ps->pl_prev->pl_next = ps_playing->pl_next; - ps_playing->pl_next->pl_prev = ps->pl_prev; - ps->pl_prev = ps_playing; - ps_playing->pl_next = ps; - - // Insert ps_shuffle after ps_playing - ps_shuffle->shuffle_prev->shuffle_next = ps_playing->shuffle_next; - ps_playing->shuffle_next->shuffle_prev = ps_shuffle->shuffle_prev; - ps_shuffle->shuffle_prev = ps_playing; - ps_playing->shuffle_next = ps_shuffle; - } - else - { - source_head = ps; - shuffle_head = ps_shuffle; - } + queue_add_after(queue, items, cur_id); if (shuffle) - source_reshuffle(); + queue_shuffle(queue, cur_id); + //TODO [refactor] Unnecessary if, always set plid to 0 after adding items if (cur_plid != 0) cur_plid = 0; cur_plversion++; @@ -3750,68 +3359,22 @@ playerqueue_add_next(struct player_command *cmd) } static int -playerqueue_move(struct player_command *cmd) +playerqueue_move_bypos(struct player_command *cmd) { - struct player_source *ps; - struct player_source *ps_src; - struct player_source *ps_dst; - int pos_max; - int i; + struct player_source *ps_playing; DPRINTF(E_DBG, L_PLAYER, "Moving song from position %d to be the next song after %d\n", cmd->arg.ps_pos[0], cmd->arg.ps_pos[1]); - ps = cur_playing ? cur_playing : cur_streaming; - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Current playing/streaming song not found\n"); - return -1; - } + ps_playing = source_now_playing(); - pos_max = MAX(cmd->arg.ps_pos[0], cmd->arg.ps_pos[1]); - ps_src = NULL; - ps_dst = NULL; + if (!ps_playing) + { + DPRINTF(E_LOG, L_PLAYER, "Can't move item, no playing item found\n"); + return -1; + } - for (i = 0; i <= pos_max; i++) - { - if (i == cmd->arg.ps_pos[0]) - ps_src = ps; - if (i == cmd->arg.ps_pos[1]) - ps_dst = ps; - - ps = next_ps(ps, shuffle); - } - - if (!ps_src || !ps_dst || (ps_src == ps_dst)) - { - DPRINTF(E_LOG, L_PLAYER, "Invalid source and/or destination for queue_move\n"); - return -1; - } - - if (shuffle) - { - // Remove ps_src from shuffle queue - ps_src->shuffle_prev->shuffle_next = ps_src->shuffle_next; - ps_src->shuffle_next->shuffle_prev = ps_src->shuffle_prev; - - // Insert after ps_dst - ps_src->shuffle_prev = ps_dst; - ps_src->shuffle_next = ps_dst->shuffle_next; - ps_dst->shuffle_next->shuffle_prev = ps_src; - ps_dst->shuffle_next = ps_src; - } - else - { - // Remove ps_src from queue - ps_src->pl_prev->pl_next = ps_src->pl_next; - ps_src->pl_next->pl_prev = ps_src->pl_prev; - - // Insert after ps_dst - ps_src->pl_prev = ps_dst; - ps_src->pl_next = ps_dst->pl_next; - ps_dst->pl_next->pl_prev = ps_src; - ps_dst->pl_next = ps_src; - } + queue_move_bypos(queue, ps_playing->queueitem_id, cmd->arg.ps_pos[0], cmd->arg.ps_pos[1], shuffle); cur_plversion++; @@ -3821,36 +3384,10 @@ playerqueue_move(struct player_command *cmd) } static int -playerqueue_remove(struct player_source *ps) +playerqueue_remove_bypos(struct player_command *cmd) { - if (ps == source_head) - source_head = ps->pl_next; - if (ps == shuffle_head) - shuffle_head = ps->shuffle_next; - - ps->shuffle_prev->shuffle_next = ps->shuffle_next; - ps->shuffle_next->shuffle_prev = ps->shuffle_prev; - - ps->pl_prev->pl_next = ps->pl_next; - ps->pl_next->pl_prev = ps->pl_prev; - - source_free(ps); - - cur_plversion++; - - listener_notify(LISTENER_PLAYLIST); - - return 0; -} - -static int -playerqueue_remove_pos_relative(struct player_command *cmd) -{ - struct player_source *ps; - struct player_source *ps_current; int pos; - int i; - int ret; + struct player_source *ps_playing; pos = cmd->arg.intval; if (pos < 1) @@ -3859,51 +3396,26 @@ playerqueue_remove_pos_relative(struct player_command *cmd) return -1; } + ps_playing = source_now_playing(); + + if (!ps_playing) + { + DPRINTF(E_LOG, L_PLAYER, "Can't remove item at pos %d, no playing item found\n", pos); + return -1; + } + DPRINTF(E_DBG, L_PLAYER, "Removing item from position %d\n", pos); + queue_remove_bypos(queue, ps_playing->queueitem_id, pos, shuffle); - ps_current = cur_playing ? cur_playing : cur_streaming; - if (!ps_current) - { - DPRINTF(E_LOG, L_PLAYER, "Current playing/streaming item not found\n"); - return -1; - } - - i = 0; - ps = ps_current; - while (ps) - { - i++; - ps = next_ps(ps, shuffle); - - if (ps == ps_current) - ps = NULL; - else if (i == pos) - break; - } - - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Can't remove requested item from queue (pos %d)\n", pos); - return -1; - } - - ret = playerqueue_remove(ps); - - return ret; + return 0; } static int -playerqueue_remove_queueitemid(struct player_command *cmd) +playerqueue_remove_byitemid(struct player_command *cmd) { - struct player_source *ps; - struct player_source *ps_current; - uint32_t pos; uint32_t id; - int i; - int ret; id = cmd->arg.id; - pos = 0; if (id < 1) { DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid id %d\n", id); @@ -3911,58 +3423,18 @@ playerqueue_remove_queueitemid(struct player_command *cmd) } DPRINTF(E_DBG, L_PLAYER, "Removing item with id %d\n", id); + queue_remove_byitemid(queue, id); - ps_current = cur_playing ? cur_playing : cur_streaming; - if (!ps_current) - { - DPRINTF(E_LOG, L_PLAYER, "Current playing/streaming item not found\n"); - return -1; - } - - i = 0; - ps = ps_current; - while (ps) - { - i++; - ps = next_ps(ps, shuffle); - - if (ps == ps_current) - ps = NULL; - else if (ps->id == id) - break; - } - - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Can't remove requested item from queue (id %d, pos %d)\n", id, pos); - return -1; - } - - ret = playerqueue_remove(ps); - - return ret; + return 0; } /* - * playerqueue_clear removes all items from the playqueue, playback must be stopped before calling playerqueue_clear + * Removes all media items from the queue */ static int playerqueue_clear(struct player_command *cmd) { - struct player_source *ps; - - if (!source_head) - return 0; - - shuffle_head = NULL; - source_head->pl_prev->pl_next = NULL; - - for (ps = source_head; ps; ps = source_head) - { - source_head = ps->pl_next; - - source_free(ps); - } + queue_clear(queue); cur_plid = 0; cur_plversion++; @@ -3973,50 +3445,12 @@ playerqueue_clear(struct player_command *cmd) } /* - * Depending on cmd->arg.intval playerqueue_empty removes all items from the history (arg.intval = 1), - * or removes all upcoming songs from the playqueue (arg.intval != 1). After calling playerqueue_empty - * to remove the upcoming songs, the playqueue will only contain the current playing song. + * Removes all items from the history */ static int -playerqueue_empty(struct player_command *cmd) +playerqueue_clear_history(struct player_command *cmd) { - int clear_hist; - struct player_source *ps; - - clear_hist = cmd->arg.intval; - if (clear_hist) - { - memset(history, 0, sizeof(struct player_history)); - } - else - { - if (!source_head || !cur_streaming) - return 0; - - // Stop playback if playing and streaming song are not the same - if (!cur_playing || cur_playing != cur_streaming) - { - playback_stop(cmd); - playerqueue_clear(cmd); - return 0; - } - - // Set head to the current playing song - shuffle_head = cur_playing; - source_head = cur_playing; - - // Free all items in the queue except the current playing song - for (ps = source_head->pl_next; ps != source_head; ps = ps->pl_next) - { - source_free(ps); - } - - // Make the queue circular again - source_head->pl_next = source_head; - source_head->pl_prev = source_head; - source_head->shuffle_next = source_head; - source_head->shuffle_prev = source_head; - } + memset(history, 0, sizeof(struct player_history)); cur_plversion++; @@ -4028,9 +3462,6 @@ playerqueue_empty(struct player_command *cmd) static int playerqueue_plid(struct player_command *cmd) { - if (!source_head) - return 0; - cur_plid = cmd->arg.id; return 0; @@ -4171,6 +3602,12 @@ player_get_status(struct player_status *status) return ret; } +/* + * Stores the now playing media item dbmfi-id in the given id pointer. + * + * @param id Pointer will hold the playing item (dbmfi) id if the function returns 0 + * @return 0 on success, -1 on failure (e. g. no playing item found) + */ int player_now_playing(uint32_t *id) { @@ -4218,14 +3655,14 @@ player_get_icy_artwork_url(uint32_t id) /* * Starts/resumes playback * - * Depending on the player state, this will either resumes playing the current item (player is paused) - * or begins playing the queue from the beginning. + * Depending on the player state, this will either resume playing the current item (player is paused) + * or begin playing the queue from the beginning. * * If shuffle is set, the queue is reshuffled prior to starting playback. * - * If a pointer is given as argument "itemid", its value will be set to the playing item id. + * If a pointer is given as argument "itemid", its value will be set to the playing item dbmfi-id. * - * @param *itemid if not NULL, will be set to the playing item id + * @param *itemid if not NULL, will be set to the playing item dbmfi-id * @return 0 if successful, -1 if an error occurred */ int @@ -4238,8 +3675,7 @@ player_playback_start(uint32_t *itemid) cmd.func = playback_start; cmd.func_bh = playback_start_bh; - cmd.arg.item_range.type = RANGEARG_NONE; - cmd.arg.item_range.id_ptr = itemid; + cmd.arg.playback_start_param.id_ptr = itemid; ret = sync_command(&cmd); @@ -4249,28 +3685,28 @@ player_playback_start(uint32_t *itemid) } /* - * Starts playback at item number "pos" of the current queue + * Starts playback with the media item at the given index of the play-queue. * * If shuffle is set, the queue is reshuffled prior to starting playback. * * If a pointer is given as argument "itemid", its value will be set to the playing item id. * + * @param index the index of the item in the play-queue * @param *itemid if not NULL, will be set to the playing item id * @return 0 if successful, -1 if an error occurred */ int -player_playback_startpos(int pos, uint32_t *itemid) +player_playback_start_byindex(int index, uint32_t *itemid) { struct player_command cmd; int ret; command_init(&cmd); - cmd.func = playback_start; + cmd.func = playback_start_byindex; cmd.func_bh = playback_start_bh; - cmd.arg.item_range.type = RANGEARG_POS; - cmd.arg.item_range.start_pos = pos; - cmd.arg.item_range.id_ptr = itemid; + cmd.arg.playback_start_param.pos = index; + cmd.arg.playback_start_param.id_ptr = itemid; ret = sync_command(&cmd); command_deinit(&cmd); @@ -4279,28 +3715,60 @@ player_playback_startpos(int pos, uint32_t *itemid) } /* - * Starts playback at item with "id" of the current queue + * Starts playback with the media item at the given position in the UpNext-queue. + * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue + * (shuffle on) after the current playing item (starting with position 0). * * If shuffle is set, the queue is reshuffled prior to starting playback. * - * If a pointer is given as argument "itemid", its value will be set to the playing item id. + * If a pointer is given as argument "itemid", its value will be set to the playing item dbmfi-id. * - * @param *itemid if not NULL, will be set to the playing item id + * @param pos the position in the UpNext-queue (zero-based) + * @param *itemid if not NULL, will be set to the playing item dbmfi-id * @return 0 if successful, -1 if an error occurred */ int -player_playback_startid(uint32_t id, uint32_t *itemid) +player_playback_start_bypos(int pos, uint32_t *itemid) { struct player_command cmd; int ret; command_init(&cmd); - cmd.func = playback_start; + cmd.func = playback_start_bypos; cmd.func_bh = playback_start_bh; - cmd.arg.item_range.type = RANGEARG_ID; - cmd.arg.item_range.id = id; - cmd.arg.item_range.id_ptr = itemid; + cmd.arg.playback_start_param.pos = pos; + cmd.arg.playback_start_param.id_ptr = itemid; + ret = sync_command(&cmd); + + command_deinit(&cmd); + + return ret; +} + +/* + * Starts playback with the media item with the given (queueitem) item-id in queue + * + * If shuffle is set, the queue is reshuffled prior to starting playback. + * + * If a pointer is given as argument "itemid", its value will be set to the playing item dbmfi-id. + * + * @param id The queue-item-id + * @param *itemid if not NULL, will be set to the playing item dbmfi-id + * @return 0 if successful, -1 if an error occurred + */ +int +player_playback_start_byitemid(uint32_t id, uint32_t *itemid) +{ + struct player_command cmd; + int ret; + + command_init(&cmd); + + cmd.func = playback_start_byitemid; + cmd.func_bh = playback_start_bh; + cmd.arg.playback_start_param.id = id; + cmd.arg.playback_start_param.id_ptr = itemid; ret = sync_command(&cmd); command_deinit(&cmd); @@ -4539,28 +4007,23 @@ player_shuffle_set(int enable) } /* - * Retrieves a list of item ids in the queue from postion 'start_pos' to 'end_pos' + * Returns the queue info for max "count" media items in the UpNext-queue * - * If start_pos is -1, the list starts with the item next from the current playing item. - * If end_pos is -1, this list contains all songs starting from 'start_pos' + * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue + * (shuffle on) after the current playing item (starting with position 0). * - * The 'shuffle' argument determines if the items are taken from the playqueue (shuffle = 0) - * or the shufflequeue (shuffle = 1). - * - * @param start_pos Start the listing from 'start_pos' - * @param end_pos End the listing at 'end_pos' - * @param shuffle If set to 1 use the shuffle queue, otherwise the playqueue - * @return List of items (ids) in the queue + * @param count max number of media items to return + * @return queue info */ -struct player_queue * -player_queue_get_relative(int count) +struct queue_info * +player_queue_get_bypos(int count) { struct player_command cmd; int ret; command_init(&cmd); - cmd.func = playerqueue_get_relative; + cmd.func = playerqueue_get_bypos; cmd.func_bh = NULL; cmd.arg.queue_get_param.pos = -1; cmd.arg.queue_get_param.count = count; @@ -4576,17 +4039,25 @@ player_queue_get_relative(int count) return cmd.arg.queue_get_param.queue; } -struct player_queue * -player_queue_get(int pos, int count) +/* + * Returns the queue info for max "count" media items starting with the item at the given + * index in the play-queue + * + * @param index Index of the play-queue for the first item + * @param count max number of media items to return + * @return queue info + */ +struct queue_info * +player_queue_get_byindex(int index, int count) { struct player_command cmd; int ret; command_init(&cmd); - cmd.func = playerqueue_get_absolute; + cmd.func = playerqueue_get_byindex; cmd.func_bh = NULL; - cmd.arg.queue_get_param.pos = pos; + cmd.arg.queue_get_param.pos = index; cmd.arg.queue_get_param.count = count; cmd.arg.queue_get_param.queue = NULL; @@ -4600,8 +4071,11 @@ player_queue_get(int pos, int count) return cmd.arg.queue_get_param.queue; } +/* + * Appends the given media items to the queue + */ int -player_queue_add(struct player_source *ps) +player_queue_add(struct queue_item *items) { struct player_command cmd; int ret; @@ -4610,46 +4084,7 @@ player_queue_add(struct player_source *ps) cmd.func = playerqueue_add; cmd.func_bh = NULL; - cmd.arg.ps = ps; - - ret = sync_command(&cmd); - - command_deinit(&cmd); - - return ret; -} - -int -player_queue_add_next(struct player_source *ps) -{ - struct player_command cmd; - int ret; - - command_init(&cmd); - - cmd.func = playerqueue_add_next; - cmd.func_bh = NULL; - cmd.arg.ps = ps; - - ret = sync_command(&cmd); - - command_deinit(&cmd); - - return ret; -} - -int -player_queue_move(int ps_pos_from, int ps_pos_to) -{ - struct player_command cmd; - int ret; - - command_init(&cmd); - - cmd.func = playerqueue_move; - cmd.func_bh = NULL; - cmd.arg.ps_pos[0] = ps_pos_from; - cmd.arg.ps_pos[1] = ps_pos_to; + cmd.arg.queue_add_param.items = items; ret = sync_command(&cmd); @@ -4659,24 +4094,71 @@ player_queue_move(int ps_pos_from, int ps_pos_to) } /* - * Removes the item at the given position from the queue, where the - * position is relative to the now playing item and dependent on the - * shuffle state of the player. - * If shuffle is on, the position determines the position in the shuffle - * queue. - * - * @param pos Position relative to the now playing item and the shuffle state - * @return 0 on success, -1 on failure + * Adds the given media items directly after the current playing/streaming media item */ int -player_queue_remove_pos_relative(int pos) +player_queue_add_next(struct queue_item *items) { struct player_command cmd; int ret; command_init(&cmd); - cmd.func = playerqueue_remove_pos_relative; + cmd.func = playerqueue_add_next; + cmd.func_bh = NULL; + cmd.arg.queue_add_param.items = items; + + ret = sync_command(&cmd); + + command_deinit(&cmd); + + return ret; +} + +/* + * Moves the media item at 'pos_from' to 'pos_to' in the UpNext-queue. + * + * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue + * (shuffle on) after the current playing item (starting with position 0). + */ +int +player_queue_move_bypos(int pos_from, int pos_to) +{ + struct player_command cmd; + int ret; + + command_init(&cmd); + + cmd.func = playerqueue_move_bypos; + cmd.func_bh = NULL; + cmd.arg.ps_pos[0] = pos_from; + cmd.arg.ps_pos[1] = pos_to; + + ret = sync_command(&cmd); + + command_deinit(&cmd); + + return ret; +} + +/* + * Removes the media item at the given position from the UpNext-queue + * + * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue + * (shuffle on) after the current playing item (starting with position 0). + * + * @param pos Position in the UpNext-queue (0-based) + * @return 0 on success, -1 on failure + */ +int +player_queue_remove_bypos(int pos) +{ + struct player_command cmd; + int ret; + + command_init(&cmd); + + cmd.func = playerqueue_remove_bypos; cmd.func_bh = NULL; cmd.arg.intval = pos; @@ -4688,20 +4170,20 @@ player_queue_remove_pos_relative(int pos) } /* - * Removes the item with the given item id from the queue + * Removes the item with the given (queueitem) item id from the queue * * @param id Id of the queue item to remove * @return 0 on success, -1 on failure */ int -player_queue_remove_queueitemid(uint32_t id) +player_queue_remove_byitemid(uint32_t id) { struct player_command cmd; int ret; command_init(&cmd); - cmd.func = playerqueue_remove_queueitemid; + cmd.func = playerqueue_remove_byitemid; cmd.func_bh = NULL; cmd.arg.id = id; @@ -4729,15 +4211,14 @@ player_queue_clear(void) } void -player_queue_empty(int clear_hist) +player_queue_clear_history() { struct player_command cmd; command_init(&cmd); - cmd.func = playerqueue_empty; + cmd.func = playerqueue_clear_history; cmd.func_bh = NULL; - cmd.arg.intval = clear_hist; sync_command(&cmd); @@ -5111,8 +4592,6 @@ player_init(void) cur_cmd = NULL; - source_head = NULL; - shuffle_head = NULL; cur_playing = NULL; cur_streaming = NULL; cur_plid = 0; @@ -5122,6 +4601,7 @@ player_init(void) repeat = REPEAT_OFF; shuffle = 0; + queue = queue_new(); history = (struct player_history *)calloc(1, sizeof(struct player_history)); /* @@ -5367,9 +4847,7 @@ player_deinit(void) return; } - if (source_head) - playerqueue_clear(NULL); - + queue_free(queue); free(history); pb_timer_stop(); diff --git a/src/player.h b/src/player.h index 2513c3b8..da4776ef 100644 --- a/src/player.h +++ b/src/player.h @@ -5,6 +5,7 @@ #include #include "db.h" +#include "queue.h" /* AirTunes v2 packet interval in ns */ /* (352 samples/packet * 1e9 ns/s) / 44100 samples/s = 7981859 ns/packet */ @@ -27,12 +28,6 @@ enum play_status { PLAY_PLAYING = 4, }; -enum repeat_mode { - REPEAT_OFF = 0, - REPEAT_SONG = 1, - REPEAT_ALL = 2, -}; - struct spk_flags { unsigned selected:1; unsigned has_password:1; @@ -72,30 +67,6 @@ struct player_status { typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg); -struct player_source -{ - uint32_t id; - uint32_t len_ms; - - enum data_kind data_kind; - enum media_kind media_kind; - int setup_done; - - 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; -}; - struct player_queue { // The item id of the current playing item @@ -146,10 +117,13 @@ int player_playback_start(uint32_t *idx_id); int -player_playback_startpos(int pos, uint32_t *itemid); +player_playback_start_byindex(int pos, uint32_t *itemid); int -player_playback_startid(uint32_t id, uint32_t *itemid); +player_playback_start_bypos(int pos, uint32_t *itemid); + +int +player_playback_start_byitemid(uint32_t id, uint32_t *itemid); int player_playback_stop(void); @@ -182,51 +156,38 @@ player_repeat_set(enum repeat_mode mode); int player_shuffle_set(int enable); -struct player_source * -player_queue_make(struct query_params *qp); -//int -//player_queue_make_daap(struct player_source **head, const char *query, const char *queuefilter, const char *sort, int quirk); +struct queue_info * +player_queue_get_bypos(int count); -struct player_source * -player_queue_make_pl(int plid, uint32_t *id); - -//struct player_source * -//player_queue_make_mpd(char *path, int recursive); - -struct player_queue * -player_queue_get_relative(int count); - -struct player_queue * -player_queue_get(int pos, int count); - -void -queue_free(struct player_queue *queue); +struct queue_info * +player_queue_get_byindex(int pos, int count); int -player_queue_add(struct player_source *ps); +player_queue_add(struct queue_item *items); int -player_queue_add_next(struct player_source *ps); +player_queue_add_next(struct queue_item *items); int -player_queue_move(int ps_pos_from, int ps_pos_to); +player_queue_move_bypos(int ps_pos_from, int ps_pos_to); int -player_queue_remove_pos_relative(int pos); +player_queue_remove_bypos(int pos); int -player_queue_remove_queueitemid(uint32_t id); +player_queue_remove_byitemid(uint32_t id); void player_queue_clear(void); void -player_queue_empty(int clear_hist); +player_queue_clear_history(void); void player_queue_plid(uint32_t plid); + struct player_history * player_history_get(void); diff --git a/src/queue.c b/src/queue.c new file mode 100644 index 00000000..64c14b53 --- /dev/null +++ b/src/queue.c @@ -0,0 +1,1091 @@ +/* + * Copyright (C) 2015 Christian Meffert + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#include "db.h" +#include "logger.h" +#include "misc.h" +#include "queue.h" +#include "rng.h" + + +/* + * Internal representation of an item in a queue. It links to the previous and the next item + * in the queue for shuffle on/off. Only the queue_item_info can be exposed. + */ +struct queue_item +{ + /* Identifies the item in the db and the queue */ + struct queue_item_info qii; + + /* Link to the previous/next item in the queue */ + struct queue_item *next; + struct queue_item *prev; + + /* Link to the previous/next item in the shuffle queue */ + struct queue_item *shuffle_next; + struct queue_item *shuffle_prev; +}; + +/* + * The queue struct references two (double) linked lists of queue_item. One for the play-queue + * and one for the shuffle-queue. + * + * Both linked lists start with the "head" item. The head item is not a media item, instead it is + * an internal item created during initialization of a new queue struct (see queue_new() function). + * The head item is always the first item in the queue and will only be removed when queue is + * destructed. + * + * The linked lists are circular, therefor the last item in a list has the first item (the head item) + * as "next" and the first item in the queue (the head item) has the last item as "prev" linked. + * + * An empty queue (with no media items) will only consist of the head item pointing to itself. + */ +struct queue +{ + /* The queue item id of the last inserted item */ + unsigned int last_inserted_item_id; + + /* The version number of the queue */ + unsigned int version; + + /* Shuffle RNG state */ + struct rng_ctx shuffle_rng; + + /* + * The head item in the queue is not an actual media item, instead it is the + * starting point for the play-queue and the shuffle-queue. It always has the + * item-id 0. The queue is circular, the last item of the queue has the head + * item as "next" and the head item has the last item as "prev". + */ + struct queue_item *head; +}; + + +/* + * Creates and initializes a new queue + */ +struct queue * +queue_new() +{ + struct queue *queue; + + queue = (struct queue *)calloc(1, sizeof(struct queue)); + queue->head = (struct queue_item *)calloc(1, sizeof(struct queue_item)); + + // Create the head item and make the queue circular (head points to itself) + queue->head->next = queue->head; + queue->head->prev = queue->head; + queue->head->shuffle_next = queue->head; + queue->head->shuffle_prev = queue->head; + + rng_init(&queue->shuffle_rng); + + return queue; +} + +/* + * Frees the given item and all linked items + */ +static void +queue_items_free(struct queue_item *item) +{ + struct queue_item *temp; + struct queue_item *next; + + item->prev->next = NULL; + + next = item; + while (next) + { + temp = next->next; + free(next); + next = temp; + } +} + +/* + * Frees the given queue and all the items in it + */ +void +queue_free(struct queue *queue) +{ + queue_items_free(queue->head); + free(queue); +} + +/* + * Returns the number of media items in the queue + * + * @param queue The queue + * @return The number of items in the queue + */ +unsigned int +queue_count(struct queue *queue) +{ + struct queue_item *item; + int count; + + count = 0; + + for (item = queue->head->next; item != queue->head; item = item->next) + { + count++; + } + + return count; +} + +/* + * Returns the next item in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) + */ +static struct queue_item * +item_next(struct queue_item *item, char shuffle) +{ + if (shuffle) + return item->shuffle_next; + return item->next; +} + +/* + * Returns the previous item in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) + */ +static struct queue_item * +item_prev(struct queue_item *item, char shuffle) +{ + if (shuffle) + return item->shuffle_prev; + return item->prev; +} + +/* + * Returns the (0-based) position of the first item with the given dbmfi-id. + * If no item is found for the given id, it returns -1. + */ +int +queueitem_pos(struct queue_item *item, uint32_t dbmfi_id) +{ + struct queue_item *temp; + int pos; + + if (dbmfi_id == 0 || item->qii.dbmfi_id == dbmfi_id) + return 0; + + pos = 1; + for (temp = item->next; (temp != item) && temp->qii.dbmfi_id != dbmfi_id; temp = temp->next) + { + pos++; + } + + if (temp == item) + { + // Item with given dbmfi_id does not exists + return -1; + } + + return pos; +} + +/* + * Returns the item with the given item_id in the queue + * + * @param queue The queue + * @param item_id The unique id of the item in the queue + * @return Item with the given item_id or NULL if not found + */ +static struct queue_item * +queueitem_get_byitemid(struct queue *queue, int item_id) +{ + struct queue_item *item; + + for (item = queue->head->next; item != queue->head && item->qii.item_id != item_id; item = item->next) + { + // Iterate through the queue until the item with item_id is found + } + + if (item == queue->head && item_id != 0) + return NULL; + + return item; +} + +/* + * Returns the item at the given index (0-based) in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) + * + * @param queue The queue + * @param index Index of item in the queue (0-based) + * @param shuffle Play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) + * @return Item at position in the queue or NULL if not found + */ +static struct queue_item * +queueitem_get_byindex(struct queue *queue, unsigned int index, char shuffle) +{ + struct queue_item *item; + int i; + + i = 0; + for (item = item_next(queue->head, shuffle); item != queue->head && i < index; item = item_next(item, shuffle)) + { + i++; + } + + if (item == queue->head) + return NULL; + + return item; +} + +/* + * Returns the item at the given position relative to the item with the given item_id in the + * play queue (shuffle = 0) or shuffle queue (shuffle = 1). + * + * The item with item_id is at pos == 0. + * + * @param queue The queue + * @param pos The position relative to the item with given queue-item-id + * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue + * @return Item at position in the queue or NULL if not found + */ +static struct queue_item * +queueitem_get_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle) +{ + struct queue_item *item_base; + struct queue_item *item; + int i; + + item_base = queueitem_get_byitemid(queue, item_id); + + if (!item_base) + return NULL; + + i = 0; + for (item = item_base; item != queue->head && i < pos; item = item_next(item, shuffle)) + { + i++; + } + + if (item == queue->head) + return NULL; + + return item; +} + +/* + * Returns the item with the given item_id in the queue + * + * @param queue The queue + * @param item_id The unique id of the item in the queue + * @return Item with the given item_id or NULL if not found + */ +struct queue_item_info * +queue_get_byitemid(struct queue *queue, unsigned int item_id) +{ + struct queue_item *item; + + item = queueitem_get_byitemid(queue, item_id); + + if (!item) + return NULL; + + return &item->qii; +} + +/* + * Returns the item at the given index (0-based) in the play queue (shuffle = 0) or shuffle queue (shuffle = 1) + * + * @param queue The queue + * @param index Position of item in the queue (zero-based) + * @param shuffle Play queue (shuffle = 0) or shuffle queue (shuffle = 1) + * @return Item at index in the queue or NULL if not found + */ +struct queue_item_info * +queue_get_byindex(struct queue *queue, unsigned int index, char shuffle) +{ + struct queue_item *item; + + item = queueitem_get_byindex(queue, index, shuffle); + + if (!item) + return NULL; + + return &item->qii; +} + +/* + * Returns the item at the given position relative to the item with the given item_id in the + * play queue (shuffle = 0) or shuffle queue (shuffle = 1). + * + * The item with item_id is at pos == 0. + * + * @param queue The queue + * @param item_id The unique id of the item in the queue + * @param pos The position relative to the item with given queue-item-id + * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue + * @return Item at position in the queue or NULL if not found + */ +struct queue_item_info * +queue_get_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle) +{ + struct queue_item *item; + + item = queueitem_get_bypos(queue, item_id, pos, shuffle); + + if (!item) + return NULL; + + return &item->qii; +} + +/* + * Returns the index of the item with the given item-id (unique id in the queue) + * or -1 if the item does not exist. Depending on the given shuffle value, the position + * is either the on in the play-queue (shuffle = 0) or the shuffle-queue (shuffle = 1). + * + * @param queue The queue to search the item + * @param item_id The id of the item in the queue + * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue + * @return Index (0-based) of the item in the given queue or -1 if it does not exist + */ +int +queue_index_byitemid(struct queue *queue, unsigned int item_id, char shuffle) +{ + struct queue_item *item; + int pos; + + pos = 0; + for (item = item_next(queue->head, shuffle); item != queue->head && item->qii.item_id != item_id; item = item_next(item, shuffle)) + { + pos++; + } + + if (item == queue->head) + // Item not found + return -1; + + return pos; +} + +/* + * Return the next item in the queue for the item with the given item-id. + * + * @param queue The queue + * @param item_id The id of the item in the queue + * @param shuffle If 0 return the next item in the play-queue, if 1 the next item in the shuffle-queue + * @param r_mode Repeat mode + * @return The next item + */ +struct queue_item_info * +queue_next(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode) +{ + struct queue_item *item; + + item = queueitem_get_byitemid(queue, item_id); + + if (!item) + // Item not found + return NULL; + + if (r_mode == REPEAT_SONG && item != queue->head) + return &item->qii; + + item = item_next(item, shuffle); + + if (item == queue->head && r_mode == REPEAT_ALL) + { + // Repeat all and end of queue reached, return first item in the queue + item = item_next(queue->head, shuffle); + } + + if (item == queue->head) + return NULL; + + return &item->qii; +} + +/* + * Return the previous item in the queue for the item with the given item-id. + * + * @param queue The queue + * @param item_id The id of the item in the queue + * @param shuffle If 0 return the next item in the play-queue, if 1 the next item in the shuffle-queue + * @param r_mode Repeat mode + * @return The previous item + */ +struct queue_item_info * +queue_prev(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode) +{ + struct queue_item *item; + + item = queueitem_get_byitemid(queue, item_id); + + if (!item) + // Item not found + return NULL; + + if (r_mode == REPEAT_SONG && item != queue->head) + return &item->qii; + + item = item_prev(item, shuffle); + + if (item == queue->head && r_mode == REPEAT_ALL) + { + // Repeat all and start of queue reached, return last item in the queue + item = item_prev(queue->head, shuffle); + } + + if (item == queue->head) + return NULL; + + return &item->qii; +} + +/* + * Creates a new queue-info for the given queue. + * + * The given number of items (count) are copied from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) + * starting with the item at the given index (0-based). + * + * If count == 0, all items from the given index up to the end of the queue will be returned. + * + * @param queue The queue + * @param index Index of the first item in the queue + * @param count Maximum number of items to copy (if 0 all remaining items after index) + * @param shuffle If 0 the play-queue, if 1 the shuffle queue + * @return A new queue-info with the specified items + */ +struct queue_info * +queue_info_new_byindex(struct queue *queue, unsigned int index, unsigned int count, char shuffle) +{ + struct queue_info *qi; + struct queue_item_info *qii; + struct queue_item *item_base; + struct queue_item *item; + unsigned int i; + unsigned int qlength; + int qii_size; + + qlength = queue_count(queue); + + qii_size = qlength - index; + if (count > 0 && count < qii_size) + qii_size = count; + + if (qii_size <= 0) + { + return NULL; + } + + item_base = queueitem_get_byindex(queue, index, shuffle); + + if (!item_base) + return NULL; + + qi = malloc(sizeof(struct queue_info)); + qii = malloc(qii_size * sizeof(struct queue_item_info)); + + i = 0; + for (item = item_base; item != queue->head && i < qii_size; item = item_next(item, shuffle)) + { + qii[i].dbmfi_id = item->qii.dbmfi_id; + qii[i].item_id = item->qii.item_id; + qii[i].len_ms = item->qii.len_ms; + qii[i].data_kind = item->qii.data_kind; + qii[i].media_kind = item->qii.media_kind; + + i++; + } + + qi->count = i; + qi->length = qlength; + qi->start_pos = index; + qi->queue = qii; + + return qi; +} + +/* + * Creates a new queue-info for the given queue. + * + * The given number of items (count) are copied from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) + * starting after the item with the given item_id. The item with item_id is excluded, therefor the first item + * is the one after the item with item_id. + * + * If count == 0, all items from the given index up to the end of the queue will be returned. + * + * @param queue The queue + * @param item_id The unique id of the item in the queue + * @param count Maximum number of items to copy (if 0 all remaining items after index) + * @param shuffle If 0 the play-queue, if 1 the shuffle queue + * @return A new queue-info with the specified items + */ +struct queue_info * +queue_info_new_bypos(struct queue *queue, unsigned int item_id, unsigned int count, char shuffle) +{ + int pos; + struct queue_info *qi; + + pos = queue_index_byitemid(queue, item_id, shuffle); + + if (pos < 0) + pos = 0; + else + pos = pos + 1; // exclude the item with the given item-id + + qi = queue_info_new_byindex(queue, pos, count, shuffle); + + return qi; +} + +/* + * Frees the queue info + */ +void +queue_info_free(struct queue_info *qi) +{ + free(qi->queue); + free(qi); +} + +/* + * Adds items to the queue after the given item + * + * @param queue The queue to add the new items + * @param item_new The item(s) to add + * @param item_prev The item to append the new items + */ +static void +queue_add_afteritem(struct queue *queue, struct queue_item *item_new, struct queue_item *item_prev) +{ + struct queue_item *item; + struct queue_item *item_tail; + + if (!item_new) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid new item given to add items\n"); + return; + } + + // Check the item after which the new items will be added + if (!item_prev) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid previous item given to add items\n"); + queue_items_free(item_new); + return; + } + + // Set item-id for all new items + queue->last_inserted_item_id++; + item_new->qii.item_id = queue->last_inserted_item_id; + for (item = item_new->next; item != item_new; item = item->next) + { + queue->last_inserted_item_id++; + item->qii.item_id = queue->last_inserted_item_id; + } + + // Add items into the queue + item_tail = item_new->prev; + + item_tail->next = item_prev->next; + item_tail->shuffle_next = item_prev->shuffle_next; + item_prev->next->prev = item_tail; + item_prev->shuffle_next->shuffle_prev = item_tail; + + item_prev->next = item_new; + item_prev->shuffle_next = item_new; + item_new->prev = item_prev; + item_new->shuffle_prev = item_prev; +} + +/* + * Adds items to the end of the queue + * + * @param queue The queue to add the new items + * @param item The item(s) to add + */ +void +queue_add(struct queue *queue, struct queue_item *item) +{ + queue_add_afteritem(queue, item, queue->head->prev); +} + +/* + * Adds items to the queue after the item with the given item id (id of the item in the queue) + * + * @param queue The queue to add the new items + * @param item The item(s) to add + * @param item_id The item id after which the new items will be inserted + */ +void +queue_add_after(struct queue *queue, struct queue_item *item, unsigned int item_id) +{ + struct queue_item *item_prev; + + // Get the item after which the new items will be added + item_prev = queueitem_get_byitemid(queue, item_id); + queue_add_afteritem(queue, item, item_prev); +} + +/* + * Moves the item at from_pos to to_pos in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) + * + * The position arguments are relativ to the item with the given id. At position = 1 is the first item + * after the item with the given id (either in the play-queue or shuffle-queue, depending on the shuffle + * argument). + * + * @param queue The queue to move items + * @param from_pos The position of the first item to be moved + * @param to_pos The position to move the items + * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue + */ +void +queue_move_bypos(struct queue *queue, unsigned int item_id, unsigned int from_pos, unsigned int to_offset, char shuffle) +{ + struct queue_item *item; + struct queue_item *item_next; + + // Get the item to be moved + item = queueitem_get_bypos(queue, item_id, from_pos, shuffle); + if (!item) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid position given to move items\n"); + return; + } + + // Get the item at the target position + item_next = queueitem_get_bypos(queue, item_id, to_offset, shuffle); + if (!item_next) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid position given to move items\n"); + return; + } + + // Remove item from the queue + if (shuffle) + { + item->shuffle_prev->shuffle_next = item->shuffle_next; + item->shuffle_next->shuffle_prev = item->shuffle_prev; + } + else + { + item->prev->next = item->next; + item->next->prev = item->prev; + } + + // Insert item into the queue befor the item at the target postion + if (shuffle) + { + item_next->shuffle_prev->shuffle_next = item; + item->shuffle_prev = item_next->shuffle_prev; + + item_next->shuffle_prev = item; + item->shuffle_next = item_next; + } + else + { + item_next->next->prev = item; + item->next = item_next->next; + + item_next->next = item; + item->prev = item_next; + } +} + +/* + * Removes the item from the queue and frees it + */ +static void +queue_remove_item(struct queue_item *item) +{ + struct queue_item *item_next; + struct queue_item *item_prev; + + item_next = item->next; + item_prev = item->prev; + + item_prev->next = item_next; + item_next->prev = item_prev; + + item_next = item->shuffle_next; + item_prev = item->shuffle_prev; + + item_prev->shuffle_next = item_next; + item_next->shuffle_prev = item_prev; + + item->next = NULL; + item->prev = NULL; + item->shuffle_next = NULL; + item->shuffle_prev = NULL; + + free(item); +} + +/* + * Removes the item with the given item-id from the queue + */ +void +queue_remove_byitemid(struct queue *queue, unsigned int item_id) +{ + struct queue_item *item; + + // Do not remove the head item + if (item_id <= 0) + return; + + // Get the item after which the items will be removed from the queue + item = queueitem_get_byitemid(queue, item_id); + if (!item) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid item-id given to remove items\n"); + return; + } + + queue_remove_item(item); +} + +/* + * Remove item at index from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) + * + * @param queue The queue + * @param index The index of the item to be removed (0-based) + * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue + */ +void +queue_remove_byindex(struct queue *queue, unsigned int index, char shuffle) +{ + struct queue_item *item; + + // Get the item after which the items will be removed from the queue + item = queueitem_get_byindex(queue, index, shuffle); + if (!item) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid position given to remove items\n"); + return; + } + + queue_remove_item(item); +} + +/* + * Removes the item at pos from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1) + * + * The position argument is relativ to the item with the given id. At position = 1 is the first item + * after the item with the given id (either in the play-queue or shuffle-queue, depending on the shuffle + * argument). + * + * @param queue The queue to add the new items + * @param item_id The unique id of the item in the queue + * @param pos The position of the first item to be removed + * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue + */ +void +queue_remove_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle) +{ + struct queue_item *item; + + // Get the item after which the items will be removed from the queue + item = queueitem_get_bypos(queue, item_id, pos, shuffle); + if (!item) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid position given to remove items\n"); + return; + } + + queue_remove_item(item); +} + +/* + * Removes all items from the queue + * + * @param queue The queue to clear + */ +void +queue_clear(struct queue *queue) +{ + struct queue_item *item; + + // Check if the queue is already empty + if (queue->head->next == queue->head) + return; + + // Remove the head item from the shuffle-queue + item = queue->head->shuffle_next; + item->shuffle_prev = queue->head->shuffle_prev; + queue->head->shuffle_prev->shuffle_next = item; + + // Remove the head item from the play-queue + item = queue->head->next; + item->prev = queue->head->prev; + queue->head->prev->next = item; + + // item now points to the first item in the play-queue (excluding the head item) + queue_items_free(item); + + // Make the queue circular again + queue->head->next = queue->head; + queue->head->prev = queue->head; + queue->head->shuffle_next = queue->head; + queue->head->shuffle_prev = queue->head; +} + +/* + * Resets the shuffle-queue to be identical to the play-queue and returns the item + * with the given item_id. + * + * If no item was found with the given item_id, it returns the head item. + */ +static struct queue_item * +queue_reset_and_find(struct queue *queue, unsigned int item_id) +{ + struct queue_item *item; + struct queue_item *temp; + + item = queue->head; + + item->shuffle_next = item->next; + item->shuffle_prev = item->prev; + + for (temp = item->next; temp != queue->head; temp = temp->next) + { + temp->shuffle_next = temp->next; + temp->shuffle_prev = temp->prev; + + if (temp->qii.item_id == item_id) + item = temp; + } + + return item; +} + +/* + * Shuffles the queue + * + * If the item_id > 0, only the items in the queue after the item (excluding it) + * with the given id are shuffled. + * + * @param queue The queue to shuffle + * @param item_id 0 to shuffle the whole queue or the item-id after which the queue gets shuffled + */ +void +queue_shuffle(struct queue *queue, unsigned int item_id) +{ + struct queue_item *temp; + struct queue_item *item; + struct queue_item **item_array; + int nitems; + int i; + + item = queue_reset_and_find(queue, item_id); + + // Count items to reshuffle + nitems = 0; + for (temp = item->next; temp != queue->head; temp = temp->next) + { + nitems++; + } + + // Do not reshuffle queue with one item + if (nitems < 2) + return; + + // Construct array for number of items in queue + item_array = (struct queue_item **)malloc(nitems * sizeof(struct queue_item *)); + if (!item_array) + { + DPRINTF(E_LOG, L_PLAYER, "Could not allocate memory for shuffle array\n"); + return; + } + + // Fill array with items in queue + i = 0; + for (temp = item->next; temp != queue->head; temp = temp->next) + { + item_array[i] = temp; + i++; + } + + // Shuffle item array + shuffle_ptr(&queue->shuffle_rng, (void **)item_array, nitems); + + // Update shuffle-next/-prev for shuffled items + for (i = 0; i < nitems; i++) + { + temp = item_array[i]; + + if (i > 0) + temp->shuffle_prev = item_array[i - 1]; + else + temp->shuffle_prev = NULL; + + if (i < (nitems - 1)) + temp->shuffle_next = item_array[i + 1]; + else + temp->shuffle_next = NULL; + } + + // Insert shuffled items after item with given item_id + item->shuffle_next = item_array[0]; + item_array[0]->shuffle_prev = item; + + queue->head->shuffle_prev = item_array[nitems - 1]; + item_array[nitems - 1]->shuffle_next = queue->head; + + free(item_array); +} + +/* + * Creates a new queue item for the given media file + * + * @param dbmfi media file info + * @return The new queue item or NULL if an error occured + */ +static struct queue_item * +queue_item_new(struct db_media_file_info *dbmfi) +{ + struct queue_item *item; + uint32_t id; + uint32_t len_ms; + uint32_t data_kind; + uint32_t media_kind; + int ret; + + ret = safe_atou32(dbmfi->id, &id); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n"); + return NULL; + } + + ret = safe_atou32(dbmfi->song_length, &len_ms); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid song length in query result!\n"); + return NULL; + } + + ret = safe_atou32(dbmfi->data_kind, &data_kind); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid data kind in query result!\n"); + return NULL; + } + + ret = safe_atou32(dbmfi->media_kind, &media_kind); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Invalid media kind in query result!\n"); + return NULL; + } + + item = (struct queue_item *) calloc(1, sizeof(struct queue_item)); + if (!item) + { + DPRINTF(E_LOG, L_PLAYER, "Out of memory for struct queue_item\n"); + return NULL; + } + + item->qii.dbmfi_id = id; + item->qii.len_ms = len_ms; + item->qii.data_kind = data_kind; + item->qii.media_kind = media_kind; + + return item; +} + +struct queue_item * +queue_make(struct query_params *qp) +{ + struct db_media_file_info dbmfi; + struct queue_item *item_head; + struct queue_item *item_tail; + struct queue_item *item_temp; + int ret; + + ret = db_query_start(qp); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Could not start query\n"); + return NULL; + } + + DPRINTF(E_DBG, L_PLAYER, "Player queue query returned %d items\n", qp->results); + + item_head = NULL; + item_tail = NULL; + while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id)) + { + item_temp = queue_item_new(&dbmfi); + if (!item_temp) + { + DPRINTF(E_LOG, L_PLAYER, "Error creating new queue_item for id '%s'\n", dbmfi.id); + continue; + } + + if (!item_head) + item_head = item_temp; + + if (item_tail) + { + item_tail->next = item_temp; + item_temp->prev = item_tail; + item_tail->shuffle_next = item_temp; + item_temp->shuffle_prev = item_tail; + } + + item_tail = item_temp; + + DPRINTF(E_DBG, L_PLAYER, "Added song id %s (%s)\n", dbmfi.id, dbmfi.title); + } + + db_query_end(qp); + + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Error fetching results\n"); + return NULL; + } + + item_head->prev = item_tail; + item_tail->next = item_head; + item_head->shuffle_prev = item_tail; + item_tail->shuffle_next = item_head; + + return item_head; +} + +struct queue_item * +queue_make_pl(int plid) +{ + struct query_params qp; + struct queue_item *item;; + + memset(&qp, 0, sizeof(struct query_params)); + + qp.id = plid; + qp.type = Q_PLITEMS; + qp.offset = 0; + qp.limit = 0; + qp.sort = S_NONE; + qp.idx_type = I_NONE; + + item = queue_make(&qp); + + return item; +} diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 00000000..27a6c0e9 --- /dev/null +++ b/src/queue.h @@ -0,0 +1,129 @@ + +#ifndef SRC_QUEUE_H_ +#define SRC_QUEUE_H_ + + +enum repeat_mode { + REPEAT_OFF = 0, + REPEAT_SONG = 1, + REPEAT_ALL = 2, +}; + + +/* + * Internal representation of a queue + */ +struct queue; + +/* + * Internal representation of a list of queue items + */ +struct queue_item; + +/* + * External representation of an item in a queue + */ +struct queue_item_info +{ + /* Item-Id is a unique id for this queue item. If the same item appears multiple + times in the queue each corresponding queue item has its own id. */ + unsigned int item_id; + + /* Id of the file/item in the files database */ + unsigned int dbmfi_id; + + /* Length of the item in ms */ + unsigned int len_ms; + + /* Data type of the item */ + enum data_kind data_kind; + /* Media type of the item */ + enum media_kind media_kind; +}; + +/* + * External representation of a queue + */ +struct queue_info +{ + // The number of items in the queue + unsigned int length; + + // The position (0-based) in the queue for the first item in the queue array + unsigned int start_pos; + // The number of items in the queue array + unsigned int count; + // The queue array (array of items infos) + struct queue_item_info *queue; +}; + +struct queue * +queue_new(); + +void +queue_free(struct queue *queue); + +unsigned int +queue_count(struct queue *queue); + +int +queueitem_pos(struct queue_item *item, uint32_t dbmfi_id); + +struct queue_item_info * +queue_get_byitemid(struct queue *queue, unsigned int item_id); + +struct queue_item_info * +queue_get_byindex(struct queue *queue, unsigned int index, char shuffle); + +struct queue_item_info * +queue_get_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle); + +int +queue_index_byitemid(struct queue *queue, unsigned int item_id, char shuffle); + +struct queue_item_info * +queue_next(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode); + +struct queue_item_info * +queue_prev(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode); + +struct queue_info * +queue_info_new_byindex(struct queue *queue, unsigned int index, unsigned int count, char shuffle); + +struct queue_info * +queue_info_new_bypos(struct queue *queue, unsigned int item_id, unsigned int count, char shuffle); + +void +queue_info_free(struct queue_info *qi); + +void +queue_add(struct queue *queue, struct queue_item *item); + +void +queue_add_after(struct queue *queue, struct queue_item *item, unsigned int item_id); + +void +queue_move_bypos(struct queue *queue, unsigned int item_id, unsigned int from_pos, unsigned int to_offset, char shuffle); + +void +queue_remove_byitemid(struct queue *queue, unsigned int item_id); + +void +queue_remove_byindex(struct queue *queue, unsigned int index, char shuffle); + +void +queue_remove_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle); + +void +queue_clear(struct queue *queue); + +void +queue_shuffle(struct queue *queue, unsigned int item_id); + +struct queue_item * +queue_make(struct query_params *qp); + +struct queue_item * +queue_make_pl(int plid); + +#endif /* SRC_QUEUE_H_ */