Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Prototype - Command line, controllable, media player

by Kirsle (Pilgrim)
on Nov 24, 2009 at 05:12 UTC ( [id://808999]=CUFP: print w/replies, xml ) Need Help??

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:

  • If you kill it via `kill` or `killall playdaemon` it doesn't seem to trigger any of my various exit handlers, so the pipe file may be left behind. You can use the --server option to force it to be a server on next run, so if the pipe is left behind it will use it anyway.
  • It can't show the current position in the track or play an audio file on loop.
  • It doesn't work on Win32, yet - but I'll use Win32::MediaPlayer for that part.

Replies are listed 'Best First'.
Re: Prototype - Command line, controllable, media player
by hossman (Prior) on Nov 24, 2009 at 06:27 UTC

    I've known various people who have implemented various systems like this. My main suggestion would be that instead of using named pipes, attempt to listen to a port. if you can, start the daemon; if you can't that means the daemon is already running and you should communicate with it (via that port).

    This not only provides an easy way to deal with the "it died horribly and left the pipe file around" type problems (because if the process dies it won't be bound to the port anymore) but also allows you to do stuff like ... make HTTP your communication protocol. that way any HTTP client library can be used to talk to your server.

      Good suggestion. I considered that initially (it would greatly improve cross-platform support since TCP sockets are pretty much universal, unlike unix-vs-win32 named pipes), but wouldn't there be the overhead (and therefore a delay) of opening a TCP socket each time the media player is commanded to do something else?

        Yep. Negligible.
Re: Prototype - Command line, controllable, media player
by ambrus (Abbot) on Nov 24, 2009 at 11:32 UTC
Re: Prototype - Command line, controllable, media player
by holli (Abbot) on Nov 26, 2009 at 16:07 UTC
    A command lined based program playing audio? Seriously, who needs this?


    holli

    You can lead your users to water, but alas, you cannot drown them.

      As an example...

      I was working on a Perl/Tk chat client for a chat server that uses a proprietary protocol. After it got several version numbers mature, I wanted to add some simple sound effects to it, such as playing a sound that was appropriate for the chat server when users entered or exited the room.

      On Windows I could've used Win32::Sound or Win32::MediaPlayer - I ended up using the latter. But this program has historically been very cross-platform and if I were to add sound effects to the Windows client, I needed to also have sound effects for other operating systems.

      I ended up just using the `play` command (from SOX on Linux) as a default for Linux, and `afplay` as a default for Mac OS X. On Win32 it would automatically use Win32::MediaPlayer, but on all other platforms a command-line program was configurable (if the user wanted to use `mplayer` instead of `play`, they could customize that).

      Now I know about GStreamer for Linux, but there's still no relatively easy module for OS X for playing sound. So this is just one example of why a command-line audio player is useful.

      The downside to `play/afplay` is that you can't control it once you start it, so it's only suitable for short sound effects. So, a command-line media player that can be commanded and controlled once it begins would be good for similar situations as this, except when longer music clips are desired and not just short sound effects.

      A command-line media player is also convenient sometimes from a resources standpoint. In my personal experience, I've found command-line tools like a CLI media player, for instance, very good from a memory/CPU usage standpoint. They tend to use a lot less of it. :-) And I like the basic Unix philosophy of 'small, specialized tools suited to do one task, and to do it well.'

      Whether I would use a media player coded in Perl however, that I don't know.
      (Don't get me wrong, I love Perl.)

      Still, an interesting use of Perl nonetheless. :-) It really is the Swiss army knife of programming languages.

      Reach out and grep someone.

      I do. Of my two current computers (both laptops) one is has a burned out video card and one is an eeePC 701 that fell into a river.

      Everyone knows that if you can't do it from a command line then there's no point in doing it at all.

      $,=qq.\n.;print q.\/\/____\/.,q./\ \ / / \\.,q.    /_/__.,q..

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://808999]
Approved by ww
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (5)
As of 2024-03-28 23:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found