ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MPV/MPV.pm
Revision: 1.16
Committed: Wed Mar 22 18:17:24 2023 UTC (13 months, 3 weeks ago) by root
Branch: MAIN
Changes since 1.15: +488 -3 lines
Log Message:
*** empty log message ***

File Contents

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