| 1 |
package Audio::Play::MPG123; |
| 2 |
|
| 3 |
use strict 'subs'; |
| 4 |
use Carp; |
| 5 |
|
| 6 |
require Exporter; |
| 7 |
use Fcntl; |
| 8 |
use IPC::Open3; |
| 9 |
use Cwd; |
| 10 |
use File::Spec; |
| 11 |
use Errno qw(EAGAIN EINTR); |
| 12 |
|
| 13 |
BEGIN { $^W=0 } # I'm fed up with bogus and unnecessary warnings nobody can turn off. |
| 14 |
|
| 15 |
@ISA = qw(Exporter); |
| 16 |
|
| 17 |
@_consts = qw(); |
| 18 |
@_funcs = qw(); |
| 19 |
|
| 20 |
@EXPORT = @_consts; |
| 21 |
@EXPORT_OK = @_funcs; |
| 22 |
%EXPORT_TAGS = (all => [@_consts,@_funcs], constants => \@_consts); |
| 23 |
$VERSION = '0.63'; |
| 24 |
|
| 25 |
$MPG123 = "mpg123"; |
| 26 |
|
| 27 |
$OPT_AUTOSTAT = 1; |
| 28 |
|
| 29 |
sub new { |
| 30 |
my $class = shift; |
| 31 |
my $self = bless { @_ }, $class; |
| 32 |
$self->start_mpg123(@{$self->{mpg123args} || []}); |
| 33 |
$self; |
| 34 |
} |
| 35 |
|
| 36 |
sub start_mpg123 { |
| 37 |
my $self = shift; |
| 38 |
local *DEVNULL; |
| 39 |
open DEVNULL, ">/dev/null" or die "/dev/null: $!"; |
| 40 |
$self->{r} = local *MPG123_READER; |
| 41 |
$self->{w} = local *MPG123_WRITER; |
| 42 |
$self->{pid} = open3($self->{w},$self->{r},">&DEVNULL",$MPG123,'-R','--aggressive',@_,''); |
| 43 |
die "Unable to start $MPG123" unless $self->{pid}; |
| 44 |
fcntl $self->{r}, F_SETFL, O_NONBLOCK; |
| 45 |
fcntl $self->{r}, F_SETFD, FD_CLOEXEC; |
| 46 |
$self->parse(qr/^\@?R (\S+)/,1) or die "Error during player startup: $self->{err}\n"; |
| 47 |
$self->{version}=$1; |
| 48 |
} |
| 49 |
|
| 50 |
sub stop_mpg123 { |
| 51 |
my $self = shift; |
| 52 |
if (delete $self->{pid}) { |
| 53 |
print {$self->{w}} "Q\n"; |
| 54 |
close $self->{w}; |
| 55 |
close $self->{r}; |
| 56 |
} |
| 57 |
} |
| 58 |
|
| 59 |
sub line { |
| 60 |
my $self = shift; |
| 61 |
my $wait = shift; |
| 62 |
while() { |
| 63 |
return $1 if $self->{buf} =~ s/^([^\n]*)\n+//; |
| 64 |
my $len = sysread $self->{r},$self->{buf},4096,length($self->{buf}); |
| 65 |
# telescope the most frequent event, very useful for slow machines |
| 66 |
$self->{buf} =~ s/^(?:\@F[^\n]*\n)+(?=\@F)//s; |
| 67 |
if (defined $len || ($! != EAGAIN && $! != EINTR)) { |
| 68 |
die "connection to mpg123 process lost: $!\n" if $len == 0; |
| 69 |
} else { |
| 70 |
if ($wait) { |
| 71 |
my $v = ""; vec($v,fileno($self->{r}),1)=1; |
| 72 |
select ($v, undef, undef, 60); |
| 73 |
} else { |
| 74 |
return (); |
| 75 |
} |
| 76 |
} |
| 77 |
} |
| 78 |
} |
| 79 |
|
| 80 |
sub parse { |
| 81 |
my $self = shift; |
| 82 |
my $re = shift; |
| 83 |
my $wait = shift; |
| 84 |
while (my $line = $self->line ($wait)) { |
| 85 |
if ($line =~ /^\@F (.*)$/) { |
| 86 |
$self->{frame}=[split /\s+/,$1]; |
| 87 |
# sno rno tim1 tim2 |
| 88 |
} elsif ($line =~ /^\@S (.*)$/) { |
| 89 |
@{$self}{qw(type layer samplerate mode mode_extension |
| 90 |
bpf channels copyrighted error_protected |
| 91 |
emphasis bitrate extension lsf)}=split /\s+/,$1; |
| 92 |
$self->{tpf} = ($self->{layer}>1 ? 1152 : 384) / $self->{samplerate}; |
| 93 |
$self->{tpf} *= 0.5 if $self->{lsf}; |
| 94 |
$self->{state} = 2; |
| 95 |
} elsif ($line =~ /^\@I ID3:(.{30})(.{30})(.{30})(....)(.{30})(.*)$/) { |
| 96 |
$self->{title}=$1; $self->{artist}=$2; |
| 97 |
$self->{album}=$3; $self->{year}=$4; |
| 98 |
$self->{comment}=$5; $self->{genre}=$6; |
| 99 |
$self->{$_} =~ s/\s+$// for qw(title artist album year comment genre); |
| 100 |
} elsif ($line =~ /^\@I (.*)$/) { |
| 101 |
$self->{title}=$1; |
| 102 |
delete @{$self}{qw(artist album year comment genre)} |
| 103 |
} elsif ($line =~ /^\@P (\d+)(?: (\S+))?$/) { |
| 104 |
$self->{state} = $1; |
| 105 |
# 0 = stopped, 1 = paused, 2 = continued |
| 106 |
} elsif ($line =~ /^\@E (.*)$/) { |
| 107 |
$self->{err}=$1; |
| 108 |
return (); |
| 109 |
} elsif ($line !~ $re) { |
| 110 |
$self->{err}="Unknown response: $line"; |
| 111 |
return (); |
| 112 |
} |
| 113 |
return $line if $line =~ $re; |
| 114 |
} |
| 115 |
delete $self->{err}; |
| 116 |
return (); |
| 117 |
} |
| 118 |
|
| 119 |
sub poll { |
| 120 |
my $self = shift; |
| 121 |
my $wait = shift; |
| 122 |
$self->parse(qr//,1) if $wait; |
| 123 |
$self->parse(qr/^X\0/,0); |
| 124 |
} |
| 125 |
|
| 126 |
sub canonicalize_url { |
| 127 |
my $self = shift; |
| 128 |
my $url = shift; |
| 129 |
if ($url !~ m%^http://%) { |
| 130 |
$url =~ s%^file://[^/]*/%%; |
| 131 |
$url = fastcwd."/".$url unless $url =~ /^\//; |
| 132 |
} |
| 133 |
$url; |
| 134 |
} |
| 135 |
|
| 136 |
sub load { |
| 137 |
my $self = shift; |
| 138 |
my $url = $self->canonicalize_url(shift); |
| 139 |
$self->{url} = $url; |
| 140 |
if ($url !~ /^http:/ && !-f $url) { |
| 141 |
$self->{err} = "No such file or directory: $url"; |
| 142 |
return (); |
| 143 |
} |
| 144 |
print {$self->{w}} "LOAD $url\n"; |
| 145 |
delete @{$self}{qw(frame type layer samplerate mode mode_extension bpf lsf |
| 146 |
channels copyrighted error_protected title artist album |
| 147 |
year comment genre emphasis bitrate extension)}; |
| 148 |
$self->parse(qr{^\@[SP]\s},1); |
| 149 |
return $self->{state}; |
| 150 |
} |
| 151 |
|
| 152 |
sub stat { |
| 153 |
my $self = shift; |
| 154 |
return unless $self->{state}; |
| 155 |
print {$self->{w}} "STAT\n"; |
| 156 |
$self->parse(qr{^\@F},1); |
| 157 |
} |
| 158 |
|
| 159 |
sub pause { |
| 160 |
my $self = shift; |
| 161 |
print {$self->{w}} "PAUSE\n"; |
| 162 |
$self->parse(qr{^\@P},1); |
| 163 |
} |
| 164 |
|
| 165 |
sub paused { |
| 166 |
2 - $_[0]{state}; |
| 167 |
} |
| 168 |
|
| 169 |
sub jump { |
| 170 |
my $self = shift; |
| 171 |
print {$self->{w}} "JUMP $_[0]\n"; |
| 172 |
} |
| 173 |
|
| 174 |
sub statfreq { |
| 175 |
my $self = shift; |
| 176 |
print {$self->{w}} "STATFREQ $_[0]\n"; |
| 177 |
} |
| 178 |
|
| 179 |
sub stop { |
| 180 |
my $self = shift; |
| 181 |
print {$self->{w}} "STOP\n"; |
| 182 |
$self->parse(qr{^\@P},1); |
| 183 |
} |
| 184 |
|
| 185 |
sub IN { |
| 186 |
$_[0]->{r}; |
| 187 |
} |
| 188 |
|
| 189 |
sub tpf { |
| 190 |
my $self = shift; |
| 191 |
$self->{tpf}; |
| 192 |
} |
| 193 |
|
| 194 |
for my $field (qw(title artist album year comment genre state url |
| 195 |
type layer samplerate mode mode_extension bpf frame |
| 196 |
channels copyrighted error_protected title artist album |
| 197 |
year comment genre emphasis bitrate extension)) { |
| 198 |
*{$field} = sub { $_[0]{$field} }; |
| 199 |
} |
| 200 |
|
| 201 |
sub error { shift->{err} } |
| 202 |
|
| 203 |
1; |
| 204 |
__END__ |
| 205 |
|
| 206 |
=head1 NAME |
| 207 |
|
| 208 |
Audio::Play::MPG123 - a frontend to mpg123 version 0.59r and beyond. |
| 209 |
|
| 210 |
=head1 SYNOPSIS |
| 211 |
|
| 212 |
use Audio::Play::MPG123; |
| 213 |
|
| 214 |
$player = new Audio::Play::MPG123; |
| 215 |
$player->load("kult.mp3"); |
| 216 |
print $player->artist,"\n"; |
| 217 |
$player->poll(1) until $player->state == 0; |
| 218 |
|
| 219 |
$player->load("http://x.y.z/kult.mp3"); |
| 220 |
|
| 221 |
# see also mpg123sh from the tarball |
| 222 |
|
| 223 |
=head1 DESCRIPTION |
| 224 |
|
| 225 |
This is a frontend to the mpg123 player. It works by starting an external |
| 226 |
mpg123 process with the C<-R> option and feeding commands to it. |
| 227 |
|
| 228 |
While the standard mpg123 player can be used to play back mp3's using |
| 229 |
this module you will encounter random deadlocks, due to bugs in its |
| 230 |
communication code. Also, many features (like C<statfreq>) only work with |
| 231 |
the included copy of mpg123, so better use that one before deciding that |
| 232 |
this module is broken. |
| 233 |
|
| 234 |
(In case you wonder, the mpg123 author is not interested in including |
| 235 |
these fixes and enhancements into mpg123). |
| 236 |
|
| 237 |
=head2 METHODS |
| 238 |
|
| 239 |
Most methods can be either BLOCKING (they wait until they get an answer, |
| 240 |
which usually takes half a mpeg frame of playing time), NONBLOCKING (the |
| 241 |
functions return as soon as they send their message, which is usallly |
| 242 |
instant) or CACHING (the method returns some cached data which only gets |
| 243 |
refreshed by an asynchronous STAT event or an explicit call to C<state>). |
| 244 |
|
| 245 |
=over 4 |
| 246 |
|
| 247 |
=item new [parameter => value, ...] |
| 248 |
|
| 249 |
This creates a new player object and also starts the mpg123 process. New |
| 250 |
supports the following parameters: |
| 251 |
|
| 252 |
mpg123args an arrayreg with additional arguments for the mpg123 process |
| 253 |
|
| 254 |
=item load(<path or url>) [BLOCKING] |
| 255 |
|
| 256 |
Immediately loads the specified file (or url, http:// and file:/// forms |
| 257 |
supported) and starts playing it. If you really want to play a file with |
| 258 |
a name starting with C<file://> or C<http://> then consider prefixing all |
| 259 |
your paths with C<file:///>. Returns a true status when the song could be |
| 260 |
started, false otherwise. |
| 261 |
|
| 262 |
=item stat [BLOCKING] |
| 263 |
|
| 264 |
This can be used to poll the player for it's current state (playing mode, |
| 265 |
frame position &c). As every other function that requires communication |
| 266 |
with mpg123, it might take up to one frame delay until the answer returns. |
| 267 |
Using C<statfreq> and infrequent calls to C<poll> is often a better |
| 268 |
strategy. |
| 269 |
|
| 270 |
=item pause [BLOCKING] |
| 271 |
|
| 272 |
Pauses or unpauses the song. C<state> (or C<paused>) can be used to find |
| 273 |
out about the current mode. |
| 274 |
|
| 275 |
=item paused [CACHING] |
| 276 |
|
| 277 |
Returns the opposite of C<state>, i.e. zero when something is playing |
| 278 |
and non-zero when the player is stopped or paused. |
| 279 |
|
| 280 |
=item jump [BLOCKING] |
| 281 |
|
| 282 |
Jumps to the specified frame of the song. If the number is prefixed with |
| 283 |
"+" or "-", the jump is relative, otherweise it is absolute. |
| 284 |
|
| 285 |
=item stop [BLOCKING] |
| 286 |
|
| 287 |
Stops the currently playing song and unloads it. |
| 288 |
|
| 289 |
=item statfreq(rate) [NONBLOCKING] |
| 290 |
|
| 291 |
Sets the rate at which automatic frame updates are sent by mpg123. C<0> |
| 292 |
turns it off, everything else is the average number of frames between |
| 293 |
updates. This can be a floating pount value, i.e. |
| 294 |
|
| 295 |
$player->statfreq(0.5/$player->tpf); |
| 296 |
|
| 297 |
will set two updates per second (one every half a second). |
| 298 |
|
| 299 |
=item state [CACHING] |
| 300 |
|
| 301 |
Returns the current state of the player: |
| 302 |
|
| 303 |
0 stopped, not playing anything |
| 304 |
1 paused, song loaded but not playing |
| 305 |
2 playing, song loaded and playing |
| 306 |
|
| 307 |
=item poll(<wait>) [BLOCKING or NONBLOCKING] |
| 308 |
|
| 309 |
Parses all outstanding events and status information. If C<wait> is zero |
| 310 |
it will only parse as many messages as are currently in the queue, if it |
| 311 |
is one it will wait until at least one event occured. |
| 312 |
|
| 313 |
This can be used to wait for the end of a song, for example. This function |
| 314 |
should be called regularly, since mpg123 will stop playing when it can't |
| 315 |
write out events because the perl program is no longer listening... |
| 316 |
|
| 317 |
=item title artist album year comment genre url type layer samplerate mode mode_extension bpf frame channels copyrighted error_protected title artist album year comment genre emphasis bitrate extension [CACHING] |
| 318 |
|
| 319 |
These accessor functions return information about the loaded |
| 320 |
song. Information about the C<artist>, C<album>, C<year>, C<comment> or |
| 321 |
C<genre> might not be available and will be returned as C<undef>. |
| 322 |
|
| 323 |
The accessor function C<frame> returns a reference to an array containing |
| 324 |
the frames played, frames left, seconds played, and seconds left in this |
| 325 |
order. Seconds are returned as floating point numbers. |
| 326 |
|
| 327 |
=item tpf [CACHING] |
| 328 |
|
| 329 |
Returns the "time per frame", i.e. the time in seconds for one frame. Useful with the C<jump>-method: |
| 330 |
|
| 331 |
$player->jump (60/$player->tpf); |
| 332 |
|
| 333 |
Jumps to second 60. |
| 334 |
|
| 335 |
=item IN |
| 336 |
|
| 337 |
Returns the input filehandle from the mpg123 player. This can be used for selects() or poll(). |
| 338 |
|
| 339 |
=back |
| 340 |
|
| 341 |
=head1 AUTHOR |
| 342 |
|
| 343 |
Marc Lehmann <schmorp@schmorp.de>. |
| 344 |
|
| 345 |
=head1 SEE ALSO |
| 346 |
|
| 347 |
perl(1). |
| 348 |
|
| 349 |
=cut |