AnyEvent-MPV

 view release on metacpan or  search on metacpan

MPV.pm  view on Meta::CPAN


   $self->{mpv}->cmd (set_property => "pause" => "yes");
   $self->{mpv}->cmd ("playlist_remove", "current");
   $self->{mpv}->cmd (set_property => "video-rotate" => 0);
   $self->{mpv}->cmd (set_property => "lavfi-complex" => "");

Loading a file is a bit more complicated, as bluray and DVD rips are
supported:

   if ($moviedir) {
      if ($moviedir eq "br") {
         $mpv->cmd (set => "bluray-device" => $path);
         $mpv->cmd (loadfile => "bd://");
      } elsif ($moviedir eq "dvd") {
         $mpv->cmd (set => "dvd-device" => $path);
         $mpv->cmd (loadfile => "dvd://");
      }
   } elsif ($type eq "video/iso-bluray") {
      $mpv->cmd (set => "bluray-device" => $path);
      $mpv->cmd (loadfile => "bd://");
   } else {
      $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:

   # every time we loaded a file, we create a new hash
   my $guards = $self->{mpv_guards} = { };

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 {
         delete $guards->{file_loaded};
         return if $guards != $self->{mpv_guards};

         $mpv->cmd (get_property => "video-format")->cb (sub {
            return if $guards != $self->{mpv_guards};

            # video-format handling
            return if eval { $_[0]->recv; 1 };

            # no video? assume audio and visualize, cpu usage be damned
            $mpv->cmd (set => "lavfi-complex" => ...");
         });

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

            $self->{mpv_eventbox}->show_all;

            $w = $mpv->cmd (get_property => "dwidth");
            $h = $mpv->cmd (get_property => "dheight");

            $h->cb (sub {
               $w = eval { $w->recv };
               $h = eval { $h->recv };

               $mpv->cmd (set_property => "pause" => "no");

               if ($w && $h) {
                  # resize our window
               }

            });
         });

      });

   });

Most of the rest of the code is much simpler and just deals with forwarding user commands:

   } elsif ($key == $Gtk2::Gdk::Keysyms{Right}) { $mpv->cmd ("osd-msg-bar" => seek => "+10");
   } elsif ($key == $Gtk2::Gdk::Keysyms{Left} ) { $mpv->cmd ("osd-msg-bar" => seek => "-10");
   } elsif ($key == $Gtk2::Gdk::Keysyms{Up}   ) { $mpv->cmd ("osd-msg-bar" => seek => "+60");
   } elsif ($key == $Gtk2::Gdk::Keysyms{Down} ) { $mpv->cmd ("osd-msg-bar" => seek => "-60");
   } elsif ($key == $Gtk2::Gdk::Keysyms{a})   ) { $mpv->cmd ("osd-msg-msg" => cycle => "audio");
   } elsif ($key == $Gtk2::Gdk::Keysyms{j}    ) { $mpv->cmd ("osd-msg-msg" => cycle => "sub");
   } elsif ($key == $Gtk2::Gdk::Keysyms{o}    ) { $mpv->cmd ("no-osd" => "cycle-values", "osd-level", "2", "3", "0", "2");
   } elsif ($key == $Gtk2::Gdk::Keysyms{p}    ) { $mpv->cmd ("no-osd" => cycle => "pause");
   } elsif ($key == $Gtk2::Gdk::Keysyms{9}    ) { $mpv->cmd ("osd-msg-bar" => add => "ao-volume", "-2");
   } elsif ($key == $Gtk2::Gdk::Keysyms{0}    ) { $mpv->cmd ("osd-msg-bar" => add => "ao-volume", "+2");

=head1 SEE ALSO



( run in 1.641 second using v1.01-cache-2.11-cpan-39bf76dae61 )