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

File Contents

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