History

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.)

wm4 committed 6 years ago

Commit ba70b151 parent 8a4f8124 files · +104 −5Browse files

Unified · Split

4 files
  1. DOCS/man/input.rst +16 −0
  2. TOOLS/lua/test-hooks.lua +32 −0
  3. player/client.c +47 −3
  4. player/loadfile.c +9 −2
DOCS/man/input.rst+16−0
@@ -1253,6 +1253,12 @@ the player freeze randomly. Basically, nobody should use this API. · 1252 unmodified lines
12531253 The C API is described in the header files. The Lua API is described in the
12541254 Lua section.
12551255
1256+Before a hook is actually invoked on an API clients, it will attempt to return
1257+new values for all observed properties that were changed before the hook. This
1258+may make it easier for an application to set defined "barriers" between property
1259+change notifications by registering hooks. (That means these hooks will have an
1260+effect, even if you do nothing and make them continue immediately.)
1261+
12561262 The following hooks are currently defined:
12571263
12581264 ``on_load``
@@ -1286,6 +1292,16 @@ The following hooks are currently defined: · 27 unmodified lines
12861292 Run before closing a file, and before actually uninitializing
12871293 everything. It's not possible to resume playback in this state.
12881294
1295+``on_before_start_file``
1296+ Run before a ``start-file`` event is sent. (If any client changes the
1297+ current playlist entry, or sends a quit command to the player, the
1298+ corresponding event will not actually happen after the hook returns.)
1299+ Useful to drain property changes before a new file is loaded.
1300+
1301+``on_after_end_file``
1302+ Run after an ``end-file`` event. Useful to drain property changes after a
1303+ file has finished.
1304+
12891305 Input Command Prefixes
12901306 ----------------------
12911307
TOOLS/lua/test-hooks.lua+32−0
@@ -0,0 +1,32 @@
1+local utils = require("mp.utils")
2+
3+function hardsleep()
4+ os.execute("sleep 1s")
5+end
6+
7+local hooks = {"on_before_start_file", "on_load", "on_load_fail",
8+ "on_preloaded", "on_unload", "on_after_end_file"}
9+
10+for _, name in ipairs(hooks) do
11+ mp.add_hook(name, 0, function()
12+ print("--- hook: " .. name)
13+ hardsleep()
14+ print(" ... continue")
15+ end)
16+end
17+
18+local events = {"start-file", "end-file", "file-loaded", "seek",
19+ "playback-restart", "idle", "shutdown"}
20+for _, name in ipairs(events) do
21+ mp.register_event(name, function()
22+ print("--- event: " .. name)
23+ end)
24+end
25+
26+local props = {"path", "metadata"}
27+for _, name in ipairs(props) do
28+ mp.observe_property(name, "native", function(name, val)
29+ print("property '" .. name .. "' changed to '" ..
30+ utils.to_string(val) .. "'")
31+ end)
32+end
player/client.c+47−3
@@ -102,6 +102,7 @@ struct observe_property { · 101 unmodified lines
102102 union m_option_value value;
103103 uint64_t value_ret_ts; // logical timestamp of value returned to user
104104 union m_option_value value_ret;
105+ bool waiting_for_hook; // flag for draining old property changes on a hook
105106 };
106107
107108 struct mpv_handle {
@@ -140,6 +141,7 @@ struct mpv_handle { · 32 unmodified lines
140141 size_t async_counter; // pending other async events
141142 bool choked; // recovering from queue overflow
142143 bool destroying; // pending destruction; no API accesses allowed
144+ bool hook_pending; // hook events are returned after draining properties
143145
144146 struct observe_property **properties;
145147 int num_properties;
@@ -847,6 +849,27 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable) · 701 unmodified lines
847849 return 0;
848850 }
849851
852+// Set waiting_for_hook==true for all possibly pending properties.
853+static void set_wait_for_hook_flags(mpv_handle *ctx)
854+{
855+ for (int n = 0; n < ctx->num_properties; n++) {
856+ struct observe_property *prop = ctx->properties[n];
857+
858+ if (prop->value_ret_ts != prop->change_ts)
859+ prop->waiting_for_hook = true;
860+ }
861+}
862+
863+// Return whether any property still has waiting_for_hook set.
864+static bool check_for_for_hook_flags(mpv_handle *ctx)
865+{
866+ for (int n = 0; n < ctx->num_properties; n++) {
867+ if (ctx->properties[n]->waiting_for_hook)
868+ return true;
869+ }
870+ return false;
871+}
872+
850873 mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
851874 {
852875 mpv_event *event = ctx->cur_event;
@@ -874,8 +897,24 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout) · 21 unmodified lines
874897 event->event_id = MPV_EVENT_QUEUE_OVERFLOW;
875898 break;
876899 }
877− if (ctx->num_events) {
878− *event = ctx->events[ctx->first_event];
900+ struct mpv_event *ev =
901+ ctx->num_events ? &ctx->events[ctx->first_event] : NULL;
902+ if (ev && ev->event_id == MPV_EVENT_HOOK) {
903+ // Give old property notifications priority over hooks. This is a
904+ // guarantee given to clients to simplify their logic. New property
905+ // changes after this are treated normally, so
906+ if (!ctx->hook_pending) {
907+ ctx->hook_pending = true;
908+ set_wait_for_hook_flags(ctx);
909+ }
910+ if (check_for_for_hook_flags(ctx)) {
911+ ev = NULL; // delay
912+ } else {
913+ ctx->hook_pending = false;
914+ }
915+ }
916+ if (ev) {
917+ *event = *ev;
879918 ctx->first_event = (ctx->first_event + 1) % ctx->max_events;
880919 ctx->num_events--;
881920 talloc_steal(event, event->data);
@@ -1608,7 +1647,7 @@ static void send_client_property_changes(struct mpv_handle *ctx) · 726 unmodified lines
16081647 ctx->async_counter -= 1;
16091648 prop_unref(prop);
16101649
1611− // Set of observed properties was changed or something similar
1650+ // Set if observed properties was changed or something similar
16121651 // => start over, retry next time.
16131652 if (cur_ts != ctx->properties_change_ts || ctx->destroying) {
16141653 m_option_free(type, &val);
@@ -1638,10 +1677,14 @@ static void send_client_property_changes(struct mpv_handle *ctx) · 23 unmodified lines
16381677 changed = true;
16391678 }
16401679
1680+ if (prop->waiting_for_hook)
1681+ ctx->new_property_events = true; // make sure to wakeup
1682+
16411683 // Avoid retriggering the change event if the property didn't change,
16421684 // and the previous value was actually returned to the client.
16431685 if (!changed && prop->value_ret_ts == prop->value_ts) {
16441686 prop->value_ret_ts = prop->change_ts; // no change => no event
1687+ prop->waiting_for_hook = false;
16451688 } else {
16461689 ctx->new_property_events = true;
16471690 }
@@ -1704,6 +1747,7 @@ static bool gen_property_change_event(struct mpv_handle *ctx) · 56 unmodified lines
17041747 prop->value_ret_ts != prop->value_ts) // other value than last time?
17051748 {
17061749 prop->value_ret_ts = prop->value_ts;
1750+ prop->waiting_for_hook = false;
17071751 prop_unref(ctx->cur_property);
17081752 ctx->cur_property = prop;
17091753 prop->refcount += 1;
player/loadfile.c+9−2
@@ -931,7 +931,8 @@ static void process_hooks(struct MPContext *mpctx, char *name) · 930 unmodified lines
931931 while (!mp_hook_test_completion(mpctx, name)) {
932932 mp_idle(mpctx);
933933
934− // We have no idea what blocks a hook, so just do a full abort.
934+ // We have no idea what blocks a hook, so just do a full abort. This
935+ // does nothing for hooks that happen outside of playback.
935936 if (mpctx->stop_play)
936937 mp_abort_playback_async(mpctx);
937938 }
@@ -1374,13 +1375,17 @@ static void play_current_file(struct MPContext *mpctx) · 436 unmodified lines
13741375 double playback_start = -1e100;
13751376
13761377 assert(mpctx->stop_play);
1378+ mpctx->stop_play = 0;
1379+
1380+ process_hooks(mpctx, "on_before_start_file");
1381+ if (mpctx->stop_play)
1382+ return;
13771383
13781384 mp_notify(mpctx, MPV_EVENT_START_FILE, NULL);
13791385
13801386 mp_cancel_reset(mpctx->playback_abort);
13811387
13821388 mpctx->error_playing = MPV_ERROR_LOADING_FAILED;
1383− mpctx->stop_play = 0;
13841389 mpctx->filename = NULL;
13851390 mpctx->shown_aframes = 0;
13861391 mpctx->shown_vframes = 0;
@@ -1701,6 +1706,8 @@ terminate_playback: · 314 unmodified lines
17011706 }
17021707
17031708 assert(mpctx->stop_play);
1709+
1710+ process_hooks(mpctx, "on_after_end_file");
17041711 }
17051712
17061713 // Determine the next file to play. Note that if this function returns non-NULL,

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t