AnyEvent-MPV

 view release on metacpan or  search on metacpan

MPV.pm  view on Meta::CPAN

Knowledge of the L<mpv command
interface|https://mpv.io/manual/stable/#command-interface> is required to
use this module.

Features of this module are:

=over

=item uses AnyEvent, so integrates well into most event-based programs

=item supports asynchronous and synchronous operation

=item allows you to properly pass binary filenames

=item accepts data encoded in any way (does not crash when mpv replies with non UTF-8 data)

=item features a simple keybind/event system

=back

=head2 OVERVIEW OF OPERATION

This module forks an F<mpv> process and uses F<--input-ipc-client> (or
equivalent) to create a bidirectional communication channel between it and
the F<mpv> process.

It then speaks the somewhat JSON-looking (but not really being JSON)
protocol that F<mpv> implements to both send it commands, decode and
handle replies, and handle asynchronous events.

Here is a very simple client:

   use AnyEvent;
   use AnyEvent::MPV;
   
   my $videofile = "./xyzzy.mkv";

   my $mpv = AnyEvent::MPV->new (trace => 1);

MPV.pm  view on Meta::CPAN


Since commands send I<to> F<mpv> are send in UTF-8, we need to escape the
filename (which might be in any encoding) using the C<esscape_binary>
method - this is not needed if your filenames are just ascii, or magically
get interpreted correctly, but if you accept arbitrary filenamews (e.g.
from the user), you need to do this.

The C<cmd_recv> method then queues the command, waits for a reply and
returns the reply data (or croaks on error). F<mpv> would, at this point,
load the file and, if everything was successful, show the first frame and
pause. Note that, since F<mpv> is implement rather synchronously itself,
do not expect commands to fail in many circumstances - for example, fit
he file does not exit, you will likely get an event, but the C<loadfile>
command itself will run successfully.

To unpause, we send another command, C<set>, to set the C<pause> property
to C<no>, this time using the C<cmd> method, which queues the command, but
instead of waiting for a reply, it immediately returns a condvar that cna
be used to receive results.

This should then cause F<mpv> to start playing the video.

MPV.pm  view on Meta::CPAN

=cut

sub on_eof {
   my ($self) = @_;

   $self->{on_eof}($self) if $self->{on_eof};
}

=item $mpv->on_event ($event, $data)

This method is called when F<mpv> sends an asynchronous event. The default
implementation will call the C<on_event> code reference specified in the
constructor, or do nothing if none was given.

The first/implicit argument is the C<$mpv> object, the second is the
event name (same as C<< $data->{event} >>, purely for convenience), and
the third argument is the event object as sent by F<mpv> (sans C<event>
key). See L<List of events|https://mpv.io/manual/stable/#list-of-events>
in its documentation.

For subclassing, see I<SUBCLASSING>, below.

MPV.pm  view on Meta::CPAN

immediately return a condvar.

See L<the mpv
documentation|https://mpv.io/manual/stable/#list-of-input-commands> for
details on individual commands.

The condvar can be ignored:

   $mpv->cmd (set_property => "deinterlace", "yes");

Or it can be used to synchronously wait for the command results:

   $cv = $mpv->cmd (get_property => "video-format");
   $format = $cv->recv;

   # or simpler:

   $format = $mpv->cmd (get_property => "video-format")->recv;

   # or even simpler:

MPV.pm  view on Meta::CPAN


sub cmd {
   my $self = shift;

   $self->{_cmd}->(@_)
}

=item $result = $mpv->cmd_recv ($command => $arg, $arg...)

The same as calling C<cmd> and immediately C<recv> on its return
value. Useful when you don't want to mess with F<mpv> asynchronously or
simply needs to have the result:

   $mpv->cmd_recv ("stop");
   $position = $mpv->cmd_recv ("get_property", "playback-time");

=cut

sub cmd_recv {
   &cmd->recv
}

MPV.pm  view on Meta::CPAN

      $mpv->cmd (loadfile => $mpv->escape_binary ($path));
   }

After this, C<Gtk2::CV> waits for the file to be loaded, video to be
configured, and then queries the video size (to resize its own window)
and video format (to decide whether an audio visualizer is needed for
audio playback). The problematic word here is "wait", as this needs to be
imploemented using callbacks.

This made the code much harder to write, as the whole setup is very
asynchronous (C<Gtk2::CV> talks to the command interface in F<mpv>, which
talks to the decode and playback parts, all of which run asynchronously
w.r.t. each other. In practise, this can mean that C<Gtk2::CV> waits for
a file to be loaded by F<mpv> while the command interface of F<mpv> still
deals with the previous file and the decoder still handles an even older
file). Adding to this fact is that Gtk2::CV is bound by the glib event
loop, which means we cannot wait for replies form F<mpv> anywhere, so
everything has to be chained callbacks.

The way this is handled is by creating a new empty hash ref that is unique
for each loaded file, and use it to detect whether the event is old or
not, and also store C<AnyEvent::MPV> guard objects in it:

MPV.pm  view on Meta::CPAN


Then, when we wait for an event to occur, delete the handler, and, if the
C<mpv_guards> object has changed, we ignore it. Something like this:

   $guards->{file_loaded} = $mpv->register_event (file_loaded => sub {
      delete $guards->{file_loaded};
      return if $guards != $self->{mpv_guards};

Commands do not have guards since they cnanot be cancelled, so we don't
have to do this for commands. But what prevents us form misinterpreting
an old event? Since F<mpv> (by default) handles commands synchronously,
we can queue a dummy command, whose only purpose is to tell us when all
previous commands are done. We use C<get_version> for this.

The simplified code looks like this:

   Scalar::Util::weaken $self;

   $mpv->cmd ("get_version")->cb (sub {

      $guards->{file_loaded} = $mpv->register_event (file_loaded => sub {

README  view on Meta::CPAN

    since none of them use AnyEvent, I wrote my own. When in doubt, have a
    look at them, too.

    Knowledge of the mpv command interface
    <https://mpv.io/manual/stable/#command-interface> is required to use
    this module.

    Features of this module are:

    uses AnyEvent, so integrates well into most event-based programs
    supports asynchronous and synchronous operation
    allows you to properly pass binary filenames
    accepts data encoded in any way (does not crash when mpv replies with
    non UTF-8 data)
    features a simple keybind/event system

  OVERVIEW OF OPERATION
    This module forks an mpv process and uses --input-ipc-client (or
    equivalent) to create a bidirectional communication channel between it
    and the mpv process.

    It then speaks the somewhat JSON-looking (but not really being JSON)
    protocol that mpv implements to both send it commands, decode and handle
    replies, and handle asynchronous events.

    Here is a very simple client:

       use AnyEvent;
       use AnyEvent::MPV;
   
       my $videofile = "./xyzzy.mkv";

       my $mpv = AnyEvent::MPV->new (trace => 1);

README  view on Meta::CPAN


    Since commands send *to* mpv are send in UTF-8, we need to escape the
    filename (which might be in any encoding) using the "esscape_binary"
    method - this is not needed if your filenames are just ascii, or
    magically get interpreted correctly, but if you accept arbitrary
    filenamews (e.g. from the user), you need to do this.

    The "cmd_recv" method then queues the command, waits for a reply and
    returns the reply data (or croaks on error). mpv would, at this point,
    load the file and, if everything was successful, show the first frame
    and pause. Note that, since mpv is implement rather synchronously
    itself, do not expect commands to fail in many circumstances - for
    example, fit he file does not exit, you will likely get an event, but
    the "loadfile" command itself will run successfully.

    To unpause, we send another command, "set", to set the "pause" property
    to "no", this time using the "cmd" method, which queues the command, but
    instead of waiting for a reply, it immediately returns a condvar that
    cna be used to receive results.

    This should then cause mpv to start playing the video.

README  view on Meta::CPAN

        again.

    $mpv->on_eof
        This method is called when mpv quits - usually unexpectedly. The
        default implementation will call the "on_eof" code reference
        specified in the constructor, or do nothing if none was given.

        For subclassing, see *SUBCLASSING*, below.

    $mpv->on_event ($event, $data)
        This method is called when mpv sends an asynchronous event. The
        default implementation will call the "on_event" code reference
        specified in the constructor, or do nothing if none was given.

        The first/implicit argument is the $mpv object, the second is the
        event name (same as "$data->{event}", purely for convenience), and
        the third argument is the event object as sent by mpv (sans "event"
        key). See List of events
        <https://mpv.io/manual/stable/#list-of-events> in its documentation.

        For subclassing, see *SUBCLASSING*, below.

README  view on Meta::CPAN

        immediately return a condvar.

        See the mpv documentation
        <https://mpv.io/manual/stable/#list-of-input-commands> for details
        on individual commands.

        The condvar can be ignored:

           $mpv->cmd (set_property => "deinterlace", "yes");

        Or it can be used to synchronously wait for the command results:

           $cv = $mpv->cmd (get_property => "video-format");
           $format = $cv->recv;

           # or simpler:

           $format = $mpv->cmd (get_property => "video-format")->recv;

           # or even simpler:

README  view on Meta::CPAN


           $cv = $mpv->cmd (get_property => "video-format");
           $cv->cb (sub {
              my $format = $_[0]->recv;
           });

        On error, the condvar will croak when "recv" is called.

    $result = $mpv->cmd_recv ($command => $arg, $arg...)
        The same as calling "cmd" and immediately "recv" on its return
        value. Useful when you don't want to mess with mpv asynchronously or
        simply needs to have the result:

           $mpv->cmd_recv ("stop");
           $position = $mpv->cmd_recv ("get_property", "playback-time");

    $mpv->bind_key ($INPUT => $string)
        This is an extension implement by this module to make it easy to get
        key events. The way this is implemented is to bind a
        "client-message" witha first argument of "AnyEvent::MPV" and the
        $string you passed. This $string is then passed to the "on_key"

README  view on Meta::CPAN

          $mpv->cmd (loadfile => $mpv->escape_binary ($path));
       }

    After this, "Gtk2::CV" waits for the file to be loaded, video to be
    configured, and then queries the video size (to resize its own window)
    and video format (to decide whether an audio visualizer is needed for
    audio playback). The problematic word here is "wait", as this needs to
    be imploemented using callbacks.

    This made the code much harder to write, as the whole setup is very
    asynchronous ("Gtk2::CV" talks to the command interface in mpv, which
    talks to the decode and playback parts, all of which run asynchronously
    w.r.t. each other. In practise, this can mean that "Gtk2::CV" waits for
    a file to be loaded by mpv while the command interface of mpv still
    deals with the previous file and the decoder still handles an even older
    file). Adding to this fact is that Gtk2::CV is bound by the glib event
    loop, which means we cannot wait for replies form mpv anywhere, so
    everything has to be chained callbacks.

    The way this is handled is by creating a new empty hash ref that is
    unique for each loaded file, and use it to detect whether the event is
    old or not, and also store "AnyEvent::MPV" guard objects in it:

README  view on Meta::CPAN


    Then, when we wait for an event to occur, delete the handler, and, if
    the "mpv_guards" object has changed, we ignore it. Something like this:

       $guards->{file_loaded} = $mpv->register_event (file_loaded => sub {
          delete $guards->{file_loaded};
          return if $guards != $self->{mpv_guards};

    Commands do not have guards since they cnanot be cancelled, so we don't
    have to do this for commands. But what prevents us form misinterpreting
    an old event? Since mpv (by default) handles commands synchronously, we
    can queue a dummy command, whose only purpose is to tell us when all
    previous commands are done. We use "get_version" for this.

    The simplified code looks like this:

       Scalar::Util::weaken $self;

       $mpv->cmd ("get_version")->cb (sub {

          $guards->{file_loaded} = $mpv->register_event (file_loaded => sub {



( run in 0.760 second using v1.01-cache-2.11-cpan-ff066701436 )