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