From 8e4fa5fcd15ecba6a046aa71650bbfc759856fa8 Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 15 Oct 2014 23:09:53 +0200 Subject: command: add a mechanism to allow scripts to intercept file loads A vague idea to get something similar what libquvi did. Undocumented because it might change a lot, or even be removed. To give an idea what it does, a Lua script could do the following: -- type ID priority mp.commandv("hook_add", "on_load", 0, 0) mp.register_script_message("hook_run", function(param, param2) -- param is "0", the user-chosen ID from the hook_add command -- param2 is the magic value that has to be passed to finish -- the hook mp.resume_all() -- do something, maybe set options that are reset on end: mp.set_property("file-local-options/name", "value") -- or change the URL that's being opened: local url = mp.get_property("stream-open-filename") mp.set_property("stream-open-filename", url .. ".png") -- let the player (or the next script) continue mp.commandv("hook_ack", param2) end) --- input/cmd_list.c | 3 ++ input/cmd_list.h | 3 ++ input/input.h | 1 + player/client.c | 4 ++ player/command.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ player/command.h | 4 ++ player/core.h | 1 + player/loadfile.c | 51 +++++++++++++++----- 8 files changed, 193 insertions(+), 12 deletions(-) diff --git a/input/cmd_list.c b/input/cmd_list.c index bac9b7931a..e6329b9d89 100644 --- a/input/cmd_list.c +++ b/input/cmd_list.c @@ -170,6 +170,9 @@ const struct mp_cmd_def mp_cmds[] = { { MP_CMD_WRITE_WATCH_LATER_CONFIG, "write_watch_later_config", }, + { MP_CMD_HOOK_ADD, "hook_add", { ARG_STRING, ARG_INT, ARG_INT } }, + { MP_CMD_HOOK_ACK, "hook_ack", { ARG_STRING } }, + {0} }; diff --git a/input/cmd_list.h b/input/cmd_list.h index b2b8e2a4d2..7d395cb329 100644 --- a/input/cmd_list.h +++ b/input/cmd_list.h @@ -97,6 +97,9 @@ enum mp_command_type { MP_CMD_WRITE_WATCH_LATER_CONFIG, + MP_CMD_HOOK_ADD, + MP_CMD_HOOK_ACK, + // Internal MP_CMD_COMMAND_LIST, // list of sub-commands in args[0].v.p }; diff --git a/input/input.h b/input/input.h index 40c9369271..877f79a0e6 100644 --- a/input/input.h +++ b/input/input.h @@ -82,6 +82,7 @@ typedef struct mp_cmd { struct mp_cmd *queue_next; double scale; // for scaling numeric arguments const struct mp_cmd_def *def; + char *sender; // name of the client API user which sent this } mp_cmd_t; struct mp_input_src { diff --git a/player/client.c b/player/client.c index 38b0898f86..2166255c2c 100644 --- a/player/client.c +++ b/player/client.c @@ -871,6 +871,8 @@ static int run_client_command(mpv_handle *ctx, struct mp_cmd *cmd) if (mp_input_is_abort_cmd(cmd)) mp_cancel_trigger(ctx->mpctx->playback_abort); + cmd->sender = ctx->name; + struct cmd_request req = { .mpctx = ctx->mpctx, .cmd = cmd, @@ -905,6 +907,8 @@ static int run_cmd_async(mpv_handle *ctx, uint64_t ud, struct mp_cmd *cmd) if (!cmd) return MPV_ERROR_INVALID_PARAMETER; + cmd->sender = ctx->name; + struct cmd_request *req = talloc_ptrtype(NULL, req); *req = (struct cmd_request){ .mpctx = ctx->mpctx, diff --git a/player/command.c b/player/command.c index ee782da8ee..2d05ff6c4e 100644 --- a/player/command.c +++ b/player/command.c @@ -85,6 +85,10 @@ struct command_ctx { struct sub_bitmaps overlay_osd[2]; struct sub_bitmaps *overlay_osd_current; + struct hook_handler **hooks; + int num_hooks; + int64_t hook_seq; // for hook_handler.seq + struct ao_device_list *cached_ao_devices; }; @@ -94,11 +98,107 @@ struct overlay { struct sub_bitmap osd; }; +struct hook_handler { + char *client; // client API user name + char *type; // kind of hook, e.g. "on_load" + char *user_id; // numeric user-chosen ID, printed as string + int priority; // priority for global hook order + int64_t seq; // unique ID (also age -> fixed order for equal priorities) + bool active; // hook is currently in progress (only 1 at a time for now) +}; + static int edit_filters(struct MPContext *mpctx, enum stream_type mediatype, const char *cmd, const char *arg); static int set_filters(struct MPContext *mpctx, enum stream_type mediatype, struct m_obj_settings *new_chain); +bool mp_hook_test_completion(struct MPContext *mpctx, char *type) +{ + struct command_ctx *cmd = mpctx->command_ctx; + for (int n = 0; n < cmd->num_hooks; n++) { + struct hook_handler *h = cmd->hooks[n]; + if (h->active && strcmp(h->type, type) == 0) + return false; + } + return true; +} + +static bool send_hook_msg(struct MPContext *mpctx, struct hook_handler *h, + char *cmd) +{ + mpv_event_client_message *m = talloc_ptrtype(NULL, m); + *m = (mpv_event_client_message){0}; + MP_TARRAY_APPEND(m, m->args, m->num_args, "hook_run"); + MP_TARRAY_APPEND(m, m->args, m->num_args, talloc_strdup(m, h->user_id)); + MP_TARRAY_APPEND(m, m->args, m->num_args, talloc_strdup(m, h->type)); + bool r = + mp_client_send_event(mpctx, h->client, MPV_EVENT_CLIENT_MESSAGE, m) >= 0; + if (!r) + MP_WARN(mpctx, "Sending hook command failed.\n"); + return r; +} + +// client==NULL means start the hook chain +void mp_hook_run(struct MPContext *mpctx, char *client, char *type) +{ + struct command_ctx *cmd = mpctx->command_ctx; + struct hook_handler *next = NULL; + bool found_current = !client; + for (int n = 0; n < cmd->num_hooks; n++) { + struct hook_handler *h = cmd->hooks[n]; + if (!found_current) { + if (h->active && strcmp(h->type, type) == 0) { + h->active = false; + found_current = true; + } + } else if (strcmp(h->type, type) == 0) { + next = h; + break; + } + } + if (!next) + return; + MP_VERBOSE(mpctx, "Running hook: %s/%s\n", next->client, type); + next->active = true; + send_hook_msg(mpctx, next, "hook_run"); +} + +void mp_hook_abort(struct MPContext *mpctx, char *type) +{ + struct command_ctx *cmd = mpctx->command_ctx; + for (int n = 0; n < cmd->num_hooks; n++) { + struct hook_handler *h = cmd->hooks[n]; + if (h->active && strcmp(h->type, type) == 0) + send_hook_msg(mpctx, h, "hook_abort"); + } +} + +static int compare_hook(const void *pa, const void *pb) +{ + struct hook_handler **h1 = (void *)pa; + struct hook_handler **h2 = (void *)pb; + if ((*h1)->priority != (*h2)->priority) + return (*h1)->priority - (*h2)->priority; + return (*h1)->seq - (*h2)->seq; +} + +static void mp_hook_add(struct MPContext *mpctx, char *client, char *name, + int id, int pri) +{ + struct command_ctx *cmd = mpctx->command_ctx; + struct hook_handler *h = talloc_ptrtype(cmd, h); + int64_t seq = cmd->hook_seq++; + *h = (struct hook_handler){ + .client = talloc_strdup(h, client), + .type = talloc_strdup(h, name), + .user_id = talloc_asprintf(h, "%d", id), + .priority = pri, + .seq = seq, + }; + MP_TARRAY_APPEND(cmd, cmd->hooks, cmd->num_hooks, h); + qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook); +} + // Call before a seek, in order to allow revert_seek to undo the seek. static void mark_seek(struct MPContext *mpctx) { @@ -213,6 +313,27 @@ static int mp_property_filename(void *ctx, struct m_property *prop, return r; } +static int mp_property_stream_open_filename(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + if (!mpctx->stream_open_filename || !mpctx->playing) + return M_PROPERTY_UNAVAILABLE; + switch (action) { + case M_PROPERTY_SET: { + if (mpctx->stream) + return M_PROPERTY_ERROR; + mpctx->stream_open_filename = + talloc_strdup(mpctx->stream_open_filename, *(char **)arg); + return M_PROPERTY_OK; + } + case M_PROPERTY_GET_TYPE: + case M_PROPERTY_GET: + return m_property_strdup_ro(action, arg, mpctx->stream_open_filename); + } + return M_PROPERTY_NOT_IMPLEMENTED; +} + static int mp_property_file_size(void *ctx, struct m_property *prop, int action, void *arg) { @@ -2771,6 +2892,7 @@ static const struct m_property mp_properties[] = { {"loop-file", mp_property_generic_option}, {"speed", mp_property_playback_speed}, {"filename", mp_property_filename}, + {"stream-open-filename", mp_property_stream_open_filename}, {"file-size", mp_property_file_size}, {"path", mp_property_path}, {"media-title", mp_property_media_title}, @@ -4066,6 +4188,22 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd) break; } + case MP_CMD_HOOK_ADD: + if (!cmd->sender) { + MP_ERR(mpctx, "Can be used from client API only.\n"); + return -1; + } + mp_hook_add(mpctx, cmd->sender, cmd->args[0].v.s, cmd->args[1].v.i, + cmd->args[2].v.i); + break; + case MP_CMD_HOOK_ACK: + if (!cmd->sender) { + MP_ERR(mpctx, "Can be used from client API only.\n"); + return -1; + } + mp_hook_run(mpctx, cmd->sender, cmd->args[0].v.s); + break; + default: MP_VERBOSE(mpctx, "Received unknown cmd %s\n", cmd->name); return -1; diff --git a/player/command.h b/player/command.h index 6e3312b806..f9bb7c49a0 100644 --- a/player/command.h +++ b/player/command.h @@ -43,4 +43,8 @@ uint64_t mp_get_property_event_mask(const char *name); #define INTERNAL_EVENT_BASE 24 #define MP_EVENT_CACHE_UPDATE (INTERNAL_EVENT_BASE + 0) +bool mp_hook_test_completion(struct MPContext *mpctx, char *type); +void mp_hook_run(struct MPContext *mpctx, char *client, char *type); +void mp_hook_abort(struct MPContext *mpctx, char *type); + #endif /* MPLAYER_COMMAND_H */ diff --git a/player/core.h b/player/core.h index f16cca166d..4f88710081 100644 --- a/player/core.h +++ b/player/core.h @@ -179,6 +179,7 @@ typedef struct MPContext { struct playlist *playlist; struct playlist_entry *playing; // currently playing file char *filename; // immutable copy of playing->filename (or NULL) + char *stream_open_filename; struct mp_resolve_result *resolve_result; enum stop_play_reason stop_play; bool playback_initialized; // playloop can be run/is running diff --git a/player/loadfile.c b/player/loadfile.c index 306e340127..6d6de1676b 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -794,6 +794,36 @@ static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl) } } +static int process_open_hooks(struct MPContext *mpctx) +{ + + mp_hook_run(mpctx, NULL, "on_load"); + + while (!mp_hook_test_completion(mpctx, "on_load")) { + mp_idle(mpctx); + if (mpctx->stop_play) { + if (mpctx->stop_play == PT_QUIT) + return -1; + // Can't exit immediately, the script would interfere with the + // next file being loaded. + mp_hook_abort(mpctx, "on_load"); + } + } + + // quvi stuff + char *filename = mpctx->stream_open_filename; + mpctx->resolve_result = resolve_url(filename, mpctx->global); + if (mpctx->resolve_result) { + print_resolve_contents(mpctx->log, mpctx->resolve_result); + if (mpctx->resolve_result->playlist) { + transfer_playlist(mpctx, mpctx->resolve_result->playlist); + return 1; + } + mpctx->stream_open_filename = mpctx->resolve_result->url; + } + return 0; +} + static void print_timeline(struct MPContext *mpctx) { if (mpctx->timeline) { @@ -936,6 +966,7 @@ static void play_current_file(struct MPContext *mpctx) mpctx->playing->reserved += 1; mpctx->filename = talloc_strdup(tmp, mpctx->playing->filename); + mpctx->stream_open_filename = mpctx->filename; mpctx->add_osd_seek_info &= OSD_SEEK_INFO_EDITION; @@ -972,21 +1003,16 @@ static void play_current_file(struct MPContext *mpctx) assert(mpctx->d_sub[0] == NULL); assert(mpctx->d_sub[1] == NULL); - char *stream_filename = mpctx->filename; - mpctx->resolve_result = resolve_url(stream_filename, mpctx->global); - if (mpctx->resolve_result) { - talloc_steal(tmp, mpctx->resolve_result); - print_resolve_contents(mpctx->log, mpctx->resolve_result); - if (mpctx->resolve_result->playlist) { - transfer_playlist(mpctx, mpctx->resolve_result->playlist); - goto terminate_playback; - } - stream_filename = mpctx->resolve_result->url; - } + int hooks_res = process_open_hooks(mpctx); + talloc_steal(tmp, mpctx->resolve_result); + if (hooks_res) + goto terminate_playback; // quit or preloaded playlist special-case + int stream_flags = STREAM_READ; if (!opts->load_unsafe_playlists) stream_flags |= mpctx->playing->stream_flags; - mpctx->stream = open_stream_async(mpctx, stream_filename, stream_flags); + mpctx->stream = open_stream_async(mpctx, mpctx->stream_open_filename, + stream_flags); if (!mpctx->stream) goto terminate_playback; @@ -1245,6 +1271,7 @@ terminate_playback: playlist_entry_unref(mpctx->playing); mpctx->playing = NULL; mpctx->filename = NULL; + mpctx->stream_open_filename = NULL; talloc_free(tmp); } -- cgit v1.2.3