From 87ca6363aebfb0f000b3ae824bca6d53bbeb52d8 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 16 Feb 2019 19:34:36 +0100 Subject: [PATCH] [player/input] Refactor - WIP * Open input sources earlier * Gapless playback * Remove fixed 44100/16 from player * Complete restructure player internals --- src/httpd_artworkapi.c | 2 +- src/httpd_dacp.c | 4 +- src/input.c | 693 ++++++++++++------- src/input.h | 158 ++--- src/inputs/file_http.c | 120 ++-- src/inputs/pipe.c | 146 ++-- src/inputs/spotify.c | 33 +- src/misc.h | 5 + src/outputs/raop.c | 6 +- src/player.c | 1458 ++++++++++++++++++---------------------- src/player.h | 2 +- src/spotify.c | 3 +- 12 files changed, 1315 insertions(+), 1315 deletions(-) diff --git a/src/httpd_artworkapi.c b/src/httpd_artworkapi.c index df6d0827..d7198b1a 100644 --- a/src/httpd_artworkapi.c +++ b/src/httpd_artworkapi.c @@ -88,7 +88,7 @@ artworkapi_reply_nowplaying(struct httpd_request *hreq) if (ret != 0) return ret; - ret = player_now_playing(&id); + ret = player_playing_now(&id); if (ret != 0) return HTTP_NOTFOUND; diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index d8dde6b6..8364e9c6 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -1083,7 +1083,7 @@ dacp_propset_userrating(const char *value, struct httpd_request *hreq) { DPRINTF(E_WARN, L_DACP, "Invalid id %d for rating, defaulting to player id\n", itemid); - ret = player_now_playing(&itemid); + ret = player_playing_now(&itemid); if (ret < 0) { DPRINTF(E_WARN, L_DACP, "Could not find an id for rating\n"); @@ -2277,7 +2277,7 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq) goto error; } - ret = player_now_playing(&id); + ret = player_playing_now(&id); if (ret < 0) goto no_artwork; diff --git a/src/input.c b/src/input.c index 42173fba..2b633fa0 100644 --- a/src/input.c +++ b/src/input.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include "misc.h" #include "logger.h" +#include "commands.h" #include "input.h" // Disallow further writes to the buffer when its size is larger than this threshold @@ -100,10 +102,26 @@ struct input_buffer pthread_cond_t cond; }; +struct input_arg +{ + uint32_t item_id; + int seek_ms; + struct input_metadata *metadata; +}; + /* --- Globals --- */ // Input thread static pthread_t tid_input; +// Event base, cmdbase and event we use to iterate in the playback loop +static struct event_base *evbase_input; +static struct commands_base *cmdbase; +static struct event *inputev; +static bool input_initialized; + +// The source we are reading now +static struct input_source input_now_reading; + // Input buffer static struct input_buffer input_buffer; @@ -115,7 +133,7 @@ static size_t debug_elapsed; #endif -/* ------------------------------ MISC HELPERS ---------------------------- */ +/* ------------------------------- MISC HELPERS ----------------------------- */ static int map_data_kind(int data_kind) @@ -142,14 +160,14 @@ map_data_kind(int data_kind) } static void -marker_add(short flags) +marker_add(size_t pos, short flags) { struct marker *head; struct marker *marker; CHECK_NULL(L_PLAYER, marker = calloc(1, sizeof(struct marker))); - marker->pos = input_buffer.bytes_written; + marker->pos = pos; marker->quality = input_buffer.cur_write_quality; marker->flags = flags; @@ -162,88 +180,280 @@ marker_add(short flags) head->prev = marker; } -static int -source_check_and_map(struct player_source *ps, const char *action, char check_setup) + +/* ------------------------- INPUT SOURCE HANDLING -------------------------- */ + +static void +clear(struct input_source *source) { - int type; - -#ifdef DEBUG - DPRINTF(E_DBG, L_PLAYER, "Action is %s\n", action); -#endif - - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Stream %s called with invalid player source\n", action); - return -1; - } - - if (check_setup && !ps->setup_done) - { - DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, %s not possible\n", action); - return -1; - } - - type = map_data_kind(ps->data_kind); - if (type < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Unsupported input type, %s not possible\n", action); - return -1; - } - - return type; + free(source->path); + memset(source, 0, sizeof(struct input_source)); } -/* ----------------------------- PLAYBACK LOOP ---------------------------- */ -/* Thread: input */ - -// TODO Thread safety of ps? -static void * -playback(void *arg) +static void +flush(short *flags) { - struct player_source *ps = arg; - int type; - int ret; - - type = source_check_and_map(ps, "start", 1); - if ((type < 0) || (inputs[type]->disabled)) - goto thread_exit; - - // Loops until input_loop_break is set or no more input, e.g. EOF - ret = inputs[type]->start(ps); - if (ret < 0) - input_write(NULL, NULL, INPUT_FLAG_ERROR); - -#ifdef DEBUG - DPRINTF(E_DBG, L_PLAYER, "Playback loop stopped (break is %d, ret %d)\n", input_loop_break, ret); -#endif - - thread_exit: - pthread_exit(NULL); -} - -void -input_wait(void) -{ - struct timespec ts; + struct marker *marker; + size_t len; pthread_mutex_lock(&input_buffer.mutex); - ts = timespec_reltoabs(input_loop_timeout); - pthread_cond_timedwait(&input_buffer.cond, &input_buffer.mutex, &ts); + // We will return an OR of all the unread marker flags + *flags = 0; + for (marker = input_buffer.marker_tail; marker; marker = input_buffer.marker_tail) + { + *flags |= marker->flags; + input_buffer.marker_tail = marker->prev; + free(marker); + } + + len = evbuffer_get_length(input_buffer.evbuf); + + evbuffer_drain(input_buffer.evbuf, len); + + memset(&input_buffer.cur_read_quality, 0, sizeof(struct media_quality)); + memset(&input_buffer.cur_write_quality, 0, sizeof(struct media_quality)); + + input_buffer.bytes_read = 0; + input_buffer.bytes_written = 0; + + input_buffer.full_cb = NULL; pthread_mutex_unlock(&input_buffer.mutex); + +#ifdef DEBUG + DPRINTF(E_DBG, L_PLAYER, "Flushing %zu bytes with flags %d\n", len, *flags); +#endif } +static enum command_state +stop(void *arg, int *retval) +{ + short flags; + int type; + + event_del(inputev); + + type = input_now_reading.type; + + if (inputs[type]->stop) + inputs[type]->stop(&input_now_reading); + + flush(&flags); + + clear(&input_now_reading); + + *retval = 0; + return COMMAND_END; +} + +static int +seek(struct input_source *source, int seek_ms) +{ + if (seek_ms > 0 && inputs[source->type]->seek) + return inputs[source->type]->seek(source, seek_ms); + else + return 0; +} + +// On error returns -1, on success + seek given + seekable returns the position +// that the seek gave us, otherwise returns 0. +static int +setup(struct input_source *source, struct db_queue_item *queue_item, int seek_ms) +{ + int type; + int ret; + + type = map_data_kind(queue_item->data_kind); + if ((type < 0) || (inputs[type]->disabled)) + goto setup_error; + + source->type = type; + source->data_kind = queue_item->data_kind; + source->media_kind = queue_item->media_kind; + source->item_id = queue_item->id; + source->id = queue_item->file_id; + source->len_ms = queue_item->song_length; + source->path = safe_strdup(queue_item->path); + + DPRINTF(E_DBG, L_PLAYER, "Setting up input item '%s' (item id %" PRIu32 ")\n", source->path, source->item_id); + + if (inputs[type]->setup) + { + ret = inputs[type]->setup(source); + if (ret < 0) + goto setup_error; + } + + source->open = true; + + ret = seek(source, seek_ms); + if (ret < 0) + goto seek_error; + + return ret; + + seek_error: + stop(NULL, NULL); + setup_error: + clear(source); + return -1; +} + +static enum command_state +start(void *arg, int *retval) +{ + struct input_arg *cmdarg = arg; + struct db_queue_item *queue_item; + short flags; + int ret; + + // If we are asked to start the item that is currently open we can just seek + if (input_now_reading.open && cmdarg->item_id == input_now_reading.item_id) + { + flush(&flags); + + ret = seek(&input_now_reading, cmdarg->seek_ms); + if (ret < 0) + DPRINTF(E_WARN, L_PLAYER, "Ignoring failed seek to %d ms in '%s'\n", cmdarg->seek_ms, input_now_reading.path); + } + else + { + if (input_now_reading.open) + stop(NULL, NULL); + + // Get the queue_item from the db + queue_item = db_queue_fetch_byitemid(cmdarg->item_id); + if (!queue_item) + { + DPRINTF(E_LOG, L_PLAYER, "Input start was called with an item id that has disappeared (id=%d)\n", cmdarg->item_id); + goto error; + } + + ret = setup(&input_now_reading, queue_item, cmdarg->seek_ms); + free_queue_item(queue_item, 0); + if (ret < 0) + goto error; + } + + DPRINTF(E_DBG, L_PLAYER, "Starting input read loop for item '%s' (item id %" PRIu32 "), seek %d\n", + input_now_reading.path, input_now_reading.item_id, cmdarg->seek_ms); + + event_active(inputev, 0, 0); + + *retval = ret; // Return is the seek result + return COMMAND_END; + + error: + input_write(NULL, NULL, INPUT_FLAG_ERROR); + clear(&input_now_reading); + *retval = -1; + return COMMAND_END; +} + +/* +static enum command_state +next(void *arg, int *retval) +{ + struct player_status status; + struct db_queue_item *queue_item; + uint32_t item_id; + int type; + int ret; + + // We may have finished reading source way before end of playback, and we + // don't want to proceed prematurely. So we wait until the input buffer is + // below the write threshold. + ret = input_wait(); + if (ret < 0) + { + input_next(); // Async call to ourselves + return; + } + + item_id = input_now_reading.item_id; + + // Cleans up the source that has ended/failed and clears input_now_reading + stop(NULL, NULL); + + player_get_status(&status); + + // TODO what about repeat/repeat_all? Maybe move next() to player that can + // just call input_start() + + // Get the next queue_item from the db + queue_item = db_queue_fetch_next(item_id, status.shuffle); + if (!queue_item) + { + DPRINTF(E_DBG, L_PLAYER, "Reached end of playback queue\n"); + *retval = 0; + return COMMAND_END; + } + + ret = setup(&input_now_reading, queue_item, 0); + free_queue_item(queue_item, 0); + if (ret < 0) + goto error; + + DPRINTF(E_DBG, L_PLAYER, "Continuing input read loop for item '%s' (item id %" PRIu32 ")\n", input_now_reading.path, input_now_reading.item_id); + + event_active(inputev, 0, 0); + + *retval = 0; + return COMMAND_END; + + error: + input_write(NULL, NULL, INPUT_FLAG_ERROR); + clear(&input_now_reading); + *retval = -1; + return COMMAND_END; +} +*/ + +static enum command_state +metadata_get(void *arg, int *retval) +{ + struct input_arg *cmdarg = arg; + int type; + + if (!input_now_reading.open) + { + DPRINTF(E_WARN, L_PLAYER, "Source is no longer available for input_metadata_get()\n"); + goto error; + } + + type = input_now_reading.type; + if ((type < 0) || (inputs[type]->disabled)) + goto error; + + if (inputs[type]->metadata_get) + *retval = inputs[type]->metadata_get(cmdarg->metadata, &input_now_reading); + else + *retval = 0; + + return COMMAND_END; + + error: + *retval = -1; + return COMMAND_END; +} + + +/* ---------------------- Interface towards input backends ------------------ */ +/* Thread: input and spotify */ + // Called by input modules from within the playback loop int input_write(struct evbuffer *evbuf, struct media_quality *quality, short flags) { - struct timespec ts; + bool read_end; int ret; pthread_mutex_lock(&input_buffer.mutex); - while ( (!input_loop_break) && (evbuffer_get_length(input_buffer.evbuf) > INPUT_BUFFER_THRESHOLD) && evbuf ) + read_end = (flags & (INPUT_FLAG_EOF | INPUT_FLAG_ERROR)); + + if ((evbuffer_get_length(input_buffer.evbuf) > INPUT_BUFFER_THRESHOLD) && evbuf) { if (input_buffer.full_cb) { @@ -251,26 +461,19 @@ input_write(struct evbuffer *evbuf, struct media_quality *quality, short flags) input_buffer.full_cb = NULL; } - if (flags & INPUT_FLAG_NONBLOCK) + // In case of EOF or error the input is always allowed to write, even if the + // buffer is full. There is no point in holding back the input in that case. + if (!read_end) { pthread_mutex_unlock(&input_buffer.mutex); return EAGAIN; } - - ts = timespec_reltoabs(input_loop_timeout); - pthread_cond_timedwait(&input_buffer.cond, &input_buffer.mutex, &ts); - } - - if (input_loop_break) - { - pthread_mutex_unlock(&input_buffer.mutex); - return 0; } if (quality && !quality_is_equal(quality, &input_buffer.cur_write_quality)) { input_buffer.cur_write_quality = *quality; - marker_add(INPUT_FLAG_QUALITY); + marker_add(input_buffer.bytes_written, INPUT_FLAG_QUALITY); } ret = 0; @@ -280,24 +483,109 @@ input_write(struct evbuffer *evbuf, struct media_quality *quality, short flags) ret = evbuffer_add_buffer(input_buffer.evbuf, evbuf); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Error adding stream data to input buffer\n"); + DPRINTF(E_LOG, L_PLAYER, "Error adding stream data to input buffer, stopping\n"); + input_stop(); flags |= INPUT_FLAG_ERROR; } } - // Note this marker is added at the post-write position, since EOF and ERROR - // belong there. We never want to add a marker for the NONBLOCK flag. - if (flags & ~INPUT_FLAG_NONBLOCK) - marker_add(flags); + if (flags) + { + if (input_buffer.bytes_written > INPUT_BUFFER_THRESHOLD) + marker_add(input_buffer.bytes_written - INPUT_BUFFER_THRESHOLD, INPUT_FLAG_START_NEXT); + else + marker_add(input_buffer.bytes_written, INPUT_FLAG_START_NEXT); + + // Note this marker is added at the post-write position, since EOF, error + // and metadata belong there. + marker_add(input_buffer.bytes_written, flags); + } pthread_mutex_unlock(&input_buffer.mutex); return ret; } +int +input_wait(void) +{ + struct timespec ts; -/* -------------------- Interface towards player thread ------------------- */ -/* Thread: player */ + pthread_mutex_lock(&input_buffer.mutex); + + ts = timespec_reltoabs(input_loop_timeout); + pthread_cond_timedwait(&input_buffer.cond, &input_buffer.mutex, &ts); + + // Is the buffer full? + if (evbuffer_get_length(input_buffer.evbuf) > INPUT_BUFFER_THRESHOLD) + { + pthread_mutex_unlock(&input_buffer.mutex); + return -1; + } + + pthread_mutex_unlock(&input_buffer.mutex); + return 0; +} + +/*void +input_next(void) +{ + commands_exec_async(cmdbase, next, NULL); +}*/ + +/* ---------------------------------- MAIN ---------------------------------- */ +/* Thread: input */ + +static void * +input(void *arg) +{ + int ret; + + ret = db_perthread_init(); + if (ret < 0) + { + DPRINTF(E_LOG, L_MAIN, "Error: DB init failed (input thread)\n"); + pthread_exit(NULL); + } + + input_initialized = true; + + event_base_dispatch(evbase_input); + + if (input_initialized) + { + DPRINTF(E_LOG, L_MAIN, "Input event loop terminated ahead of time!\n"); + input_initialized = false; + } + + db_perthread_deinit(); + + pthread_exit(NULL); +} + +static void +play(evutil_socket_t fd, short flags, void *arg) +{ + struct timeval tv = { 0, 0 }; + int ret; + + // Spotify runs in its own thread, so no reading is done by the input thread, + // thus there is no reason to activate inputev + if (!inputs[input_now_reading.type]->play) + return; + + // Return will be negative if there is an error or EOF. Here, we just don't + // loop any more. input_write() will pass the message to the player. + ret = inputs[input_now_reading.type]->play(&input_now_reading); + if (ret < 0) + return; // Error or EOF, so don't come back + + event_add(inputev, &tv); +} + + +/* ---------------------- Interface towards player thread ------------------- */ +/* Thread: player */ int input_read(void *data, size_t size, short *flags) @@ -307,12 +595,6 @@ input_read(void *data, size_t size, short *flags) *flags = 0; - if (!tid_input) - { - DPRINTF(E_LOG, L_PLAYER, "Bug! Read called, but playback not running\n"); - return -1; - } - pthread_mutex_lock(&input_buffer.mutex); // First we check if there is a marker in the requested samples. If there is, @@ -380,157 +662,40 @@ input_buffer_full_cb(input_cb cb) } int -input_setup(struct player_source *ps) +input_seek(uint32_t item_id, int seek_ms) { - int type; + struct input_arg cmdarg; - type = source_check_and_map(ps, "setup", 0); - if ((type < 0) || (inputs[type]->disabled)) - return -1; + cmdarg.item_id = item_id; + cmdarg.seek_ms = seek_ms; - if (!inputs[type]->setup) - return 0; - - return inputs[type]->setup(ps); + return commands_exec_sync(cmdbase, start, NULL, &cmdarg); } -int -input_start(struct player_source *ps) +void +input_start(uint32_t item_id) { - int ret; + struct input_arg *cmdarg; - if (tid_input) - { - DPRINTF(E_WARN, L_PLAYER, "Input start called, but playback already running\n"); - return 0; - } + CHECK_NULL(L_PLAYER, cmdarg = malloc(sizeof(struct input_arg))); - input_loop_break = 0; + cmdarg->item_id = item_id; + cmdarg->seek_ms = 0; - ret = pthread_create(&tid_input, NULL, playback, ps); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not spawn input thread: %s\n", strerror(errno)); - return -1; - } - -#if defined(HAVE_PTHREAD_SETNAME_NP) - pthread_setname_np(tid_input, "input"); -#elif defined(HAVE_PTHREAD_SET_NAME_NP) - pthread_set_name_np(tid_input, "input"); -#endif - - return 0; + commands_exec_async(cmdbase, start, cmdarg); } -int -input_pause(struct player_source *ps) +void +input_stop(void) { - short flags; - int ret; - -#ifdef DEBUG - DPRINTF(E_DBG, L_PLAYER, "Pause called, stopping playback loop\n"); -#endif - - if (!tid_input) - return -1; - - pthread_mutex_lock(&input_buffer.mutex); - - input_loop_break = 1; - - pthread_cond_signal(&input_buffer.cond); - pthread_mutex_unlock(&input_buffer.mutex); - - // TODO What if input thread is hanging waiting for source? Kill thread? - ret = pthread_join(tid_input, NULL); - if (ret != 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not join input thread: %s\n", strerror(errno)); - return -1; - } - - tid_input = 0; - - input_flush(&flags); - - return 0; -} - -int -input_stop(struct player_source *ps) -{ - int type; - - if (tid_input) - input_pause(ps); - - if (!ps) - return 0; - - type = source_check_and_map(ps, "stop", 1); - if ((type < 0) || (inputs[type]->disabled)) - return -1; - - if (!inputs[type]->stop) - return 0; - - return inputs[type]->stop(ps); -} - -int -input_seek(struct player_source *ps, int seek_ms) -{ - int type; - - type = source_check_and_map(ps, "seek", 1); - if ((type < 0) || (inputs[type]->disabled)) - return -1; - - if (!inputs[type]->seek) - return 0; - - if (tid_input) - input_pause(ps); - - return inputs[type]->seek(ps, seek_ms); + commands_exec_async(cmdbase, stop, NULL); } void input_flush(short *flags) { - struct marker *marker; - size_t len; - - pthread_mutex_lock(&input_buffer.mutex); - - // We will return an OR of all the unread marker flags - *flags = 0; - for (marker = input_buffer.marker_tail; marker; marker = input_buffer.marker_tail) - { - *flags |= marker->flags; - input_buffer.marker_tail = marker->prev; - free(marker); - } - - len = evbuffer_get_length(input_buffer.evbuf); - - evbuffer_drain(input_buffer.evbuf, len); - - memset(&input_buffer.cur_read_quality, 0, sizeof(struct media_quality)); - memset(&input_buffer.cur_write_quality, 0, sizeof(struct media_quality)); - - input_buffer.bytes_read = 0; - input_buffer.bytes_written = 0; - - input_buffer.full_cb = NULL; - - pthread_mutex_unlock(&input_buffer.mutex); - -#ifdef DEBUG - DPRINTF(E_DBG, L_PLAYER, "Flushing %zu bytes with flags %d\n", len, *flags); -#endif + // Flush should be thread safe + flush(flags); } int @@ -542,33 +707,13 @@ input_quality_get(struct media_quality *quality) } int -input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime) +input_metadata_get(struct input_metadata *metadata) { - int type; + struct input_arg cmdarg; - if (!metadata || !ps || !ps->stream_start || !ps->output_start) - { - DPRINTF(E_LOG, L_PLAYER, "Bug! Unhandled case in input_metadata_get()\n"); - return -1; - } + cmdarg.metadata = metadata; - memset(metadata, 0, sizeof(struct input_metadata)); - - metadata->item_id = ps->item_id; - - metadata->startup = startup; - metadata->offset = ps->output_start - ps->stream_start; - metadata->rtptime = ps->stream_start; - - // Note that the source may overwrite the above progress metadata - type = source_check_and_map(ps, "metadata_get", 1); - if ((type < 0) || (inputs[type]->disabled)) - return -1; - - if (!inputs[type]->metadata_get) - return 0; - - return inputs[type]->metadata_get(metadata, ps, rtptime); + return commands_exec_sync(cmdbase, metadata_get, NULL, &cmdarg); } void @@ -597,12 +742,9 @@ input_init(void) pthread_mutex_init(&input_buffer.mutex, NULL); pthread_cond_init(&input_buffer.cond, NULL); - input_buffer.evbuf = evbuffer_new(); - if (!input_buffer.evbuf) - { - DPRINTF(E_LOG, L_PLAYER, "Out of memory for input buffer\n"); - return -1; - } + CHECK_NULL(L_PLAYER, evbase_input = event_base_new()); + CHECK_NULL(L_PLAYER, input_buffer.evbuf = evbuffer_new()); + CHECK_NULL(L_PLAYER, inputev = event_new(evbase_input, -1, EV_PERSIST, play, NULL)); no_input = 1; for (i = 0; inputs[i]; i++) @@ -610,7 +752,7 @@ input_init(void) if (inputs[i]->type != i) { DPRINTF(E_FATAL, L_PLAYER, "BUG! Input definitions are misaligned with input enum\n"); - return -1; + goto input_fail; } if (!inputs[i]->init) @@ -627,17 +769,42 @@ input_init(void) } if (no_input) - return -1; + goto input_fail; + + cmdbase = commands_base_new(evbase_input, NULL); + + ret = pthread_create(&tid_input, NULL, input, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_MAIN, "Could not spawn input thread: %s\n", strerror(errno)); + goto thread_fail; + } + +#if defined(HAVE_PTHREAD_SETNAME_NP) + pthread_setname_np(tid_input, "input"); +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_set_name_np(tid_input, "input"); +#endif return 0; + + thread_fail: + commands_base_free(cmdbase); + input_fail: + event_free(inputev); + evbuffer_free(input_buffer.evbuf); + event_base_free(evbase_input); + return -1; } void input_deinit(void) { int i; + int ret; - input_stop(NULL); +// TODO ok to do from here? + input_stop(); for (i = 0; inputs[i]; i++) { @@ -648,9 +815,21 @@ input_deinit(void) inputs[i]->deinit(); } + input_initialized = false; + commands_base_destroy(cmdbase); + + ret = pthread_join(tid_input, NULL); + if (ret != 0) + { + DPRINTF(E_FATAL, L_MAIN, "Could not join input thread: %s\n", strerror(errno)); + return; + } + pthread_cond_destroy(&input_buffer.cond); pthread_mutex_destroy(&input_buffer.mutex); + event_free(inputev); evbuffer_free(input_buffer.evbuf); + event_base_free(evbase_input); } diff --git a/src/input.h b/src/input.h index aa7120cf..e3797366 100644 --- a/src/input.h +++ b/src/input.h @@ -6,8 +6,8 @@ # include #endif #include +#include "db.h" #include "misc.h" -#include "transcode.h" // Must be in sync with inputs[] in input.c enum input_types @@ -22,64 +22,49 @@ enum input_types enum input_flags { - // Write to input buffer must not block - INPUT_FLAG_NONBLOCK = (1 << 0), + // Flags that input is closing current source + INPUT_FLAG_START_NEXT = (1 << 0), // Flags end of file - INPUT_FLAG_EOF = (1 << 1), + INPUT_FLAG_EOF = (1 << 1), // Flags error reading file - INPUT_FLAG_ERROR = (1 << 2), + INPUT_FLAG_ERROR = (1 << 2), // Flags possible new stream metadata - INPUT_FLAG_METADATA = (1 << 3), + INPUT_FLAG_METADATA = (1 << 3), // Flags new stream quality - INPUT_FLAG_QUALITY = (1 << 4), + INPUT_FLAG_QUALITY = (1 << 4), }; -struct player_source +struct input_source { - /* Id of the file/item in the files database */ - uint32_t id; + // Type of input + enum input_types type; - /* Item-Id of the file/item in the queue */ + // Item-Id of the file/item in the queue uint32_t item_id; - /* Length of the file/item in milliseconds */ + // Id of the file/item in the files database + uint32_t id; + + // Length of the file/item in milliseconds uint32_t len_ms; enum data_kind data_kind; enum media_kind media_kind; char *path; - /* Start time of the media item as rtp-time - The stream-start is the rtp-time the media item did or would have - started playing (after seek or pause), therefor the elapsed time of the - media item is always: - elapsed time = current rtptime - stream-start */ - uint64_t stream_start; + // Flags that the input has been opened (i.e. needs to be closed) + bool open; - /* Output start time of the media item as rtp-time - The output start time is the rtp-time of the first audio packet send - to the audio outputs. - It differs from stream-start especially after a seek, where the first audio - packet has the next rtp-time as output start and stream start becomes the - rtp-time the media item would have been started playing if the seek did - not happen. */ - uint64_t output_start; - - /* End time of media item as rtp-time - The end time is set if the reading (source_read) of the media item reached - end of file, until then it is 0. */ - uint64_t end; - - /* Opaque pointer to data that the input sets up when called with setup(), and - * which is cleaned up by the input with stop() - */ + // The below is private data for the input backend. It is optional for the + // backend to use, so nothing in the input or player should depend on it! + // + // Opaque pointer to data that the input backend sets up when start() is + // called, and that is cleaned up by the backend when stop() is called void *input_ctx; - - /* Input has completed setup of the source - */ - int setup_done; - - struct player_source *play_next; + // Private evbuf. Alloc'ed by backend at start() and free'd at stop() + struct evbuffer *evbuf; + // Private source quality storage + struct media_quality quality; }; typedef int (*input_cb)(void); @@ -90,6 +75,7 @@ struct input_metadata int startup; + uint64_t start; uint64_t rtptime; uint64_t offset; @@ -115,55 +101,63 @@ struct input_definition char disabled; // Prepare a playback session - int (*setup)(struct player_source *ps); + int (*setup)(struct input_source *source); - // Starts playback loop (must be defined) - int (*start)(struct player_source *ps); + // One iteration of the playback loop (= a read operation from source) + int (*play)(struct input_source *source); - // Cleans up when playback loop has ended - int (*stop)(struct player_source *ps); + // Cleans up (only required when stopping source before it ends itself) + int (*stop)(struct input_source *source); // Changes the playback position - int (*seek)(struct player_source *ps, int seek_ms); + int (*seek)(struct input_source *source, int seek_ms); // Return metadata - int (*metadata_get)(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime); + int (*metadata_get)(struct input_metadata *metadata, struct input_source *source); // Initialization function called during startup int (*init)(void); // Deinitialization function called at shutdown void (*deinit)(void); - }; -/* - * Input modules should use this to test if playback should end - */ -int input_loop_break; + +/* ---------------------- Interface towards input backends ------------------ */ +/* Thread: input and spotify */ /* * Transfer stream data to the player's input buffer. Data must be PCM-LE * samples. The input evbuf will be drained on succesful write. This is to avoid - * copying memory. If the player's input buffer is full the function will block - * until the write can be made (unless INPUT_FILE_NONBLOCK is set). + * copying memory. * * @in evbuf Raw PCM_LE audio data to write * @in evbuf Quality of the PCM (sample rate etc.) * @in flags One or more INPUT_FLAG_* - * @return 0 on success, EAGAIN if buffer was full (and _NONBLOCK is set), - * -1 on error + * @return 0 on success, EAGAIN if buffer was full, -1 on error */ int input_write(struct evbuffer *evbuf, struct media_quality *quality, short flags); /* - * Input modules can use this to wait in the playback loop (like input_write() - * would have done) + * Input modules can use this to wait for the input_buffer to be ready for + * writing. The wait is max INPUT_LOOP_TIMEOUT, which allows the event base to + * loop and process pending commands once in a while. */ -void +int input_wait(void); +/* + * Async switch to the next song in the queue. Mostly for internal use, but + * might be relevant some day externally? + */ +//void +//input_next(void); + + +/* ---------------------- Interface towards player thread ------------------- */ +/* Thread: player */ + /* * Move a chunk of stream data from the player's input buffer to an output * buffer. Should only be called by the player thread. Will not block. @@ -186,39 +180,31 @@ void input_buffer_full_cb(input_cb cb); /* - * Initializes the given player source for playback + * Tells the input to start, i.e. after calling this function the input buffer + * will begin to fill up, and should be read periodically with input_read(). If + * called while another item is still open, it will be closed and the input + * buffer will be flushed. This operation blocks. + * + * @in item_id Queue item id to start playing + * @in seek_ms Position to start playing + * @return Actual seek position if seekable, 0 otherwise, -1 on error */ int -input_setup(struct player_source *ps); +input_seek(uint32_t item_id, int seek_ms); /* - * Tells the input to start or resume playback, i.e. after calling this function - * the input buffer will begin to fill up, and should be read periodically with - * input_read(). Before calling this input_setup() must have been called. + * Same as input_seek(), just non-blocking and does not offer seek. + * + * @in item_id Queue item id to start playing */ -int -input_start(struct player_source *ps); +void +input_start(uint32_t item_id); /* - * Pauses playback of the given player source (stops playback loop) and flushes - * the input buffer + * Stops the input and clears everything. Flushes the input buffer. */ -int -input_pause(struct player_source *ps); - -/* - * Stops playback loop (if running), flushes input buffer and cleans up the - * player source - */ -int -input_stop(struct player_source *ps); - -/* - * Seeks playback position to seek_ms. Returns actual seek position, 0 on - * unseekable, -1 on error. May block. - */ -int -input_seek(struct player_source *ps, int seek_ms); +void +input_stop(void); /* * Flush input buffer. Output flags will be the same as input_read(). @@ -236,7 +222,7 @@ input_quality_get(struct media_quality *quality); * Gets metadata from the input, returns 0 if metadata is set, otherwise -1 */ int -input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime); +input_metadata_get(struct input_metadata *metadata); /* * Free the entire struct diff --git a/src/inputs/file_http.c b/src/inputs/file_http.c index 4f2743f1..4c435722 100644 --- a/src/inputs/file_http.c +++ b/src/inputs/file_http.c @@ -30,96 +30,100 @@ #include "input.h" static int -setup(struct player_source *ps) +setup(struct input_source *source) { - ps->input_ctx = transcode_setup(XCODE_PCM_NATIVE, ps->data_kind, ps->path, ps->len_ms, NULL); - if (!ps->input_ctx) + struct transcode_ctx *ctx; + + ctx = transcode_setup(XCODE_PCM_NATIVE, source->data_kind, source->path, source->len_ms, NULL); + if (!ctx) return -1; - ps->setup_done = 1; + CHECK_NULL(L_PLAYER, source->evbuf = evbuffer_new()); + + source->quality.sample_rate = transcode_encode_query(ctx->encode_ctx, "sample_rate"); + source->quality.bits_per_sample = transcode_encode_query(ctx->encode_ctx, "bits_per_sample"); + source->quality.channels = transcode_encode_query(ctx->encode_ctx, "channels"); + + source->input_ctx = ctx; return 0; } static int -setup_http(struct player_source *ps) +setup_http(struct input_source *source) { char *url; - if (http_stream_setup(&url, ps->path) < 0) + if (http_stream_setup(&url, source->path) < 0) return -1; - free(ps->path); - ps->path = url; + free(source->path); + source->path = url; - return setup(ps); + return setup(source); } static int -start(struct player_source *ps) +stop(struct input_source *source) { - struct transcode_ctx *ctx = ps->input_ctx; - struct media_quality quality = { 0 }; - struct evbuffer *evbuf; - short flags; - int ret; - int icy_timer; - - evbuf = evbuffer_new(); - - quality.sample_rate = transcode_encode_query(ctx->encode_ctx, "sample_rate"); - quality.bits_per_sample = transcode_encode_query(ctx->encode_ctx, "bits_per_sample"); - quality.channels = transcode_encode_query(ctx->encode_ctx, "channels"); - - ret = -1; - flags = 0; - while (!input_loop_break && !(flags & INPUT_FLAG_EOF)) - { - // We set "wanted" to 1 because the read size doesn't matter to us - // TODO optimize? - ret = transcode(evbuf, &icy_timer, ctx, 1); - if (ret < 0) - break; - - flags = ((ret == 0) ? INPUT_FLAG_EOF : 0) | - (icy_timer ? INPUT_FLAG_METADATA : 0); - - ret = input_write(evbuf, &quality, flags); - if (ret < 0) - break; - } - - evbuffer_free(evbuf); - - return ret; -} - -static int -stop(struct player_source *ps) -{ - struct transcode_ctx *ctx = ps->input_ctx; + struct transcode_ctx *ctx = source->input_ctx; transcode_cleanup(&ctx); + evbuffer_free(source->evbuf); - ps->input_ctx = NULL; - ps->setup_done = 0; + source->input_ctx = NULL; return 0; } static int -seek(struct player_source *ps, int seek_ms) +play(struct input_source *source) { - return transcode_seek(ps->input_ctx, seek_ms); + struct transcode_ctx *ctx = source->input_ctx; + int icy_timer; + int ret; + short flags; + + ret = input_wait(); + if (ret < 0) + return 0; // Loop, input_buffer is not ready for writing + + // We set "wanted" to 1 because the read size doesn't matter to us + // TODO optimize? + ret = transcode(source->evbuf, &icy_timer, ctx, 1); + if (ret == 0) + { + input_write(source->evbuf, &source->quality, INPUT_FLAG_EOF); + stop(source); + return -1; + } + else if (ret < 0) + { + input_write(NULL, NULL, INPUT_FLAG_ERROR); + stop(source); + return -1; + } + + flags = (icy_timer ? INPUT_FLAG_METADATA : 0); + + input_write(source->evbuf, &source->quality, flags); + + return 0; } static int -metadata_get_http(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime) +seek(struct input_source *source, int seek_ms) +{ + return transcode_seek(source->input_ctx, seek_ms); +} + +static int +metadata_get_http(struct input_metadata *metadata, struct input_source *source) { struct http_icy_metadata *m; int changed; - m = transcode_metadata(ps->input_ctx, &changed); + m = transcode_metadata(source->input_ctx, &changed); if (!m) return -1; @@ -147,7 +151,7 @@ struct input_definition input_file = .type = INPUT_TYPE_FILE, .disabled = 0, .setup = setup, - .start = start, + .play = play, .stop = stop, .seek = seek, }; @@ -158,7 +162,7 @@ struct input_definition input_http = .type = INPUT_TYPE_HTTP, .disabled = 0, .setup = setup_http, - .start = start, + .play = play, .stop = stop, .metadata_get = metadata_get_http, }; diff --git a/src/inputs/pipe.c b/src/inputs/pipe.c index b37308bc..a13dced1 100644 --- a/src/inputs/pipe.c +++ b/src/inputs/pipe.c @@ -125,7 +125,7 @@ static pthread_mutex_t pipe_metadata_lock; // True if there is new metadata to push to the player static bool pipe_metadata_is_new; -/* -------------------------------- HELPERS ------------------------------- */ +/* -------------------------------- HELPERS --------------------------------- */ static struct pipe * pipe_create(const char *path, int id, enum pipetype type, event_callback_fn cb) @@ -500,8 +500,8 @@ pipe_metadata_parse(struct input_metadata *m, struct evbuffer *evbuf) } -/* ----------------------------- PIPE WATCHING ---------------------------- */ -/* Thread: pipe */ +/* ------------------------------ PIPE WATCHING ----------------------------- */ +/* Thread: pipe */ // Some data arrived on a pipe we watch - let's autostart playback static void @@ -617,8 +617,8 @@ pipe_thread_run(void *arg) } -/* -------------------------- METADATA PIPE HANDLING ---------------------- */ -/* Thread: worker */ +/* --------------------------- METADATA PIPE HANDLING ----------------------- */ +/* Thread: worker */ static void pipe_metadata_watch_del(void *arg) @@ -706,8 +706,8 @@ pipe_metadata_watch_add(void *arg) } -/* ---------------------- PIPE WATCH THREAD START/STOP -------------------- */ -/* Thread: filescanner */ +/* ----------------------- PIPE WATCH THREAD START/STOP --------------------- */ +/* Thread: filescanner */ static void pipe_thread_start(void) @@ -802,92 +802,46 @@ pipe_listener_cb(short event_mask) } -/* -------------------------- PIPE INPUT INTERFACE ------------------------ */ -/* Thread: player/input */ +/* --------------------------- PIPE INPUT INTERFACE ------------------------- */ +/* Thread: input */ static int -setup(struct player_source *ps) +setup(struct input_source *source) { struct pipe *pipe; int fd; - fd = pipe_open(ps->path, 0); + fd = pipe_open(source->path, 0); if (fd < 0) return -1; - CHECK_NULL(L_PLAYER, pipe = pipe_create(ps->path, ps->id, PIPE_PCM, NULL)); + CHECK_NULL(L_PLAYER, pipe = pipe_create(source->path, source->id, PIPE_PCM, NULL)); + CHECK_NULL(L_PLAYER, source->evbuf = evbuffer_new()); + pipe->fd = fd; - pipe->is_autostarted = (ps->id == pipe_autostart_id); + pipe->is_autostarted = (source->id == pipe_autostart_id); - worker_execute(pipe_metadata_watch_add, ps->path, strlen(ps->path) + 1, 0); + worker_execute(pipe_metadata_watch_add, source->path, strlen(source->path) + 1, 0); - ps->input_ctx = pipe; - ps->setup_done = 1; + source->input_ctx = pipe; + + source->quality.sample_rate = pipe_sample_rate; + source->quality.bits_per_sample = pipe_bits_per_sample; + source->quality.channels = 2; return 0; } static int -start(struct player_source *ps) +stop(struct input_source *source) { - struct pipe *pipe = ps->input_ctx; - struct media_quality quality = { 0 }; - struct evbuffer *evbuf; - short flags; - int ret; - - evbuf = evbuffer_new(); - if (!evbuf) - { - DPRINTF(E_LOG, L_PLAYER, "Out of memory for pipe evbuf\n"); - return -1; - } - - quality.sample_rate = pipe_sample_rate; - quality.bits_per_sample = pipe_bits_per_sample; - quality.channels = 2; - - ret = -1; - while (!input_loop_break) - { - ret = evbuffer_read(evbuf, pipe->fd, PIPE_READ_MAX); - if ((ret == 0) && (pipe->is_autostarted)) - { - input_write(evbuf, NULL, INPUT_FLAG_EOF); // Autostop - break; - } - else if ((ret == 0) || ((ret < 0) && (errno == EAGAIN))) - { - input_wait(); - continue; - } - else if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe '%s': %s\n", ps->path, strerror(errno)); - break; - } - - flags = (pipe_metadata_is_new ? INPUT_FLAG_METADATA : 0); - pipe_metadata_is_new = 0; - - ret = input_write(evbuf, &quality, flags); - if (ret < 0) - break; - } - - evbuffer_free(evbuf); - - return ret; -} - -static int -stop(struct player_source *ps) -{ - struct pipe *pipe = ps->input_ctx; + struct pipe *pipe = source->input_ctx; union pipe_arg *cmdarg; DPRINTF(E_DBG, L_PLAYER, "Stopping pipe\n"); + evbuffer_free(source->evbuf); + pipe_close(pipe->fd); // Reset the pipe and start watching it again for new data. Must be async or @@ -903,14 +857,51 @@ stop(struct player_source *ps) pipe_free(pipe); - ps->input_ctx = NULL; - ps->setup_done = 0; + source->input_ctx = NULL; return 0; } static int -metadata_get(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime) +play(struct input_source *source) +{ + struct pipe *pipe = source->input_ctx; + short flags; + int ret; + + ret = input_wait(); + if (ret < 0) + return 0; // Loop, input_buffer is not ready for writing + + ret = evbuffer_read(source->evbuf, pipe->fd, PIPE_READ_MAX); + if ((ret == 0) && (pipe->is_autostarted)) + { + input_write(source->evbuf, NULL, INPUT_FLAG_EOF); // Autostop + stop(source); + return -1; + } + else if ((ret == 0) || ((ret < 0) && (errno == EAGAIN))) + { + return 0; // Loop + } + else if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe '%s': %s\n", source->path, strerror(errno)); + input_write(NULL, NULL, INPUT_FLAG_ERROR); + stop(source); + return -1; + } + + flags = (pipe_metadata_is_new ? INPUT_FLAG_METADATA : 0); + pipe_metadata_is_new = 0; + + input_write(source->evbuf, &source->quality, flags); + + return 0; +} + +static int +metadata_get(struct input_metadata *metadata, struct input_source *source) { pthread_mutex_lock(&pipe_metadata_lock); @@ -927,8 +918,9 @@ metadata_get(struct input_metadata *metadata, struct player_source *ps, uint64_t if (pipe_metadata_parsed.song_length) { - if (rtptime > ps->stream_start) - metadata->rtptime = rtptime - pipe_metadata_parsed.offset; +// TODO this is probably broken + if (metadata->rtptime > metadata->start) + metadata->rtptime -= pipe_metadata_parsed.offset; metadata->offset = pipe_metadata_parsed.offset; metadata->song_length = pipe_metadata_parsed.song_length; } @@ -988,7 +980,7 @@ struct input_definition input_pipe = .type = INPUT_TYPE_PIPE, .disabled = 0, .setup = setup, - .start = start, + .play = play, .stop = stop, .metadata_get = metadata_get, .init = init, diff --git a/src/inputs/spotify.c b/src/inputs/spotify.c index a52ebc07..cddc1424 100644 --- a/src/inputs/spotify.c +++ b/src/inputs/spotify.c @@ -31,12 +31,12 @@ #define SPOTIFY_SETUP_RETRY_WAIT 500000 static int -setup(struct player_source *ps) +setup(struct input_source *source) { int i = 0; int ret; - while((ret = spotify_playback_setup(ps->path)) == SPOTIFY_SETUP_ERROR_IS_LOADING) + while((ret = spotify_playback_setup(source->path)) == SPOTIFY_SETUP_ERROR_IS_LOADING) { if (i >= SPOTIFY_SETUP_RETRIES) break; @@ -49,32 +49,15 @@ setup(struct player_source *ps) if (ret < 0) return -1; - ps->setup_done = 1; + ret = spotify_playback_play(); + if (ret < 0) + return -1; return 0; } static int -start(struct player_source *ps) -{ - int ret; - - ret = spotify_playback_play(); - if (ret < 0) - return -1; - - while (!input_loop_break) - { - input_wait(); - } - - ret = spotify_playback_pause(); - - return ret; -} - -static int -stop(struct player_source *ps) +stop(struct input_source *source) { int ret; @@ -82,13 +65,11 @@ stop(struct player_source *ps) if (ret < 0) return -1; - ps->setup_done = 0; - return 0; } static int -seek(struct player_source *ps, int seek_ms) +seek(struct input_source *source, int seek_ms) { int ret; diff --git a/src/misc.h b/src/misc.h index 2921cfa4..dcd0396c 100644 --- a/src/misc.h +++ b/src/misc.h @@ -21,6 +21,11 @@ # define MIN(a, b) ((a < b) ? a : b) #endif +#ifndef MAX +#define MAX(a, b) ((a > b) ? a : b) +#endif + + // Remember to adjust quality_is_equal() if adding elements struct media_quality { int sample_rate; diff --git a/src/outputs/raop.c b/src/outputs/raop.c index 688cae1f..0dd347f3 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -2358,6 +2358,9 @@ raop_metadata_send_progress(struct raop_session *rs, struct evbuffer *evbuf, str return -1; } + DPRINTF(E_DBG, L_PLAYER, "Metadata send is start_time=%zu, start=%zu, display=%zu, current=%zu, end=%zu\n", + rs->start_rtptime, rmd->start, rmd->start - delay, rmd->start + offset, rmd->end); + ret = raop_send_req_set_parameter(rs, evbuf, "text/parameters", NULL, raop_cb_metadata, "send_progress"); if (ret < 0) DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER progress request to '%s'\n", rs->devname); @@ -3014,7 +3017,8 @@ packets_sync_send(struct raop_master_session *rms, struct timespec pts) // OUTPUTS_BUFFER_DURATION secs into the future. However, in the sync packet // we want to tell the device what it should be playing right now. So we give // it a cur_time where we subtract this duration. - cur_stamp.ts.tv_sec = pts.tv_sec - OUTPUTS_BUFFER_DURATION; +// TODO update comment to match reality + cur_stamp.ts.tv_sec = pts.tv_sec; cur_stamp.ts.tv_nsec = pts.tv_nsec; // The cur_pos will be the rtptime of the coming packet, minus diff --git a/src/player.c b/src/player.c index c5b11300..a42cc9da 100644 --- a/src/player.c +++ b/src/player.c @@ -1,6 +1,6 @@ /* + * Copyright (C) 2016-2019 Espen Jürgensen * Copyright (C) 2010-2011 Julien BLACHE - * Copyright (C) 2016-2017 Espen Jürgensen * * 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 @@ -101,14 +101,6 @@ # include "lastfm.h" #endif -#ifndef MIN -# define MIN(a, b) ((a < b) ? a : b) -#endif - -#ifndef MAX -#define MAX(a, b) ((a > b) ? a : b) -#endif - // Default volume (must be from 0 - 100) #define PLAYER_DEFAULT_VOLUME 50 @@ -134,9 +126,6 @@ // (value is in milliseconds) #define PLAYER_WRITE_BEHIND_MAX 1500 -// TODO get rid of me -#define TEMP_NEXT_RTPTIME (last_rtptime + pb_session.samples_written + pb_session.samples_per_read) - struct volume_param { int volume; uint64_t spk_id; @@ -185,25 +174,78 @@ union player_arg int intval; }; +struct player_source +{ + /* Id of the file/item in the files database */ + uint32_t id; + + /* Item-Id of the file/item in the queue */ + uint32_t item_id; + + /* Length of the file/item in milliseconds */ + uint32_t len_ms; + + /* Quality of the source (sample rate etc.) */ + struct media_quality quality; + + enum data_kind data_kind; + enum media_kind media_kind; + char *path; + + // This is the position (measured in samples) the session was at when we + // started reading from the input source. + uint64_t read_start; + + // This is the position (measured in samples) the session was at when we got + // a EOF or error from the input. + uint64_t read_end; + + // Same as the above, but added with samples equivalent to + // OUTPUTS_BUFFER_DURATION. So when the session position reaches play_start it + // means the media should actually be playing on your device. + uint64_t play_start; + uint64_t play_end; + + // The number of milliseconds into the media that we started + uint32_t seek_ms; + // This should at any time match the millisecond position of the media that is + // coming out of your device. Will be 0 during initial buffering. + uint32_t pos_ms; + + // How many samples the outputs buffer before playing (=delay) + int output_buffer_samples; +}; + struct player_session { uint8_t *buffer; size_t bufsize; - uint32_t samples_written; - int samples_per_read; - - struct media_quality quality; - // The time the playback session started struct timespec start_ts; // The time the first sample in the buffer should be played by the output. // It will be equal to: // pts = start_ts + OUTPUTS_BUFFER_DURATION + ticks_elapsed * player_tick_interval +// TODO is above still correct? struct timespec pts; + + // Equals current number of samples written to outputs + uint32_t pos; + + // The item from the queue being read by the input now, previously and next + struct player_source *reading_now; + struct player_source *reading_next; + struct player_source *reading_prev; + + // The item from the queue being played right now. This will normally point at + // reading_now or reading_prev. It should only be NULL if no playback. + struct player_source *playing_now; }; + +static int debug_counter = -1; + static struct player_session pb_session; struct event_base *evbase_player; @@ -250,8 +292,8 @@ static int pb_write_deficit_max; static bool pb_write_recovery; // Sync values -static struct timespec pb_pos_stamp; -static uint64_t pb_pos; +//static struct timespec pb_pos_stamp; +//static uint64_t pb_pos; // Stream position (packets) static uint64_t last_rtptime; @@ -263,8 +305,6 @@ static int output_sessions; static int master_volume; // Audio source -static struct player_source *cur_playing; -static struct player_source *cur_streaming; static uint32_t cur_plid; static uint32_t cur_plversion; @@ -284,8 +324,6 @@ playback_abort(void); static void playback_suspend(void); -static int -player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit); /* ----------------------------- Volume helpers ----------------------------- */ @@ -417,6 +455,12 @@ metadata_update_cb(void *arg) struct db_queue_item *queue_item; int ret; + ret = input_metadata_get(metadata); + if (ret < 0) + { + goto out_free_metadata; + } + queue_item = db_queue_fetch_byitemid(metadata->item_id); if (!queue_item) { @@ -470,11 +514,15 @@ static void metadata_trigger(int startup) { struct input_metadata metadata; - int ret; - ret = input_metadata_get(&metadata, cur_streaming, startup, TEMP_NEXT_RTPTIME); - if (ret < 0) - return; + memset(&metadata, 0, sizeof(struct input_metadata)); + + metadata.item_id = pb_session.playing_now->item_id; + + metadata.startup = startup; + metadata.start = pb_session.playing_now->read_start; + metadata.offset = pb_session.playing_now->play_start - pb_session.playing_now->read_start; + metadata.rtptime = pb_session.pos; worker_execute(metadata_update_cb, &metadata, sizeof(metadata), 0); } @@ -511,16 +559,57 @@ history_add(uint32_t id, uint32_t item_id) static void seek_save(void) { - int seek; + struct player_source *ps = pb_session.playing_now; - if (!cur_streaming) - return; + if (ps && (ps->media_kind & (MEDIA_KIND_MOVIE | MEDIA_KIND_PODCAST | MEDIA_KIND_AUDIOBOOK | MEDIA_KIND_TVSHOW))) + db_file_seek_update(ps->id, ps->pos_ms); +} - if (cur_streaming->media_kind & (MEDIA_KIND_MOVIE | MEDIA_KIND_PODCAST | MEDIA_KIND_AUDIOBOOK | MEDIA_KIND_TVSHOW)) +/* + * Returns the next queue item based on the current streaming source and repeat mode + * + * If repeat mode is repeat all, shuffle is active and the current streaming source is the + * last item in the queue, the queue is reshuffled prior to returning the first item of the + * queue. + */ +static struct db_queue_item * +queue_item_next(uint32_t item_id) +{ + struct db_queue_item *queue_item; + + if (repeat == REPEAT_SONG) { - seek = (cur_streaming->output_start - cur_streaming->stream_start) / 44100 * 1000; - db_file_seek_update(cur_streaming->id, seek); + queue_item = db_queue_fetch_byitemid(item_id); + if (!queue_item) + return NULL; } + else + { + queue_item = db_queue_fetch_next(item_id, shuffle); + if (!queue_item && repeat == REPEAT_ALL) + { + if (shuffle) + db_queue_reshuffle(0); + + queue_item = db_queue_fetch_bypos(0, shuffle); + if (!queue_item) + return NULL; + } + } + + if (!queue_item) + { + DPRINTF(E_DBG, L_PLAYER, "Reached end of queue\n"); + return NULL; + } + + return queue_item; +} + +static struct db_queue_item * +queue_item_prev(uint32_t item_id) +{ + return db_queue_fetch_prev(item_id, shuffle); } static void @@ -534,15 +623,6 @@ status_update(enum play_status status) /* ----------- Audio source handling (interfaces with input module) --------- */ -static struct player_source * -source_now_playing() -{ - if (cur_playing) - return cur_playing; - - return cur_streaming; -} - /* * Creates a new player source for the given queue item */ @@ -551,559 +631,442 @@ source_new(struct db_queue_item *queue_item) { struct player_source *ps; - ps = calloc(1, sizeof(struct player_source)); - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Out of memory (ps)\n"); - return NULL; - } + CHECK_NULL(L_PLAYER, ps = calloc(1, sizeof(struct player_source))); ps->id = queue_item->file_id; ps->item_id = queue_item->id; ps->data_kind = queue_item->data_kind; ps->media_kind = queue_item->media_kind; ps->len_ms = queue_item->song_length; - ps->play_next = NULL; ps->path = strdup(queue_item->path); return ps; } static void -source_free(struct player_source *ps) +source_free(struct player_source **ps) { - if (ps->path) - free(ps->path); + if (!(*ps)) + return; - free(ps); + free((*ps)->path); + free(*ps); + + *ps = NULL; } -/* - * Stops playback for the current streaming source and frees all - * player sources (starting from the playing source). Sets current streaming - * and playing sources to NULL. - */ static void -source_stop() +source_stop(void) { - struct player_source *ps_playing; - struct player_source *ps_temp; - - if (cur_streaming) - input_stop(cur_streaming); - - ps_playing = source_now_playing(); - - while (ps_playing) - { - ps_temp = ps_playing; - ps_playing = ps_playing->play_next; - - ps_temp->play_next = NULL; - source_free(ps_temp); - } - - cur_playing = NULL; - cur_streaming = NULL; + input_stop(); } -/* - * Pauses playback - * - * Resets the streaming source to the playing source and adjusts stream-start - * and output-start values to the playing time. Sets the current streaming - * source to NULL. - */ -static int -source_pause(uint64_t pos) +static struct player_source * +source_next_create(struct player_source *current) { - struct player_source *ps_playing; - struct player_source *ps_playnext; - struct player_source *ps_temp; - uint64_t seek_frames; - int seek_ms; - int ret; - - ps_playing = source_now_playing(); - if (!ps_playing) - return -1; - - if (cur_streaming) - { - if (ps_playing != cur_streaming) - { - DPRINTF(E_DBG, L_PLAYER, - "Pause called on playing source (id=%d) and streaming source already " - "switched to the next item (id=%d)\n", ps_playing->id, cur_streaming->id); - ret = input_stop(cur_streaming); - if (ret < 0) - return -1; - } - else - { - ret = input_pause(cur_streaming); - if (ret < 0) - return -1; - } - } - - ps_playnext = ps_playing->play_next; - while (ps_playnext) - { - ps_temp = ps_playnext; - ps_playnext = ps_playnext->play_next; - - ps_temp->play_next = NULL; - source_free(ps_temp); - } - ps_playing->play_next = NULL; - - cur_playing = NULL; - cur_streaming = ps_playing; - - if (!cur_streaming->setup_done) - { - DPRINTF(E_INFO, L_PLAYER, "Opening '%s'\n", cur_streaming->path); - - ret = input_setup(cur_streaming); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s'\n", cur_streaming->path); - return -1; - } - } - - // Seek back to the pause position - seek_frames = (pos - cur_streaming->stream_start); - seek_ms = (int)((seek_frames * 1000) / 44100); - ret = input_seek(cur_streaming, seek_ms); - -// TODO what if ret < 0? - - // Adjust start_pos to take into account the pause and seek back - cur_streaming->stream_start = TEMP_NEXT_RTPTIME - ((uint64_t)ret * 44100) / 1000; - cur_streaming->output_start = TEMP_NEXT_RTPTIME; - cur_streaming->end = 0; - - return 0; -} - -/* - * Seeks the current streaming source to the given postion in milliseconds - * and adjusts stream-start and output-start values. - * - * @param seek_ms Position in milliseconds to seek - * @return The new position in milliseconds or -1 on error - */ -static int -source_seek(int seek_ms) -{ - int ret; - - ret = input_seek(cur_streaming, seek_ms); - if (ret < 0) - return -1; - - // Adjust start_pos to take into account the pause and seek back - cur_streaming->stream_start = TEMP_NEXT_RTPTIME - ((uint64_t)ret * 44100) / 1000; - cur_streaming->output_start = TEMP_NEXT_RTPTIME; - - return ret; -} - -/* - * Starts or resumes playback - */ -static int -source_play() -{ - int ret; - - ret = input_start(cur_streaming); - - return ret; -} - -/* - * Opens the given player source for playback (but does not start playback) - * - * The given source is appended to the current streaming source (if one exists) and - * becomes the new current streaming source. - * - * Stream-start and output-start values are set to the given start position. - */ -static int -source_open(struct player_source *ps, uint64_t start_pos, int seek_ms) -{ - int ret; - - DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id); - - if (cur_streaming && cur_streaming->end == 0) - { - DPRINTF(E_LOG, L_PLAYER, "Current streaming source not at eof '%s' (id=%d, item-id=%d)\n", - cur_streaming->path, cur_streaming->id, cur_streaming->item_id); - return -1; - } - - ret = input_setup(ps); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id); - return -1; - } - - // If a streaming source exists, append the new source as play-next and set it - // as the new streaming source - if (cur_streaming) - cur_streaming->play_next = ps; - - cur_streaming = ps; - - cur_streaming->stream_start = start_pos; - cur_streaming->output_start = cur_streaming->stream_start; - cur_streaming->end = 0; - - // Seek to the given seek position - if (seek_ms) - { - DPRINTF(E_INFO, L_PLAYER, "Seek to %d ms for '%s' (id=%d, item-id=%d)\n", seek_ms, ps->path, ps->id, ps->item_id); - source_seek(seek_ms); - } - - return ret; -} - -/* - * Closes the current streaming source and sets its end-time to the given - * position - */ -static int -source_close(uint64_t end_pos) -{ - input_stop(cur_streaming); - - cur_streaming->end = end_pos; - - return 0; -} - -/* - * Updates the now playing item (cur_playing) and notifies remotes and raop devices - * about changes. Also takes care of stopping playback after the last item. - * - * @return Returns the current playback position as rtp-time - */ -static uint64_t -source_check(void) -{ - struct timespec ts; struct player_source *ps; - uint64_t pos; - int i; - int id; - int ret; + struct db_queue_item *queue_item; - ret = player_get_current_pos(&pos, &ts, 0); - if (ret < 0) + if (!current) + return NULL; + + queue_item = queue_item_next(current->item_id); + if (!queue_item) { - DPRINTF(E_LOG, L_PLAYER, "Couldn't get current playback position\n"); - - return 0; + DPRINTF(E_LOG, L_PLAYER, "Error fetching next item from queue (item-id=%d, repeat=%d)\n", current->item_id, repeat); + return NULL; } - if (player_state == PLAY_STOPPED) + ps = source_new(queue_item); + + free_queue_item(queue_item, 0); + + return ps; +} + +static void +source_next(void) +{ + if (!pb_session.reading_next) + return; + + DPRINTF(E_DBG, L_PLAYER, "Opening next track: '%s' (id=%d)\n", pb_session.reading_next->path, pb_session.reading_next->item_id); + + input_start(pb_session.reading_next->item_id); +} + +static int +source_start(void) +{ + if (!pb_session.reading_next) + return 0; + + DPRINTF(E_DBG, L_PLAYER, "(Re)opening track: '%s' (id=%d, seek=%d)\n", pb_session.reading_next->path, pb_session.reading_next->item_id, pb_session.reading_next->seek_ms); + + return input_seek(pb_session.reading_next->item_id, (int)pb_session.reading_next->seek_ms); +} + + +/* ------------------------ Playback session upkeep ------------------------- */ + +// The below update the playback session so it is always in mint condition. That +// is all they do, they should not do anything else. If you are looking for a +// place to add some non session actions, look further down at the events. + +static int +source_print(char *line, size_t linesize, struct player_source *ps, const char *name) +{ + int pos = 0; + + if (ps) { - DPRINTF(E_LOG, L_PLAYER, "Bug! source_check called but playback has already stopped\n"); - - return pos; - } - - // If cur_playing is NULL, we are still in the first two seconds after starting the stream - if (!cur_playing) - { - if (pos >= cur_streaming->output_start) - { - cur_playing = cur_streaming; - status_update(PLAY_PLAYING); - - // Start of streaming, no metadata to prune yet - } - - return pos; - } - - // Check if we are still in the middle of the current playing song - if ((cur_playing->end == 0) || (pos < cur_playing->end)) - return pos; - - // We have reached the end of the current playing song, update cur_playing to - // the next song in the queue and initialize stream_start and output_start values. - - i = 0; - while (cur_playing && (cur_playing->end != 0) && (pos > cur_playing->end)) - { - i++; - - id = (int)cur_playing->id; - - if (id != DB_MEDIA_FILE_NON_PERSISTENT_ID) - { - worker_execute(playcount_inc_cb, &id, sizeof(int), 5); -#ifdef LASTFM - worker_execute(scrobble_cb, &id, sizeof(int), 8); -#endif - history_add(cur_playing->id, cur_playing->item_id); - } - - if (consume) - db_queue_delete_byitemid(cur_playing->item_id); - - if (!cur_playing->play_next) - { - playback_abort(); - return pos; - } - - ps = cur_playing; - cur_playing = cur_playing->play_next; - - source_free(ps); - } - - if (i > 0) - { - DPRINTF(E_DBG, L_PLAYER, "Playback switched to next song\n"); - - status_update(PLAY_PLAYING); - - outputs_metadata_prune(pos); + pos += snprintf(line + pos, linesize - pos, "%s.path=%s; ", name, ps->path); + pos += snprintf(line + pos, linesize - pos, "%s.quality=%d; ", name, ps->quality.sample_rate); + pos += snprintf(line + pos, linesize - pos, "%s.item_id=%u; ", name, ps->item_id); + pos += snprintf(line + pos, linesize - pos, "%s.read_start=%lu; ", name, ps->read_start); + pos += snprintf(line + pos, linesize - pos, "%s.play_start=%lu; ", name, ps->play_start); + pos += snprintf(line + pos, linesize - pos, "%s.read_end=%lu; ", name, ps->read_end); + pos += snprintf(line + pos, linesize - pos, "%s.play_end=%lu; ", name, ps->play_end); + pos += snprintf(line + pos, linesize - pos, "%s.pos_ms=%d; ", name, ps->pos_ms); + pos += snprintf(line + pos, linesize - pos, "%s.seek_ms=%d; ", name, ps->seek_ms); } + else + pos += snprintf(line + pos, linesize - pos, "%s=(null); ", name); return pos; } -/* - * Returns the next player source based on the current streaming source and repeat mode - * - * If repeat mode is repeat all, shuffle is active and the current streaming source is the - * last item in the queue, the queue is reshuffled prior to returning the first item of the - * queue. - */ -static struct player_source * -source_next() +static void +session_dump(void) { - struct player_source *ps = NULL; - struct db_queue_item *queue_item; + char line[4096]; + int pos = 0; - if (!cur_streaming) - { - DPRINTF(E_LOG, L_PLAYER, "source_next() called with no current streaming source available\n"); - return NULL; - } + pos += snprintf(line + pos, sizeof(line) - pos, "pos=%d; ", pb_session.pos); + pos += source_print(line + pos, sizeof(line) - pos, pb_session.reading_now, "reading_now"); - if (repeat == REPEAT_SONG) - { - queue_item = db_queue_fetch_byitemid(cur_streaming->item_id); - if (!queue_item) - { - DPRINTF(E_LOG, L_PLAYER, "Error fetching item from queue '%s' (id=%d, item-id=%d)\n", cur_streaming->path, cur_streaming->id, cur_streaming->item_id); - return NULL; - } - } - else - { - queue_item = db_queue_fetch_next(cur_streaming->item_id, shuffle); - if (!queue_item && repeat == REPEAT_ALL) - { - if (shuffle) - { - db_queue_reshuffle(0); - } + DPRINTF(E_DBG, L_PLAYER, "%s\n", line); - queue_item = db_queue_fetch_bypos(0, shuffle); - if (!queue_item) - { - DPRINTF(E_LOG, L_PLAYER, "Error fetching item from queue '%s' (id=%d, item-id=%d)\n", cur_streaming->path, cur_streaming->id, cur_streaming->item_id); - return NULL; - } - } - } + pos = 0; + pos += snprintf(line + pos, sizeof(line) - pos, "pos=%d; ", pb_session.pos); + pos += source_print(line + pos, sizeof(line) - pos, pb_session.playing_now, "playing_now"); - if (!queue_item) - { - DPRINTF(E_DBG, L_PLAYER, "Reached end of queue\n"); - return NULL; - } + DPRINTF(E_DBG, L_PLAYER, "%s\n", line); - ps = source_new(queue_item); - free_queue_item(queue_item, 0); - return ps; + pos = 0; + pos += snprintf(line + pos, sizeof(line) - pos, "pos=%d; ", pb_session.pos); + pos += source_print(line + pos, sizeof(line) - pos, pb_session.reading_prev, "reading_prev"); + + DPRINTF(E_DBG, L_PLAYER, "%s\n", line); + + pos = 0; + pos += snprintf(line + pos, sizeof(line) - pos, "pos=%d; ", pb_session.pos); + pos += source_print(line + pos, sizeof(line) - pos, pb_session.reading_next, "reading_next"); + + DPRINTF(E_DBG, L_PLAYER, "%s\n", line); } -/* - * Returns the previous player source based on the current streaming source - */ -static struct player_source * -source_prev() +static void +session_update_play_eof(void) { - struct player_source *ps = NULL; - struct db_queue_item *queue_item; - - if (!cur_streaming) - { - DPRINTF(E_LOG, L_PLAYER, "source_prev() called with no current streaming source available\n"); - return NULL; - } - - queue_item = db_queue_fetch_prev(cur_streaming->item_id, shuffle); - if (!queue_item) - return NULL; - - ps = source_new(queue_item); - free_queue_item(queue_item, 0); - - return ps; + pb_session.playing_now = pb_session.reading_now; } -static int -source_switch(int nbytes) +static void +session_update_play_start(void) +{ + pb_session.playing_now = pb_session.reading_now; +} + +static void +session_update_read_next(void) { struct player_source *ps; - int ret; - DPRINTF(E_DBG, L_PLAYER, "Switching track\n"); - - source_close(TEMP_NEXT_RTPTIME + BTOS(nbytes, pb_session.quality.bits_per_sample, 2) - 1); - - while ((ps = source_next())) - { - ret = source_open(ps, cur_streaming->end + 1, 0); - if (ret < 0) - { - db_queue_delete_byitemid(ps->item_id); - continue; - } - - ret = source_play(); - if (ret < 0) - { - db_queue_delete_byitemid(ps->item_id); - source_close(TEMP_NEXT_RTPTIME + BTOS(nbytes, pb_session.quality.bits_per_sample, 2) - 1); - continue; - } - - break; - } - - if (!ps) // End of queue - { - cur_streaming = NULL; - return 0; - } - - metadata_trigger(0); - - return 0; + ps = source_next_create(pb_session.reading_next); + source_free(&pb_session.reading_next); + pb_session.reading_next = ps; } static void -session_reset(struct player_session *session, struct media_quality *quality) +session_update_read_eof(void) { - session->samples_written = 0; - session->quality = *quality; - session->samples_per_read = (quality->sample_rate / 1000) * (player_tick_interval.tv_nsec / 1000000); - session->bufsize = STOB(session->samples_per_read, quality->bits_per_sample, quality->channels); + pb_session.reading_now->read_end = pb_session.pos - 1; + pb_session.reading_now->play_end = pb_session.pos - 1 + pb_session.reading_now->output_buffer_samples; + + source_free(&pb_session.reading_prev); + pb_session.reading_prev = pb_session.reading_now; + pb_session.reading_now = pb_session.reading_next; + pb_session.reading_next = NULL; + + + // There is nothing else to play + if (!pb_session.reading_now) + return; + + pb_session.reading_now->read_start = pb_session.pos; +} + +static void +session_update_read_start(uint32_t seek_ms) +{ + source_free(&pb_session.reading_prev); + pb_session.reading_prev = pb_session.reading_now; + pb_session.reading_now = pb_session.reading_next; + pb_session.reading_next = NULL; + + // There is nothing else to play + if (!pb_session.reading_now) + return; + + pb_session.reading_now->seek_ms = seek_ms; + pb_session.reading_now->read_start = pb_session.pos; + + pb_session.playing_now = pb_session.reading_now; +} + +static inline void +session_update_read(int nsamples) +{ + // Did we just complete our first read? Then set the start timestamp + if (pb_session.pos == 0) + { + clock_gettime_with_res(CLOCK_MONOTONIC, &pb_session.start_ts, &player_timer_res); + pb_session.pts = pb_session.start_ts; + } + + // Advance position + pb_session.pos += nsamples; + + // After we have started playing we also must calculate new pos_ms + if (pb_session.playing_now->quality.sample_rate && pb_session.pos > pb_session.playing_now->play_start) + pb_session.playing_now->pos_ms = pb_session.playing_now->seek_ms + 1000UL * (pb_session.pos - pb_session.playing_now->play_start) / pb_session.playing_now->quality.sample_rate; +} + +static void +session_update_read_quality(struct media_quality *quality) +{ + int samples_per_read; + + if (quality_is_equal(quality, &pb_session.reading_now->quality)) + return; + + pb_session.reading_now->quality = *quality; + samples_per_read = ((uint64_t)quality->sample_rate * (player_tick_interval.tv_nsec / 1000000)) / 1000; + pb_session.reading_now->output_buffer_samples = OUTPUTS_BUFFER_DURATION * quality->sample_rate; + + pb_session.bufsize = STOB(samples_per_read, quality->bits_per_sample, quality->channels); DPRINTF(E_DBG, L_PLAYER, "New session values (q=%d/%d/%d, spr=%d, bufsize=%zu)\n", - quality->sample_rate, quality->bits_per_sample, quality->channels, - session->samples_per_read, session->bufsize); + quality->sample_rate, quality->bits_per_sample, quality->channels, samples_per_read, pb_session.bufsize); - if (session->buffer) - session->buffer = realloc(session->buffer, session->bufsize); + if (pb_session.buffer) + pb_session.buffer = realloc(pb_session.buffer, pb_session.bufsize); else - session->buffer = malloc(session->bufsize); + pb_session.buffer = malloc(pb_session.bufsize); - CHECK_NULL(L_PLAYER, session->buffer); + CHECK_NULL(L_PLAYER, pb_session.buffer); - clock_gettime_with_res(CLOCK_MONOTONIC, &session->start_ts, &player_timer_res); - session->pts.tv_sec = session->start_ts.tv_sec + OUTPUTS_BUFFER_DURATION; - session->pts.tv_nsec = session->start_ts.tv_nsec; + pb_session.reading_now->play_start = pb_session.reading_now->play_start + pb_session.reading_now->output_buffer_samples; } static void -session_clear(struct player_session *session) +session_stop(void) { - free(session->buffer); - memset(session, 0, sizeof(struct player_session)); + free(pb_session.buffer); + pb_session.buffer = NULL; + + source_free(&pb_session.reading_prev); + source_free(&pb_session.reading_now); + source_free(&pb_session.reading_next); + + memset(&pb_session, 0, sizeof(struct player_session)); } -/* ----------------- Main read, write and playback timer event -------------- */ - -// Returns -1 on error (caller should abort playback), or bytes read (possibly 0) -static int -source_read(uint8_t *buf, int len) +static void +session_start(struct player_source *ps, uint32_t seek_ms) { - struct media_quality quality; - int nbytes; - uint32_t item_id; - int ret; - short flags; + session_stop(); - // Nothing to read, stream silence until source_check() stops playback - if (!cur_streaming) + // Add the item to play as reading_next + pb_session.reading_next = ps; + pb_session.reading_next->seek_ms = seek_ms; +} + + +/* ------------------------- Playback event handlers ------------------------ */ + +static void +event_read_quality() +{ + DPRINTF(E_DBG, L_PLAYER, "event_read_quality()\n"); + + struct media_quality quality; + + input_quality_get(&quality); + + session_update_read_quality(&quality); +} + +// Stuff to do when read of current track ends +static void +event_read_eof() +{ + DPRINTF(E_DBG, L_PLAYER, "event_read_eof()\n"); + + session_update_read_eof(); +} + +static void +event_read_error() +{ + DPRINTF(E_DBG, L_PLAYER, "event_read_error()\n"); + + db_queue_delete_byitemid(pb_session.reading_now->item_id); + + event_read_eof(); +} + +// Kicks of input reading of next source (async), session is not affected by +// this, so there is no session update +static void +event_read_start_next() +{ + DPRINTF(E_DBG, L_PLAYER, "event_start_next()\n"); + + source_next(); +} + +static void +event_metadata_new() +{ + DPRINTF(E_DBG, L_PLAYER, "event_metadata_new()\n"); + + metadata_trigger(0); +} + +static void +event_play_end() +{ + DPRINTF(E_DBG, L_PLAYER, "event_play_end()\n"); + + playback_abort(); +} + +// Stuff to do when playback of current track ends +static void +event_play_eof() +{ + DPRINTF(E_DBG, L_PLAYER, "event_play_eof()\n"); + + int id = (int)pb_session.playing_now->id; + + if (id != DB_MEDIA_FILE_NON_PERSISTENT_ID) { - memset(buf, 0, len); - return len; + worker_execute(playcount_inc_cb, &id, sizeof(int), 5); +#ifdef LASTFM + worker_execute(scrobble_cb, &id, sizeof(int), 8); +#endif + history_add(pb_session.playing_now->id, pb_session.playing_now->item_id); } - nbytes = input_read(buf, len, &flags); - if ((nbytes < 0) || (flags & INPUT_FLAG_ERROR)) - { - DPRINTF(E_LOG, L_PLAYER, "Error reading source %d\n", cur_streaming->id); + if (consume) + db_queue_delete_byitemid(pb_session.playing_now->item_id); - nbytes = 0; - item_id = cur_streaming->item_id; - ret = source_switch(0); - db_queue_delete_byitemid(item_id); - if (ret < 0) - return -1; + outputs_metadata_prune(pb_session.pos); + + session_update_play_eof(); +} + +static void +event_play_start() +{ + DPRINTF(E_DBG, L_PLAYER, "event_play_start()\n"); + + event_metadata_new(); + + status_update(PLAY_PLAYING); + + session_update_play_start(); +} + +// Checks if the new playback position requires change of play status, plus +// calls session_update_read that updates playback position +static inline void +event_read(int nsamples) +{ + if (!pb_session.playing_now) // Shouldn't happen + return; + + if (pb_session.playing_now->play_end != 0 && pb_session.pos + nsamples >= pb_session.playing_now->play_end) + { + event_play_eof(); + + if (!pb_session.playing_now) + { + event_play_end(); + return; + } + } + + if (pb_session.playing_now->pos_ms == 0 && pb_session.pos + nsamples >= pb_session.playing_now->play_start) + event_play_start(); + + session_update_read(nsamples); +} + + +/* ---- Main playback stuff: Start, read, write and playback timer event ---- */ + +// Returns -1 on error or bytes read (possibly 0) +static inline int +source_read(int *nbytes, int *nsamples, struct media_quality *quality, uint8_t *buf, int len) +{ + short flags; + + *quality = pb_session.reading_now->quality; + *nsamples = 0; + *nbytes = input_read(buf, len, &flags); + if ((*nbytes < 0) || (flags & INPUT_FLAG_ERROR)) + { + DPRINTF(E_LOG, L_PLAYER, "Error reading source '%s' (id=%d)\n", pb_session.reading_now->path, pb_session.reading_now->id); + event_read_error(); + return -1; + } + else if (flags & INPUT_FLAG_START_NEXT) + { + event_read_start_next(); } else if (flags & INPUT_FLAG_EOF) { - ret = source_switch(nbytes); - if (ret < 0) - return -1; + event_read_eof(); } else if (flags & INPUT_FLAG_METADATA) { - metadata_trigger(0); + event_metadata_new(); } else if (flags & INPUT_FLAG_QUALITY) { - input_quality_get(&quality); - - if (!quality_is_equal(&quality, &pb_session.quality)) - session_reset(&pb_session, &quality); + event_read_quality(); } - // We pad the output buffer with silence if we don't have enough data for a - // full packet and there is no more data coming up (no more tracks in queue) - if ((nbytes < len) && (!cur_streaming)) - { - memset(buf + nbytes, 0, len - nbytes); - nbytes = len; - } + if (*nbytes == 0 || quality->channels == 0) + return 0; - return nbytes; + *nsamples = BTOS(*nbytes, quality->bits_per_sample, quality->channels); + + event_read(*nsamples); + + return 0; } static void playback_cb(int fd, short what, void *arg) { struct timespec ts; + struct media_quality quality; uint64_t overrun; - int got; + int nbytes; int nsamples; int i; int ret; @@ -1147,40 +1110,35 @@ playback_cb(int fd, short what, void *arg) pb_write_recovery = false; } +// debug_counter++; +// if (debug_counter % 100 == 0) +// session_dump(); + // If there was an overrun, we will try to read/write a corresponding number // of times so we catch up. The read from the input is non-blocking, so it // should not bring us further behind, even if there is no data. - for (i = 1 + overrun + pb_read_deficit; i > 0; i--) + for (i = 1 + overrun + pb_read_deficit; i > 0 && pb_session.reading_now; i--) { - source_check(); - - // Make sure playback is still running after source_check() - if (player_state != PLAY_PLAYING) - return; - - got = source_read(pb_session.buffer, pb_session.bufsize); - if (got < 0) + ret = source_read(&nbytes, &nsamples, &quality, pb_session.buffer, pb_session.bufsize); + if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Error reading from source, aborting playback\n"); - playback_abort(); + DPRINTF(E_LOG, L_PLAYER, "Error reading from source\n"); break; } - else if (got == 0) + if (nbytes == 0) { pb_read_deficit++; break; } - nsamples = BTOS(got, pb_session.quality.bits_per_sample, pb_session.quality.channels); - outputs_write(pb_session.buffer, pb_session.bufsize, &pb_session.quality, nsamples, &pb_session.pts); - pb_session.samples_written += nsamples; + outputs_write(pb_session.buffer, pb_session.bufsize, &quality, nsamples, &pb_session.pts); - if (got < pb_session.bufsize) + if (nbytes < pb_session.bufsize) { - DPRINTF(E_DBG, L_PLAYER, "Incomplete read, wanted %zu, got %d\n", pb_session.bufsize, got); + DPRINTF(E_DBG, L_PLAYER, "Incomplete read, wanted %zu, got %d\n", pb_session.bufsize, nbytes); // How much the number of samples we got corresponds to in time (nanoseconds) ts.tv_sec = 0; - ts.tv_nsec = 1000000000L * nsamples / pb_session.quality.sample_rate; + ts.tv_nsec = 1000000000L * nsamples / quality.sample_rate; pb_session.pts = timespec_add(pb_session.pts, ts); pb_read_deficit++; } @@ -1608,32 +1566,57 @@ playback_timer_stop(void) return 0; } +// Initiates the session and starts the input source static int -pb_session_stop(void) +playback_session_start(struct db_queue_item *queue_item, uint32_t seek_ms) { + struct player_source *ps; int ret; - ret = playback_timer_stop(); + ps = source_new(queue_item); - session_clear(&pb_session); + // Clears the session and attaches the new source as reading_next + session_start(ps, seek_ms); + + // Sets of opening of the new source + while ( (ret = source_start()) < 0) + { + // Couldn't start requested item, remove it from queue and try next in line + db_queue_delete_byitemid(pb_session.reading_next->item_id); + session_update_read_next(); + } + + session_update_read_start((uint32_t)ret); + + if (!pb_session.playing_now) + return -1; return ret; } +// Stops input source and deallocates pb_session content +static void +playback_session_stop(void) +{ + source_stop(); + + session_stop(); + + playback_timer_stop(); + + status_update(PLAY_STOPPED); +} + static void playback_abort(void) { outputs_playback_stop(); - pb_session_stop(); - - source_stop(); + playback_session_stop(); if (!clear_queue_on_stop_disabled) db_queue_clear(0); - status_update(PLAY_STOPPED); - outputs_metadata_purge(); } @@ -1644,7 +1627,7 @@ playback_suspend(void) { player_flush_pending = outputs_flush(device_command_cb); - pb_session_stop(); + playback_timer_stop(); status_update(PLAY_PAUSED); @@ -1662,10 +1645,6 @@ static enum command_state get_status(void *arg, int *retval) { struct player_status *status = arg; - struct timespec ts; - struct player_source *ps; - uint64_t pos; - int ret; memset(status, 0, sizeof(struct player_status)); @@ -1682,59 +1661,40 @@ get_status(void *arg, int *retval) case PLAY_STOPPED: DPRINTF(E_DBG, L_PLAYER, "Player status: stopped\n"); - status->status = PLAY_STOPPED; + status->status = PLAY_STOPPED; break; case PLAY_PAUSED: DPRINTF(E_DBG, L_PLAYER, "Player status: paused\n"); - status->status = PLAY_PAUSED; - status->id = cur_streaming->id; - status->item_id = cur_streaming->item_id; + status->status = PLAY_PAUSED; + status->id = pb_session.playing_now->id; + status->item_id = pb_session.playing_now->item_id; - pos = TEMP_NEXT_RTPTIME - cur_streaming->stream_start; - status->pos_ms = (pos * 1000) / 44100; - status->len_ms = cur_streaming->len_ms; + status->pos_ms = pb_session.playing_now->pos_ms; + status->len_ms = pb_session.playing_now->len_ms; break; case PLAY_PLAYING: - if (!cur_playing) + if (pb_session.playing_now->pos_ms == 0) { DPRINTF(E_DBG, L_PLAYER, "Player status: playing (buffering)\n"); status->status = PLAY_PAUSED; - ps = cur_streaming; - - // Avoid a visible 2-second jump backward for the client - pos = ps->output_start - ps->stream_start; } else { DPRINTF(E_DBG, L_PLAYER, "Player status: playing\n"); status->status = PLAY_PLAYING; - ps = cur_playing; - - ret = player_get_current_pos(&pos, &ts, 0); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not get current stream position for playstatus\n"); - - pos = 0; - } - - if (pos < ps->stream_start) - pos = 0; - else - pos -= ps->stream_start; } - status->pos_ms = (pos * 1000) / 44100; - status->len_ms = ps->len_ms; + status->id = pb_session.playing_now->id; + status->item_id = pb_session.playing_now->item_id; - status->id = ps->id; - status->item_id = ps->item_id; + status->pos_ms = pb_session.playing_now->pos_ms; + status->len_ms = pb_session.playing_now->len_ms; break; } @@ -1744,21 +1704,18 @@ get_status(void *arg, int *retval) } static enum command_state -now_playing(void *arg, int *retval) +playing_now(void *arg, int *retval) { uint32_t *id = arg; - struct player_source *ps_playing; - ps_playing = source_now_playing(); - - if (ps_playing) - *id = ps_playing->id; - else + if (player_state == PLAY_STOPPED) { *retval = -1; return COMMAND_END; } + *id = pb_session.playing_now->id; + *retval = 0; return COMMAND_END; } @@ -1766,27 +1723,20 @@ now_playing(void *arg, int *retval) static enum command_state playback_stop(void *arg, int *retval) { - struct player_source *ps_playing; - if (player_state == PLAY_STOPPED) { *retval = 0; return COMMAND_END; } + if (pb_session.playing_now && pb_session.playing_now->pos_ms > 0) + history_add(pb_session.playing_now->id, pb_session.playing_now->item_id); + // We may be restarting very soon, so we don't bring the devices to a full // stop just yet; this saves time when restarting, which is nicer for the user *retval = outputs_flush(device_command_cb); - pb_session_stop(); - - ps_playing = source_now_playing(); - if (ps_playing) - { - history_add(ps_playing->id, ps_playing->item_id); - } - - source_stop(); + playback_session_stop(); status_update(PLAY_STOPPED); @@ -1804,29 +1754,19 @@ playback_start_bh(void *arg, int *retval) { int ret; - // initialize the packet timer to the same relative time that we have - // for the playback timer. -// packet_timer_last.tv_sec = pb_pos_stamp.tv_sec; -// packet_timer_last.tv_nsec = pb_pos_stamp.tv_nsec; - -// pb_timer_last.tv_sec = pb_pos_stamp.tv_sec; -// pb_timer_last.tv_nsec = pb_pos_stamp.tv_nsec; - -// pb_buffer_offset = 0; pb_read_deficit = 0; ret = playback_timer_start(); if (ret < 0) - goto out_fail; + goto error; status_update(PLAY_PLAYING); *retval = 0; return COMMAND_END; - out_fail: + error: playback_abort(); - *retval = -1; return COMMAND_END; } @@ -1838,7 +1778,7 @@ playback_start_item(void *arg, int *retval) struct media_file_info *mfi; struct output_device *device; struct player_source *ps; - int seek_ms; + uint32_t seek_ms; int ret; if (player_state == PLAY_PLAYING) @@ -1851,9 +1791,6 @@ playback_start_item(void *arg, int *retval) return COMMAND_END; } - // Update global playback position - pb_pos = TEMP_NEXT_RTPTIME - 88200; - if (player_state == PLAY_STOPPED && !queue_item) { DPRINTF(E_LOG, L_PLAYER, "Failed to start/resume playback, no queue item given\n"); @@ -1864,24 +1801,22 @@ playback_start_item(void *arg, int *retval) if (!queue_item) { - // Resume playback of current source - ps = source_now_playing(); + ps = pb_session.playing_now; + if (!ps) + { + DPRINTF(E_WARN, L_PLAYER, "Bug! playing_now is null but playback is not stopped!\n"); + *retval = -1; + return COMMAND_END; + } + DPRINTF(E_DBG, L_PLAYER, "Resume playback of '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id); } else { // Start playback for given queue item DPRINTF(E_DBG, L_PLAYER, "Start playback of '%s' (id=%d, item-id=%d)\n", queue_item->path, queue_item->file_id, queue_item->id); - source_stop(); - - ps = source_new(queue_item); - if (!ps) - { - playback_abort(); - *retval = -1; - return COMMAND_END; - } + // Look up where we should start seek_ms = 0; if (queue_item->file_id > 0) { @@ -1893,24 +1828,14 @@ playback_start_item(void *arg, int *retval) } } - ret = source_open(ps, TEMP_NEXT_RTPTIME, seek_ms); + ret = playback_session_start(queue_item, seek_ms); if (ret < 0) { - playback_abort(); - source_free(ps); *retval = -1; return COMMAND_END; } } - ret = source_play(); - if (ret < 0) - { - playback_abort(); - *retval = -1; - return COMMAND_END; - } - metadata_trigger(1); // Start sessions on selected devices @@ -2018,70 +1943,38 @@ playback_start(void *arg, int *retval) static enum command_state playback_prev_bh(void *arg, int *retval) { + struct db_queue_item *queue_item; int ret; - int pos_sec; - struct player_source *ps; - // The upper half is playback_pause, therefor the current playing item is - // already set as the cur_streaming (cur_playing is NULL). - if (!cur_streaming) + // outputs_flush() in playback_pause() may have a caused a failure callback + // from the output, which in streaming_cb() can cause playback_abort() + if (player_state == PLAY_STOPPED) { - DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n"); - *retval = -1; - return COMMAND_END; + goto error; } // Only add to history if playback started - if (cur_streaming->output_start > cur_streaming->stream_start) - history_add(cur_streaming->id, cur_streaming->item_id); - - // Compute the playing time in seconds for the current song - if (cur_streaming->output_start > cur_streaming->stream_start) - pos_sec = (cur_streaming->output_start - cur_streaming->stream_start) / 44100; - else - pos_sec = 0; + if (pb_session.playing_now->pos_ms > 0) + history_add(pb_session.playing_now->id, pb_session.playing_now->item_id); // Only skip to the previous song if the playing time is less than 3 seconds, // otherwise restart the current song. - DPRINTF(E_DBG, L_PLAYER, "Skipping song played %d sec\n", pos_sec); - if (pos_sec < 3) - { - ps = source_prev(); - if (!ps) - { - playback_abort(); - *retval = -1; - return COMMAND_END; - } - - source_stop(); - - ret = source_open(ps, TEMP_NEXT_RTPTIME, 0); - if (ret < 0) - { - source_free(ps); - playback_abort(); - - *retval = -1; - return COMMAND_END; - } - } + if (pb_session.playing_now->pos_ms < 3000) + queue_item = queue_item_prev(pb_session.playing_now->item_id); else + queue_item = db_queue_fetch_byitemid(pb_session.playing_now->item_id); + if (!queue_item) { - ret = source_seek(0); - if (ret < 0) - { - playback_abort(); - - *retval = -1; - return COMMAND_END; - } + DPRINTF(E_DBG, L_PLAYER, "Error finding previous source, queue item has disappeared\n"); + goto error; } - if (player_state == PLAY_STOPPED) + ret = playback_session_start(queue_item, 0); + free_queue_item(queue_item, 0); + if (ret < 0) { - *retval = -1; - return COMMAND_END; + DPRINTF(E_DBG, L_PLAYER, "Error skipping to previous item, aborting playback\n"); + goto error; } // Silent status change - playback_start() sends the real status update @@ -2089,90 +1982,93 @@ playback_prev_bh(void *arg, int *retval) *retval = 0; return COMMAND_END; + + error: + playback_abort(); + *retval = -1; + return COMMAND_END; } static enum command_state playback_next_bh(void *arg, int *retval) { - struct player_source *ps; + struct db_queue_item *queue_item; int ret; int id; - uint32_t item_id; - // The upper half is playback_pause, therefor the current playing item is - // already set as the cur_streaming (cur_playing is NULL). - if (!cur_streaming) + // outputs_flush() in playback_pause() may have a caused a failure callback + // from the output, which in streaming_cb() can cause playback_abort() + if (player_state == PLAY_STOPPED) { - DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n"); - *retval = -1; - return COMMAND_END; + goto error; } - item_id = cur_streaming->item_id; - // Only add to history if playback started - if (cur_streaming->output_start > cur_streaming->stream_start) + if (pb_session.playing_now->pos_ms > 0) { - history_add(cur_streaming->id, item_id); + history_add(pb_session.playing_now->id, pb_session.playing_now->item_id); - id = (int)cur_streaming->id; + id = (int)(pb_session.playing_now->id); worker_execute(skipcount_inc_cb, &id, sizeof(int), 5); } - ps = source_next(); - if (!ps) + if (consume) + db_queue_delete_byitemid(pb_session.playing_now->item_id); + + queue_item = queue_item_next(pb_session.playing_now->item_id); + if (!queue_item) { - playback_abort(); - *retval = -1; - return COMMAND_END; + DPRINTF(E_DBG, L_PLAYER, "Error finding next source, queue item has disappeared\n"); + goto error; } - source_stop(); - - ret = source_open(ps, TEMP_NEXT_RTPTIME, 0); + ret = playback_session_start(queue_item, 0); + free_queue_item(queue_item, 0); if (ret < 0) { - source_free(ps); - playback_abort(); - *retval = -1; - return COMMAND_END; + DPRINTF(E_DBG, L_PLAYER, "Error skipping to next item, aborting playback\n"); + goto error; } - if (player_state == PLAY_STOPPED) - { - *retval = -1; - return COMMAND_END; - } - - if (consume) - db_queue_delete_byitemid(item_id); - // Silent status change - playback_start() sends the real status update player_state = PLAY_PAUSED; *retval = 0; return COMMAND_END; + + error: + playback_abort(); + *retval = -1; + return COMMAND_END; } static enum command_state playback_seek_bh(void *arg, int *retval) { + struct db_queue_item *queue_item; union player_arg *cmdarg = arg; - int ms; int ret; - *retval = -1; + // outputs_flush() in playback_pause() may have a caused a failure callback + // from the output, which in streaming_cb() can cause playback_abort() + if (player_state == PLAY_STOPPED) + { + goto error; + } - if (!cur_streaming) - return COMMAND_END; + queue_item = db_queue_fetch_byitemid(pb_session.playing_now->item_id); + if (!queue_item) + { + DPRINTF(E_DBG, L_PLAYER, "Error seeking in source, queue item has disappeared\n"); + goto error; + } - ms = cmdarg->intval; - - ret = source_seek(ms); + ret = playback_session_start(queue_item, cmdarg->intval); + free_queue_item(queue_item, 0); if (ret < 0) { - playback_abort(); - return COMMAND_END; + DPRINTF(E_DBG, L_PLAYER, "Error seeking to %d, aborting playback\n", cmdarg->intval); + goto error; } // Silent status change - playback_start() sends the real status update @@ -2180,39 +2076,57 @@ playback_seek_bh(void *arg, int *retval) *retval = 0; return COMMAND_END; + + error: + playback_abort(); + *retval = -1; + return COMMAND_END; } static enum command_state playback_pause_bh(void *arg, int *retval) { - *retval = -1; + struct db_queue_item *queue_item; + int ret; // outputs_flush() in playback_pause() may have a caused a failure callback - // from the output, which in streaming_cb() can cause playback_abort() -> - // cur_streaming is NULL - if (!cur_streaming) - return COMMAND_END; - - if (cur_streaming->data_kind == DATA_KIND_HTTP || cur_streaming->data_kind == DATA_KIND_PIPE) + // from the output, which in streaming_cb() can cause playback_abort() + if (player_state == PLAY_STOPPED) { - DPRINTF(E_DBG, L_PLAYER, "Source is not pausable, abort playback\n"); - - playback_abort(); - return COMMAND_END; + goto error; } + + queue_item = db_queue_fetch_byitemid(pb_session.playing_now->item_id); + if (!queue_item) + { + DPRINTF(E_DBG, L_PLAYER, "Error pausing source, queue item has disappeared\n"); + goto error; + } + + ret = playback_session_start(queue_item, pb_session.playing_now->pos_ms); + free_queue_item(queue_item, 0); + if (ret < 0) + { + DPRINTF(E_DBG, L_PLAYER, "Error pausing source, aborting playback\n"); + goto error; + } + status_update(PLAY_PAUSED); seek_save(); *retval = 0; return COMMAND_END; + + error: + playback_abort(); + *retval = -1; + return COMMAND_END; } static enum command_state playback_pause(void *arg, int *retval) { - uint64_t pos; - if (player_state == PLAY_STOPPED) { *retval = -1; @@ -2225,29 +2139,10 @@ playback_pause(void *arg, int *retval) return COMMAND_END; } - pos = source_check(); - if (pos == 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not retrieve current position for pause\n"); - - playback_abort(); - *retval = -1; - return COMMAND_END; - } - - // Make sure playback is still running after source_check() - if (player_state == PLAY_STOPPED) - { - *retval = -1; - return COMMAND_END; - } + playback_timer_stop(); *retval = outputs_flush(device_command_cb); - pb_session_stop(); - - source_pause(pos); - outputs_metadata_purge(); // We're async if we need to flush devices @@ -2568,7 +2463,7 @@ volume_setrel_speaker(void *arg, int *retval) #endif if (device->session) - *retval = outputs_device_volume_set(device, device_command_cb); + *retval += outputs_device_volume_set(device, device_command_cb); break; } @@ -2624,7 +2519,7 @@ volume_setabs_speaker(void *arg, int *retval) #endif if (device->session) - *retval = outputs_device_volume_set(device, device_command_cb);//FIXME Does this need to be += ? + *retval += outputs_device_volume_set(device, device_command_cb); } } @@ -2724,7 +2619,6 @@ shuffle_set(void *arg, int *retval) { union player_arg *cmdarg = arg; char new_shuffle; - uint32_t cur_id; new_shuffle = (cmdarg->intval == 0) ? 0 : 1; @@ -2735,8 +2629,10 @@ shuffle_set(void *arg, int *retval) // Update queue and notify listeners if (new_shuffle) { - cur_id = cur_streaming ? cur_streaming->item_id : 0; - db_queue_reshuffle(cur_id); + if (pb_session.playing_now) + db_queue_reshuffle(pb_session.playing_now->item_id); + else + db_queue_reshuffle(0); } else { @@ -2794,50 +2690,6 @@ playerqueue_plid(void *arg, int *retval) /* ------------------------------- Player API ------------------------------- */ -// TODO no longer part of API -static int -player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit) -{ - uint64_t delta; - int ret; - - ret = clock_gettime_with_res(CLOCK_MONOTONIC, ts, &player_timer_res); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Couldn't get clock: %s\n", strerror(errno)); - - return -1; - } - - delta = (ts->tv_sec - pb_pos_stamp.tv_sec) * 1000000 + (ts->tv_nsec - pb_pos_stamp.tv_nsec) / 1000; - -#ifdef DEBUG_SYNC - DPRINTF(E_DBG, L_PLAYER, "Delta is %" PRIu64 " usec\n", delta); -#endif - - delta = (delta * 44100) / 1000000; - -#ifdef DEBUG_SYNC - DPRINTF(E_DBG, L_PLAYER, "Delta is %" PRIu64 " samples\n", delta); -#endif - - *pos = pb_pos + delta; - - if (commit) - { - pb_pos = *pos; - - pb_pos_stamp.tv_sec = ts->tv_sec; - pb_pos_stamp.tv_nsec = ts->tv_nsec; - -#ifdef DEBUG_SYNC - DPRINTF(E_DBG, L_PLAYER, "Pos: %" PRIu64 " (clock)\n", *pos); -#endif - } - - return 0; -} - int player_get_status(struct player_status *status) { @@ -2857,11 +2709,11 @@ player_get_status(struct player_status *status) * @return 0 on success, -1 on failure (e. g. no playing item found) */ int -player_now_playing(uint32_t *id) +player_playing_now(uint32_t *id) { int ret; - ret = commands_exec_sync(cmdbase, now_playing, NULL, id); + ret = commands_exec_sync(cmdbase, playing_now, NULL, id); return ret; } @@ -3418,8 +3270,6 @@ player_deinit(void) player_exit = 1; commands_base_destroy(cmdbase); - session_clear(&pb_session); - ret = pthread_join(tid_player, NULL); if (ret != 0) { diff --git a/src/player.h b/src/player.h index 525d2f03..70bb19bc 100644 --- a/src/player.h +++ b/src/player.h @@ -76,7 +76,7 @@ int player_get_status(struct player_status *status); int -player_now_playing(uint32_t *id); +player_playing_now(uint32_t *id); void player_speaker_enumerate(spk_enum_cb cb, void *arg); diff --git a/src/spotify.c b/src/spotify.c index 22f5e255..ffe94bff 100644 --- a/src/spotify.c +++ b/src/spotify.c @@ -718,7 +718,6 @@ playback_eot(void *arg, int *retval) g_state = SPOTIFY_STATE_STOPPING; - // TODO 1) This will block for a while, but perhaps ok? input_write(spotify_audio_buffer, NULL, INPUT_FLAG_EOF); *retval = 0; @@ -1042,7 +1041,7 @@ static int music_delivery(sp_session *sess, const sp_audioformat *format, // The input buffer only accepts writing when it is approaching depletion, and // because we use NONBLOCK it will just return if this is not the case. So in // most cases no actual write is made and spotify_audio_buffer will just grow. - input_write(spotify_audio_buffer, &quality, INPUT_FLAG_NONBLOCK); + input_write(spotify_audio_buffer, &quality, 0); return num_frames; }