09.24Tutorial - Playing FLV video in plain AS3 - Part 3 (Updated)
Hi Everyone,
it's been a while.. a long while. I have been selling my time to Adobe, doing Flex work with a great team in NYC; we are working on the MediaStore for Verizon Wireless since July 21st? I forget.. NYC is nice, but not for me. I'm a nature lover!
A few people posted comments on the second part of this tutorial, kudos to you! You're partly to blame for this late evening blog post
For those who didn't read them already, I strongly suggest starting by reading the first parts of this tutorial :
The Events
In part 2, pretty much all the functionality was covered, except the events class. I like to use custom events, which are derived from flash.events.Event because then you are allowed to add custom properties to your events, which are easy to pick up by the listeners. For this particular case, I have identified 3 different types of events I would like to dispatch, based on the properties that are associated with each:
- MediaEvent -- for basic events that don't require any data
- MediaSizeEvent -- for events related to the size (in pixels) of the media playing
- MediaTimeEvent -- for the events related to time (lenght, time position)
MediaEvent
-
package newcommerce.media
-
{
-
import flash.events.Event;
-
-
public class MediaEvent extends Event
-
{
-
// dispatched when the media has finished playing
-
public static const FINISHED_PLAYING:String = "finished_playing";
-
-
// dispatched when the media has fully loaded
-
public static const FULLY_LOADED:String = "fully_loaded";
-
-
// dispatched when there is an error playing the media
-
public static const ERROR:String = "error";
-
-
// dispatched when the media has started playing
-
public static const STARTED_PLAYING:String = "started_playing";
-
-
// text property is used to carry error text
-
protected var _text:String;
-
public function get text():String { return _text; }
-
-
public function MediaEvent(type:String, text:String = "")
-
{
-
super(type);
-
_text = text;
-
}
-
-
public override function toString():String
-
{
-
return "newcommerce.media.MediaEvent";
-
}
-
}
-
}
MediaSizeEvent
-
package ca.newcommerce.media
-
{
-
import flash.events.Event;
-
-
public class MediaSizeEvent extends Event
-
{
-
// dispatched when we received the media size in the metadata so the UI can adapt to it
-
public static const SIZE:String = "media_size";
-
-
// width and height properties contain the actual media size
-
protected var _width:Number;
-
protected var _height:Number;
-
-
public function get height():Number { return _height; }
-
public function get width():Number { return _width; }
-
-
public function MediaSizeEvent(type:String, width:Number, height:Number)
-
{
-
super(type);
-
_width = width;
-
_height = height;
-
}
-
}
-
}
MediaTimeEvent
-
package newcommerce.media
-
{
-
import flash.events.Event;
-
-
public class MediaTimeEvent extends Event
-
{
-
// dispatched to update the listeners on the current position (media_time) of the playing media
-
public static const TIME:String = "media_time";
-
-
// dispatched once we receive the media duration information (in the metadata)
-
public static const DURATION:String = "media_duration";
-
-
// actual media time (position or total time depending on the event type used)
-
protected var _time:Number;
-
-
// the percentage of the media that is currently loaded
-
protected var _loaded:Number;
-
-
public function get time():Number { return _time; }
-
public function get loaded():Number { return _loaded; }
-
-
public function MediaTimeEvent(type:String, time:Number, loaded:Number = -1)
-
{
-
super(type);
-
_time = time;
-
_loaded = loaded;
-
}
-
}
-
}
Integration inside of the code
In the preview part, I hadn't yet seperated the events to different classes in the code. So here is the updated source for FLVPlayer
-
package newcommerce.media
-
{
-
import newcommerce.media.MediaSizeEvent;
-
import newcommerce.media.MediaTimeEvent;
-
import newcommerce.media.MediaData;
-
import newcommerce.media.MediaEvent;
-
-
import flash.media.SoundTransform;
-
-
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;
-
-
public function set video(video:Video):void { _video = video; }
-
public function get video():Video { return _video; }
-
public function get playing():Boolean { return _playing; }
-
public function get paused():Boolean { return _paused; }
-
public function get currentMedia():MediaData { return _currentMedia; }
-
public function get volume():Number { return _lastVolume * 100; }
-
-
public function get position():Number
-
{
-
if(_stream != null)
-
return _stream.time;
-
else
-
return 0;
-
}
-
-
public function get duration():Number
-
{
-
if(_currentMedia != null)
-
{
-
return _currentMedia.duration;
-
}
-
else
-
return -1;
-
}
-
-
public function FLVPlayer()
-
{
-
_soundTransform = new SoundTransform();
-
-
_timer = new Timer(250);
-
_timer.addEventListener(TimerEvent.TIMER, doMediaTime);
-
-
_streamStatus = [];
-
}
-
-
-
protected function pushStatus(status:String):void
-
{
-
_streamStatus.push(status);
-
while (_streamStatus.length> 3)
-
_streamStatus.shift();
-
}
-
-
public function reset():void
-
{
-
stop();
-
newStream();
-
}
-
-
protected function newStream():void
-
{
-
if(_stream != null)
-
{
-
_stream.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, doAsyncError);
-
_stream.removeEventListener(NetStatusEvent.NET_STATUS, doStreamStatus);
-
_stream.removeEventListener(IOErrorEvent.IO_ERROR, doIOError);
-
_stream.close();
-
}
-
-
if(_connection != null)
-
{
-
_connection.removeEventListener(NetStatusEvent.NET_STATUS, doConnectionStatus);
-
_connection.removeEventListener(IOErrorEvent.IO_ERROR, doIOError);
-
_connection.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, doSecurityError);
-
_connection.close();
-
}
-
-
_connection = new NetConnection();
-
_connection.connect(null);
-
_connection.addEventListener(NetStatusEvent.NET_STATUS, doConnectionStatus);
-
_connection.addEventListener(IOErrorEvent.IO_ERROR, doIOError);
-
_connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, doSecurityError);
-
-
_stream = new NetStream(_connection);
-
_stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, doAsyncError);
-
_stream.addEventListener(NetStatusEvent.NET_STATUS, doStreamStatus);
-
_stream.addEventListener(IOErrorEvent.IO_ERROR, doIOError);
-
_stream.client = this;
-
-
_video.attachNetStream(_stream);
-
-
_stream.bufferTime = 1;
-
_stream.receiveAudio(true);
-
_stream.receiveVideo(true);
-
}
-
-
public function stop():void
-
{
-
if(!_playing)
-
return;
-
-
_stream.pause();
-
_playing = false;
-
_timer.stop();
-
}
-
-
public function playMedia(media:MediaData):void
-
{
-
_mediaFinished = false;
-
if(media == null)
-
return;
-
-
_currentMedia = media;
-
_streamStatus = [];
-
-
play(true);
-
}
-
-
public function seek(seconds:Number):void
-
{
-
if (seconds <_currentMedia.duration-2 && _playing)
-
{
-
_lastPos = _stream.time;
-
_stream.seek(seconds);
-
_stream.resume();
-
}
-
}
-
-
public function pause():void
-
{
-
_stream.pause();
-
_paused = true;
-
_timer.stop();
-
}
-
-
protected function doConnectionStatus(evt:NetStatusEvent):void
-
{
-
trace("connection: " + evt.info.code);
-
}
-
-
protected function doStreamStatus(evt:NetStatusEvent):void
-
{
-
trace("stream: " + evt.info.code);
-
if (evt.info.code == "NetStream.Seek.InvalidTime")
-
{
-
_stream.seek(_lastPos);
-
_stream.resume();
-
}
-
else
-
{
-
pushStatus(evt.info.code);
-
analyzeStatus();
-
}
-
}
-
-
protected function analyzeStatus():void
-
{
-
trace("analyzeStatus()");
-
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;
-
-
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 (mediaFinished)
-
{
-
trace("finished playing");
-
dispatchEvent(new MediaEvent(MediaEvent.FINISHED_PLAYING))
-
while (_streamStatus.length> 0)
-
_streamStatus.pop();
-
}
-
else if (_streamStatus[_streamStatus.length-1] == "NetStream.Play.Start")
-
dispatchEvent(new MediaEvent(MediaEvent.STARTED_PLAYING));
-
-
}
-
-
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
-
{
-
_lastVolume = _soundTransform.volume = volume/100;
-
-
if(!_mute)
-
_stream.soundTransform = _soundTransform;
-
}
-
-
public function play(forced:Boolean = false):void
-
{
-
if(_paused && !forced)
-
{
-
_stream.resume();
-
_paused = false;
-
_timer.start();
-
return;
-
}
-
-
reset();
-
-
_stream.play(_currentMedia.uri);
-
-
setVolume(this._lastVolume*100);
-
-
_playing = true;
-
_paused = false;
-
_timer.start();
-
}
-
-
public function onMetaData(infoObject:Object):void
-
{
-
if (infoObject.duration != null)
-
{
-
// dispatch a DURATION event
-
dispatchEvent(new MediaTimeEvent(MediaTimeEvent.DURATION, infoObject.duration));
-
}
-
-
var lclHeight:Number = infoObject.height;
-
var lclWidth:Number = infoObject.width;
-
-
if(isNaN(lclHeight) || isNaN(lclWidth))
-
{
-
lclWidth = 425;
-
lclHeight= 320;
-
}
-
-
// dispatch the size received event
-
dispatchEvent(new MediaSizeEvent(MediaSizeEvent.SIZE, lclWidth, lclHeight));
-
-
if(!willTrigger(MediaSizeEvent.SIZE))
-
{
-
var yDif:Number = (_video.height - lclHeight)/2;
-
_video.y += yDif;
-
_video.height = lclHeight;
-
}
-
}
-
-
public function doMediaTime(evt:TimerEvent):void
-
{
-
var pct:Number = Math.round(_stream.bytesLoaded / _stream.bytesTotal * 100);
-
-
// dispatch a MEDIA_TIME event
-
dispatchEvent(new MediaTimeEvent(MediaTimeEvent.TIME, _stream.time, pct));
-
}
-
-
protected function doSecurityError(evt:SecurityErrorEvent):void
-
{
-
trace("AbstractStream.securityError");
-
dispatchEvent(new MediaEvent(MediaEvent.ERROR, evt.text));
-
}
-
-
protected function doIOError(evt:IOErrorEvent):void
-
{
-
trace("AbstractScreem.ioError");
-
dispatchEvent(new MediaEvent(MediaEvent.ERROR, evt.text));
-
}
-
-
protected function doAsyncError(evt:AsyncErrorEvent)
-
{
-
trace("AsyncError");
-
dispatchEvent(new MediaEvent(MediaEvent.ERROR, evt.text));
-
}
-
-
protected function doNetStatus(evt:NetStatusEvent):void
-
{
-
}
-
}
-
}
MediaData
and just in case you missed it in the other parts, here is the source for the MediaData class..
-
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 = 120, image:String = "", width:Number = 320, height:Number = 200)
-
{
-
_uri = uri;
-
_title = title;
-
_duration = duration;
-
_image = image;
-
_width = width;
-
_height = height;
-
}
-
}
-
}
That's it folks!
I have used these classes in many projects so far, they work great! With each project they got refined a bit, so the version in this tutorial is actually the latest, which I fine-tuned for ZooToo PetWars widget. They are also used for all the work I did for Music Nation, and I am sure I will re-use them again and again. Hope you enjoyed, I will put the source .zip file up here in a few days.
[2009.12.10] UPDATE -- SOURCE CODE
Due to popular request, and although I believe I shouldn't do this, here is the source code.
Thanks!
Martin
Related posts (automatically generated):
- Tutorial - Playing FLV video in plain AS3 - Part 1
- Tutorial - Playing FLV video in plain AS3 - Part 2
- Tutorial - Playing FLV video in plain AS3 - Part 4 - Sample Usage

Excellent tutorial, looking forward to getting all the bits together and playing around with it! Thanks again
September 26th, 2008 at 12:47 am
Hi,
This is great stuff, I’m sure (I get no errors when referencing the classes). But I’m having a little hard time finding out how to implement it so that I can actually get a video to play (anywhere. On stage or another MC instance).
Do I have to create an instance of a video component of some sort first to get it to work, or? It all seem a bit abstract to me (yes, fairly new to OOP concepts. But learning though).
Will there be a chance to see a (simple) tutorial on how to implement this thing?
September 29th, 2008 at 1:56 am
OK, this is what I try to do. So you may get why I’m off in the wrong direction:
(first I reference the classes in my class using the import statement), then:
var myMediaData = new MediaData(”some.flv”, “some title”, 234, “some.jpg”, 508, 169);
var myFLVPlayer = new FLVPlayer();
myFLVPlayer.playMedia(myMediaData);
But when I dispatch an event from a thumbnail (movieclip) to play a given flv file, I get an Error #1009: Cannot access a property or method of a null object reference.
Any idea what I’m missing?
September 29th, 2008 at 2:17 am
Ok, i’ve got it working, having a few problems with the aspect ratio (but i’m sure that’s a small issue)
You need to create an instance of the Video class and then set that to the video instance for the FLVPlayer to use.
So something like this…
var myMediaData = new MediaData(“videos/test.flv“, “some title“, 234, “image.jpg“, 320, 200);
var myFLVPlayer = new FLVPlayer();
var video:Video = new Video(320, 200);
myFLVPlayer.video = video;
myFLVPlayer.playMedia(myMediaData);
stage.addChild(video);
Hope that helps!
Andy
September 29th, 2008 at 10:42 am
very good!
As for the aspect ratio, you want to catch the “MediaSizeEvent”, and adapt accordingly:
[as]
// add this before the call to playMedia
myFLVPlayer.addEventListener(MediaSizeEvent.SIZE, doMediaSize);
// then add this function
function doMediaSize(evt:MediaSizeEvent):void
{
// make sure the “video” variable is available in this scope
video.width = evt.width;
video.heigth = evt.height;
}
[/as]
If my memory serves well, this should do the trick!
September 29th, 2008 at 11:18 am
Hi (again)
Martin, you should post a complete tutorial on using this :).
I can’t seem to get the MediaTimeEvent to work.
I have added a simple class that stretches according to a percentage recieved. I’ve added an eventListener to this, that listen for MediaTimeEvent. But it doesn’t seem to hear anything. When I simply run trace within the attached handler it never gets called. I’ve tried to enable bubbles but that didn’t seem to help.
Would you be able to show a simple example of how to set this up?
November 20th, 2008 at 10:52 pm
Correction ‘listen for MediaTimeEvent.TIME’
November 20th, 2008 at 10:53 pm
Very nice piece. Well done.
Just a quick note tho incase someone did a cut n paste, you spelt height wrong in your September 29th, 2008 at 11:18 am comment
ie
video.heigth = evt.height;
November 30th, 2008 at 3:36 pm
Great tutorial! Does this code also work with RTMP using either FMS or Red5? If not, any chance for an updated tutorial with the additional code required?
December 8th, 2008 at 9:52 pm
Great tutorial,
anyone knows, how you can assign buttons to different video files, so the video plays only when you click a button. lets say i have 5 different movies, and 5 buttons assigned to them. by clicking a button, the asigned video starts to play…
can i use the netconnect method? anyone knows how?
would be great to get some help
December 25th, 2008 at 4:38 pm
It seems like a very useful tutorial, but I don’t have much time now to inspect the code now.
Could you please, tell me, if this media player gives the opportunity to stream video from the middle of it. I mean, before it downloaded 100%, lets’s say it has only downloaded 20% of the video, and if the user moves the pointer to 60% on bar, will it start downloading the video from the 60% of the it or it will continue from 20%?
Thanks in advance.
April 7th, 2009 at 4:54 am
hi, very nice tutorial, it helps me a lot. Thank u very much.
April 23rd, 2009 at 1:47 am
Great tutorial! It has been very helpful.
Thanks for sharing your knowledge.
June 4th, 2009 at 3:35 am
Thanks for given this great post…
July 9th, 2009 at 3:46 am
Hey,
I was wondering why you dont override the Clone function and the toString functions as required by the documentation?
Thanks in advance
August 13th, 2009 at 11:27 am
Hi Martin,
but there isn’t the “source.zip” file???
Thanks
August 27th, 2009 at 3:39 am
Hi Martin.
A quick ‘thank you!’ for writing this article. I’m new to ActionScript and thirsty for well written, object-oriented code - your work is really helping me to understand.
Again, thank you!
September 12th, 2009 at 6:19 am
I took the aspect ratio on with a different method.
I defined a variable for the width - and then used scaleX scaleY to maintain proper proportions.
//
var vWidth:Number = new Number(350);
var vHeight:Number = new Number(178);
//
//““`~~~~~~“““
private function doMediaSize(e:MediaSizeEvent):void {
// make sure the “video” variable is available in this scope
// If I want to use the default size of the media - do it this way
//video.width = e.width;
//video.height = e.height;
// If I want to specify a size and constrain to that spec / dimension - do it this way:
video.width = vWidth;
video.scaleX = video.scaleY;
}
September 29th, 2009 at 4:41 pm
Nice tutorial!!!
Would you mind to publish the zip file?
Thanks
October 5th, 2009 at 9:35 am
hey marti, not sure why but your RSS feed and your homepage http://blog.martinlegris.com are not updating correctly. I am only seeing posts from July? Some how stumbled on this post via google.
October 12th, 2009 at 11:43 am
Hello Mr. Legris,
thank you very much for your tutorial on how to create own flv player.
I am trying to find a way how to add subtitles feature to your implementation. Please may you point me in right direction ? I am just beginner of AS (but have some programming skills from other languages).
I was thinking of adding textbox over player, parse XML TimedText subs format amd register ASCuePoints (for every single line of subs), than display text from CUEPoint when it is reached (OnCuePoints event) in textbox…
Is it doable ? Is there any easier solution ? (FLVCaptioning component seems not to work for me). I am using pseudo streaming throught xmoov PHP script, so I guess I need to re-register all the subtitles cuePoints every time user seeks to different time…
XML TText subs do not work with any major FLV player on net (e.g. JWplayer/Flowplayer etc.). So I guess I need to implement it myself
Thank you very much for any tip & info you want to share!
Have a great day!
John
October 21st, 2009 at 8:07 am
Hi,
I am having a problem I wonder if anyone can help with.
I’ve used the player as so:
var myMediaData = new MediaData(”videos/”+videoToPlay, “title”, 234, “image.jpg”, vidWidth, vidHeight);
myFLVPlayer = new FLVPlayer();
var video:Video = new Video(vidWidth, vidHeight);
myFLVPlayer.video = video;
myFLVPlayer.playMedia(myMediaData);
addChild(video);
in a function.
the first time I call the function it plays video A with sizes 640 x 360,
I then have function which removes the video object
and re-runs the above function with a new video to play with a new size of: 1024 x 576
The first video is removed successfully, and the second video is loaded and added to the stage, the problem is, it has the wrong sizes.
The second video object has the same height and width of the original video object. The actual video on the stage though, has the correct height, but the old width. Seems very strange.
is there a better to way to play back another video of a different size?
Many thanks
Chris
November 11th, 2009 at 4:11 pm
@Chris, you need to listen to the event MediaSizeEvent from the FlvPlayer.. so
[as]
myFlvPlayer.addEventListener(MediaSizeEvent.SIZE, doMediaSize);
// then…
protected function doMediaSize(evt:MediaSizeEvent):void
{
// use evt.width & evt.height to adjust your video object.. or force a certain width/height on it
}
[/as]
Otherwise, in FLVPlayer.as on the function “onMedaData” I do this:
[as]
if(!willTrigger(MediaSizeEvent.SIZE))
{
var yDif:Number = (_video.height - lclHeight)/2;
_video.y += yDif;
_video.height = lclHeight;
}
[/as]
which means if the FLVPlayer doesn’t trigger a MediaSizeEvent, there is a default behavior which is probably not what you want at this time..
Hope this helps.. Martin
November 11th, 2009 at 4:40 pm
@all who want a ZIP file..
it is not hard to copy paste this code somewhere. In the process you will learn things; I am all about learning and understand and not so much about mindless integration of other people’s code.. If you want a zip file, just send me a request by email and I will package it for you.
November 11th, 2009 at 4:41 pm
Hi. I was greatly appreciate it if you sent me a package of your “Playing FLV video” file. Thanks.
Justin
November 16th, 2009 at 12:06 pm
Thanks so much for this buddy, I’d written out 80% of this beforehand but you’ve out-eleganced me by miles with these custom events. Really appreciate the tutorial format too, it forces me to read through before the trial and error begins. I read something about over-riding the clone and the toString functions in the custom events, so have done that, but not sure if it is necessary - I know that it’s not always the case. The issue I always had with the NetStream technique was the lack of onComplete when dealing with progressive download. I think you’ve got that sorted though…not there yet.
November 23rd, 2009 at 11:17 am