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 (16 months ago) by root
Branch: MAIN
CVS Tags: rel-1_0, rel-1_02, rel-1_03, rel-1_01, rel-1_04, HEAD
Changes since 1.3: +539 -23 lines
Log Message:
1.0

File Contents

# Content
1 NAME
2 AnyEvent::MPV - remote control mpv (https://mpv.io)
3
4 SYNOPSIS
5 use AnyEvent::MPV;
6
7 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 DESCRIPTION
17 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 my $videofile = "./xyzzy.mkv";
54
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 my $videofile = "./xyzzy.mkv";
89
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 my $videofile = "xyzzy.mkv";
144
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
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 $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 playing. Also, most of the logic is now implement in event handlers.
171
172 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
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 Data received from mpv, however, is *not* decoded to unicode, as data
190 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 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
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 $string you passed. This $string is then passed to the "on_key"
368 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 You cna find a list of key names in the mpv documentation
383 <https://mpv.io/manual/stable/#key-names>.
384
385 The key configuration is lost when mpv is stopped and must be
386 (re-)done after every "start".
387
388 [$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 [$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 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
477 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 SEE ALSO
952 AnyEvent, the mpv command documentation
953 <https://mpv.io/manual/stable/#command-interface>.
954
955 AUTHOR
956 Marc Lehmann <schmorp@schmorp.de>
957 http://home.schmorp.de/
958