1# Copyright (C) 2005-2013 Quentin Sculo <squentin@free.fr> 2# 3# This file is part of Gmusicbrowser. 4# Gmusicbrowser is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License version 3, as 6# published by the Free Software Foundation 7 8package Play_mplayer; 9use strict; 10use warnings; 11use IO::Handle; 12 13use POSIX ':sys_wait_h'; #for WNOHANG in waitpid 14 15#$SIG{CHLD} = 'IGNORE'; # to make sure there are no zombies #cause crash after displaying a file dialog and then runnning an external command with mandriva's gtk2 16#$SIG{CHLD} = sub { while (waitpid(-1, WNOHANG)>0) {} }; 17 18my (@cmd_and_args,$file,$ChildPID,$WatchTag,$WatchTag2,$OUTPUTfh,@pidToKill,$Kill9); 19my $CMDfh; 20my (%supported,$mplayer); 21my $SoftVolume; 22my $GainFactor=1; 23my $playcounter; 24my $EQon; 25 26$::PlayPacks{Play_mplayer}=1; #register the package 27 28sub init 29{ undef %supported; 30 $mplayer= $::Options{mplayer_cmd}; 31 if ($mplayer && !-x $mplayer && !(::first { -x $_ } map $_.::SLASH.$mplayer, split /:/, $ENV{PATH})) 32 { $mplayer=undef; 33 } 34 $mplayer ||= ::first { -x $_ } map $_.::SLASH.'mplayer', split /:/, $ENV{PATH}; 35 36 return unless $mplayer; 37 return bless {RG=>1,EQ=>1},__PACKAGE__; #FIXME RG should be 0 if replaygain tags are disabled or if not $SoftVolume 38} 39 40sub supported_formats 41{ return () unless $mplayer; 42 unless (keys %supported) 43 {for (qx($mplayer -msglevel all=4 -ac help)) 44 { if (m/^(?:mad|ffmp3)\W.*working/){$supported{mp3}=undef} 45 elsif (m/^vorbis.*working/) {$supported{oga}=undef} 46 elsif (m/^musepack.*working/) {$supported{mpc}=undef} 47 elsif (m/^ffflac.*working/) {$supported{flac}=undef} 48 elsif (m/^ffwavpack.*working/){$supported{wv}=undef} 49 elsif (m/^ffape.*working/) {$supported{ape}=undef} 50 elsif (m/^faad.*working/) {$supported{m4a}=undef} 51 } 52 } 53 return keys %supported; 54} 55 56sub VolInit 57{ # check if support -volume option 58 $SoftVolume= !system($mplayer,qw/-really-quiet -softvol -volume 100/) unless defined $SoftVolume; 59 return undef if $SoftVolume; #use methods from this package 60 return Play_amixer::init(); #use methods from Play_amixer 61} 62 63sub launch_mplayer 64{ $playcounter=0; 65 #-nocache because when using the cache option it spawns a child process, which makes monitoring the mplayer process much harder 66 #-hr-mp3-seek to fix wrong time with some mp3s 67 @cmd_and_args=($mplayer,qw#-nocache -idle -slave -novideo -nolirc -hr-mp3-seek -msglevel all=1:statusline=5:global=6 --input=nodefault-bindings:conf=/dev/null#); 68 push @cmd_and_args, qw/-softvol -volume 0 --softvol-max=100/ if $SoftVolume; 69 push @cmd_and_args,split / /,$::Options{mplayeroptions} if $::Options{mplayeroptions}; 70 warn "@cmd_and_args\n" if $::debug; 71 pipe $OUTPUTfh,my$wfh; 72 pipe my($rfh),$CMDfh; 73 $ChildPID=fork; 74 if (!defined $ChildPID) { warn "gmusicbrowser_mplayer : fork failed : $!\n"; ::ErrorPlay("Fork failed : $!"); return } 75 elsif ($ChildPID==0) #child 76 { close $OUTPUTfh; close $CMDfh; 77 open my($olderr), ">&", \*STDERR; 78 open \*STDIN, '<&='.fileno $rfh; 79 open \*STDOUT,'>&='.fileno $wfh; 80 open \*STDERR,'>&='.fileno $wfh; 81 exec @cmd_and_args or print $olderr "launch failed (@cmd_and_args) : $!\n"; 82 POSIX::_exit(1); 83 } 84 close $wfh; close $rfh; 85 $CMDfh->autoflush(1); 86 $OUTPUTfh->blocking(0); #set non-blocking IO 87 $WatchTag= Glib::IO->add_watch(fileno($OUTPUTfh),'hup',\&_eos_cb); 88 $WatchTag2=Glib::IO->add_watch(fileno($OUTPUTfh),'in',\&_remotemsg); 89 #Glib::Timeout->add(500,\&_UpdateTime); 90 return 1; 91} 92 93sub Play 94{ (undef,$file,my$sec)=@_; 95 launch_mplayer() unless $ChildPID; 96 print $CMDfh "loadfile \"$file\"\n"; 97 $EQon= $::Options{use_equalizer} ? 1 : 0; 98 print $CMDfh "af_add equalizer=$::Options{equalizer}\n" if $EQon; 99 $playcounter++; 100 RG_set_options(); 101 SetVolume(undef,$::Volume) if $SoftVolume; 102 $sec = $sec ? $sec : 0; 103 SkipTo(undef,$sec); 104 warn "playing $file (pid=$ChildPID)\n" if $::Verbose; 105} 106 107sub handle_error 108{ my $error=shift; 109 ::ErrorPlay($error,_("Command used :")."\n@cmd_and_args"); 110 Stop(); 111} 112 113sub _eos_cb 114{ my $error; 115 _remotemsg();#parse last lines 116 #close $OUTPUTfh; 117 if ($ChildPID && $ChildPID==waitpid($ChildPID, WNOHANG)) 118 { $error=_"Check your audio settings" if $?; 119 } 120 while (waitpid(-1, WNOHANG)>0) {} #reap dead children 121 Glib::Source->remove($WatchTag); 122 Glib::Source->remove($WatchTag2); 123 $WatchTag=$WatchTag2=$ChildPID=undef; 124 handle_error($error) if $error; 125 return 1; 126} 127 128sub set_equalizer 129{ my (undef,$val)=@_; 130 return unless $ChildPID; 131 return if !$EQon && $val eq '0:0:0:0:0:0:0:0:0:0'; 132 # should be able to do either af_add or af_cmdline, but for some reason af_add doesn't 133 # set the equalizer, so do either af_add+af_cmdline or af_cmdline 134 print $CMDfh "af_add equalizer $val\n" unless $EQon; 135 print $CMDfh "af_cmdline equalizer $val\n"; 136 $EQon=1; 137} 138 139sub EQ_Get_Range 140{ return (-12,12,'dB'); 141} 142sub EQ_Get_Hz 143{ my $i=$_[1]; 144 # mplayer and GST equalizers use the same bands, but they are indicated differently 145 # mplayer docs list band center frequences, GST reports band start freqs. Using GST values here for consistency 146 my @bands=(qw/29Hz 59Hz 119Hz 237Hz 474Hz 947Hz 1.9kHz 3.8kHz 7.5kHz 15.0kHz/); 147 return $bands[$i]; 148} 149 150sub _remotemsg 151{ for (<$OUTPUTfh>) 152 { if ($playcounter==1 && m#^A:\s*(\d+)\.(\d) #) { ::UpdateTime( $1+($2>=5?1:0) ); next } 153 warn "mplayer:$_" if $::debug; 154 if ($playcounter>0 && m#^EOF code: \d#) 155 { ::end_of_file() unless --$playcounter; 156 } 157 if ( m#^(Could not open/initialize audio device)# || 158 m#^(Failed to open .+)# || 159 m#^(Failed to recognize file format)# 160 ) 161 { handle_error($1); 162 return 1; 163 } 164 } 165 return 1; 166} 167 168sub Pause 169{ print $CMDfh "pause\n"; 170} 171sub Resume 172{ print $CMDfh "pause\n"; 173} 174 175sub SkipTo 176{ my $sec=$_[1]; 177 ::setlocale(::LC_NUMERIC, 'C'); 178 print $CMDfh "pausing_keep seek $sec 2\n"; 179 ::setlocale(::LC_NUMERIC, ''); 180} 181 182 183sub Stop 184{ if ($WatchTag) 185 { Glib::Source->remove($WatchTag); 186 Glib::Source->remove($WatchTag2); 187 $WatchTag=$WatchTag2=undef; 188 } 189 if ($ChildPID) 190 { print $CMDfh "quit\n"; 191 #close $OUTPUTfh; 192 Glib::Timeout->add( 100,\&_Kill_timeout ) unless @pidToKill; 193 $Kill9=0; #_Kill_timeout will first try INT, then KILL 194 push @pidToKill,$ChildPID; 195 undef $ChildPID; 196 } 197} 198sub _Kill_timeout #make sure old children are dead 199{ while (waitpid(-1, WNOHANG)>0) {} #reap dead children 200 @pidToKill=grep kill(0,$_), @pidToKill; #checks to see which ones are still there 201 if (@pidToKill) 202 { warn "Sending ".($Kill9 ? 'KILL' : 'INT')." signal to @pidToKill\n" if $::debug; 203 if ($Kill9) {kill KILL=>@pidToKill;} 204 else {kill INT=>@pidToKill;} 205 $Kill9=1; #use KILL if they are still there next time 206 } 207 return @pidToKill; #removes the timeout if no more @pidToKill 208} 209 210sub AdvancedOptions 211{ my $vbox=Gtk2::VBox->new(::FALSE, 2); 212 my $sg1=Gtk2::SizeGroup->new('horizontal'); 213 my $opt=::NewPrefEntry('mplayeroptions',_"mplayer options :", sizeg1=>$sg1); 214 my $cmd=::NewPrefEntry('mplayer_cmd',_"mplayer executable :", cb=> \&init, tip=>_"Will use default if not found", sizeg1=>$sg1); 215 $vbox->pack_start($_,::FALSE,::FALSE,2), for $cmd,$opt; 216 VolInit() unless defined $SoftVolume; 217 $vbox->pack_start(Play_amixer::make_option_widget(),::FALSE,::FALSE,2) unless $SoftVolume; 218 return $vbox; 219} 220 221# Volume functions 222sub GetVolume {$::Volume} 223sub GetMute {$::Mute} 224sub SetVolume 225{ shift; 226 my $set=shift; 227 if ($set eq 'mute') { $::Mute=$::Volume; $::Volume=0; } 228 elsif ($set eq 'unmute') { $::Volume=$::Mute; $::Mute=0; } 229 elsif ($set=~m/^\+(\d+)$/) { $::Volume+=$1; } 230 elsif ($set=~m/^-(\d+)$/) { $::Volume-=$1; } 231 elsif ($set=~m/(\d+)/) { $::Volume =$1; } 232 $::Volume=0 if $::Volume<0; 233 $::Volume=100 if $::Volume>100; 234 my $vol= convertvolume($::Volume); #use a cubic volume scale and apply $GainFactor 235 print $CMDfh "volume $vol 1\n" if $ChildPID; 236 ::HasChanged('Vol'); 237 $::Options{Volume}=$::Volume; 238 $::Options{Volume_mute}=$::Mute; 239} 240 241sub convertvolume #convert a linear volume to cubic volume scale and apply $GainFactor 242{ my $vol=$_[0]; 243 $vol= 100*($vol/100)**3; 244 $vol*= $GainFactor; 245 # will be sent to mplayer as string, make sure it use a dot as decimal separator 246 ::setlocale(::LC_NUMERIC, 'C'); 247 $vol="$vol"; 248 ::setlocale(::LC_NUMERIC, ''); 249 return $vol; 250} 251 252sub RG_set_options 253{ return unless $SoftVolume; 254 if (defined($::PlayingID) && $::Options{use_replaygain}) 255 { my ($gain1,$gain2,$peak1,$peak2)= Songs::Get($::PlayingID, qw/replaygain_track_gain replaygain_album_gain replaygain_track_peak replaygain_album_peak/); 256 ($gain1,$gain2,$peak1,$peak2)= ($gain2,$gain1,$peak1,$peak2) if $::Options{rg_albummode}; 257 my $gain= ::first { $_ ne '' } $gain1, $gain2, $::Options{rg_fallback}; 258 $gain+= $::Options{rg_preamp}; 259 $GainFactor= 10**($gain/20); 260 my $peak= $peak1 || $peak2 || 1; 261 my $invpeak= 1/$peak; 262 warn "gain=$gain peak=$peak => scale factor=min($GainFactor,$invpeak)\n" if $::debug; 263 $GainFactor= $invpeak if $invpeak<$GainFactor; #clipping prevention, make it an option ? 264 } 265 else {$GainFactor=1} 266 return unless $ChildPID; 267 my $vol= convertvolume($::Volume); 268 print $CMDfh "volume $vol 1\n"; 269} 270 271#sub sendcmd {print $CMDfh "$_[0]\n";} #DEBUG #Play_mplayer::sendcmd('volume 0') 2721; 273