{ # ---8<--- put this in file Sound.pm if you like ---8<--- package Sound; use warnings; use strict; use Carp; use IPC::Run qw/start/; use Scalar::Util qw/weaken/; our $DEFAULT_PLAYER = ['play','-q']; # SoX my @all_sounds; my %NEW_KNOWN_ARGS = map {$_=>1} qw/file player/; sub new { my $class = shift; my %self = (player => $DEFAULT_PLAYER); $self{file} = shift if @_%2; my %args = @_; $NEW_KNOWN_ARGS{$_} or croak "new: unknown arg '$_'" for keys %args; carp "new: file specified twice (or odd number of arguments)" if defined $self{file} && defined $args{file}; defined $args{$_} and $self{$_} = $args{$_} for keys %args; croak "new: player must be a nonempty arrayref" if ref $self{player} ne 'ARRAY' || @{$self{player}}<1; croak "new: no file specified" unless defined $self{file}; my $self = bless \%self, $class; push @all_sounds, $self; weaken $all_sounds[-1]; return $self; } sub stop_all_Sounds { # package function, stops *all* Sounds globally! defined and $_->stop for @all_sounds; } sub play { my $self = shift; $self->stop if $self->is_playing; $self->{harness} = start [ @{$self->{player}}, ref $self->{file} eq 'ARRAY' ? @{$self->{file}} : $self->{file} ]; return $self; } sub is_playing { my $h = shift->{harness}; return unless defined $h; return 1 if $h->pumpable; $h->finish; return; } sub wait { my $h = shift->{harness}; return unless defined $h; return $h->finish; } sub stop { my $h = shift->{harness}; return unless defined $h; $h->signal('INT'); return $h->finish; } sub DESTROY { shift->stop; # take this opportunity to clean up the array a bit @all_sounds = grep {defined} @all_sounds; # need to re-weaken as per Scalar::Util docs weaken $_ for @all_sounds; } 1; } # --->8--- end package Sound --->8--- BEGIN { $INC{'Sound.pm'}++ } # ignore; just for inlining the package # ----- ----- Demo ----- ----- use warnings; use strict; use Sound; # Note: my "short" sound is ~2.3s long my $snd_short = Sound->new('/tmp/short.mp3'); my $snd_long = Sound->new('/tmp/long.mp3'); local $SIG{INT} = sub { print "Caught SIGINT, stopping and exiting...\n"; Sound->stop_all_Sounds(); exit }; print "Playing long sound in background, and sleeping 10s...\n"; $snd_long->play; sleep 10; print "Playing short sound, blocking until done...\n"; $snd_short->play->wait; print "Sleeping 3s...\n"; sleep 3; print "Playing short sound in background, not sleeping...\n"; $snd_short->play; print "Re-starting playback of long sound...\n"; $snd_long->play; print "Waiting for playback of short sound to finish...\n"; $snd_short->wait; print "Sleeping 5s (long sound playback will continue)...\n"; sleep 5; # $snd_long will stop playing when the variable goes out of scope # (in this case, when the script ends) my $snd_two = Sound->new( # multiple files played one after another file => ['/tmp/short.mp3', '/tmp/short.mp3'], # player can be specified with absolute path, or no path # if the binary is in the PATH environment variable player => ['/usr/bin/mpg123','-q'] ); print "Playing second short sound in background...\n"; $snd_two->play; for (1..10) { # loop for roughly 10 seconds print "Monitoring playback (loop $_, sleeping)...\n"; sleep 1; if (not $snd_two->is_playing) { print "Playback ended, re-starting with long sound...\n"; $snd_two = Sound->new('/tmp/long.mp3')->play; } } print "Stopping second sound...\n"; $snd_two->stop; print "Script done.\n";