+static char get_playback_state(struct play_task *pt)
+{
+ switch (pt->rq) {
+ case CRT_NONE: return pt->playing? 'P' : 'U';
+ case CRT_REPOS: return 'R';
+ case CRT_FILE_CHANGE: return 'F';
+ case CRT_TERM_RQ: return 'X';
+ }
+ assert(false);
+};
+
+static long unsigned get_play_time(struct play_task *pt)
+{
+ char state = get_playback_state(pt);
+ long unsigned result;
+
+ if (state != 'P' && state != 'U')
+ return 0;
+ if (pt->num_chunks == 0 || pt->seconds == 0)
+ return 0;
+ /* where the stream started (in seconds) */
+ result = pt->start_chunk * pt->seconds / pt->num_chunks;
+ if (pt->wn.btrn) { /* Add the uptime of the writer node */
+ struct timeval diff = {.tv_sec = 0}, wstime;
+ btr_get_node_start(pt->wn.btrn, &wstime);
+ if (wstime.tv_sec > 0)
+ tv_diff(now, &wstime, &diff);
+ result += diff.tv_sec;
+ }
+ result = PARA_MIN(result, pt->seconds);
+ result = PARA_MAX(result, 0UL);
+ return result;
+}
+
+static void wipe_receiver_node(struct play_task *pt)
+{
+ PARA_NOTICE_LOG("cleaning up receiver node\n");
+ btr_remove_node(&pt->rn.btrn);
+ afh_recv->close(&pt->rn);
+ afh_recv->free_config(pt->rn.conf);
+ memset(&pt->rn, 0, sizeof(struct receiver_node));
+}
+
+/* returns: 0 not eof, 1: eof, < 0: fatal error. */
+static int get_playback_error(struct play_task *pt)
+{
+ int err = pt->wn.task.error;
+
+ if (err >= 0)
+ return 0;
+ if (pt->fn.task.error >= 0)
+ return 0;
+ if (pt->rn.task.error >= 0)
+ return 0;
+ if (err == -E_BTR_EOF || err == -E_RECV_EOF || err == -E_EOF
+ || err == -E_WRITE_COMMON_EOF)
+ return 1;
+ return err;
+}
+
+static int eof_cleanup(struct play_task *pt)
+{
+ struct writer *w = writers + DEFAULT_WRITER;
+ struct filter *decoder = filters + pt->fn.filter_num;
+ int ret;
+
+ ret = get_playback_error(pt);
+ if (ret == 0)
+ return ret;
+ PARA_NOTICE_LOG("cleaning up wn/fn nodes\n");
+ w->close(&pt->wn);
+ btr_remove_node(&pt->wn.btrn);
+ w->free_config(pt->wn.conf);
+ memset(&pt->wn, 0, sizeof(struct writer_node));
+
+ decoder->close(&pt->fn);
+ btr_remove_node(&pt->fn.btrn);
+ free(pt->fn.conf);
+ memset(&pt->fn, 0, sizeof(struct filter_node));
+
+ btr_remove_node(&pt->rn.btrn);
+ /*
+ * On eof (ret > 0), we do not wipe the receiver node struct until a
+ * new file is loaded because we still need it for jumping around when
+ * paused.
+ */
+ if (ret < 0)
+ wipe_receiver_node(pt);
+ return ret;
+}
+
+static int shuffle_compare(__a_unused const void *a, __a_unused const void *b)
+{
+ return para_random(100) - 50;
+}
+
+static void shuffle(char **base, size_t num)
+{
+ srandom(now->tv_sec);
+ qsort(base, num, sizeof(char *), shuffle_compare);
+}
+
+static struct btr_node *new_recv_btrn(struct receiver_node *rn)
+{
+ return btr_new_node(&(struct btr_node_description)
+ EMBRACE(.name = afh_recv->name, .context = rn,
+ .handler = afh_recv->execute));
+}
+
+static int open_new_file(struct play_task *pt)
+{
+ int ret;
+ char *tmp, *path = conf.inputs[pt->next_file], *afh_recv_conf[] =
+ {"play", "-f", path, "-b", "0", NULL};
+
+ PARA_NOTICE_LOG("next file: %s\n", path);
+ wipe_receiver_node(pt);
+ pt->start_chunk = 0;
+ pt->rn.btrn = new_recv_btrn(&pt->rn);
+ pt->rn.conf = afh_recv->parse_config(ARRAY_SIZE(afh_recv_conf) - 1,
+ afh_recv_conf);
+ assert(pt->rn.conf);
+ pt->rn.receiver = afh_recv;
+ ret = afh_recv->open(&pt->rn);
+ if (ret < 0) {
+ PARA_ERROR_LOG("could not open %s: %s\n", path,
+ para_strerror(-ret));
+ goto fail;
+ }
+ pt->audio_format_num = ret;
+ free(pt->afhi_txt);
+ ret = btr_exec_up(pt->rn.btrn, "afhi", &pt->afhi_txt);
+ if (ret < 0)
+ pt->afhi_txt = make_message("[afhi command failed]\n");
+ ret = btr_exec_up(pt->rn.btrn, "seconds_total", &tmp);
+ if (ret < 0)
+ pt->seconds = 1;
+ else {
+ int32_t x;
+ ret = para_atoi32(tmp, &x);
+ pt->seconds = ret < 0? 1 : x;
+ free(tmp);
+ tmp = NULL;
+ }
+ ret = btr_exec_up(pt->rn.btrn, "chunks_total", &tmp);
+ if (ret < 0)
+ pt->num_chunks = 1;
+ else {
+ int32_t x;
+ ret = para_atoi32(tmp, &x);
+ pt->num_chunks = ret < 0? 1 : x;
+ free(tmp);
+ tmp = NULL;
+ }
+ pt->rn.task.pre_select = afh_recv->pre_select;
+ if (afh_recv->new_post_select) {
+ pt->rn.task.new_post_select = afh_recv->new_post_select;
+ pt->rn.task.post_select = NULL;
+ } else {
+ pt->rn.task.post_select = NULL;
+ pt->rn.task.new_post_select = afh_recv->new_post_select;
+ }
+ sprintf(pt->rn.task.status, "%s receiver node", afh_recv->name);
+ return 1;
+fail:
+ wipe_receiver_node(pt);
+ return ret;
+}
+
+static int load_file(struct play_task *pt)
+{
+ const char *af;
+ char *tmp;
+ int ret;
+ struct filter *decoder;
+
+ btr_remove_node(&pt->rn.btrn);
+ if (!pt->rn.receiver || pt->next_file != pt->current_file) {
+ ret = open_new_file(pt);
+ if (ret < 0)
+ return ret;
+ } else {
+ char buf[20];
+ pt->rn.btrn = new_recv_btrn(&pt->rn);
+ sprintf(buf, "repos %lu", pt->start_chunk);
+ ret = btr_exec_up(pt->rn.btrn, buf, &tmp);
+ if (ret < 0)
+ PARA_CRIT_LOG("repos failed: %s\n", para_strerror(-ret));
+ freep(&tmp);
+ }
+ if (!pt->playing)
+ return 0;
+ /* set up decoding filter */
+ af = audio_format_name(pt->audio_format_num);
+ tmp = make_message("%sdec", af);
+ ret = check_filter_arg(tmp, &pt->fn.conf);
+ freep(&tmp);
+ if (ret < 0)
+ goto fail;
+ pt->fn.filter_num = ret;
+ decoder = filters + ret;
+ pt->fn.task.pre_select = decoder->pre_select;
+ if (decoder->new_post_select) {
+ pt->fn.task.new_post_select = decoder->new_post_select;
+ pt->fn.task.post_select = NULL;
+ } else {
+ pt->fn.task.new_post_select = NULL;
+ pt->fn.task.post_select = decoder->post_select;
+ }
+ sprintf(pt->fn.task.status, "%s decoder", af);
+ pt->fn.btrn = btr_new_node(&(struct btr_node_description)
+ EMBRACE(.name = decoder->name, .parent = pt->rn.btrn,
+ .handler = decoder->execute, .context = &pt->fn));
+ decoder->open(&pt->fn);
+
+ /* setup default writer */
+ pt->wn.conf = check_writer_arg_or_die(NULL, &pt->wn.writer_num);
+ pt->wn.task.error = 0;
+
+ /* success, register tasks */
+ register_task(&sched, &pt->rn.task);
+ register_task(&sched, &pt->fn.task);
+ register_writer_node(&pt->wn, pt->fn.btrn, &sched);
+ return 1;
+fail:
+ wipe_receiver_node(pt);
+ return ret;
+}
+
+static int next_valid_file(struct play_task *pt)
+{
+ int i, j = pt->current_file;
+
+ FOR_EACH_PLAYLIST_FILE(i) {
+ j = (j + 1) % conf.inputs_num;
+ if (!pt->invalid[j])
+ return j;
+ }
+ return -E_NO_VALID_FILES;
+}
+
+static int load_next_file(struct play_task *pt)
+{
+ int ret;
+
+again:
+ if (pt->rq == CRT_NONE || pt->rq == CRT_FILE_CHANGE) {
+ pt->start_chunk = 0;
+ ret = next_valid_file(pt);
+ if (ret < 0)
+ return ret;
+ pt->next_file = ret;
+ } else if (pt->rq == CRT_REPOS)
+ pt->next_file = pt->current_file;
+ ret = load_file(pt);
+ if (ret < 0) {
+ pt->invalid[pt->next_file] = true;
+ pt->rq = CRT_NONE;
+ goto again;
+ }
+ pt->current_file = pt->next_file;
+ pt->rq = CRT_NONE;
+ return ret;
+}
+
+static void kill_stream(struct play_task *pt)
+{
+ task_notify(&pt->wn.task, E_EOF);
+}
+
+#ifdef HAVE_READLINE
+
+/* only called from com_prev(), nec. only if we have readline */
+static int previous_valid_file(struct play_task *pt)
+{
+ int i, j = pt->current_file;
+
+ FOR_EACH_PLAYLIST_FILE(i) {
+ j--;
+ if (j < 0)
+ j = conf.inputs_num - 1;
+ if (!pt->invalid[j])
+ return j;
+ }
+ return -E_NO_VALID_FILES;
+}
+
+#include "interactive.h"
+