From dc00566963b35c931a4cf489d4361daa8283697a Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 5 May 2018 20:20:21 +0200 Subject: command: handle list commands like normal commands Pretty annoying. --- player/command.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index cc8b609c50..3fe39642a6 100644 --- a/player/command.c +++ b/player/command.c @@ -4825,6 +4825,16 @@ static void cmd_cycle_values(void *p) change_property_cmd(cmd, name, M_PROPERTY_SET_STRING, cmd->args[current].v.s); } +static void cmd_list(void *p) +{ + struct mp_cmd_ctx *cmd = p; + + for (struct mp_cmd *sub = cmd->cmd->args[0].v.p; sub; sub = sub->queue_next) + run_command(cmd->mpctx, sub, NULL); +} + +const struct mp_cmd_def mp_cmd_list = { "list", cmd_list }; + int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *res) { struct mpv_node dummy_node = {0}; @@ -4861,13 +4871,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re } } - if (cmd->def == &mp_cmd_list) { - for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) - run_command(mpctx, sub, NULL); - } else { - assert(cmd->def->handler); - cmd->def->handler(ctx); - } + cmd->def->handler(ctx); if (!ctx->success) mpv_free_node_contents(ctx->result); -- cgit v1.2.3 From b440f6dfb3d29651d8dcb7abfeb8ed18e3f2b995 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 6 May 2018 18:27:18 +0200 Subject: command: add infrastructure for async commands This enables two types of command behavior: 1. Plain async behavior, like "loadfile" not completing until the file is fully loaded. 2. Running parts of the command on worker threads, e.g. for I/O, such as "sub-add" doing network accesses on a thread while the core continues. Both have no implementation yet, and most new code is actually inactive. The plan is to implement a number of useful cases in the following commits. The most tricky part is handling internal keybindings (input.conf) and the multi-command feature (concatenating commands with ";"). It requires a bunch of roundabout code to make it do the expected thing in combination with async commands. There is the question how commands should be handled that come in at a higher rate than what can be handled by the core. Currently, it will simply queue up input.conf commands as long as memory lasts. The client API is limited by the size of the reply queue per client. For commands which require a worker thread, the thread pool is limited to 30 threads, and then will queue up work in memory. The number is completely arbitrary. --- player/command.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 151 insertions(+), 19 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index 3fe39642a6..4600e65236 100644 --- a/player/command.c +++ b/player/command.c @@ -60,7 +60,9 @@ #include "video/out/bitmap_packer.h" #include "options/path.h" #include "screenshot.h" +#include "misc/dispatch.h" #include "misc/node.h" +#include "misc/thread_pool.h" #include "osdep/io.h" #include "osdep/subprocess.h" @@ -4825,27 +4827,147 @@ static void cmd_cycle_values(void *p) change_property_cmd(cmd, name, M_PROPERTY_SET_STRING, cmd->args[current].v.s); } +struct cmd_list_ctx { + struct MPContext *mpctx; + + // actual list command + struct mp_cmd_ctx *parent; + + bool current_valid; + pthread_t current; + bool completed_recursive; + + // list of sub commands yet to run + struct mp_cmd **sub; + int num_sub; +}; + +static void continue_cmd_list(struct cmd_list_ctx *list); + +static void on_cmd_list_sub_completion(struct mp_cmd_ctx *cmd) +{ + struct cmd_list_ctx *list = cmd->on_completion_priv; + + if (list->current_valid && pthread_equal(list->current, pthread_self())) { + list->completed_recursive = true; + } else { + continue_cmd_list(list); + } +} + +static void continue_cmd_list(struct cmd_list_ctx *list) +{ + while (list->parent->args[0].v.p) { + struct mp_cmd *sub = list->parent->args[0].v.p; + list->parent->args[0].v.p = sub->queue_next; + + ta_xset_parent(sub, NULL); + + if (sub->flags & MP_ASYNC_CMD) { + // We run it "detached" (fire & forget) + run_command(list->mpctx, sub, NULL, NULL); + } else { + // Run the next command once this one completes. + + list->completed_recursive = false; + list->current_valid = true; + list->current = pthread_self(); + + run_command(list->mpctx, sub, on_cmd_list_sub_completion, list); + + list->current_valid = false; + + // run_command() either recursively calls the completion function, + // or lets the command continue run in the background. If it was + // completed recursively, we can just continue our loop. Otherwise + // the completion handler will invoke this loop again elsewhere. + // We could unconditionally call continue_cmd_list() in the handler + // instead, but then stack depth would grow with list length. + if (!list->completed_recursive) + return; + } + } + + mp_cmd_ctx_complete(list->parent); + talloc_free(list); +} + static void cmd_list(void *p) { struct mp_cmd_ctx *cmd = p; - for (struct mp_cmd *sub = cmd->cmd->args[0].v.p; sub; sub = sub->queue_next) - run_command(cmd->mpctx, sub, NULL); + cmd->completed = false; + + struct cmd_list_ctx *list = talloc_zero(NULL, struct cmd_list_ctx); + list->mpctx = cmd->mpctx; + list->parent = p; + + continue_cmd_list(list); +} + +const struct mp_cmd_def mp_cmd_list = { "list", cmd_list, .exec_async = true }; + +// Signal that the command is complete now. This also deallocates cmd. +// You must call this function in a state where the core is locked for the +// current thread (e.g. from the main thread, or from within mp_dispatch_lock()). +// Completion means the command is finished, even if it errored or never ran. +// Keep in mind that calling this can execute further user command that can +// change arbitrary state (due to cmd_list). +void mp_cmd_ctx_complete(struct mp_cmd_ctx *cmd) +{ + cmd->completed = true; + if (!cmd->success) + mpv_free_node_contents(&cmd->result); + if (cmd->on_completion) + cmd->on_completion(cmd); + mpv_free_node_contents(&cmd->result); + talloc_free(cmd->cmd); + talloc_free(cmd); } -const struct mp_cmd_def mp_cmd_list = { "list", cmd_list }; +static void run_command_on_worker_thread(void *p) +{ + struct mp_cmd_ctx *ctx = p; + struct MPContext *mpctx = ctx->mpctx; + + mp_core_lock(mpctx); + + bool exec_async = ctx->cmd->def->exec_async; + ctx->cmd->def->handler(ctx); + if (!exec_async) + mp_cmd_ctx_complete(ctx); -int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *res) + mpctx->outstanding_async -= 1; + if (!mpctx->outstanding_async && mp_is_shutting_down(mpctx)) + mp_wakeup_core(mpctx); + + mp_core_unlock(mpctx); +} + +// Run the given command. Upon command completion, on_completion is called. This +// can happen within the function, or for async commands, some time after the +// function returns (the caller is supposed to be able to handle both cases). In +// both cases, the callback will be called while the core is locked (i.e. you +// can access the core freely). +// on_completion_priv is copied to mp_cmd_ctx.on_completion_priv and can be +// accessed from the completion callback. +// The completion callback is invoked exactly once. If it's NULL, it's ignored. +// Ownership of cmd goes to the caller. +void run_command(struct MPContext *mpctx, struct mp_cmd *cmd, + void (*on_completion)(struct mp_cmd_ctx *cmd), + void *on_completion_priv) { - struct mpv_node dummy_node = {0}; - struct mp_cmd_ctx *ctx = &(struct mp_cmd_ctx){ + struct mp_cmd_ctx *ctx = talloc(NULL, struct mp_cmd_ctx); + *ctx = (struct mp_cmd_ctx){ .mpctx = mpctx, .cmd = cmd, .args = cmd->args, .num_args = cmd->nargs, .priv = cmd->def->priv, .success = true, - .result = res ? res : &dummy_node, + .completed = true, + .on_completion = on_completion, + .on_completion_priv = on_completion_priv, }; struct MPOpts *opts = mpctx->opts; @@ -4863,22 +4985,32 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re for (int n = 0; n < cmd->nargs; n++) { if (cmd->args[n].type->type == CONF_TYPE_STRING) { char *s = mp_property_expand_string(mpctx, cmd->args[n].v.s); - if (!s) - return -1; + if (!s) { + ctx->success = false; + mp_cmd_ctx_complete(ctx); + return; + } talloc_free(cmd->args[n].v.s); cmd->args[n].v.s = s; } } } - cmd->def->handler(ctx); - - if (!ctx->success) - mpv_free_node_contents(ctx->result); - - mpv_free_node_contents(&dummy_node); - - return ctx->success ? 0 : -1; + if (cmd->def->spawn_thread) { + mpctx->outstanding_async += 1; // prevent that core disappears + if (!mp_thread_pool_queue(mpctx->thread_pool, + run_command_on_worker_thread, ctx)) + { + mpctx->outstanding_async -= 1; + ctx->success = false; + mp_cmd_ctx_complete(ctx); + } + } else { + bool exec_async = cmd->def->exec_async; + cmd->def->handler(ctx); + if (!exec_async) + mp_cmd_ctx_complete(ctx); + } } static void cmd_seek(void *p) @@ -5199,7 +5331,7 @@ static void cmd_expand_text(void *p) struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; - *cmd->result = (mpv_node){ + cmd->result = (mpv_node){ .format = MPV_FORMAT_STRING, .u.string = mp_property_expand_string(mpctx, cmd->args[0].v.s) }; @@ -5513,7 +5645,7 @@ static void cmd_screenshot_raw(void *p) { struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; - struct mpv_node *res = cmd->result; + struct mpv_node *res = &cmd->result; struct mp_image *img = screenshot_get_rgb(mpctx, cmd->args[0].v.i); if (!img) { -- cgit v1.2.3 From c349e2f337693c53687b0bd5e4d8669363e2d79d Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 6 May 2018 21:23:28 +0200 Subject: command: make sub-add and audio-add commands async Pretty trivial, since commands can be async now, and the common code even provides convenience like running commands on a worker thread. The only ugly thing is that mp_add_external_file() needs an extra flag for locking. This is because there's still some code which calls this synchronously from the main thread, and unlocking the core makes no sense there. --- player/command.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index 4600e65236..9aa3a9e6d8 100644 --- a/player/command.c +++ b/player/command.c @@ -5520,7 +5520,7 @@ static void cmd_track_add(void *p) return; } } - int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type); + int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type, true); if (first < 0) { cmd->success = false; return; @@ -5584,7 +5584,7 @@ static void cmd_track_reload(void *p) if (t && t->is_external && t->external_filename) { char *filename = talloc_strdup(NULL, t->external_filename); mp_remove_track(mpctx, t); - nt_num = mp_add_external_file(mpctx, filename, type); + nt_num = mp_add_external_file(mpctx, filename, type, false); talloc_free(filename); } @@ -6039,6 +6039,7 @@ const struct mp_cmd_def mp_cmds[] = { OARG_STRING(""), OARG_STRING(""), }, .priv = &(const int){STREAM_SUB}, + .spawn_thread = true, }, { "sub-remove", cmd_track_remove, { OARG_INT(-1) }, .priv = &(const int){STREAM_SUB}, }, @@ -6169,6 +6170,7 @@ const struct mp_cmd_def mp_cmds[] = { OARG_STRING(""), OARG_STRING(""), }, .priv = &(const int){STREAM_AUDIO}, + .spawn_thread = true, }, { "audio-remove", cmd_track_remove, { OARG_INT(-1) }, .priv = &(const int){STREAM_AUDIO}, }, -- cgit v1.2.3 From 1b611e38ef9291c309e97379ef432fd301605033 Mon Sep 17 00:00:00 2001 From: wm4 Date: Mon, 7 May 2018 20:36:17 +0200 Subject: player: make all external file loading actions async Still missing: not freezing when removing a track (i.e. closing demuxer) with the sub-remove/audio-remove/rescan-external-files commands. --- player/command.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index 9aa3a9e6d8..12c232640a 100644 --- a/player/command.c +++ b/player/command.c @@ -5520,7 +5520,7 @@ static void cmd_track_add(void *p) return; } } - int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type, true); + int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type); if (first < 0) { cmd->success = false; return; @@ -5584,7 +5584,7 @@ static void cmd_track_reload(void *p) if (t && t->is_external && t->external_filename) { char *filename = talloc_strdup(NULL, t->external_filename); mp_remove_track(mpctx, t); - nt_num = mp_add_external_file(mpctx, filename, type, false); + nt_num = mp_add_external_file(mpctx, filename, type); talloc_free(filename); } @@ -6044,7 +6044,9 @@ const struct mp_cmd_def mp_cmds[] = { { "sub-remove", cmd_track_remove, { OARG_INT(-1) }, .priv = &(const int){STREAM_SUB}, }, { "sub-reload", cmd_track_reload, { OARG_INT(-1) }, - .priv = &(const int){STREAM_SUB}, }, + .priv = &(const int){STREAM_SUB}, + .spawn_thread = true, + }, { "tv-last-channel", cmd_tv_last_channel, }, @@ -6175,12 +6177,16 @@ const struct mp_cmd_def mp_cmds[] = { { "audio-remove", cmd_track_remove, { OARG_INT(-1) }, .priv = &(const int){STREAM_AUDIO}, }, { "audio-reload", cmd_track_reload, { OARG_INT(-1) }, - .priv = &(const int){STREAM_AUDIO}, }, + .priv = &(const int){STREAM_AUDIO}, + .spawn_thread = true, + }, { "rescan-external-files", cmd_rescan_external_files, { OARG_CHOICE(1, ({"keep-selection", 0}, {"reselect", 1})), - }}, + }, + .spawn_thread = true, + }, { "apply-profile", cmd_apply_profile, {ARG_STRING } }, -- cgit v1.2.3 From 059e7fdb3aaace0c6259728547da1896249818e4 Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 9 May 2018 21:09:31 +0200 Subject: command: move screenshot command stubs to screenshot.c Commands are not a monolithic giant switch() statement anymore, but individual functions. There's no reason to have the command handlers themselves in command.c, with a weird under-defined API in between. (In the future, I'd like to split up command.c further, and when I do that, scrrenshot.c will probably gets its own mp_cmd_def[] array, and define the commands locally instead of exporting the raw handlers.) --- player/command.c | 45 --------------------------------------------- 1 file changed, 45 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index 12c232640a..7d2109be7f 100644 --- a/player/command.c +++ b/player/command.c @@ -5622,51 +5622,6 @@ static void cmd_rescan_external_files(void *p) } } -static void cmd_screenshot(void *p) -{ - struct mp_cmd_ctx *cmd = p; - struct MPContext *mpctx = cmd->mpctx; - bool async = cmd->cmd->flags & MP_ASYNC_CMD; - int mode = cmd->args[0].v.i & 3; - int freq = (cmd->args[0].v.i | cmd->args[1].v.i) >> 3; - screenshot_request(mpctx, mode, freq, cmd->msg_osd, async); -} - -static void cmd_screenshot_to_file(void *p) -{ - struct mp_cmd_ctx *cmd = p; - struct MPContext *mpctx = cmd->mpctx; - bool async = cmd->cmd->flags & MP_ASYNC_CMD; - screenshot_to_file(mpctx, cmd->args[0].v.s, cmd->args[1].v.i, cmd->msg_osd, - async); -} - -static void cmd_screenshot_raw(void *p) -{ - struct mp_cmd_ctx *cmd = p; - struct MPContext *mpctx = cmd->mpctx; - struct mpv_node *res = &cmd->result; - - struct mp_image *img = screenshot_get_rgb(mpctx, cmd->args[0].v.i); - if (!img) { - cmd->success = false; - return; - } - - node_init(res, MPV_FORMAT_NODE_MAP, NULL); - node_map_add_int64(res, "w", img->w); - node_map_add_int64(res, "h", img->h); - node_map_add_int64(res, "stride", img->stride[0]); - node_map_add_string(res, "format", "bgr0"); - struct mpv_byte_array *ba = - node_map_add(res, "data", MPV_FORMAT_BYTE_ARRAY)->u.ba; - *ba = (struct mpv_byte_array){ - .data = img->planes[0], - .size = img->stride[0] * img->h, - }; - talloc_steal(ba, img); -} - static void cmd_run(void *p) { struct mp_cmd_ctx *cmd = p; -- cgit v1.2.3 From a4321cf6877f47a381ff40bea73ca2934635ea58 Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 9 May 2018 22:38:21 +0200 Subject: screenshot: change async behavior to be in line with new semantics Basically reimplement the async behavior on top of the async command code. With this, all screenshot commands are async, and the "async" prefix basically does nothing. The prefix now behaves exactly like with other commands that use spawn_thread. This also means using the prefix in the preset input.conf is pointless (without effect) and misleading, so remove that. The each_frame mode was actually particularly painful in making this change, since the player wants to block for it when writing a screenshot, and generally doesn't fit into the new infrastructure. It was still relatively easy to reimplement by copying the original command and then repeating it on each frame. The waiting is reentrant now, so move the call in video.c to a "safer" spot. One way to observe how the new semantics interact with everything is using the mpv repl script and sending a screenshot command through it. Without async flag, the script will freeze while writing the screenshot (while playback continues), while with async flag it continues. --- player/command.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index 7d2109be7f..10c58e86d8 100644 --- a/player/command.c +++ b/player/command.c @@ -6013,13 +6013,17 @@ const struct mp_cmd_def mp_cmds[] = { // backwards compatibility OARG_CHOICE(0, ({"unused", 0}, {"single", 0}, {"each-frame", 8})), - }}, + }, + .spawn_thread = true, + }, { "screenshot-to-file", cmd_screenshot_to_file, { ARG_STRING, OARG_CHOICE(2, ({"video", 0}, {"window", 1}, {"subtitles", 2})), - }}, + }, + .spawn_thread = true, + }, { "screenshot-raw", cmd_screenshot_raw, { OARG_CHOICE(2, ({"video", 0}, {"window", 1}, -- cgit v1.2.3 From cc2490ea7eb4dc1480bc26d62a3bbb15d387c35b Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 10 May 2018 15:57:36 +0200 Subject: input: add a define for the number of mouse buttons and use it (Why the fuck are there up to 20 mouse buttons?) --- player/command.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index 10c58e86d8..fb0180b4b0 100644 --- a/player/command.c +++ b/player/command.c @@ -5848,7 +5848,7 @@ static void cmd_mouse(void *p) mp_input_set_mouse_pos_artificial(mpctx->input, x, y); return; } - if (button < 0 || button >= 20) {// invalid button + if (button < 0 || button >= MP_KEY_MOUSE_BTN_COUNT) {// invalid button MP_ERR(mpctx, "%d is not a valid mouse button number.\n", button); cmd->success = false; return; -- cgit v1.2.3 From d9bc97bda6e4750af2fbbfcb51ddb6b2c04c277b Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 12 May 2018 15:14:07 +0200 Subject: command: add a subprocess command This supports named arguments. It benefits from the infrastructure of async commands. The plan is to reimplement Lua's utils.subprocess() on top of it. --- player/command.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index fb0180b4b0..fe6c3fd2ca 100644 --- a/player/command.c +++ b/player/command.c @@ -5633,6 +5633,91 @@ static void cmd_run(void *p) talloc_free(args); } +struct subprocess_cb_ctx { + struct mp_log *log; + void* talloc_ctx; + int64_t max_size; + bool capture[3]; + bstr output[3]; +}; + +static void subprocess_output(struct subprocess_cb_ctx *ctx, int fd, + char *data, size_t size) +{ + if (ctx->capture[fd]) { + if (ctx->output[fd].len < ctx->max_size) + bstr_xappend(ctx->talloc_ctx, &ctx->output[fd], (bstr){data, size}); + } else { + int msgl = fd == 2 ? MSGL_ERR : MSGL_INFO; + mp_msg(ctx->log, msgl, "%.*s", (int)size, data); + } +} + +static void subprocess_stdout(void *p, char *data, size_t size) +{ + struct subprocess_cb_ctx *ctx = p; + subprocess_output(ctx, 1, data, size); +} + +static void subprocess_stderr(void *p, char *data, size_t size) +{ + struct subprocess_cb_ctx *ctx = p; + subprocess_output(ctx, 2, data, size); +} + +static void cmd_subprocess(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + char **args = cmd->args[0].v.str_list; + bool playback_only = cmd->args[1].v.i; + + if (!args || !args[0]) { + MP_ERR(mpctx, "program name missing\n"); + cmd->success = false; + return; + } + + void *tmp = talloc_new(NULL); + struct subprocess_cb_ctx ctx = { + .log = mpctx->log, + .talloc_ctx = tmp, + .max_size = cmd->args[2].v.i, + .capture = {0, cmd->args[3].v.i, cmd->args[4].v.i}, + }; + + struct mp_cancel *cancel = NULL; + if (playback_only) + cancel = mpctx->playback_abort; + + mp_core_unlock(mpctx); + + char *error = NULL; + int status = mp_subprocess(args, cancel, &ctx, subprocess_stdout, + subprocess_stderr, &error); + + mp_core_lock(mpctx); + + struct mpv_node *res = &cmd->result; + node_init(res, MPV_FORMAT_NODE_MAP, NULL); + node_map_add_int64(res, "status", status); + node_map_add_flag(res, "killed_by_us", status == MP_SUBPROCESS_EKILLED_BY_US); + node_map_add_string(res, "error_string", error ? error : ""); + const char *sname[] = {NULL, "stdout", "stderr"}; + for (int n = 1; n < 3; n++) { + if (!ctx.capture[n]) + continue; + struct mpv_byte_array *ba = + node_map_add(res, sname[n], MPV_FORMAT_BYTE_ARRAY)->u.ba; + *ba = (struct mpv_byte_array){ + .data = talloc_steal(ba, ctx.output[n].start), + .size = ctx.output[n].len, + }; + } + + talloc_free(tmp); +} + static void cmd_enable_input_section(void *p) { struct mp_cmd_ctx *cmd = p; @@ -6047,6 +6132,17 @@ const struct mp_cmd_def mp_cmds[] = { }}, { "playlist-move", cmd_playlist_move, { ARG_INT, ARG_INT } }, { "run", cmd_run, { ARG_STRING, ARG_STRING }, .vararg = true }, + { "subprocess", cmd_subprocess, + { + OPT_STRINGLIST("args", v.str_list, 0), + OPT_FLAG("playback_only", v.i, 0, OPTDEF_INT(1)), + OPT_BYTE_SIZE("capture_size", v.i64, 0, 0, INT_MAX, + OPTDEF_INT64(64 * 1024 * 1024)), + OPT_FLAG("capture_stdout", v.i, 0, OPTDEF_INT(0)), + OPT_FLAG("capture_stderr", v.i, 0, OPTDEF_INT(0)), + }, + .spawn_thread = true, + }, { "set", cmd_set, { ARG_STRING, ARG_STRING } }, { "change-list", cmd_change_list, { ARG_STRING, ARG_STRING, ARG_STRING } }, -- cgit v1.2.3 From 548ef07864f3e1a40f731b2643f037435ceae46d Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 12 May 2018 15:36:43 +0200 Subject: lua: reimplement mp.subprocess() by invoking the new subprocess command We keep mp.subprocess() with roughly the same semantics for compatibility with scripts (including the internal ytdl script). Seems to work with rhe ytdl wrapper. Not tested further. --- player/command.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index fe6c3fd2ca..f2d7eedd5f 100644 --- a/player/command.c +++ b/player/command.c @@ -5680,7 +5680,7 @@ static void cmd_subprocess(void *p) void *tmp = talloc_new(NULL); struct subprocess_cb_ctx ctx = { - .log = mpctx->log, + .log = mp_log_new(tmp, mpctx->log, cmd->cmd->sender), .talloc_ctx = tmp, .max_size = cmd->args[2].v.i, .capture = {0, cmd->args[3].v.i, cmd->args[4].v.i}, -- cgit v1.2.3 From e4fb23ed7de874bb2d05824d7edb84cfd1b21101 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 12 May 2018 18:46:37 +0200 Subject: command: add a way to abort asynchronous commands Many asynchronous commands are potentially long running operations, such as loading something from network or running a foreign process. Obviously it shouldn't just be possible for them to freeze the player if they don't terminate as expected. Also, there will be situations where you want to explicitly stop some of those operations explicitly. So add an infrastructure for this. Commands have to support this explicitly. The next commit uses this to actually add support to a command. --- player/command.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index f2d7eedd5f..cd623c03f4 100644 --- a/player/command.c +++ b/player/command.c @@ -4865,7 +4865,7 @@ static void continue_cmd_list(struct cmd_list_ctx *list) if (sub->flags & MP_ASYNC_CMD) { // We run it "detached" (fire & forget) - run_command(list->mpctx, sub, NULL, NULL); + run_command(list->mpctx, sub, NULL, NULL, NULL); } else { // Run the next command once this one completes. @@ -4873,7 +4873,7 @@ static void continue_cmd_list(struct cmd_list_ctx *list) list->current_valid = true; list->current = pthread_self(); - run_command(list->mpctx, sub, on_cmd_list_sub_completion, list); + run_command(list->mpctx, sub, NULL, on_cmd_list_sub_completion, list); list->current_valid = false; @@ -4920,8 +4920,9 @@ void mp_cmd_ctx_complete(struct mp_cmd_ctx *cmd) mpv_free_node_contents(&cmd->result); if (cmd->on_completion) cmd->on_completion(cmd); + if (cmd->abort) + mp_abort_remove(cmd->mpctx, cmd->abort); mpv_free_node_contents(&cmd->result); - talloc_free(cmd->cmd); talloc_free(cmd); } @@ -4949,27 +4950,40 @@ static void run_command_on_worker_thread(void *p) // function returns (the caller is supposed to be able to handle both cases). In // both cases, the callback will be called while the core is locked (i.e. you // can access the core freely). +// If abort is non-NULL, then the caller creates the abort object. It must have +// been allocated with talloc. run_command() will register/unregister/destroy +// it. Must not be set if cmd->def->can_abort==false. // on_completion_priv is copied to mp_cmd_ctx.on_completion_priv and can be // accessed from the completion callback. // The completion callback is invoked exactly once. If it's NULL, it's ignored. // Ownership of cmd goes to the caller. void run_command(struct MPContext *mpctx, struct mp_cmd *cmd, + struct mp_abort_entry *abort, void (*on_completion)(struct mp_cmd_ctx *cmd), void *on_completion_priv) { struct mp_cmd_ctx *ctx = talloc(NULL, struct mp_cmd_ctx); *ctx = (struct mp_cmd_ctx){ .mpctx = mpctx, - .cmd = cmd, + .cmd = talloc_steal(ctx, cmd), .args = cmd->args, .num_args = cmd->nargs, .priv = cmd->def->priv, + .abort = talloc_steal(ctx, abort), .success = true, .completed = true, .on_completion = on_completion, .on_completion_priv = on_completion_priv, }; + if (!ctx->abort && cmd->def->can_abort) + ctx->abort = talloc_zero(ctx, struct mp_abort_entry); + + assert(cmd->def->can_abort == !!ctx->abort); + + if (ctx->abort) + mp_abort_add(mpctx, ctx->abort); + struct MPOpts *opts = mpctx->opts; ctx->on_osd = cmd->flags & MP_ON_OSD_FLAGS; bool auto_osd = ctx->on_osd == MP_ON_OSD_AUTO; -- cgit v1.2.3 From 9c530c7ee9ac0641e58ac1203bd46675e8700cc5 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 12 May 2018 18:47:43 +0200 Subject: command: make "subprocess" explicitly abortable Now mpv_abort_async_command() can be used to stop the process. --- player/command.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index cd623c03f4..ffe9683adb 100644 --- a/player/command.c +++ b/player/command.c @@ -5700,15 +5700,16 @@ static void cmd_subprocess(void *p) .capture = {0, cmd->args[3].v.i, cmd->args[4].v.i}, }; - struct mp_cancel *cancel = NULL; - if (playback_only) - cancel = mpctx->playback_abort; + pthread_mutex_lock(&mpctx->abort_lock); + cmd->abort->coupled_to_playback = playback_only; + mp_abort_recheck_locked(mpctx, cmd->abort); + pthread_mutex_unlock(&mpctx->abort_lock); mp_core_unlock(mpctx); char *error = NULL; - int status = mp_subprocess(args, cancel, &ctx, subprocess_stdout, - subprocess_stderr, &error); + int status = mp_subprocess(args, cmd->abort->cancel, &ctx, + subprocess_stdout, subprocess_stderr, &error); mp_core_lock(mpctx); @@ -6156,6 +6157,7 @@ const struct mp_cmd_def mp_cmds[] = { OPT_FLAG("capture_stderr", v.i, 0, OPTDEF_INT(0)), }, .spawn_thread = true, + .can_abort = true, }, { "set", cmd_set, { ARG_STRING, ARG_STRING } }, -- cgit v1.2.3 From 332907e1d7225ae39565d462aac5c45c3a5cad97 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 17 May 2018 20:10:39 +0200 Subject: command: give named arguments to almost all commands Before this change, only 1 command or so had named arguments. There is no reason why other commands can't have them, except that it's a bit of work to add them. Commands with variable number of arguments are inherently incompatible to named arguments, such as the "run" command. They still have dummy names, but obviously you can't assign multiple values to a single named argument (unless the argument has an array type, which would be something different). For now, disallow using named argument APIs with these commands. This might change later. 2 commands are adjusted to not need a separate default value by changing flag constants. (The numeric values are C only and can't be set by users.) Make the command syntax in the manpage more consistent. Now none of the allowed choice/flag names are in the command header, and all arguments are shown with their proper name and quoted with <...>. Some places in the manpage and the client.h doxygen are updated to reflect that most commands support named arguments. In addition, try to improve the documentation of the syntax and need for escaping etc. as well. (Or actually most uses of the word "argument" should be "parameter".) --- player/command.c | 395 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 225 insertions(+), 170 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index ffe9683adb..9c6bce6aa1 100644 --- a/player/command.c +++ b/player/command.c @@ -5623,7 +5623,7 @@ static void cmd_rescan_external_files(void *p) } autoload_external_files(mpctx); - if (cmd->args[0].v.i && mpctx->playback_initialized) { + if (!cmd->args[0].v.i && mpctx->playback_initialized) { // somewhat fuzzy and not ideal struct track *a = select_default_track(mpctx, 0, STREAM_AUDIO); if (a && a->is_external) @@ -5752,7 +5752,7 @@ static void cmd_define_input_section(void *p) struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; mp_input_define_section(mpctx->input, cmd->args[0].v.s, "", - cmd->args[1].v.s, !!cmd->args[2].v.i, + cmd->args[1].v.s, !cmd->args[2].v.i, cmd->cmd->sender); } @@ -6005,263 +6005,318 @@ static void cmd_load_script(void *p) cmd->success = false; } -// This does not specify the real destination of the command parameter values, -// it just provides a dummy for the OPT_ macros. -#define OPT_BASE_STRUCT struct mp_cmd_arg -#define ARG(t) "", v. t - /* This array defines all known commands. - * The first field is an id used to recognize the command. - * The second is the command name used in slave mode and input.conf. - * Then comes the definition of each argument, first mandatory arguments - * (ARG_INT, ARG_FLOAT, ARG_STRING) if any, then optional arguments - * (OARG_INT(default), etc) if any. The command will be given the default - * argument value if the user didn't give enough arguments to specify it. - * A command can take a maximum of MP_CMD_DEF_MAX_ARGS arguments, or more - * if the command uses varargs. + * The first field the command name used in libmpv and input.conf. + * The second field is the handler function (see mp_cmd_def.handler and + * run_command()). + * Then comes the definition of each argument. They are defined like options, + * except that the result is parsed into mp_cmd.args[] (thus the option variable + * is a field in the mp_cmd_arg union field). Arguments are optional if either + * defval is set (usually via OPTDEF_ macros), or the MP_CMD_OPT_ARG flag is + * set, or if it's the last argument and .vararg is set. If .vararg is set, the + * command has an arbitrary number of arguments, all using the type indicated by + * the last argument (they are appended to mp_cmd.args[] starting at the last + * argument's index). + * Arguments have named, which can be used by named argument functions, e.g. in + * Lua with mp.command_native(). */ -#define ARG_INT OPT_INT(ARG(i), 0) -#define ARG_FLOAT OPT_FLOAT(ARG(f), 0) -#define ARG_DOUBLE OPT_DOUBLE(ARG(d), 0) -#define ARG_STRING OPT_STRING(ARG(s), 0) -#define ARG_CHOICE(c) OPT_CHOICE(ARG(i), 0, c) -#define ARG_CHOICE_OR_INT(...) OPT_CHOICE_OR_INT(ARG(i), 0, __VA_ARGS__) -#define ARG_TIME OPT_TIME(ARG(d), 0) -#define OARG_DOUBLE(def) OPT_DOUBLE(ARG(d), 0, OPTDEF_DOUBLE(def)) -#define OARG_INT(def) OPT_INT(ARG(i), 0, OPTDEF_INT(def)) -#define OARG_CHOICE(def, c) OPT_CHOICE(ARG(i), 0, c, OPTDEF_INT(def)) -#define OARG_FLAGS(def, c) OPT_FLAGS(ARG(i), 0, c, OPTDEF_INT(def)) -#define OARG_STRING(def) OPT_STRING(ARG(s), 0, OPTDEF_STR(def)) - -#define OARG_CYCLEDIR(def) OPT_CYCLEDIR(ARG(d), 0, OPTDEF_DOUBLE(def)) +// This does not specify the real destination of the command parameter values, +// it just provides a dummy for the OPT_ macros. The real destination is an +// array item in mp_cmd.args[], using the index of the option definition. +#define OPT_BASE_STRUCT struct mp_cmd_arg const struct mp_cmd_def mp_cmds[] = { { "ignore", cmd_ignore, .is_ignore = true }, - { "seek", cmd_seek, { - ARG_TIME, - OARG_FLAGS(4|0, ({"relative", 4|0}, {"-", 4|0}, - {"absolute-percent", 4|1}, - {"absolute", 4|2}, - {"relative-percent", 4|3}, - {"keyframes", 32|8}, - {"exact", 32|16})), - // backwards compatibility only - OARG_CHOICE(0, ({"unused", 0}, {"default-precise", 0}, + { "seek", cmd_seek, + { + OPT_TIME("target", v.d, 0), + OPT_FLAGS("flags", v.i, 0, + ({"relative", 4|0}, {"-", 4|0}, + {"absolute-percent", 4|1}, + {"absolute", 4|2}, + {"relative-percent", 4|3}, + {"keyframes", 32|8}, + {"exact", 32|16}), + OPTDEF_INT(4|0)), + // backwards compatibility only + OPT_CHOICE("legacy", v.i, MP_CMD_OPT_ARG, + ({"unused", 0}, {"default-precise", 0}, {"keyframes", 32|8}, {"exact", 32|16})), }, .allow_auto_repeat = true, .scalable = true, }, - { "revert-seek", cmd_revert_seek, { - OARG_FLAGS(0, ({"mark", 1})), - }}, - { "quit", cmd_quit, { OARG_INT(0) }, + { "revert-seek", cmd_revert_seek, + {OPT_FLAGS("flags", v.i, MP_CMD_OPT_ARG, ({"mark", 1}))}, + }, + { "quit", cmd_quit, { OPT_INT("code", v.i, MP_CMD_OPT_ARG) }, .priv = &(const bool){0}, .is_abort = true }, - { "quit-watch-later", cmd_quit, { OARG_INT(0) }, + { "quit-watch-later", cmd_quit, { OPT_INT("code", v.i, MP_CMD_OPT_ARG) }, .priv = &(const bool){1}, .is_abort = true }, { "stop", cmd_stop, .is_abort = true }, { "frame-step", cmd_frame_step, .allow_auto_repeat = true, .on_updown = true }, { "frame-back-step", cmd_frame_back_step, .allow_auto_repeat = true }, - { "playlist-next", cmd_playlist_next_prev, { - OARG_CHOICE(0, ({"weak", 0}, - {"force", 1})), + { "playlist-next", cmd_playlist_next_prev, + { + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, ({"weak", 0}, + {"force", 1})), }, .is_soft_abort = true, .priv = &(const int){1}, }, - { "playlist-prev", cmd_playlist_next_prev, { - OARG_CHOICE(0, ({"weak", 0}, - {"force", 1})), + { "playlist-prev", cmd_playlist_next_prev, + { + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, ({"weak", 0}, + {"force", 1})), }, .is_soft_abort = true, .priv = &(const int){-1}, }, { "playlist-shuffle", cmd_playlist_shuffle, }, - { "sub-step", cmd_sub_step_seek, { ARG_INT }, .allow_auto_repeat = true, - .priv = &(const bool){true} }, - { "sub-seek", cmd_sub_step_seek, { ARG_INT }, .allow_auto_repeat = true, - .priv = &(const bool){false} }, - { "print-text", cmd_print_text, { ARG_STRING }, .allow_auto_repeat = true }, - { "show-text", cmd_show_text, { ARG_STRING, OARG_INT(-1), OARG_INT(0) }, + { "sub-step", cmd_sub_step_seek, { OPT_INT("skip", v.i, 0) }, + .allow_auto_repeat = true, .priv = &(const bool){true} }, + { "sub-seek", cmd_sub_step_seek, { OPT_INT("skip", v.i, 0) }, + .allow_auto_repeat = true, .priv = &(const bool){false} }, + { "print-text", cmd_print_text, { OPT_STRING("text", v.s, 0) }, + .allow_auto_repeat = true }, + { "show-text", cmd_show_text, { OPT_STRING("text", v.s, 0), + OPT_INT("duration", v.i, 0, OPTDEF_INT(-1)), + OPT_INT("level", v.i, MP_CMD_OPT_ARG), }, .allow_auto_repeat = true}, - { "expand-text", cmd_expand_text, { ARG_STRING } }, + { "expand-text", cmd_expand_text, { OPT_STRING("text", v.s, 0) } }, { "show-progress", cmd_show_progress, .allow_auto_repeat = true}, - { "sub-add", cmd_track_add, { - ARG_STRING, - OARG_CHOICE(0, ({"select", 0}, {"auto", 1}, {"cached", 2})), - OARG_STRING(""), OARG_STRING(""), + + { "sub-add", cmd_track_add, + { + OPT_STRING("url", v.s, 0), + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, + ({"select", 0}, {"auto", 1}, {"cached", 2})), + OPT_STRING("title", v.s, MP_CMD_OPT_ARG), + OPT_STRING("lang", v.s, MP_CMD_OPT_ARG), }, .priv = &(const int){STREAM_SUB}, .spawn_thread = true, }, - { "sub-remove", cmd_track_remove, { OARG_INT(-1) }, + { "audio-add", cmd_track_add, + { + OPT_STRING("url", v.s, 0), + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, + ({"select", 0}, {"auto", 1}, {"cached", 2})), + OPT_STRING("title", v.s, MP_CMD_OPT_ARG), + OPT_STRING("lang", v.s, MP_CMD_OPT_ARG), + }, + .priv = &(const int){STREAM_AUDIO}, + .spawn_thread = true, + }, + + { "sub-remove", cmd_track_remove, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) }, .priv = &(const int){STREAM_SUB}, }, - { "sub-reload", cmd_track_reload, { OARG_INT(-1) }, + { "audio-remove", cmd_track_remove, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) }, + .priv = &(const int){STREAM_AUDIO}, }, + + { "sub-reload", cmd_track_reload, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) }, .priv = &(const int){STREAM_SUB}, .spawn_thread = true, }, + { "audio-reload", cmd_track_reload, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) }, + .priv = &(const int){STREAM_AUDIO}, + .spawn_thread = true, + }, + + { "rescan-external-files", cmd_rescan_external_files, + { + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, + ({"keep-selection", 1}, + {"reselect", 0})), + }, + .spawn_thread = true, + }, { "tv-last-channel", cmd_tv_last_channel, }, - { "screenshot", cmd_screenshot, { - OARG_FLAGS(4|2, ({"video", 4|0}, {"-", 4|0}, - {"window", 4|1}, - {"subtitles", 4|2}, - {"each-frame", 8})), - // backwards compatibility - OARG_CHOICE(0, ({"unused", 0}, {"single", 0}, - {"each-frame", 8})), + { "screenshot", cmd_screenshot, + { + OPT_FLAGS("flags", v.i, 0, + ({"video", 4|0}, {"-", 4|0}, + {"window", 4|1}, + {"subtitles", 4|2}, + {"each-frame", 8}), + OPTDEF_INT(4|2)), + // backwards compatibility + OPT_CHOICE("legacy", v.i, MP_CMD_OPT_ARG, + ({"unused", 0}, {"single", 0}, + {"each-frame", 8})), }, .spawn_thread = true, }, - { "screenshot-to-file", cmd_screenshot_to_file, { - ARG_STRING, - OARG_CHOICE(2, ({"video", 0}, + { "screenshot-to-file", cmd_screenshot_to_file, + { + OPT_STRING("filename", v.s, 0), + OPT_CHOICE("flags", v.i, 0, + ({"video", 0}, {"window", 1}, - {"subtitles", 2})), + {"subtitles", 2}), + OPTDEF_INT(2)), }, .spawn_thread = true, }, - { "screenshot-raw", cmd_screenshot_raw, { - OARG_CHOICE(2, ({"video", 0}, + { "screenshot-raw", cmd_screenshot_raw, + { + OPT_CHOICE("flags", v.i, 0, + ({"video", 0}, {"window", 1}, - {"subtitles", 2})), - }}, - { "loadfile", cmd_loadfile, { - ARG_STRING, - OARG_CHOICE(0, ({"replace", 0}, + {"subtitles", 2}), + OPTDEF_INT(2)), + }, + }, + { "loadfile", cmd_loadfile, + { + OPT_STRING("url", v.s, 0), + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, + ({"replace", 0}, {"append", 1}, {"append-play", 2})), - OPT_KEYVALUELIST(ARG(str_list), MP_CMD_OPT_ARG), - }}, - { "loadlist", cmd_loadlist, { - ARG_STRING, - OARG_CHOICE(0, ({"replace", 0}, - {"append", 1})), - }}, + OPT_KEYVALUELIST("options", v.str_list, MP_CMD_OPT_ARG), + }, + }, + { "loadlist", cmd_loadlist, { OPT_STRING("url", v.s, 0), + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, + ({"replace", 0}, {"append", 1})), }}, { "playlist-clear", cmd_playlist_clear }, - { "playlist-remove", cmd_playlist_remove, { - ARG_CHOICE_OR_INT(0, INT_MAX, ({"current", -1})), - }}, - { "playlist-move", cmd_playlist_move, { ARG_INT, ARG_INT } }, - { "run", cmd_run, { ARG_STRING, ARG_STRING }, .vararg = true }, + { "playlist-remove", cmd_playlist_remove, + {OPT_CHOICE_OR_INT("index", v.i, MP_CMD_OPT_ARG, 0, INT_MAX, + ({"current", -1}))}, + }, + { "playlist-move", cmd_playlist_move, { OPT_INT("index1", v.i, 0), + OPT_INT("index2", v.i, 0), }}, + { "run", cmd_run, { OPT_STRING("command", v.s, 0), + OPT_STRING("args", v.s, 0), }, + .vararg = true, + }, { "subprocess", cmd_subprocess, { OPT_STRINGLIST("args", v.str_list, 0), OPT_FLAG("playback_only", v.i, 0, OPTDEF_INT(1)), OPT_BYTE_SIZE("capture_size", v.i64, 0, 0, INT_MAX, OPTDEF_INT64(64 * 1024 * 1024)), - OPT_FLAG("capture_stdout", v.i, 0, OPTDEF_INT(0)), - OPT_FLAG("capture_stderr", v.i, 0, OPTDEF_INT(0)), + OPT_FLAG("capture_stdout", v.i, MP_CMD_OPT_ARG), + OPT_FLAG("capture_stderr", v.i, MP_CMD_OPT_ARG), }, .spawn_thread = true, .can_abort = true, }, - { "set", cmd_set, { ARG_STRING, ARG_STRING } }, - { "change-list", cmd_change_list, { ARG_STRING, ARG_STRING, ARG_STRING } }, - { "add", cmd_add_cycle, { ARG_STRING, OARG_DOUBLE(1) }, + { "set", cmd_set, {OPT_STRING("name", v.s, 0), OPT_STRING("value", v.s, 0)}}, + { "change-list", cmd_change_list, { OPT_STRING("name", v.s, 0), + OPT_STRING("operation", v.s, 0), + OPT_STRING("value", v.s, 0) }}, + { "add", cmd_add_cycle, { OPT_STRING("name", v.s, 0), + OPT_DOUBLE("value", v.d, 0, OPTDEF_DOUBLE(1)), }, .allow_auto_repeat = true, .scalable = true, }, - { "cycle", cmd_add_cycle, { - ARG_STRING, - OARG_CYCLEDIR(1), - }, + { "cycle", cmd_add_cycle, { OPT_STRING("name", v.s, 0), + OPT_CYCLEDIR("value", v.d, 0, OPTDEF_DOUBLE(1)), }, .allow_auto_repeat = true, .scalable = true, .priv = "", }, - { "multiply", cmd_multiply, { ARG_STRING, ARG_DOUBLE }, + { "multiply", cmd_multiply, { OPT_STRING("name", v.s, 0), + OPT_DOUBLE("value", v.d, 0)}, .allow_auto_repeat = true}, - { "cycle-values", cmd_cycle_values, { ARG_STRING, ARG_STRING, ARG_STRING }, + { "cycle-values", cmd_cycle_values, { OPT_STRING("arg0", v.s, 0), + OPT_STRING("arg1", v.s, 0), + OPT_STRING("argN", v.s, 0), }, .vararg = true}, - { "enable-section", cmd_enable_input_section, { - ARG_STRING, - OARG_FLAGS(0, ({"default", 0}, + { "enable-section", cmd_enable_input_section, + { + OPT_STRING("name", v.s, 0), + OPT_FLAGS("flags", v.i, MP_CMD_OPT_ARG, + ({"default", 0}, {"exclusive", MP_INPUT_EXCLUSIVE}, {"allow-hide-cursor", MP_INPUT_ALLOW_HIDE_CURSOR}, {"allow-vo-dragging", MP_INPUT_ALLOW_VO_DRAGGING})), - }}, - { "disable-section", cmd_disable_input_section, { ARG_STRING } }, - { "define-section", cmd_define_input_section, { - ARG_STRING, - ARG_STRING, - OARG_CHOICE(1, ({"default", 1}, - {"force", 0})), - }}, + } + }, + { "disable-section", cmd_disable_input_section, + {OPT_STRING("name", v.s, 0) }}, + { "define-section", cmd_define_input_section, + { + OPT_STRING("name", v.s, 0), + OPT_STRING("contents", v.s, 0), + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, + ({"default", 0}, {"force", 1})), + }, + }, { "ab-loop", cmd_ab_loop }, { "drop-buffers", cmd_drop_buffers, }, - { "af", cmd_filter, { ARG_STRING, ARG_STRING }, - .priv = &(const int){STREAM_AUDIO}, }, - { "af-command", cmd_filter_command, { ARG_STRING, ARG_STRING, ARG_STRING }, - .priv = &(const int){STREAM_AUDIO}, }, - { "ao-reload", cmd_ao_reload }, + { "af", cmd_filter, { OPT_STRING("operation", v.s, 0), + OPT_STRING("value", v.s, 0), }, + .priv = &(const int){STREAM_AUDIO} }, + { "vf", cmd_filter, { OPT_STRING("operation", v.s, 0), + OPT_STRING("value", v.s, 0), }, + .priv = &(const int){STREAM_VIDEO} }, + + { "af-command", cmd_filter_command, { OPT_STRING("label", v.s, 0), + OPT_STRING("command", v.s, 0), + OPT_STRING("argument", v.s, 0), }, + .priv = &(const int){STREAM_AUDIO} }, + { "vf-command", cmd_filter_command, { OPT_STRING("label", v.s, 0), + OPT_STRING("command", v.s, 0), + OPT_STRING("argument", v.s, 0), }, + .priv = &(const int){STREAM_VIDEO} }, - { "vf", cmd_filter, { ARG_STRING, ARG_STRING }, - .priv = &(const int){STREAM_VIDEO}, }, - { "vf-command", cmd_filter_command, { ARG_STRING, ARG_STRING, ARG_STRING }, - .priv = &(const int){STREAM_VIDEO}, }, + { "ao-reload", cmd_ao_reload }, - { "script-binding", cmd_script_binding, { ARG_STRING }, + { "script-binding", cmd_script_binding, { OPT_STRING("name", v.s, 0) }, .allow_auto_repeat = true, .on_updown = true}, - { "script-message", cmd_script_message, { ARG_STRING }, .vararg = true }, - { "script-message-to", cmd_script_message_to, { ARG_STRING, ARG_STRING }, + { "script-message", cmd_script_message, { OPT_STRING("args", v.s, 0) }, + .vararg = true }, + { "script-message-to", cmd_script_message_to, { OPT_STRING("target", v.s, 0), + OPT_STRING("args", v.s, 0) }, .vararg = true }, - { "overlay-add", cmd_overlay_add, - { ARG_INT, ARG_INT, ARG_INT, ARG_STRING, ARG_INT, ARG_STRING, ARG_INT, - ARG_INT, ARG_INT }}, - { "overlay-remove", cmd_overlay_remove, { ARG_INT } }, + { "overlay-add", cmd_overlay_add, { OPT_INT("id", v.i, 0), + OPT_INT("x", v.i, 0), + OPT_INT("y", v.i, 0), + OPT_STRING("file", v.s, 0), + OPT_INT("offset", v.i, 0), + OPT_STRING("fmt", v.s, 0), + OPT_INT("w", v.i, 0), + OPT_INT("h", v.i, 0), + OPT_INT("stride", v.i, 0), }}, + { "overlay-remove", cmd_overlay_remove, { OPT_INT("id", v.i, 0) } }, { "write-watch-later-config", cmd_write_watch_later_config }, - { "hook-add", cmd_hook_add, { ARG_STRING, ARG_INT, ARG_INT } }, - { "hook-ack", cmd_hook_ack, { ARG_INT } }, - - { "mouse", cmd_mouse, { - ARG_INT, ARG_INT, // coordinate (x, y) - OARG_INT(-1), // button number - OARG_CHOICE(0, ({"single", 0}, - {"double", 1})), - }}, - { "keypress", cmd_key, { ARG_STRING }, .priv = &(const int){0}}, - { "keydown", cmd_key, { ARG_STRING }, .priv = &(const int){MP_KEY_STATE_DOWN}}, - { "keyup", cmd_key, { OARG_STRING("") }, .priv = &(const int){MP_KEY_STATE_UP}}, - - { "audio-add", cmd_track_add, { - ARG_STRING, - OARG_CHOICE(0, ({"select", 0}, {"auto", 1}, {"cached", 2})), - OARG_STRING(""), OARG_STRING(""), - }, - .priv = &(const int){STREAM_AUDIO}, - .spawn_thread = true, - }, - { "audio-remove", cmd_track_remove, { OARG_INT(-1) }, - .priv = &(const int){STREAM_AUDIO}, }, - { "audio-reload", cmd_track_reload, { OARG_INT(-1) }, - .priv = &(const int){STREAM_AUDIO}, - .spawn_thread = true, - }, - - { "rescan-external-files", cmd_rescan_external_files, { - OARG_CHOICE(1, ({"keep-selection", 0}, - {"reselect", 1})), - }, - .spawn_thread = true, - }, - - { "apply-profile", cmd_apply_profile, {ARG_STRING } }, - - { "load-script", cmd_load_script, {ARG_STRING} }, + { "hook-add", cmd_hook_add, { OPT_STRING("arg0", v.s, 0), + OPT_INT("arg1", v.i, 0), + OPT_INT("arg2", v.i, 0) }}, + { "hook-ack", cmd_hook_ack, { OPT_INT("arg0", v.i, 0) }}, + + { "mouse", cmd_mouse, { OPT_INT("x", v.i, 0), + OPT_INT("y", v.i, 0), + OPT_INT("button", v.i, 0, OPTDEF_INT(-1)), + OPT_CHOICE("mode", v.i, MP_CMD_OPT_ARG, + ({"single", 0}, {"double", 1})), }}, + { "keypress", cmd_key, { OPT_STRING("name", v.s, 0) }, + .priv = &(const int){0}}, + { "keydown", cmd_key, { OPT_STRING("name", v.s, 0) }, + .priv = &(const int){MP_KEY_STATE_DOWN}}, + { "keyup", cmd_key, { OPT_STRING("name", v.s, MP_CMD_OPT_ARG) }, + .priv = &(const int){MP_KEY_STATE_UP}}, + + { "apply-profile", cmd_apply_profile, {OPT_STRING("name", v.s, 0)} }, + + { "load-script", cmd_load_script, {OPT_STRING("filename", v.s, 0)} }, {0} }; -- cgit v1.2.3 From 12d1404b04e90f5357882e5c1048d92305248cb9 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 18 May 2018 21:38:17 +0200 Subject: player: make various commands for managing external tracks abortable Until now, they could be aborted only by ending playback, and calling mpv_abort_async_command didn't do anything. This requires furthering the mess how playback abort is done. The main reason why mp_cancel exists at all is to avoid that a "frozen" demuxer (blocked on network I/O or whatever) cannot freeze the core. The core should always get its way. Previously, there was a single mp_cancel handle, that could be signaled, and all demuxers would unfreeze. With external files, we might want to abort loading of a certain external file, which automatically means they need a separate mp_cancel. So give every demuxer its own mp_cancel, and "slave" it to whatever parent mp_cancel handles aborting. Since the mpv demuxer API conflates creating the demuxer and reading the file headers, mp_cancel strictly need to be created before the demuxer is created (or we couldn't abort loading). Although we give every demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer), it's still rather messy to create/destroy it along with the demuxer. --- player/command.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index 9c6bce6aa1..fb8f6f14c5 100644 --- a/player/command.c +++ b/player/command.c @@ -4981,8 +4981,10 @@ void run_command(struct MPContext *mpctx, struct mp_cmd *cmd, assert(cmd->def->can_abort == !!ctx->abort); - if (ctx->abort) + if (ctx->abort) { + ctx->abort->coupled_to_playback |= cmd->def->abort_on_playback_end; mp_abort_add(mpctx, ctx->abort); + } struct MPOpts *opts = mpctx->opts; ctx->on_osd = cmd->flags & MP_ON_OSD_FLAGS; @@ -5534,7 +5536,8 @@ static void cmd_track_add(void *p) return; } } - int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type); + int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type, + cmd->abort->cancel); if (first < 0) { cmd->success = false; return; @@ -5598,7 +5601,7 @@ static void cmd_track_reload(void *p) if (t && t->is_external && t->external_filename) { char *filename = talloc_strdup(NULL, t->external_filename); mp_remove_track(mpctx, t); - nt_num = mp_add_external_file(mpctx, filename, type); + nt_num = mp_add_external_file(mpctx, filename, type, cmd->abort->cancel); talloc_free(filename); } @@ -5622,7 +5625,7 @@ static void cmd_rescan_external_files(void *p) return; } - autoload_external_files(mpctx); + autoload_external_files(mpctx, cmd->abort->cancel); if (!cmd->args[0].v.i && mpctx->playback_initialized) { // somewhat fuzzy and not ideal struct track *a = select_default_track(mpctx, 0, STREAM_AUDIO); @@ -6098,6 +6101,8 @@ const struct mp_cmd_def mp_cmds[] = { }, .priv = &(const int){STREAM_SUB}, .spawn_thread = true, + .can_abort = true, + .abort_on_playback_end = true, }, { "audio-add", cmd_track_add, { @@ -6109,6 +6114,8 @@ const struct mp_cmd_def mp_cmds[] = { }, .priv = &(const int){STREAM_AUDIO}, .spawn_thread = true, + .can_abort = true, + .abort_on_playback_end = true, }, { "sub-remove", cmd_track_remove, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) }, @@ -6119,10 +6126,14 @@ const struct mp_cmd_def mp_cmds[] = { { "sub-reload", cmd_track_reload, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) }, .priv = &(const int){STREAM_SUB}, .spawn_thread = true, + .can_abort = true, + .abort_on_playback_end = true, }, { "audio-reload", cmd_track_reload, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) }, .priv = &(const int){STREAM_AUDIO}, .spawn_thread = true, + .can_abort = true, + .abort_on_playback_end = true, }, { "rescan-external-files", cmd_rescan_external_files, @@ -6132,6 +6143,8 @@ const struct mp_cmd_def mp_cmds[] = { {"reselect", 0})), }, .spawn_thread = true, + .can_abort = true, + .abort_on_playback_end = true, }, { "tv-last-channel", cmd_tv_last_channel, }, -- cgit v1.2.3 From 76dc5d9aa92f5c91c728004214f7b08c3a85a5f6 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 18 May 2018 22:19:10 +0200 Subject: command: make loadlist command async and abortable Don't allow it to freeze everything when loading a playlist from network (although you definitely shouldn't do that, but whatever). This also affects the really obscure --ordered-chapters-files option. The --playlist option on the other hand has no choice but to freeze the shit, because there's no concept of aborting the player during command line parsing. --- player/command.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index fb8f6f14c5..55ad018c8c 100644 --- a/player/command.c +++ b/player/command.c @@ -5387,7 +5387,8 @@ static void cmd_loadlist(void *p) char *filename = cmd->args[0].v.s; bool append = cmd->args[1].v.i; - struct playlist *pl = playlist_parse_file(filename, mpctx->global); + struct playlist *pl = playlist_parse_file(filename, cmd->abort->cancel, + mpctx->global); if (pl) { prepare_playlist(mpctx, pl); struct playlist_entry *new = pl->current; @@ -6196,7 +6197,10 @@ const struct mp_cmd_def mp_cmds[] = { }, { "loadlist", cmd_loadlist, { OPT_STRING("url", v.s, 0), OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, - ({"replace", 0}, {"append", 1})), }}, + ({"replace", 0}, {"append", 1})), }, + .spawn_thread = true, + .can_abort = true, + }, { "playlist-clear", cmd_playlist_clear }, { "playlist-remove", cmd_playlist_remove, {OPT_CHOICE_OR_INT("index", v.i, MP_CMD_OPT_ARG, 0, INT_MAX, -- cgit v1.2.3 From d7ca95c3ea90782c786a6a607d3713bb42a104b1 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 19 May 2018 12:08:48 +0200 Subject: command: whitelist some blocking accesses for certain demuxers/streams The properties/commands touched in this commit are all for obscure special inputs (BD/DVD/DVB/TV), and they all block on the demuxer/stream layer. For network streams, this blocking is very unwelcome. They will affect playback and probably introduce pauses and frame drops. The player can even freeze fully, and the logic that tries to make playback abortable even if frozen complicates the player. Since the mentioned accesses are not needed for network streams, but they will block on network streams even though they're going to fail, add a flag that coarsely enables/disables these accesses. Essentially it establishes a whitelist of demuxers/streams which support them. In theory you could to access BD/DVD images over network (or add such support, I don't think it's a thing in mpv). In these cases these controls still can block and could even "freeze" the player completely. Writing to the "program" and "cache-size" properties still can block even for network streams. Just don't use them if you don't want freezes. --- player/command.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'player/command.c') diff --git a/player/command.c b/player/command.c index 55ad018c8c..292384f2d9 100644 --- a/player/command.c +++ b/player/command.c @@ -907,7 +907,7 @@ static int mp_property_disc_title(void *ctx, struct m_property *prop, { MPContext *mpctx = ctx; struct demuxer *d = mpctx->demuxer; - if (!d) + if (!d || !d->extended_ctrls) return M_PROPERTY_UNAVAILABLE; unsigned int title = -1; switch (action) { @@ -1224,8 +1224,9 @@ static int mp_property_disc_titles(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; struct demuxer *demuxer = mpctx->demuxer; unsigned int num_titles; - if (!demuxer || demux_stream_control(demuxer, STREAM_CTRL_GET_NUM_TITLES, - &num_titles) < 1) + if (!demuxer || !demuxer->extended_ctrls || + demux_stream_control(demuxer, STREAM_CTRL_GET_NUM_TITLES, + &num_titles) < 1) return M_PROPERTY_UNAVAILABLE; return m_property_int_ro(action, arg, num_titles); } @@ -1255,8 +1256,9 @@ static int mp_property_list_disc_titles(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; struct demuxer *demuxer = mpctx->demuxer; unsigned int num_titles; - if (!demuxer || demux_stream_control(demuxer, STREAM_CTRL_GET_NUM_TITLES, - &num_titles) < 1) + if (!demuxer || !demuxer->extended_ctrls || + demux_stream_control(demuxer, STREAM_CTRL_GET_NUM_TITLES, + &num_titles) < 1) return M_PROPERTY_UNAVAILABLE; return m_property_read_list(action, arg, num_titles, get_disc_title_entry, mpctx); @@ -1291,7 +1293,7 @@ static int mp_property_angle(void *ctx, struct m_property *prop, { MPContext *mpctx = ctx; struct demuxer *demuxer = mpctx->demuxer; - if (!demuxer) + if (!demuxer || !demuxer->extended_ctrls)