[AS3+ffmpeg] PLAY MULTI-VIDEO FORMATS IN AIR

May 5, 2011 at 4:54 AM (Actionscript) (, , , )

This is an attempt to make a AIR Video player which can play not only flv or mp4 but also avi, mov, mpeg or to put it simple what ever ffmpeg can decode(to flv)..
This will be achieved by using Native process api to run ffmpeg from adobe air and send the video file path to ffmpeg along with decoding settings and ask it to output as flv … the output from ffmpeg is received by air through standard output…and the received data is played in video object through netstream’s new method appendBytes

Download ffmpeg to try this demo…if you have downloded windows version download the archive, extract it, and look for ffmpeg.exe in bin folder….

First lets see how to run ffmpeg from air and pass the needed arguments to the ffmpeg…

First check if your platform supports NativeProcess

function checkForNPSupport():Boolean
		{
			if (NativeProcess.isSupported)
			{
				return true				
			}
			else
			{
				return false
			}
		}

If NativeProcess is supported then lets proceed with application or close the the app with alert or revert the application to be a normal flv/mp4 player…(though in this demo it justnt do anything if Native process is not supported)


var process:NativeProcess;//NativeProcess instance
var pInfo:NativeProcessStartupInfo;
var args:Vector.<String>;//Arguments to be passed to the process 
//
var stream:NetStream;
var connection:NetConnection;
//
var pFile:File;//File Object for ffmpeg
var vfile:File;//File Object for video file
var file:File = File.applicationDirectory;
//
function init_the_process():void
{

	pFile = file.resolvePath("ffmpeg.exe");//have placed ffmpeg in the application directory
	pInfo = new NativeProcessStartupInfo();
	pInfo.executable = pFile;
	process = new NativeProcess();
	process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,onErrorS);
	process.addEventListener(IOErrorEvent.STANDARD_INPUT_IO_ERROR,onError);
	process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR,onError);
	process.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR,onError);
	process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onOutputData);
	//process.addEventListener(ProgressEvent.STANDARD_INPUT_PROGRESS, inputProgressListener);
	start_netconnection()
}
function onError(evt:IOErrorEvent):void
{
//Any error in writing input/output pipes will trigger this function
	trace(evt.text);
}
function onErrorS(evt:ProgressEvent):void
{
	//Any error or information from ffmpeg during decoding is sent through STANDARD_ERROR_DATA
	// not sure why even video info is sent through STANDARD_ERROR_DATA 
	var n:Number = process.standardError.bytesAvailable;
	var s:String = process.standardError.readUTFBytes(n);
	//debug_info.appendText("INFO -"+s+"\n");
}
function inputProgressListener(evt:ProgressEvent):void
{
}
function onOutputData(evt:ProgressEvent):void
{
//The decoded flv data is received here
	if (process.running)
	{
		var videoStream : ByteArray = new ByteArray();
		process.standardOutput.readBytes(videoStream,0,process.standardOutput.bytesAvailable);
//Since the netstream is in data generation mode you can pass a ByteArray into the netStream instance
		stream.appendBytes(videoStream);
	}
}
function start_netConnection()
{	
	connection = new NetConnection();
	connection.addEventListener(NetStatusEvent.NET_STATUS, onNetstatusHandler);
	connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
	connection.connect(null);		
}
function onNetstatusHandler(evt:NetStatusEvent):void
{
	
	//debug_info.appendText("INFO -"+(evt.info.code)+"\n");
	switch (evt.info.code)
	{
		case "NetConnection.Connect.Success" :
			start_decode_process();
			break;
		case "NetStream.Play.StreamNotFound" :
			//Stream not found initiate necessary action
			break;
	}
}
function securityErrorHandler(event:SecurityErrorEvent):void
{
	trace(event.toString());
}
function start_decode_process():void
{
	stream = new NetStream(connection);
	stream.addEventListener(NetStatusEvent.NET_STATUS, onNetstatusHandler);
	stream.client = {onMetaData:metaDataHandler};
//video - video object placed in stage
	video.attachNetStream(stream);
//Now put the netstream instance to data generation mode by passing null while calling 'play'...
	stream.play(null);
	args = new Vector.<String>();
	//-i - video info; -sameeq quality of output same as input file; -f output format here 'flv';
	//"-" means the output needs to send through std out 
	//for quality settings and other info regarding arguments refer <a href="http://www.ffmpeg.org/ffmpeg.html">ffmpeg documents</a>
	args.push("-i",vfile.nativePath,"-sameq","-f","flv","-");
	pInfo.arguments = args;
	if (process.running)
	{
		process.closeInput();
		process.exit();
	}
	//all the info needed to to start the process(executable) is feeded into NativeProcessStartupInfo and sent as a argument while starting the NativeProcess
	process.start(pInfo);
}

Lets Now Select a video file and play it in video object added to stage

function browseVideo(evt:MouseEvent):void
{
	if (! vfile)
	{
		vfile = new File();
	}
	vfile.addEventListener(Event.SELECT, onSelectHandler);
	vfile.browseForOpen("Select Video File");
}
function onSelectHandler(event : Event):void
{
	if (process.running)
	{
		process.closeInput();
		process.exit();
	}
	init()
}

You can get all video info from metatag…now lets adjust the video objects dimension to fit our stage

var screen_width:Number=480
var screen_height:Number=360
function metaDataHandler(infoObject:Object):void {
	var w:Number = infoObject.width;
	var h:Number= infoObject.height;
	var whR:Number=h/w
	var hwR:Number=w/h
	var vw:Number
	var vh:Number
	if(w>=h){
	vw=Math.min(screen_width,w)
	vh=vw*whR
	}else{
		vh=Math.min(screen_height,h)
	vw=vh*hwR
	}
	video.width=vw
	video.height=vh
	video.x=(480-vw)/2
	video.y=(360-vh)/2
}

Now here is full source of this demo..this Document class if you are trying it out in flash create a video object, file browse button in stage or modify the class if ou want to create dynamically in script…

package 
{
	import flash.display.Sprite;
	import flash.events.*;
	import flash.utils.*;
	import flash.media.*;
	import flash.filesystem.File;
	import flash.desktop.NativeProcess;
	import flash.desktop.NativeProcessStartupInfo;
	import flash.net.*;
	public class Main extends Sprite
	{
		private var pFile:File;
		private var vfile:File;
		private var process:NativeProcess;
		private var pInfo:NativeProcessStartupInfo;
		private var args:Vector.<String > ;
		//
		//var video:Video;
		private var stream:NetStream;
		private var connection:NetConnection;
		private var file:File = File.applicationDirectory;
		public function Main()
		{
			// constructor code
			if(checkForNPSupport()){
			pFile = file.resolvePath("ffmpeg.exe");
			//open_btn button object created on stage
			open_btn.buttonMode = true;
			open_btn.addEventListener(MouseEvent.CLICK,browseVideo);
			}else{
				open_btn.enabled=false
				debug_info.appendText("Multi Video Format Player Not Supported!\n")
				//To Do - Show alert that the application not supported and close the application
			}
		}
		private function checkForNPSupport():Boolean
		{
			if (NativeProcess.isSupported)
			{
				return true				
			}
			else
			{
				return false
			}
		}
		private function init()
		{
			if (process && process.running)
			{
				process.closeInput();
				process.exit();
			}
			init_the_process();			
		}
		private function init_the_process():void
		{
			debug_info.appendText("PROCESS_INIT\n");
			pInfo = new NativeProcessStartupInfo();
			debug_info.appendText(pFile.nativePath+" file path\n");
			pInfo.executable = pFile;
			debug_info.appendText(pFile.nativePath+"\n");
			process = new NativeProcess();
			process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,onErrorS);
			process.addEventListener(IOErrorEvent.STANDARD_INPUT_IO_ERROR,onError);
			process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR,onError);
			process.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR,onError);
			process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onOutputData);
			//process.addEventListener(ProgressEvent.STANDARD_INPUT_PROGRESS, inputProgressListener);
			//debug_info.appendText("PROCESS_INIT_END\n")
			start_netConnection();
		}
		private function onError(evt:IOErrorEvent):void
		{
			trace(evt.text);
			//debug_info.text+=evt.text+"\n"
			debug_info.appendText(evt.type+":"+evt.text+"\n");
		}
		private function onErrorS(evt:ProgressEvent):void
		{
			//trace(evt.text)
			//debug_info.text+=evt.text+"\n"
			var n:Number = process.standardError.bytesAvailable;
			var s:String = process.standardError.readUTFBytes(n);
			//debug_info.appendText("INFO -"+s+"\n");
		}
		private function inputProgressListener(evt:ProgressEvent):void
		{
			//debug_info.appendText("inputProgressListener\n");
			//process.closeInput();
		}
		private function onOutputData(evt:ProgressEvent):void
		{
			if (process.running)
			{
				var videoStream : ByteArray = new ByteArray();
				process.standardOutput.readBytes(videoStream,0,process.standardOutput.bytesAvailable);
				stream.appendBytes(videoStream);
			}
		}
		private function start_netConnection()
		{


			connection = new NetConnection();
			connection.addEventListener(NetStatusEvent.NET_STATUS, onNetstatusHandler);
			connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
			connection.connect(null);

		}
		function onNetstatusHandler(event:NetStatusEvent):void
		{
			trace(event.toString());
			debug_info.appendText("INFO -"+(event.info.code)+"\n");
			switch (event.info.code)
			{
				case "NetConnection.Connect.Success" :
					start_decode_process();
					break;
				case "NetStream.Play.StreamNotFound" :
					trace("Stream not found: ");
					debug_info.appendText("Stream not found: \n");
					break;
			}
		}

		function securityErrorHandler(event:SecurityErrorEvent):void
		{
			trace(event.toString());
		}
		function start_decode_process():void
		{
			stream = new NetStream(connection);
			stream.addEventListener(NetStatusEvent.NET_STATUS, onNetstatusHandler);
			stream.client = {onMetaData:metaDataHandler};
			video.attachNetStream(stream);
			stream.play(null);
			args = new Vector.<String>();
			args.push("-i",vfile.nativePath,"-sameq","-f","flv","-");
			pInfo.arguments = args;
			debug_info.appendText(pInfo.arguments.length+"\n");
			if (process.running)
			{
				process.closeInput();
				process.exit();
			}
			process.start(pInfo);
		}
		function browseVideo(evt:MouseEvent):void
		{
			if (! vfile)
			{
				vfile = new File();
			}
			vfile.addEventListener(Event.SELECT, onSelectHandler);
			vfile.browseForOpen("Select Video File");
		}
		function onSelectHandler(event : Event):void
		{			
			init();
		}

		function metaDataHandler(infoObject:Object):void
		{
			var w:Number = infoObject.width;
			var h:Number = infoObject.height;
			var whR:Number = h / w;
			var hwR:Number = w / h;
			var vw:Number;
			var vh:Number;
			if (w >= h)
			{
				vw = Math.min(480,w);
				vh = vw * whR;
			}
			else
			{
				vh = Math.min(360,h);
				vw = vh * hwR;
			}
			video.width = vw;
			video.height = vh;
			video.x=(480-vw)/2;
			video.y=(360-vh)/2;
		}



	}

}

Permalink 20 Comments