Using the AIR 2.0 Native Process API to control MPlayer
Adobe AIR has been a big step forward in creating cross-platform applications. Unfortunately, there are still some big gotchas. For us at cloud.tv our biggest issue are the lack of media playback options. Our AIR-based media center, cloudskipper, can play any media Flash supports, which has gotten better since Adobe added H.264 support in Flash 9.0.124, but still leaves out a lot (XVID, AAC audio supporrt, etc). This is particularly true with desktop media where there dozens of different codecs.
Luckily, a few weeks ago Adobe released the beta of AIR 2.0 and with that the NativeProcess API. The NativeProcess API essentially allows you to invoke any native process similar to how you would via the command line but in your AIR app. You then communicate with that process via STDIN and STDOUT. Jeff Swartz over at the Adobe Developer Connection wrote nice introduction to this, which I highly recommend.
Can we use this new capability to do something a little more interesting than “echotest”, like say play a video HD video encoded with XVID? I’m happy to say that with some caveats the answer is yes =)
Media Player Choice
The first thing we need to play media is well… a media player. Not just any media player, a media player with a few specific requirements:
- Plays many different media types (this probably means it has various media codecs packaged with it)
- Cross platform (mac, linux, windows)
- Can be invoked via the command line
- Can receive input and send out via STDIN/STDOUT
- Open source (distribution limitations and the inability to make modifications are show stoppers for commercial software)
From my research the only two media players I could find that meet both these requirements are VLC and MPlayer. Both MPlayer and VLC can be invoked via the command line. They also both have modes where they work with STDIN/STDOUT, “remote control” mode (use the argument -Irc) in VLC and “slave” mode (use the argument -slave) in MPlayer. You can find about VLC’s remote control mode here and MPlayer’s slave mode here.
VLC seems to actually be able to play a wider array of media, but the choice for me came down to what will work with AIR. Unfortunately, for some reason I just couldn’t get AIR to play nice with VLC in remote control mode. AIR seems to ignore the -Irc argument and just open up VLC. Maybe, I’m missing something with VLC, but I was able to get MPlayer to open up just fine in slave mode so that made my choice moving forward pretty easy.
MPlayer Installation
The easiest way to install MPlayer on OS X is via macports. Just open up the terminal and type the command “sudo port install mplayer-devel”. Now sit back make yourself a cup of coffee, maybe have lunch or go for a run around the block while macports compiles and installs mplayer and all of it’s dependencies. Note we’re using mplayer-devel, which I assume is the “latest” development release. This has some fixes to make MPlayer work on Snow Leopard and generally seems pretty stable (you can still install “mplayer” if you’re on Leopard or below). When MPlayer is done compiling/installing do a mplayer -v to make sure everything installed correctly. Then execute “which mplayer” and not the location (it will most likely be /opt/local/bin/mplayer). Note MPlayer’s location because we’re going to need this later.
Windows seems to be a bit tricker. MPlayer HQ seems to have both windows soure and binaries though so I’d recommend heading over there. I’d love to hear some first hand reports on this.
If your on linux I think app-get should work similiarly to macports, but I haven’t tested this.
AIR 2 SDK Installaton
You’re going to need to install the AIR 2 SDK to take advantage of the NativeProcess API. Luckily, I just posted a guide on how to do this =) You can check that out here: Installing the AIR 2 SDK on OS X (this is be pretty similiar for linux and windows).
The Code
Ok, finally, to the code! I’m not going to go through everything line by line here, but I will go over the key parts.
Update Application Descriptor
Extended desktop mode needs to be enabled for NativeProccess support.
Set the location of mplayer
protected const WIN_MPLAYER_PATH:String = 'c:\\path\\to\\mplayer';
(Sorry, didn’t add linux, but it should be fairly trivial)
Create the arguments
The three arguments we’re passing to MPlayer are:
- slave – switches MPlayer to use STDIN/STDOUT, making communication with AIR possible. You can find more information about slave mode here.
- quiet – makes output less verbose. this speeds things up a bit and make STDOUT parasing more reliable. Read more on MPlayer’s man page.
- idle – supposed to keep mplayer open when a file finishes playing, but doesn’t seem to work though.
Note, file here is the platform specific mplayer executable.
nativeProcessStartupInfo.executable = file;
var args:Vector.<String> = new Vector.<String>();
args.push('-slave');
args.push('-quiet');
args.push('-idle');
args.push(mediaFile.nativePath);
nativeProcessStartupInfo.arguments = args;
Send commands to STDIN
This is pretty straight forward. I wrote a little wrapper called “cmd” to handle error checking, but really all it does is write to the process’s standard input.
{
if(process && process.running){
process.standardInput.writeUTFBytes(cmdStr + "\n");
}
}
Receive commands from STDOUT and parse
MPlayer sends back information in a standard format. This means we just have to parse out certain strings to get the information we want. In this case we’re looking for the video’s position and length to update the scrubber.
protected function onOutputData(event:ProgressEvent):void
{
var output:String = process.standardOutput.readUTFBytes(process.standardOutput.bytesAvailable);
outputText += output;
textReceived.verticalScrollPosition = textReceived.maxVerticalScrollPosition;
var arr:Array;
if(output.indexOf('ANS_TIME_POSITION') > -1){
arr = output.split('ANS_TIME_POSITION=');
vidPosition = parseFloat(arr[1]);
}
if(output.indexOf('ANS_LENGTH') > -1){
arr = output.split('ANS_LENGTH=');
vidLength = parseFloat(arr[1]);
}
}
Scrubbing
Scrubbing is a little tricker because we have to worry about states (paused or playing). I also added the volume hack you see people because issuing all commands unpauses MPlayer and causes an audible pop.
{
if(!playing) {
// cmd('volume 0 1');
cmd('mute 1');
cmd('pause');
}
cmd('seek '+ (event.currentTarget as HSlider).value + ' 2');
cmd('get_time_pos');
if(!playing) {
cmd('pause');
}
}
Download source:
AIRmplayer.mxml and AIRmplayer-app.mxml
Playing Audio
From my tests MPlayer actually performs very well with audio. Not only does it seem to play mp3, m4a, ogg, flac, it also handles large files MUCH better than flash and even iTunes. There also aren’t the same windowing issues you have with video (except for m4a’s with visual content).
Issues and Final Thoughts
This is by no means a complete solution. There are several issues that need to be addressed before I would consider it viable in production environment:
- windowing – communicating between the MPlayer window and the AIR app is challenge if you want to integrate video playback
- separate app – as stands MPlayer opens up it’s dock icon (on os x) when it opens up a new movie window
- distribution – it will be tricky to compile mplayer with all dependencies localized in order to distribute it as native installer
Challenges aside, I think this still serves as an interesting demo of the possibilities of what we can now do with AIR 2 and the NativeProcess API.
Resources
http://blogs.adobe.com/cantrell/archives/2009/11/air_2_public_beta_resources.html
http://www.adobe.com/devnet/air/flex/quickstart/interacting_with_native_process_02.html