06.10Tutorial - Playing FLV video in plain AS3 - Part 2
Hi Everyone,
today I will continue on this topic:
and the following installments are here:
- Tutorial - Playing FLV video in plain AS3 - Part 3
- Tutorial - Playing FLV video in plain AS3 - Part 4 - Sample Usage
In the first part I only discussed the essentials for playing an FLV. Today I will show how to build a re-usable class around those basic ideas. At the end of this tutorial you will have a solid set of classes (3) that you can reuse as a base to play FLV videos in all your widgets.
Architecture
There will be three classes involved in the development today:
- MediaData -- to hold the information on the FLV being played
- FLVPlayer -- to play the FLV
- MediaEvent -- to communicate with the outside world as to what is going on inside the FLVPlayer
This is a gross preview of where we are heading at:
MediaData
Will hold the basic information about the FLV to be played: it's height, width, duration, uri, image to represent it, and title
FLVPlayer
Will play the actual FLV, using the MediaData object as input, and dispatch MediaEvent's to inform the outside world of what is going on.
MediaEvent
Hold information to be received by the outside world, updating the status on what is happening in the FLVPlayer
MediaData
This is a simple class, whose role is simply to hold the information on a particular FLV file. It contains:
- title -- the title of the flv
- image -- an image to be used to represent the flv, either as a thumbnail or a place holder before the flv loads
- uri -- the uri of the FLV file itself
- duration -- the duration in seconds of the flv file
- width -- the width in pixels of the video
- height -- the height in pixels of the video
-
package newcommerce.media
-
{
-
public class MediaData
-
{
-
protected var _uri:String;
-
protected var _title:String;
-
protected var _duration:Number;
-
protected var _image:String;
-
protected var _width:Number;
-
protected var _height:Number;
-
-
public function set uri(uri:String):void { _uri = uri; }
-
public function get uri():String { return _uri; }
-
public function set image(image:String):void { _image = image; }
-
public function get image():String { return _image; }
-
public function set title(title:String):void { _title = title; }
-
public function get title():String { return _title; }
-
public function set duration(duration:Number):void { _duration = duration; }
-
public function get duration():Number { return _duration; }
-
public function get width():Number { return _width; }
-
public function set width(value:Number):void { _width = value; }
-
public function get height():Number { return _height; }
-
public function set height(value:Number):void { _height = value; }
-
-
function MediaData(uri:String = "", title:String = "", duration:Number = 0, image:String = "", width:Number = 320, height:Number = 200)
-
{
-
_uri = uri;
-
_title = title;
-
_duration = duration;
-
_image = image;
-
_width = width;
-
_height = height;
-
}
-
}
-
}
FLVPlayer
The FLVPlayer is the core where all the action happens. First let's start with the most important functions:
- connect() -- to initialize the NetConnect & NetStream objects
- playMedia(media:MediaData) -- to start playing the media
- pause() -- to pause the media
- seek(pos:Number) -- to seek to a certain position
For an explanation of the code down here, please refer to the first part of the tutorial, which covers the basics. Note that if you want efficient loading of FLV's one after the other, where it's possible that the first one is not completely loaded yet when you want the second one to start playing (each new video replacing the old ones) it's important to close the NetStream then the NetConnection, and start over with fresh ones (from my experience).
-
package newcommerce.media
-
{
-
import flash.media.SoundTransform;
-
-
import newcommerce.media.MediaData;
-
import newcommerce.media.MediaEvent;
-
-
import flash.net.NetConnection;
-
import flash.net.NetStream;
-
import flash.media.Video;
-
import flash.display.Sprite;
-
-
import flash.events.IOErrorEvent;
-
import flash.events.NetStatusEvent;
-
import flash.events.SecurityErrorEvent;
-
import flash.events.AsyncErrorEvent;
-
-
import flash.events.EventDispatcher;
-
import flash.utils.Timer;
-
import flash.events.TimerEvent;
-
-
public class FLVPlayer extends EventDispatcher
-
{
-
protected var _currentMedia:MediaData;
-
protected var _stream:NetStream;
-
protected var _paused:Boolean = false;
-
protected var _playing:Boolean = false;
-
protected var _timer:Timer;
-
protected var _soundTransform:SoundTransform;
-
protected var _lastVolume:Number = 1;
-
protected var _mute:Boolean = false;
-
protected var _connection:NetConnection;
-
protected var _mediaFinished:Boolean = false;
-
protected var _streamStatus:Array;
-
protected var _lastPos:Number;
-
protected var _video:Video;
-
-
-
// constructor will go here
-
-
public function connect():void
-
{
-
// stop the stream if it exists and is playing
-
stop();
-
-
// build a new NetConnection and NetStream
-
newStream();
-
}
-
-
protected function newStream():void
-
{
-
// if either the _stream or _connection are open, remove event listeners and close them
-
if(_stream != null)
-
{
-
_stream.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, doAsyncError);
-
_stream.removeEventListener(NetStatusEvent.NET_STATUS, doNetStatus);
-
_stream.removeEventListener(IOErrorEvent.IO_ERROR, doIOError);
-
_stream.close();
-
}
-
-
if(_connection != null)
-
{
-
_connection.removeEventListener(NetStatusEvent.NET_STATUS, doNetStatus);
-
_connection.removeEventListener(IOErrorEvent.IO_ERROR, doIOError);
-
_connection.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, doSecurityError);
-
_connection.close();
-
}
-
-
// create a new connection
-
_connection = new NetConnection();
-
_connection.connect(null);
-
_connection.addEventListener(NetStatusEvent.NET_STATUS, doNetStatus);
-
_connection.addEventListener(IOErrorEvent.IO_ERROR, doIOError);
-
_connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, doSecurityError);
-
-
// create a new stream
-
_stream = new NetStream(_connection);
-
_stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, doAsyncError);
-
_stream.addEventListener(NetStatusEvent.NET_STATUS, doNetStatus);
-
_stream.addEventListener(IOErrorEvent.IO_ERROR, doIOError);
-
_stream.client = this;
-
-
// attach the stream to the Video visual component
-
_video.attachNetStream(_stream);
-
-
// set the bufferTime (in seconds)
-
_stream.bufferTime = 1;
-
-
// receive audio and video
-
_stream.receiveAudio(true);
-
_stream.receiveVideo(true);
-
}
-
-
public function playMedia(media:AbstractMediaData):void
-
{
-
// used to track when a media has finished playing or not
-
_mediaFinished = false;
-
if(media == null)
-
return;
-
-
_currentMedia = media;
-
-
// used to track the status message we received in the doNetStatus function
-
_streamStatus = [];
-
-
// force a fresh play
-
play(true);
-
}
-
-
// function to play the _currentMedia, forced means to force it to restart at the beginning, while unforced can serve to "de-pause" a playing FLV
-
public function play(forced:Boolean = false):void
-
{
-
// if the FLV is paused and we are not forcing it to start at the beginning
-
if(_paused && !forced)
-
{
-
_stream.resume();
-
_paused = false;
-
_timer.start();
-
return;
-
}
-
-
// reset the NetConnection and NetStream objects (this is important when having a player with a playlist with a SKIP or NEXT button..
-
connect();
-
-
// officially starts streaming the .FLV file
-
_stream.play(_currentMedia.uri);
-
-
// set the volume to the _lastVolume value
-
setVolume(this._lastVolume*100);
-
-
// set the playing flag
-
_playing = true;
-
-
// set the pause flag
-
_paused = false;
-
-
// start our timer to track stream position & loading progress
-
_timer.start();
-
}
-
-
public function stop():void
-
{
-
// if playing flag is on
-
if(!_playing)
-
return;
-
-
// pause the stream
-
_stream.pause();
-
-
// set playing flag to false
-
_playing = false;
-
-
// stop the timer
-
_timer.stop();
-
}
-
-
public function pause():void
-
{
-
// pause the stream
-
_stream.pause();
-
-
// set the paused flag to true
-
_paused = true;
-
-
// stop the timer
-
_timer.stop();
-
-
// notice the _playing flag is untouched. when paused, we have _playing = true & _paused = true, this could be simplified, I know.
-
}
-
-
public function seek(seconds:Number):void
-
{
-
// if the requested position is less than the media's known duration (-2 is because FLV's don't like to be seeked near the end), and we are currently _playing
-
if (seconds <_currentMedia.duration-2 && _playing)
-
{
-
// record the last know valid playing position in case an error happens
-
_lastPos = _stream.time;
-
-
// seek to the requested position
-
_stream.seek(seconds);
-
-
// make sure the stream is playing
-
_stream.resume();
-
-
// unset the pause flag
-
_paused = false;
-
}
-
}
Now we can tackle the rest...
-
public function FLVPlayer()
-
{
-
// create a new SoundTransform object, for the volume of the playing FLV
-
_soundTransform = new SoundTransform();
-
-
// this timer object is used to keep track of the stream position & loading progress
-
_timer = new Timer(250);
-
_timer.addEventListener(TimerEvent.TIMER, doMediaTime);
-
-
// initialize the streamStatus array to keep a small (3 items) log of the last doNetStatus events
-
// I analyze this information to understand exactly when a FLV has reached the end since time values cannot be trusted.
-
_streamStatus = [];
-
}
-
-
-
public function mute(muted:Boolean = true):void
-
{
-
_mute = muted;
-
if(_mute)
-
{
-
_soundTransform.volume = 0;
-
_stream.soundTransform = _soundTransform;
-
}
-
else
-
{
-
_soundTransform.volume = _lastVolume;
-
_stream.soundTransform = _soundTransform;
-
}
-
}
-
-
public function setVolume(volume:Number):void
-
{
-
// keep track of the last set volume for the mute function
-
_lastVolume = _soundTransform.volume = volume/100;
-
-
// if not muted, apply the requested volume
-
if(!_mute)
-
_stream.soundTransform = _soundTransform;
-
}
and finally the event handling function
-
public function onMetaData(infoObject:Object):void
-
{
-
// if the event contains duration information
-
if (infoObject.duration != null)
-
{
-
// dispatch a DURATION event
-
dispatchEvent(new MediaEvent(MediaEvent.DURATION_RECEIVED, {duration:infoObject.duration}));
-
-
// set the mediaData's duration
-
_currentMedia.duration = infoObject.duration;
-
}
-
-
// normally we received the width and height of the video as well
-
var lclHeight:Number = infoObject.height;
-
var lclWidth:Number = infoObject.width;
-
-
// if they are invalid, got to your default values
-
if(isNaN(lclHeight) || isNaN(lclWidth))
-
{
-
lclWidth = 425;
-
lclHeight= 320;
-
}
-
-
// dispatch the size received event
-
dispatchEvent(new MediaEvent(MediaEvent.SIZE_RECEIVED, { width:lclWidth, height:lclHeight } ));
-
}
-
-
// function function is on the _timer object, running 4 times per second
-
public function doMediaTime(evt:TimerEvent):void
-
{
-
// calculate the percentage loaded
-
var pct:Number = Math.round(_stream.bytesLoaded / _stream.bytesTotal * 100);
-
-
// dispatch a TIME event containing the stream position & percentage loaded
-
dispatchEvent(new MediaEvent(MediaEvent.TIME, { seconds:_stream.time, loaded:pct } ));
-
}
-
-
// these error handlers are basic, the listeners can catch then and handle the errors accordingly
-
protected function doSecurityError(evt:SecurityErrorEvent):void
-
{
-
trace("AbstractStream.securityError");
-
dispatchEvent(new MediaEvent(MediaEvent.ERROR, { errorEvt:evt } ));
-
}
-
-
protected function doIOError(evt:IOErrorEvent):void
-
{
-
trace("AbstractScreem.ioError");
-
dispatchEvent(new MediaEvent(MediaEvent.ERROR, { errorEvt:evt } ));
-
}
-
-
protected function doAsyncError(evt:AsyncErrorEvent)
-
{
-
trace("AsyncError");
-
dispatchEvent(new MediaEvent(MediaEvent.ERROR, { errorEvt:evt } ));
-
}
-
-
// this function handles the Stream's & NetConnection's NetStatus events. They could be seperated
-
protected function doNetStatus(evt:NetStatusEvent):void
-
{
-
// this happens when somebody seeks to close to the end.. if it happens, revert to the last known playing position
-
if (evt.info.code == "NetStream.Seek.InvalidTime")
-
{
-
_stream.seek(_lastPos);
-
_stream.resume();
-
}
-
// otherwise push the code into the log and analyze the log
-
else
-
{
-
pushStatus(evt.info.code);
-
analyzeStatus();
-
}
-
}
finally the function to push a status code, and analyze the log
-
// add the code to the log, and for the current analysis only the last 3 are needed to dump the others
-
protected function pushStatus(status:String):void
-
{
-
_streamStatus.push(status);
-
while (_streamStatus.length> 3)
-
_streamStatus.shift();
-
}
-
-
// this function analyzes the status code log to find when a media has really finished playing since the time values couldn't be trusted.
-
protected function analyzeStatus():void
-
{
-
// find the position of three important netstatus codes in the log
-
var stopIdx:Number = _streamStatus.lastIndexOf("NetStream.Play.Stop");
-
var flushIdx:Number = _streamStatus.lastIndexOf("NetStream.Buffer.Flush");
-
var emptyIdx:Number = _streamStatus.lastIndexOf("NetStream.Buffer.Empty");
-
-
var mediaFinished:Boolean = false;
-
-
// then depending if they are there, and in which order they appear, we know when a media has finished playing.
-
if (stopIdx> -1 && flushIdx> -1 && emptyIdx> -1)
-
{
-
if (flushIdx <stopIdx && stopIdx <emptyIdx)
-
{
-
mediaFinished = true;
-
}
-
}
-
else if (flushIdx> -1 && emptyIdx> -1)
-
{
-
if (flushIdx <emptyIdx)
-
mediaFinished = true;
-
}
-
else if (stopIdx> -1 && flushIdx> -1)
-
{
-
mediaFinished = true;
-
}
-
-
// if the media has finished playing, dispatch the event and clean the log
-
if (mediaFinished)
-
{
-
dispatchEvent(new MediaEvent(MediaEvent.FINISHED_PLAYING, { } ))
-
while (_streamStatus.length> 0)
-
_streamStatus.pop();
-
}
-
}
The rest is here: Tutorial - Playing FLV video in plain AS3 - Part 3
Related posts (automatically generated):
- Tutorial - Playing FLV video in plain AS3 - Part 1
- Tutorial - Playing FLV video in plain AS3 - Part 3 (Updated)
- Tutorial - Playing FLV video in plain AS3 - Part 4 - Sample Usage

great tutorial !!!
June 24th, 2008 at 3:22 pm
most comprehensive tutorial I have seen on this! any sign of a follow up?
August 15th, 2008 at 10:21 am
Great tutorial, do you think you’ll have time to complete the next bit soon?
Keep up the good work!
Andy
September 16th, 2008 at 6:57 am
Will the tutorial on the Event class be finished?
September 27th, 2008 at 2:03 pm
hello, everyone! I am making a flv video player.I want to complete that click anwhere then play at the position but not care if the position in the buffered scope, I use NetStream.seek(minute), but this only userfull int the buffered scope, can anyone help me? thanks .
November 2nd, 2009 at 6:50 pm