use strict;
use warnings;
use GStreamer qw/ -init GST_SECOND /;
use Glib qw/TRUE FALSE/;
use POSIX ();
use Carp 'confess';
use File::Spec;

my $loop = Glib::MainLoop->new();
my $filename = File::Spec->rel2abs('foo.mp3');

my $pipeline = GStreamer::Pipeline->new('audio-pipe');
my $decoder = GStreamer::ElementFactory->make (uridecodebin=>'audio-decoder');
my $audio_sink = GStreamer::ElementFactory->make ("appsink", "audio-appsink");

$pipeline->add($decoder, $audio_sink);
$decoder->link($audio_sink);
$decoder->set(uri => Glib::filename_to_uri ($filename, "localhost"));
$audio_sink->set("sync", FALSE);
$audio_sink->set(emit_signals => TRUE);

my $TOTAL_RUNS = 2;
my $CURRENT_RUN = 1;
my $SECONDS_TO_SKIP = 25 * GST_SECOND;
my $SECONDS_TO_RECORD = 1;
my $SEEK = Glib::TRUE;
my $CAPTURE = Glib::FALSE;

$decoder->signal_connect('pad-added', sub{
      warn '[pad-added]';
      my ($adbin, $pad) = @_;
      $decoder->link($audio_sink);
   }
);

my $format;
my @datas;
my $datatarget;
my $datasize=0;
$audio_sink->signal_connect("new-buffer", sub{
      return unless $CAPTURE;
      warn '[new-buffer] @ ' . query_time();
      my $buf = $audio_sink->signal_emit('pull_buffer');
      my $data = $buf->data;
      my $size = $buf->size;
      unless ($format){
         my $caps = $buf->get_caps();
         $format = _read_audio_caps($caps);
         $datatarget = $format->{channels} * $SECONDS_TO_RECORD *
                       ($format->{width}/8) * $format->{rate};
      }
      push @datas, $data;
      $datasize += $size;
      $loop->quit if $audio_sink->get('eos');
      if ($datasize >= $datatarget) {
         $CURRENT_RUN++;
         if ($CURRENT_RUN > $TOTAL_RUNS) {
            $loop->quit;
         } else {
            $datasize = 0;
            $CAPTURE = Glib::FALSE;
            $SEEK = Glib::TRUE;
            # Note that we have to use Glib::Idle as we would otherwise
            # create a deadlock.
            Glib::Idle->add(sub {
               $pipeline->set_state('paused');
               Glib::SOURCE_REMOVE;
            });
         }
      }
      return 1;
   }
);

$pipeline->get_bus()->add_watch(sub {
  my ($bus, $message) = @_;
  if ($message->type & "state-changed") {
    printf "state-changed: %s => %s\n", $message->src->get_name, $message->new_state;
    if ($message->src == $pipeline && $message->new_state eq "paused") {
      if ($SEEK) {
        my $new_pos = $CURRENT_RUN * $SECONDS_TO_SKIP +
                      ($CURRENT_RUN - 1) * $SECONDS_TO_RECORD;
        $pipeline->seek(1, 'time', 'flush', 'set', $new_pos, 'none', 0);
        $CAPTURE = Glib::TRUE;
        $SEEK = Glib::FALSE;
        $pipeline->set_state('playing');
      }
    }
  }

  elsif ($message->type & "error") {
    warn $message -> error;
    $loop->quit;
  }

  elsif ($message->type & "eos") {
    $loop->quit;
  }

  # remove message from the queue
  return TRUE;
});

$pipeline->set_state('paused');
$loop->run;
$pipeline->set_state('null');

open my $fh, '>', 'tmp.wav';
print $fh @datas;
close $fh;

sub query_time{
   my $q = GStreamer::Query::Position->new('time'); #bleh
   $pipeline->query($q);
   return $q->position / GST_SECOND;
}

sub _read_audio_caps{
   my $caps_obj = shift;
   my $caps = $caps_obj->to_string;
   my ($endian) = $caps =~ /endianness=\(int\)(\d)/;
   my $littleendian = $endian==4;
   my ($rate) = $caps =~ /rate=\(int\)(\d+)\b/;
   my ($signed) = $caps =~ /signed=\(boolean\)(\w+)\b/;
   my $signedness = $signed eq 'true';
   #ignoring depth. I don't suppose it's relevant.
   my ($width) = $caps =~ /width=\(int\)(\d+)\b/;
   my ($channels) = $caps =~ /channels=\(int\)(\d)/;
 
   my $ptemplate; #TEMPLATE for unpack. bleh.
   $ptemplate = 'n' if (($width==16) and !$littleendian);
   $ptemplate = 'N' if (($width==32) and !$littleendian);
   $ptemplate = 's' if (($width==16) and $littleendian);
   $ptemplate = 'l' if (($width==32) and $littleendian);
   die "$caps unpackable?" unless $ptemplate;

   my $format = {
      littleendian => $littleendian,
      rate => $rate,
      width => $width,
      signed => $signedness,
      channels => $channels,
      packtemplate => $ptemplate,
   };
   return $format;
}
