summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2018-03-23 16:24:49 +0100
committerKevin Mitchell <kevmitch@gmail.com>2018-03-26 23:02:23 -0700
commitf60826c3a14ba3b49077f17e5364b7347f9b468a (patch)
tree285f39963a9f978939743b12527539e1ec8a3f54
parent6d7cfdfae582353e1f10797bb2c587e6ada0aed7 (diff)
downloadmpv-f60826c3a14ba3b49077f17e5364b7347f9b468a.tar.bz2
mpv-f60826c3a14ba3b49077f17e5364b7347f9b468a.tar.xz
client API: add a first class hook API, and deprecate old API
As it turns out, there are multiple libmpv users who saw a need to use the hook API. The API is kind of shitty and was never meant to be actually public (it was mostly a hack for the ytdl script). Introduce a proper API and deprecate the old one. The old one will probably continue to work for a few releases, but will be removed eventually. There are some slight changes to the old API, but if a user followed the manual properly, it won't break. Mostly untested. Appears to work with ytdl_hook.
-rw-r--r--DOCS/client-api-changes.rst3
-rw-r--r--DOCS/interface-changes.rst2
-rw-r--r--DOCS/man/input.rst77
-rw-r--r--input/cmd_list.c2
-rw-r--r--libmpv/client.h79
-rw-r--r--libmpv/mpv.def2
-rw-r--r--player/client.c23
-rw-r--r--player/client.h2
-rw-r--r--player/command.c139
-rw-r--r--player/command.h7
-rw-r--r--player/loadfile.c6
-rw-r--r--player/lua.c24
-rw-r--r--player/lua/defaults.lua17
-rw-r--r--player/scripting.c3
14 files changed, 283 insertions, 103 deletions
diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst
index 83db23df30..a451f6e032 100644
--- a/DOCS/client-api-changes.rst
+++ b/DOCS/client-api-changes.rst
@@ -41,6 +41,9 @@ API changes
- deprecate mpv_get_wakeup_pipe(). It's complex, but easy to replace
using normal API (just set a wakeup callback to a function which
writes to a pipe).
+ - add a 1st class hook API, which replaces the hacky mpv_command()
+ based one. The old API is deprecated and will be removed soon. The
+ old API was never meant to be stable, while the new API is.
1.29 - the behavior of mpv_terminate_destroy() and mpv_detach_destroy()
changes subtly (see documentation in the header file). In particular,
mpv_detach_destroy() will not leave the player running in all
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index 268a34c686..4aae43db7a 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -82,6 +82,8 @@ Interface changes
- change vf_vavpp default to use the best deinterlace algorithm by default
- remove a compatibility hack that allowed CLI aliases to be set as property
(such as "sub-file"), deprecated in mpv 0.26.0
+ - deprecate the old command based hook API, and introduce a proper C API
+ (the high level Lua API for this does not change)
--- mpv 0.28.0 ---
- rename --hwdec=mediacodec option to mediacodec-copy, to reflect
conventions followed by other hardware video decoding APIs
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index 120337e598..7efc700550 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -762,40 +762,8 @@ and obscure way to handle events that require stricter coordination. There are
no API stability guarantees made. Not following the protocol exactly can make
the player freeze randomly. Basically, nobody should use this API.
-There are two special commands involved. Also, the client must listen for
-client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
-
-``hook-add <hook-name> <id> <priority>``
- Subscribe to the hook identified by the first argument (basically, the
- name of event). The ``id`` argument is an arbitrary integer chosen by the
- user. ``priority`` is used to sort all hook handlers globally across all
- clients. Each client can register multiple hook handlers (even for the
- same hook-name). Once the hook is registered, it cannot be unregistered.
-
- When a specific event happens, all registered handlers are run serially.
- This uses a protocol every client has to follow explicitly. When a hook
- handler is run, a client message (``MPV_EVENT_CLIENT_MESSAGE``) is sent to
- the client which registered the hook. This message has the following
- arguments:
-
- 1. the string ``hook_run``
- 2. the ``id`` argument the hook was registered with as string (this can be
- used to correctly handle multiple hooks registered by the same client,
- as long as the ``id`` argument is unique in the client)
- 3. something undefined, used by the hook mechanism to track hook execution
- (currently, it's the hook-name, but this might change without warning)
-
- Upon receiving this message, the client can handle the event. While doing
- this, the player core will still react to requests, but playback will
- typically be stopped.
-
- When the client is done, it must continue the core's hook execution by
- running the ``hook-ack`` command.
-
-``hook-ack <string>``
- Run the next hook in the global chain of hooks. The argument is the 3rd
- argument of the client message that starts hook execution for the
- current client.
+The C API is described in the header files. The Lua API is described in the
+Lua section.
The following hooks are currently defined:
@@ -830,6 +798,47 @@ The following hooks are currently defined:
Run before closing a file, and before actually uninitializing
everything. It's not possible to resume playback in this state.
+Legacy hook API
+~~~~~~~~~~~~~~~
+
+.. warning::
+
+ The legacy API is deprecated and will be removed soon.
+
+There are two special commands involved. Also, the client must listen for
+client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
+
+``hook-add <hook-name> <id> <priority>``
+ Subscribe to the hook identified by the first argument (basically, the
+ name of event). The ``id`` argument is an arbitrary integer chosen by the
+ user. ``priority`` is used to sort all hook handlers globally across all
+ clients. Each client can register multiple hook handlers (even for the
+ same hook-name). Once the hook is registered, it cannot be unregistered.
+
+ When a specific event happens, all registered handlers are run serially.
+ This uses a protocol every client has to follow explicitly. When a hook
+ handler is run, a client message (``MPV_EVENT_CLIENT_MESSAGE``) is sent to
+ the client which registered the hook. This message has the following
+ arguments:
+
+ 1. the string ``hook_run``
+ 2. the ``id`` argument the hook was registered with as string (this can be
+ used to correctly handle multiple hooks registered by the same client,
+ as long as the ``id`` argument is unique in the client)
+ 3. something undefined, used by the hook mechanism to track hook execution
+
+ Upon receiving this message, the client can handle the event. While doing
+ this, the player core will still react to requests, but playback will
+ typically be stopped.
+
+ When the client is done, it must continue the core's hook execution by
+ running the ``hook-ack`` command.
+
+``hook-ack <string>``
+ Run the next hook in the global chain of hooks. The argument is the 3rd
+ argument of the client message that starts hook execution for the
+ current client.
+
Input Command Prefixes
----------------------
diff --git a/input/cmd_list.c b/input/cmd_list.c
index 58c7601c84..5e1780c24c 100644
--- a/input/cmd_list.c
+++ b/input/cmd_list.c
@@ -209,7 +209,7 @@ 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 } },
+ { MP_CMD_HOOK_ACK, "hook-ack", { ARG_INT } },
{ MP_CMD_MOUSE, "mouse", {
ARG_INT, ARG_INT, // coordinate (x, y)
diff --git a/libmpv/client.h b/libmpv/client.h
index cd646eb218..77c149e405 100644
--- a/libmpv/client.h
+++ b/libmpv/client.h
@@ -1133,6 +1133,9 @@ int mpv_get_property_async(mpv_handle *ctx, uint64_t reply_userdata,
* property yourself. Try to avoid endless feedback loops, which could happen
* if you react to the change notifications triggered by your own change.
*
+ * Only the mpv_handle on which this was called will receive the property
+ * change events, or can unobserve them.
+ *
* @param reply_userdata This will be used for the mpv_event.reply_userdata
* field for the received MPV_EVENT_PROPERTY_CHANGE
* events. (Also see section about asynchronous calls,
@@ -1350,7 +1353,14 @@ typedef enum mpv_event_id {
* Event delivery will continue normally once this event was returned
* (this forces the client to empty the queue completely).
*/
- MPV_EVENT_QUEUE_OVERFLOW = 24
+ MPV_EVENT_QUEUE_OVERFLOW = 24,
+ /**
+ * Triggered if a hook handler was registered with mpv_hook_add(), and the
+ * hook is invoked. If you receive this, you must handle it, and continue
+ * the hook with mpv_hook_continue().
+ * See also mpv_event and mpv_event_hook.
+ */
+ MPV_EVENT_HOOK = 25,
// Internal note: adjust INTERNAL_EVENT_BASE when adding new events.
} mpv_event_id;
@@ -1514,6 +1524,17 @@ typedef struct mpv_event_client_message {
const char **args;
} mpv_event_client_message;
+typedef struct mpv_event_hook {
+ /**
+ * The hook name as passed to mpv_hook_add().
+ */
+ const char *name;
+ /**
+ * Internal ID that must be passed to mpv_hook_continue().
+ */
+ uint64_t id;
+} mpv_event_hook;
+
typedef struct mpv_event {
/**
* One of mpv_event. Keep in mind that later ABI compatible releases might
@@ -1682,6 +1703,62 @@ void mpv_set_wakeup_callback(mpv_handle *ctx, void (*cb)(void *d), void *d);
*/
void mpv_wait_async_requests(mpv_handle *ctx);
+/**
+ * A hook is like a synchronous event that blocks the player. You register
+ * a hook handler with this function. You will get an event, which you need
+ * to handle, and once things are ready, you can let the player continue with
+ * mpv_hook_continue().
+ *
+ * Currently, hooks can't be removed explicitly. But they will be implicitly
+ * removed if the mpv_handle it was registered with is destroyed. This also
+ * continues the hook if it was being handled by the destroyed mpv_handle (but
+ * this should be avoided, as it might mess up order of hook execution).
+ *
+ * Hook handlers are ordered globally by priority and order of registration.
+ * Handlers for the same hook with same priority are invoked in order of
+ * registration (the handler registered first is run first). Handlers with
+ * lower priority are run first (which seems backward).
+ *
+ * See the "Hooks" section in the manpage to see which hooks are currently
+ * defined.
+ *
+ * Some hooks might be reentrant (so you get multiple MPV_EVENT_HOOK for the
+ * same hook). If this can happen for a specific hook type, it will be
+ * explicitly documented in the manpage.
+ *
+ * Only the mpv_handle on which this was called will receive the hook events,
+ * or can "continue" them.
+ *
+ * @param reply_userdata This will be used for the mpv_event.reply_userdata
+ * field for the received MPV_EVENT_HOOK events.
+ * If you have no use for this, pass 0.
+ * @param name The hook name. This should be one of the documented names. But
+ * if the name is unknown, the hook event will simply be never
+ * raised.
+ * @param priority See remarks above. Use 0 as a neutral default.
+ * @return error code (usually fails only on OOM)
+ */
+int mpv_hook_add(mpv_handle *ctx, uint64_t reply_userdata,
+ const char *name, int priority);
+
+/**
+ * Respond to a MPV_EVENT_HOOK event. You must call this after you have handled
+ * the event. There is no way to "cancel" or "stop" the hook.
+ *
+ * Calling this will will typically unblock the player for whatever the hook
+ * is responsible for (e.g. for the "on_load" hook it lets it continue
+ * playback).
+ *
+ * It is explicitly undefined behavior to call this more than once for each
+ * MPV_EVENT_HOOK, to pass an incorrect ID, or to call this on a mpv_handle
+ * different from the one that registered the handler and received the event.
+ *
+ * @param id This must be the value of the mpv_event_hook.id field for the
+ * corresponding MPV_EVENT_HOOK.
+ * @return error code
+ */
+int mpv_hook_continue(mpv_handle *ctx, uint64_t id);
+
#if MPV_ENABLE_DEPRECATED
/**
diff --git a/libmpv/mpv.def b/libmpv/mpv.def
index ccd98422a7..1d828f4b2b 100644
--- a/libmpv/mpv.def
+++ b/libmpv/mpv.def
@@ -21,6 +21,8 @@ mpv_get_property_string
mpv_get_sub_api
mpv_get_time_us
mpv_get_wakeup_pipe
+mpv_hook_add
+mpv_hook_continue
mpv_initialize
mpv_load_config_file
mpv_observe_property
diff --git a/player/client.c b/player/client.c
index c6803806c9..1fe38881ad 100644
--- a/player/client.c
+++ b/player/client.c
@@ -728,7 +728,7 @@ void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data)
// If client_name == NULL, then broadcast and free the event.
int mp_client_send_event(struct MPContext *mpctx, const char *client_name,
- int event, void *data)
+ uint64_t reply_userdata, int event, void *data)
{
if (!client_name) {
mp_client_broadcast_event(mpctx, event, data);
@@ -742,6 +742,7 @@ int mp_client_send_event(struct MPContext *mpctx, const char *client_name,
struct mpv_event event_data = {
.event_id = event,
.data = data,
+ .reply_userdata = reply_userdata,
};
pthread_mutex_lock(&clients->lock);
@@ -773,7 +774,7 @@ int mp_client_send_event_dup(struct MPContext *mpctx, const char *client_name,
};
dup_event_data(&event_data);
- return mp_client_send_event(mpctx, client_name, event, event_data.data);
+ return mp_client_send_event(mpctx, client_name, 0, event, event_data.data);
}
int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable)
@@ -1558,6 +1559,23 @@ static bool gen_property_change_event(struct mpv_handle *ctx)
return false;
}
+int mpv_hook_add(mpv_handle *ctx, uint64_t reply_userdata,
+ const char *name, int priority)
+{
+ lock_core(ctx);
+ mp_hook_add(ctx->mpctx, ctx->name, name, reply_userdata, priority, false);
+ unlock_core(ctx);
+ return 0;
+}
+
+int mpv_hook_continue(mpv_handle *ctx, uint64_t id)
+{
+ lock_core(ctx);
+ int r = mp_hook_continue(ctx->mpctx, ctx->name, id);
+ unlock_core(ctx);
+ return r;
+}
+
int mpv_load_config_file(mpv_handle *ctx, const char *filename)
{
int flags = ctx->mpctx->initialized ? M_SETOPT_RUNTIME : 0;
@@ -1708,6 +1726,7 @@ static const char *const event_table[] = {
[MPV_EVENT_PROPERTY_CHANGE] = "property-change",
[MPV_EVENT_CHAPTER_CHANGE] = "chapter-change",
[MPV_EVENT_QUEUE_OVERFLOW] = "event-queue-overflow",
+ [MPV_EVENT_HOOK] = "hook",
};
const char *mpv_event_name(mpv_event_id event)
diff --git a/player/client.h b/player/client.h
index 9ecbe2ed35..b1c2ffc500 100644
--- a/player/client.h
+++ b/player/client.h
@@ -25,7 +25,7 @@ bool mp_clients_all_initialized(struct MPContext *mpctx);
bool mp_client_exists(struct MPContext *mpctx, const char *client_name);
void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data);
int mp_client_send_event(struct MPContext *mpctx, const char *client_name,
- int event, void *data);
+ uint64_t reply_userdata, int event, void *data);
int mp_client_send_event_dup(struct MPContext *mpctx, const char *client_name,
int event, void *data);
bool mp_client_event_is_registered(struct MPContext *mpctx, int event);
diff --git a/player/command.c b/player/command.c
index 572930e69f..da5a7e39f5 100644
--- a/player/command.c
+++ b/player/command.c
@@ -115,9 +115,10 @@ struct overlay {
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
+ uint64_t user_id; // user-chosen ID
int priority; // priority for global hook order
- int64_t seq; // unique ID (also age -> fixed order for equal priorities)
+ int64_t seq; // unique ID, != 0, also for fixed order on equal priorities
+ bool legacy; // old cmd based hook API
bool active; // hook is currently in progress (only 1 at a time for now)
};
@@ -137,12 +138,17 @@ static int set_filters(struct MPContext *mpctx, enum stream_type mediatype,
static int mp_property_do_silent(const char *name, int action, void *val,
struct MPContext *ctx);
-static void hook_remove(struct MPContext *mpctx, int index)
+static void hook_remove(struct MPContext *mpctx, struct hook_handler *h)
{
struct command_ctx *cmd = mpctx->command_ctx;
- assert(index >= 0 && index < cmd->num_hooks);
- talloc_free(cmd->hooks[index]);
- MP_TARRAY_REMOVE_AT(cmd->hooks, cmd->num_hooks, index);
+ for (int n = 0; n < cmd->num_hooks; n++) {
+ if (cmd->hooks[n] == h) {
+ talloc_free(cmd->hooks[n]);
+ MP_TARRAY_REMOVE_AT(cmd->hooks, cmd->num_hooks, n);
+ return;
+ }
+ }
+ assert(0);
}
bool mp_hook_test_completion(struct MPContext *mpctx, char *type)
@@ -152,7 +158,8 @@ bool mp_hook_test_completion(struct MPContext *mpctx, char *type)
struct hook_handler *h = cmd->hooks[n];
if (h->active && strcmp(h->type, type) == 0) {
if (!mp_client_exists(mpctx, h->client)) {
- hook_remove(mpctx, n);
+ MP_WARN(mpctx, "client removed during hook handling\n");
+ hook_remove(mpctx, h);
break;
}
return false;
@@ -161,49 +168,81 @@ bool mp_hook_test_completion(struct MPContext *mpctx, char *type)
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, cmd);
- 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");
+static int invoke_hook_handler(struct MPContext *mpctx, struct hook_handler *h)
+{
+ MP_VERBOSE(mpctx, "Running hook: %s/%s\n", h->client, h->type);
+ h->active = true;
+
+ uint64_t reply_id = 0;
+ void *data;
+ int msg;
+ if (h->legacy) {
+ 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_asprintf(m, "%llu", (long long)h->user_id));
+ MP_TARRAY_APPEND(m, m->args, m->num_args,
+ talloc_asprintf(m, "%llu", (long long)h->seq));
+ data = m;
+ msg = MPV_EVENT_CLIENT_MESSAGE;
+ } else {
+ mpv_event_hook *m = talloc_ptrtype(NULL, m);
+ *m = (mpv_event_hook){
+ .name = talloc_strdup(m, h->type),
+ .id = h->seq,
+ },
+ reply_id = h->user_id;
+ data = m;
+ msg = MPV_EVENT_HOOK;
+ }
+ int r = mp_client_send_event(mpctx, h->client, reply_id, msg, data);
+ if (r < 0) {
+ MP_WARN(mpctx, "Sending hook command failed. Removing hook.\n");
+ hook_remove(mpctx, h);
+ mp_wakeup_core(mpctx); // repeat next iteration to finish
+ }
return r;
}
-// client==NULL means start the hook chain
-void mp_hook_run(struct MPContext *mpctx, char *client, char *type)
+static int run_next_hook_handler(struct MPContext *mpctx, char *type, int index)
+{
+ struct command_ctx *cmd = mpctx->command_ctx;
+
+ for (int n = index; n < cmd->num_hooks; n++) {
+ struct hook_handler *h = cmd->hooks[n];
+ if (strcmp(h->type, type) == 0)
+ return invoke_hook_handler(mpctx, h);
+ }
+
+ mp_wakeup_core(mpctx); // finished hook
+ return 0;
+}
+
+void mp_hook_run(struct MPContext *mpctx, char *type)
+{
+ while (run_next_hook_handler(mpctx, type, 0) < 0) {
+ // We can repeat this until all broken clients have been removed, and
+ // hook processing is successfully started.
+ }
+}
+
+int mp_hook_continue(struct MPContext *mpctx, char *client, uint64_t id)
{
struct command_ctx *cmd = mpctx->command_ctx;
- bool found_current = !client;
- int index = -1;
+
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;
- mp_wakeup_core(mpctx);
- }
- } else if (strcmp(h->type, type) == 0) {
- index = n;
- break;
+ if (strcmp(h->client, client) == 0 && h->seq == id) {
+ if (!h->active)
+ break;
+ h->active = false;
+ return run_next_hook_handler(mpctx, h->type, n + 1);
}
}
- if (index < 0)
- return;
- struct hook_handler *next = cmd->hooks[index];
- MP_VERBOSE(mpctx, "Running hook: %s/%s\n", next->client, type);
- next->active = true;
- if (!send_hook_msg(mpctx, next, "hook_run")) {
- hook_remove(mpctx, index);
- mp_wakeup_core(mpctx); // repeat next iteration to finish
- }
+
+ MP_ERR(mpctx, "invalid hook API usage\n");
+ return MPV_ERROR_INVALID_PARAMETER;
}
static int compare_hook(const void *pa, const void *pb)
@@ -215,18 +254,22 @@ static int compare_hook(const void *pa, const void *pb)
return (*h1)->seq - (*h2)->seq;
}
-static void mp_hook_add(struct MPContext *mpctx, char *client, char *name,
- int id, int pri)
+void mp_hook_add(struct MPContext *mpctx, const char *client, const char *name,
+ uint64_t user_id, int pri, bool legacy)
{
+ if (legacy)
+ MP_WARN(mpctx, "The old hook API is deprecated! Use the libmpv API.\n");
+
struct command_ctx *cmd = mpctx->command_ctx;
struct hook_handler *h = talloc_ptrtype(cmd, h);
- int64_t seq = cmd->hook_seq++;
+ 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),
+ .user_id = user_id,
.priority = pri,
.seq = seq,
+ .legacy = legacy,
};
MP_TARRAY_APPEND(cmd, cmd->hooks, cmd->num_hooks, h);
qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook);
@@ -5409,7 +5452,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
MP_TARRAY_APPEND(event, event->args, event->num_args,
talloc_strdup(event, cmd->args[n].v.s));
}
- if (mp_client_send_event(mpctx, cmd->args[0].v.s,
+ if (mp_client_send_event(mpctx, cmd->args[0].v.s, 0,
MPV_EVENT_CLIENT_MESSAGE, event) < 0)
{
MP_VERBOSE(mpctx, "Can't find script '%s' for %s.\n",
@@ -5459,14 +5502,14 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
return -1;
}
mp_hook_add(mpctx, cmd->sender, cmd->args[0].v.s, cmd->args[1].v.i,
- cmd->args[2].v.i);
+ cmd->args[2].v.i, true);
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);
+ mp_hook_continue(mpctx, cmd->sender, cmd->args[0].v.i);
break;
case MP_CMD_MOUSE: {
diff --git a/player/command.h b/player/command.h
index 478bc8c737..2a102ddfbe 100644
--- a/player/command.h
+++ b/player/command.h
@@ -50,7 +50,7 @@ uint64_t mp_get_property_event_mask(const char *name);
enum {
// Must start with the first unused positive value in enum mpv_event_id
// MPV_EVENT_* and MP_EVENT_* must not overlap.
- INTERNAL_EVENT_BASE = 25,
+ INTERNAL_EVENT_BASE = 26,
MP_EVENT_CHANGE_ALL,
MP_EVENT_CACHE_UPDATE,
MP_EVENT_WIN_RESIZE,
@@ -61,7 +61,10 @@ enum {
};
bool mp_hook_test_completion(struct MPContext *mpctx, char *type);
-void mp_hook_run(struct MPContext *mpctx, char *client, char *type);
+void mp_hook_run(struct MPContext *mpctx, char *type);
+int mp_hook_continue(struct MPContext *mpctx, char *client, uint64_t id);
+void mp_hook_add(struct MPContext *mpctx, const char *client, const char *name,
+ uint64_t user_id, int pri, bool legacy);
void mark_seek(struct MPContext *mpctx);
diff --git a/player/loadfile.c b/player/loadfile.c
index 5c741369c0..32f41213fd 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -753,7 +753,7 @@ static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl)
static int process_open_hooks(struct MPContext *mpctx, char *name)
{
- mp_hook_run(mpctx, NULL, name);
+ mp_hook_run(mpctx, name);
while (!mp_hook_test_completion(mpctx, name)) {
mp_idle(mpctx);
@@ -770,7 +770,7 @@ static int process_open_hooks(struct MPContext *mpctx, char *name)
static int process_preloaded_hooks(struct MPContext *mpctx)
{
- mp_hook_run(mpctx, NULL, "on_preloaded");
+ mp_hook_run(mpctx, "on_preloaded");
while (!mp_hook_test_completion(mpctx, "on_preloaded")) {
mp_idle(mpctx);
@@ -783,7 +783,7 @@ static int process_preloaded_hooks(struct MPContext *mpctx)
static void process_unload_hooks(struct MPContext *mpctx)
{
- mp_hook_run(mpctx, NULL, "on_unload");
+ mp_hook_run(mpctx, "on_unload");
while (!mp_hook_test_completion(mpctx, "on_unload"))
mp_idle(mpctx);
diff --git a/player/lua.c b/player/lua.c
index 778830976c..98dcfee5b2 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -556,6 +556,12 @@ static int script_wait_event(lua_State *L)
lua_setfield(L, -2, "data");
break;
}
+ case MPV_EVENT_HOOK: {
+ mpv_event_hook *hook = event->data;
+ lua_pushinteger(L, hook->id);
+ lua_setfield(L, -2, "hook_id");
+ break;
+ }
default: ;
}
@@ -1046,6 +1052,22 @@ static int script_get_wakeup_pipe(lua_State *L)
return 1;
}
+static int script_raw_hook_add(lua_State *L)
+{
+ struct script_ctx *ctx = get_ctx(L);
+ uint64_t ud = luaL_checkinteger(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ int pri = luaL_checkinteger(L, 3);
+ return check_error(L, mpv_hook_add(ctx->client, ud, name, pri));
+}
+
+static int script_raw_hook_continue(lua_State *L)
+{
+ struct script_ctx *ctx = get_ctx(L);
+ lua_Integer id = luaL_checkinteger(L, 1);
+ return check_error(L, mpv_hook_continue(ctx->client, id));
+}
+
static int script_readdir(lua_State *L)
{
// 0 1 2 3
@@ -1335,6 +1357,8 @@ static const struct fn_entry main_fns[] = {
FN_ENTRY(format_time),
FN_ENTRY(enable_messages),
FN_ENTRY(get_wakeup_pipe),
+ FN_ENTRY(raw_hook_add),
+ FN_ENTRY(raw_hook_continue),
{0}
};
diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua
index 32dfed9948..d5bb194c50 100644
--- a/player/lua/defaults.lua
+++ b/player/lua/defaults.lua
@@ -511,24 +511,21 @@ function mp.osd_message(text, duration)
end
local hook_table = {}
-local hook_registered = false
-local function hook_run(id, cont)
- local fn = hook_table[tonumber(id)]
+mp.register_event("hook", function(ev)
+ local fn = hook_table[tonumber(ev.id)]
if fn then
fn()
end
- mp.commandv("hook-ack", cont)
-end
+ mp.raw_hook_continue(ev.hook_id)
+end)
function mp.add_hook(name, pri, cb)
- if not hook_registered then
- mp.register_script_message("hook_run", hook_run)
- hook_registered = true
- end
local id = #hook_table + 1
hook_table[id] = cb
- mp.commandv("hook-add", name, id, pri)
+ -- The C API suggests using 0 for a neutral priority, but lua.rst suggests
+ -- 50 (?), so whatever.
+ mp.raw_hook_add(id, name, pri - 50)
end
local mp_utils = package.loaded["mp.utils"]
diff --git a/player/scripting.c b/player/scripting.c
index 9d2cd97de5..b98588d948 100644
--- a/player/scripting.c
+++ b/player/scripting.c
@@ -207,7 +207,8 @@ static void load_builtin_script(struct MPContext *mpctx, bool enable,
// terminated, or re-enabling the script could be racy (because it'd
// recognize a still-terminating script as "loaded").
while (mp_client_exists(mpctx, name)) {
- if (mp_client_send_event(mpctx, name, MPV_EVENT_SHUTDOWN, NULL) < 0)
+ if (mp_client_send_event(mpctx, name, 0, MPV_EVENT_SHUTDOWN,
+ NULL) < 0)
break;
mp_idle(mpctx);
}