1 |
=head1 NAME |
2 |
|
3 |
AnyEvent::MPV - remote control mpv (https://mpv.io) |
4 |
|
5 |
=head1 SYNOPSIS |
6 |
|
7 |
use AnyEvent::MPV; |
8 |
|
9 |
=head1 DESCRIPTION |
10 |
|
11 |
This module allows you to remote control F<mpv> (a video player). It also |
12 |
is an L<AnyEvent> user, you need to make sure that you use and run a |
13 |
supported event loop. |
14 |
|
15 |
There are other modules doing this, and I haven't looked much at them |
16 |
other than to decide that they don't handle encodings correctly, and since |
17 |
none of them use AnyEvent, I wrote my own. When in doubt, have a look at |
18 |
them, too. |
19 |
|
20 |
Knowledge of the L<mpv command |
21 |
interface|https://mpv.io/manual/stable/#command-interface> is required to |
22 |
use this module. |
23 |
|
24 |
Features of this module are: |
25 |
|
26 |
=over |
27 |
|
28 |
=item uses AnyEvent, so integrates well into most event-based programs |
29 |
|
30 |
=item supports asynchronous and synchronous operation |
31 |
|
32 |
=item allows you to properly pass binary filenames |
33 |
|
34 |
=item accepts data encoded in any way (does not crash when mpv replies with non UTF-8 data) |
35 |
|
36 |
=item features a simple keybind/event system |
37 |
|
38 |
=back |
39 |
|
40 |
=head2 OVERVIEW OF OPERATION |
41 |
|
42 |
This module forks an F<mpv> process and uses F<--input-ipc-client> (or |
43 |
equivalent) to create a bidirectional communication channel between it and |
44 |
the F<mpv> process. |
45 |
|
46 |
It then speaks the somewhat JSON-looking (but not really being JSON) |
47 |
protocol that F<mpv> implements to both send it commands, decode and |
48 |
handle replies, and handle asynchronous events. |
49 |
|
50 |
Here is a very simple client: |
51 |
|
52 |
use AnyEvent; |
53 |
use AnyEvent::MPV; |
54 |
|
55 |
my $videofile = "./xyzzy.mp4"; |
56 |
|
57 |
my $mpv = AnyEvent::MPV->new (trace => 1); |
58 |
|
59 |
$mpv->start ("--", $videofile); |
60 |
|
61 |
my $timer = AE::timer 2, 0, my $quit = AE::cv; |
62 |
$quit->recv; |
63 |
|
64 |
This starts F<mpv> with the two arguments C<--> and C<$videofile>, which |
65 |
it should load and play. It then waits two seconds by starting a timer and |
66 |
quits. The C<trace> argument to the constructor makes F<mpv> more verbose |
67 |
and also prints the commands and responses, so you cna have an idea what |
68 |
is going on. |
69 |
|
70 |
This is not usually very useful (you could just run F<mpv> as a simple |
71 |
shell command), so let us load the file at runtime: |
72 |
|
73 |
use AnyEvent; |
74 |
use AnyEvent::MPV; |
75 |
|
76 |
my $videofile = "./xyzzy.mp4"; |
77 |
|
78 |
my $mpv = AnyEvent::MPV->new ( |
79 |
trace => 1, |
80 |
args => ["--pause", "--idle=yes"], |
81 |
); |
82 |
|
83 |
$mpv->start; |
84 |
$mpv->cmd_recv (loadfile => $mpv->escape_binary ($videofile)); |
85 |
$mpv->cmd ("set", "pause", "no"); |
86 |
|
87 |
my $timer = AE::timer 2, 0, my $quit = AE::cv; |
88 |
$quit->recv; |
89 |
|
90 |
This specifies extra arguments in the constructor - these arguments are |
91 |
used every time you C<< ->start >> F<mpv>, while the arguments to C<< |
92 |
->start >> are only used for this specific clal to0 C<start>. The argument |
93 |
F<--pause> keeps F<mpv> in pause mode (i.e. it does not play the file |
94 |
after loading it), and C<--idle=yes> tells F<mpv> to not quit when it does |
95 |
not have a playlist - as no files are specified on the command line. |
96 |
|
97 |
To load a file, we then send it a C<loadfile> command, which accepts, as |
98 |
first argument, the URL or path to a video file. To make sure F<mpv> does |
99 |
not misinterpret the path as a URL, it was prefixed with F<./> (similarly |
100 |
to "protecting" paths in perls C<open>). |
101 |
|
102 |
Since commands send I<to> F<mpv> are send in UTF-8, we need to escape the |
103 |
filename (which might be in any encoding) using the C<esscape_binary> |
104 |
method - this is not needed if your filenames are just ascii, or magically |
105 |
get interpreted correctly, but if you accept arbitrary filenamews (e.g. |
106 |
from the user), you need to do this. |
107 |
|
108 |
The C<cmd_recv> method then queues the command, waits for a reply and |
109 |
returns the reply data (or croaks on error). F<mpv> would, at this point, |
110 |
load the file and, if everything was successful, show the first frame and |
111 |
pause. Note that, since F<mpv> is implement rather synchronously itself, |
112 |
do not expect commands to fail in many circumstances - for example, fit |
113 |
he file does not exit, you will likely get an event, but the C<loadfile> |
114 |
command itself will run successfully. |
115 |
|
116 |
To unpause, we send another command, C<set>, to set the C<pause> property |
117 |
to C<no>, this time using the C<cmd> method, which queues the command, but |
118 |
instead of waiting for a reply, it immediately returns a condvar that cna |
119 |
be used to receive results. |
120 |
|
121 |
This should then cause F<mpv> to start playing the video. |
122 |
|
123 |
It then again waits two seconds and quits. |
124 |
|
125 |
Now, just waiting two seconds is rather, eh, unuseful, so let's look at |
126 |
receiving events (using a somewhat embellished example): |
127 |
|
128 |
use AnyEvent; |
129 |
use AnyEvent::MPV; |
130 |
|
131 |
my $videofile = "xyzzy.mp4"; |
132 |
|
133 |
my $quit = AE::cv; |
134 |
|
135 |
my $mpv = AnyEvent::MPV->new ( |
136 |
trace => 1, |
137 |
args => ["--pause", "--idle=yes"], |
138 |
on_event => sub { |
139 |
my ($mpv, $event, $data) = @_; |
140 |
|
141 |
if ($event eq "start-file") { |
142 |
$mpv->cmd ("set", "pause", "no"); |
143 |
} elsif ($event eq "end-file") { |
144 |
print "end-file<$data->{reason}>\n"; |
145 |
$quit->send; |
146 |
} |
147 |
}, |
148 |
); |
149 |
|
150 |
$mpv->start; |
151 |
$mpv->cmd (loadfile => $mpv->escape_binary ($videofile)); |
152 |
|
153 |
$quit->recv; |
154 |
|
155 |
This example uses a global condvar C<$quit> to wait for the file to finish |
156 |
playing. Also, most of the logic is now in an C<on_event> callback, which |
157 |
receives an event name and the actual event object. |
158 |
|
159 |
The two events we handle are C<start-file>, which is emitted by F<mpv> |
160 |
once it has loaded a new file, and C<end-file>, which signals the end |
161 |
of a file. |
162 |
|
163 |
In the former event, we again set the C<pause> property to C<no> so the |
164 |
movie starts playing. For the latter event, we tell the main program to |
165 |
quit by invoking C<$quit>. |
166 |
|
167 |
This should conclude the basics of operation. There are a few more |
168 |
examples later in the documentation. |
169 |
|
170 |
=head2 ENCODING CONVENTIONS |
171 |
|
172 |
As a rule of thumb, all data you pass to this module to be sent to F<mpv> |
173 |
is expected to be in unicode. To pass something that isn't, you need to |
174 |
escape it using C<escape_binary>. |
175 |
|
176 |
Data received from C<$mpv>, however, is I<not> decoded to unicode, as data |
177 |
returned by F<mpv> is not generally encoded in unicode, and the encoding |
178 |
is usually unspecified. So if you receive data and expect it to be in |
179 |
unicode, you need to first decode it from UTF-8, but note that this might |
180 |
fail. This is not a limitation of this module - F<mpv> simply does not |
181 |
specify nor guarantee a specific encoding, or any encoding at all, in its |
182 |
protocol. |
183 |
|
184 |
=head2 METHODS |
185 |
|
186 |
=over |
187 |
|
188 |
=cut |
189 |
|
190 |
package AnyEvent::MPV; |
191 |
|
192 |
use common::sense; |
193 |
|
194 |
use Fcntl (); |
195 |
use Scalar::Util (); |
196 |
|
197 |
use AnyEvent (); |
198 |
use AnyEvent::Util (); |
199 |
|
200 |
our $JSON = eval { require JSON::XS; JSON::XS:: } |
201 |
|| do { require JSON::PP; JSON::PP:: }; |
202 |
|
203 |
our $JSON_CODER = |
204 |
|
205 |
our $VERSION = '0.1'; |
206 |
|
207 |
our $mpv_path; # last mpv path used |
208 |
our $mpv_optionlist; # output of mpv --list-options |
209 |
|
210 |
=item $mpv = AnyEvent::MPV->new (key => value...) |
211 |
|
212 |
Creates a new C<mpv> object, but does not yet do anything. The support key-value pairs are: |
213 |
|
214 |
=over |
215 |
|
216 |
=item mpv => $path |
217 |
|
218 |
The path to the F<mpv> binary to use - by default, C<mpv> is used and |
219 |
therefore, uses your C<PATH> to find it. |
220 |
|
221 |
=item args => [...] |
222 |
|
223 |
Arguments to pass to F<mpv>. These arguments are passed after the |
224 |
hardcoded arguments used by this module, but before the arguments passed |
225 |
ot C<start>. It does not matter whether you specify your arguments using |
226 |
this key, or in the C<start> call, but when you invoke F<mpv> multiple |
227 |
times, typically the arguments used for all invocations go here, while |
228 |
arguments used for specific invocations (e..g filenames) are passed to |
229 |
C<start>. |
230 |
|
231 |
=item trace => false|true|coderef |
232 |
|
233 |
Enables tracing if true. In trace mode, output from F<mpv> is printed to |
234 |
standard error using a C<< mpv> >> prefix, and commands sent to F<mpv> |
235 |
are printed with a C<< >mpv >> prefix. |
236 |
|
237 |
If a code reference is passed, then instead of printing to standard |
238 |
errort, this coderef is invoked with a first arfgument being either |
239 |
C<< mpv> >> or C<< >mpv >>, and the second argument being a string to |
240 |
display. The default implementation simply does this: |
241 |
|
242 |
sub { |
243 |
warn "$_[0] $_[1]\n"; |
244 |
} |
245 |
|
246 |
=item on_eof => $coderef->($mpv) |
247 |
|
248 |
=item on_event => $coderef->($mpv, $event, $data) |
249 |
|
250 |
=item on_key => $coderef->($mpv, $string) |
251 |
|
252 |
These are invoked by the default method implementation of the same name - |
253 |
see below. |
254 |
|
255 |
=back |
256 |
|
257 |
=cut |
258 |
|
259 |
sub new { |
260 |
my ($class, %kv) = @_; |
261 |
|
262 |
bless { |
263 |
mpv => "mpv", |
264 |
args => [], |
265 |
%kv, |
266 |
}, $class |
267 |
} |
268 |
|
269 |
=item $string = $mpv->escape_binary ($string) |
270 |
|
271 |
This module excects all command data sent to F<mpv> to be in unicode. Some |
272 |
things are not, such as filenames. To pass binary data such as filenames |
273 |
through a comamnd, you need to escape it using this method. |
274 |
|
275 |
The simplest example is a C<loadfile> command: |
276 |
|
277 |
$mpv->cmd_recv (loadfile => $mpv->escape_binary ($path)); |
278 |
|
279 |
=cut |
280 |
|
281 |
# can be used to escape filenames |
282 |
sub escape_binary { |
283 |
shift; |
284 |
local $_ = shift; |
285 |
# we escape every "illegal" octet using U+10e5df HEX. this is later undone in cmd |
286 |
s/([\x00-\x1f\x80-\xff])/sprintf "\x{10e5df}%02x", ord $1/ge; |
287 |
$_ |
288 |
} |
289 |
|
290 |
=item $started = $mpv->start (argument...) |
291 |
|
292 |
Starts F<mpv>, passing the given arguemnts as extra arguments to |
293 |
F<mpv>. If F<mpv> is already running, it returns false, otherwise it |
294 |
returns a true value, so you can easily start F<mpv> on demand by calling |
295 |
C<start> just before using it, and if it is already running, it will not |
296 |
be started again. |
297 |
|
298 |
The arguments passwd to F<mpv> are a set of hardcoded built-in arguments, |
299 |
followed by the arguments specified in the constructor, followed by the |
300 |
arguments passwd to this method. The built-in arguments currently are |
301 |
F<--no-input-terminal>, F<--really-quiet> (or F<--quiet> in C<trace> |
302 |
mode), and C<--input-ipc-client> (or equivalent). |
303 |
|
304 |
Some commonly used and/or even useful arguments you might want to pass are: |
305 |
|
306 |
=over |
307 |
|
308 |
=item F<--idle=yes> or F<--idle=once> to keep F<mpv> from quitting when you |
309 |
don't specify a file to play. |
310 |
|
311 |
=item F<--pause>, to keep F<mpv> from instantly starting to play a file, in case you want to |
312 |
inspect/change properties first. |
313 |
|
314 |
=item F<--force-window=no> (or similar), to keep F<mpv> from instantly opening a window, or to force it to do so. |
315 |
|
316 |
=item F<--audio-client-name=yourappname>, to make sure audio streams are associated witht eh right program. |
317 |
|
318 |
=item F<--wid=id>, to embed F<mpv> into another application. |
319 |
|
320 |
=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. |
321 |
|
322 |
=back |
323 |
|
324 |
The return value can be used to decide whether F<mpv> needs initializing: |
325 |
|
326 |
if ($mpv->start) { |
327 |
$mpv->bind_key (...); |
328 |
$mpv->cmd (set => property => value); |
329 |
... |
330 |
} |
331 |
|
332 |
You can immediately starting sending commands when this method returns, |
333 |
even if F<mpv> has not yet started. |
334 |
|
335 |
=cut |
336 |
|
337 |
sub start { |
338 |
my ($self, @extra_args) = @_; |
339 |
|
340 |
return 0 if $self->{fh}; |
341 |
|
342 |
# cache optionlist for same "path" |
343 |
($mpv_path, $mpv_optionlist) = ($self->{mpv}, scalar qx{\Q$self->{mpv}\E --list-options}) |
344 |
if $self->{mpv} ne $mpv_path; |
345 |
|
346 |
my $options = $mpv_optionlist; |
347 |
|
348 |
my ($fh, $slave) = AnyEvent::Util::portable_socketpair |
349 |
or die "socketpair: $!\n"; |
350 |
|
351 |
AnyEvent::Util::fh_nonblocking $fh, 1; |
352 |
|
353 |
$self->{pid} = fork; |
354 |
|
355 |
if ($self->{pid} eq 0) { |
356 |
AnyEvent::Util::fh_nonblocking $slave, 0; |
357 |
fcntl $slave, Fcntl::F_SETFD, 0; |
358 |
|
359 |
my $input_file = $options =~ /\s--input-ipc-client\s/ ? "input-ipc-client" : "input-file"; |
360 |
|
361 |
exec $self->{mpv}, |
362 |
qw(--no-input-terminal), |
363 |
($self->{trace} ? "--quiet" : "--really-quiet"), |
364 |
"--$input_file=fd://" . (fileno $slave), |
365 |
@{ $self->{args} }, |
366 |
@extra_args; |
367 |
exit 1; |
368 |
} |
369 |
|
370 |
$self->{fh} = $fh; |
371 |
|
372 |
my $trace = delete $self->{trace} || sub { }; |
373 |
|
374 |
$trace = sub { warn "$_[0] $_[1]\n" } if $trace && !ref $trace; |
375 |
|
376 |
my $buf; |
377 |
my $wbuf; |
378 |
|
379 |
Scalar::Util::weaken $self; |
380 |
|
381 |
$self->{rw} = AE::io $fh, 0, sub { |
382 |
if (sysread $fh, $buf, 8192, length $buf) { |
383 |
while ($buf =~ s/^([^\n]+)\n//) { |
384 |
$trace->("mpv>" => "$1"); |
385 |
|
386 |
if ("{" eq substr $1, 0, 1) { |
387 |
eval { |
388 |
my $reply = $JSON->new->latin1->decode ($1); |
389 |
|
390 |
if (exists $reply->{event}) { |
391 |
if ( |
392 |
$reply->{event} eq "client-message" |
393 |
and $reply->{args}[0] eq "AnyEvent::MPV" |
394 |
) { |
395 |
if ($reply->{args}[1] eq "key") { |
396 |
(my $key = $reply->{args}[2]) =~ s/\\x(..)/chr hex $1/ge; |
397 |
$self->on_key ($key); |
398 |
} |
399 |
} else { |
400 |
$self->on_event ($reply->{event}, $reply); |
401 |
} |
402 |
} elsif (exists $reply->{request_id}) { |
403 |
my $cv = delete $self->{cmd_cv}{$reply->{request_id}}; |
404 |
|
405 |
unless ($cv) { |
406 |
warn "no cv found for request id <$reply->{request_id}>\n"; |
407 |
next; |
408 |
} |
409 |
|
410 |
if (exists $reply->{data}) { |
411 |
$cv->send ($reply->{data}); |
412 |
} elsif ($reply->{error} eq "success") { # success means error... eh.. no... |
413 |
$cv->send; |
414 |
} else { |
415 |
$cv->croak ($reply->{error}); |
416 |
} |
417 |
|
418 |
} else { |
419 |
warn "unexpected reply from mpv, pleasew report: <$1>\n"; |
420 |
} |
421 |
}; |
422 |
warn $@ if $@; |
423 |
} else { |
424 |
$trace->("mpv>" => "$1"); |
425 |
} |
426 |
} |
427 |
} else { |
428 |
$self->stop; |
429 |
$self->on_eof; |
430 |
} |
431 |
}; |
432 |
|
433 |
$self->{_send} = sub { |
434 |
$wbuf .= "$_[0]\n"; |
435 |
|
436 |
$trace->(">mpv" => "$_[0]"); |
437 |
|
438 |
$self->{ww} ||= AE::io $fh, 1, sub { |
439 |
my $len = syswrite $fh, $wbuf; |
440 |
substr $wbuf, 0, $len, ""; |
441 |
undef $self->{ww} unless length $wbuf; |
442 |
}; |
443 |
}; |
444 |
|
445 |
1 |
446 |
} |
447 |
|
448 |
=item $mpv->stop |
449 |
|
450 |
Ensures that F<mpv> is being stopped, by killing F<mpv> with a C<TERM> |
451 |
signal if needed. After this, you can C<< ->start >> a new instance again. |
452 |
|
453 |
=cut |
454 |
|
455 |
sub stop { |
456 |
my ($self) = @_; |
457 |
|
458 |
delete $self->{rw}; |
459 |
delete $self->{ww}; |
460 |
|
461 |
if ($self->{pid}) { |
462 |
|
463 |
close delete $self->{fh}; # current mpv versions should cleanup on their own on close |
464 |
|
465 |
kill TERM => $self->{pid}; |
466 |
|
467 |
} |
468 |
|
469 |
delete $self->{pid}; |
470 |
delete $self->{cmd_cv}; |
471 |
} |
472 |
|
473 |
=item $mpv->on_eof |
474 |
|
475 |
This method is called when F<mpv> quits - usually unexpectedly. The |
476 |
default implementation will call the C<on_eof> code reference specified in |
477 |
the constructor, or do nothing if none was given. |
478 |
|
479 |
For subclassing, see I<SUBCLASSING>, below. |
480 |
|
481 |
=cut |
482 |
|
483 |
sub on_eof { |
484 |
my ($self) = @_; |
485 |
|
486 |
$self->{on_eof}($self) if $self->{on_eof}; |
487 |
} |
488 |
|
489 |
=item $mpv->on_event ($event, $data) |
490 |
|
491 |
This method is called when F<mpv> sends an asynchronous event. The default |
492 |
implementation will call the C<on_event> code reference specified in the |
493 |
constructor, or do nothing if none was given. |
494 |
|
495 |
The first/implicit argument is the C<$mpv> object, the second is the event |
496 |
name (same as C<< $data->{event} >>, purely for convenience), and the |
497 |
third argument is the full event object as sent by F<mpv>. See L<List of |
498 |
events|https://mpv.io/manual/stable/#list-of-events> in its documentation. |
499 |
|
500 |
For subclassing, see I<SUBCLASSING>, below. |
501 |
|
502 |
=cut |
503 |
|
504 |
sub on_event { |
505 |
my ($self, $key) = @_; |
506 |
|
507 |
$self->{on_event}($self, $key) if $self->{on_event}; |
508 |
} |
509 |
|
510 |
=item $mpv->on_key ($string) |
511 |
|
512 |
Invoked when a key declared by C<< ->bind_key >> is pressed. The default |
513 |
invokes the C<on_key> code reference specified in the constructor with the |
514 |
C<$mpv> object and the key name as arguments, or do nothing if none was |
515 |
given. |
516 |
|
517 |
For more details and examples, see the C<bind_key> method. |
518 |
|
519 |
For subclassing, see I<SUBCLASSING>, below. |
520 |
|
521 |
=cut |
522 |
|
523 |
sub on_key { |
524 |
my ($self, $key) = @_; |
525 |
|
526 |
$self->{on_key}($self, $key) if $self->{on_key}; |
527 |
} |
528 |
|
529 |
=item $mpv->cmd ($command => $arg, $arg...) |
530 |
|
531 |
Queues a command to be sent to F<mpv>, using the given arguments, and |
532 |
immediately return a condvar. |
533 |
|
534 |
See L<the mpv |
535 |
documentation|https://mpv.io/manual/stable/#list-of-input-commands> for |
536 |
details on individual commands. |
537 |
|
538 |
The condvar can be ignored: |
539 |
|
540 |
$mpv->cmd (set_property => "deinterlace", "yes"); |
541 |
|
542 |
Or it can be used to synchronously wait for the command results: |
543 |
|
544 |
$cv = $mpv->cmd (get_property => "video-format"); |
545 |
$format = $cv->recv; |
546 |
|
547 |
# or simpler: |
548 |
|
549 |
$format = $mpv->cmd (get_property => "video-format")->recv; |
550 |
|
551 |
# or even simpler: |
552 |
|
553 |
$format = $mpv->cmd_recv (get_property => "video-format"); |
554 |
|
555 |
Or you can set a callback: |
556 |
|
557 |
$cv = $mpv->cmd (get_property => "video-format"); |
558 |
$cv->cb (sub { |
559 |
my $format = $_[0]->recv; |
560 |
}); |
561 |
|
562 |
On error, the condvar will croak when C<recv> is called. |
563 |
|
564 |
=cut |
565 |
|
566 |
sub cmd { |
567 |
my ($self, @cmd) = @_; |
568 |
|
569 |
my $cv = AE::cv; |
570 |
|
571 |
my $reqid = ++$self->{reqid}; |
572 |
$self->{cmd_cv}{$reqid} = $cv; |
573 |
|
574 |
my $cmd = $JSON->new->utf8->encode ({ command => ref $cmd[0] ? $cmd[0] : \@cmd, request_id => $reqid*1 }); |
575 |
|
576 |
# (un-)apply escape_binary hack |
577 |
$cmd =~ s/\xf4\x8e\x97\x9f(..)/sprintf sprintf "\\x%02x", hex $1/ges; # f48e979f == 10e5df in utf-8 |
578 |
|
579 |
$self->{_send}($cmd); |
580 |
|
581 |
$cv |
582 |
} |
583 |
|
584 |
=item $result = $mpv->cmd_recv ($command => $arg, $arg...) |
585 |
|
586 |
The same as calling C<cmd> and immediately C<recv> on its return |
587 |
value. Useful when you don't want to mess with F<mpv> asynchronously or |
588 |
simply needs to have the result: |
589 |
|
590 |
$mpv->cmd_recv ("stop"); |
591 |
$position = $mpv->cmd_recv ("get_property", "playback-time"); |
592 |
|
593 |
=cut |
594 |
|
595 |
sub cmd_recv { |
596 |
&cmd->recv |
597 |
} |
598 |
|
599 |
=item $mpv->bind_key ($INPUT => $string) |
600 |
|
601 |
This is an extension implement by this module to make it easy to get key events. The way this is implemented |
602 |
is to bind a C<client-message> witha first argument of C<AnyEvent::MPV> and the C<$string> you passed. This C<$string> is then |
603 |
passed ot the C<on_key> handle when the key is proessed, e.g.: |
604 |
|
605 |
my $mpv = AnyEvent::MPV->new ( |
606 |
on_key => sub { |
607 |
my ($mpv, $key) = @_; |
608 |
|
609 |
if ($key eq "letmeout") { |
610 |
print "user pressed escape\n"; |
611 |
} |
612 |
}, |
613 |
); |
614 |
|
615 |
$mpv_>bind_key (ESC => "letmeout"); |
616 |
|
617 |
The key configuration is lost when F<mpv> is stopped and must be (re-)done |
618 |
after every C<start>. |
619 |
|
620 |
=cut |
621 |
|
622 |
sub bind_key { |
623 |
my ($self, $key, $event) = @_; |
624 |
|
625 |
$event =~ s/([^A-Za-z0-9\-_])/sprintf "\\x%02x", ord $1/ge; |
626 |
$self->cmd (keybind => $key => "no-osd script-message AnyEvent::MPV key $event"); |
627 |
} |
628 |
|
629 |
=back |
630 |
|
631 |
=head2 SUBCLASSING |
632 |
|
633 |
Like most perl objects, C<AnyEvent::MPV> objects are implemented as |
634 |
hashes, with the constructor simply storing all passed key-value pairs in |
635 |
the object. If you want to subclass to provide your own C<on_*> methods, |
636 |
be my guest and rummage around in the internals as much as you wish - the |
637 |
only guarantee that this module dcoes is that it will not use keys with |
638 |
double colons in the name, so youc an use those, or chose to simply not |
639 |
care and deal with the breakage. |
640 |
|
641 |
If you don't want to go to the effort of subclassing this module, you can |
642 |
also specify all event handlers as constructor keys. |
643 |
|
644 |
=head1 SEE ALSO |
645 |
|
646 |
L<AnyEvent>, L<the mpv command documentation|https://mpv.io/manual/stable/#command-interface>. |
647 |
|
648 |
=head1 AUTHOR |
649 |
|
650 |
Marc Lehmann <schmorp@schmorp.de> |
651 |
http://home.schmorp.de/ |
652 |
|
653 |
=cut |
654 |
|
655 |
1 |
656 |
|