From 661289990c9a23ecd079d2b754c290aa1d3f30a7 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 1 May 2020 20:11:45 +0200 Subject: [PATCH] [player] Migrate a lot of speaker handling to outputs.c Moves speaker selection, volume handling and startup to outputs.c, plus adds the ability to "resurrect" a speaker that disconnects. The purpose of moving the code is to concentrate device handling in one place. Also changes how we deal with speaker selection. The player will now generally not alter a user selection, even if the device fails. The purpose of this is to maintain selection both if the device briefly fails, and if the user switches off the device (we stop playback) and later turns it on + starts new playback. --- src/outputs.c | 287 +++++++++++++++---- src/outputs.h | 37 ++- src/outputs/alsa.c | 8 +- src/outputs/cast.c | 8 +- src/outputs/dummy.c | 8 +- src/outputs/fifo.c | 8 +- src/outputs/pulse.c | 8 +- src/outputs/raop.c | 12 +- src/player.c | 670 ++++++++++++-------------------------------- src/player.h | 3 + 10 files changed, 476 insertions(+), 573 deletions(-) diff --git a/src/outputs.c b/src/outputs.c index 00a8286e..f643925d 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -75,6 +75,9 @@ static struct output_definition *outputs[] = { NULL }; +// Default volume (must be from 0 - 100) +#define OUTPUTS_DEFAULT_VOLUME 50 + // When we stop, we keep the outputs open for a while, just in case we are // actually just restarting. This timeout determines how long we wait before // full stop. @@ -210,11 +213,13 @@ deferred_cb(int fd, short what, void *arg) // The device has left the building (stopped/failed), and the backend // is not using it any more - if (!device->advertised && !device->session) + if (device && !device->advertised && !device->session) { outputs_device_remove(device); device = NULL; } + else if (device) + device->state = state; DPRINTF(E_DBG, L_PLAYER, "Making deferred callback to %s, id was %d\n", player_pmap(cb), callback_id); @@ -239,9 +244,12 @@ stop_timer_cb(int fd, short what, void *arg) } static void -device_stop_cb(struct output_device *device, enum output_device_state status) +device_stop_cb(struct output_device *device, enum output_device_state state) { - if (status == OUTPUT_STATE_FAILED) + if (device) + device->state = state; + + if (state == OUTPUT_STATE_FAILED) DPRINTF(E_WARN, L_PLAYER, "Failed to stop device\n"); else DPRINTF(E_INFO, L_PLAYER, "Device stopped properly\n"); @@ -406,6 +414,16 @@ device_list_sort(void) while (swaps > 0); } +// Convenience function +static inline int +device_state_update(struct output_device *device, int ret) +{ + if (ret < 0) + device->state = OUTPUT_STATE_FAILED; + + return ret; +} + static void metadata_cb_send(int fd, short what, void *arg) { @@ -461,6 +479,70 @@ metadata_send(enum output_types type, uint32_t item_id, bool startup, output_met } +/* ----------------------------- Volume helpers ----------------------------- */ + +static int +rel_to_vol(int relvol, int master_volume) +{ + float vol; + + if (relvol == 100) + return master_volume; + + vol = ((float)relvol * (float)master_volume) / 100.0; + + return (int)vol; +} + +static int +vol_to_rel(int volume, int master_volume) +{ + float rel; + + if (volume == master_volume) + return 100; + + rel = ((float)volume / (float)master_volume) * 100.0; + + return (int)rel; +} + +static void +vol_adjust(void) +{ + struct output_device *device; + int selected_highest = -1; + int all_highest = -1; + + for (device = output_device_list; device; device = device->next) + { + if (device->selected && (device->volume > selected_highest)) + selected_highest = device->volume; + + if (device->volume > all_highest) + all_highest = device->volume; + } + + outputs_master_volume = (selected_highest >= 0) ? selected_highest : all_highest; + + for (device = output_device_list; device; device = device->next) + { + if (!device->selected && (device->volume > outputs_master_volume)) + device->volume = outputs_master_volume; + + device->relvol = vol_to_rel(device->volume, outputs_master_volume); + } + +#ifdef DEBUG_VOLUME + DPRINTF(E_DBG, L_PLAYER, "*** Master: %d\n", outputs_master_volume); + + for (device = output_device_list; device; device = device->next) + { + DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d selected %d\n", device->name, device->volume, device->relvol, device->selected); + } +#endif +} + /* ----------------------------------- API ---------------------------------- */ struct output_device * @@ -474,7 +556,7 @@ outputs_device_get(uint64_t device_id) return device; } - DPRINTF(E_LOG, L_PLAYER, "Output device with id %" PRIu64 " has disappeared from our list\n", device_id); + DPRINTF(E_WARN, L_PLAYER, "Output device with id %" PRIu64 " has disappeared from our list\n", device_id); return NULL; } @@ -613,7 +695,7 @@ outputs_listener_notify(void) /* ---------------------------- Called by player ---------------------------- */ struct output_device * -outputs_device_add(struct output_device *add, bool new_deselect, int default_volume) +outputs_device_add(struct output_device *add, bool new_deselect) { struct output_device *device; char *keep_name; @@ -637,7 +719,7 @@ outputs_device_add(struct output_device *add, bool new_deselect, int default_vol if (ret < 0) { device->selected = 0; - device->volume = default_volume; + device->volume = (outputs_master_volume >= 0) ? outputs_master_volume : OUTPUTS_DEFAULT_VOLUME;; } free(device->name); @@ -686,9 +768,11 @@ outputs_device_add(struct output_device *add, bool new_deselect, int default_vol device_list_sort(); + vol_adjust(); + device->advertised = 1; - listener_notify(LISTENER_SPEAKER); + listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME); return device; } @@ -732,37 +816,62 @@ outputs_device_remove(struct output_device *remove) outputs_device_free(remove); - listener_notify(LISTENER_SPEAKER); + vol_adjust(); + + listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME); +} + +void +outputs_device_select(struct output_device *device) +{ + device->selected = 1; + device->prevent_playback = 0; + device->busy = 0; + + vol_adjust(); +} + +void +outputs_device_deselect(struct output_device *device) +{ + device->selected = 0; + + vol_adjust(); } int -outputs_device_start(struct output_device *device, output_status_cb cb) +outputs_device_start(struct output_device *device, output_status_cb cb, bool only_probe) { - if (outputs[device->type]->disabled || !outputs[device->type]->device_start) + int ret; + + if (outputs[device->type]->disabled || !outputs[device->type]->device_start || !outputs[device->type]->device_probe) return -1; if (device->session) - { - DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_start() called for a device that already has a session\n"); - return -1; - } + return 0; // Device is already running, nothing to do - return outputs[device->type]->device_start(device, callback_add(device, cb)); + if (only_probe) + ret = outputs[device->type]->device_probe(device, callback_add(device, cb)); + else + ret = outputs[device->type]->device_start(device, callback_add(device, cb)); + + return device_state_update(device, ret);; } int outputs_device_stop(struct output_device *device, output_status_cb cb) { + int ret; + if (outputs[device->type]->disabled || !outputs[device->type]->device_stop) return -1; if (!device->session) - { - DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_stop() called for a device that has no session\n"); - return -1; - } + return 0; // Device is already stopped, nothing to do - return outputs[device->type]->device_stop(device, callback_add(device, cb)); + ret = outputs[device->type]->device_stop(device, callback_add(device, cb)); + + return device_state_update(device, ret); } int @@ -772,52 +881,58 @@ outputs_device_stop_delayed(struct output_device *device, output_status_cb cb) return -1; if (!device->session) - { - DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_stop_delayed() called for a device that has no session\n"); - return -1; - } + return 0; // Device is already stopped, nothing to do outputs[device->type]->device_cb_set(device, callback_add(device, cb)); event_add(device->stop_timer, &outputs_stop_timeout); - return 0; + return 1; } int outputs_device_flush(struct output_device *device, output_status_cb cb) { + int ret; + if (outputs[device->type]->disabled || !outputs[device->type]->device_flush) return -1; if (!device->session) - return -1; + return 0; // Nothing to flush - return outputs[device->type]->device_flush(device, callback_add(device, cb)); + ret = outputs[device->type]->device_flush(device, callback_add(device, cb)); + + return device_state_update(device, ret); } -int -outputs_device_probe(struct output_device *device, output_status_cb cb) +void +outputs_device_volume_register(struct output_device *device, int absvol, int relvol) { - if (outputs[device->type]->disabled || !outputs[device->type]->device_probe) - return -1; + if (absvol > -1) + device->volume = absvol; + else if (relvol > -1) + device->volume = rel_to_vol(relvol, outputs_master_volume); - if (device->session) - { - DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_probe() called for a device that already has a session\n"); - return -1; - } + vol_adjust(); - return outputs[device->type]->device_probe(device, callback_add(device, cb)); + listener_notify(LISTENER_VOLUME); } int outputs_device_volume_set(struct output_device *device, output_status_cb cb) { + int ret; + if (outputs[device->type]->disabled || !outputs[device->type]->device_volume_set) return -1; - return outputs[device->type]->device_volume_set(device, callback_add(device, cb)); + if (!device->session) + return 0; // Device isn't active + + ret = outputs[device->type]->device_volume_set(device, callback_add(device, cb)); + + return device_state_update(device, ret); } int @@ -832,10 +947,14 @@ outputs_device_volume_to_pct(struct output_device *device, const char *volume) int outputs_device_quality_set(struct output_device *device, struct media_quality *quality, output_status_cb cb) { + int ret; + if (outputs[device->type]->disabled || !outputs[device->type]->device_quality_set) return -1; - return outputs[device->type]->device_quality_set(device, quality, callback_add(device, cb)); + ret = outputs[device->type]->device_quality_set(device, quality, callback_add(device, cb)); + + return device_state_update(device, ret); } void @@ -876,30 +995,37 @@ outputs_device_free(struct output_device *device) free(device); } +// The return value will be the number of devices we need to wait for, either +// because they are starting or shutting down. The return value is only negative +// if we don't have to wait, i.e. all the selected devices failed immediately. int -outputs_flush(output_status_cb cb) +outputs_start(output_status_cb started_cb, output_status_cb stopped_cb, bool only_probe) { struct output_device *device; - int count = 0; - int ret; + int pending = 0; + int ret = -1; for (device = output_device_list; device; device = device->next) { - ret = outputs_device_flush(device, cb); + if (device->selected) + ret = outputs_device_start(device, started_cb, only_probe); + else + ret = outputs_device_stop(device, stopped_cb); + if (ret < 0) continue; - count++; + pending += ret; } - return count; + return (pending > 0) ? pending : ret; } int outputs_stop(output_status_cb cb) { struct output_device *device; - int count = 0; + int pending = 0; int ret; for (device = output_device_list; device; device = device->next) @@ -911,10 +1037,10 @@ outputs_stop(output_status_cb cb) if (ret < 0) continue; - count++; + pending += ret; } - return count; + return pending; } int @@ -928,6 +1054,69 @@ outputs_stop_delayed_cancel(void) return 0; } +int +outputs_flush(output_status_cb cb) +{ + struct output_device *device; + int pending = 0; + int ret; + + for (device = output_device_list; device; device = device->next) + { + ret = outputs_device_flush(device, cb); + if (ret < 0) + continue; + + pending += ret; + } + + return pending; +} + +int +outputs_volume_set(int volume, output_status_cb cb) +{ + struct output_device *device; + int pending = 0; + int ret; + + if (outputs_master_volume == volume) + return 0; + + outputs_master_volume = volume; + + for (device = output_device_list; device; device = device->next) + { + if (!device->selected) + continue; + + device->volume = rel_to_vol(device->relvol, outputs_master_volume); + + ret = outputs_device_volume_set(device, cb); + if (ret < 0) + continue; + + pending += ret; + } + + listener_notify(LISTENER_VOLUME); + + return pending; +} + +int +outputs_sessions_count(void) +{ + struct output_device *device; + int count = 0; + + for (device = output_device_list; device; device = device->next) + if (device->session) + count++; + + return count; +} + void outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts) { @@ -1004,6 +1193,8 @@ outputs_init(void) int ret; int i; + outputs_master_volume = -1; + CHECK_NULL(L_PLAYER, outputs_deferredev = evtimer_new(evbase_player, deferred_cb, NULL)); no_output = 1; diff --git a/src/outputs.h b/src/outputs.h index fef5fd0f..c5d42443 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -122,6 +122,10 @@ struct output_device // Type of output (string) const char *type_name; + // Last state that the backend returned to the handlers in outputs.c. This + // field must only be set in outputs.c (not in the backends/player). + enum output_device_state state; + // Misc device flags unsigned selected:1; unsigned advertised:1; @@ -218,6 +222,11 @@ struct output_definition // Deinitialization function called at shutdown void (*deinit)(void); + // For all the below that take callbacks, the return values are: + // - negative: error + // - zero: ok, won't make a callback + // - positive: number of callbacks that will be made + // Prepare a playback session on device and call back int (*device_start)(struct output_device *device, int callback_id); @@ -293,17 +302,26 @@ outputs_listener_notify(void); /* ---------------------------- Called by player ---------------------------- */ +int +outputs_master_volume; + // Ownership of *add is transferred, so don't address after calling. Instead you // can address the return value (which is not the same if the device was already // in the list. struct output_device * -outputs_device_add(struct output_device *add, bool new_deselect, int default_volume); +outputs_device_add(struct output_device *add, bool new_deselect); void outputs_device_remove(struct output_device *remove); +void +outputs_device_select(struct output_device *device); + +void +outputs_device_deselect(struct output_device *device); + int -outputs_device_start(struct output_device *device, output_status_cb cb); +outputs_device_start(struct output_device *device, output_status_cb cb, bool only_probe); int outputs_device_stop(struct output_device *device, output_status_cb cb); @@ -314,8 +332,8 @@ outputs_device_stop_delayed(struct output_device *device, output_status_cb cb); int outputs_device_flush(struct output_device *device, output_status_cb cb); -int -outputs_device_probe(struct output_device *device, output_status_cb cb); +void +outputs_device_volume_register(struct output_device *device, int absvol, int relvol); int outputs_device_volume_set(struct output_device *device, output_status_cb cb); @@ -333,14 +351,23 @@ void outputs_device_free(struct output_device *device); int -outputs_flush(output_status_cb cb); +outputs_start(output_status_cb started_cb, output_status_cb stopped_cb, bool only_probe); int outputs_stop(output_status_cb cb); +int +outputs_flush(output_status_cb cb); + +int +outputs_volume_set(int volume, output_status_cb cb); + int outputs_stop_delayed_cancel(void); +int +outputs_sessions_count(void); + void outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts); diff --git a/src/outputs/alsa.c b/src/outputs/alsa.c index 5af554ae..e7044821 100644 --- a/src/outputs/alsa.c +++ b/src/outputs/alsa.c @@ -1143,7 +1143,7 @@ alsa_device_start(struct output_device *device, int callback_id) as->state = OUTPUT_STATE_CONNECTED; alsa_status(as); - return 0; + return 1; } static int @@ -1155,7 +1155,7 @@ alsa_device_stop(struct output_device *device, int callback_id) as->state = OUTPUT_STATE_STOPPED; alsa_status(as); // Will terminate the session since the state is STOPPED - return 0; + return 1; } static int @@ -1169,7 +1169,7 @@ alsa_device_flush(struct output_device *device, int callback_id) as->state = OUTPUT_STATE_CONNECTED; alsa_status(as); - return 0; + return 1; } static int @@ -1184,7 +1184,7 @@ alsa_device_probe(struct output_device *device, int callback_id) as->state = OUTPUT_STATE_STOPPED; alsa_status(as); // Will terminate the session since the state is STOPPED - return 0; + return 1; } static int diff --git a/src/outputs/cast.c b/src/outputs/cast.c index 8a732ef5..aa89f62b 100644 --- a/src/outputs/cast.c +++ b/src/outputs/cast.c @@ -1932,7 +1932,7 @@ cast_device_start_generic(struct output_device *device, int callback_id, cast_re cast_session_cleanup(cs); } else - return 0; + return 1; } cs = cast_session_make(device, AF_INET, callback_id); @@ -1950,7 +1950,7 @@ cast_device_start_generic(struct output_device *device, int callback_id, cast_re return -1; } - return 0; + return 1; } static int @@ -1974,7 +1974,7 @@ cast_device_stop(struct output_device *device, int callback_id) cast_session_shutdown(cs, CAST_STATE_NONE); - return 0; + return 1; } static int @@ -1986,7 +1986,7 @@ cast_device_flush(struct output_device *device, int callback_id) cs->state = CAST_STATE_MEDIA_CONNECTED; cast_status(cs); - return 0; + return 1; } static void diff --git a/src/outputs/dummy.c b/src/outputs/dummy.c index a1865652..9de6e85a 100644 --- a/src/outputs/dummy.c +++ b/src/outputs/dummy.c @@ -115,7 +115,7 @@ dummy_device_start(struct output_device *device, int callback_id) dummy_status(ds); - return 0; + return 1; } static int @@ -128,7 +128,7 @@ dummy_device_stop(struct output_device *device, int callback_id) dummy_status(ds); - return 0; + return 1; } static int @@ -141,7 +141,7 @@ dummy_device_flush(struct output_device *device, int callback_id) dummy_status(ds); - return 0; + return 1; } static int @@ -158,7 +158,7 @@ dummy_device_probe(struct output_device *device, int callback_id) dummy_status(ds); - return 0; + return 1; } static int diff --git a/src/outputs/fifo.c b/src/outputs/fifo.c index e94b32bd..b87510b6 100644 --- a/src/outputs/fifo.c +++ b/src/outputs/fifo.c @@ -302,7 +302,7 @@ fifo_device_start(struct output_device *device, int callback_id) fifo_status(fifo_session); - return 0; + return 1; } static int @@ -320,7 +320,7 @@ fifo_device_stop(struct output_device *device, int callback_id) fifo_session->state = OUTPUT_STATE_STOPPED; fifo_status(fifo_session); - return 0; + return 1; } static int @@ -335,7 +335,7 @@ fifo_device_flush(struct output_device *device, int callback_id) fifo_session->state = OUTPUT_STATE_CONNECTED; fifo_status(fifo_session); - return 0; + return 1; } static int @@ -362,7 +362,7 @@ fifo_device_probe(struct output_device *device, int callback_id) fifo_status(fifo_session); - return 0; + return 1; } static int diff --git a/src/outputs/pulse.c b/src/outputs/pulse.c index f5f02eb9..41aa3aa4 100644 --- a/src/outputs/pulse.c +++ b/src/outputs/pulse.c @@ -754,7 +754,7 @@ pulse_device_start(struct output_device *device, int callback_id) pulse_status(ps); - return 0; + return 1; } static int @@ -768,7 +768,7 @@ pulse_device_stop(struct output_device *device, int callback_id) stream_close(ps, close_cb); - return 0; + return 1; } static int @@ -801,7 +801,7 @@ pulse_device_flush(struct output_device *device, int callback_id) pa_threaded_mainloop_unlock(pulse.mainloop); - return 0; + return 1; } static int @@ -823,7 +823,7 @@ pulse_device_probe(struct output_device *device, int callback_id) return -1; } - return 0; + return 1; } static void diff --git a/src/outputs/raop.c b/src/outputs/raop.c index 113c20b6..4c0a7c8a 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -3759,7 +3759,7 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg) token = strtok_r(token, ";=", &ptr); while (token) { - DPRINTF(E_DBG, L_RAOP, "token: %s\n", token); + DPRINTF(E_SPAM, L_RAOP, "token: %s\n", token); if (strcmp(token, "server_port") == 0) { @@ -4693,7 +4693,7 @@ raop_device_start_generic(struct output_device *device, int callback_id, bool on ret = raop_send_req_options(rs, raop_cb_startup_options, "device_start"); if (ret == 0) - return 0; + return 1; else { DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv6\n"); @@ -4719,7 +4719,7 @@ raop_device_start_generic(struct output_device *device, int callback_id, bool on return -1; } - return 0; + return 1; } static int @@ -4741,7 +4741,9 @@ raop_device_stop(struct output_device *device, int callback_id) rs->callback_id = callback_id; - return session_teardown(rs, "device_stop"); + session_teardown(rs, "device_stop"); + + return 1; } static int @@ -4759,7 +4761,7 @@ raop_device_flush(struct output_device *device, int callback_id) rs->callback_id = callback_id; - return 0; + return 1; } static void diff --git a/src/player.c b/src/player.c index 049afcc4..8e04fd5e 100644 --- a/src/player.c +++ b/src/player.c @@ -85,9 +85,6 @@ # include "lastfm.h" #endif -// Default volume (must be from 0 - 100) -#define PLAYER_DEFAULT_VOLUME 50 - // The interval between each tick of the playback clock in ms. This means that // we read 10 ms frames from the input and pass to the output, so the clock // ticks 100 times a second. We use this value because most common sample rates @@ -110,6 +107,15 @@ // (value is in milliseconds) #define PLAYER_WRITE_BEHIND_MAX 1500 +// If a speaker fails during playback we try to bring it back by reconnecting +// after this number of seconds. When this feature was added, we had an issue +// with Homepods and ATV4's dropping connections, so it is also a workaround. +#define PLAYER_SPEAKER_RESURRECT_TIME 5 + +// Shorthand condition for outputs_start and outputs_device_start, both need to +// know if they should only probe the device, or fully start it. +#define PLAYER_ONLY_PROBE (player_state != PLAY_PLAYING) + //#define DEBUG_PLAYER 1 struct spk_enum @@ -121,7 +127,6 @@ struct spk_enum struct speaker_set_param { uint64_t *device_ids; - int intval; }; struct speaker_attr_param @@ -296,12 +301,6 @@ static int pb_write_deficit_max; // True if we are trying to recover from a major playback timer overrun (write problems) static bool pb_write_recovery; -// Output status -static int output_sessions; - -// Last commanded volume -static int master_volume; - // Audio source static uint32_t cur_plid; static uint32_t cur_plversion; @@ -315,101 +314,10 @@ static struct player_history *history; static void pb_abort(void); -static void +static int pb_suspend(void); -/* ----------------------------- Volume helpers ----------------------------- */ - -static int -rel_to_vol(int relvol) -{ - float vol; - - if (relvol == 100) - return master_volume; - - vol = ((float)relvol * (float)master_volume) / 100.0; - - return (int)vol; -} - -static int -vol_to_rel(int volume) -{ - float rel; - - if (volume == master_volume) - return 100; - - rel = ((float)volume / (float)master_volume) * 100.0; - - return (int)rel; -} - -// Master volume helpers -static void -volume_master_update(int newvol) -{ - struct output_device *device; - - master_volume = newvol; - - for (device = output_device_list; device; device = device->next) - { - if (device->selected) - device->relvol = vol_to_rel(device->volume); - } -} - -static void -volume_master_find(void) -{ - struct output_device *device; - int newmaster; - - newmaster = -1; - - for (device = output_device_list; device; device = device->next) - { - if (device->selected && (device->volume > newmaster)) - newmaster = device->volume; - } - - volume_master_update(newmaster); -} - - -/* ---------------------- Device select/deselect hooks ---------------------- */ - -static void -speaker_select_output(struct output_device *device) -{ - device->selected = 1; - device->prevent_playback = 0; - device->busy = 0; - - if (device->volume > master_volume) - { - if (player_state == PLAY_STOPPED || master_volume == -1) - volume_master_update(device->volume); - else - device->volume = master_volume; - } - - device->relvol = vol_to_rel(device->volume); -} - -static void -speaker_deselect_output(struct output_device *device) -{ - device->selected = 0; - - if (device->volume == master_volume) - volume_master_find(); -} - - /* ----------------------- Misc helpers and callbacks ----------------------- */ // Callback from the worker thread (async operation as it may block) @@ -1143,7 +1051,12 @@ playback_cb(int fd, short what, void *arg) DPRINTF(E_LOG, L_PLAYER, "Output delay detected (behind=%" PRIu64 ", max=%d), resetting all outputs\n", overrun, pb_write_deficit_max); pb_write_recovery = true; - pb_suspend(); + player_flush_pending = pb_suspend(); + // No devices to wait for, just set the restart cb right away. Otherwise + // the trigger will be set by device_flush_cb. + if (player_flush_pending == 0) + input_buffer_full_cb(player_playback_start); + return; } else @@ -1209,7 +1122,11 @@ playback_cb(int fd, short what, void *arg) DPRINTF(E_LOG, L_PLAYER, "Source is not providing sufficient data, temporarily suspending playback (deficit=%zu/%zu bytes)\n", pb_session.read_deficit, pb_session.read_deficit_max); - pb_suspend(); + player_flush_pending = pb_suspend(); + // No devices to wait for, just set the restart cb right away. Otherwise + // the trigger will be set by device_flush_cb. + if (player_flush_pending == 0) + input_buffer_full_cb(player_playback_start); } } @@ -1222,20 +1139,13 @@ device_add(void *arg, int *retval) union player_arg *cmdarg = arg; struct output_device *device = cmdarg->device; bool new_deselect; - int default_volume; - - // Default volume for new devices - default_volume = (master_volume >= 0) ? master_volume : PLAYER_DEFAULT_VOLUME; // Never turn on new devices during playback new_deselect = (player_state == PLAY_PLAYING); - device = outputs_device_add(device, new_deselect, default_volume); + device = outputs_device_add(device, new_deselect); *retval = device ? 0 : -1; - if (device && device->selected) - speaker_select_output(device); - return COMMAND_END; } @@ -1282,10 +1192,7 @@ device_remove_family(void *arg, int *retval) // the device. If the output backend never gives a callback (can that // happen?) then the device will never be removed. if (!device->session) - { - outputs_device_remove(device); - volume_master_find(); - } + outputs_device_remove(device); } outputs_device_free(remove); @@ -1314,30 +1221,24 @@ device_streaming_cb(struct output_device *device, enum output_device_state statu if (!device) { DPRINTF(E_LOG, L_PLAYER, "Output device disappeared during streaming!\n"); - - output_sessions--; return; } - DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_streaming_cb (status %d)\n", outputs_name(device->type), status); + DPRINTF(E_DBG, L_PLAYER, "Callback from %s device %s to device_streaming_cb (status %d)\n", device->type_name, device->name, status); if (status == OUTPUT_STATE_FAILED) { - DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' FAILED\n", device->type_name, device->name); + DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' failed - attempting reconnect in %d sec\n", device->type_name, device->name, PLAYER_SPEAKER_RESURRECT_TIME); - output_sessions--; + if (outputs_sessions_count() == 0) + pb_suspend(); - if (player_state == PLAY_PLAYING) - speaker_deselect_output(device); - - if (output_sessions == 0) - pb_abort(); + // TODO do this internally instead of through the worker + worker_execute(player_speaker_resurrect, &(device->id), sizeof(device->id), PLAYER_SPEAKER_RESURRECT_TIME); } else if (status == OUTPUT_STATE_STOPPED) { DPRINTF(E_INFO, L_PLAYER, "The %s device '%s' stopped\n", device->type_name, device->name); - - output_sessions--; } else outputs_device_cb_set(device, device_streaming_cb); @@ -1352,7 +1253,7 @@ device_command_cb(struct output_device *device, enum output_device_state status) goto out; } - DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_command_cb (status %d)\n", outputs_name(device->type), status); + DPRINTF(E_DBG, L_PLAYER, "Callback from %s device %s to device_command_cb (status %d)\n", device->type_name, device->name, status); outputs_device_cb_set(device, device_streaming_cb); @@ -1372,7 +1273,7 @@ device_flush_cb(struct output_device *device, enum output_device_state status) goto out; } - DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_flush_cb (status %d)\n", outputs_name(device->type), status); + DPRINTF(E_DBG, L_PLAYER, "Callback from %s device %s to device_flush_cb (status %d)\n", device->type_name, device->name, status); if (status == OUTPUT_STATE_FAILED) device_streaming_cb(device, status); @@ -1396,9 +1297,6 @@ device_shutdown_cb(struct output_device *device, enum output_device_state status { int retval; - if (output_sessions) - output_sessions--; - retval = commands_exec_returnvalue(cmdbase); if (!device) { @@ -1409,13 +1307,9 @@ device_shutdown_cb(struct output_device *device, enum output_device_state status goto out; } - DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_shutdown_cb (status %d)\n", outputs_name(device->type), status); + DPRINTF(E_DBG, L_PLAYER, "Callback from %s device %s to device_shutdown_cb (status %d)\n", device->type_name, device->name, status); out: - /* cur_cmd->ret already set - * - to 0 (or -2 if password issue) in speaker_set() - * - to -1 above on error - */ commands_exec_end(cmdbase, retval); } @@ -1434,110 +1328,26 @@ device_activate_cb(struct output_device *device, enum output_device_state status goto out; } - DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_activate_cb (status %d)\n", outputs_name(device->type), status); + DPRINTF(E_DBG, L_PLAYER, "Callback from %s device %s to device_activate_cb (status %d)\n", device->type_name, device->name, status); if (status == OUTPUT_STATE_PASSWORD) { + DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' requires a valid password\n", device->type_name, device->name); + status = OUTPUT_STATE_FAILED; retval = -2; } if (status == OUTPUT_STATE_FAILED) { - speaker_deselect_output(device); + DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' failed to activate\n", device->type_name, device->name); if (retval != -2) retval = -1; goto out; } - output_sessions++; - - outputs_device_cb_set(device, device_streaming_cb); - - out: - /* cur_cmd->ret already set - * - to 0 in speaker_set() (default) - * - to -2 above if password issue - * - to -1 above on error - */ - commands_exec_end(cmdbase, retval); -} - -static void -device_probe_cb(struct output_device *device, enum output_device_state status) -{ - int retval; - - retval = commands_exec_returnvalue(cmdbase); - if (!device) - { - DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during probe!\n"); - - if (retval != -2) - retval = -1; - goto out; - } - - DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_probe_cb (status %d)\n", outputs_name(device->type), status); - - if (status == OUTPUT_STATE_PASSWORD) - { - status = OUTPUT_STATE_FAILED; - retval = -2; - } - - if (status == OUTPUT_STATE_FAILED) - { - speaker_deselect_output(device); - - if (retval != -2) - retval = -1; - goto out; - } - - out: - /* cur_cmd->ret already set - * - to 0 in speaker_set() (default) - * - to -2 above if password issue - * - to -1 above on error - */ - commands_exec_end(cmdbase, retval); -} - -static void -device_restart_cb(struct output_device *device, enum output_device_state status) -{ - int retval; - - retval = commands_exec_returnvalue(cmdbase); - if (!device) - { - DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during restart!\n"); - - if (retval != -2) - retval = -1; - goto out; - } - - DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb (status %d)\n", outputs_name(device->type), status); - - if (status == OUTPUT_STATE_PASSWORD) - { - status = OUTPUT_STATE_FAILED; - retval = -2; - } - - if (status == OUTPUT_STATE_FAILED) - { - speaker_deselect_output(device); - - if (retval != -2) - retval = -1; - goto out; - } - - output_sessions++; + // If we were just probing this is a no-op outputs_device_cb_set(device, device_streaming_cb); out: @@ -1547,11 +1357,7 @@ device_restart_cb(struct output_device *device, enum output_device_state status) const char * player_pmap(void *p) { - if (p == device_restart_cb) - return "device_restart_cb"; - else if (p == device_probe_cb) - return "device_probe_cb"; - else if (p == device_activate_cb) + if (p == device_activate_cb) return "device_activate_cb"; else if (p == device_streaming_cb) return "device_streaming_cb"; @@ -1738,10 +1544,11 @@ pb_resume(void) // Temporarily suspends/resets playback, used when input buffer underruns or in // case of problems writing to the outputs. -static void +static int pb_suspend(void) { struct db_queue_item *queue_item; + int flush_pending; int ret; // If ->next is set then suspend was called during a track change, which is a @@ -1760,7 +1567,7 @@ pb_suspend(void) { DPRINTF(E_LOG, L_PLAYER, "Error suspending playback, could not retrieve queue item currently being played\n"); pb_abort(); - return; + return -1; } ret = pb_session_start(queue_item, 0); @@ -1769,11 +1576,11 @@ pb_suspend(void) { DPRINTF(E_LOG, L_PLAYER, "Error suspending playback, could not start session\n"); pb_abort(); - return; + return -1; } } - player_flush_pending = outputs_flush(device_flush_cb); + flush_pending = outputs_flush(device_flush_cb); pb_timer_stop(); @@ -1781,9 +1588,7 @@ pb_suspend(void) seek_save(); - // No devices to wait for, just set the restart cb right away - if (player_flush_pending == 0) - input_buffer_full_cb(player_playback_start); + return flush_pending; } @@ -1800,7 +1605,7 @@ get_status(void *arg, int *retval) status->consume = consume; status->repeat = repeat; - status->volume = master_volume; + status->volume = outputs_master_volume; status->plid = cur_plid; @@ -1987,47 +1792,28 @@ playback_start_item(void *arg, int *retval) } } - // Start sessions on selected devices - *retval = 0; - - for (device = output_device_list; device; device = device->next) + // Start sessions on selected devices. We shouldn't see any callbacks to + // device_shutdown_cb, since the unselected devices shouldn't have sessions. + *retval = outputs_start(device_activate_cb, device_shutdown_cb, false); + if (*retval < 0) { - if (device->selected && !device->session) - { - ret = outputs_device_start(device, device_restart_cb); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not start selected %s device '%s'\n", device->type_name, device->name); - continue; - } + DPRINTF(E_LOG, L_PLAYER, "All selected speakers failed to start\n"); - DPRINTF(E_INFO, L_PLAYER, "Using selected %s device '%s'\n", device->type_name, device->name); - (*retval)++; + // All selected devices failed, autoselect an unselected (if enabled) + for (device = output_device_list; (*retval < 0) && speaker_autoselect && device; device = device->next) + { + if (!device->selected && outputs_priority(device) != 0 && !device->session) + *retval = outputs_device_start(device, device_activate_cb, false); + } + + if (*retval >= 0) + { + DPRINTF(E_LOG, L_PLAYER, "Autoselected %s device '%s'\n", device->type_name, device->name); + outputs_device_select(device); } } - // If autoselecting is enabled, try to autoselect a non-selected device if the above failed - if (speaker_autoselect && (*retval == 0) && (output_sessions == 0)) - for (device = output_device_list; device; device = device->next) - { - if ((outputs_priority(device) == 0) || device->session) - continue; - - speaker_select_output(device); - ret = outputs_device_start(device, device_restart_cb); - if (ret < 0) - { - DPRINTF(E_DBG, L_PLAYER, "Could not autoselect %s device '%s'\n", device->type_name, device->name); - speaker_deselect_output(device); - continue; - } - - DPRINTF(E_INFO, L_PLAYER, "Autoselecting %s device '%s'\n", device->type_name, device->name); - (*retval)++; - break; - } - - // We're async if we need to start devices + // We're async if we need to wait for devices starting if (*retval > 0) return COMMAND_PENDING; // async @@ -2416,7 +2202,12 @@ device_to_speaker_info(struct player_speaker_info *spk, struct output_device *de spk->relvol = device->relvol; spk->absvol = device->volume; - spk->selected = device->selected; + // We can't map device->selected directly to spk->selected, since the former + // means "has the user selected the device" (even if it isn't working or is + // unavailable), while clients that get the latter just want to know if the + // speaker plays or will be playing. + spk->selected = (device->selected && device->state >= OUTPUT_STATE_STOPPED && !device->busy && !device->prevent_playback); + spk->has_password = device->has_password; spk->has_video = device->has_video; spk->requires_auth = device->requires_auth; @@ -2485,72 +2276,6 @@ speaker_get_byactiveremote(void *arg, int *retval) return COMMAND_END; } -static int -speaker_activate(struct output_device *device) -{ - int ret; - - if (device->has_password && !device->password) - { - DPRINTF(E_INFO, L_PLAYER, "The %s device '%s' is password-protected, but we don't have it\n", device->type_name, device->name); - - return -2; - } - - DPRINTF(E_DBG, L_PLAYER, "The %s device '%s' is selected\n", device->type_name, device->name); - - if (!device->selected) - speaker_select_output(device); - - if (device->session) - return 0; - - if (player_state == PLAY_PLAYING) - { - DPRINTF(E_DBG, L_PLAYER, "Activating %s device '%s'\n", device->type_name, device->name); - - ret = outputs_device_start(device, device_activate_cb); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not start %s device '%s'\n", device->type_name, device->name); - goto error; - } - } - else - { - DPRINTF(E_DBG, L_PLAYER, "Probing %s device '%s'\n", device->type_name, device->name); - - ret = outputs_device_probe(device, device_probe_cb); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not probe %s device '%s'\n", device->type_name, device->name); - goto error; - } - } - - return 0; - - error: - DPRINTF(E_LOG, L_PLAYER, "Could not activate %s device '%s'\n", device->type_name, device->name); - speaker_deselect_output(device); - return -1; -} - -static int -speaker_deactivate(struct output_device *device) -{ - DPRINTF(E_DBG, L_PLAYER, "Deactivating %s device '%s'\n", device->type_name, device->name); - - if (device->selected) - speaker_deselect_output(device); - - if (!device->session) - return 0; - - outputs_device_stop(device, device_shutdown_cb); - return 1; -} - static enum command_state speaker_set(void *arg, int *retval) { @@ -2559,9 +2284,9 @@ speaker_set(void *arg, int *retval) uint64_t *ids; int nspk; int i; - int ret; - *retval = 0; + *retval = -1; + ids = speaker_set_param->device_ids; if (ids) @@ -2571,40 +2296,25 @@ speaker_set(void *arg, int *retval) DPRINTF(E_DBG, L_PLAYER, "Speaker set: %d speakers\n", nspk); - *retval = 0; - for (device = output_device_list; device; device = device->next) { for (i = 1; i <= nspk; i++) { - DPRINTF(E_DBG, L_PLAYER, "Set %" PRIu64 " device %" PRIu64 "\n", ids[i], device->id); - if (ids[i] == device->id) break; } if (i <= nspk) - { - ret = speaker_activate(device); - - if (ret > 0) - (*retval)++; - else if (ret < 0 && speaker_set_param->intval != -2) - speaker_set_param->intval = ret; - } + outputs_device_select(device); else - { - ret = speaker_deactivate(device); - - if (ret > 0) - (*retval)++; - } + outputs_device_deselect(device); } + *retval = outputs_start(device_activate_cb, device_shutdown_cb, PLAYER_ONLY_PROBE); + if (*retval > 0) return COMMAND_PENDING; // async - *retval = speaker_set_param->intval; return COMMAND_END; } @@ -2614,13 +2324,17 @@ speaker_enable(void *arg, int *retval) uint64_t *id = arg; struct output_device *device; + *retval = -1; + device = outputs_device_get(*id); if (!device) return COMMAND_END; DPRINTF(E_DBG, L_PLAYER, "Speaker enable: '%s' (id=%" PRIu64 ")\n", device->name, *id); - *retval = speaker_activate(device); + outputs_device_select(device); + + *retval = outputs_device_start(device, device_activate_cb, PLAYER_ONLY_PROBE); if (*retval > 0) return COMMAND_PENDING; // async @@ -2634,13 +2348,17 @@ speaker_disable(void *arg, int *retval) uint64_t *id = arg; struct output_device *device; + *retval = -1; + device = outputs_device_get(*id); if (!device) return COMMAND_END; DPRINTF(E_DBG, L_PLAYER, "Speaker disable: '%s' (id=%" PRIu64 ")\n", device->name, *id); - *retval = speaker_deactivate(device); + outputs_device_deselect(device); + + *retval = outputs_device_stop(device, device_shutdown_cb); if (*retval > 0) return COMMAND_PENDING; // async @@ -2680,9 +2398,9 @@ speaker_prevent_playback_set(void *arg, int *retval) DPRINTF(E_DBG, L_PLAYER, "Speaker prevent playback: '%s' (id=%" PRIu64 ")\n", device->name, device->id); if (device->prevent_playback) - *retval = speaker_deactivate(device); + *retval = outputs_device_stop(device, device_shutdown_cb); else if (!device->busy) - *retval = speaker_activate(device); + *retval = outputs_device_start(device, device_activate_cb, PLAYER_ONLY_PROBE); else *retval = 0; @@ -2697,7 +2415,7 @@ speaker_prevent_playback_set_bh(void *arg, int *retval) { struct speaker_attr_param *param = arg; - if (output_sessions == 0) + if (outputs_sessions_count() == 0) { DPRINTF(E_INFO, L_PLAYER, "Ending playback, speaker (id=%" PRIu64 ") set 'busy' or 'prevent-playback' flag\n", param->spk_id); pb_abort(); // TODO Would be better for the user if we paused, but we don't have a handy function for that @@ -2722,9 +2440,9 @@ speaker_busy_set(void *arg, int *retval) DPRINTF(E_DBG, L_PLAYER, "Speaker busy: '%s' (id=%" PRIu64 ")\n", device->name, device->id); if (device->busy) - *retval = speaker_deactivate(device); + *retval = outputs_device_stop(device, device_shutdown_cb); else if (!device->prevent_playback) - *retval = speaker_activate(device); + *retval = outputs_device_start(device, device_activate_cb, PLAYER_ONLY_PROBE); else *retval = 0; @@ -2734,37 +2452,71 @@ speaker_busy_set(void *arg, int *retval) return COMMAND_END; } +// Attempts to reactivate a speaker that has failed. That includes restarting +// playback if it was stopped. +static enum command_state +speaker_resurrect(void *arg, int *retval) +{ + struct speaker_set_param *param = arg; + struct output_device *device; + + *retval = -1; + + device = outputs_device_get(*param->device_ids); + if (!device) + goto out; + + DPRINTF(E_DBG, L_PLAYER, "Speaker resurrect: '%s' (id=%" PRIu64 ")\n", device->name, device->id); + + if (device->busy || device->prevent_playback) + goto out; + + if (player_state == PLAY_PAUSED) + { + // Playback was suspended by device_streaming_cb() because the speaker was + // the only one playing. In that case we need to first resume the source, + // then wait for the speaker to reactivate, and then run bottom half. + *retval = pb_resume(); + if (*retval < 0) + goto out; + } + else if (player_state == PLAY_STOPPED) + { + // If PLAY_STOPPED there is nothing to do, we can't resurrect since the + // source is gone + goto out; + } + + *retval = outputs_device_start(device, device_activate_cb, false); + + if (*retval > 0) + return COMMAND_PENDING; // Wait for speaker + + out: + return COMMAND_END; +} + +static enum command_state +speaker_resurrect_bh(void *arg, int *retval) +{ + // Playback was suspended by device_streaming_cb. We resumed the input in the + // top half, now we have to start the playback timer and update status + if (player_state == PLAY_PAUSED) + return playback_start_bh(arg, retval); + + *retval = 0; + return COMMAND_END; +} + static enum command_state volume_set(void *arg, int *retval) { union player_arg *cmdarg = arg; - struct output_device *device; int volume; - *retval = 0; volume = cmdarg->intval; - if (master_volume == volume) - return COMMAND_END; - - master_volume = volume; - - for (device = output_device_list; device; device = device->next) - { - if (!device->selected) - continue; - - device->volume = rel_to_vol(device->relvol); - -#ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); -#endif - - if (device->session) - *retval += outputs_device_volume_set(device, device_command_cb); - } - - listener_notify(LISTENER_VOLUME); + *retval = outputs_volume_set(volume, device_command_cb); if (*retval > 0) return COMMAND_PENDING; // async @@ -2772,65 +2524,23 @@ volume_set(void *arg, int *retval) return COMMAND_END; } -#ifdef DEBUG_RELVOL -static void debug_print_speaker() -{ - struct output_device *device; - - DPRINTF(E_DBG, L_PLAYER, "*** Master: %d\n", master_volume); - - for (device = output_device_list; device; device = device->next) - { - if (!device->selected) - continue; - - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); - } -} -#endif - static enum command_state volume_setrel_speaker(void *arg, int *retval) { struct speaker_attr_param *vol_param = arg; struct output_device *device; - uint64_t id; - int relvol; - *retval = 0; - id = vol_param->spk_id; - relvol = vol_param->volume; - - for (device = output_device_list; device; device = device->next) + device = outputs_device_get(vol_param->spk_id); + if (!device) { - if (device->id != id) - continue; - - if (!device->selected) - { - *retval = 0; - return COMMAND_END; - } - - device->relvol = relvol; - device->volume = rel_to_vol(relvol); - -#ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); -#endif - - if (device->session) - *retval += outputs_device_volume_set(device, device_command_cb); - - break; + DPRINTF(E_WARN, L_PLAYER, "Could not set volume for speaker id %" PRIu64 ", speaker disappeared\n", vol_param->spk_id); + *retval = -1; + return COMMAND_END; } - volume_master_find(); + outputs_device_volume_register(device, -1, vol_param->volume); -#ifdef DEBUG_RELVOL - debug_print_speaker(); -#endif - listener_notify(LISTENER_VOLUME); + *retval = outputs_device_volume_set(device, device_command_cb); if (*retval > 0) return COMMAND_PENDING; // async @@ -2843,50 +2553,18 @@ volume_setabs_speaker(void *arg, int *retval) { struct speaker_attr_param *vol_param = arg; struct output_device *device; - uint64_t id; - int volume; - *retval = 0; - id = vol_param->spk_id; - volume = vol_param->volume; - - master_volume = volume; - - for (device = output_device_list; device; device = device->next) + device = outputs_device_get(vol_param->spk_id); + if (!device) { - if (!device->selected) - continue; - - if (device->id != id) - { - device->relvol = vol_to_rel(device->volume); - -#ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); -#endif - continue; - } - else - { - device->relvol = 100; - device->volume = master_volume; - -#ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); -#endif - - if (device->session) - *retval += outputs_device_volume_set(device, device_command_cb); - } + DPRINTF(E_WARN, L_PLAYER, "Could not set volume for speaker id %" PRIu64 ", speaker disappeared\n", vol_param->spk_id); + *retval = -1; + return COMMAND_END; } - volume_master_find(); + outputs_device_volume_register(device, vol_param->volume, -1); -#ifdef DEBUG_RELVOL - debug_print_speaker(); -#endif - - listener_notify(LISTENER_VOLUME); + *retval = outputs_device_volume_set(device, device_command_cb); if (*retval > 0) return COMMAND_PENDING; // async @@ -2905,6 +2583,7 @@ volume_update_speaker(void *arg, int *retval) device = outputs_device_get(vol_param->spk_id); if (!device) { + DPRINTF(E_WARN, L_PLAYER, "Could not set volume for speaker id %" PRIu64 ", speaker disappeared\n", vol_param->spk_id); *retval = -1; return COMMAND_END; } @@ -2912,20 +2591,12 @@ volume_update_speaker(void *arg, int *retval) volume = outputs_device_volume_to_pct(device, vol_param->volstr); // Only converts if (volume < 0) { - DPRINTF(E_LOG, L_DACP, "Could not parse volume '%s' in update_volume() for speaker '%s'\n", vol_param->volstr, device->name); + DPRINTF(E_LOG, L_PLAYER, "Could not parse volume '%s' in update_volume() for speaker '%s'\n", vol_param->volstr, device->name); *retval = -1; return COMMAND_END; } - device->volume = volume; - - volume_master_find(); - -#ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); -#endif - - listener_notify(LISTENER_VOLUME); + outputs_device_volume_register(device, volume, -1); *retval = 0; return COMMAND_END; @@ -3206,7 +2877,6 @@ player_speaker_set(uint64_t *ids) int ret; speaker_set_param.device_ids = ids; - speaker_set_param.intval = 0; ret = commands_exec_sync(cmdbase, speaker_set, NULL, &speaker_set_param); @@ -3297,6 +2967,18 @@ player_speaker_busy_set(uint64_t id, bool busy) return ret; } +void +player_speaker_resurrect(void *arg) +{ + struct speaker_set_param param; + + param.device_ids = (uint64_t *)arg; + + commands_exec_sync(cmdbase, speaker_resurrect, speaker_resurrect_bh, ¶m); + + listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME); +} + int player_volume_set(int vol) { @@ -3537,8 +3219,6 @@ player_init(void) speaker_autoselect = cfg_getbool(cfg_getsec(cfg, "general"), "speaker_autoselect"); clear_queue_on_stop_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable"); - master_volume = -1; - player_state = PLAY_STOPPED; repeat = REPEAT_OFF; diff --git a/src/player.h b/src/player.h index d8f33cb3..45cf104c 100644 --- a/src/player.h +++ b/src/player.h @@ -111,6 +111,9 @@ player_speaker_prevent_playback_set(uint64_t id, bool prevent_playback); int player_speaker_busy_set(uint64_t id, bool busy); +void +player_speaker_resurrect(void *arg); + int player_playback_start(void);