File Coverage

blib/lib/AnyEvent/MPV.pm
Criterion Covered Total %
statement 15 144 10.4
branch 0 62 0.0
condition 0 14 0.0
subroutine 5 26 19.2
pod 13 13 100.0
total 33 259 12.7


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