http://www.perlmonks.org?node_id=808999

Greetings fellow monks,

I have this idea about creating a command-line application that can play media (both short sound effects and long music clips), and then let the calling application control the playing media, in order to pause it, stop it, or make it play something else.

The idea is that such a script could be compiled via pp or PerlApp and distributed and used by (not only) Perl scripts to give them the ability to play music and sound effects, without needing to bog themselves down with all the dependencies they may need to implement audio themselves. It would also be convenient if they wanted to quickly add audio support to an already large (Perl/Tk) application, where threads may need to be introduced if they wanted to implement audio themselves.

And finally it would be a much lighter-weight alternative than requiring your users to install VLC or Xine or MPlayer in order to use the command-line front-ends to these large applications, just for your (probably command-line-based) application to have audio support.

It basically works by using unix named pipes (and will use Win32::Pipe on Windows). Each time you run the command you give it a user-defined unique ID; the first instance starts a server, and creates a named pipe in /tmp; later instances just echo commands into that pipe for the server to read and act upon.

So, command line usage is like so:

# start the daemon & play something ./playdaemon -i mytest play "../Music/Snow Patrol - Run.mp3" # command the daemon to pause the track ./playdaemon -i mytest pause # resume where it left off ./playdaemon -i mytest play # stop it altogether ./playdaemon -i mytest stop # restart it again (it remembers the last file loaded) ./playdaemon -i mytest play # play a different track ./playdaemon -i mytest play "/media/flash/Music/Coldplay - Viva La Vid +a.mp3" # we're done, kill the server ./playdaemon -i mytest exit

The code is currently a very early prototype; it only supports Linux and it uses the GStreamer library to play audio. It has no way of letting you know the current position in the track or to play a track on loop. I'm submitting it now because I may not get a lot of time to work on it and it may inspire others. So feel free to post any comments or questions about it. :)

Options:

--id, -i
Name your media player with a unique ID (default is "playdaemon")

--foreground, --fg, -f
Keep the (server) instance from forking into the background.

--debug
Print (early) debug information (things that happen before
the fork occurs; after the fork, all info is sent via
normal prints if --foreground is used).

--server, -s
Force the daemon to be the server. Even if the pipe file
already exists, it will use it and be the server.

--help
Get help (not done yet).

#!/usr/bin/perl -w # usage: playdaemon [--id uniqueid, opts] {play|pause|stop} <audiofile +> use strict; use warnings; use Getopt::Long; use Cwd "abs_path"; use threads; use threads::shared; # Media player to use? our $os = ''; if ($^O =~ /^MSWin/) { # Windows support not yet implemented! require Win32::MediaPlayer; require Win32::Pipe; $os = 'Windows'; die "Win32 support not yet implemented!"; } elsif ($^O =~ /linux/i) { require GStreamer; $os = 'Linux'; } else { die "Don't know what media library to use"; } # Collect command-line arguments. my %o = ( id => "playdaemon", # --id, unique ID to use debug => 0, # --debug, debug mode fg => 0, # --foreground, --fg, -f, don't fork to ba +ckground server => 0, # --server, -s force server mode help => 0, # --help, wanting help ); GetOptions ( 'id|i=s' => \$o{id}, 'debug' => \$o{debug}, 'foreground|fg|f' => \$o{fg}, 'server|s' => \$o{server}, 'help|h|?' => \$o{help}, ); # Help? if ($o{help}) { &help(); exit(0); } # Validate the commands. my ($cmd,$file) = (lc(shift(@ARGV)), shift(@ARGV)); if ($cmd !~ /^play|pause|stop|exit$/ || (defined $file && !-f $file)) +{ print "Usage: playdaemon [--id uniqueid, opts] {play|pause|stop|ex +it} [audiofile]\n" . "See playdaemon --help for help.\n"; exit(1); } if (defined $file && -f $file) { $file = fullpath($file); } # See if the named pipe exists. my $pipe = 'playdaemon-' . $o{id}; my $mode = ''; # server or client if ($os eq 'Linux') { # See if the pipe exists in /tmp. if (!-e "/tmp/$pipe") { # It doesn't; so we are the server. Create the pipe. &debug("Creating named pipe at /tmp/$pipe - we are the server. +"); system("mkfifo /tmp/$pipe"); $mode = 'server'; } else { # The pipe exists. We are the client. &debug("Found existing pipe at /tmp/$pipe - we are the client. +"); $mode = 'client'; } # Are we forcing server mode? if ($o{server}) { # We are the server even if the pipe was left behind from anot +her process. &debug("Forcing server mode."); $mode = 'server'; } } # Set up kill handlers. sub DESTROY () { if ($^O eq 'Linux') { if (-e "/tmp/$pipe") { unlink("/tmp/$pipe"); } } exit(0); } $SIG{HUP} = sub { DESTROY(); }; $SIG{INT} = sub { DESTROY(); }; END { if ($os eq 'Linux' && $mode eq 'server') { if (-e "/tmp/$pipe") { print "Unlink named pipe\n"; unlink("/tmp/$pipe"); } } } # Go to the background so we can return. unless ($o{fg}) { my $pid = fork(); if ($pid) { &debug("Forking to background as PID $pid"); exit(0); } close (STDOUT); close (STDERR); close (STDIN); } # Are we the server? if ($mode eq 'server') { # Spawn a thread that'll watch the pipe. my @frompipe : shared; my $thread = threads->create (sub { # Read from the pipe. while (1) { open (READ, "/tmp/$pipe"); my $line = <READ>; chomp $line; print "Read from pipe: $line\n"; push (@frompipe, $line); if ($line =~ /^shutdown/i) { close(READ); last; } } }); # Initialize the media player. my $play; if ($os eq 'Linux') { # Initialize GStreamer. GStreamer->init(); # Set it up. $play = GStreamer::ElementFactory->make("playbin", "play"); $play->set_state("null"); if ($cmd eq "play" && $file) { $play->set (uri => Glib::filename_to_uri ($file, "localhos +t")); $play->set_state("playing"); } } # Wait for inputs. while (1) { select(undef,undef,undef,0.1); if (scalar(@frompipe)) { my $next = shift(@frompipe); ($cmd,$file) = split(/\s+/, $next, 2); if (defined $file && length $file && !-f $file) { next; } elsif (defined $file && -f $file) { $file = fullpath($file); } if ($cmd eq "stop") { if ($os eq 'Linux') { print "Stopping playback.\n"; $play->set_state("null"); } } elsif ($cmd eq "pause") { if ($os eq 'Linux') { print "Pausing playback.\n"; $play->set_state("paused"); } } elsif ($cmd eq "play") { if ($os eq 'Linux') { # Did they give us a new file? if ($file) { # Yes; stop and load the new file. print "Loading new file: $file\n"; $play->set_state("null"); $play->set (uri => Glib::filename_to_uri ($fil +e, "localhost")); $play->set_state("playing"); } else { # No; just resume playing. print "Resuming play\n"; $play->set_state("playing"); } } } elsif ($cmd eq "exit") { print "Shutting down - unlinking /tmp/$pipe\n"; unlink("/tmp/$pipe") or warn "Can't delete? $! $@"; $thread->join(); exit(0); } } } } elsif ($mode eq 'client') { # Add the commands to the pipe. if (!-e "/tmp/$pipe") { die "Can't write to pipe: file not found!"; } open (PIPE, ">/tmp/$pipe"); print PIPE "$cmd $file"; close (PIPE); } # Turn a relative path into a full one. sub fullpath { return abs_path($_[0]); } # Print a debug line. sub debug { if ($o{debug}) { print $_, "\n"; } } sub help { print "Help TODO\n"; }

Things still left to do: