mirror of https://github.com/mpv-player/mpv
client API: provide ways to finish property changes on file changes
When the current file changes (or rather, when starting/finishing playback of a playlist entry), clients tend to have the problem that it's hard to tell whether a property change notification (via mpv_observe_property() and mechanisms layered on top of it) is from the previous or new playlist entry. The previous commit probably helps, but all the asynchronity is still a bit unhelpful. Try to make this better by adding new hooks, that are run before/after playback init/deinit. This is similar to the existing hooks, except they're outside of "initialized" playback, which excludes that you might accidentally get an overlap between the current and the previous/next playlist entry. That still doesn't seem quite enough, since normally, property change notifications come after the hook event. So basically a client would have to explicitly "drain" the event queue within the hook, and make the hook continue only after that is done. Knowing when property notifications are done is another asynchronous nightmare (how exactly it works keeps changing within client.c, and an API user probably can't tell anymore when all pending properties are truly done). So introduce another guarantee: properties that were changed before the hook happens will be returned before the hook event is returned. That means the client will have received all pending property notifications from the previous playlist entry (or whatever) before the hook is entered. As another minor complication, we shouldn't just keep the hook pending until _all_ property notifications are done, since the client's hook could produce new ones. (Or just consider things like the demuxer thread hammering the client with cache update events, while the "on_preloaded" hook is run.) So there is some extra untested, fragile logic in client.c to handle this (the waiting_for_hook flag). This probably works, but was barely tested. Not sure if this helps anyone, but I think it's fine for my own purposes. (I really hated this aspect of the API whenever I used it myself.)
This commit is contained in:
parent
8a4f812b76
commit
ba70b150fb
|
@ -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
|
The C API is described in the header files. The Lua API is described in the
|
||||||
Lua section.
|
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:
|
The following hooks are currently defined:
|
||||||
|
|
||||||
``on_load``
|
``on_load``
|
||||||
|
@ -1286,6 +1292,16 @@ The following hooks are currently defined:
|
||||||
Run before closing a file, and before actually uninitializing
|
Run before closing a file, and before actually uninitializing
|
||||||
everything. It's not possible to resume playback in this state.
|
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
|
Input Command Prefixes
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -102,6 +102,7 @@ struct observe_property {
|
||||||
union m_option_value value;
|
union m_option_value value;
|
||||||
uint64_t value_ret_ts; // logical timestamp of value returned to user
|
uint64_t value_ret_ts; // logical timestamp of value returned to user
|
||||||
union m_option_value value_ret;
|
union m_option_value value_ret;
|
||||||
|
bool waiting_for_hook; // flag for draining old property changes on a hook
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mpv_handle {
|
struct mpv_handle {
|
||||||
|
@ -140,6 +141,7 @@ struct mpv_handle {
|
||||||
size_t async_counter; // pending other async events
|
size_t async_counter; // pending other async events
|
||||||
bool choked; // recovering from queue overflow
|
bool choked; // recovering from queue overflow
|
||||||
bool destroying; // pending destruction; no API accesses allowed
|
bool destroying; // pending destruction; no API accesses allowed
|
||||||
|
bool hook_pending; // hook events are returned after draining properties
|
||||||
|
|
||||||
struct observe_property **properties;
|
struct observe_property **properties;
|
||||||
int num_properties;
|
int num_properties;
|
||||||
|
@ -847,6 +849,27 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable)
|
||||||
return 0;
|
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 *mpv_wait_event(mpv_handle *ctx, double timeout)
|
||||||
{
|
{
|
||||||
mpv_event *event = ctx->cur_event;
|
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;
|
event->event_id = MPV_EVENT_QUEUE_OVERFLOW;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (ctx->num_events) {
|
struct mpv_event *ev =
|
||||||
*event = ctx->events[ctx->first_event];
|
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->first_event = (ctx->first_event + 1) % ctx->max_events;
|
||||||
ctx->num_events--;
|
ctx->num_events--;
|
||||||
talloc_steal(event, event->data);
|
talloc_steal(event, event->data);
|
||||||
|
@ -1608,7 +1647,7 @@ static void send_client_property_changes(struct mpv_handle *ctx)
|
||||||
ctx->async_counter -= 1;
|
ctx->async_counter -= 1;
|
||||||
prop_unref(prop);
|
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.
|
// => start over, retry next time.
|
||||||
if (cur_ts != ctx->properties_change_ts || ctx->destroying) {
|
if (cur_ts != ctx->properties_change_ts || ctx->destroying) {
|
||||||
m_option_free(type, &val);
|
m_option_free(type, &val);
|
||||||
|
@ -1638,10 +1677,14 @@ static void send_client_property_changes(struct mpv_handle *ctx)
|
||||||
changed = true;
|
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,
|
// Avoid retriggering the change event if the property didn't change,
|
||||||
// and the previous value was actually returned to the client.
|
// and the previous value was actually returned to the client.
|
||||||
if (!changed && prop->value_ret_ts == prop->value_ts) {
|
if (!changed && prop->value_ret_ts == prop->value_ts) {
|
||||||
prop->value_ret_ts = prop->change_ts; // no change => no event
|
prop->value_ret_ts = prop->change_ts; // no change => no event
|
||||||
|
prop->waiting_for_hook = false;
|
||||||
} else {
|
} else {
|
||||||
ctx->new_property_events = true;
|
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) // other value than last time?
|
||||||
{
|
{
|
||||||
prop->value_ret_ts = prop->value_ts;
|
prop->value_ret_ts = prop->value_ts;
|
||||||
|
prop->waiting_for_hook = false;
|
||||||
prop_unref(ctx->cur_property);
|
prop_unref(ctx->cur_property);
|
||||||
ctx->cur_property = prop;
|
ctx->cur_property = prop;
|
||||||
prop->refcount += 1;
|
prop->refcount += 1;
|
||||||
|
|
|
@ -931,7 +931,8 @@ static void process_hooks(struct MPContext *mpctx, char *name)
|
||||||
while (!mp_hook_test_completion(mpctx, name)) {
|
while (!mp_hook_test_completion(mpctx, name)) {
|
||||||
mp_idle(mpctx);
|
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)
|
if (mpctx->stop_play)
|
||||||
mp_abort_playback_async(mpctx);
|
mp_abort_playback_async(mpctx);
|
||||||
}
|
}
|
||||||
|
@ -1374,13 +1375,17 @@ static void play_current_file(struct MPContext *mpctx)
|
||||||
double playback_start = -1e100;
|
double playback_start = -1e100;
|
||||||
|
|
||||||
assert(mpctx->stop_play);
|
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_notify(mpctx, MPV_EVENT_START_FILE, NULL);
|
||||||
|
|
||||||
mp_cancel_reset(mpctx->playback_abort);
|
mp_cancel_reset(mpctx->playback_abort);
|
||||||
|
|
||||||
mpctx->error_playing = MPV_ERROR_LOADING_FAILED;
|
mpctx->error_playing = MPV_ERROR_LOADING_FAILED;
|
||||||
mpctx->stop_play = 0;
|
|
||||||
mpctx->filename = NULL;
|
mpctx->filename = NULL;
|
||||||
mpctx->shown_aframes = 0;
|
mpctx->shown_aframes = 0;
|
||||||
mpctx->shown_vframes = 0;
|
mpctx->shown_vframes = 0;
|
||||||
|
@ -1701,6 +1706,8 @@ terminate_playback:
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(mpctx->stop_play);
|
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,
|
// Determine the next file to play. Note that if this function returns non-NULL,
|
||||||
|
|
Loading…
Reference in New Issue