Maintain Pixel Aspect Ratio using NetStream in AS3
While recently working with the NetStream class, I was surprised to learn that there was no preset method in place for maintaining the pixel aspect ratio. The situation I faced was that I was building a video player that needed to host both 4:3 and 16:9 ratios. However if the Video object's dimensions are preset to that of a 16:9 ratio, and a video with a 4:3 ratio is loaded in, instead of the clip maintaining shape and centering within the video object, it instead flushes with the edges of the 16:9 dimensions.
Makes sense, right? In reality it's doing what I ask. It's displaying the video at the dimensions I specified. However, it's distorting the the video in the same process.
Due to lack of HD video clips lying around on my desktop, the above 16:3 example is being faked by cropping in on the original.
When working with both 4:3 and 16:9 ratios, the one common ground you can find between the two is the height. If you look at players like hulu and youtube, and the difference between the HD videos and the standard videos, both video ratios are displayed at the same height. The difference is(for obvious reasons) the 16:9 ratio fills the entire width, while in 4:3 the screen is letterboxed in. Since the NetStream flushes its dimensions to match that of the video object's dimensions, the simple solution here is to use the ratio of the video being played to modify the width property of the video object.
Since we already know the height, and we already know the ratio from the encoding process, the formula then looks like:
width = height * ( aspect ratio width / aspect ratio height )
Then we can center the video player using:
video.x = ( max width of player/2 ) - ( updated width of video/2 );
So with that being said, the initial class looks like:
package com { import flash.media.Video; import flash.net.NetConnection; import flash.net.NetStream; import flash.display.Sprite; public class videoPlayer extends Sprite { /// set properties private var _video:Video; private var _connection:NetConnection; private var _client:Object; private var _width:Number; private var _height:Number; // keep public so you can access if need be. public var stream:NetStream; // constructor public function videoPlayer ( args:Object ) { init( args ); } private function init( args:Object ):void { // if no target defined, throw error if ( args.target == undefined ) { try { throw new Error("\nYou must assign a value to the target property in the args object."); } catch ( errObject:Error ) { trace("Error in videoPlayer Class: " + errObject.message); } } // set defualts _width = ( args.width != undefined ) ? args.width : 320; _height = ( args.height != undefined ) ? args.height : 240; //create net connection var _connection:NetConnection = new NetConnection(); _connection.connect( null ); /// create netstream stream = new NetStream( _connection ); // add metaData object var _client:Object = new Object(); _client.onMetaData = onMetaData; stream.client = _client; // add video player _video = new Video( _width, _height ); args.target.addChild( _video ); } private function onMetaData( data:Object ):void { // do some meta stuff here. } } }
Now here's where we add our ratio equation. We'll create a playVideo method in which we'll add the video source as a string, and then pass the ratio along with it as an array. We can then use that method to swap videos and change the pixel aspect ratio based on those two parameters. Add the following method just below below the onMetaData method above:
public function playVideo( media:String, aspectratio:Array = null ):void { // play stream stream.play( media ); _video.attachNetStream( stream ); if ( aspectratio != null && aspectratio.length > 1 ) { _video.width = Math.ceil( _height * ( int( aspectratio[0] ) / int( aspectratio[1] ) ) ); _video.x = Math.round( ( _width/2 ) - ( _video.width/2 ) ); } }
Since the apsectratio parameter defaults to null, we can then ignore that parameter if we simply just want the video to flush with the dimensions passed in the args object on declaration. So now putting it all together, add your videoPlayer to the display list as follows:
import com.videoPlayer; // create video instance var videoContain:Sprite = new Sprite(); var myVid:videoPlayer = new videoPlayer( { target : videoContain, width : 432, height : 240 } ); addChild( videoContain );
Then to play a video call the playVideo method:
myVid.playVideo( "yourfile.flv", [4,3] );
With this method comes the responsibility of keeping track of the ratios of each video during the encoding process. With that data logged, you can then create a JSON, or XML file with the video names and its aspect ratio. If you want to pass the ratio in a Flash var within an embed script, you can convert the string to an array using the String.split() method:
import com.videoPlayer; var mediaFile:String = this.loaderInfo.parameters.mediaFile; // string of media file name passed in embed script var aspectratio:Array = this.loaderInfo.parameters.aspectratio; // if flash var aspectratio equals "3:4" // create video instance var videoContain:Sprite = new Sprite(); var myVid:videoPlayer = new videoPlayer( { target : videoContain, width : 432, height : 240 } ); addChild( videoContain ); myVid.playVideo( mediaFile, aspectratio.split(":") );
Again, I found this useful for creating a class I could use in different applications. If you already know the height and width, and are only going to use the class once or twice, then you could really just get away with hard-coding the dimensions rather than resizing based on the ratio.
No Comments »
No comments yet.
RSS feed for comments on this post. TrackBack URI
Leave a comment