Exclusive mode seems to work without deadlocks
[mirrors/Programs.git] / perl / mpmix / MPlayer.pm
... / ...
CommitLineData
1package Audio::Play::MPlayer;
2
3use strict;
4use warnings;
5#use base qw(Class::Accessor::Fast);
6use base qw(Object::Accessor);
7
8=head1 NAME
9
10Audio::Play::MPlayer - a frontend to play audio files using MPlayer
11
12=head1 SYNOPSIS
13
14 use Audio::Play::MPlayer;
15
16 # same as Audio::Play::MPG123
17 $player = Audio::Play::MPlayer->new;
18
19 # Set speed to _2.5x_
20 $player->speed(+1.5);
21
22 $player->load( "ex-mp30.mp3" );
23 print $player->title, "\n";
24 $player->poll( 1 ) until $player->state == 0;
25
26=head1 DESCRIPTION
27
28This module acts as a frontend to an external MPlayer process started
29with the C<-slave> command-line option. The idea and interface (and
30in part the code) has been taken from L<Audio::Play::MPG123>.
31
32Please see L<Audio::Play::MPG123> for the documentation. Take into account
33that the methods:
34
35 copyrighted
36 emphasis
37 error_protected
38 extension
39 layer
40 mode
41 mode_extension
42 stat
43 statfreq
44 type
45 url
46 IN
47
48have not been implemented, and that:
49
50 jump
51 tpf
52
53work differently: C<jump> takes offsets in seconds, and C<tpf> always
54returns C<1> to make it possible to write:
55
56 $player->jump( 60 / $player->tpf );
57
58=cut
59
60use IPC::Open3 qw(open3);
61use IO::Handle;
62
63our $VERSION = '0.03';
64
65# FIXME, missing
66# url type layer mode mode_extension copyrighted error_protected
67# emphasis extension
68#__PACKAGE__->mk_accessors( qw(frame title artist album year comment genre samplerate channels bitrate extension) );
69#Object::Accessor->new( qw(frame title artist album year comment genre samplerate channels bitrate extension) );
70
71#my $bool = $obj->mk_accessors( qw(frame title artist album year comment genre samplerate channels bitrate extension) );
72
73sub new {
74 my( $class, %args ) = @_;
75 my $self = bless \%args, $class;
76
77 $self->start_mplayer( $args{mplayerargs} );
78 $self->{state} = 0;
79
80 $self->mk_accessors( qw(frame title artist album year comment genre samplerate channels bitrate extension) );
81 return $self;
82}
83
84sub DESTROY {
85 my( $self ) = @_;
86
87 $self->stop_mplayer;
88}
89
90sub start_mplayer {
91 my( $self, $args ) = @_;
92 my( $wr, $rd );
93
94 my $pid = open3( $wr, $rd, $rd,
95 'mplayer', '-slave', '-idle', @{$args || []} );
96
97 die "Can't start mplayer" unless $pid;
98
99 $self->{pid} = $pid;
100 $self->{r} = $rd;
101 $self->{w} = $wr;
102 $self->{r}->blocking( 0 );
103 $self->{frame} = [ undef, undef, undef, undef ];
104 $self->{buffer} = '';
105}
106
107sub stop_mplayer {
108 my( $self ) = @_;
109
110 return unless $self->{pid};
111 $self->command( 'quit' );
112 my $pid = delete $self->{pid};
113 close delete $self->{r};
114 close delete $self->{w};
115 waitpid $pid, 0;
116}
117
118sub line {
119 my( $self, $wait ) = @_;
120
121 for(;;) {
122 # append to buffer
123 my $len = sysread $self->{r}, $self->{buffer}, 2048,
124 length( $self->{buffer} );
125 return $1 if $self->{buffer} =~ s/^([^\n\r]*)[\r\n]+//;
126 if( $wait ) {
127 vec( my $rbits = '', fileno( $self->{r} ), 1 ) = 1;
128 select $rbits, undef, undef, 60;
129 } else {
130 return;
131 }
132 }
133}
134
135my %info =
136 ( meta_title => [ 'get_meta_title', 'title', ],
137 meta_artist => [ 'get_meta_artist', 'artist', ],
138 meta_album => [ 'get_meta_album', 'album', ],
139 meta_year => [ 'get_meta_year', 'year', ],
140 meta_comment => [ 'get_meta_comment', 'comment', ],
141 meta_genre => [ 'get_meta_genre', 'genre', ],
142 );
143
144# AUDIO: 44100 Hz, 2 ch, s16le, 128.0 kbit/9.07% (ratio: 16000->176400)
145# A: 16.5 (16.4) of 252.3 (04:12.3) 3.6%
146# ===== PAUSE =====
147sub parse {
148 my( $self, $re, $wait ) = @_;
149
150 while( my $line = $self->line( $wait ) ) {
151 if( $line =~ /^A:\s+([\d\.]+)\s+\([\d\:\.]+\)\s+of\s+([\d\.]+)/ ) {
152 $self->{frame}->[2] = $1;
153 $self->{frame}->[3] = $2 - $1;
154 # FIXME heuristic
155 $self->{state} = 0 if $self->{frame}->[3] <= 0;
156 } elsif( $line =~ /=====\s+PAUSE\s+=====/ ) {
157 $self->{state} = 1;
158 } elsif( $line =~ /^ANS_(\w+)='([^']+)'$/ ) {
159 # FIXME quoting
160 my( $k, $v ) = ( lc( $1 ), $2 );
161
162 if( $info{$k} ) {
163 $self->{$info{$k}->[1]} = $v;
164 }
165 } elsif( $line =~ /^AUDIO:\s+(\d+)/ ) {
166 $self->{samplerate} = $1;
167 if( $line =~ /(\d+)\s+ch/i ) {
168 $self->{channels} = $1;
169 }
170 if( $line =~ /(\d+)\.\d+\s+kbit/i ) {
171 $self->{bitrate} = $1;
172 }
173 } elsif( $line =~ /^Playing\s/ ) {
174 $self->{$_->[1]} = undef foreach values %info;
175 $self->command( $_->[0] ) foreach values %info;
176 } elsif( $line =~ /^\s+(title|artist|album|year|comment|genre):\s(.*?)\s*$/i ) {
177 # FIXME heuristic
178 $self->{lc($1)} = $2;
179 } else {
180 # print STDERR $line, "\n";
181 }
182 return $line if $line =~ $re;
183 }
184
185 return;
186}
187
188sub poll {
189 my( $self, $wait ) = @_;
190
191 $self->parse( qr/./, $wait ); # wait for anything
192 $self->parse( qr/^\0/, 0 ); # consume pending output
193}
194
195sub command {
196 my( $self, $command ) = @_;
197
198 print { $self->{w} } $command, "\n";
199}
200
201sub load {
202 my( $self, $file ) = @_;
203
204 # FIXME quoting
205 $self->command( qq{loadfile "$file"} );
206 $self->{state} = 2; # feeling lucky
207}
208
209sub state {
210 my( $self ) = @_;
211
212 return $self->{state};
213}
214
215# FIXME works more-or-less
216sub stop {
217 my( $self ) = @_;
218
219 return if $self->{state} == 0;
220 $self->pause if $self->{state} == 2;
221 $self->command( 'pausing_keep seek 0.0 2' );
222 $self->poll;
223 $self->{state} = 0;
224}
225
226sub pause {
227 my( $self ) = @_;
228
229 return if $self->{state} == 0;
230 $self->command( "pause" );
231 if( $self->{state} == 2 ) {
232 $self->parse( qr/=====\s+PAUSE\s+=====/, 1 );
233 # try to parse metatdata command answers
234 $self->poll;
235 } elsif( $self->{state} == 1 ) {
236 $self->{state} = 2;
237 }
238}
239
240sub paused { $_[0]->{state} == 2 ? 0 : 1 }
241
242# FIXME not like Audio::Play::MPG123
243sub jump {
244 my( $self, $seconds ) = @_;
245
246 if( $seconds && $seconds =~ /^[+\-]/ ) {
247 $self->command( "seek $seconds 0" );
248 } else {
249 $self->command( "seek $seconds 2" );
250 }
251}
252
253# Sets the player speed to current speed + delta
254sub speed {
255 my( $self, $delta ) = @_;
256
257 return if $self->{state} == 0;
258 return if not defined $delta or $delta == 0;
259
260 $self->command( sprintf( "speed %.2f", $delta ) );
261 return;
262}
263
264# mock Audio::Play::MPG123
265sub tpf { 1 }
266
267=head1 AUTHOR
268
269Mattia Barbon <mbarbon@cpan.org>, using ideas from
270L<Audio::Play::MPG123> by Marc Lehmann <schmorp@schmorp.de>.
271
272=head1 LICENSE
273
274This program is free software; you can redistribute it and/or
275modify it under the same terms as Perl itself.
276
277=head1 SOURCES
278
279The latest sources can be found on GitHub at
280L<http://github.com/mbarbon/audio-play-mplayer/tree>
281
282=head1 THANKS
283
284Marc Lehmann <schmorp@schmorp.de> for L<Audio::Play::MPG123>.
285
286The MP3 file used in tests has been taken from
287L<http://www.shellworld.net/~davidsky/exam-mp3.htm> (test ID3 tag not
288in original).
289
290=cut
291
2921;
This page took 0.650769 seconds and 4 git commands to generate.