File Coverage

blib/lib/Audio/Play/MPG123.pm
Criterion Covered Total %
statement 22 133 16.5
branch 0 48 0.0
condition 0 9 0.0
subroutine 8 26 30.7
pod 11 17 64.7
total 41 233 17.6


line stmt bran cond sub pod time code
1             package Audio::Play::MPG123;
2              
3 1     1   1086 use strict 'subs';
  1         2  
  1         36  
4 1     1   6 use Carp;
  1         2  
  1         163  
5              
6             require Exporter;
7 1     1   6 use Fcntl;
  1         6  
  1         375  
8 1     1   1226 use IPC::Open3;
  1         7029  
  1         57  
9 1     1   9 use Cwd;
  1         2  
  1         67  
10 1     1   6 use File::Spec;
  1         2  
  1         24  
11 1     1   953 use Errno qw(EAGAIN EINTR);
  1         1431  
  1         126  
12              
13 1     1   2216 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 0     0 1   my $class = shift;
31 0           my $self = bless { @_ }, $class;
32 0 0         $self->start_mpg123(@{$self->{mpg123args} || []});
  0            
33 0           $self;
34             }
35              
36             sub start_mpg123 {
37 0     0 0   my $self = shift;
38 0           local *DEVNULL;
39 0 0         open DEVNULL, ">/dev/null" or die "/dev/null: $!";
40 0           $self->{r} = local *MPG123_READER;
41 0           $self->{w} = local *MPG123_WRITER;
42 0           $self->{pid} = open3($self->{w},$self->{r},">&DEVNULL",$MPG123,'-R','--aggressive',@_,'');
43 0 0         die "Unable to start $MPG123" unless $self->{pid};
44 0           fcntl $self->{r}, F_SETFL, O_NONBLOCK;
45 0           fcntl $self->{r}, F_SETFD, FD_CLOEXEC;
46 0 0         $self->parse(qr/^\@?R (\S+)/,1) or die "Error during player startup: $self->{err}\n";
47 0           $self->{version}=$1;
48             }
49              
50             sub stop_mpg123 {
51 0     0 0   my $self = shift;
52 0 0         if (delete $self->{pid}) {
53 0           print {$self->{w}} "Q\n";
  0            
54 0           close $self->{w};
55 0           close $self->{r};
56             }
57             }
58              
59             sub line {
60 0     0 0   my $self = shift;
61 0           my $wait = shift;
62 0           while() {
63 0 0         return $1 if $self->{buf} =~ s/^([^\n]*)\n+//;
64 0           my $len = sysread $self->{r},$self->{buf},4096,length($self->{buf});
65             # telescope the most frequent event, very useful for slow machines
66 0           $self->{buf} =~ s/^(?:\@F[^\n]*\n)+(?=\@F)//s;
67 0 0 0       if (defined $len || ($! != EAGAIN && $! != EINTR)) {
      0        
68 0 0         die "connection to mpg123 process lost: $!\n" if $len == 0;
69             } else {
70 0 0         if ($wait) {
71 0           my $v = ""; vec($v,fileno($self->{r}),1)=1;
  0            
72 0           select ($v, undef, undef, 60);
73             } else {
74 0           return ();
75             }
76             }
77             }
78             }
79              
80             sub parse {
81 0     0 0   my $self = shift;
82 0           my $re = shift;
83 0           my $wait = shift;
84 0           while (my $line = $self->line ($wait)) {
85 0 0         if ($line =~ /^\@F (.*)$/) {
    0          
    0          
    0          
    0          
    0          
    0          
86 0           $self->{frame}=[split /\s+/,$1];
87             # sno rno tim1 tim2
88             } elsif ($line =~ /^\@S (.*)$/) {
89 0           @{$self}{qw(type layer samplerate mode mode_extension
  0            
90             bpf channels copyrighted error_protected
91             emphasis bitrate extension lsf)}=split /\s+/,$1;
92 0 0         $self->{tpf} = ($self->{layer}>1 ? 1152 : 384) / $self->{samplerate};
93 0 0         $self->{tpf} *= 0.5 if $self->{lsf};
94 0           $self->{state} = 2;
95             } elsif ($line =~ /^\@I ID3:(.{30})(.{30})(.{30})(....)(.{30})(.*)$/) {
96 0           $self->{title}=$1; $self->{artist}=$2;
  0            
97 0           $self->{album}=$3; $self->{year}=$4;
  0            
98 0           $self->{comment}=$5; $self->{genre}=$6;
  0            
99 0           $self->{$_} =~ s/\s+$// for qw(title artist album year comment genre);
100             } elsif ($line =~ /^\@I (.*)$/) {
101 0           $self->{title}=$1;
102 0           delete @{$self}{qw(artist album year comment genre)}
  0            
103             } elsif ($line =~ /^\@P (\d+)(?: (\S+))?$/) {
104 0           $self->{state} = $1;
105             # 0 = stopped, 1 = paused, 2 = continued
106             } elsif ($line =~ /^\@E (.*)$/) {
107 0           $self->{err}=$1;
108 0           return ();
109             } elsif ($line !~ $re) {
110 0           $self->{err}="Unknown response: $line";
111 0           return ();
112             }
113 0 0         return $line if $line =~ $re;
114             }
115 0           delete $self->{err};
116 0           return ();
117             }
118              
119             sub poll {
120 0     0 1   my $self = shift;
121 0           my $wait = shift;
122 0 0         $self->parse(qr//,1) if $wait;
123 0           $self->parse(qr/^X\0/,0);
124             }
125              
126             sub canonicalize_url {
127 0     0 0   my $self = shift;
128 0           my $url = shift;
129 0 0         if ($url !~ m%^http://%) {
130 0           $url =~ s%^file://[^/]*/%%;
131 0 0         $url = fastcwd."/".$url unless $url =~ /^\//;
132             }
133 0           $url;
134             }
135              
136             sub load {
137 0     0 1   my $self = shift;
138 0           my $url = $self->canonicalize_url(shift);
139 0           $self->{url} = $url;
140 0 0 0       if ($url !~ /^http:/ && !-f $url) {
141 0           $self->{err} = "No such file or directory: $url";
142 0           return ();
143             }
144 0           print {$self->{w}} "LOAD $url\n";
  0            
145 0           delete @{$self}{qw(frame type layer samplerate mode mode_extension bpf lsf
  0            
146             channels copyrighted error_protected title artist album
147             year comment genre emphasis bitrate extension)};
148 0           $self->parse(qr{^\@[SP]\s},1);
149 0           return $self->{state};
150             }
151              
152             sub stat {
153 0     0 1   my $self = shift;
154 0 0         return unless $self->{state};
155 0           print {$self->{w}} "STAT\n";
  0            
156 0           $self->parse(qr{^\@F},1);
157             }
158              
159             sub pause {
160 0     0 1   my $self = shift;
161 0           print {$self->{w}} "PAUSE\n";
  0            
162 0           $self->parse(qr{^\@P},1);
163             }
164              
165             sub paused {
166 0     0 1   2 - $_[0]{state};
167             }
168              
169             sub jump {
170 0     0 1   my $self = shift;
171 0           print {$self->{w}} "JUMP $_[0]\n";
  0            
172             }
173              
174             sub statfreq {
175 0     0 1   my $self = shift;
176 0           print {$self->{w}} "STATFREQ $_[0]\n";
  0            
177             }
178              
179             sub stop {
180 0     0 1   my $self = shift;
181 0           print {$self->{w}} "STOP\n";
  0            
182 0           $self->parse(qr{^\@P},1);
183             }
184              
185             sub IN {
186 0     0 1   $_[0]->{r};
187             }
188              
189             sub tpf {
190 0     0 1   my $self = shift;
191 0           $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 0     0     *{$field} = sub { $_[0]{$field} };
199             }
200              
201 0     0 0   sub error { shift->{err} }
202              
203             1;
204             __END__