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

File Contents

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