mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-04 18:36:02 -05:00
[outputs] Add a 10 sec stop timer + drop playback_stop()
In the output implementations playback_stop() was somewhat redundant, since device_stop() does the same. The timer should make sure that we always close outputs (previously they were in some cases kept open). The commit also includes some renaming.
This commit is contained in:
parent
e3d39cff9b
commit
b8e0280567
160
src/outputs.c
160
src/outputs.c
@ -74,9 +74,15 @@ static struct output_definition *outputs[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
// 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.
|
||||
// (value is in seconds)
|
||||
#define OUTPUTS_STOP_TIMEOUT 10
|
||||
|
||||
#define OUTPUTS_MAX_CALLBACKS 64
|
||||
|
||||
struct outputs_callback_queue
|
||||
struct outputs_callback_register
|
||||
{
|
||||
output_status_cb cb;
|
||||
struct output_device *device;
|
||||
@ -89,9 +95,6 @@ struct outputs_callback_queue
|
||||
enum output_device_state state;
|
||||
};
|
||||
|
||||
struct outputs_callback_queue outputs_cb_queue[OUTPUTS_MAX_CALLBACKS];
|
||||
struct event *outputs_deferredev;
|
||||
|
||||
struct output_quality_subscription
|
||||
{
|
||||
int count;
|
||||
@ -99,13 +102,31 @@ struct output_quality_subscription
|
||||
struct encode_ctx *encode_ctx;
|
||||
};
|
||||
|
||||
static struct outputs_callback_register outputs_cb_register[OUTPUTS_MAX_CALLBACKS];
|
||||
static struct event *outputs_deferredev;
|
||||
static struct timeval outputs_stop_timeout = { OUTPUTS_STOP_TIMEOUT, 0 };
|
||||
|
||||
// Last element is a zero terminator
|
||||
static struct output_quality_subscription output_quality_subscriptions[OUTPUTS_MAX_QUALITY_SUBSCRIPTIONS + 1];
|
||||
static bool output_got_new_subscription;
|
||||
static bool outputs_got_new_subscription;
|
||||
|
||||
|
||||
/* ------------------------------- MISC HELPERS ----------------------------- */
|
||||
|
||||
static output_status_cb
|
||||
callback_get(struct output_device *device)
|
||||
{
|
||||
int callback_id;
|
||||
|
||||
for (callback_id = 0; callback_id < ARRAY_SIZE(outputs_cb_register); callback_id++)
|
||||
{
|
||||
if (outputs_cb_register[callback_id].device == device)
|
||||
return outputs_cb_register[callback_id].cb;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
callback_remove(struct output_device *device)
|
||||
{
|
||||
@ -114,12 +135,12 @@ callback_remove(struct output_device *device)
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
for (callback_id = 0; callback_id < ARRAY_SIZE(outputs_cb_queue); callback_id++)
|
||||
for (callback_id = 0; callback_id < ARRAY_SIZE(outputs_cb_register); callback_id++)
|
||||
{
|
||||
if (outputs_cb_queue[callback_id].device == device)
|
||||
if (outputs_cb_register[callback_id].device == device)
|
||||
{
|
||||
DPRINTF(E_DBG, L_PLAYER, "Removing callback to %s, id %d\n", player_pmap(outputs_cb_queue[callback_id].cb), callback_id);
|
||||
memset(&outputs_cb_queue[callback_id], 0, sizeof(struct outputs_callback_queue));
|
||||
DPRINTF(E_DBG, L_PLAYER, "Removing callback to %s, id %d\n", player_pmap(outputs_cb_register[callback_id].cb), callback_id);
|
||||
memset(&outputs_cb_register[callback_id], 0, sizeof(struct outputs_callback_register));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,26 +158,26 @@ callback_add(struct output_device *device, output_status_cb cb)
|
||||
callback_remove(device);
|
||||
|
||||
// Find a free slot in the queue
|
||||
for (callback_id = 0; callback_id < ARRAY_SIZE(outputs_cb_queue); callback_id++)
|
||||
for (callback_id = 0; callback_id < ARRAY_SIZE(outputs_cb_register); callback_id++)
|
||||
{
|
||||
if (outputs_cb_queue[callback_id].cb == NULL)
|
||||
if (outputs_cb_register[callback_id].cb == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
if (callback_id == ARRAY_SIZE(outputs_cb_queue))
|
||||
if (callback_id == ARRAY_SIZE(outputs_cb_register))
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Output callback queue is full! (size is %d)\n", OUTPUTS_MAX_CALLBACKS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
outputs_cb_queue[callback_id].cb = cb;
|
||||
outputs_cb_queue[callback_id].device = device; // Don't dereference this later, it might become invalid!
|
||||
outputs_cb_register[callback_id].cb = cb;
|
||||
outputs_cb_register[callback_id].device = device; // Don't dereference this later, it might become invalid!
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "Registered callback to %s with id %d (device %p, %s)\n", player_pmap(cb), callback_id, device, device->name);
|
||||
|
||||
int active = 0;
|
||||
for (int i = 0; i < ARRAY_SIZE(outputs_cb_queue); i++)
|
||||
if (outputs_cb_queue[i].cb)
|
||||
for (int i = 0; i < ARRAY_SIZE(outputs_cb_register); i++)
|
||||
if (outputs_cb_register[i].cb)
|
||||
active++;
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "Number of active callbacks: %d\n", active);
|
||||
@ -172,19 +193,19 @@ deferred_cb(int fd, short what, void *arg)
|
||||
enum output_device_state state;
|
||||
int callback_id;
|
||||
|
||||
for (callback_id = 0; callback_id < ARRAY_SIZE(outputs_cb_queue); callback_id++)
|
||||
for (callback_id = 0; callback_id < ARRAY_SIZE(outputs_cb_register); callback_id++)
|
||||
{
|
||||
if (outputs_cb_queue[callback_id].ready)
|
||||
if (outputs_cb_register[callback_id].ready)
|
||||
{
|
||||
// Must copy before making callback, since you never know what the
|
||||
// callback might result in (could call back in)
|
||||
cb = outputs_cb_queue[callback_id].cb;
|
||||
state = outputs_cb_queue[callback_id].state;
|
||||
cb = outputs_cb_register[callback_id].cb;
|
||||
state = outputs_cb_register[callback_id].state;
|
||||
|
||||
// Will be NULL if the device has disappeared
|
||||
device = outputs_device_get(outputs_cb_queue[callback_id].device_id);
|
||||
device = outputs_device_get(outputs_cb_register[callback_id].device_id);
|
||||
|
||||
memset(&outputs_cb_queue[callback_id], 0, sizeof(struct outputs_callback_queue));
|
||||
memset(&outputs_cb_register[callback_id], 0, sizeof(struct outputs_callback_register));
|
||||
|
||||
// The device has left the building (stopped/failed), and the backend
|
||||
// is not using it any more
|
||||
@ -200,13 +221,22 @@ deferred_cb(int fd, short what, void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(outputs_cb_queue); i++)
|
||||
for (int i = 0; i < ARRAY_SIZE(outputs_cb_register); i++)
|
||||
{
|
||||
if (outputs_cb_queue[i].cb)
|
||||
DPRINTF(E_DBG, L_PLAYER, "%d. Active callback: %s\n", i, player_pmap(outputs_cb_queue[i].cb));
|
||||
if (outputs_cb_register[i].cb)
|
||||
DPRINTF(E_DBG, L_PLAYER, "%d. Active callback: %s\n", i, player_pmap(outputs_cb_register[i].cb));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stop_timer_cb(int fd, short what, void *arg)
|
||||
{
|
||||
struct output_device *device = arg;
|
||||
output_status_cb cb = callback_get(device);
|
||||
|
||||
outputs_device_stop(device, cb);
|
||||
}
|
||||
|
||||
static void
|
||||
device_stop_cb(struct output_device *device, enum output_device_state status)
|
||||
{
|
||||
@ -300,10 +330,10 @@ buffer_fill(struct output_buffer *obuf, void *buf, size_t bufsize, struct media_
|
||||
// The resampling/encoding (transcode) contexts work for a given input quality,
|
||||
// so if the quality changes we need to reset the contexts. We also do that if
|
||||
// we have received a subscription for a new quality.
|
||||
if (!quality_is_equal(quality, &obuf->data[0].quality) || output_got_new_subscription)
|
||||
if (!quality_is_equal(quality, &obuf->data[0].quality) || outputs_got_new_subscription)
|
||||
{
|
||||
encoding_reset(quality);
|
||||
output_got_new_subscription = false;
|
||||
outputs_got_new_subscription = false;
|
||||
}
|
||||
|
||||
// The first element of the output_buffer is always just the raw input data
|
||||
@ -468,7 +498,7 @@ outputs_quality_subscribe(struct media_quality *quality)
|
||||
quality->sample_rate, quality->bits_per_sample, quality->channels, output_quality_subscriptions[i].count);
|
||||
|
||||
// Better way of signaling this?
|
||||
output_got_new_subscription = true;
|
||||
outputs_got_new_subscription = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -515,7 +545,7 @@ outputs_cb(int callback_id, uint64_t device_id, enum output_device_state state)
|
||||
if (callback_id < 0)
|
||||
return;
|
||||
|
||||
if (!(callback_id < ARRAY_SIZE(outputs_cb_queue)) || !outputs_cb_queue[callback_id].cb)
|
||||
if (!(callback_id < ARRAY_SIZE(outputs_cb_register)) || !outputs_cb_register[callback_id].cb)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Bug! Output backend called us with an illegal callback id (%d)\n", callback_id);
|
||||
return;
|
||||
@ -523,9 +553,9 @@ outputs_cb(int callback_id, uint64_t device_id, enum output_device_state state)
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "Callback request received, id is %i\n", callback_id);
|
||||
|
||||
outputs_cb_queue[callback_id].ready = true;
|
||||
outputs_cb_queue[callback_id].device_id = device_id;
|
||||
outputs_cb_queue[callback_id].state = state;
|
||||
outputs_cb_register[callback_id].ready = true;
|
||||
outputs_cb_register[callback_id].device_id = device_id;
|
||||
outputs_cb_register[callback_id].state = state;
|
||||
event_active(outputs_deferredev, 0, 0);
|
||||
}
|
||||
|
||||
@ -558,6 +588,8 @@ outputs_device_add(struct output_device *add, bool new_deselect, int default_vol
|
||||
{
|
||||
device = add;
|
||||
|
||||
device->stop_timer = evtimer_new(evbase_player, stop_timer_cb, device);
|
||||
|
||||
keep_name = strdup(device->name);
|
||||
ret = db_speaker_get(device, device->id);
|
||||
if (ret < 0)
|
||||
@ -691,6 +723,19 @@ outputs_device_stop(struct output_device *device, output_status_cb cb)
|
||||
return outputs[device->type]->device_stop(device, callback_add(device, cb));
|
||||
}
|
||||
|
||||
int
|
||||
outputs_device_stop_delayed(struct output_device *device, output_status_cb cb)
|
||||
{
|
||||
if (outputs[device->type]->disabled || !outputs[device->type]->device_stop)
|
||||
return -1;
|
||||
|
||||
outputs[device->type]->device_cb_set(device, callback_add(device, cb));
|
||||
|
||||
event_add(device->stop_timer, &outputs_stop_timeout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
outputs_device_flush(struct output_device *device, output_status_cb cb)
|
||||
{
|
||||
@ -772,6 +817,9 @@ outputs_device_free(struct output_device *device)
|
||||
if (outputs[device->type]->device_free_extra)
|
||||
outputs[device->type]->device_free_extra(device);
|
||||
|
||||
if (device->stop_timer)
|
||||
event_free(device->stop_timer);
|
||||
|
||||
free(device->name);
|
||||
free(device->auth_key);
|
||||
free(device->v4_address);
|
||||
@ -780,21 +828,6 @@ outputs_device_free(struct output_device *device)
|
||||
free(device);
|
||||
}
|
||||
|
||||
void
|
||||
outputs_playback_stop(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; outputs[i]; i++)
|
||||
{
|
||||
if (outputs[i]->disabled)
|
||||
continue;
|
||||
|
||||
if (outputs[i]->playback_stop)
|
||||
outputs[i]->playback_stop();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
outputs_flush(output_status_cb cb)
|
||||
{
|
||||
@ -814,6 +847,39 @@ outputs_flush(output_status_cb cb)
|
||||
return count;
|
||||
}
|
||||
|
||||
int
|
||||
outputs_stop(output_status_cb cb)
|
||||
{
|
||||
struct output_device *device;
|
||||
int count = 0;
|
||||
int ret;
|
||||
|
||||
for (device = output_device_list; device; device = device->next)
|
||||
{
|
||||
if (!device->session)
|
||||
continue;
|
||||
|
||||
ret = outputs_device_stop(device, cb);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int
|
||||
outputs_stop_delayed_cancel(void)
|
||||
{
|
||||
struct output_device *device;
|
||||
|
||||
for (device = output_device_list; device; device = device->next)
|
||||
event_del(device->stop_timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
#define __OUTPUTS_H__
|
||||
|
||||
#include <time.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/buffer.h>
|
||||
#include "misc.h"
|
||||
|
||||
@ -137,6 +138,8 @@ struct output_device
|
||||
short v4_port;
|
||||
short v6_port;
|
||||
|
||||
struct event *stop_timer;
|
||||
|
||||
// Opaque pointers to device and session data
|
||||
void *extra_device_info;
|
||||
void *session;
|
||||
@ -220,9 +223,6 @@ struct output_definition
|
||||
// Free the private device data
|
||||
void (*device_free_extra)(struct output_device *device);
|
||||
|
||||
// Start/stop playback on devices that were started
|
||||
void (*playback_stop)(void);
|
||||
|
||||
// Write stream data to the output devices
|
||||
void (*write)(struct output_buffer *buffer);
|
||||
|
||||
@ -281,6 +281,9 @@ outputs_device_start(struct output_device *device, output_status_cb cb);
|
||||
int
|
||||
outputs_device_stop(struct output_device *device, output_status_cb cb);
|
||||
|
||||
int
|
||||
outputs_device_stop_delayed(struct output_device *device, output_status_cb cb);
|
||||
|
||||
int
|
||||
outputs_device_flush(struct output_device *device, output_status_cb cb);
|
||||
|
||||
@ -302,12 +305,15 @@ outputs_device_cb_set(struct output_device *device, output_status_cb cb);
|
||||
void
|
||||
outputs_device_free(struct output_device *device);
|
||||
|
||||
void
|
||||
outputs_playback_stop(void);
|
||||
|
||||
int
|
||||
outputs_flush(output_status_cb cb);
|
||||
|
||||
int
|
||||
outputs_stop(output_status_cb cb);
|
||||
|
||||
int
|
||||
outputs_stop_delayed_cancel(void);
|
||||
|
||||
void
|
||||
outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts);
|
||||
|
||||
|
@ -967,22 +967,6 @@ alsa_device_cb_set(struct output_device *device, int callback_id)
|
||||
as->callback_id = callback_id;
|
||||
}
|
||||
|
||||
static void
|
||||
alsa_playback_stop(void)
|
||||
{
|
||||
struct alsa_session *as;
|
||||
struct alsa_session *next;
|
||||
|
||||
for (as = sessions; as; as = next)
|
||||
{
|
||||
next = as->next;
|
||||
snd_pcm_drop(as->hdl);
|
||||
|
||||
as->state = OUTPUT_STATE_STOPPED;
|
||||
alsa_status(as); // Will stop the session
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
alsa_write(struct output_buffer *obuf)
|
||||
{
|
||||
@ -1061,6 +1045,5 @@ struct output_definition output_alsa =
|
||||
.device_probe = alsa_device_probe,
|
||||
.device_volume_set = alsa_device_volume_set,
|
||||
.device_cb_set = alsa_device_cb_set,
|
||||
.playback_stop = alsa_playback_stop,
|
||||
.write = alsa_write,
|
||||
};
|
||||
|
@ -2062,20 +2062,6 @@ cast_device_volume_set(struct output_device *device, int callback_id)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
cast_playback_stop(void)
|
||||
{
|
||||
struct cast_session *cs;
|
||||
struct cast_session *next;
|
||||
|
||||
for (cs = cast_sessions; cs; cs = next)
|
||||
{
|
||||
next = cs->next;
|
||||
if (cs->state & CAST_STATE_F_MEDIA_CONNECTED)
|
||||
cast_session_shutdown(cs, CAST_STATE_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cast_write(struct output_buffer *obuf)
|
||||
{
|
||||
@ -2220,7 +2206,6 @@ struct output_definition output_cast =
|
||||
.device_flush = cast_device_flush,
|
||||
.device_cb_set = cast_device_cb_set,
|
||||
.device_volume_set = cast_device_volume_set,
|
||||
.playback_stop = cast_playback_stop,
|
||||
.write = cast_write,
|
||||
.init = cast_init,
|
||||
.deinit = cast_deinit,
|
||||
|
@ -183,18 +183,6 @@ dummy_device_cb_set(struct output_device *device, int callback_id)
|
||||
ds->callback_id = callback_id;
|
||||
}
|
||||
|
||||
static void
|
||||
dummy_playback_stop(void)
|
||||
{
|
||||
struct dummy_session *ds = sessions;
|
||||
|
||||
if (!sessions)
|
||||
return;
|
||||
|
||||
ds->state = OUTPUT_STATE_CONNECTED;
|
||||
dummy_status(ds);
|
||||
}
|
||||
|
||||
static int
|
||||
dummy_init(void)
|
||||
{
|
||||
@ -245,5 +233,4 @@ struct output_definition output_dummy =
|
||||
.device_probe = dummy_device_probe,
|
||||
.device_volume_set = dummy_device_volume_set,
|
||||
.device_cb_set = dummy_device_cb_set,
|
||||
.playback_stop = dummy_playback_stop,
|
||||
};
|
||||
|
@ -387,20 +387,6 @@ fifo_device_cb_set(struct output_device *device, int callback_id)
|
||||
fifo_session->callback_id = callback_id;
|
||||
}
|
||||
|
||||
static void
|
||||
fifo_playback_stop(void)
|
||||
{
|
||||
struct fifo_session *fifo_session = sessions;
|
||||
|
||||
if (!fifo_session)
|
||||
return;
|
||||
|
||||
free_buffer();
|
||||
|
||||
fifo_session->state = OUTPUT_STATE_CONNECTED;
|
||||
fifo_status(fifo_session);
|
||||
}
|
||||
|
||||
static void
|
||||
fifo_write(struct output_buffer *obuf)
|
||||
{
|
||||
@ -532,6 +518,5 @@ struct output_definition output_fifo =
|
||||
.device_probe = fifo_device_probe,
|
||||
.device_volume_set = fifo_device_volume_set,
|
||||
.device_cb_set = fifo_device_cb_set,
|
||||
.playback_stop = fifo_playback_stop,
|
||||
.write = fifo_write,
|
||||
};
|
||||
|
@ -771,7 +771,6 @@ pulse_device_stop(struct output_device *device, int callback_id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
pulse_device_flush(struct output_device *device, int callback_id)
|
||||
{
|
||||
@ -907,36 +906,6 @@ pulse_write(struct output_buffer *obuf)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pulse_playback_stop(void)
|
||||
{
|
||||
struct pulse_session *ps;
|
||||
pa_operation* o;
|
||||
|
||||
pa_threaded_mainloop_lock(pulse.mainloop);
|
||||
|
||||
for (ps = sessions; ps; ps = ps->next)
|
||||
{
|
||||
o = pa_stream_cork(ps->stream, 1, NULL, NULL);
|
||||
if (!o)
|
||||
{
|
||||
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not pause '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
|
||||
continue;
|
||||
}
|
||||
pa_operation_unref(o);
|
||||
|
||||
o = pa_stream_flush(ps->stream, NULL, NULL);
|
||||
if (!o)
|
||||
{
|
||||
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not flush '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
|
||||
continue;
|
||||
}
|
||||
pa_operation_unref(o);
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pulse.mainloop);
|
||||
}
|
||||
|
||||
static int
|
||||
pulse_init(void)
|
||||
{
|
||||
@ -1032,7 +1001,6 @@ struct output_definition output_pulse =
|
||||
.device_free_extra = pulse_device_free_extra,
|
||||
.device_cb_set = pulse_device_cb_set,
|
||||
.device_volume_set = pulse_device_volume_set,
|
||||
.playback_stop = pulse_playback_stop,
|
||||
.write = pulse_write,
|
||||
};
|
||||
|
||||
|
@ -2816,6 +2816,12 @@ raop_keep_alive_timer_cb(int fd, short what, void *arg)
|
||||
{
|
||||
struct raop_session *rs;
|
||||
|
||||
if (!raop_sessions)
|
||||
{
|
||||
event_del(keep_alive_timer);
|
||||
return;
|
||||
}
|
||||
|
||||
for (rs = raop_sessions; rs; rs = rs->next)
|
||||
{
|
||||
if (!(rs->state & RAOP_STATE_F_CONNECTED))
|
||||
@ -4850,17 +4856,6 @@ raop_device_free_extra(struct output_device *device)
|
||||
free(re);
|
||||
}
|
||||
|
||||
static void
|
||||
raop_playback_stop(void)
|
||||
{
|
||||
struct raop_session *rs;
|
||||
|
||||
evtimer_del(keep_alive_timer);
|
||||
|
||||
for (rs = raop_sessions; rs; rs = rs->next)
|
||||
session_teardown(rs, "playback_stop");
|
||||
}
|
||||
|
||||
static void
|
||||
raop_write(struct output_buffer *obuf)
|
||||
{
|
||||
@ -5069,7 +5064,6 @@ struct output_definition output_raop =
|
||||
.device_free_extra = raop_device_free_extra,
|
||||
.device_volume_set = raop_set_volume_one,
|
||||
.device_volume_to_pct = raop_volume_to_pct,
|
||||
.playback_stop = raop_playback_stop,
|
||||
.write = raop_write,
|
||||
.metadata_prepare = raop_metadata_prepare,
|
||||
.metadata_send = raop_metadata_send,
|
||||
|
161
src/player.c
161
src/player.c
@ -128,15 +128,9 @@
|
||||
|
||||
// When we pause, we keep the input open, but we can't do that forever. We must
|
||||
// think of the poor streaming servers, for instance. This timeout determines
|
||||
// how long we stay paused, before we go to a full stop.
|
||||
// how long we stay paused, before we close the inputs.
|
||||
// (value is in seconds)
|
||||
#define PLAYER_PAUSE_TIME_MAX 600
|
||||
|
||||
// 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.
|
||||
// (value is in seconds)
|
||||
#define PLAYER_STOP_TIME_MAX 10
|
||||
#define PLAYER_PAUSE_TIMEOUT 600
|
||||
|
||||
//#define DEBUG_PLAYER 1
|
||||
|
||||
@ -291,15 +285,14 @@ static int pb_timer_fd;
|
||||
timer_t pb_timer;
|
||||
#endif
|
||||
static struct event *pb_timer_ev;
|
||||
static struct event *player_abort_timeout_ev;
|
||||
static struct event *player_pause_timeout_ev;
|
||||
|
||||
// Time between ticks, i.e. time between when playback_cb() is invoked
|
||||
static struct timespec player_tick_interval;
|
||||
// Timer resolution
|
||||
static struct timespec player_timer_res;
|
||||
|
||||
static struct timeval player_pause_timeout = { PLAYER_PAUSE_TIME_MAX, 0 };
|
||||
static struct timeval player_stop_timeout = { PLAYER_STOP_TIME_MAX, 0 };
|
||||
static struct timeval player_pause_timeout = { PLAYER_PAUSE_TIMEOUT, 0 };
|
||||
|
||||
// PLAYER_WRITE_BEHIND_MAX converted to clock ticks
|
||||
static int pb_write_deficit_max;
|
||||
@ -324,10 +317,10 @@ static struct player_history *history;
|
||||
/* -------------------------------- Forwards -------------------------------- */
|
||||
|
||||
static void
|
||||
playback_abort(void);
|
||||
pb_abort(void);
|
||||
|
||||
static void
|
||||
playback_suspend(void);
|
||||
pb_suspend(void);
|
||||
|
||||
|
||||
/* ----------------------------- Volume helpers ----------------------------- */
|
||||
@ -452,7 +445,7 @@ scrobble_cb(void *arg)
|
||||
static void
|
||||
pause_timer_cb(int fd, short what, void *arg)
|
||||
{
|
||||
playback_abort();
|
||||
pb_abort();
|
||||
}
|
||||
|
||||
// Callback from the worker thread. Here the heavy lifting is done: updating the
|
||||
@ -988,7 +981,7 @@ event_play_end()
|
||||
{
|
||||
DPRINTF(E_DBG, L_PLAYER, "event_play_end()\n");
|
||||
|
||||
playback_abort();
|
||||
pb_abort();
|
||||
}
|
||||
|
||||
// Stuff to do when playback of current track ends
|
||||
@ -1157,13 +1150,13 @@ playback_cb(int fd, short what, void *arg)
|
||||
if (pb_write_recovery)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Permanent output delay detected (behind=%" PRIu64 ", max=%d), aborting\n", overrun, pb_write_deficit_max);
|
||||
playback_abort();
|
||||
pb_abort();
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
playback_suspend();
|
||||
pb_suspend();
|
||||
return;
|
||||
}
|
||||
else
|
||||
@ -1229,7 +1222,7 @@ 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);
|
||||
|
||||
playback_suspend();
|
||||
pb_suspend();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1372,7 +1365,7 @@ device_streaming_cb(struct output_device *device, enum output_device_state statu
|
||||
speaker_deselect_output(device);
|
||||
|
||||
if (output_sessions == 0)
|
||||
playback_abort();
|
||||
pb_abort();
|
||||
}
|
||||
else if (status == OUTPUT_STATE_STOPPED)
|
||||
{
|
||||
@ -1400,7 +1393,25 @@ device_command_cb(struct output_device *device, enum output_device_state status)
|
||||
if (status == OUTPUT_STATE_FAILED)
|
||||
device_streaming_cb(device, status);
|
||||
|
||||
// Used by playback_suspend - is basically the bottom half
|
||||
out:
|
||||
commands_exec_end(cmdbase, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
device_flush_cb(struct output_device *device, enum output_device_state status)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Output device disappeared before flush completion!\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_flush_cb (status %d)\n", outputs_name(device->type), status);
|
||||
|
||||
if (status == OUTPUT_STATE_FAILED)
|
||||
device_streaming_cb(device, status);
|
||||
|
||||
// Used by pb_suspend - is basically the bottom half
|
||||
if (player_flush_pending > 0)
|
||||
{
|
||||
player_flush_pending--;
|
||||
@ -1408,6 +1419,8 @@ device_command_cb(struct output_device *device, enum output_device_state status)
|
||||
input_buffer_full_cb(player_playback_start);
|
||||
}
|
||||
|
||||
outputs_device_stop_delayed(device, device_streaming_cb);
|
||||
|
||||
out:
|
||||
commands_exec_end(cmdbase, 0);
|
||||
}
|
||||
@ -1578,6 +1591,8 @@ player_pmap(void *p)
|
||||
return "device_streaming_cb";
|
||||
else if (p == device_command_cb)
|
||||
return "device_command_cb";
|
||||
else if (p == device_flush_cb)
|
||||
return "device_flush_cb";
|
||||
else if (p == device_shutdown_cb)
|
||||
return "device_shutdown_cb";
|
||||
else
|
||||
@ -1587,14 +1602,15 @@ player_pmap(void *p)
|
||||
/* ------------------------- Internal playback routines --------------------- */
|
||||
|
||||
static int
|
||||
playback_timer_start(void)
|
||||
pb_timer_start(void)
|
||||
{
|
||||
struct itimerspec tick;
|
||||
int ret;
|
||||
|
||||
// The pause timer will be active if we have recently paused, but now that
|
||||
// the playback loop has been kicked off, we no longer want that
|
||||
event_del(player_abort_timeout_ev);
|
||||
// The stop timers will be active if we have recently paused, but now that the
|
||||
// playback loop has been kicked off, we deactivate them
|
||||
event_del(player_pause_timeout_ev);
|
||||
outputs_stop_delayed_cancel();
|
||||
|
||||
ret = event_add(pb_timer_ev, NULL);
|
||||
if (ret < 0)
|
||||
@ -1623,7 +1639,7 @@ playback_timer_start(void)
|
||||
}
|
||||
|
||||
static int
|
||||
playback_timer_stop(void)
|
||||
pb_timer_stop(void)
|
||||
{
|
||||
struct itimerspec tick;
|
||||
int ret;
|
||||
@ -1648,7 +1664,7 @@ playback_timer_stop(void)
|
||||
|
||||
// Initiates the session and starts the input source
|
||||
static int
|
||||
playback_session_start(struct db_queue_item *queue_item, uint32_t seek_ms)
|
||||
pb_session_start(struct db_queue_item *queue_item, uint32_t seek_ms)
|
||||
{
|
||||
struct player_source *ps;
|
||||
int ret;
|
||||
@ -1673,45 +1689,45 @@ playback_session_start(struct db_queue_item *queue_item, uint32_t seek_ms)
|
||||
|
||||
// The input source is now open and ready, but we might actually be paused. So
|
||||
// we activate the below event in case the user never starts us again
|
||||
event_add(player_abort_timeout_ev, &player_pause_timeout);
|
||||
event_add(player_pause_timeout_ev, &player_pause_timeout);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Stops input source and deallocates pb_session content
|
||||
static void
|
||||
playback_session_stop(void)
|
||||
pb_session_stop(void)
|
||||
{
|
||||
source_stop();
|
||||
|
||||
session_stop();
|
||||
|
||||
playback_timer_stop();
|
||||
pb_timer_stop();
|
||||
|
||||
status_update(PLAY_STOPPED);
|
||||
}
|
||||
|
||||
static void
|
||||
playback_abort(void)
|
||||
pb_abort(void)
|
||||
{
|
||||
outputs_playback_stop();
|
||||
// Immediate stop of all outputs
|
||||
outputs_stop(device_streaming_cb);
|
||||
outputs_metadata_purge();
|
||||
|
||||
playback_session_stop();
|
||||
pb_session_stop();
|
||||
|
||||
if (!clear_queue_on_stop_disabled)
|
||||
db_queue_clear(0);
|
||||
|
||||
outputs_metadata_purge();
|
||||
}
|
||||
|
||||
// Temporarily suspends/resets playback, used when input buffer underruns or in
|
||||
// case of problems writing to the outputs
|
||||
static void
|
||||
playback_suspend(void)
|
||||
pb_suspend(void)
|
||||
{
|
||||
player_flush_pending = outputs_flush(device_command_cb);
|
||||
player_flush_pending = outputs_flush(device_flush_cb);
|
||||
|
||||
playback_timer_stop();
|
||||
pb_timer_stop();
|
||||
|
||||
status_update(PLAY_PAUSED);
|
||||
|
||||
@ -1818,17 +1834,13 @@ playback_stop(void *arg, int *retval)
|
||||
|
||||
// We may be restarting very soon, so we don't bring the devices to a full
|
||||
// stop just yet; this saves time when restarting, which is nicer for the user
|
||||
*retval = outputs_flush(device_command_cb);
|
||||
|
||||
// Stops the input
|
||||
playback_session_stop();
|
||||
|
||||
status_update(PLAY_STOPPED);
|
||||
|
||||
*retval = outputs_flush(device_flush_cb);
|
||||
outputs_metadata_purge();
|
||||
|
||||
// In case we aren't restarting soon we want to make a full stop
|
||||
event_add(player_abort_timeout_ev, &player_stop_timeout);
|
||||
// Stops the input
|
||||
pb_session_stop();
|
||||
|
||||
status_update(PLAY_STOPPED);
|
||||
|
||||
// We're async if we need to flush devices
|
||||
if (*retval > 0)
|
||||
@ -1837,12 +1849,21 @@ playback_stop(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
playback_abort(void *arg, int *retval)
|
||||
{
|
||||
pb_abort();
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
playback_start_bh(void *arg, int *retval)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = playback_timer_start();
|
||||
ret = pb_timer_start();
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
@ -1852,7 +1873,7 @@ playback_start_bh(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
|
||||
error:
|
||||
playback_abort();
|
||||
pb_abort();
|
||||
*retval = -1;
|
||||
return COMMAND_END;
|
||||
}
|
||||
@ -1914,7 +1935,7 @@ playback_start_item(void *arg, int *retval)
|
||||
}
|
||||
}
|
||||
|
||||
ret = playback_session_start(queue_item, seek_ms);
|
||||
ret = pb_session_start(queue_item, seek_ms);
|
||||
if (ret < 0)
|
||||
{
|
||||
*retval = -1;
|
||||
@ -2033,7 +2054,7 @@ playback_prev_bh(void *arg, int *retval)
|
||||
int ret;
|
||||
|
||||
// outputs_flush() in playback_pause() may have a caused a failure callback
|
||||
// from the output, which in streaming_cb() can cause playback_abort()
|
||||
// from the output, which in streaming_cb() can cause pb_abort()
|
||||
if (player_state == PLAY_STOPPED)
|
||||
{
|
||||
goto error;
|
||||
@ -2055,7 +2076,7 @@ playback_prev_bh(void *arg, int *retval)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = playback_session_start(queue_item, 0);
|
||||
ret = pb_session_start(queue_item, 0);
|
||||
free_queue_item(queue_item, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
@ -2070,7 +2091,7 @@ playback_prev_bh(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
|
||||
error:
|
||||
playback_abort();
|
||||
pb_abort();
|
||||
*retval = -1;
|
||||
return COMMAND_END;
|
||||
}
|
||||
@ -2083,7 +2104,7 @@ playback_next_bh(void *arg, int *retval)
|
||||
int id;
|
||||
|
||||
// outputs_flush() in playback_pause() may have a caused a failure callback
|
||||
// from the output, which in streaming_cb() can cause playback_abort()
|
||||
// from the output, which in streaming_cb() can cause pb_abort()
|
||||
if (player_state == PLAY_STOPPED)
|
||||
{
|
||||
goto error;
|
||||
@ -2108,7 +2129,7 @@ playback_next_bh(void *arg, int *retval)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = playback_session_start(queue_item, 0);
|
||||
ret = pb_session_start(queue_item, 0);
|
||||
free_queue_item(queue_item, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
@ -2123,7 +2144,7 @@ playback_next_bh(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
|
||||
error:
|
||||
playback_abort();
|
||||
pb_abort();
|
||||
*retval = -1;
|
||||
return COMMAND_END;
|
||||
}
|
||||
@ -2136,7 +2157,7 @@ playback_seek_bh(void *arg, int *retval)
|
||||
int ret;
|
||||
|
||||
// outputs_flush() in playback_pause() may have a caused a failure callback
|
||||
// from the output, which in streaming_cb() can cause playback_abort()
|
||||
// from the output, which in streaming_cb() can cause pb_abort()
|
||||
if (player_state == PLAY_STOPPED)
|
||||
{
|
||||
goto error;
|
||||
@ -2149,7 +2170,7 @@ playback_seek_bh(void *arg, int *retval)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = playback_session_start(queue_item, cmdarg->intval);
|
||||
ret = pb_session_start(queue_item, cmdarg->intval);
|
||||
free_queue_item(queue_item, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
@ -2164,7 +2185,7 @@ playback_seek_bh(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
|
||||
error:
|
||||
playback_abort();
|
||||
pb_abort();
|
||||
*retval = -1;
|
||||
return COMMAND_END;
|
||||
}
|
||||
@ -2176,7 +2197,7 @@ playback_pause_bh(void *arg, int *retval)
|
||||
int ret;
|
||||
|
||||
// outputs_flush() in playback_pause() may have a caused a failure callback
|
||||
// from the output, which in streaming_cb() can cause playback_abort()
|
||||
// from the output, which in streaming_cb() can cause pb_abort()
|
||||
if (player_state == PLAY_STOPPED)
|
||||
{
|
||||
goto error;
|
||||
@ -2189,7 +2210,7 @@ playback_pause_bh(void *arg, int *retval)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = playback_session_start(queue_item, pb_session.playing_now->pos_ms);
|
||||
ret = pb_session_start(queue_item, pb_session.playing_now->pos_ms);
|
||||
free_queue_item(queue_item, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
@ -2205,7 +2226,7 @@ playback_pause_bh(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
|
||||
error:
|
||||
playback_abort();
|
||||
pb_abort();
|
||||
*retval = -1;
|
||||
return COMMAND_END;
|
||||
}
|
||||
@ -2225,10 +2246,9 @@ playback_pause(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
playback_timer_stop();
|
||||
|
||||
*retval = outputs_flush(device_command_cb);
|
||||
pb_timer_stop();
|
||||
|
||||
*retval = outputs_flush(device_flush_cb);
|
||||
outputs_metadata_purge();
|
||||
|
||||
// We're async if we need to flush devices
|
||||
@ -2859,6 +2879,15 @@ player_playback_stop(void)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
player_playback_abort(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, playback_abort, NULL, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
player_playback_pause(void)
|
||||
{
|
||||
@ -3258,7 +3287,7 @@ player_init(void)
|
||||
}
|
||||
|
||||
CHECK_NULL(L_PLAYER, evbase_player = event_base_new());
|
||||
CHECK_NULL(L_PLAYER, player_abort_timeout_ev = evtimer_new(evbase_player, pause_timer_cb, NULL));
|
||||
CHECK_NULL(L_PLAYER, player_pause_timeout_ev = evtimer_new(evbase_player, pause_timer_cb, NULL));
|
||||
#ifdef HAVE_TIMERFD
|
||||
CHECK_NULL(L_PLAYER, pb_timer_ev = event_new(evbase_player, pb_timer_fd, EV_READ | EV_PERSIST, playback_cb, NULL));
|
||||
#else
|
||||
@ -3317,7 +3346,7 @@ player_deinit(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
player_playback_stop();
|
||||
player_playback_abort();
|
||||
|
||||
#ifdef HAVE_TIMERFD
|
||||
close(pb_timer_fd);
|
||||
|
Loading…
x
Reference in New Issue
Block a user