diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 625fc088..1c6115cb 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -1131,23 +1131,76 @@ next_ps(struct player_source *ps, char shuffle) return ps->pl_next; } +static int +playqueuecontents_add_source(struct evbuffer *songlist, uint32_t source_id, int pos_in_queue, uint32_t plid) +{ + struct evbuffer *song; + struct media_file_info *mfi; + int ret; + + song = evbuffer_new(); + if (!song) + { + DPRINTF(E_LOG, L_DACP, "Could not allocate song evbuffer for playqueue-contents\n"); + return -1; + } + + mfi = db_file_fetch_byid(source_id); + if (!mfi) + { + DPRINTF(E_LOG, L_DACP, "Could not fetch file id %d\n", source_id); + return -1; + } + dmap_add_container(song, "ceQs", 16); + dmap_add_raw_uint32(song, 1); /* Database */ + dmap_add_raw_uint32(song, plid); + dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */ + dmap_add_raw_uint32(song, mfi->id); + dmap_add_string(song, "ceQn", mfi->title); + dmap_add_string(song, "ceQr", mfi->artist); + dmap_add_string(song, "ceQa", mfi->album); + dmap_add_string(song, "ceQg", mfi->genre); + dmap_add_long(song, "asai", mfi->songalbumid); + dmap_add_int(song, "cmmk", mfi->media_kind); + dmap_add_int(song, "casa", 1); /* Unknown */ + dmap_add_int(song, "astm", mfi->song_length); + dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */ + dmap_add_char(song, "caks", 6); /* Unknown */ + dmap_add_int(song, "ceQI", pos_in_queue); + + dmap_add_container(songlist, "mlit", EVBUFFER_LENGTH(song)); + + ret = evbuffer_add_buffer(songlist, song); + evbuffer_free(song); + free_mfi(mfi, 0); + + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); + return ret; + } + + return 0; +} + static void -dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, + struct evkeyvalq *query) { struct daap_session *s; - struct evbuffer *song; struct evbuffer *songlist; struct evbuffer *playlists; - struct media_file_info *mfi; struct player_source *ps; struct player_source *head; struct player_status status; + struct player_history *history; const char *param; int span; int i; int n; int songlist_length; int ret; + int start_index; /* /ctrl-int/1/playqueue-contents?span=50&session-id=... */ @@ -1168,17 +1221,12 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, songlist = NULL; i = 0; - n = 0; + n = 0; // count of songs in songlist player_get_status(&status); - /* Get queue and make songlist only if playing or paused */ - if ((status.status != PLAY_STOPPED) && (head = player_queue_get())) - { - /* Fast forward to song currently being played */ - ps = head; - while ((ps->id != status.id) && (ps = next_ps(ps, status.shuffle)) && (ps != head)) - i++; - /* Make song list for Up Next, begin with first song after playlist position */ + /* Get queue and make songlist only if playing or paused */ + if (status.status != PLAY_STOPPED) + { songlist = evbuffer_new(); if (!songlist) { @@ -1188,52 +1236,61 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, return; } - while ((n < abs(span)) && (ps = next_ps(ps, status.shuffle)) && (ps != head)) + /* + * If the span parameter is negativ make song list for Previously Played, + * otherwise make song list for Up Next and begin with first song after playlist position. + */ + if (span < 0) { - n++; - song = evbuffer_new(); - if (!song) + history = player_history_get(); + if (abs(span) > history->count) { - DPRINTF(E_LOG, L_DACP, "Could not allocate song evbuffer for playqueue-contents\n"); - - dmap_send_error(req, "ceQR", "Out of memory"); - return; + start_index = history->start_index; } - - mfi = db_file_fetch_byid(ps->id); - dmap_add_container(song, "ceQs", 16); - dmap_add_raw_uint32(song, 1); /* Database */ - dmap_add_raw_uint32(song, status.plid); - dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */ - dmap_add_raw_uint32(song, mfi->id); - dmap_add_string(song, "ceQn", mfi->title); - dmap_add_string(song, "ceQr", mfi->artist); - dmap_add_string(song, "ceQa", mfi->album); - dmap_add_string(song, "ceQg", mfi->genre); - dmap_add_long(song, "asai", mfi->songalbumid); - dmap_add_int(song, "cmmk", mfi->media_kind); - dmap_add_int(song, "casa", 1); /* Unknown */ - dmap_add_int(song, "astm", mfi->song_length); - dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */ - dmap_add_char(song, "caks", 6); /* Unknown */ - dmap_add_int(song, "ceQI", n + i + 1); - - dmap_add_container(songlist, "mlit", EVBUFFER_LENGTH(song)); - ret = evbuffer_add_buffer(songlist, song); - evbuffer_free(song); - if (mfi) - free_mfi(mfi, 0); - if (ret < 0) + else { - DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); - - dmap_send_error(req, "ceQR", "Out of memory"); - return; + start_index = (history->start_index + history->count - abs(span)) % MAX_HISTORY_COUNT; } - } + for (n = 0; n < history->count && n < abs(span); n++) + { + ret = playqueuecontents_add_source(songlist, history->id[(start_index + n) % MAX_HISTORY_COUNT], (n + 1), status.plid); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); + + dmap_send_error(req, "ceQR", "Out of memory"); + return; + } + } + } + else + { + /* Fast forward to song currently being played */ + head = player_queue_get(); + if (head) + { + ps = head; + while ((ps->id != status.id) && (ps = next_ps(ps, status.shuffle)) && (ps != head)) + i++; + + while ((n < abs(span)) && (ps = next_ps(ps, status.shuffle)) && (ps != head)) + { + n++; + + ret = playqueuecontents_add_source(songlist, ps->id, (n + i + 1), status.plid); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); + + dmap_send_error(req, "ceQR", "Out of memory"); + return; + } + } + } + } } - /* Playlists are hist, curr and main. Currently we don't support hist. */ + /* Playlists are hist, curr and main. */ playlists = evbuffer_new(); if (!playlists) { diff --git a/src/player.c b/src/player.c index 5fd46257..724970c4 100644 --- a/src/player.c +++ b/src/player.c @@ -204,6 +204,8 @@ static struct player_source *cur_streaming; static uint32_t cur_plid; static struct evbuffer *audio_buf; +/* Play history */ +static struct player_history *history; /* Command helpers */ static void @@ -1434,6 +1436,40 @@ source_check(void) return pos; } +struct player_history * +player_history_get(void) +{ + return history; +} + +/* + * Add the song with the given id to the list of previously played songs + */ +static void +history_add(uint32_t id) +{ + unsigned int cur_index; + unsigned int next_index; + + /* Check if the current song is already the last in the history to avoid duplicates */ + cur_index = (history->start_index + history->count - 1) % MAX_HISTORY_COUNT; + if (id == history->id[cur_index]) + { + DPRINTF(E_LOG, L_PLAYER, "Current playing/streaming song already in history\n"); + return; + } + + /* Calculate the next index and update the start-index and count for the id-buffer */ + next_index = (history->start_index + history->count) % MAX_HISTORY_COUNT; + if (next_index == history->start_index && history->count > 0) + history->start_index = (history->start_index + 1) % MAX_HISTORY_COUNT; + + history->id[next_index] = id; + + if (history->count < MAX_HISTORY_COUNT) + history->count++; +} + static int source_read(uint8_t *buf, int len, uint64_t rtptime) { @@ -1454,6 +1490,9 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) new = 0; + // add song to the played history + history_add(cur_streaming->id); + ret = source_next(0); if (ret < 0) return -1; @@ -2268,9 +2307,15 @@ playback_stop(struct player_command *cmd) pb_timer_fd = -1; if (cur_playing) - source_stop(cur_playing); - else - source_stop(cur_streaming); + { + history_add(cur_playing->id); + source_stop(cur_playing); + } + else if (cur_streaming) + { + history_add(cur_streaming->id); + source_stop(cur_streaming); + } cur_playing = NULL; cur_streaming = NULL; @@ -2588,10 +2633,17 @@ playback_prev_bh(struct player_command *cmd) { int ret; - if (cur_playing) - source_stop(cur_playing); - else - source_stop(cur_streaming); + if (!cur_streaming) + { + DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n"); + return -1; + } + + /* Only add to history if playback started. */ + if (cur_streaming->end > cur_streaming->stream_start) + history_add(cur_streaming->id); + + source_stop(cur_streaming); ret = source_prev(); if (ret < 0) @@ -2620,10 +2672,17 @@ playback_next_bh(struct player_command *cmd) { int ret; - if (cur_playing) - source_stop(cur_playing); - else - source_stop(cur_streaming); + if (!cur_streaming) + { + DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n"); + return -1; + } + + /* Only add to history if playback started. */ + if (cur_streaming->end > cur_streaming->stream_start) + history_add(cur_streaming->id); + + source_stop(cur_streaming); ret = source_next(1); if (ret < 0) @@ -4429,6 +4488,8 @@ player_init(void) update_handler = NULL; + history = (struct player_history *) calloc(1, sizeof(struct player_history)); + #if defined(__linux__) /* * Determine if the resolution of the system timer is > or < the size @@ -4620,6 +4681,8 @@ player_deinit(void) if (source_head) queue_clear(NULL); + free(history); + evbuffer_free(audio_buf); laudio_deinit(); diff --git a/src/player.h b/src/player.h index 71e5510f..21f5bded 100644 --- a/src/player.h +++ b/src/player.h @@ -21,6 +21,9 @@ #define STOB(s) ((s) * 4) #define BTOS(b) ((b) / 4) +/* Maximum number of previously played songs that are remembered */ +#define MAX_HISTORY_COUNT 20 + enum play_status { PLAY_STOPPED = 2, PLAY_PAUSED = 3, @@ -84,6 +87,19 @@ struct player_source struct player_source *play_next; }; +struct player_history +{ + /* Buffer index of the oldest remembered song */ + unsigned int start_index; + + /* Count of song ids in the buffer */ + unsigned int count; + + /* Circular buffer of song ids previously played by forked-daapd */ + uint32_t id[MAX_HISTORY_COUNT]; +}; + + int player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit); @@ -160,6 +176,9 @@ player_queue_clear(void); void player_queue_plid(uint32_t plid); +struct player_history * +player_history_get(void); + void player_set_update_handler(player_status_handler handler);