[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.
This commit is contained in:
ejurgensen 2020-05-01 20:11:45 +02:00
parent d0738aabae
commit 661289990c
10 changed files with 476 additions and 573 deletions

View File

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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

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