summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/input.rst16
-rw-r--r--TOOLS/lua/test-hooks.lua32
-rw-r--r--player/client.c50
-rw-r--r--player/loadfile.c11
4 files changed, 104 insertions, 5 deletions
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index 7c93843619..082776f66d 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -1253,6 +1253,12 @@ the player freeze randomly. Basically, nobody should use this API.
The C API is described in the header files. The Lua API is described in the
Lua section.
+Before a hook is actually invoked on an API clients, it will attempt to return
+new values for all observed properties that were changed before the hook. This
+may make it easier for an application to set defined "barriers" between property
+change notifications by registering hooks. (That means these hooks will have an
+effect, even if you do nothing and make them continue immediately.)
+
The following hooks are currently defined:
``on_load``
@@ -1286,6 +1292,16 @@ 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.
+``on_before_start_file``
+ Run before a ``start-file`` event is sent. (If any client changes the
+ current playlist entry, or sends a quit command to the player, the
+ corresponding event will not actually happen after the hook returns.)
+ Useful to drain property changes before a new file is loaded.
+
+``on_after_end_file``
+ Run after an ``end-file`` event. Useful to drain property changes after a
+ file has finished.
+
Input Command Prefixes
----------------------
diff --git a/TOOLS/lua/test-hooks.lua b/TOOLS/lua/test-hooks.lua
new file mode 100644
index 0000000000..4e84d9e465
--- /dev/null
+++ b/TOOLS/lua/test-hooks.lua
@@ -0,0 +1,32 @@
+local utils = require("mp.utils")
+
+function hardsleep()
+ os.execute("sleep 1s")
+end
+
+local hooks = {"on_before_start_file", "on_load", "on_load_fail",
+ "on_preloaded", "on_unload", "on_after_end_file"}
+
+for _, name in ipairs(hooks) do
+ mp.add_hook(name, 0, function()
+ print("--- hook: " .. name)
+ hardsleep()
+ print(" ... continue")
+ end)
+end
+
+local events = {"start-file", "end-file", "file-loaded", "seek",
+ "playback-restart", "idle", "shutdown"}
+for _, name in ipairs(events) do
+ mp.register_event(name, function()
+ print("--- event: " .. name)
+ end)
+end
+
+local props = {"path", "metadata"}
+for _, name in ipairs(props) do
+ mp.observe_property(name, "native", function(name, val)
+ print("property '" .. name .. "' changed to '" ..
+ utils.to_string(val) .. "'")
+ end)
+end
diff --git a/player/client.c b/player/client.c
index 815c64c005..460632805f 100644
--- a/player/client.c
+++ b/player/client.c
@@ -102,6 +102,7 @@ struct observe_property {
union m_option_value value;
uint64_t value_ret_ts; // logical timestamp of value returned to user
union m_option_value value_ret;
+ bool waiting_for_hook; // flag for draining old property changes on a hook
};
struct mpv_handle {
@@ -140,6 +141,7 @@ struct mpv_handle {
size_t async_counter; // pending other async events
bool choked; // recovering from queue overflow
bool destroying; // pending destruction; no API accesses allowed
+ bool hook_pending; // hook events are returned after draining properties
struct observe_property **properties;
int num_properties;
@@ -847,6 +849,27 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable)
return 0;
}
+// Set waiting_for_hook==true for all possibly pending properties.
+static void set_wait_for_hook_flags(mpv_handle *ctx)
+{
+ for (int n = 0; n < ctx->num_properties; n++) {
+ struct observe_property *prop = ctx->properties[n];
+
+ if (prop->value_ret_ts != prop->change_ts)
+ prop->waiting_for_hook = true;
+ }
+}
+
+// Return whether any property still has waiting_for_hook set.
+static bool check_for_for_hook_flags(mpv_handle *ctx)
+{
+ for (int n = 0; n < ctx->num_properties; n++) {
+ if (ctx->properties[n]->waiting_for_hook)
+ return true;
+ }
+ return false;
+}
+
mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
{
mpv_event *event = ctx->cur_event;
@@ -874,8 +897,24 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
event->event_id = MPV_EVENT_QUEUE_OVERFLOW;
break;
}
- if (ctx->num_events) {
- *event = ctx->events[ctx->first_event];
+ struct mpv_event *ev =
+ ctx->num_events ? &ctx->events[ctx->first_event] : NULL;
+ if (ev && ev->event_id == MPV_EVENT_HOOK) {
+ // Give old property notifications priority over hooks. This is a
+ // guarantee given to clients to simplify their logic. New property
+ // changes after this are treated normally, so
+ if (!ctx->hook_pending) {
+ ctx->hook_pending = true;
+ set_wait_for_hook_flags(ctx);
+ }
+ if (check_for_for_hook_flags(ctx)) {
+ ev = NULL; // delay
+ } else {
+ ctx->hook_pending = false;
+ }
+ }
+ if (ev) {
+ *event = *ev;
ctx->first_event = (ctx->first_event + 1) % ctx->max_events;
ctx->num_events--;
talloc_steal(event, event->data);
@@ -1608,7 +1647,7 @@ static void send_client_property_changes(struct mpv_handle *ctx)
ctx->async_counter -= 1;
prop_unref(prop);
- // Set of observed properties was changed or something similar
+ // Set if observed properties was changed or something similar
// => start over, retry next time.
if (cur_ts != ctx->properties_change_ts || ctx->destroying) {
m_option_free(type, &val);
@@ -1638,10 +1677,14 @@ static void send_client_property_changes(struct mpv_handle *ctx)
changed = true;
}
+ if (prop->waiting_for_hook)
+ ctx->new_property_events = true; // make sure to wakeup
+
// Avoid retriggering the change event if the property didn't change,
// and the previous value was actually returned to the client.
if (!changed && prop->value_ret_ts == prop->value_ts) {
prop->value_ret_ts = prop->change_ts; // no change => no event
+ prop->waiting_for_hook = false;
} else {
ctx->new_property_events = true;
}
@@ -1704,6 +1747,7 @@ static bool gen_property_change_event(struct mpv_handle *ctx)
prop->value_ret_ts != prop->value_ts) // other value than last time?
{
prop->value_ret_ts = prop->value_ts;
+ prop->waiting_for_hook = false;
prop_unref(ctx->cur_property);
ctx->cur_property = prop;
prop->refcount += 1;
diff --git a/player/loadfile.c b/player/loadfile.c
index d2bdb47ccd..9eb58c0f4d 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -931,7 +931,8 @@ static void process_hooks(struct MPContext *mpctx, char *name)
while (!mp_hook_test_completion(mpctx, name)) {
mp_idle(mpctx);
- // We have no idea what blocks a hook, so just do a full abort.
+ // We have no idea what blocks a hook, so just do a full abort. This
+ // does nothing for hooks that happen outside of playback.
if (mpctx->stop_play)
mp_abort_playback_async(mpctx);
}
@@ -1374,13 +1375,17 @@ static void play_current_file(struct MPContext *mpctx)
double playback_start = -1e100;
assert(mpctx->stop_play);
+ mpctx->stop_play = 0;
+
+ process_hooks(mpctx, "on_before_start_file");
+ if (mpctx->stop_play)
+ return;
mp_notify(mpctx, MPV_EVENT_START_FILE, NULL);
mp_cancel_reset(mpctx->playback_abort);
mpctx->error_playing = MPV_ERROR_LOADING_FAILED;
- mpctx->stop_play = 0;
mpctx->filename = NULL;
mpctx->shown_aframes = 0;
mpctx->shown_vframes = 0;
@@ -1701,6 +1706,8 @@ terminate_playback:
}
assert(mpctx->stop_play);
+
+ process_hooks(mpctx, "on_after_end_file");
}
// Determine the next file to play. Note that if this function returns non-NULL,