mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-27 13:29:17 -05:00
Merge branch 'speaker_selection1'
This commit is contained in:
commit
945aefaaa5
@ -47,7 +47,7 @@ general {
|
|||||||
|
|
||||||
# When starting playback, autoselect speaker (if none of the previously
|
# When starting playback, autoselect speaker (if none of the previously
|
||||||
# selected speakers/outputs are available)
|
# selected speakers/outputs are available)
|
||||||
# speaker_autoselect = yes
|
# speaker_autoselect = no
|
||||||
|
|
||||||
# Most modern systems have a high-resolution clock, but if you are on an
|
# Most modern systems have a high-resolution clock, but if you are on an
|
||||||
# unusual platform and experience audio drop-outs, you can try changing
|
# unusual platform and experience audio drop-outs, you can try changing
|
||||||
|
@ -54,7 +54,7 @@ static cfg_opt_t sec_general[] =
|
|||||||
CFG_BOOL("ipv6", cfg_true, CFGF_NONE),
|
CFG_BOOL("ipv6", cfg_true, CFGF_NONE),
|
||||||
CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
|
CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
|
||||||
CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
|
CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
|
||||||
CFG_BOOL("speaker_autoselect", cfg_true, CFGF_NONE),
|
CFG_BOOL("speaker_autoselect", cfg_false, CFGF_NONE),
|
||||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||||
CFG_BOOL("high_resolution_clock", cfg_false, CFGF_NONE),
|
CFG_BOOL("high_resolution_clock", cfg_false, CFGF_NONE),
|
||||||
#else
|
#else
|
||||||
|
287
src/outputs.c
287
src/outputs.c
@ -75,6 +75,9 @@ static struct output_definition *outputs[] = {
|
|||||||
NULL
|
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
|
// 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
|
// actually just restarting. This timeout determines how long we wait before
|
||||||
// full stop.
|
// 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
|
// The device has left the building (stopped/failed), and the backend
|
||||||
// is not using it any more
|
// is not using it any more
|
||||||
if (!device->advertised && !device->session)
|
if (device && !device->advertised && !device->session)
|
||||||
{
|
{
|
||||||
outputs_device_remove(device);
|
outputs_device_remove(device);
|
||||||
device = NULL;
|
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);
|
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
|
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");
|
DPRINTF(E_WARN, L_PLAYER, "Failed to stop device\n");
|
||||||
else
|
else
|
||||||
DPRINTF(E_INFO, L_PLAYER, "Device stopped properly\n");
|
DPRINTF(E_INFO, L_PLAYER, "Device stopped properly\n");
|
||||||
@ -406,6 +414,16 @@ device_list_sort(void)
|
|||||||
while (swaps > 0);
|
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
|
static void
|
||||||
metadata_cb_send(int fd, short what, void *arg)
|
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 ---------------------------------- */
|
/* ----------------------------------- API ---------------------------------- */
|
||||||
|
|
||||||
struct output_device *
|
struct output_device *
|
||||||
@ -474,7 +556,7 @@ outputs_device_get(uint64_t device_id)
|
|||||||
return device;
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,7 +695,7 @@ outputs_listener_notify(void)
|
|||||||
/* ---------------------------- Called by player ---------------------------- */
|
/* ---------------------------- Called by player ---------------------------- */
|
||||||
|
|
||||||
struct output_device *
|
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;
|
struct output_device *device;
|
||||||
char *keep_name;
|
char *keep_name;
|
||||||
@ -637,7 +719,7 @@ outputs_device_add(struct output_device *add, bool new_deselect, int default_vol
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
device->selected = 0;
|
device->selected = 0;
|
||||||
device->volume = default_volume;
|
device->volume = (outputs_master_volume >= 0) ? outputs_master_volume : OUTPUTS_DEFAULT_VOLUME;;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(device->name);
|
free(device->name);
|
||||||
@ -686,9 +768,11 @@ outputs_device_add(struct output_device *add, bool new_deselect, int default_vol
|
|||||||
|
|
||||||
device_list_sort();
|
device_list_sort();
|
||||||
|
|
||||||
|
vol_adjust();
|
||||||
|
|
||||||
device->advertised = 1;
|
device->advertised = 1;
|
||||||
|
|
||||||
listener_notify(LISTENER_SPEAKER);
|
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||||
|
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
@ -732,37 +816,62 @@ outputs_device_remove(struct output_device *remove)
|
|||||||
|
|
||||||
outputs_device_free(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
|
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;
|
return -1;
|
||||||
|
|
||||||
if (device->session)
|
if (device->session)
|
||||||
{
|
return 0; // Device is already running, nothing to do
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_start() called for a device that already has a session\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
int
|
||||||
outputs_device_stop(struct output_device *device, output_status_cb cb)
|
outputs_device_stop(struct output_device *device, output_status_cb cb)
|
||||||
{
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
if (outputs[device->type]->disabled || !outputs[device->type]->device_stop)
|
if (outputs[device->type]->disabled || !outputs[device->type]->device_stop)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (!device->session)
|
if (!device->session)
|
||||||
{
|
return 0; // Device is already stopped, nothing to do
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_stop() called for a device that has no session\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
int
|
||||||
@ -772,52 +881,58 @@ outputs_device_stop_delayed(struct output_device *device, output_status_cb cb)
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (!device->session)
|
if (!device->session)
|
||||||
{
|
return 0; // Device is already stopped, nothing to do
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_stop_delayed() called for a device that has no session\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs[device->type]->device_cb_set(device, callback_add(device, cb));
|
outputs[device->type]->device_cb_set(device, callback_add(device, cb));
|
||||||
|
|
||||||
event_add(device->stop_timer, &outputs_stop_timeout);
|
event_add(device->stop_timer, &outputs_stop_timeout);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
outputs_device_flush(struct output_device *device, output_status_cb cb)
|
outputs_device_flush(struct output_device *device, output_status_cb cb)
|
||||||
{
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
if (outputs[device->type]->disabled || !outputs[device->type]->device_flush)
|
if (outputs[device->type]->disabled || !outputs[device->type]->device_flush)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (!device->session)
|
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
|
void
|
||||||
outputs_device_probe(struct output_device *device, output_status_cb cb)
|
outputs_device_volume_register(struct output_device *device, int absvol, int relvol)
|
||||||
{
|
{
|
||||||
if (outputs[device->type]->disabled || !outputs[device->type]->device_probe)
|
if (absvol > -1)
|
||||||
return -1;
|
device->volume = absvol;
|
||||||
|
else if (relvol > -1)
|
||||||
|
device->volume = rel_to_vol(relvol, outputs_master_volume);
|
||||||
|
|
||||||
if (device->session)
|
vol_adjust();
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_probe() called for a device that already has a session\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputs[device->type]->device_probe(device, callback_add(device, cb));
|
listener_notify(LISTENER_VOLUME);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
outputs_device_volume_set(struct output_device *device, output_status_cb cb)
|
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)
|
if (outputs[device->type]->disabled || !outputs[device->type]->device_volume_set)
|
||||||
return -1;
|
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
|
int
|
||||||
@ -832,10 +947,14 @@ outputs_device_volume_to_pct(struct output_device *device, const char *volume)
|
|||||||
int
|
int
|
||||||
outputs_device_quality_set(struct output_device *device, struct media_quality *quality, output_status_cb cb)
|
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)
|
if (outputs[device->type]->disabled || !outputs[device->type]->device_quality_set)
|
||||||
return -1;
|
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
|
void
|
||||||
@ -876,30 +995,37 @@ outputs_device_free(struct output_device *device)
|
|||||||
free(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
|
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;
|
struct output_device *device;
|
||||||
int count = 0;
|
int pending = 0;
|
||||||
int ret;
|
int ret = -1;
|
||||||
|
|
||||||
for (device = output_device_list; device; device = device->next)
|
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)
|
if (ret < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
count++;
|
pending += ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return (pending > 0) ? pending : ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
outputs_stop(output_status_cb cb)
|
outputs_stop(output_status_cb cb)
|
||||||
{
|
{
|
||||||
struct output_device *device;
|
struct output_device *device;
|
||||||
int count = 0;
|
int pending = 0;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
for (device = output_device_list; device; device = device->next)
|
for (device = output_device_list; device; device = device->next)
|
||||||
@ -911,10 +1037,10 @@ outputs_stop(output_status_cb cb)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
count++;
|
pending += ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -928,6 +1054,69 @@ outputs_stop_delayed_cancel(void)
|
|||||||
return 0;
|
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
|
void
|
||||||
outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts)
|
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 ret;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
outputs_master_volume = -1;
|
||||||
|
|
||||||
CHECK_NULL(L_PLAYER, outputs_deferredev = evtimer_new(evbase_player, deferred_cb, NULL));
|
CHECK_NULL(L_PLAYER, outputs_deferredev = evtimer_new(evbase_player, deferred_cb, NULL));
|
||||||
|
|
||||||
no_output = 1;
|
no_output = 1;
|
||||||
|
@ -122,6 +122,10 @@ struct output_device
|
|||||||
// Type of output (string)
|
// Type of output (string)
|
||||||
const char *type_name;
|
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
|
// Misc device flags
|
||||||
unsigned selected:1;
|
unsigned selected:1;
|
||||||
unsigned advertised:1;
|
unsigned advertised:1;
|
||||||
@ -218,6 +222,11 @@ struct output_definition
|
|||||||
// Deinitialization function called at shutdown
|
// Deinitialization function called at shutdown
|
||||||
void (*deinit)(void);
|
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
|
// Prepare a playback session on device and call back
|
||||||
int (*device_start)(struct output_device *device, int callback_id);
|
int (*device_start)(struct output_device *device, int callback_id);
|
||||||
|
|
||||||
@ -293,17 +302,26 @@ outputs_listener_notify(void);
|
|||||||
|
|
||||||
/* ---------------------------- Called by player ---------------------------- */
|
/* ---------------------------- Called by player ---------------------------- */
|
||||||
|
|
||||||
|
int
|
||||||
|
outputs_master_volume;
|
||||||
|
|
||||||
// Ownership of *add is transferred, so don't address after calling. Instead you
|
// 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
|
// can address the return value (which is not the same if the device was already
|
||||||
// in the list.
|
// in the list.
|
||||||
struct output_device *
|
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
|
void
|
||||||
outputs_device_remove(struct output_device *remove);
|
outputs_device_remove(struct output_device *remove);
|
||||||
|
|
||||||
|
void
|
||||||
|
outputs_device_select(struct output_device *device);
|
||||||
|
|
||||||
|
void
|
||||||
|
outputs_device_deselect(struct output_device *device);
|
||||||
|
|
||||||
int
|
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
|
int
|
||||||
outputs_device_stop(struct output_device *device, output_status_cb cb);
|
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
|
int
|
||||||
outputs_device_flush(struct output_device *device, output_status_cb cb);
|
outputs_device_flush(struct output_device *device, output_status_cb cb);
|
||||||
|
|
||||||
int
|
void
|
||||||
outputs_device_probe(struct output_device *device, output_status_cb cb);
|
outputs_device_volume_register(struct output_device *device, int absvol, int relvol);
|
||||||
|
|
||||||
int
|
int
|
||||||
outputs_device_volume_set(struct output_device *device, output_status_cb cb);
|
outputs_device_volume_set(struct output_device *device, output_status_cb cb);
|
||||||
@ -333,14 +351,23 @@ void
|
|||||||
outputs_device_free(struct output_device *device);
|
outputs_device_free(struct output_device *device);
|
||||||
|
|
||||||
int
|
int
|
||||||
outputs_flush(output_status_cb cb);
|
outputs_start(output_status_cb started_cb, output_status_cb stopped_cb, bool only_probe);
|
||||||
|
|
||||||
int
|
int
|
||||||
outputs_stop(output_status_cb cb);
|
outputs_stop(output_status_cb cb);
|
||||||
|
|
||||||
|
int
|
||||||
|
outputs_flush(output_status_cb cb);
|
||||||
|
|
||||||
|
int
|
||||||
|
outputs_volume_set(int volume, output_status_cb cb);
|
||||||
|
|
||||||
int
|
int
|
||||||
outputs_stop_delayed_cancel(void);
|
outputs_stop_delayed_cancel(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
outputs_sessions_count(void);
|
||||||
|
|
||||||
void
|
void
|
||||||
outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts);
|
outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts);
|
||||||
|
|
||||||
|
@ -1143,7 +1143,7 @@ alsa_device_start(struct output_device *device, int callback_id)
|
|||||||
as->state = OUTPUT_STATE_CONNECTED;
|
as->state = OUTPUT_STATE_CONNECTED;
|
||||||
alsa_status(as);
|
alsa_status(as);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -1155,7 +1155,7 @@ alsa_device_stop(struct output_device *device, int callback_id)
|
|||||||
as->state = OUTPUT_STATE_STOPPED;
|
as->state = OUTPUT_STATE_STOPPED;
|
||||||
alsa_status(as); // Will terminate the session since the state is STOPPED
|
alsa_status(as); // Will terminate the session since the state is STOPPED
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -1169,7 +1169,7 @@ alsa_device_flush(struct output_device *device, int callback_id)
|
|||||||
as->state = OUTPUT_STATE_CONNECTED;
|
as->state = OUTPUT_STATE_CONNECTED;
|
||||||
alsa_status(as);
|
alsa_status(as);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -1184,7 +1184,7 @@ alsa_device_probe(struct output_device *device, int callback_id)
|
|||||||
as->state = OUTPUT_STATE_STOPPED;
|
as->state = OUTPUT_STATE_STOPPED;
|
||||||
alsa_status(as); // Will terminate the session since the state is STOPPED
|
alsa_status(as); // Will terminate the session since the state is STOPPED
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -1932,7 +1932,7 @@ cast_device_start_generic(struct output_device *device, int callback_id, cast_re
|
|||||||
cast_session_cleanup(cs);
|
cast_session_cleanup(cs);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
cs = cast_session_make(device, AF_INET, callback_id);
|
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 -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -1974,7 +1974,7 @@ cast_device_stop(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
cast_session_shutdown(cs, CAST_STATE_NONE);
|
cast_session_shutdown(cs, CAST_STATE_NONE);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -1986,7 +1986,7 @@ cast_device_flush(struct output_device *device, int callback_id)
|
|||||||
cs->state = CAST_STATE_MEDIA_CONNECTED;
|
cs->state = CAST_STATE_MEDIA_CONNECTED;
|
||||||
cast_status(cs);
|
cast_status(cs);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -115,7 +115,7 @@ dummy_device_start(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
dummy_status(ds);
|
dummy_status(ds);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -128,7 +128,7 @@ dummy_device_stop(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
dummy_status(ds);
|
dummy_status(ds);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -141,7 +141,7 @@ dummy_device_flush(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
dummy_status(ds);
|
dummy_status(ds);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -158,7 +158,7 @@ dummy_device_probe(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
dummy_status(ds);
|
dummy_status(ds);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -302,7 +302,7 @@ fifo_device_start(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
fifo_status(fifo_session);
|
fifo_status(fifo_session);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -320,7 +320,7 @@ fifo_device_stop(struct output_device *device, int callback_id)
|
|||||||
fifo_session->state = OUTPUT_STATE_STOPPED;
|
fifo_session->state = OUTPUT_STATE_STOPPED;
|
||||||
fifo_status(fifo_session);
|
fifo_status(fifo_session);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -335,7 +335,7 @@ fifo_device_flush(struct output_device *device, int callback_id)
|
|||||||
fifo_session->state = OUTPUT_STATE_CONNECTED;
|
fifo_session->state = OUTPUT_STATE_CONNECTED;
|
||||||
fifo_status(fifo_session);
|
fifo_status(fifo_session);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -362,7 +362,7 @@ fifo_device_probe(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
fifo_status(fifo_session);
|
fifo_status(fifo_session);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -754,7 +754,7 @@ pulse_device_start(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
pulse_status(ps);
|
pulse_status(ps);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -768,7 +768,7 @@ pulse_device_stop(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
stream_close(ps, close_cb);
|
stream_close(ps, close_cb);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -801,7 +801,7 @@ pulse_device_flush(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
pa_threaded_mainloop_unlock(pulse.mainloop);
|
pa_threaded_mainloop_unlock(pulse.mainloop);
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -823,7 +823,7 @@ pulse_device_probe(struct output_device *device, int callback_id)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -100,6 +100,12 @@
|
|||||||
#define RAOP_MD_WANTS_ARTWORK (1 << 1)
|
#define RAOP_MD_WANTS_ARTWORK (1 << 1)
|
||||||
#define RAOP_MD_WANTS_PROGRESS (1 << 2)
|
#define RAOP_MD_WANTS_PROGRESS (1 << 2)
|
||||||
|
|
||||||
|
// ATV4 and Homepod disconnect for reasons that are not clear, but sending them
|
||||||
|
// progress metadata at regular intervals reduces the problem. The below
|
||||||
|
// interval was determined via testing, see:
|
||||||
|
// https://github.com/ejurgensen/forked-daapd/issues/734#issuecomment-622959334
|
||||||
|
#define RAOP_KEEP_ALIVE_INTERVAL 25
|
||||||
|
|
||||||
// This is an arbitrary value which just needs to be kept in sync with the config
|
// This is an arbitrary value which just needs to be kept in sync with the config
|
||||||
#define RAOP_CONFIG_MAX_VOLUME 11
|
#define RAOP_CONFIG_MAX_VOLUME 11
|
||||||
|
|
||||||
@ -142,6 +148,8 @@ enum raop_state {
|
|||||||
RAOP_STATE_CONNECTED = RAOP_STATE_F_CONNECTED | 0x01,
|
RAOP_STATE_CONNECTED = RAOP_STATE_F_CONNECTED | 0x01,
|
||||||
// Media data is being sent
|
// Media data is being sent
|
||||||
RAOP_STATE_STREAMING = RAOP_STATE_F_CONNECTED | 0x02,
|
RAOP_STATE_STREAMING = RAOP_STATE_F_CONNECTED | 0x02,
|
||||||
|
// Session teardown in progress (-> going to STOPPED state)
|
||||||
|
RAOP_STATE_TEARDOWN = RAOP_STATE_F_CONNECTED | 0x03,
|
||||||
// Session is failed, couldn't startup or error occurred
|
// Session is failed, couldn't startup or error occurred
|
||||||
RAOP_STATE_FAILED = RAOP_STATE_F_FAILED | 0x01,
|
RAOP_STATE_FAILED = RAOP_STATE_F_FAILED | 0x01,
|
||||||
// Password issue: unknown password or bad password
|
// Password issue: unknown password or bad password
|
||||||
@ -337,7 +345,7 @@ static struct output_metadata *raop_cur_metadata;
|
|||||||
|
|
||||||
/* Keep-alive timer - hack for ATV's with tvOS 10 */
|
/* Keep-alive timer - hack for ATV's with tvOS 10 */
|
||||||
static struct event *keep_alive_timer;
|
static struct event *keep_alive_timer;
|
||||||
static struct timeval keep_alive_tv = { 30, 0 };
|
static struct timeval keep_alive_tv = { RAOP_KEEP_ALIVE_INTERVAL, 0 };
|
||||||
|
|
||||||
/* Sessions */
|
/* Sessions */
|
||||||
static struct raop_master_session *raop_master_sessions;
|
static struct raop_master_session *raop_master_sessions;
|
||||||
@ -1224,7 +1232,6 @@ raop_send_req_teardown(struct raop_session *rs, evrtsp_req_cb cb, const char *lo
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
rs->state = RAOP_STATE_CONNECTED;
|
|
||||||
rs->reqs_in_flight++;
|
rs->reqs_in_flight++;
|
||||||
|
|
||||||
evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL);
|
evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL);
|
||||||
@ -1737,8 +1744,12 @@ raop_status(struct raop_session *rs)
|
|||||||
case RAOP_STATE_STREAMING:
|
case RAOP_STATE_STREAMING:
|
||||||
state = OUTPUT_STATE_STREAMING;
|
state = OUTPUT_STATE_STREAMING;
|
||||||
break;
|
break;
|
||||||
|
case RAOP_STATE_TEARDOWN:
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Bug! raop_status() called with transitional state (TEARDOWN)\n");
|
||||||
|
state = OUTPUT_STATE_STOPPED;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
DPRINTF(E_LOG, L_RAOP, "Bug! Unhandled state in raop_status()\n");
|
DPRINTF(E_LOG, L_RAOP, "Bug! Unhandled state in raop_status(): %d\n", rs->state);
|
||||||
state = OUTPUT_STATE_FAILED;
|
state = OUTPUT_STATE_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1956,6 +1967,9 @@ session_teardown(struct raop_session *rs, const char *log_caller)
|
|||||||
deferred_session_failure(rs);
|
deferred_session_failure(rs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change state immediately so we won't write any more to the device
|
||||||
|
rs->state = RAOP_STATE_TEARDOWN;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3514,6 +3528,8 @@ raop_startup_cancel(struct raop_session *rs)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rs->state = RAOP_STATE_TEARDOWN;
|
||||||
|
|
||||||
ret = raop_send_req_teardown(rs, raop_cb_startup_cancel, "startup_cancel");
|
ret = raop_send_req_teardown(rs, raop_cb_startup_cancel, "startup_cancel");
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
session_failure(rs);
|
session_failure(rs);
|
||||||
@ -3759,7 +3775,7 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg)
|
|||||||
token = strtok_r(token, ";=", &ptr);
|
token = strtok_r(token, ";=", &ptr);
|
||||||
while (token)
|
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)
|
if (strcmp(token, "server_port") == 0)
|
||||||
{
|
{
|
||||||
@ -4693,7 +4709,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");
|
ret = raop_send_req_options(rs, raop_cb_startup_options, "device_start");
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
return 0;
|
return 1;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv6\n");
|
DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv6\n");
|
||||||
@ -4719,7 +4735,7 @@ raop_device_start_generic(struct output_device *device, int callback_id, bool on
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -4741,7 +4757,9 @@ raop_device_stop(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
rs->callback_id = callback_id;
|
rs->callback_id = callback_id;
|
||||||
|
|
||||||
return session_teardown(rs, "device_stop");
|
session_teardown(rs, "device_stop");
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -4759,7 +4777,7 @@ raop_device_flush(struct output_device *device, int callback_id)
|
|||||||
|
|
||||||
rs->callback_id = callback_id;
|
rs->callback_id = callback_id;
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
670
src/player.c
670
src/player.c
File diff suppressed because it is too large
Load Diff
@ -111,6 +111,9 @@ player_speaker_prevent_playback_set(uint64_t id, bool prevent_playback);
|
|||||||
int
|
int
|
||||||
player_speaker_busy_set(uint64_t id, bool busy);
|
player_speaker_busy_set(uint64_t id, bool busy);
|
||||||
|
|
||||||
|
void
|
||||||
|
player_speaker_resurrect(void *arg);
|
||||||
|
|
||||||
int
|
int
|
||||||
player_playback_start(void);
|
player_playback_start(void);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user