ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MPV/MPV.pm
Revision: 1.21
Committed: Sun May 26 01:53:22 2024 UTC (8 weeks, 2 days ago) by root
Branch: MAIN
CVS Tags: rel-1_04, HEAD
Changes since 1.20: +4 -1 lines
Log Message:
1.04

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