+/** Description to be included in the --detailed-help output. */
+#define PP_DESC \
+"para_play is a command line audio player.\n" \
+"\n" \
+"It operates either in command mode or in insert mode. In insert mode it\n" \
+"presents a prompt and allows to enter para_play commands like stop, play, pause\n" \
+"etc. In command mode, the current audio file is shown and the program reads\n" \
+"single key strokes from stdin. Keys may be mapped to para_play commands.\n" \
+"Whenever a mapped key is pressed, the associated command is executed.\n" \
+
+__noreturn static void print_help_and_die(void)
+{
+ int d = conf.detailed_help_given;
+ const char **p = d? play_args_info_detailed_help
+ : play_args_info_help;
+
+// printf_or_die("%s\n\n", PLAY_CMDLINE_PARSER_PACKAGE "-"
+// PLAY_CMDLINE_PARSER_VERSION);
+
+ printf_or_die("%s\n\n", play_args_info_usage);
+ if (d)
+ printf_or_die("%s\n", PP_DESC);
+ for (; *p; p++)
+ printf_or_die("%s\n", *p);
+ exit(0);
+}
+
+static void parse_config_or_die(int argc, char *argv[])
+{
+ int i, ret;
+ char *config_file;
+ struct play_cmdline_parser_params params = {
+ .override = 0,
+ .initialize = 1,
+ .check_required = 0,
+ .check_ambiguity = 0,
+ .print_errors = 1
+ };
+
+ if (play_cmdline_parser_ext(argc, argv, &conf, ¶ms))
+ exit(EXIT_FAILURE);
+ HANDLE_VERSION_FLAG("play", conf);
+ if (conf.help_given || conf.detailed_help_given)
+ print_help_and_die();
+ loglevel = get_loglevel_by_name(conf.loglevel_arg);
+ if (conf.config_file_given)
+ config_file = para_strdup(conf.config_file_arg);
+ else {
+ char *home = para_homedir();
+ config_file = make_message("%s/.paraslash/play.conf", home);
+ free(home);
+ }
+ ret = file_exists(config_file);
+ if (conf.config_file_given && !ret) {
+ PARA_EMERG_LOG("can not read config file %s\n", config_file);
+ goto err;
+ }
+ if (ret) {
+ params.initialize = 0;
+ params.check_required = 1;
+ play_cmdline_parser_config_file(config_file, &conf, ¶ms);
+ }
+ for (i = 0; i < conf.key_map_given; i++) {
+ char *s = strchr(conf.key_map_arg[i] + 1, ':');
+ if (s)
+ continue;
+ PARA_EMERG_LOG("invalid key map arg: %s\n", conf.key_map_arg[i]);
+ goto err;
+ }
+ free(config_file);
+ return;
+err:
+ free(config_file);
+ exit(EXIT_FAILURE);
+}
+
+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)