X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=play.c;h=86edc4d4e4f47bc1bd38197ed5982e97df9f72cf;hp=e608c6cb4364d9da3135fa08b0f6d95d39314cc3;hb=HEAD;hpb=f3dc51fae7365722ccbefd737eac293e12feb94a diff --git a/play.c b/play.c index e608c6cb..bd183b6b 100644 --- a/play.c +++ b/play.c @@ -1,22 +1,20 @@ -/* - * Copyright (C) 2012 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2012 Andre Noll , see file COPYING. */ /** \file play.c Paraslash's standalone player. */ #include #include -#include #include +#include "recv_cmd.lsg.h" +#include "filter_cmd.lsg.h" +#include "play_cmd.lsg.h" +#include "write_cmd.lsg.h" +#include "play.lsg.h" #include "para.h" +#include "lsu.h" #include "list.h" -#include "play.cmdline.h" -#include "play_cmd.lsg.h" #include "error.h" -#include "ggo.h" #include "buffer_tree.h" #include "version.h" #include "string.h" @@ -25,27 +23,25 @@ #include "afh.h" #include "recv.h" #include "write.h" -#include "write_common.h" #include "fd.h" -/** - * Besides playback tasks which correspond to the receiver/filter/writer nodes, - * para_play creates two further tasks: The play task and the i9e task. It is - * important whether a function can be called in the context of para_play or - * i9e or both. As a rule, all command handlers are called only in i9e context via - * the line handler (input mode) or the key handler (command mode) below. - * - * Playlist handling is done exclusively in play context. - */ - /** Array of error strings. */ DEFINE_PARA_ERRLIST; +static struct lls_parse_result *play_lpr; + +#define CMD_PTR (lls_cmd(0, play_suite)) +#define OPT_RESULT(_name) \ + (lls_opt_result(LSG_PLAY_PARA_PLAY_OPT_ ## _name, play_lpr)) +#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name))) +#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name))) +#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name))) + /** * Describes a request to change the state of para_play. * * There is only one variable of this type: \a rq of the global play task - * structure. Command handlers only set this variable and the post_select() + * structure. Command handlers only set this variable and the post_monitor() * function of the play task investigates its value during each iteration of * the scheduler run and performs the actual work. */ @@ -77,7 +73,7 @@ struct play_task { struct filter_node fn; struct writer_node wn; - /* See comment to enum state_change_request_type above */ + /* 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; @@ -100,23 +96,10 @@ struct play_task { char *afhi_txt; }; -typedef int (*play_cmd_handler_t)(struct play_task *pt, - struct lls_parse_result *lpr); +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 \ - }; - -/* 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; @@ -125,90 +108,71 @@ 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; +static struct play_task play_task, *pt = &play_task; -static struct sched sched = {.max_fileno = 0}; -static struct play_task play_task; -static struct receiver *afh_recv; +#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 void check_afh_receiver_or_die(void) -{ - int i; +static unsigned *shuffle_map; - 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); +static const char *get_playlist_file(unsigned idx) +{ + return lls_input(shuffle_map[idx], play_lpr); } -__noreturn static void print_help_and_die(void) +static void handle_help_flags(void) { - struct ggo_help help = DEFINE_GGO_HELP(play); - unsigned flags = conf.detailed_help_given? - GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS; + char *help; - ggo_print_help(&help, flags); - printf("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS); - exit(0); + if (OPT_GIVEN(DETAILED_HELP)) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP) || lls_num_inputs(play_lpr) == 0) + 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[]) { 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 - }; + unsigned num_kmas; + char *errctx; - play_cmdline_parser_ext(argc, argv, &conf, ¶ms); - loglevel = get_loglevel_by_name(conf.loglevel_arg); - version_handle_flag("play", conf.version_given); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); - 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; + ret = lls(lls_parse(argc, argv, CMD_PTR, &play_lpr, &errctx)); + if (ret < 0) { + if (errctx) + PARA_EMERG_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("failed to parse command line options: %s\n", + para_strerror(-ret)); + exit(EXIT_FAILURE); } - if (ret) { - params.initialize = 0; - params.check_required = 1; - play_cmdline_parser_config_file(config_file, &conf, ¶ms); - loglevel = get_loglevel_by_name(conf.loglevel_arg); + loglevel = OPT_UINT32_VAL(LOGLEVEL); + version_handle_flag("play", OPT_GIVEN(VERSION)); + handle_help_flags(); /* also handles the zero-arg case */ + ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE), + "play.conf", &play_lpr, CMD_PTR, play_suite, 0 /* flags */); + if (ret < 0) { + PARA_EMERG_LOG("failed to parse config file: %s\n", + para_strerror(-ret)); + exit(EXIT_FAILURE); } - for (i = 0; i < conf.key_map_given; i++) { - char *kma = conf.key_map_arg[i]; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + 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 err; + exit(EXIT_FAILURE); } - free(config_file); - return; -err: - free(config_file); - exit(EXIT_FAILURE); } -static char get_playback_state(struct play_task *pt) +static char get_playback_state(void) { switch (pt->rq) { case CRT_NONE: return pt->playing? 'P' : 'U'; @@ -219,40 +183,41 @@ static char get_playback_state(struct play_task *pt) assert(false); }; -static long unsigned get_play_time(struct play_task *pt) +/* returns number of milliseconds */ +static long unsigned get_play_time(void) { - char state = get_playback_state(pt); + char state = get_playback_state(); 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; + /* where the stream started (in milliseconds) */ + result = 1000ULL * 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 += tv2ms(&diff); } - result = PARA_MIN(result, pt->seconds); + result = PARA_MIN(result, pt->seconds * 1000); result = PARA_MAX(result, 0UL); return result; } -static void wipe_receiver_node(struct play_task *pt) +static void wipe_receiver_node(void) { 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); + AFH_RECV->close(&pt->rn); + lls_free_parse_result(pt->rn.lpr, AFH_RECV_CMD); 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) +static int get_playback_error(void) { int err; @@ -265,32 +230,33 @@ static int get_playback_error(struct play_task *pt) return 0; if (task_status(pt->rn.task) >= 0) return 0; - if (err == -E_BTR_EOF || err == -E_RECV_EOF || err == -E_EOF - || err == -E_WRITE_COMMON_EOF) + if (err == -E_EOF) return 1; return err; } -static int eof_cleanup(struct play_task *pt) +static int eof_cleanup(void) { - struct writer *w = writers + DEFAULT_WRITER; - const struct filter *decoder = filter_get(pt->fn.filter_num); + const struct filter *decoder; + const struct writer *w = writer_get(-1); /* default writer */ int ret; - ret = get_playback_error(pt); + ret = get_playback_error(); 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); + lls_free_parse_result(pt->wn.lpr, WRITE_CMD(pt->wn.wid)); memset(&pt->wn, 0, sizeof(struct writer_node)); + decoder = filter_get(pt->fn.filter_num); task_reap(&pt->fn.task); if (decoder->close) decoder->close(&pt->fn); btr_remove_node(&pt->fn.btrn); + lls_free_parse_result(pt->fn.lpr, FILTER_CMD(pt->fn.filter_num)); free(pt->fn.conf); memset(&pt->fn, 0, sizeof(struct filter_node)); @@ -302,7 +268,7 @@ static int eof_cleanup(struct play_task *pt) * paused. */ if (ret < 0) - wipe_receiver_node(pt); + wipe_receiver_node(); return ret; } @@ -311,34 +277,42 @@ 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) +static void init_shuffle_map(void) { + unsigned n, num_inputs = lls_num_inputs(play_lpr); + shuffle_map = arr_alloc(num_inputs, sizeof(unsigned)); + for (n = 0; n < num_inputs; n++) + shuffle_map[n] = n; + if (!OPT_GIVEN(RANDOMIZE)) + return; srandom(time(NULL)); - qsort(base, num, sizeof(char *), shuffle_compare); + qsort(shuffle_map, num_inputs, sizeof(unsigned), 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)); + EMBRACE(.name = lls_command_name(AFH_RECV_CMD), .context = rn, + .handler = AFH_RECV->execute)); } -static int open_new_file(struct play_task *pt) +static int open_new_file(void) { int ret; - char *tmp, *path = conf.inputs[pt->next_file], *afh_recv_conf[] = - {"play", "-f", path, "-b", "0", NULL}; + const char *path = get_playlist_file(pt->next_file); + char *tmp = para_strdup(path), *errctx; + char *argv[] = {"play", "-f", tmp, "-b", "0", NULL}; PARA_NOTICE_LOG("next file: %s\n", path); - wipe_receiver_node(pt); + wipe_receiver_node(); 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); + ret = lls(lls_parse(ARRAY_SIZE(argv) - 1, argv, AFH_RECV_CMD, + &pt->rn.lpr, &errctx)); + free(tmp); + assert(ret >= 0); + pt->rn.receiver = AFH_RECV; + ret = AFH_RECV->open(&pt->rn); if (ret < 0) { PARA_ERROR_LOG("could not open %s\n", path); goto fail; @@ -370,20 +344,21 @@ static int open_new_file(struct play_task *pt) } return 1; fail: - wipe_receiver_node(pt); + wipe_receiver_node(); return ret; } -static int load_file(struct play_task *pt) +static int load_file(void) { const char *af; char *tmp, buf[20]; int ret; const struct filter *decoder; + static struct lls_parse_result *filter_lpr, *writer_lpr; btr_remove_node(&pt->rn.btrn); if (!pt->rn.receiver || pt->next_file != pt->current_file) { - ret = open_new_file(pt); + ret = open_new_file(); if (ret < 0) return ret; } else { @@ -399,73 +374,84 @@ static int load_file(struct play_task *pt) /* set up decoding filter */ af = audio_format_name(pt->audio_format_num); tmp = make_message("%sdec", af); - PARA_INFO_LOG("decoder: %s\n", tmp); - ret = check_filter_arg(tmp, &pt->fn.conf); + ret = filter_setup(tmp, &pt->fn.conf, &filter_lpr); freep(&tmp); if (ret < 0) goto fail; pt->fn.filter_num = ret; + pt->fn.lpr = filter_lpr; decoder = filter_get(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)); + EMBRACE(.name = filter_name(pt->fn.filter_num), + .parent = pt->rn.btrn, .handler = decoder->execute, + .context = &pt->fn)); if (decoder->open) decoder->open(&pt->fn); PARA_INFO_LOG("buffer tree:\n"); btr_log_tree(pt->rn.btrn, LL_INFO); /* setup default writer */ - pt->wn.conf = check_writer_arg_or_die(NULL, &pt->wn.writer_num); - + pt->wn.wid = check_writer_arg_or_die(NULL, &writer_lpr); + pt->wn.lpr = writer_lpr; /* 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, + .name = lls_command_name(AFH_RECV_CMD), + .pre_monitor = AFH_RECV->pre_monitor, + .post_monitor = AFH_RECV->post_monitor, .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, + .pre_monitor = decoder->pre_monitor, + .post_monitor = decoder->post_monitor, .context = &pt->fn }, &sched); register_writer_node(&pt->wn, pt->fn.btrn, &sched); return 1; fail: - wipe_receiver_node(pt); + wipe_receiver_node(); return ret; } -static int next_valid_file(struct play_task *pt) +static int next_valid_file(void) { int i, j = pt->current_file; + unsigned num_inputs = lls_num_inputs(play_lpr); - FOR_EACH_PLAYLIST_FILE(i) { - j = (j + 1) % conf.inputs_num; + if (j == num_inputs - 1) { + switch (OPT_UINT32_VAL(END_OF_PLAYLIST)) { + case EOP_LOOP: break; + case EOP_STOP: + pt->playing = false; + return 0; + case EOP_QUIT: return -E_EOP; + } + } + for (i = 0; i < num_inputs; i++) { + j = (j + 1) % num_inputs; if (!pt->invalid[j]) return j; } return -E_NO_VALID_FILES; } -static int load_next_file(struct play_task *pt) +static int load_next_file(void) { int ret; again: if (pt->rq == CRT_NONE) { pt->start_chunk = 0; - ret = next_valid_file(pt); + ret = next_valid_file(); 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); + ret = load_file(); if (ret < 0) { PARA_ERROR_LOG("%s: marking file as invalid\n", para_strerror(-ret)); @@ -478,7 +464,7 @@ again: return ret; } -static void kill_stream(struct play_task *pt) +static void kill_stream(void) { if (pt->wn.task) task_notify(pt->wn.task, E_EOF); @@ -487,14 +473,15 @@ static void kill_stream(struct play_task *pt) #ifdef HAVE_READLINE /* only called from com_prev(), nec. only if we have readline */ -static int previous_valid_file(struct play_task *pt) +static int previous_valid_file(void) { int i, j = pt->current_file; + unsigned num_inputs = lls_num_inputs(play_lpr); - FOR_EACH_PLAYLIST_FILE(i) { + for (i = 0; i < num_inputs; i++) { j--; if (j < 0) - j = conf.inputs_num - 1; + j = num_inputs - 1; if (!pt->invalid[j]) return j; } @@ -545,7 +532,7 @@ static const char *default_keyseqs[] = {INTERNAL_KEYMAP_ENTRIES}; static const char *default_commands[] = {INTERNAL_KEYMAP_ENTRIES}; #undef KEYMAP_ENTRY #define NUM_INTERNALLY_MAPPED_KEYS ARRAY_SIZE(default_commands) -#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + conf.key_map_given) +#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + OPT_GIVEN(KEY_MAP)) #define FOR_EACH_MAPPED_KEY(i) for (i = 0; i < NUM_MAPPED_KEYS; i++) static inline bool is_internal_key(int key) @@ -576,9 +563,9 @@ static inline int get_key_map_idx(int key) get_internal_key_map_idx(key) : get_user_key_map_idx(key); } -static inline char *get_user_key_map_arg(int key) +static inline const char *get_user_key_map_arg(int key) { - return conf.key_map_arg[get_user_key_map_idx(key)]; + return lls_string_val(get_user_key_map_idx(key), OPT_RESULT(KEY_MAP)); } static inline char *get_internal_key_map_seq(int key) @@ -596,7 +583,7 @@ static char *get_user_key_map_seq(int key) if (!p) return NULL; len = p - kma; - result = para_malloc(len + 1); + result = alloc(len + 1); memcpy(result, kma, len); result[len] = '\0'; return result; @@ -616,7 +603,7 @@ static char *get_key_map_seq_safe(int key) if (len == 1 && isprint(*seq)) return seq; - sseq = para_malloc(2 + 2 * len + 1); + sseq = alloc(2 + 2 * len + 1); sseq[0] = '0'; sseq[1] = 'x'; for (n = 0; n < len; n++) { @@ -656,7 +643,7 @@ static char **get_mapped_keyseqs(void) char **result; int i; - result = para_malloc((NUM_MAPPED_KEYS + 1) * sizeof(char *)); + result = arr_alloc(NUM_MAPPED_KEYS + 1, sizeof(char *)); FOR_EACH_MAPPED_KEY(i) { char *seq = get_key_map_seq(i); result[i] = seq; @@ -665,23 +652,6 @@ static char **get_mapped_keyseqs(void) return result; } -#include "play.command_list.h" - -typedef int play_command_handler_t(struct play_task *, int, char**); -static play_command_handler_t PLAY_COMMAND_HANDLERS; - -/* defines one command of para_play */ -struct pp_command { - const char *name; - play_command_handler_t *handler; - const char *description; - const char *usage; - const char *help; -}; - -static struct pp_command pp_cmds[] = {DEFINE_PLAY_CMD_ARRAY}; -#define FOR_EACH_COMMAND(c) for (c = 0; pp_cmds[c].name; c++) - static struct i9e_completer pp_completers[]; I9E_DUMMY_COMPLETER(jmp); @@ -693,18 +663,22 @@ I9E_DUMMY_COMPLETER(ls); I9E_DUMMY_COMPLETER(info); I9E_DUMMY_COMPLETER(play); I9E_DUMMY_COMPLETER(pause); -I9E_DUMMY_COMPLETER(stop); I9E_DUMMY_COMPLETER(tasks); I9E_DUMMY_COMPLETER(quit); I9E_DUMMY_COMPLETER(ff); static void help_completer(struct i9e_completion_info *ci, - struct i9e_completion_result *result) + struct i9e_completion_result *cr) { - result->matches = i9e_complete_commands(ci->word, pp_completers); + char *opts[] = {LSG_PLAY_CMD_HELP_OPTS, NULL}; + + if (ci->word[0] == '-') { + i9e_complete_option(opts, ci, cr); + return; + } + cr->matches = i9e_complete_commands(ci->word, pp_completers); } -I9E_DUMMY_COMPLETER(SUPERCOMMAND_UNAVAILABLE); static struct i9e_completer pp_completers[] = { #define LSG_PLAY_CMD_CMD(_name) {.name = #_name, \ .completer = _name ## _completer} @@ -713,7 +687,7 @@ static struct i9e_completer pp_completers[] = { {.name = NULL} }; -static void attach_stdout(struct play_task *pt, const char *name) +static void attach_stdout(const char *name) { if (pt->btrn) return; @@ -722,47 +696,34 @@ static void attach_stdout(struct play_task *pt, const char *name) i9e_attach_to_stdout(pt->btrn); } -static void detach_stdout(struct play_task *pt) +static void detach_stdout(void) { btr_remove_node(&pt->btrn); } -static int com_quit(struct play_task *pt, int argc, __a_unused char **argv) +#define EXPORT_PLAY_CMD_HANDLER(_cmd) \ + const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \ + .handler = com_ ## _cmd \ + }; + +static int com_quit(__a_unused struct lls_parse_result *lpr) { - if (argc != 1) - return -E_PLAY_SYNTAX; pt->rq = CRT_TERM_RQ; return 0; } +EXPORT_PLAY_CMD_HANDLER(quit); -static int com_help(struct play_task *pt, struct lls_parse_result *lpr) +static int com_help(struct lls_parse_result *lpr) { - int i, ret; - char *buf, *errctx; + int i; + char *buf; size_t sz; - const struct lls_command *cmd; + unsigned n; + const struct lls_opt_result *r = + lls_opt_result(LSG_PLAY_CMD_HELP_OPT_LONG, lpr); + bool long_help = lls_opt_given(r); - ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx)); - if (ret < 0) { - if (errctx) - PARA_ERROR_LOG("%s\n", errctx); - free(errctx); - return ret; - } - if (lls_num_inputs(lpr) == 0) { - if (pt->background) { - for (i = 1; (cmd = lls_cmd(i, play_cmd_suite)); i++) { - sz = xasprintf(&buf, "%s\t%s\n", - lls_command_name(cmd), lls_purpose(cmd)); - btr_add_output(buf, sz, pt->btrn); - } - FOR_EACH_COMMAND(i) { - sz = xasprintf(&buf, "%s\t%s\n", pp_cmds[i].name, - pp_cmds[i].description); - btr_add_output(buf, sz, pt->btrn); - } - return 0; - } + if (!pt->background) { FOR_EACH_MAPPED_KEY(i) { bool internal = is_internal_key(i); int idx = get_key_map_idx(i); @@ -777,31 +738,20 @@ static int com_help(struct play_task *pt, struct lls_parse_result *lpr) } return 0; } - ret = lls(lls_lookup_subcmd(lls_input(0, lpr), play_cmd_suite, - &errctx)); - if (ret < 0) { - if (errctx) - PARA_ERROR_LOG("%s\n", errctx); - free(errctx); - return ret; - } - cmd = lls_cmd(ret, play_cmd_suite); - buf = lls_long_help(cmd); - assert(buf); - btr_add_output(buf, strlen(buf), pt->btrn); + lsu_com_help(long_help, lpr, play_cmd_suite, NULL, &buf, &n); + btr_add_output(buf, n, pt->btrn); return 0; } EXPORT_PLAY_CMD_HANDLER(help); -static int com_info(struct play_task *pt, - __a_unused struct lls_parse_result *lpr) +static int com_info(__a_unused struct lls_parse_result *lpr) { char *buf; size_t sz; static char dflt[] = "[no information available]"; sz = xasprintf(&buf, "playlist_pos: %u\npath: %s\n", - pt->current_file, conf.inputs[pt->current_file]); + pt->current_file, get_playlist_file(pt->current_file)); btr_add_output(buf, sz, pt->btrn); buf = pt->afhi_txt? pt->afhi_txt : dflt; btr_add_output_dont_free(buf, strlen(buf), pt->btrn); @@ -809,18 +759,17 @@ static int com_info(struct play_task *pt, } EXPORT_PLAY_CMD_HANDLER(info); -static void list_file(struct play_task *pt, int num) +static void list_file(int num) { char *buf; size_t sz; sz = xasprintf(&buf, "%s %4d %s\n", num == pt->current_file? - "*" : " ", num, conf.inputs[num]); + "*" : " ", num, get_playlist_file(num)); btr_add_output(buf, sz, pt->btrn); } -static int com_tasks(struct play_task *pt, - __a_unused struct lls_parse_result *lpr) +static int com_tasks(__a_unused struct lls_parse_result *lpr) { static char state; char *buf; @@ -828,25 +777,25 @@ static int com_tasks(struct play_task *pt, buf = get_task_list(&sched); btr_add_output(buf, strlen(buf), pt->btrn); - state = get_playback_state(pt); + state = get_playback_state(); sz = xasprintf(&buf, "state: %c\n", state); btr_add_output(buf, sz, pt->btrn); return 0; } EXPORT_PLAY_CMD_HANDLER(tasks); -static int com_ls(struct play_task *pt, - __a_unused struct lls_parse_result *lpr) +static int com_ls(__a_unused struct lls_parse_result *lpr) { int i; + unsigned num_inputs = lls_num_inputs(play_lpr); - FOR_EACH_PLAYLIST_FILE(i) - list_file(pt, i); + for (i = 0; i < num_inputs; i++) + list_file(i); return 0; } EXPORT_PLAY_CMD_HANDLER(ls); -static int com_play(struct play_task *pt, struct lls_parse_result *lpr) +static int com_play(struct lls_parse_result *lpr) { int32_t x; int ret; @@ -859,7 +808,7 @@ static int com_play(struct play_task *pt, struct lls_parse_result *lpr) free(errctx); return ret; } - state = get_playback_state(pt); + state = get_playback_state(); if (lls_num_inputs(lpr) == 0) { if (state == 'P') return 0; @@ -871,47 +820,46 @@ static int com_play(struct play_task *pt, struct lls_parse_result *lpr) ret = para_atoi32(lls_input(0, lpr), &x); if (ret < 0) return ret; - if (x < 0 || x >= conf.inputs_num) + if (x < 0 || x >= lls_num_inputs(play_lpr)) return -ERRNO_TO_PARA_ERROR(EINVAL); - kill_stream(pt); + kill_stream(); pt->next_file = x; pt->rq = CRT_FILE_CHANGE; return 0; } EXPORT_PLAY_CMD_HANDLER(play); -static int com_pause(struct play_task *pt, - __a_unused struct lls_parse_result *lpr) +static int com_pause(__a_unused struct lls_parse_result *lpr) { char state; - long unsigned seconds, ss; + uint64_t ms; + unsigned long cn; /* chunk num */ - state = get_playback_state(pt); + state = get_playback_state(); pt->playing = false; if (state != 'P') return 0; - seconds = get_play_time(pt); + ms = get_play_time(); pt->playing = false; - ss = 0; + cn = 0; if (pt->seconds > 0) - ss = seconds * pt->num_chunks / pt->seconds + 1; - ss = PARA_MAX(ss, 0UL); - ss = PARA_MIN(ss, pt->num_chunks); - pt->start_chunk = ss; - kill_stream(pt); + cn = ms * pt->num_chunks / pt->seconds / 1000 + 1; + cn = PARA_MIN(cn, pt->num_chunks); + pt->start_chunk = cn; + pt->rq = CRT_REPOS; + kill_stream(); return 0; } EXPORT_PLAY_CMD_HANDLER(pause); -static int com_prev(struct play_task *pt, - __a_unused struct lls_parse_result *lpr) +static int com_prev(__a_unused struct lls_parse_result *lpr) { int ret; - ret = previous_valid_file(pt); + ret = previous_valid_file(); if (ret < 0) return ret; - kill_stream(pt); + kill_stream(); pt->next_file = ret; pt->rq = CRT_FILE_CHANGE; pt->start_chunk = 0; @@ -919,15 +867,14 @@ static int com_prev(struct play_task *pt, } EXPORT_PLAY_CMD_HANDLER(prev); -static int com_next(struct play_task *pt, - __a_unused struct lls_parse_result *lpr) +static int com_next(__a_unused struct lls_parse_result *lpr) { int ret; - ret = next_valid_file(pt); + ret = next_valid_file(); if (ret < 0) return ret; - kill_stream(pt); + kill_stream(); pt->next_file = ret; pt->rq = CRT_FILE_CHANGE; pt->start_chunk = 0; @@ -935,23 +882,21 @@ static int com_next(struct play_task *pt, } EXPORT_PLAY_CMD_HANDLER(next); -static int com_fg(struct play_task *pt, - __a_unused struct lls_parse_result *lpr) +static int com_fg(__a_unused struct lls_parse_result *lpr) { pt->background = false; return 0; } EXPORT_PLAY_CMD_HANDLER(fg); -static int com_bg(struct play_task *pt, - __a_unused struct lls_parse_result *lpr) +static int com_bg(__a_unused struct lls_parse_result *lpr) { pt->background = true; return 0; } EXPORT_PLAY_CMD_HANDLER(bg); -static int com_jmp(struct play_task *pt, struct lls_parse_result *lpr) +static int com_jmp(struct lls_parse_result *lpr) { int32_t percent; int ret; @@ -970,19 +915,19 @@ static int com_jmp(struct play_task *pt, struct lls_parse_result *lpr) if (percent < 0 || percent > 100) return -ERRNO_TO_PARA_ERROR(EINVAL); if (percent == 100) - return com_next(pt, NULL); + return com_next(NULL); if (pt->playing && !pt->fn.btrn) return 0; pt->start_chunk = percent * pt->num_chunks / 100; if (!pt->playing) return 0; pt->rq = CRT_REPOS; - kill_stream(pt); + kill_stream(); return 0; } EXPORT_PLAY_CMD_HANDLER(jmp); -static int com_ff(struct play_task *pt, struct lls_parse_result *lpr) +static int com_ff(struct lls_parse_result *lpr) { int32_t seconds; char *errctx; @@ -1000,7 +945,7 @@ static int com_ff(struct play_task *pt, struct lls_parse_result *lpr) return ret; if (pt->playing && !pt->fn.btrn) return 0; - seconds += get_play_time(pt); + seconds += (get_play_time() + 500) / 1000; seconds = PARA_MIN(seconds, (typeof(seconds))pt->seconds - 4); seconds = PARA_MAX(seconds, 0); pt->start_chunk = pt->num_chunks * seconds / pt->seconds; @@ -1009,47 +954,37 @@ static int com_ff(struct play_task *pt, struct lls_parse_result *lpr) if (!pt->playing) return 0; pt->rq = CRT_REPOS; - kill_stream(pt); + kill_stream(); return 0; } EXPORT_PLAY_CMD_HANDLER(ff); -static int run_command(char *line, struct play_task *pt) +static int run_command(char *line) { - int i, ret, argc; + int ret, argc; char **argv = NULL; char *errctx = NULL; const struct play_command_info *pci; struct lls_parse_result *lpr; const struct lls_command *cmd; - attach_stdout(pt, __FUNCTION__); + attach_stdout(__FUNCTION__); ret = create_argv(line, " ", &argv); if (ret < 0) goto out; if (ret == 0) goto out; argc = ret; - ret = lls(lls_lookup_subcmd(argv[0], play_cmd_suite, &errctx)); - if (ret >= 0) { - cmd = lls_cmd(ret, play_cmd_suite); - ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx)); - if (ret < 0) - goto out; - pci = lls_user_data(cmd); - ret = pci->handler(pt, lpr); - lls_free_parse_result(lpr, cmd); - } else { - FOR_EACH_COMMAND(i) { - if (strcmp(pp_cmds[i].name, argv[0])) - continue; - free(errctx); - errctx = NULL; - ret = pp_cmds[i].handler(pt, argc, argv); - break; - } - } + if (ret < 0) + goto out; + cmd = lls_cmd(ret, play_cmd_suite); + ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx)); + if (ret < 0) + goto out; + pci = lls_user_data(cmd); + ret = pci->handler(lpr); + lls_free_parse_result(lpr, cmd); out: if (errctx) PARA_ERROR_LOG("%s\n", errctx); @@ -1060,12 +995,11 @@ out: static int play_i9e_line_handler(char *line) { - return run_command(line, &play_task); + return run_command(line); } static int play_i9e_key_handler(int key) { - struct play_task *pt = &play_task; int idx = get_key_map_idx(key); char *seq = get_key_map_seq(key); char *cmd = get_key_map_cmd(key); @@ -1074,7 +1008,7 @@ static int play_i9e_key_handler(int key) PARA_NOTICE_LOG("pressed %d: %s key #%d (%s -> %s)\n", key, internal? "internal" : "user-defined", idx, seq, cmd); - run_command(cmd, pt); + run_command(cmd); free(seq); free(cmd); pt->next_update = *now; @@ -1091,7 +1025,7 @@ static struct i9e_client_info ici = { static void sigint_handler(int sig) { - play_task.background = true; + pt->background = true; i9e_signal_dispatch(sig); } @@ -1100,20 +1034,27 @@ static void sigint_handler(int sig) * stderr. Once the i9e subsystem has been initialized, we switch to the i9e * log facility. */ -static void session_open(struct play_task *pt) +static void session_open(void) { int ret; char *history_file; struct sigaction act; PARA_NOTICE_LOG("\n%s\n", version_text("play")); - if (conf.history_file_given) - history_file = para_strdup(conf.history_file_arg); + if (OPT_GIVEN(HISTORY_FILE)) + history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE)); else { char *home = para_homedir(); - history_file = make_message("%s/.paraslash/play.history", - home); + char *dot_para = make_message("%s/.paraslash", home); + free(home); + ret = para_mkdir(dot_para); + /* warn, but otherwise ignore mkdir error */ + if (ret < 0) + PARA_WARNING_LOG("Can not create %s: %s\n", dot_para, + para_strerror(-ret)); + history_file = make_message("%s/play.history", dot_para); + free(dot_para); } ici.history_file = history_file; ici.loglevel = loglevel; @@ -1126,7 +1067,7 @@ static void session_open(struct play_task *pt) sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGWINCH, &act, NULL); - sched.select_function = i9e_select; + sched.poll_function = i9e_poll; ici.bound_keyseqs = get_mapped_keyseqs(); pt->btrn = ici.producer = btr_new_node(&(struct btr_node_description) @@ -1144,7 +1085,7 @@ out: exit(EXIT_FAILURE); } -static void session_update_time_string(struct play_task *pt, char *str, unsigned len) +static void session_update_time_string(char *str, unsigned len) { if (pt->background) return; @@ -1154,84 +1095,82 @@ static void session_update_time_string(struct play_task *pt, char *str, unsigned if (btr_get_input_queue_size(pt->btrn) > 0) return; } - ie9_print_status_bar(str, len); + i9e_print_status_bar(str, len); } /* * If we are about to die we must call i9e_close() to reset the terminal. * However, i9e_close() must be called in *this* context, i.e. from - * play_task.post_select() rather than from i9e_post_select(), because + * play_task.post_monitor() rather than from i9e_post_monitor(), because * otherwise i9e would access freed memory upon return. So the play task must * stay alive until the i9e task terminates. * * We achieve this by sending a fake SIGTERM signal via i9e_signal_dispatch() - * and reschedule. In the next iteration, i9e->post_select returns an error and + * and reschedule. In the next iteration, i9e->post_monitor returns an error and * terminates. Subsequent calls to i9e_get_error() then return negative and we * are allowed to call i9e_close() and terminate as well. */ -static int session_post_select(__a_unused struct sched *s, struct play_task *pt) +static int session_post_monitor(__a_unused struct sched *s) { int ret; if (pt->background) - detach_stdout(pt); + detach_stdout(); else - attach_stdout(pt, __FUNCTION__); + attach_stdout(__FUNCTION__); ret = i9e_get_error(); if (ret < 0) { - kill_stream(pt); + kill_stream(); i9e_close(); para_log = stderr_log; free(ici.history_file); return ret; } - if (get_playback_state(pt) == 'X') + if (get_playback_state() == 'X') i9e_signal_dispatch(SIGTERM); return 0; } #else /* HAVE_READLINE */ -static int session_post_select(struct sched *s, struct play_task *pt) +static int session_post_monitor(struct sched *s) { char c; - if (!FD_ISSET(STDIN_FILENO, &s->rfds)) + if (!sched_read_ok(STDIN_FILENO, s)) return 0; if (read(STDIN_FILENO, &c, 1)) do_nothing; - kill_stream(pt); + kill_stream(); return 1; } -static void session_open(__a_unused struct play_task *pt) +static void session_open(void) { } -static void session_update_time_string(__a_unused struct play_task *pt, - char *str, __a_unused unsigned len) +static void session_update_time_string(char *str, __a_unused unsigned len) { printf("\r%s ", str); fflush(stdout); } #endif /* HAVE_READLINE */ -static void play_pre_select(struct sched *s, void *context) +static void play_pre_monitor(struct sched *s, __a_unused void *context) { - struct play_task *pt = context; char state; - para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno); - state = get_playback_state(pt); + sched_monitor_readfd(STDIN_FILENO, s); + state = get_playback_state(); if (state == 'R' || state == 'F' || state == 'X') return sched_min_delay(s); sched_request_barrier_or_min_delay(&pt->next_update, s); } -static unsigned get_time_string(struct play_task *pt, char **result) +static unsigned get_time_string(char **result) { int seconds, length; - char state = get_playback_state(pt); + char state = get_playback_state(); /* do not return anything if things are about to change */ if (state != 'P' && state != 'U') { @@ -1241,7 +1180,7 @@ static unsigned get_time_string(struct play_task *pt, char **result) length = pt->seconds; if (length == 0) return xasprintf(result, "0:00 [0:00] (0%%/0:00)"); - seconds = get_play_time(pt); + seconds = (get_play_time() + 500) / 1000; return xasprintf(result, "#%u: %d:%02d [%d:%02d] (%d%%/%d:%02d) %s", pt->current_file, seconds / 60, @@ -1251,28 +1190,27 @@ static unsigned get_time_string(struct play_task *pt, char **result) length? (seconds * 100 + length / 2) / length : 0, length / 60, length % 60, - conf.inputs[pt->current_file] + get_playlist_file(pt->current_file) ); } -static int play_post_select(struct sched *s, void *context) +static int play_post_monitor(struct sched *s, __a_unused void *context) { - struct play_task *pt = context; int ret; - ret = eof_cleanup(pt); + ret = eof_cleanup(); if (ret < 0) { pt->rq = CRT_TERM_RQ; return 0; } - ret = session_post_select(s, pt); + ret = session_post_monitor(s); if (ret < 0) goto out; if (!pt->wn.btrn && !pt->fn.btrn) { - char state = get_playback_state(pt); + char state = get_playback_state(); if (state == 'P' || state == 'R' || state == 'F') { PARA_NOTICE_LOG("state: %c\n", state); - ret = load_next_file(pt); + ret = load_next_file(); if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); pt->rq = CRT_TERM_RQ; @@ -1284,10 +1222,10 @@ static int play_post_select(struct sched *s, void *context) } if (tv_diff(now, &pt->next_update, NULL) >= 0) { char *str; - unsigned len = get_time_string(pt, &str); + unsigned len = get_time_string(&str); struct timeval delay = {.tv_sec = 0, .tv_usec = 100 * 1000}; if (str && len > 0) - session_update_time_string(pt, str, len); + session_update_time_string(str, len); free(str); tv_add(now, &delay, &pt->next_update); } @@ -1299,39 +1237,43 @@ out: /** * The main function of para_play. * - * \param argc Standard. - * \param argv Standard. + * \param argc See man page. + * \param argv See man page. + * + * para_play distributes its work by submitting various tasks to the paraslash + * scheduler. The receiver, filter and writer tasks, which are used to play an + * audio file, require one task each to maintain their underlying buffer tree + * node. These tasks only exist when an audio file is playing. + * + * The i9 task, which is submitted and maintained by the i9e subsystem, reads + * an input line and calls the corresponding command handler such as com_stop() + * which is implemented in this file. The command handlers typically write a + * request to the global play_task structure, whose contents are read and acted + * upon by another task, the play task. * - * \return \p EXIT_FAILURE or \p EXIT_SUCCESS. + * As a rule, playlist handling is performed exclusively in play context, i.e. + * in the post-monitor step of the play task, while command handlers are only + * called in i9e context. + * + * \return EXIT_FAILURE or EXIT_SUCCESS. */ int main(int argc, char *argv[]) { int ret; - struct play_task *pt = &play_task; - - /* needed this early to make help work */ - recv_init(); - filter_init(); - writer_init(); - - sched.default_timeout.tv_sec = 5; + unsigned num_inputs; + sched.default_timeout = 5000; parse_config_or_die(argc, argv); - if (conf.inputs_num == 0) - print_help_and_die(); - check_afh_receiver_or_die(); - - session_open(pt); - if (conf.randomize_given) - shuffle(conf.inputs, conf.inputs_num); - pt->invalid = para_calloc(sizeof(*pt->invalid) * conf.inputs_num); + session_open(); + num_inputs = lls_num_inputs(play_lpr); + init_shuffle_map(); + pt->invalid = arr_zalloc(num_inputs, sizeof(*pt->invalid)); pt->rq = CRT_FILE_CHANGE; - pt->current_file = conf.inputs_num - 1; pt->playing = true; pt->task = task_register(&(struct task_info){ .name = "play", - .pre_select = play_pre_select, - .post_select = play_post_select, + .pre_monitor = play_pre_monitor, + .post_monitor = play_post_monitor, .context = pt, }, &sched); ret = schedule(&sched);