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);