diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 625fc088..0e76c6a9 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -1131,23 +1131,71 @@ next_ps(struct player_source *ps, char shuffle) return ps->pl_next; } -static void -dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +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); + 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); + if (mfi) + 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) { 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=... */ @@ -1160,80 +1208,84 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, span = 50; /* Default */ param = evhttp_find_header(query, "span"); if (param) - { - ret = safe_atoi32(param, &span); - if (ret < 0) - DPRINTF(E_LOG, L_DACP, "Invalid span value in playqueue-contents request\n"); - } + { + ret = safe_atoi32(param, &span); + if (ret < 0) + DPRINTF(E_LOG, L_DACP, "Invalid span value in playqueue-contents request\n"); + } songlist = NULL; i = 0; - n = 0; + 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 */ - songlist = evbuffer_new(); - if (!songlist) + /* Get queue and make songlist only if playing or paused */ + if (status.status != PLAY_STOPPED) + { + songlist = evbuffer_new(); + if (!songlist) + { + DPRINTF(E_LOG, L_DACP, "Could not allocate songlist evbuffer for playqueue-contents\n"); + + dmap_send_error(req, "ceQR", "Out of memory"); + return; + } + + /* + * 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) + { + history = player_history_get(); + if (abs(span) > history->count) + { + start_index = history->start_index; + } + else + { + start_index = (history->start_index + history->count - abs(span)) % MAX_HISTORY_COUNT; + } + for (i = 0; i < history->count && i < abs(span); i++) + { + n++; + ret = playqueuecontents_add_source(songlist, history->buffer[(start_index + i) % MAX_HISTORY_COUNT], (n + i + 1), status.plid); + if (ret < 0) { - DPRINTF(E_LOG, L_DACP, "Could not allocate songlist evbuffer for playqueue-contents\n"); + DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); dmap_send_error(req, "ceQR", "Out of memory"); return; } - - while ((n < abs(span)) && (ps = next_ps(ps, status.shuffle)) && (ps != head)) - { - n++; - song = evbuffer_new(); - if (!song) - { - DPRINTF(E_LOG, L_DACP, "Could not allocate song evbuffer for playqueue-contents\n"); - - dmap_send_error(req, "ceQR", "Out of memory"); - return; - } - - mfi = db_file_fetch_byid(ps->id); - dmap_add_container(song, "ceQs", 16); - dmap_add_raw_uint32(song, 1); /* Database */ - dmap_add_raw_uint32(song, status.plid); - dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */ - dmap_add_raw_uint32(song, mfi->id); - dmap_add_string(song, "ceQn", mfi->title); - dmap_add_string(song, "ceQr", mfi->artist); - dmap_add_string(song, "ceQa", mfi->album); - dmap_add_string(song, "ceQg", mfi->genre); - dmap_add_long(song, "asai", mfi->songalbumid); - dmap_add_int(song, "cmmk", mfi->media_kind); - dmap_add_int(song, "casa", 1); /* Unknown */ - dmap_add_int(song, "astm", mfi->song_length); - dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */ - dmap_add_char(song, "caks", 6); /* Unknown */ - dmap_add_int(song, "ceQI", n + i + 1); - - dmap_add_container(songlist, "mlit", EVBUFFER_LENGTH(song)); - ret = evbuffer_add_buffer(songlist, song); - evbuffer_free(song); - if (mfi) - free_mfi(mfi, 0); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); - - dmap_send_error(req, "ceQR", "Out of memory"); - return; - } - } + } } + 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 = evbuffer_new(); if (!playlists) { diff --git a/src/player.c b/src/player.c index 5fd46257..21353ac2 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,28 @@ 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 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->buffer[next_index] = id; + + if (history->count < MAX_HISTORY_COUNT) + history->count++; +} + static int source_read(uint8_t *buf, int len, uint64_t rtptime) { @@ -1447,17 +1471,20 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) nbytes = 0; new = 0; while (nbytes < len) + { + if (new) { - if (new) - { - DPRINTF(E_DBG, L_PLAYER, "New file\n"); + DPRINTF(E_DBG, L_PLAYER, "New file\n"); - new = 0; + new = 0; - ret = source_next(0); - if (ret < 0) - return -1; - } + // add song to the played history + history_add(cur_streaming->id); + + ret = source_next(0); + if (ret < 0) + return -1; + } if (EVBUFFER_LENGTH(audio_buf) == 0) { @@ -2268,9 +2295,15 @@ playback_stop(struct player_command *cmd) pb_timer_fd = -1; if (cur_playing) + { + history_add(cur_playing->id); source_stop(cur_playing); - else + } + else if (cur_streaming) + { + history_add(cur_streaming->id); source_stop(cur_streaming); + } cur_playing = NULL; cur_streaming = NULL; @@ -2589,9 +2622,15 @@ playback_prev_bh(struct player_command *cmd) int ret; if (cur_playing) + { + history_add(cur_playing->id); source_stop(cur_playing); - else + } + else if (cur_streaming) + { + history_add(cur_streaming->id); source_stop(cur_streaming); + } ret = source_prev(); if (ret < 0) @@ -2621,9 +2660,15 @@ playback_next_bh(struct player_command *cmd) int ret; if (cur_playing) + { + history_add(cur_playing->id); source_stop(cur_playing); - else + } + else if (cur_streaming) + { + history_add(cur_streaming->id); source_stop(cur_streaming); + } ret = source_next(1); if (ret < 0) @@ -4429,6 +4474,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 +4667,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..8acb8d15 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 buffer[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);