#!/usr/bin/perl
#
##################################################
#
# GiantDisc mp3 Jukebox
# 
# © 2000-2004, Rolf Brugger
#
##################################################


# gdplayd.pl: Giant Disc play playlist deamon
#   When started, reads the current tracks form the playlist file and plays 
#   them sequentally.
#   Actions: (This script is only started, if something has to be played/mp3-decoded.
#             If playing is stopped or paused, this script should be terminated)
#     1) Reads the playlist and playstate table
#     2) Play tracks starting from the 'current' track
#     3) if the 'current' track comes to its end, the next track is played
#        and the playstate table is updated
#     4) while playing a track, playstate table containing the current 
#        playing position is permanently updated
#
#   The script terminates, when all tracks are played 

use lib '/home/music/bin';
use gddb;
use gdparams;
use gdgentools;

use DBI;
use IPC::Open2;    # used for gapless playback mode
use strict;

open(ERROR, '>>/home/music/tmp/gdplayd.err');

my $mp3playerpid;  # (global) variables for gapless playback mode
my $flacplayerpid;
my ($rdr,$wtr);
my ($mp3rdr,$mp3wtr);
my ($flacrdr,$flacwtr);


### constants
my $ossdriver     = 0;  # playertypes < 10 are local soundcards
my $alsadriver    = 1;
my $bluemp3driver = 10;  # mp3 streamed over bluetooth (http://www.beecon.de BlueMP3)
my $mp3streamer   = 20;  # mp3 streamer (http://www.barix.com exstreamer)



### Parameters
if(scalar(@ARGV) != 3){print("gdplayd needs three parameters (dbhost, playerid and auiochannel)\n");exit(0);}
my $dbhost   = $ARGV[0];
my $playerid = $ARGV[1];
my $audiochannel = $ARGV[2]; 

### open DB connection
my $dbh = DBI->connect("DBI:mysql:GiantDisc:".$dbhost, "music", "")
    or die "unable to connect to GiantDisc db";

### set termination handler
$SIG{TERM} = sub { print "playd killed\n"; $dbh->disconnect; exit;}; # set Kill event handler


### write my PID to DB, that this process can be killed efficiently
#   -> unstable and too slow when killed with Proc::Killall or Proc::Killfam
#print "my pid is \"".$$."\"\n";
gdgentools::pll_write_player_processid($dbh, $playerid, $audiochannel, $$);


### Begin
my $base     = gdparams::gdbase();
my $ptlogger = gdgentools::param_value($dbh, $playerid, "ptlogger");

### define play time filter pipe

my $stdfilterpipe = "| $ptlogger $dbhost $playerid $audiochannel ";
my $simulatedfilterpipe = " | gdplaytmsim.pl $dbhost $playerid $audiochannel";

my ($sth, $row, $audiofile, $trackind, $trackid, $state, $startframe);
my ($fileformat, $dummy, $audioparam);
my ($playertype, $playerhost, $playerdevice);
my ($shufflepar, $shufflestat);

### Get player type
($playertype, $playerhost, $playerdevice) = gdgentools::pll_get_playertype($dbh, $playerid, $audiochannel);
#print "play: type=$playertype, host=$playerhost, device=$playerdevice\n";

### Get State of player
($trackind, $state, $startframe) = gdgentools::pll_get_playstate($dbh, $playerid, $audiochannel);
($shufflepar, $shufflestat) =      gdgentools::pll_get_shufflestate($dbh, $playerid, $audiochannel);

if (length($shufflepar) > 1){ # are we in shuffle play mode?
print "\nSHUFFLE-PLAY with param: '$shufflepar'\n";
  gdgentools::tracklist_del_upto_item($dbh, $playerid, $audiochannel, $trackind);
  ### generate next tracks to be played
  fill_random_tracks($dbh, $playerid, 0, $shufflepar);
  $trackind = 0;
}

# get first track to be played
$trackid = gdgentools::tracklist_get_item($dbh, $playerid, 0, $trackind);

###
#print "gdplayd.pl sends message now ($playerid, $audiochannel)\n";
gdgentools::plmsg_send($playerid, $audiochannel);
#print "gdplayd.pl: message sent\n";

while($trackid){
  gdgentools::pll_write_playstate($dbh, $playerid, $audiochannel, $trackind, "pl");
  gdgentools::pll_save_resume($dbh, $playerid, $audiochannel); # save playstate for resume feature

  $sth = $dbh->prepare("SELECT * FROM tracks WHERE id = $trackid");
  $sth->execute;
  $row = $sth->fetchrow_hashref;
  $sth->finish;

  ($fileformat, $audioparam) = gdgentools::bitrate_str_to_format_param($row->{bitrate});  # split "mp3 128"
  $audiofile = "";
  if (gdgentools::is_mp3stream($row->{mp3file})){
    ### it's a URL -> stream it
    $audiofile = $row->{mp3file};
  }
  else{
    ### it's a normal audio file -> retrieve it in one of the audio file directories
    if (length($row->{mp3file})>4){
      $audiofile = `ls $base/[0-9][0-9]/$row->{mp3file}`;
      chop ($audiofile);
    }else{print "Error: audiofile'".$row->{mp3file}."' broken?\n"}
  }
  if(length($audiofile)>4){
    my $startsec  = int($startframe / gdgentools::frames_per_second()); # frames->sec
    my $tracklen  = $row->{length};

    print("playing $audiofile ($row->{title}) starting at $startsec sec, format: '$fileformat'\n");
    ### while playing, redirect std-err to playtime logger

    ####################################################################################
    ### play on soundcard
    if ( ($playertype == $ossdriver) || ($playertype == $alsadriver) ){
      if (gdgentools::param_value($dbh, $playerid, "gaplessplayback")) {
        print "gapless: play next\n";
        localplay_gapless($audiofile, $fileformat, $startframe, $tracklen);
      }
      else{
        localplay_separate_invokations($audiofile, $fileformat, $startframe, $tracklen);
      }
    } # end: local soundcard


    ####################################################################################
    ### send mp3 stream over bluetooth
#    elsif ($playertype == $bluemp3driver){  # playerhost is bluetooth hardware address
#      print ("l2send $playerhost $audiofile  $stdfilterpipe $tracklen l2send\n");
#      system("l2send $playerhost $audiofile  $stdfilterpipe $tracklen l2send");
#    }
    
    ####################################################################################
    ### send audio stream to streaming client
    elsif($playertype == $mp3streamer){  # playerdevice is port number
      print ("gdstream.pl -h $playerhost -p $playerdevice -b ".$audioparam*1000
            ." -f $fileformat -s $startsec -l $tracklen"
            ." -d $dbhost -i $playerid -c $audiochannel $audiofile\n");
      system ("gdstream.pl -h $playerhost -p $playerdevice -b ".$audioparam*1000
             ." -f $fileformat -s $startsec -l $tracklen"
             ." -d $dbhost -i $playerid -c $audiochannel $audiofile");
      sleep 2;    # give input buffer time to recover  (extreamer skips tracks if sleep=1)
    }

    else{
        print "Player Error: unknown player type $playertype\n";
    }
    
    $startframe = 0; # play next tracks from beginning
  }
  else {
    print("ERROR: could not find mp3 file! ID=$row->{sourceid}, track=$row->{tracknb}\n");
  }  

  # track has ended - write it's id to the playlog
  gdgentools::log_trackid($dbh, $trackid);

  ### get next track
  if (length($shufflepar) > 1){ # are we in shuffle play mode?
    ### generate next tracks to be played; trackindex remains 0!
    gdgentools::tracklist_del_item($dbh, $playerid, 0, 0); # remove 0th item
    fill_random_tracks($dbh, $playerid, 0, $shufflepar);
  }
  else{                         # are we in normal play mode
    ### go to next track
    $trackind++;
  }
  ### get track-ID at current index
  $trackid = gdgentools::tracklist_get_item($dbh, $playerid, 0, $trackind);
}

gdgentools::pll_write_playstate($dbh, $playerid, $audiochannel, $trackind, "st");
gdgentools::pll_write_playtime($dbh, $playerid, $audiochannel, 
        0,   # frames played
        0);  # frames total


### clear my PID in DB, telling that player is inactive
#gdgentools::pll_write_player_processid($dbh, $playerid, $audiochannel, $$);

if (gdgentools::param_value($dbh, $playerid, "gaplessplayback")) {
  kill 9, $flacplayerpid;
  kill 9, $mp3playerpid;
}
$dbh->disconnect;

exit(0);




####################################################################################
####################################################################################
####################################################################################


sub localplay_separate_invokations{
  my ($audiofile, $fileformat, $startframe, $tracklen) = @_;

  if (gdgentools::param_set($dbh, $playerid, $fileformat."player")){
    my $playpar = gdgentools::param_value($dbh, $playerid, $fileformat."playerparams");
    ### specific audio decoder specified for current format
    if ($fileformat eq "mp3"){     ### mp3 ###
      #print( "mpg123 -v $playpar --skip $startframe $audiofile 2>&1 $stdfilterpipe $tracklen mpg123\n");
      system( "mpg123 -v $playpar --skip $startframe $audiofile 2>&1 $stdfilterpipe $tracklen mpg123");
    }
    elsif ($fileformat eq "ogg"){  ### ogg ###
      my $startsec  = int($startframe / gdgentools::frames_per_second()); # frames->sec
      #print( "ogg123 -v $playpar --skip $startsec $audiofile 2>&1 $stdfilterpipe $tracklen ogg123\n");
      system( "ogg123 -v $playpar --skip $startsec $audiofile 2>&1 $stdfilterpipe $tracklen ogg123");
    }
    elsif ($fileformat eq "flac"){ ### flac ###
      ### we have to write the total track length (used by playtimesimulator)
      my $startsamp = int($startframe * gdgentools::samples_per_frame());
      gdgentools::pll_write_playtime($dbh, $playerid, $audiochannel, 
            $startframe, int($tracklen * gdgentools::frames_per_second()) );
      system ("flac $playpar -c -d -s --skip $startsamp --endian=little --sign=signed --force-raw-format $audiofile "
             ." | rawplay -f s16_le $simulatedfilterpipe");
    }
    elsif ($fileformat eq "real"){ ### Real Audio Stream ###
      system( "trplayer $playpar $audiofile 2>&1 $simulatedfilterpipe");
    }
    else{
      print "Player Error: unknown file type $fileformat of $audiofile\n";
    }
  }
  else{
    ### use default (audio) decoder
    localplay_default($audiofile, $startframe, $tracklen);
  }
}


####################################################################################


sub localplay_gapless{
  ### gapless playback code was contributed by Mark Alston (July '04)
  #
  # Method: 
  #  - start audio player in interactive mode
  #  - permanently analyze std-out of audio player
  #  - when track ends, immediately start playing next track (command through std-in of audio player)
  
  my ($audiofile, $fileformat, $startframe, $tracklen) = @_;

  if (gdgentools::param_set($dbh, $playerid, $fileformat."player")){
    my $playpar = gdgentools::param_value($dbh, $playerid, $fileformat."playerparams");
    my $startsec  = int($startframe / gdgentools::frames_per_second()); # frames->sec
    ### specific audio decoder specified for current format
    if ($fileformat eq "ogg"){  ### ogg ###
      system( "ogg123 -v $playpar --skip $startsec $audiofile 2>&1 $stdfilterpipe $tracklen ogg123");
    }
    elsif ($fileformat eq "mp3" || $fileformat eq "flac"){     ### mp3 or flac ###
      if ($fileformat eq "mp3" ){ ### mp3 ### 
        unless (defined $mp3playerpid) {
          # Open mpg321 in remote mode.  "junk" is needed to avoid
          # having mpg321 throw an error.
          # We open mpg321 with both read and write handles.
          $mp3playerpid = open2($mp3rdr, $mp3wtr, 'mpg321 -R junk');
        }
        # Set our current read and write handles to the mp3 ones.
        $wtr = $mp3wtr;
        $rdr = $mp3rdr;
      }
      elsif ($fileformat eq "flac"){ ### flac ###
        unless (defined $flacplayerpid) {
          # Open flac123 in remote mode.  "junk" is needed to avoid
          # having flac123 throw an error.
          # We open flac123 with both read and write handles.
          $flacplayerpid = open2($flacrdr, $flacwtr, 'flac123 -R junk');
        }
        # Set our current read and write handles to the flac ones.
        $wtr = $flacwtr;
        $rdr = $flacrdr;
      }
      ### we have to write the total track length (used by playtimesimulator)
      gdgentools::pll_write_playtime($dbh, $playerid, $audiochannel, 
                         $startframe, int($row->{length} * gdgentools::frames_per_second()) );
      print ("loading file into player");
      my ($frmplayed, $frmtotal) = gdgentools::pll_get_playtime($dbh, $playerid, $audiochannel);
  
      # This sends the load command to player.
      print $wtr "load $audiofile\n";
  
      # If we need to start at a point other than the beginning, 
      # tell player to go to that point.
      if ( $startsec != 0 ) {
        print $wtr "jump $startsec\n";
      }
  
      # We now watch the output of the player to both update the 
      # playtime entry but also to know when to load the next 
      # track.  With flac123 this results is absolutely gapless playback.
      # With mpg321 you should be nearly gapless playback.
      my $ret;
      my @array = ();
      while ($ret=<$rdr>) {
        @array = split(/ /,$ret);
        if ( @array[0] eq "\@F" ) {
          # Lines starting with @F contain the playtime info.
          # See the readme.REMOTE file in flac123 or mpg321 dist for info.
          my $string = @array[2];
          gdgentools::pll_write_playtime($dbh, $playerid, $audiochannel, @array[3] * gdgentools::frames_per_second(), $frmtotal);
          # When $string = 0 then we have finished with the current file.  
          # Time to move on.
          if ($string eq 0) {
            last;
          }
        }
      }
    }
    elsif ($fileformat eq "real"){ ### Real Audio Stream ###
      system( "trplayer $playpar $audiofile 2>&1 $simulatedfilterpipe");
    }
  }
  else{
    ### use default (audio) decoder
    localplay_default($audiofile, $startframe, $tracklen);
  }
}


####################################################################################


sub localplay_default{
  my ($audiofile, $startframe, $tracklen) = @_; # the other parameters are global variables: $dbh, $playerid, $stdfilterpipe

  # print "use default $fileformat player\n";
  my $playpar = gdgentools::param_value($dbh, $playerid, "defaultplayerparams");
  my $startsec  = int($startframe / gdgentools::frames_per_second()); # frames->sec
  print ( "mplayer $playpar -ss $startsec $audiofile $stdfilterpipe $tracklen mplayer\n");
  system( "mplayer $playpar -ss $startsec $audiofile $stdfilterpipe $tracklen mplayer");
}


####################################################################################

### Shuffle play functions
sub fill_random_tracks{
  my ($dbh, $playerid, $listtype, $shufflepar) = @_;
  my $tracks_predetermined = 5;
  #print("we have".gdgentools::tracklist_get_nb_items($dbh, $playerid, $listtype)." tracks\n");
  while(gdgentools::tracklist_get_nb_items($dbh, $playerid, $listtype) < $tracks_predetermined){

    my $where = gddb::track_where_clause($gddb::get_tracks, split('\t', $shufflepar));
    #print("whereclause: $where\n");
    my ($row, $trackid, $sth);
    $sth = $dbh->prepare( 
         "SELECT id,(id*0)+RAND() AS randnum FROM tracks "
        ."WHERE $where ORDER BY randnum LIMIT 1" );
    $sth->execute;
    $trackid = 1; # in case of an error we want to have something
    if($row = $sth->fetchrow_hashref){
      $trackid = $row->{id};
    }
    #print("adding random track id=$trackid\n");
    gdgentools::tracklist_append_list($dbh, $playerid, $listtype, $trackid);
    $sth->finish;
  } # end while
}
#

####################################################################################
