File Coverage

blib/lib/AnyEvent/MPV.pm
Criterion Covered Total %
statement 15 142 10.5
branch 0 60 0.0
condition 0 14 0.0
subroutine 5 26 19.2
pod 13 13 100.0
total 33 255 12.9


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