[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:
ejurgensen 2019-03-05 22:45:38 +01:00
parent e3d39cff9b
commit b8e0280567
9 changed files with 226 additions and 223 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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