+ /* See comment to enum state_change_request_type above */
+ enum state_change_request_type rq;
+ /* only relevant if rq == CRT_FILE_CHANGE */
+ unsigned next_file;
+ /*
+ bg: read lines at prompt, fg: display status and wait
+ for keystroke.
+ */
+ bool background;
+
+ /* We have the *intention* to play. Set by com_play(). */
+ bool playing;
+
+ /* as returned by afh_recv->open() */
+ int audio_format_num;
+
+ /* retrieved via the btr exec mechanism */
+ long unsigned start_chunk;
+ long unsigned seconds;
+ long unsigned num_chunks;
+ char *afhi_txt;
+};
+
+/** Initialize the array of errors for para_play. */
+INIT_PLAY_ERRLISTS;
+
+/* Activate the afh receiver. */
+extern void afh_recv_init(struct receiver *r);
+#undef AFH_RECEIVER
+/** Initialization code for a receiver struct. */
+#define AFH_RECEIVER {.name = "afh", .init = afh_recv_init},
+/** This expands to the array of all receivers. */
+DEFINE_RECEIVER_ARRAY;
+
+static int loglevel = LL_WARNING;
+
+/** The log function which writes log messages to stderr. */
+INIT_STDERR_LOGGING(loglevel);
+
+char *stat_item_values[NUM_STAT_ITEMS] = {NULL};
+
+/** Iterate over all files in the playlist. */
+#define FOR_EACH_PLAYLIST_FILE(i) for (i = 0; i < conf.inputs_num; i++)
+static struct play_args_info conf;
+
+static struct sched sched = {.max_fileno = 0};
+static struct play_task play_task;
+static struct receiver *afh_recv;
+
+static void check_afh_receiver_or_die(void)
+{
+ int i;
+
+ FOR_EACH_RECEIVER(i) {
+ struct receiver *r = receivers + i;
+ if (strcmp(r->name, "afh"))
+ continue;
+ afh_recv = r;
+ return;
+ }
+ PARA_EMERG_LOG("fatal: afh receiver not found\n");
+ exit(EXIT_FAILURE);
+}
+
+/** 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)