+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");
+ task_reap(&pt->wn.task);
+ w->close(&pt->wn);
+ btr_remove_node(&pt->wn.btrn);
+ w->free_config(pt->wn.conf);
+ memset(&pt->wn, 0, sizeof(struct writer_node));
+
+ task_reap(&pt->fn.task);
+ decoder->close(&pt->fn);
+ btr_remove_node(&pt->fn.btrn);
+ free(pt->fn.conf);
+ memset(&pt->fn, 0, sizeof(struct filter_node));
+
+ task_reap(&pt->rn.task);
+ 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(time(NULL));
+ 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;
+ }
+ return 1;
+fail:
+ wipe_receiver_node(pt);
+ return ret;
+}
+
+static int load_file(struct play_task *pt)
+{
+ const char *af;
+ char *tmp, buf[20];
+ 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 {
+ 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.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);
+
+ /* success, register tasks */
+ pt->rn.task = task_register(
+ &(struct task_info) {
+ .name = afh_recv->name,
+ .pre_select = afh_recv->pre_select,
+ .post_select = afh_recv->post_select,
+ .context = &pt->rn
+ }, &sched);
+ sprintf(buf, "%s decoder", af);
+ pt->fn.task = task_register(
+ &(struct task_info) {
+ .name = buf,
+ .pre_select = decoder->pre_select,
+ .post_select = decoder->post_select,
+ .context = &pt->fn
+ }, &sched);
+ 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->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)
+{
+ if (pt->wn.task)
+ 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"
+