topical media & game development
mashup-rmx-10-VideoBitmapDisplay.ax
mashup-rmx-10-VideoBitmapDisplay.ax
(swf
)
[ flash
]
flex
package com.almerblank.flex.components.video
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.AsyncErrorEvent;
import flash.events.Event;
import flash.events.NetStatusEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;
import flash.geom.Rectangle;
import flash.media.SoundTransform;
import flash.media.Video;
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.net.ObjectEncoding;
import flash.utils.Timer;
import mx.containers.Canvas;
import mx.controls.Image;
import mx.events.FlexEvent;
import mx.events.VideoEvent;
import mx.utils.ObjectUtil;
The VideoBitmapDisplay class works almost identically to the VideoDisplay class to
display video. It provides extra functionality and extendability that the VideoDisplay
class does not.
author: Omar Gonzalez
[Bindable]
public class VideoBitmapDisplay extends Canvas
{
// VideoBitmapDisplay properties
private var _videoHeight:int = 240;
private var _videoWidth:int = 320;
private var _playing:Boolean = false;
private var _maintainAspectRatio:Boolean = false;
private var _scaleContent:Boolean = true;
private var _smoothing:Boolean = true;
private var _volume:Number = .75;
private var _playheadUpdateInterval:int = 50;
private var _source:String = null;
private var _autoPlay:Boolean = true;
private var _autoRewind:Boolean = true;
private var _autoBandwidthDetection:Boolean = false;
private var _bufferTime:Number = 3;
private var _idleTimeout:Number = 300000;
private var _cuePointManagerClass:Class = null;
private var _cuePoints:Array = null;
private var _live:Boolean = false;
private var _playheadTime:Number;
private var _progressInterval:int = 250;
private var _totalTime:Number;
private var _backgroundColor:Number = 0x000000;
private var _clearEndScreen:Boolean = true;
private var _playheadManager:Timer;
private var _progressManager:Timer;
private var _bytesLoaded:Number;
private var _bytesTotal:Number;
private var _bufferEmpty:Boolean = true;
private var _downloadComplete:Boolean = false;
private var _sourceChanged:Boolean = false;
// video objects
private var _netConn:NetConnection = null;
private var _connected:Boolean = false;
private var _videoStream:NetStream = null;
private var _video:Video;
private var _sound:SoundTransform;
private var _refreshManager:Timer;
private var _streamName:String;
// display objects
private var _display:Image;
public var bitmapData:BitmapData;
private var _videoBitmap:Bitmap;
Class constructor.
public function VideoBitmapDisplay()
{
super();
super.addEventListener ( FlexEvent.CREATION_COMPLETE, init );
}
Retrieves the totalTime from the metadata in the video
when it is received.
parameter: info
public function onMetaData(info:Object):void
{
//trace("metadata: duration=" + info.duration + " width=" + info.width + " height=" + info.height + " framerate=" + info.framerate);
totalTime = Number ( info.duration );
}
Handles cuePoint info from the video.
parameter: info
public function onCuePoint(info:Object):void
{
trace("cuepoint: time=" + info.time + " name=" + info.name + " type=" + info.type);
}
Handles AsyncErrorEvent to prevent rte.
parameter: e
private function asyncErrorHandler(e:AsyncErrorEvent):void
{
return;
}
The <code>connStatus()</code> method handles status
events dispatched by the NetConnection object.
parameter: event
private function connStatus (event:NetStatusEvent):void
{
switch ( event.info.code )
{
case 'NetConnection.Connect.Success' :
_connected = true;
if (!live )
{
openStream();
}
else if ( live )
{
subscribe(); // this custom method is used to subscribe to the FMS application.
}
break;
case 'NetConnection.Connect.Failed' :
case 'NetConnection.Connect.Rejected' :
openConnection();
break;
case 'NetConnection.Connect.Closed' :
_connected = false;
default : break ;
}
}
This method is used to subscribe to a specific CDN's FMS server.
This could/should change for your specific implementations of
live video. The <code>onFCSSubscribe()</code> method is also
an expected callback of this CDN to complete the connection.
private function subscribe():void
{
//var res:Responder = new Responder( onFCSubscribe );
_netConn.call( "FCSubscribe", null, _streamName );
}
Callback method called by the server on the client of the
NetConnection object. This method is specific to the CDN
this class was implemented for. You could change this for
your specific implementation.
parameter: info
public function onFCSubscribe ( info:Object ):void
{
openStream();
}
private function securityErrorHandler(event:SecurityErrorEvent):void
{
trace("securityErrorHandler: " + event);
}
This method opens the NetConnection object, has
some specific logic for connection to FMS2, which
uses AMF0 to communicate.
private function openConnection():void
{
_netConn = new NetConnection();
_netConn.addEventListener( NetStatusEvent.NET_STATUS, connStatus );
_netConn.addEventListener( SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler );
if ( !live )
{
_netConn.connect( null );
}
else if ( live )
{
_netConn.client = this;
_netConn.objectEncoding = ObjectEncoding.AMF0; // used to connect to FMS2
_netConn.connect( source );
}
}
This method opens the NetStream object, and
sets up the sound and video display. It also
autoplays the video, if autoPlay is true.
private function openStream():void
{
_videoStream = new NetStream( _netConn );
_videoStream.client = this;
_videoStream.bufferTime = _bufferTime;
_videoStream.addEventListener( NetStatusEvent.NET_STATUS, streamStatus );
_videoStream.addEventListener( AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler );
_video = new Video( _videoWidth, _videoHeight );
_video.smoothing = _smoothing;
_video.width = _videoWidth;
_video.height = _videoHeight;
_video.attachNetStream( _videoStream );
updateSound();
createVideoDisplay();
// dispatch video ready event.
var e:VideoEvent = new VideoEvent ( VideoEvent.READY, true );
dispatchEvent( e );
if ( _autoPlay )
{
play();
}
}
Updates the sound on the video stream.
private function updateSound():void
{
var s:SoundTransform = new SoundTransform();
s.volume = _volume;
_videoStream.soundTransform = s;
}
Creates the video display, using an Image object.
private function createVideoDisplay():void
{
_display = new Image();
_display.width = _videoWidth;
_display.height = _videoHeight;
_display.setStyle('backgroundColor', 0x000000);
_display.setStyle('backgroundAlpha', 1);
// Create the BitmapData
bitmapData = new BitmapData( _videoWidth, _videoHeight, false, 0x000000 );
// Create the Bitmap display object
_videoBitmap = new Bitmap( bitmapData );
_videoBitmap.width = _videoWidth;
_videoBitmap.height = _videoHeight;
// attach bitmap to image display
_display.source = _videoBitmap;
// add to stage
addChild ( _display );
}
Handles the creationComplete event for this component.
parameter: event
private function init ( event:FlexEvent ):void
{
setStyle('backgroundColor', backgroundColor);
_playheadManager = new Timer ( _playheadUpdateInterval );
_playheadManager.addEventListener( TimerEvent.TIMER, updatePlayhead );
_progressManager = new Timer ( _progressInterval );
_progressManager.addEventListener( TimerEvent.TIMER, updateProgress );
openConnection();
}
Updates the playback progress and dispatches a ProgressEvent.PROGRESS
event.
parameter: event
private function updateProgress ( event:TimerEvent ):void
{
_bytesLoaded = _videoStream.bytesLoaded;
_bytesTotal = _videoStream.bytesTotal;
var e:ProgressEvent = new ProgressEvent ( ProgressEvent.PROGRESS, true );
e.bytesLoaded = _videoStream.bytesLoaded;
e.bytesTotal = _videoStream.bytesTotal;
dispatchEvent( e );
if ( e.bytesLoaded == e.bytesTotal )
{
_progressManager.stop();
_downloadComplete = true;
}
}
Updates the _playheadTime and dispatches a
VideoEvent.PLAYHEAD_UPDATE event.
parameter: event
private function updatePlayhead ( event:TimerEvent ):void
{
//trace('updating playhead...' );
_playheadTime = _videoStream.time;
var e:VideoEvent = new VideoEvent ( VideoEvent.PLAYHEAD_UPDATE, true );
e.playheadTime = _videoStream.time;
dispatchEvent( e );
}
Handles the stream status events. Custom logic for
end of stream can be added in the NetStream.Buffer.Empty
switch case.
parameter: event
private function streamStatus (event:NetStatusEvent):void
{
switch ( event.info.code )
{
case 'NetStream.Play.Start' :
_refreshManager = new Timer ( _playheadUpdateInterval );
_refreshManager.addEventListener( TimerEvent.TIMER, updateDisplay );
_refreshManager.start();
_playing = true;
break;
case 'NetStream.Buffer.Full' :
_bufferEmpty = false;
break;
case 'NetStream.Buffer.Empty' :
_bufferEmpty = true;
if ( _videoStream.bufferLength < .1 && ((Math.floor( _totalTime ) - Math.floor( _playheadTime )) <= 1) )
{
//trace("video ended...");
playing = false;
if ( _autoRewind )
_videoStream.seek( 0 );
if ( _clearEndScreen )
clearVideo();
// dispatch videoComplete event.
var e:VideoEvent = new VideoEvent ( VideoEvent.COMPLETE, true );
e.playheadTime = _videoStream.time;
dispatchEvent( e );
}
break;
case 'NetStream.Buffer.Flush' :
break;
default : break ;
}
}
Updates the display using the BitmapData.draw() method.
parameter: e
private function updateDisplay( e:Event ):void
{
if ( _video && _playing )
{
_video.attachNetStream( null );
bitmapData.draw( _video );
_video.attachNetStream( _videoStream );
}
}
// PUBLIC METHODS
Stops playback of video.
public function stop():void
{
playing = false;
_videoStream.seek( 0 );
}
Closes the video stream, stopping the
download if not complete.
public function close():void
{
playing = false;
if (_videoStream)
{
_videoStream.close();
}
}
Pauses playback of video.
public function pause():void
{
playing = false;
}
Plays the video loaded.
public function play():void
{
if ( _videoStream )
{
if ( _videoStream.time > 0 && !_bufferEmpty )
{
playing = true;
}
else if ( _videoStream.time == 0 )
{
if ( _sourceChanged )
{
if ( !live )
{
_videoStream.play( _source );
_playheadManager.start();
_progressManager.start();
_sourceChanged = false;
}
else if ( live )
{
trace( ' source changed for live ' );
_videoStream.play( _streamName );
_playheadManager.start();
_progressManager.start();
_sourceChanged = false;
}
}
else if ( _downloadComplete && !live )
{
playing = true;
}
}
}
}
Clears the video left at the end of the video
playback with a black screen.
public function clearVideo():void
{
trace('clearing video...');
var rect:Rectangle = new Rectangle ( 0, 0, _videoWidth, _videoHeight );
bitmapData.fillRect( rect, 0x00000000 );
}
// GETTERS / SETTERS
The playing property can be used to toggle playback.
It starts and stops the bitmap drawing to the screen when
the video is not playing.
parameter: isPlaying
public function set playing ( isPlaying:Boolean ):void
{
_playing = isPlaying;
if ( _playing && _videoStream )
{
_videoStream.resume();
_playheadManager.start();
}
else if ( !_playing && _videoStream )
{
_videoStream.pause();
_playheadManager.stop();
}
//isPlaying ? _videoStream.play() :_videoStream.pause();
if ( _playing && _refreshManager )
{
_refreshManager.start()
}
else if ( _refreshManager )
{
_refreshManager.stop();
}
}
public function get playing ():Boolean
{
return _playing;
}
public function set bufferTime( time:Number ):void
{
_bufferTime = time;
if ( _videoStream )
_videoStream.bufferTime = time;
}
public function get bufferTime():Number
{
return _bufferTime;
}
public function get volume():Number
{
return _volume;
}
public function set volume( value:Number ):void
{
_volume = value;
if ( _videoStream )
updateSound();
}
public function set totalTime(time:Number):void
{
_totalTime = time;
}
public function get totalTime():Number
{
return _totalTime;
}
public function set backgroundColor ( color:Number ):void
{
_backgroundColor = color;
}
public function get backgroundColor():Number
{
return _backgroundColor;
}
public function set source ( value:String ):void
{
if ( _videoStream && playing )
{
playing = false;
_videoStream.close();
}
_source = value;
_sourceChanged = true;
if ( _videoStream )
{
openConnection();
}
getStreamName();
if ( _videoStream && _autoPlay )
play();
}
private function getStreamName():void
{
if ( source.search( "/" ) > -1 )
{
var urlParts:Array = source.split("/");
_streamName = urlParts[urlParts.length - 1]; // grabs last bit of rtmp:// url, from end of string to first "/"
// trace('_streamName = ' + _streamName );
}
}
public function get source():String
{
return _source;
}
public function set autoPlay( play:Boolean ):void
{
_autoPlay = play;
}
public function get autoPlay():Boolean
{
return _autoPlay;
}
public function set playheadTime( time:Number ):void
{
_playheadTime = time;
_videoStream.seek( time );
}
public function get playheadTime():Number
{
if ( _videoStream )
{
return _videoStream.time;
}
else return 0;
}
public function get bytesLoaded():Number
{
return _bytesLoaded;
}
public function get bytesTotal():Number
{
return _bytesTotal;
}
public function get playheadUpdateInterval():Number
{
return _playheadUpdateInterval;
}
public function get progressInterval():Number
{
return _progressInterval;
}
public function set playheadUpdateInterval( value:Number ):void
{
_playheadUpdateInterval = value;
}
public function set progressInterval( value:Number ):void
{
_progressInterval = value;
}
public function set live(on:Boolean):void
{
_live = on;
}
public function get live():Boolean
{
return _live;
}
The set width setter is overridden to update the
_videoWidth property.
parameter: value
override public function set width(value:Number):void
{
super.width = value;
_videoWidth = value;
}
The set height setter is overridden to update the
_videoHeight property.
parameter: value
override public function set height(value:Number):void
{
super.height = value;
_videoHeight = value;
}
}
}
(C) Æliens
18/6/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.