ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MPV/README
Revision: 1.4
Committed: Wed Mar 22 19:37:00 2023 UTC (13 months, 3 weeks ago) by root
Branch: MAIN
CVS Tags: rel-1_0, rel-1_02, rel-1_03, rel-1_01, HEAD
Changes since 1.3: +539 -23 lines
Log Message:
1.0

File Contents

# User Rev Content
1 root 1.1 NAME
2     AnyEvent::MPV - remote control mpv (https://mpv.io)
3    
4     SYNOPSIS
5     use AnyEvent::MPV;
6    
7 root 1.4 my $videofile = "path/to/file.mkv";
8     use AnyEvent;
9     my $mpv = AnyEvent::MPV->new (trace => 1);
10     $mpv->start ("--idle=yes");
11     $mpv->cmd (loadfile => $mpv->escape_binary ($videofile));
12     my $quit = AE::cv;
13     $mpv->register_event (end_file => $quit);
14     $quit->recv;
15    
16 root 1.1 DESCRIPTION
17 root 1.2 This module allows you to remote control mpv (a video player). It also
18     is an AnyEvent user, you need to make sure that you use and run a
19     supported event loop.
20    
21     There are other modules doing this, and I haven't looked much at them
22     other than to decide that they don't handle encodings correctly, and
23     since none of them use AnyEvent, I wrote my own. When in doubt, have a
24     look at them, too.
25    
26     Knowledge of the mpv command interface
27     <https://mpv.io/manual/stable/#command-interface> is required to use
28     this module.
29    
30     Features of this module are:
31    
32     uses AnyEvent, so integrates well into most event-based programs
33     supports asynchronous and synchronous operation
34     allows you to properly pass binary filenames
35     accepts data encoded in any way (does not crash when mpv replies with
36     non UTF-8 data)
37     features a simple keybind/event system
38    
39     OVERVIEW OF OPERATION
40     This module forks an mpv process and uses --input-ipc-client (or
41     equivalent) to create a bidirectional communication channel between it
42     and the mpv process.
43    
44     It then speaks the somewhat JSON-looking (but not really being JSON)
45     protocol that mpv implements to both send it commands, decode and handle
46     replies, and handle asynchronous events.
47    
48     Here is a very simple client:
49    
50     use AnyEvent;
51     use AnyEvent::MPV;
52    
53 root 1.4 my $videofile = "./xyzzy.mkv";
54 root 1.2
55     my $mpv = AnyEvent::MPV->new (trace => 1);
56    
57     $mpv->start ("--", $videofile);
58    
59     my $timer = AE::timer 2, 0, my $quit = AE::cv;
60     $quit->recv;
61    
62     This starts mpv with the two arguments "--" and $videofile, which it
63     should load and play. It then waits two seconds by starting a timer and
64     quits. The "trace" argument to the constructor makes mpv more verbose
65     and also prints the commands and responses, so you can have an idea what
66     is going on.
67    
68     In my case, the above example would output something like this:
69    
70     [uosc] Disabled because original osc is enabled!
71     mpv> {"event":"start-file","playlist_entry_id":1}
72     mpv> {"event":"tracks-changed"}
73     (+) Video --vid=1 (*) (h264 480x480 30.000fps)
74     mpv> {"event":"metadata-update"}
75     mpv> {"event":"file-loaded"}
76     Using hardware decoding (nvdec).
77     mpv> {"event":"video-reconfig"}
78     VO: [gpu] 480x480 cuda[nv12]
79     mpv> {"event":"video-reconfig"}
80     mpv> {"event":"playback-restart"}
81    
82     This is not usually very useful (you could just run mpv as a simple
83     shell command), so let us load the file at runtime:
84    
85     use AnyEvent;
86     use AnyEvent::MPV;
87    
88 root 1.4 my $videofile = "./xyzzy.mkv";
89 root 1.2
90     my $mpv = AnyEvent::MPV->new (
91     trace => 1,
92     args => ["--pause", "--idle=yes"],
93     );
94    
95     $mpv->start;
96     $mpv->cmd_recv (loadfile => $mpv->escape_binary ($videofile));
97     $mpv->cmd ("set", "pause", "no");
98    
99     my $timer = AE::timer 2, 0, my $quit = AE::cv;
100     $quit->recv;
101    
102     This specifies extra arguments in the constructor - these arguments are
103     used every time you "->start" mpv, while the arguments to "->start" are
104     only used for this specific clal to0 "start". The argument --pause keeps
105     mpv in pause mode (i.e. it does not play the file after loading it), and
106     "--idle=yes" tells mpv to not quit when it does not have a playlist - as
107     no files are specified on the command line.
108    
109     To load a file, we then send it a "loadfile" command, which accepts, as
110     first argument, the URL or path to a video file. To make sure mpv does
111     not misinterpret the path as a URL, it was prefixed with ./ (similarly
112     to "protecting" paths in perls "open").
113    
114     Since commands send *to* mpv are send in UTF-8, we need to escape the
115     filename (which might be in any encoding) using the "esscape_binary"
116     method - this is not needed if your filenames are just ascii, or
117     magically get interpreted correctly, but if you accept arbitrary
118     filenamews (e.g. from the user), you need to do this.
119    
120     The "cmd_recv" method then queues the command, waits for a reply and
121     returns the reply data (or croaks on error). mpv would, at this point,
122     load the file and, if everything was successful, show the first frame
123     and pause. Note that, since mpv is implement rather synchronously
124     itself, do not expect commands to fail in many circumstances - for
125     example, fit he file does not exit, you will likely get an event, but
126     the "loadfile" command itself will run successfully.
127    
128     To unpause, we send another command, "set", to set the "pause" property
129     to "no", this time using the "cmd" method, which queues the command, but
130     instead of waiting for a reply, it immediately returns a condvar that
131     cna be used to receive results.
132    
133     This should then cause mpv to start playing the video.
134    
135     It then again waits two seconds and quits.
136    
137     Now, just waiting two seconds is rather, eh, unuseful, so let's look at
138     receiving events (using a somewhat embellished example):
139    
140     use AnyEvent;
141     use AnyEvent::MPV;
142    
143 root 1.4 my $videofile = "xyzzy.mkv";
144 root 1.2
145     my $quit = AE::cv;
146    
147     my $mpv = AnyEvent::MPV->new (
148     trace => 1,
149     args => ["--pause", "--idle=yes"],
150     );
151    
152     $mpv->start;
153 root 1.4
154     $mpv->register_event (start_file => sub {
155     $mpv->cmd ("set", "pause", "no");
156     });
157    
158     $mpv->register_event (end_file => sub {
159     my ($mpv, $event, $data) = @_;
160    
161     print "end-file<$data->{reason}>\n";
162     $quit->send;
163     });
164    
165 root 1.2 $mpv->cmd (loadfile => $mpv->escape_binary ($videofile));
166    
167     $quit->recv;
168    
169     This example uses a global condvar $quit to wait for the file to finish
170 root 1.4 playing. Also, most of the logic is now implement in event handlers.
171 root 1.2
172 root 1.4 The two events handlers we register are "start-file", which is emitted
173     by mpv once it has loaded a new file, and "end-file", which signals the
174     end of a file (underscores are internally replaced by minus signs, so
175     you cna speicfy event names with either).
176    
177     In the "start-file" event, we again set the "pause" property to "no" so
178     the movie starts playing. For the "end-file" event, we tell the main
179     program to quit by invoking $quit.
180 root 1.2
181     This should conclude the basics of operation. There are a few more
182     examples later in the documentation.
183    
184     ENCODING CONVENTIONS
185     As a rule of thumb, all data you pass to this module to be sent to mpv
186     is expected to be in unicode. To pass something that isn't, you need to
187     escape it using "escape_binary".
188    
189 root 1.4 Data received from mpv, however, is *not* decoded to unicode, as data
190 root 1.2 returned by mpv is not generally encoded in unicode, and the encoding is
191     usually unspecified. So if you receive data and expect it to be in
192     unicode, you need to first decode it from UTF-8, but note that this
193     might fail. This is not a limitation of this module - mpv simply does
194     not specify nor guarantee a specific encoding, or any encoding at all,
195     in its protocol.
196    
197     METHODS
198     $mpv = AnyEvent::MPV->new (key => value...)
199     Creates a new "mpv" object, but does not yet do anything. The
200     support key-value pairs are:
201    
202     mpv => $path
203     The path to the mpv binary to use - by default, "mpv" is used
204     and therefore, uses your "PATH" to find it.
205    
206     args => [...]
207     Arguments to pass to mpv. These arguments are passed after the
208     hardcoded arguments used by this module, but before the
209     arguments passed ot "start". It does not matter whether you
210     specify your arguments using this key, or in the "start" call,
211     but when you invoke mpv multiple times, typically the arguments
212     used for all invocations go here, while arguments used for
213     specific invocations (e..g filenames) are passed to "start".
214    
215     trace => false|true|coderef
216     Enables tracing if true. In trace mode, output from mpv is
217     printed to standard error using a "mpv>" prefix, and commands
218     sent to mpv are printed with a ">mpv" prefix.
219    
220     If a code reference is passed, then instead of printing to
221     standard errort, this coderef is invoked with a first arfgument
222     being either "mpv>" or ">mpv", and the second argument being a
223     string to display. The default implementation simply does this:
224    
225     sub {
226     warn "$_[0] $_[1]\n";
227     }
228    
229     on_eof => $coderef->($mpv)
230     on_event => $coderef->($mpv, $event, $data)
231     on_key => $coderef->($mpv, $string)
232     These are invoked by the default method implementation of the
233     same name - see below.
234    
235     $string = $mpv->escape_binary ($string)
236     This module excects all command data sent to mpv to be in unicode.
237     Some things are not, such as filenames. To pass binary data such as
238     filenames through a comamnd, you need to escape it using this
239     method.
240    
241     The simplest example is a "loadfile" command:
242    
243     $mpv->cmd_recv (loadfile => $mpv->escape_binary ($path));
244    
245     $started = $mpv->start (argument...)
246     Starts mpv, passing the given arguemnts as extra arguments to mpv.
247     If mpv is already running, it returns false, otherwise it returns a
248     true value, so you can easily start mpv on demand by calling "start"
249     just before using it, and if it is already running, it will not be
250     started again.
251    
252     The arguments passwd to mpv are a set of hardcoded built-in
253     arguments, followed by the arguments specified in the constructor,
254     followed by the arguments passwd to this method. The built-in
255     arguments currently are --no-input-terminal, --really-quiet (or
256     --quiet in "trace" mode), and "--input-ipc-client" (or equivalent).
257    
258     Some commonly used and/or even useful arguments you might want to
259     pass are:
260    
261     --idle=yes or --idle=once to keep mpv from quitting when you don't
262     specify a file to play.
263     --pause, to keep mpv from instantly starting to play a file, in case
264     you want to inspect/change properties first.
265     --force-window=no (or similar), to keep mpv from instantly opening a
266     window, or to force it to do so.
267     --audio-client-name=yourappname, to make sure audio streams are
268     associated witht eh right program.
269     --wid=id, to embed mpv into another application.
270     --no-terminal, --no-input-default-bindings, --no-input-cursor,
271     --input-conf=/dev/null, --input-vo-keyboard=no - to ensure only you
272     control input.
273    
274     The return value can be used to decide whether mpv needs
275     initializing:
276    
277     if ($mpv->start) {
278     $mpv->bind_key (...);
279     $mpv->cmd (set => property => value);
280     ...
281     }
282    
283     You can immediately starting sending commands when this method
284     returns, even if mpv has not yet started.
285    
286     $mpv->stop
287     Ensures that mpv is being stopped, by killing mpv with a "TERM"
288     signal if needed. After this, you can "->start" a new instance
289     again.
290    
291     $mpv->on_eof
292     This method is called when mpv quits - usually unexpectedly. The
293     default implementation will call the "on_eof" code reference
294     specified in the constructor, or do nothing if none was given.
295    
296     For subclassing, see *SUBCLASSING*, below.
297    
298     $mpv->on_event ($event, $data)
299     This method is called when mpv sends an asynchronous event. The
300     default implementation will call the "on_event" code reference
301     specified in the constructor, or do nothing if none was given.
302    
303     The first/implicit argument is the $mpv object, the second is the
304     event name (same as "$data->{event}", purely for convenience), and
305 root 1.3 the third argument is the event object as sent by mpv (sans "event"
306     key). See List of events
307     <https://mpv.io/manual/stable/#list-of-events> in its documentation.
308 root 1.2
309     For subclassing, see *SUBCLASSING*, below.
310    
311     $mpv->on_key ($string)
312     Invoked when a key declared by "->bind_key" is pressed. The default
313     invokes the "on_key" code reference specified in the constructor
314     with the $mpv object and the key name as arguments, or do nothing if
315     none was given.
316    
317     For more details and examples, see the "bind_key" method.
318    
319     For subclassing, see *SUBCLASSING*, below.
320    
321     $mpv->cmd ($command => $arg, $arg...)
322     Queues a command to be sent to mpv, using the given arguments, and
323     immediately return a condvar.
324    
325     See the mpv documentation
326     <https://mpv.io/manual/stable/#list-of-input-commands> for details
327     on individual commands.
328    
329     The condvar can be ignored:
330    
331     $mpv->cmd (set_property => "deinterlace", "yes");
332    
333     Or it can be used to synchronously wait for the command results:
334    
335     $cv = $mpv->cmd (get_property => "video-format");
336     $format = $cv->recv;
337    
338     # or simpler:
339    
340     $format = $mpv->cmd (get_property => "video-format")->recv;
341    
342     # or even simpler:
343    
344     $format = $mpv->cmd_recv (get_property => "video-format");
345    
346     Or you can set a callback:
347    
348     $cv = $mpv->cmd (get_property => "video-format");
349     $cv->cb (sub {
350     my $format = $_[0]->recv;
351     });
352    
353     On error, the condvar will croak when "recv" is called.
354    
355     $result = $mpv->cmd_recv ($command => $arg, $arg...)
356     The same as calling "cmd" and immediately "recv" on its return
357     value. Useful when you don't want to mess with mpv asynchronously or
358     simply needs to have the result:
359    
360     $mpv->cmd_recv ("stop");
361     $position = $mpv->cmd_recv ("get_property", "playback-time");
362    
363     $mpv->bind_key ($INPUT => $string)
364     This is an extension implement by this module to make it easy to get
365     key events. The way this is implemented is to bind a
366     "client-message" witha first argument of "AnyEvent::MPV" and the
367 root 1.3 $string you passed. This $string is then passed to the "on_key"
368 root 1.2 handle when the key is proessed, e.g.:
369    
370     my $mpv = AnyEvent::MPV->new (
371     on_key => sub {
372     my ($mpv, $key) = @_;
373    
374     if ($key eq "letmeout") {
375     print "user pressed escape\n";
376     }
377     },
378     );
379    
380     $mpv_>bind_key (ESC => "letmeout");
381    
382 root 1.4 You cna find a list of key names in the mpv documentation
383     <https://mpv.io/manual/stable/#key-names>.
384    
385 root 1.2 The key configuration is lost when mpv is stopped and must be
386     (re-)done after every "start".
387    
388 root 1.4 [$guard] = $mpv->register_event ($event => $coderef->($mpv, $event,
389     $data))
390     This method registers a callback to be invoked for a specific event.
391     Whenever the event occurs, it calls the coderef with the $mpv
392     object, the $event name and the event object, just like the
393     "on_event" method.
394    
395     For a lst of events, see the mpv documentation
396     <https://mpv.io/manual/stable/#list-of-events>. Any underscore in
397     the event name is replaced by a minus sign, so you can specify event
398     names using underscores for easier quoting in Perl.
399    
400     In void context, the handler stays registered until "stop" is
401     called. In any other context, it returns a guard object that, when
402     destroyed, will unregister the handler.
403    
404     You can register multiple handlers for the same event, and this
405     method does not interfere with the "on_event" mechanism. That is,
406     you can completely ignore this method and handle events in a
407     "on_event" handler, or mix both approaches as you see fit.
408    
409     Note that unlike commands, event handlers are registered
410     immediately, that is, you can issue a command, then register an
411     event handler and then get an event for this handler *before* the
412     command is even sent to mpv. If this kind of race is an issue, you
413     can issue a dummy command such as "get_version" and register the
414     handler when the reply is received.
415    
416 root 1.3 [$guard] = $mpv->observe_property ($name => $coderef->($mpv, $name,
417     $value))
418     [$guard] = $mpv->observe_property_string ($name => $coderef->($mpv,
419     $name, $value))
420     These methods wrap a registry system around mpv's "observe_property"
421     and "observe_property_string" commands - every time the named
422     property changes, the coderef is invoked with the $mpv object, the
423     name of the property and the new value.
424    
425     For a list of properties that you can observe, see the mpv
426     documentation <https://mpv.io/manual/stable/#property-list>.
427    
428     Due to the (sane :) way mpv handles these requests, you will always
429     get a property cxhange event right after registering an observer
430     (meaning you don't have to query the current value), and it is also
431     possible to register multiple observers for the same property - they
432     will all be handled properly.
433    
434     When called in void context, the observer stays in place until mpv
435     is stopped. In any otrher context, these methods return a guard
436     object that, when it goes out of scope, unregisters the observe
437     using "unobserve_property".
438    
439     Internally, this method uses observer ids of 2**52
440     (0x10000000000000) or higher - it will not interfere with lower
441     ovserver ids, so it is possible to completely ignore this system and
442     execute "observe_property" commands yourself, whilst listening to
443     "property-change" events - as long as your ids stay below 2**52.
444    
445     Example: register observers for changtes in "aid" and "sid". Note
446     that a dummy statement is added to make sure the method is called in
447     void context.
448    
449     sub register_observers {
450     my ($mpv) = @_;
451    
452     $mpv->observe_property (aid => sub {
453     my ($mpv, $name, $value) = @_;
454     print "property aid (=$name) has changed to $value\n";
455     });
456    
457     $mpv->observe_property (sid => sub {
458     my ($mpv, $name, $value) = @_;
459     print "property sid (=$name) has changed to $value\n";
460     });
461    
462     () # ensure the above method is called in void context
463     }
464    
465 root 1.2 SUBCLASSING
466     Like most perl objects, "AnyEvent::MPV" objects are implemented as
467     hashes, with the constructor simply storing all passed key-value pairs
468     in the object. If you want to subclass to provide your own "on_*"
469     methods, be my guest and rummage around in the internals as much as you
470     wish - the only guarantee that this module dcoes is that it will not use
471     keys with double colons in the name, so youc an use those, or chose to
472     simply not care and deal with the breakage.
473    
474     If you don't want to go to the effort of subclassing this module, you
475     can also specify all event handlers as constructor keys.
476 root 1.1
477 root 1.4 EXAMPLES
478     Here are some real-world code snippets, thrown in here mainly to give
479     you some example code to copy.
480    
481     doomfrontend
482     At one point I replaced mythtv-frontend by my own terminal-based video
483     player (based on rxvt-unicode). I toyed with the diea of using mpv's
484     subtitle engine to create the user interface, but that is hard to use
485     since you don't know how big your letters are. It is also where most of
486     this modules code has originally been developed in.
487    
488     It uses a unified input queue to handle various remote controls, so its
489     event handling needs are very simple - it simply feeds all events into
490     the input queue:
491    
492     my $mpv = AnyEvent::MPV->new (
493     mpv => $MPV,
494     args => \@MPV_ARGS,
495     on_event => sub {
496     input_feed "mpv/$_[1]", $_[2];
497     },
498     on_key => sub {
499     input_feed $_[1];
500     },
501     on_eof => sub {
502     input_feed "mpv/quit";
503     },
504     );
505    
506     ...
507    
508     $mpv->start ("--idle=yes", "--pause", "--force-window=no");
509    
510     It also doesn't use complicated command line arguments - the file search
511     options have the most impact, as they prevent mpv from scanning
512     directories with tens of thousands of files for subtitles and more:
513    
514     --audio-client-name=doomfrontend
515     --osd-on-seek=msg-bar --osd-bar-align-y=-0.85 --osd-bar-w=95
516     --sub-auto=exact --audio-file-auto=exact
517    
518     Since it runs on a TV without a desktop environemnt, it tries to keep
519     complications such as dbus away and the screensaver happy:
520    
521     # prevent xscreensaver from doing something stupid, such as starting dbus
522     $ENV{DBUS_SESSION_BUS_ADDRESS} = "/"; # prevent dbus autostart for sure
523     $ENV{XDG_CURRENT_DESKTOP} = "generic";
524    
525     It does bind a number of keys to internal (to doomfrontend) commands:
526    
527     for (
528     List::Util::pairs qw(
529     ESC return
530     q return
531     ENTER enter
532     SPACE pause
533     [ steprev
534     ] stepfwd
535     j subtitle
536     BS red
537     i green
538     o yellow
539     b blue
540     D triangle
541     UP up
542     DOWN down
543     RIGHT right
544     LEFT left
545     ),
546     (map { ("KP$_" => "num$_") } 0..9),
547     KP_INS => 0, # KP0, but different
548     ) {
549     $mpv->bind_key ($_->[0] => $_->[1]);
550     }
551    
552     It also reacts to sponsorblock chapters, so it needs to know when vidoe
553     chapters change. Preadting "AnyEvent::MPV", it handles observers
554     manually:
555    
556     $mpv->cmd (observe_property => 1, "chapter-metadata");
557    
558     It also tries to apply an mpv profile, if it exists:
559    
560     eval {
561     # the profile is optional
562     $mpv->cmd ("apply-profile" => "doomfrontend");
563     };
564    
565     Most of the complicated parts deal with saving and restoring per-video
566     data, such as bookmarks, playing position, selected audio and subtitle
567     tracks and so on. However, since it uses Coro, it can conveniently block
568     and wait for replies, which is n ot possible in purely event based
569     programs, as you are not allowed to block inside event callbacks in most
570     event loops. This simplifies the code quite a bit.
571    
572     When the file to be played is a Tv recording done by mythtv, it uses the
573     "appending" protocol and deinterlacing:
574    
575     if (is_myth $mpv_path) {
576     $mpv_path = "appending://$mpv_path";
577     $initial_deinterlace = 1;
578     }
579    
580     Otherwise, it sets some defaults and loads the file (I forgot what the
581     "dummy" argument is for, but I am sure it is needed by some mpv
582     version):
583    
584     $mpv->cmd ("script-message", "osc-visibility", "never", "dummy");
585     $mpv->cmd ("set", "vid", "auto");
586     $mpv->cmd ("set", "aid", "auto");
587     $mpv->cmd ("set", "sid", "no");
588     $mpv->cmd ("set", "file-local-options/chapters-file", $mpv->escape_binary ("$mpv_path.chapters"));
589     $mpv->cmd ("loadfile", $mpv->escape_binary ($mpv_path));
590     $mpv->cmd ("script-message", "osc-visibility", "auto", "dummy");
591    
592     Handling events makes the main bulk of video playback code. For example,
593     various ways of ending playback:
594    
595     if ($INPUT eq "mpv/quit") { # should not happen, but allows user to kill etc. without consequence
596     $status = 1;
597     mpv_init; # try reinit
598     last;
599    
600     } elsif ($INPUT eq "mpv/idle") { # normal end-of-file
601     last;
602    
603     } elsif ($INPUT eq "return") {
604     $status = 1;
605     last;
606    
607     Or the code that actually starts playback, once the file is loaded:
608    
609     our %SAVE_PROPERTY = (aid => 1, sid => 1, "audio-delay" => 1);
610    
611     ...
612    
613     my $oid = 100;
614    
615     } elsif ($INPUT eq "mpv/file-loaded") { # start playing, configure video
616     $mpv->cmd ("seek", $playback_start, "absolute+exact") if $playback_start > 0;
617    
618     my $target_fps = eval { $mpv->cmd_recv ("get_property", "container-fps") } || 60;
619     $target_fps *= play_video_speed_mult;
620     set_fps $target_fps;
621    
622     unless (eval { $mpv->cmd_recv ("get_property", "video-format") }) {
623     $mpv->cmd ("set", "file-local-options/lavfi-complex", "[aid1] asplit [ao], showcqt=..., format=yuv420p [vo]");
624     };
625    
626     for my $prop (keys %SAVE_PROPERTY) {
627     if (exists $PLAYING_STATE->{"mpv_$prop"}) {
628     $mpv->cmd ("set", "$prop", $PLAYING_STATE->{"mpv_$prop"} . "");
629     }
630    
631     $mpv->cmd ("observe_property", ++$oid, $prop);
632     }
633    
634     play_video_set_speed;
635     $mpv->cmd ("set", "osd-level", "$OSD_LEVEL");
636     $mpv->cmd ("observe_property", ++$oid, "osd-level");
637     $mpv->cmd ("set", "pause", "no");
638    
639     $mpv->cmd ("set_property", "deinterlace", "yes")
640     if $initial_deinterlace;
641    
642     There is a lot going on here. First it seeks to the actual playback
643     position, if it is not at the start of the file (it would probaby be
644     more efficient to set the starting position before loading the file,
645     though, but this is good enough).
646    
647     Then it plays with the display fps, to set it to something harmonious
648     w.r.t. the video framerate.
649    
650     If the file does not have a video part, it assumes it is an audio file
651     and sets a visualizer.
652    
653     Also, a number of properties are not global, but per-file. At the
654     moment, this is "audio-delay", and the current audio/subtitle track,
655     which it sets, and also creates an observer. Again, this doesn'T use the
656     observe functionality of this module, but handles it itself, assigning
657     obsevrer ids 100+ to temporary/per-file observers.
658    
659     Lastly, it sets some global (or per-youtube-uploader) parameters, such
660     as speed, and unpauses. Property changes are handled like other input
661     events:
662    
663     } elsif ($INPUT eq "mpv/property-change") {
664     my $prop = $INPUT_DATA->{name};
665    
666     if ($prop eq "chapter-metadata") {
667     if ($INPUT_DATA->{data}{TITLE} =~ /^\[SponsorBlock\]: (.*)/) {
668     my $section = $1;
669     my $skip;
670    
671     $skip ||= $SPONSOR_SKIP{$_}
672     for split /\s*,\s*/, $section;
673    
674     if (defined $skip) {
675     if ($skip) {
676     # delay a bit, in case we get two metadata changes in quick succession, e.g.
677     # because we have a skip at file load time.
678     $skip_delay = AE::timer 2/50, 0, sub {
679     $mpv->cmd ("no-osd", "add", "chapter", 1);
680     $mpv->cmd ("show-text", "skipped sponsorblock section \"$section\"", 3000);
681     };
682     } else {
683     undef $skip_delay;
684     $mpv->cmd ("show-text", "NOT skipping sponsorblock section \"$section\"", 3000);
685     }
686     } else {
687     $mpv->cmd ("show-text", "UNRECOGNIZED sponsorblock section \"$section\"", 60000);
688     }
689     } else {
690     # cancel a queued skip
691     undef $skip_delay;
692     }
693    
694     } elsif (exists $SAVE_PROPERTY{$prop}) {
695     $PLAYING_STATE->{"mpv_$prop"} = $INPUT_DATA->{data};
696     ::state_save;
697     }
698    
699     This saves back the per-file properties, and also handles chapter
700     changes in a hacky way.
701    
702     Most of the handlers are very simple, though. For example:
703    
704     } elsif ($INPUT eq "pause") {
705     $mpv->cmd ("cycle", "pause");
706     $PLAYING_STATE->{curpos} = $mpv->cmd_recv ("get_property", "playback-time");
707     } elsif ($INPUT eq "right") {
708     $mpv->cmd ("osd-msg-bar", "seek", 30, "relative+exact");
709     } elsif ($INPUT eq "left") {
710     $mpv->cmd ("osd-msg-bar", "seek", -5, "relative+exact");
711     } elsif ($INPUT eq "up") {
712     $mpv->cmd ("osd-msg-bar", "seek", +600, "relative+exact");
713     } elsif ($INPUT eq "down") {
714     $mpv->cmd ("osd-msg-bar", "seek", -600, "relative+exact");
715     } elsif ($INPUT eq "select") {
716     $mpv->cmd ("osd-msg-bar", "add", "audio-delay", "-0.100");
717     } elsif ($INPUT eq "start") {
718     $mpv->cmd ("osd-msg-bar", "add", "audio-delay", "0.100");
719     } elsif ($INPUT eq "intfwd") {
720     $mpv->cmd ("no-osd", "frame-step");
721     } elsif ($INPUT eq "audio") {
722     $mpv->cmd ("osd-auto", "cycle", "audio");
723     } elsif ($INPUT eq "subtitle") {
724     $mpv->cmd ("osd-auto", "cycle", "sub");
725     } elsif ($INPUT eq "triangle") {
726     $mpv->cmd ("osd-auto", "cycle", "deinterlace");
727    
728     Once a file has finished playing (or the user strops playback), it
729     pauses, unobserves the per-file observers, and saves the current
730     position for to be able to resume:
731    
732     $mpv->cmd ("set", "pause", "yes");
733    
734     while ($oid > 100) {
735     $mpv->cmd ("unobserve_property", $oid--);
736     }
737    
738     $PLAYING_STATE->{curpos} = $mpv->cmd_recv ("get_property", "playback-time");
739    
740     And thats most of the mpv-related code.
741    
742     Gtk2::CV
743     Gtk2::CV is low-feature image viewer that I use many times daily because
744     it can handle directories with millions of files without falling over.
745     It also had the ability to play videos for ages, but it used an older,
746     crappier protocol to talk to mpv and used ffprobe before playing each
747     file instead of letting mpv handle format/size detection.
748    
749     After writing this module, I decided to upgprade Gtk2::CV by making use
750     of it, with the goal of getting rid of ffprobe and being ablew to reuse
751     mpv processes, which would have a multitude of speed benefits (for
752     example, fork+exec of mpv caused the kernel to close all file
753     descriptors, which could take minutes if a large file was being copied
754     via NFS, as the kernel waited for thr buffers to be flushed on close -
755     not having to start mpv gets rid of this issue).
756    
757     Setting up is only complicated by the fact that mpv needs to be embedded
758     into an existing window. To keep control of all inputs, Gtk2::CV puts an
759     eventbox in front of mpv, so mpv receives no input events:
760    
761     $self->{mpv} = AnyEvent::MPV->new (
762     trace => $ENV{CV_MPV_TRACE},
763     );
764    
765     # create an eventbox, so we receive all input events
766     my $box = $self->{mpv_eventbox} = new Gtk2::EventBox;
767     $box->set_above_child (1);
768     $box->set_visible_window (0);
769     $box->set_events ([]);
770     $box->can_focus (0);
771    
772     # create a drawingarea that mpv can display into
773     my $window = $self->{mpv_window} = new Gtk2::DrawingArea;
774     $box->add ($window);
775    
776     # put the drawingarea intot he eventbox, and the eventbox into our display window
777     $self->add ($box);
778    
779     # we need to pass the window id to F<mpv>, which means we need to realise
780     # the drawingarea, so an X window is allocated for it.
781     $self->show_all;
782     $window->realize;
783     my $xid = $window->window->get_xid;
784    
785     Then it starts mpv using this setup:
786    
787     local $ENV{LC_ALL} = "POSIX";
788     $self->{mpv}->start (
789     "--no-terminal",
790     "--no-input-terminal",
791     "--no-input-default-bindings",
792     "--no-input-cursor",
793     "--input-conf=/dev/null",
794     "--input-vo-keyboard=no",
795    
796     "--loop-file=inf",
797     "--force-window=yes",
798     "--idle=yes",
799    
800     "--audio-client-name=CV",
801    
802     "--osc=yes", # --osc=no displays fading play/pause buttons instead
803    
804     "--wid=$xid",
805     );
806    
807     $self->{mpv}->cmd ("script-message" => "osc-visibility" => "never", "dummy");
808     $self->{mpv}->cmd ("osc-idlescreen" => "no");
809    
810     It also prepares a hack to force a ConfigureNotify event on every vidoe
811     reconfig:
812    
813     # force a configurenotify on every video-reconfig
814     $self->{mpv_reconfig} = $self->{mpv}->register_event (video_reconfig => sub {
815     my ($mpv, $event, $data) = @_;
816    
817     $self->mpv_window_update;
818     });
819    
820     The way this is done is by doing a "dummy" resize to 1x1 and back:
821    
822     $self->{mpv_window}->window->resize (1, 1),
823     $self->{mpv_window}->window->resize ($self->{w}, $self->{h});
824    
825     Without this, mpv often doesn't "get" the correct window size. Doing it
826     this way is not nice, but I didn't fine a nicer way to do it.
827    
828     When no file is being played, mpv is hidden and prepared:
829    
830     $self->{mpv_eventbox}->hide;
831    
832     $self->{mpv}->cmd (set_property => "pause" => "yes");
833     $self->{mpv}->cmd ("playlist_remove", "current");
834     $self->{mpv}->cmd (set_property => "video-rotate" => 0);
835     $self->{mpv}->cmd (set_property => "lavfi-complex" => "");
836    
837     Loading a file is a bit more complicated, as bluray and DVD rips are
838     supported:
839    
840     if ($moviedir) {
841     if ($moviedir eq "br") {
842     $mpv->cmd (set => "bluray-device" => $path);
843     $mpv->cmd (loadfile => "bd://");
844     } elsif ($moviedir eq "dvd") {
845     $mpv->cmd (set => "dvd-device" => $path);
846     $mpv->cmd (loadfile => "dvd://");
847     }
848     } elsif ($type eq "video/iso-bluray") {
849     $mpv->cmd (set => "bluray-device" => $path);
850     $mpv->cmd (loadfile => "bd://");
851     } else {
852     $mpv->cmd (loadfile => $mpv->escape_binary ($path));
853     }
854    
855     After this, "Gtk2::CV" waits for the file to be loaded, video to be
856     configured, and then queries the video size (to resize its own window)
857     and video format (to decide whether an audio visualizer is needed for
858     audio playback). The problematic word here is "wait", as this needs to
859     be imploemented using callbacks.
860    
861     This made the code much harder to write, as the whole setup is very
862     asynchronous ("Gtk2::CV" talks to the command interface in mpv, which
863     talks to the decode and playback parts, all of which run asynchronously
864     w.r.t. each other. In practise, this can mean that "Gtk2::CV" waits for
865     a file to be loaded by mpv while the command interface of mpv still
866     deals with the previous file and the decoder still handles an even older
867     file). Adding to this fact is that Gtk2::CV is bound by the glib event
868     loop, which means we cannot wait for replies form mpv anywhere, so
869     everything has to be chained callbacks.
870    
871     The way this is handled is by creating a new empty hash ref that is
872     unique for each loaded file, and use it to detect whether the event is
873     old or not, and also store "AnyEvent::MPV" guard objects in it:
874    
875     # every time we loaded a file, we create a new hash
876     my $guards = $self->{mpv_guards} = { };
877    
878     Then, when we wait for an event to occur, delete the handler, and, if
879     the "mpv_guards" object has changed, we ignore it. Something like this:
880    
881     $guards->{file_loaded} = $mpv->register_event (file_loaded => sub {
882     delete $guards->{file_loaded};
883     return if $guards != $self->{mpv_guards};
884    
885     Commands do not have guards since they cnanot be cancelled, so we don't
886     have to do this for commands. But what prevents us form misinterpreting
887     an old event? Since mpv (by default) handles commands synchronously, we
888     can queue a dummy command, whose only purpose is to tell us when all
889     previous commands are done. We use "get_version" for this.
890    
891     The simplified code looks like this:
892    
893     Scalar::Util::weaken $self;
894    
895     $mpv->cmd ("get_version")->cb (sub {
896    
897     $guards->{file_loaded} = $mpv->register_event (file_loaded => sub {
898     delete $guards->{file_loaded};
899     return if $guards != $self->{mpv_guards};
900    
901     $mpv->cmd (get_property => "video-format")->cb (sub {
902     return if $guards != $self->{mpv_guards};
903    
904     # video-format handling
905     return if eval { $_[0]->recv; 1 };
906    
907     # no video? assume audio and visualize, cpu usage be damned
908     $mpv->cmd (set => "lavfi-complex" => ...");
909     });
910    
911     $guards->{show} = $mpv->register_event (video_reconfig => sub {
912     delete $guards->{show};
913     return if $guards != $self->{mpv_guards};
914    
915     $self->{mpv_eventbox}->show_all;
916    
917     $w = $mpv->cmd (get_property => "dwidth");
918     $h = $mpv->cmd (get_property => "dheight");
919    
920     $h->cb (sub {
921     $w = eval { $w->recv };
922     $h = eval { $h->recv };
923    
924     $mpv->cmd (set_property => "pause" => "no");
925    
926     if ($w && $h) {
927     # resize our window
928     }
929    
930     });
931     });
932    
933     });
934    
935     });
936    
937     Most of the rest of the code is much simpler and just deals with
938     forwarding user commands:
939    
940     } elsif ($key == $Gtk2::Gdk::Keysyms{Right}) { $mpv->cmd ("osd-msg-bar" => seek => "+10");
941     } elsif ($key == $Gtk2::Gdk::Keysyms{Left} ) { $mpv->cmd ("osd-msg-bar" => seek => "-10");
942     } elsif ($key == $Gtk2::Gdk::Keysyms{Up} ) { $mpv->cmd ("osd-msg-bar" => seek => "+60");
943     } elsif ($key == $Gtk2::Gdk::Keysyms{Down} ) { $mpv->cmd ("osd-msg-bar" => seek => "-60");
944     } elsif ($key == $Gtk2::Gdk::Keysyms{a}) ) { $mpv->cmd ("osd-msg-msg" => cycle => "audio");
945     } elsif ($key == $Gtk2::Gdk::Keysyms{j} ) { $mpv->cmd ("osd-msg-msg" => cycle => "sub");
946     } elsif ($key == $Gtk2::Gdk::Keysyms{o} ) { $mpv->cmd ("no-osd" => "cycle-values", "osd-level", "2", "3", "0", "2");
947     } elsif ($key == $Gtk2::Gdk::Keysyms{p} ) { $mpv->cmd ("no-osd" => cycle => "pause");
948     } elsif ($key == $Gtk2::Gdk::Keysyms{9} ) { $mpv->cmd ("osd-msg-bar" => add => "ao-volume", "-2");
949     } elsif ($key == $Gtk2::Gdk::Keysyms{0} ) { $mpv->cmd ("osd-msg-bar" => add => "ao-volume", "+2");
950    
951 root 1.1 SEE ALSO
952 root 1.2 AnyEvent, the mpv command documentation
953     <https://mpv.io/manual/stable/#command-interface>.
954 root 1.1
955     AUTHOR
956     Marc Lehmann <schmorp@schmorp.de>
957     http://home.schmorp.de/
958