+enum state_change_request_type {
+ /** Everybody is happy. */
+ CRT_NONE,
+ /** Stream must be repositioned (com_jmp(), com_ff()). */
+ CRT_REPOS,
+ /** New file should be loaded (com_next()). */
+ CRT_FILE_CHANGE,
+ /** Someone wants us for dead (com_quit()). */
+ CRT_TERM_RQ
+};
+
+struct play_task {
+ struct task *task;
+ /* A bit array of invalid files (those will be skipped). */
+ bool *invalid;
+ /* The file which is currently open. */
+ unsigned current_file;
+ /* When to update the status again. */
+ struct timeval next_update;
+
+ /* Root of the buffer tree for command and status output. */
+ struct btr_node *btrn;
+
+ /* The decoding machinery. */
+ struct receiver_node rn;
+ struct filter_node fn;
+ struct writer_node wn;
+
+ /* See comment to enum \ref 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;
+};
+
+typedef int (*play_cmd_handler_t)(struct lls_parse_result *lpr);
+struct play_command_info {
+ play_cmd_handler_t handler;
+};
+#define EXPORT_PLAY_CMD_HANDLER(_cmd) \
+ const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \
+ .handler = com_ ## _cmd \
+ };
+
+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};
+
+static struct sched sched = {.max_fileno = 0};
+static struct play_task play_task, *pt = &play_task;
+
+#define AFH_RECV_CMD (lls_cmd(LSG_RECV_CMD_CMD_AFH, recv_cmd_suite))
+#define AFH_RECV ((struct receiver *)lls_user_data(AFH_RECV_CMD))
+
+static unsigned *shuffle_map;
+
+static const char *get_playlist_file(unsigned idx)
+{
+ return lls_input(shuffle_map[idx], play_lpr);
+}
+
+static void handle_help_flags(void)
+{
+ char *help;
+
+ if (OPT_GIVEN(DETAILED_HELP))
+ help = lls_long_help(CMD_PTR);
+ else if (OPT_GIVEN(HELP))
+ help = lls_short_help(CMD_PTR);
+ else
+ return;
+ printf("%s\n", help);
+ free(help);
+ exit(EXIT_SUCCESS);
+}
+
+static void parse_config_or_die(int argc, char *argv[])
+{
+ const struct lls_command *cmd = CMD_PTR;
+ int i, ret, cf_argc;
+ char *cf, *errctx, **cf_argv;
+ struct lls_parse_result *cf_lpr, *merged_lpr;
+ unsigned num_kmas;
+ void *map;
+ size_t sz;
+
+ ret = lls(lls_parse(argc, argv, cmd, &play_lpr, &errctx));
+ if (ret < 0)
+ goto fail;
+ loglevel = OPT_UINT32_VAL(LOGLEVEL);
+ version_handle_flag("play", OPT_GIVEN(VERSION));
+ handle_help_flags();
+ if (OPT_GIVEN(CONFIG_FILE))
+ cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
+ else {
+ char *home = para_homedir();
+ cf = make_message("%s/.paraslash/play.conf", home);
+ free(home);
+ }
+ ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+ if (ret < 0) {
+ if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
+ goto free_cf;
+ if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
+ goto free_cf;
+ ret = 0;
+ goto free_cf;
+ }
+ ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
+ para_munmap(map, sz);
+ if (ret < 0)
+ goto free_cf;
+ cf_argc = ret;
+ ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
+ lls_free_argv(cf_argv);
+ if (ret < 0)
+ goto free_cf;
+ ret = lls(lls_merge(play_lpr, cf_lpr, cmd, &merged_lpr, &errctx));
+ lls_free_parse_result(cf_lpr, cmd);
+ if (ret < 0)
+ goto free_cf;
+ lls_free_parse_result(play_lpr, cmd);
+ play_lpr = merged_lpr;
+ loglevel = OPT_UINT32_VAL(LOGLEVEL);
+
+ ret = lls(lls_check_arg_count(play_lpr, 1, INT_MAX, &errctx));
+ if (ret < 0)
+ goto free_cf;
+ num_kmas = OPT_GIVEN(KEY_MAP);
+ for (i = 0; i < num_kmas; i++) {
+ const char *kma = lls_string_val(i, OPT_RESULT(KEY_MAP));
+ if (*kma && strchr(kma + 1, ':'))
+ continue;
+ PARA_EMERG_LOG("invalid key map arg: %s\n", kma);
+ goto free_cf;
+ }
+ ret = 1;
+free_cf:
+ free(cf);
+ if (ret >= 0)
+ return;
+ lls_free_parse_result(play_lpr, cmd);
+fail:
+ if (errctx)
+ PARA_EMERG_LOG("%s\n", errctx);
+ free(errctx);
+ PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+ exit(EXIT_FAILURE);
+}
+
+static char get_playback_state(void)