<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="http://feedproxy.google.com/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feedproxy.google.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>The Tech Labs » Video</title>
	
	<link>http://www.thetechlabs.com</link>
	<description>Adobe Air, Flash and Flex Tutorials</description>
	<pubDate>Thu, 23 Oct 2008 22:58:12 +0000</pubDate>
	<generator>http://wordpress.org/?v=abc</generator>
	<language>en</language>
			<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feedproxy.google.com/TheTechLabs_video" type="application/rss+xml" /><item>
		<title>Expanding the AS3 Videoplayer</title>
		<link>http://feedproxy.google.com/~r/TheTechLabs_video/~3/EgEFFZ6188o/</link>
		<comments>http://www.thetechlabs.com/video/expanding-the-as3-videoplayer/#comments</comments>
		<pubDate>Wed, 13 Aug 2008 23:40:13 +0000</pubDate>
		<dc:creator>Rafael Nuenlist</dc:creator>
		
		<category><![CDATA[Flash]]></category>

		<category><![CDATA[Interfaces]]></category>

		<category><![CDATA[Video]]></category>

		<category><![CDATA[as3]]></category>

		<category><![CDATA[fullscreen]]></category>

		<category><![CDATA[playlist]]></category>

		<category><![CDATA[progress bar]]></category>

		<category><![CDATA[tutorial]]></category>

		<category><![CDATA[video player]]></category>

		<category><![CDATA[volume]]></category>

		<guid isPermaLink="false">http://www.thetechlabs.com/?p=88</guid>
		<description><![CDATA[<p>Alright, here&#8217;s the second part of the video player tutorial. We will be adding the following new features:<br />
- Playlist support<br />
- Fullscreen support<br />
- Save volume<br />
- Clickable progress/volume bar</p>
<small><em>posted in <a href="http://www.thetechlabs.com/category/flash/">Flash</a> by Rafael Nuenlist <a href="http://www.thetechlabs.com/video/expanding-the-as3-videoplayer/#comments">Leave A Comment</a><br />&copy;2008 <a href="http://www.thetechlabs.com">The Tech Labs</a>. All Rights Reserved.</em></small>]]></description>
			<content:encoded><![CDATA[<p>Alright, here&#8217;s the second part of the video player tutorial. We will be adding the following new features:<br />
- Playlist support<br />
- Fullscreen support<br />
- Save volume<br />
- Clickable progress/volume bar</p>
<p>You can preview the final result of this tutorial <a title="Video Player Demo" href="http://thetechlabs.com/tutorials/files/flash/rnunlist/videoplayer_prt2/videoplayer_prt2.html"  target="_blank">here</a>.</p>
<h3>Requirements</h3>
<p><strong>Adobe Flash CS3</strong></p>
<p><a title="Try / buy" onclick="javascript:pageTracker._trackPageview('/outbound/article/http://www.adobe.com/products/flash/');" href="http://www.adobe.com/products/flash/" onclick="javascript:pageTracker._trackPageview('oclicks/http://www.adobe.com/products/flash/');" target="_blank">Try / Buy</a></p>
<p><strong>Source Files</strong></p>
<p><a title="Download Source Files" onclick="javascript:pageTracker._trackPageview('/downloads/tutorials/files/flash/fcarrera/flickrsearchengine/flickrsearchengine_sfiles.zip');" href="http://thetechlabs.com/tutorials/files/flash/rnunlist/videoplayer_prt2/videoplayer_prt2.zip" onclick="javascript:pageTracker._trackPageview('downloads/tutorials/files/flash/rnunlist/videoplayer_prt2/videoplayer_prt2.zip');" target="_self">Download</a></p>
<p>We also wanted to create a box displaying a message when the video is still buffering. But there has been an issue with the NetStreamEvent. It doesn&#8217;t fire the NetStream.Buffer.Empty event when the player&#8217;s actually buffering. People from the community say that this problem is somehow related with the encoding software of the flash video files. Since we didn&#8217;t find a proper solution, we left this feature out. If someone has an idea how this problem can still be properly solved, feel free to leave us a comment.</p>
<h3>Playlist support</h3>
<p>Ok, let&#8217;s begin with the major new feature. Adding the playlist support is quite simple and nearly implemented the same way as in the slideshow tutorial. So we have our XML file which looks like this:</p>
<pre>&lt;playlist&gt;
	&lt;vid src="video/hancock.flv" desc="Hancock (2008) - Movie Trailer"/&gt;
	&lt;vid src="video/redbelt.flv" desc="Redbelt (2008) - Movie Trailer"/&gt;
	&lt;vid src="video/the_dark_knight.flv" desc="The Dark Knight (2008) - Movie Trailer"/&gt;
&lt;/playlist&gt;</pre>
<p>As you can see, each video has two attributes. One describes the source to the file and the other one is a short description or the title of the movie.<br />
Now it&#8217;s time to go on with coding. We changed the line which defines the source of the flash video file to the following:</p>
<pre>var strSource:String = root.loaderInfo.parameters.playlist == null ? "playlist.xml" : root.loaderInfo.parameters.playlist;</pre>
<p>Like this, flash will look for the flash variable playlist which you can add in the html file. If it&#8217;s defined, then the value will be assigned to the variable strSource. Otherwise we just assign a default location. Like this, you can use one compiled SWF file for every playlist you want to publish.<br />
In order to load this file, we need to add three more variables to our video player:</p>
<pre>var urlLoader:URLLoader;
var urlRequest:URLRequest;
var xmlPlaylist:XML;</pre>
<p>urlLoader will later be used to load and handle the loaded xml file. urlRequest is an object for the urlLoader to load the playlist file. And xmlPlaylist will then contain the loaded xml data from the urlLoader.</p>
<p>We&#8217;ve added these code lines add the bottom to the initVideoPlayer() function:</p>
<pre>	urlRequest = new URLRequest(strSource);
	urlLoader = new URLLoader();
	urlLoader.addEventListener(Event.COMPLETE, playlistLoaded);
	urlLoader.load(urlRequest);</pre>
<p>A new urlRequest will be created with the source location of the xml file. Then we create a new urlLoader, add an event listener when the loading is completed and finally load the file. What&#8217;s also important is that we hide the controls of the video player in the first line of the initVideoPlayer() function since we don&#8217;t want that the user can click anything if the playlist hasn&#8217;t been loaded yet.<br />
As soon as the playlist is loaded we call the playlistLoaded() function which looks like this:</p>
<pre>function playlistLoaded(e:Event):void {
	xmlPlaylist = new XML(urlLoader.data);
	playVid(0, false)
	mcVideoControls.visible = true;
}</pre>
<p>We assign the received data from the loader to the xml object, set the first video source but not play the video and show the video controls since we&#8217;ve loaded all the stuff.</p>
<p>Now we need a back and forward button. We add two event listeners for the buttons in the initVideoPlayer() function:</p>
<pre>	mcVideoControls.btnNext.addEventListener(MouseEvent.CLICK, playNext);
	mcVideoControls.btnPrevious.addEventListener(MouseEvent.CLICK, playPrevious);</pre>
<p>Then we define a new variable that saves the current number:</p>
<pre>var intActiveVid:int;</pre>
<p>Now let&#8217;s take a closer look at the playVid() function</p>
<pre>function playVid(intVid:int = 0, bolPlay = true):void {
	if(bolPlay) {
		tmrDisplay.stop();
		nsStream.play(String(xmlPlaylist..vid[intVid].@src));
		mcVideoControls.btnPause.visible	= true;
		mcVideoControls.btnPlay.visible		= false;
	} else {
		strSource = xmlPlaylist..vid[intVid].@src;
	}

	vidDisplay.visible					= true;

	mcVideoControls.mcVideoDescription.lblDescription.x = 0;
	mcVideoControls.mcVideoDescription.lblDescription.htmlText = (intVid + 1) + &#8220;. <span style="color: #ffffff;">&#8221; + String(xmlPlaylist..vid[intVid].@desc) + &#8220;</span>&#8220;;

	intActiveVid = intVid;
}</pre>
<p>As you can see, it now needs to have a video number and a boolean value if the video should be played or just be set to the strSource variable. The function also sets the new description label of the requested video with the value from the xml object and positions it to zero on the x-axis. And finally the intVid value gets assigned to the variable intActiveVid.<br />
The playNext() and playPrevious() function are now ease to implement. We just check if there there is still a video left we can skip or rewind and call the playVid function with the parameter intActiveVid + 1 or intActiveVid - 1 depending on the direction:</p>
<pre>function playNext(e:MouseEvent = null):void {
	if(intActiveVid + 1 &lt; xmlPlaylist..vid.length())
		playVid(intActiveVid + 1);

}

function playPrevious(e:MouseEvent = null):void {
	if(intActiveVid - 1 &gt;= 0)
		playVid(intActiveVid - 1);
}</pre>
<p>When a video reached it&#8217;s end, we stoped the player. But now we want to play the next video in the playlist if there&#8217;s still one left, if not, ok then we stop the player <img src='http://www.thetechlabs.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> So we change this in the netStatusHandler() function:</p>
<pre>	if(intActiveVid + 1 &lt; xmlPlaylist..vid.length())
		playNext();
	else
		stopVideoPlayer();</pre>
<p>Now there&#8217;s only the vertical scrolling of the video description label left for the playlist feature. It could be that you need an extra long title for your video. In that case, we&#8217;ve just created a very long one lined textfield that&#8217;s masked. On top of that is an invisible button that will be used to know, if the user moves the mouse over the label so we can scroll it if it&#8217;s to long. In order to know if we&#8217;re currently scrolling the label and to know in which direction the scrolling is going, we need to set two new variables:</p>
<pre>var bolDescriptionHover:Boolean = false;
var bolDescriptionHoverForward:Boolean = true;</pre>
<p>Now we need to add an event listener on the invisible button:</p>
<pre>	mcVideoControls.mcVideoDescription.btnDescription.addEventListener(MouseEvent.MOUSE_OVER, startDescriptionScroll);
	mcVideoControls.mcVideoDescription.btnDescription.addEventListener(MouseEvent.MOUSE_OUT, stopDescriptionScroll);</pre>
<p>The functions startDescriptionScroll() and stopDescriptionScroll() are simply setting the bolDescriptionHover variable to true or false depending on the mouse event. startDescriptionScroll() additionally checks, if it&#8217;s even necessary to scroll the text:</p>
<pre>function startDescriptionScroll(e:MouseEvent):void {
	if(mcVideoControls.mcVideoDescription.lblDescription.textWidth &gt; 138)
		bolDescriptionHover = true;
}

function stopDescriptionScroll(e:MouseEvent):void {
	bolDescriptionHover = false;
}</pre>
<p>To actually move the label we just add some new lines of code to the updateDisplay() function which is called by the timer object:</p>
<pre>	if(bolDescriptionHover) {
		if(bolDescriptionHoverForward) {
			mcVideoControls.mcVideoDescription.lblDescription.x -= 0.1;
			if(mcVideoControls.mcVideoDescription.lblDescription.textWidth - 133 &lt;= Math.abs(mcVideoControls.mcVideoDescription.lblDescription.x))
				bolDescriptionHoverForward = false;
		} else {
			mcVideoControls.mcVideoDescription.lblDescription.x += 0.1;
			if(mcVideoControls.mcVideoDescription.lblDescription.x &gt;= 0)
				bolDescriptionHoverForward = true;
		}
	} else {
		mcVideoControls.mcVideoDescription.lblDescription.x = 0;
		bolDescriptionHoverForward = true;
	}</pre>
<p>First, we check if we&#8217;re currently over the label. The we figure out, which way we move the text. Since the updateDisplay() function is called pretty often, we get a smooth motion by incrementing and decrementing the label&#8217;s x value just by 0.1. So, when moving forward, we actualy move the label to the left and check, if we&#8217;ve reached the end. If so, we invert the bolDescriptionForward value and begin to move the field to the right until it reached zero on the x-axis. If the user&#8217;s no hovering over the invisible button, we just reset the position of the label and the direction flag.</p>
<h3>Fullscreen support</h3>
<p>Since flash player version 9,0,28,0 you can use the fullscreen feature. In order to get this to work, you also need to set this in the HTML code. But since Flash does all the HTML stuff for you, you only need to change the mode to allowFullscreen in the publish settings of HTML</p>
<p><img class="aligncenter size-full wp-image-90" title="Flash Fullscreen Publish" src="http://www.thetechlabs.com/wp-content/uploads/2008/08/flash-fullscreen-publish.jpg" alt="" width="461" height="651" /></p>
<p>The implementing of the fullscreen support is rather simple in our case because we don&#8217;t have too much stuff on the stage we need to reposition/resize once we hit the fullscreen mode. So, first of all, we set the scale mode of the stage to no scale because we want to resize the object individualy and not everything. We also set the stage align mode to the top left corner:</p>
<pre>stage.scaleMode	= StageScaleMode.NO_SCALE;
stage.align		= StageAlign.TOP_LEFT;</pre>
<p>Then we add two event listeners for the fullscreen button. One to enter it and one for leaving the full screen mode. We also add an event listener when the fullscreen mode changes. The visibility of the btnFullscreenOff button will be set to false. We do all of this in the initVideoPlayer() function:</p>
<pre>	mcVideoControls.btnFullscreenOn.addEventListener(MouseEvent.CLICK, fullscreenOnClicked);
	mcVideoControls.btnFullscreenOff.addEventListener(MouseEvent.CLICK, fullscreenOffClicked);
	stage.addEventListener(FullScreenEvent.FULL_SCREEN, onFullscreen);
	mcVideoControls.btnFullscreenOff.visible = false;</pre>
<p>To actualy enter and leave the fullscreen mode, all you need to do is to set the display state of the stage to the wanted mode. That&#8217;s what we&#8217;re doing when the user clicks the fullscreen buttons:</p>
<pre>function fullscreenOnClicked(e:MouseEvent):void {
	stage.displayState = StageDisplayState.FULL_SCREEN;
}

function fullscreenOffClicked(e:MouseEvent):void {
	stage.displayState = StageDisplayState.NORMAL;
}</pre>
<p>Now we only need to define the resizing action in the onFullScreen() function:<br />
function onFullscreen(e:FullScreenEvent):void {</p>
<pre>    if (e.fullScreen) {
        mcVideoControls.btnFullscreenOn.visible = false;
		mcVideoControls.btnFullscreenOff.visible = true;

		mcVideoControls.x = (Capabilities.screenResolutionX - 440) / 2;
		mcVideoControls.y = (Capabilities.screenResolutionY - 33);

		vidDisplay.height = (Capabilities.screenResolutionY - 33);
		vidDisplay.width = vidDisplay.height * 4 / 3;
		vidDisplay.x	= (Capabilities.screenResolutionX - vidDisplay.width) / 2;
    } else {
        mcVideoControls.btnFullscreenOn.visible = true;
		mcVideoControls.btnFullscreenOff.visible = false;
		mcVideoControls.x = 0;
		mcVideoControls.y = 330;
		vidDisplay.y = 0;
		vidDisplay.x = 0;
		vidDisplay.width = 440;
		vidDisplay.height = 330;
    }
}</pre>
<p>So, first we check if we&#8217;re entering the fullscreen mode. If so, we switch the fullscreen buttons, align the control bar in the bottom center of the screen and size up the video display and center it on to the screen. If we&#8217;re leaving the fullscreen mode, what you can also do by pressing the ESC key on your keyboard, we reset all the stuff to how it was before.<br />
That&#8217;s was already everything for the fullscreen support <img src='http://www.thetechlabs.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<h3>Save volume</h3>
<p>This is quite an important feature and really ease to implement in flash. To save the last used volume you can use the flash cookies which will be stored on the computer of the user. They are like normal browser cookies, but can only accessed through flash.<br />
So, the first thing we need to do is creating a new shared object:</p>
<pre>var shoVideoPlayerSettings:SharedObject = SharedObject.getLocal("playerSettings");</pre>
<p>The function getLocal returns the shared object you&#8217;ve requested. In our case we want to have the playerSettings cookie/shared object. Since we want to set the volume on the start, we add the following line to the initVideoPlayer() function:</p>
<pre>	var tmpVolume:Number = DEFAULT_VOLUME;
	if(shoVideoPlayerSettings.data.playerVolume != undefined) {
		tmpVolume = shoVideoPlayerSettings.data.playerVolume;
		intLastVolume = tmpVolume;
	}

	mcVideoControls.mcVolumeScrubber.x = (53 * tmpVolume) + 318;
	mcVideoControls.mcVolumeFill.mcFillRed.width = mcVideoControls.mcVolumeScrubber.x - 371 + 53;
	setVolume(tmpVolume);</pre>
<p>First we assign the DEFAULT_VOLUME to a temporary variable. Then we check if the user has already a cookie with the player volume. If so, we override the value from the variable. Then we update the volume bar and set the volume with the setVolume() function.<br />
In order to store the new volume in the cookie, we add the following two new lines to the setVolume() function:</p>
<pre>	shoVideoPlayerSettings.data.playerVolume = intVolume;
	shoVideoPlayerSettings.flush();</pre>
<p>The function flush() writes the new value immadiately in the flash cookie.</p>
<h3>Clickable progress/volume bar</h3>
<p>This is just a beauty hack for the player. We want to be able to click on the progress or the volume bar to begin with the scrubbing. So, we just added two invisible buttons on the off the bars and added the function to it as when you click the scrub buttons directly:</p>
<pre>	mcVideoControls.mcVolumeScrubber.btnVolumeScrubber.addEventListener(MouseEvent.MOUSE_DOWN, volumeScrubberClicked);
	mcVideoControls.mcProgressScrubber.btnProgressScrubber.addEventListener(MouseEvent.MOUSE_DOWN, progressScrubberClicked);</pre>
<p>And what&#8217;s also changed is, that we now lock the center in the startDrag() function. Like this the scrubber will move to where you clicked on the bars:</p>
<pre>function volumeScrubberClicked(e:MouseEvent):void {
	bolVolumeScrub = true;

	mcVideoControls.mcVolumeScrubber.startDrag(true, new Rectangle(318, 19, 53, 0));
}

function progressScrubberClicked(e:MouseEvent):void {
	bolProgressScrub = true;

	mcVideoControls.mcProgressScrubber.startDrag(true, new Rectangle(0, 2, 432, 0));
}</pre>
<p>We&#8217;ve already reached the end of the second part of the video player tutorial. We hope that you enjoyed reading it and we appreciate every kind of feedback.</p>
<h3>Full code with comments</h3>
<pre>// ###############################
// ############# CONSTANTS
// ###############################

// time to buffer for the video in sec.
const BUFFER_TIME:Number				= 8;
// start volume when initializing player
const DEFAULT_VOLUME:Number				= 0.6;
// update delay in milliseconds.
const DISPLAY_TIMER_UPDATE_DELAY:int	= 10;
// smoothing for video. may slow down old computers
const SMOOTHING:Boolean					= true;

// ###############################
// ############# VARIABLES
// ###############################

// flag for knowing if user hovers over description label
var bolDescriptionHover:Boolean = false;
// flag for knowing in which direction the description label is currently moving
var bolDescriptionHoverForward:Boolean = true;
// flag for knowing if flv has been loaded
var bolLoaded:Boolean					= false;
// flag for volume scrubbing
var bolVolumeScrub:Boolean				= false;
// flag for progress scrubbing
var bolProgressScrub:Boolean			= false;
// holds the number of the active video
var intActiveVid:int;
// holds the last used volume, but never 0
var intLastVolume:Number				= DEFAULT_VOLUME;
// net connection object for net stream
var ncConnection:NetConnection;
// net stream object
var nsStream:NetStream;
// object holds all meta data
var objInfo:Object;
// shared object holding the player settings (currently only the volume)
var shoVideoPlayerSettings:SharedObject = SharedObject.getLocal("playerSettings");
// url to flv file
var strSource:String					= root.loaderInfo.parameters.playlist == null ? "playlist.xml" : root.loaderInfo.parameters.playlist;
// timer for updating player (progress, volume...)
var tmrDisplay:Timer;
// loads the xml file
var urlLoader:URLLoader;
// holds the request for the loader
var urlRequest:URLRequest;
// playlist xml
var xmlPlaylist:XML;

// ###############################
// ############# STAGE SETTINGS
// ###############################
stage.scaleMode	= StageScaleMode.NO_SCALE;
stage.align		= StageAlign.TOP_LEFT;

// ###############################
// ############# FUNCTIONS
// ###############################

// sets up the player
function initVideoPlayer():void {
	// hide video controls on initialisation
	mcVideoControls.visible = false;

	// hide buttons
	mcVideoControls.btnUnmute.visible			= false;
	mcVideoControls.btnPause.visible			= false;
	mcVideoControls.btnFullscreenOff.visible	= false;

	// set the progress/preload fill width to 1
	mcVideoControls.mcProgressFill.mcFillRed.width	= 1;
	mcVideoControls.mcProgressFill.mcFillGrey.width	= 1;

	// set time and duration label
	mcVideoControls.lblTimeDuration.htmlText		= "<span style="color: #ffffff;">00:00</span> / 00:00&#8243;;

	// add global event listener when mouse is released
	stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased);

	// add fullscreen listener
	stage.addEventListener(FullScreenEvent.FULL_SCREEN, onFullscreen);

	// add event listeners to all buttons
	mcVideoControls.btnPause.addEventListener(MouseEvent.CLICK, pauseClicked);
	mcVideoControls.btnPlay.addEventListener(MouseEvent.CLICK, playClicked);
	mcVideoControls.btnStop.addEventListener(MouseEvent.CLICK, stopClicked);
	mcVideoControls.btnNext.addEventListener(MouseEvent.CLICK, playNext);
	mcVideoControls.btnPrevious.addEventListener(MouseEvent.CLICK, playPrevious);
	mcVideoControls.btnMute.addEventListener(MouseEvent.CLICK, muteClicked);
	mcVideoControls.btnUnmute.addEventListener(MouseEvent.CLICK, unmuteClicked);
	mcVideoControls.btnFullscreenOn.addEventListener(MouseEvent.CLICK, fullscreenOnClicked);
	mcVideoControls.btnFullscreenOff.addEventListener(MouseEvent.CLICK, fullscreenOffClicked);

	mcVideoControls.btnVolumeBar.addEventListener(MouseEvent.MOUSE_DOWN, volumeScrubberClicked);
	mcVideoControls.mcVolumeScrubber.btnVolumeScrubber.addEventListener(MouseEvent.MOUSE_DOWN, volumeScrubberClicked);
	mcVideoControls.btnProgressBar.addEventListener(MouseEvent.MOUSE_DOWN, progressScrubberClicked);
	mcVideoControls.mcProgressScrubber.btnProgressScrubber.addEventListener(MouseEvent.MOUSE_DOWN, progressScrubberClicked);

	mcVideoControls.mcVideoDescription.btnDescription.addEventListener(MouseEvent.MOUSE_OVER, startDescriptionScroll);
	mcVideoControls.mcVideoDescription.btnDescription.addEventListener(MouseEvent.MOUSE_OUT, stopDescriptionScroll);

	// create timer for updating all visual parts of player and add
	// event listener
	tmrDisplay = new Timer(DISPLAY_TIMER_UPDATE_DELAY);
	tmrDisplay.addEventListener(TimerEvent.TIMER, updateDisplay);

	// create a new net connection, add event listener and connect
	// to null because we don&#8217;t have a media server
	ncConnection = new NetConnection();
	ncConnection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
	ncConnection.connect(null);

	// create a new netstream with the net connection, add event
	// listener, set client to this for handling meta data and
	// set the buffer time to the value from the constant
	nsStream = new NetStream(ncConnection);
	nsStream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
	nsStream.client = this;
	nsStream.bufferTime = BUFFER_TIME;

	// attach net stream to video object on the stage
	vidDisplay.attachNetStream(nsStream);
	// set the smoothing value from the constant
	vidDisplay.smoothing = SMOOTHING;

	// set default volume and get volume from shared object if available
	var tmpVolume:Number = DEFAULT_VOLUME;
	if(shoVideoPlayerSettings.data.playerVolume != undefined) {
		tmpVolume = shoVideoPlayerSettings.data.playerVolume;
		intLastVolume = tmpVolume;
	}
	// update volume bar and set volume
	mcVideoControls.mcVolumeScrubber.x = (53 * tmpVolume) + 318;
	mcVideoControls.mcVolumeFill.mcFillRed.width = mcVideoControls.mcVolumeScrubber.x - 371 + 53;
	setVolume(tmpVolume);

	// create new request for loading the playlist xml, add an event listener
	// and load it
	urlRequest = new URLRequest(strSource);
	urlLoader = new URLLoader();
	urlLoader.addEventListener(Event.COMPLETE, playlistLoaded);
	urlLoader.load(urlRequest);
}
function playClicked(e:MouseEvent):void {
	// check&#8217;s, if the flv has already begun
	// to download. if so, resume playback, else
	// load the file
	if(!bolLoaded) {
		nsStream.play(strSource);
		bolLoaded = true;
	}
	else{
		nsStream.resume();
	}

	vidDisplay.visible = true;

	// switch play/pause visibility
	mcVideoControls.btnPause.visible	= true;
	mcVideoControls.btnPlay.visible		= false;
}

function pauseClicked(e:MouseEvent):void {
	// pause video
	nsStream.pause();

	// switch play/pause visibility
	mcVideoControls.btnPause.visible	= false;
	mcVideoControls.btnPlay.visible		= true;
}

function stopClicked(e:MouseEvent):void {
	// calls stop function
	stopVideoPlayer();
}

function muteClicked(e:MouseEvent):void {
	// set volume to 0
	setVolume(0);

	// update scrubber and fill position/width
	mcVideoControls.mcVolumeScrubber.x				= 318;
	mcVideoControls.mcVolumeFill.mcFillRed.width	= 1;
}

function unmuteClicked(e:MouseEvent):void {
	// set volume to last used value or DEFAULT_VOLUME if last volume is zero
	var tmpVolume:Number = intLastVolume == 0 ? DEFAULT_VOLUME : intLastVolume
	setVolume(tmpVolume);

	// update scrubber and fill position/width
	mcVideoControls.mcVolumeScrubber.x = (53 * tmpVolume) + 318;
	mcVideoControls.mcVolumeFill.mcFillRed.width = mcVideoControls.mcVolumeScrubber.x - 371 + 53;
}

function volumeScrubberClicked(e:MouseEvent):void {
	// set volume scrub flag to true
	bolVolumeScrub = true;

	// start drag
	mcVideoControls.mcVolumeScrubber.startDrag(true, new Rectangle(318, 19, 53, 0)); // NOW TRUE
}

function progressScrubberClicked(e:MouseEvent):void {
	// set progress scrub flag to true
	bolProgressScrub = true;

	// start drag
	mcVideoControls.mcProgressScrubber.startDrag(true, new Rectangle(0, 2, 432, 0)); // NOW TRUE
}

function mouseReleased(e:MouseEvent):void {
	// set progress/volume scrub to false
	bolVolumeScrub		= false;
	bolProgressScrub	= false;

	// stop all dragging actions
	mcVideoControls.mcProgressScrubber.stopDrag();
	mcVideoControls.mcVolumeScrubber.stopDrag();

	// update progress/volume fill
	mcVideoControls.mcProgressFill.mcFillRed.width	= mcVideoControls.mcProgressScrubber.x + 5;
	mcVideoControls.mcVolumeFill.mcFillRed.width	= mcVideoControls.mcVolumeScrubber.x - 371 + 53;

	// save the volume if it&#8217;s greater than zero
	if((mcVideoControls.mcVolumeScrubber.x - 318) / 53 &gt; 0)
		intLastVolume = (mcVideoControls.mcVolumeScrubber.x - 318) / 53;
}

function updateDisplay(e:TimerEvent):void {
	// checks, if user is scrubbing. if so, seek in the video
	// if not, just update the position of the scrubber according
	// to the current time
	if(bolProgressScrub)
		nsStream.seek(Math.round(mcVideoControls.mcProgressScrubber.x * objInfo.duration / 432))
	else
		mcVideoControls.mcProgressScrubber.x = nsStream.time * 432 / objInfo.duration; 

	// set time and duration label
	mcVideoControls.lblTimeDuration.htmlText		= &#8220;<span style="color: #ffffff;">&#8221; + formatTime(nsStream.time) + &#8220;</span> / &#8221; + formatTime(objInfo.duration);

	// update the width from the progress bar. the grey one displays
	// the loading progress
	mcVideoControls.mcProgressFill.mcFillRed.width	= mcVideoControls.mcProgressScrubber.x + 5;
	mcVideoControls.mcProgressFill.mcFillGrey.width	= nsStream.bytesLoaded * 438 / nsStream.bytesTotal;

	// update volume and the red fill width when user is scrubbing
	if(bolVolumeScrub) {
		setVolume((mcVideoControls.mcVolumeScrubber.x - 318) / 53);
		mcVideoControls.mcVolumeFill.mcFillRed.width = mcVideoControls.mcVolumeScrubber.x - 371 + 53;
	}

	// chech if user is currently hovering over description label
	if(bolDescriptionHover) {
		// check in which direction we&#8217;re currently moving
		if(bolDescriptionHoverForward) {
			// move to the left and check if we&#8217;ve shown everthing
			mcVideoControls.mcVideoDescription.lblDescription.x -= 0.1;
			if(mcVideoControls.mcVideoDescription.lblDescription.textWidth - 133 &lt;= Math.abs(mcVideoControls.mcVideoDescription.lblDescription.x))
				bolDescriptionHoverForward = false;
		} else {
			// move to the right and check if we&#8217;re back to normal
			mcVideoControls.mcVideoDescription.lblDescription.x += 0.1;
			if(mcVideoControls.mcVideoDescription.lblDescription.x &gt;= 0)
				bolDescriptionHoverForward = true;
		}
	} else {
		// reset label position and direction variable
		mcVideoControls.mcVideoDescription.lblDescription.x = 0;
		bolDescriptionHoverForward = true;
	}
}

function onMetaData(info:Object):void {
	// stores meta data in a object
	objInfo = info;

	// now we can start the timer because
	// we have all the neccesary data
	if(!tmrDisplay.running)
		tmrDisplay.start();
}

function netStatusHandler(event:NetStatusEvent):void {
	// handles net status events
	switch (event.info.code) {
		// trace a messeage when the stream is not found
		case &#8220;NetStream.Play.StreamNotFound&#8221;:
			trace(&#8221;Stream not found: &#8221; + strSource);
		break;
		// when the video reaches its end, we check if there are
		// more video left or stop the player
		case &#8220;NetStream.Play.Stop&#8221;:
			if(intActiveVid + 1 &lt; xmlPlaylist..vid.length())
				playNext();
			else
				stopVideoPlayer();
		break;
	}
}

function stopVideoPlayer():void {
	// pause netstream, set time position to zero
	nsStream.pause();
	nsStream.seek(0);

	// in order to clear the display, we need to
	// set the visibility to false since the clear
	// function has a bug
	vidDisplay.visible					= false;

	// switch play/pause button visibility
	mcVideoControls.btnPause.visible	= false;
	mcVideoControls.btnPlay.visible		= true;
}

function setVolume(intVolume:Number = 0):void {
	// create soundtransform object with the volume from
	// the parameter
	var sndTransform		= new SoundTransform(intVolume);
	// assign object to netstream sound transform object
	nsStream.soundTransform	= sndTransform;

	// hides/shows mute and unmute button according to the
	// volume
	if(intVolume &gt; 0) {
		mcVideoControls.btnMute.visible		= true;
		mcVideoControls.btnUnmute.visible	= false;
	} else {
		mcVideoControls.btnMute.visible		= false;
		mcVideoControls.btnUnmute.visible	= true;
	}

	// store the volume in the flash cookie
	shoVideoPlayerSettings.data.playerVolume = intVolume;
	shoVideoPlayerSettings.flush();
}

function formatTime(t:int):String {
	// returns the minutes and seconds with leading zeros
	// for example: 70 returns 01:10
	var s:int = Math.round(t);
	var m:int = 0;
	if (s &gt; 0) {
		while (s &gt; 59) {
			m++; s -= 60;
		}
		return String((m &lt; 10 ? &#8220;0&#8243; : &#8220;&#8221;) + m + &#8220;:&#8221; + (s &lt; 10 ? &#8220;0&#8243; : &#8220;&#8221;) + s);
	} else {
		return &#8220;00:00&#8243;;
	}
}

function fullscreenOnClicked(e:MouseEvent):void {
	// go to fullscreen mode
	stage.displayState = StageDisplayState.FULL_SCREEN;
}

function fullscreenOffClicked(e:MouseEvent):void {
	// go to back to normal mode
	stage.displayState = StageDisplayState.NORMAL;
}

function onFullscreen(e:FullScreenEvent):void {
	// check if we&#8217;re entering or leaving fullscreen mode
    if (e.fullScreen) {
		// switch fullscreen buttons
        mcVideoControls.btnFullscreenOn.visible = false;
		mcVideoControls.btnFullscreenOff.visible = true;

		// bottom center align controls
		mcVideoControls.x = (Capabilities.screenResolutionX - 440) / 2;
		mcVideoControls.y = (Capabilities.screenResolutionY - 33);

		// size up video display
		vidDisplay.height 	= (Capabilities.screenResolutionY - 33);
		vidDisplay.width 	= vidDisplay.height * 4 / 3;
		vidDisplay.x		= (Capabilities.screenResolutionX - vidDisplay.width) / 2;
    } else {
		// switch fullscreen buttons
        mcVideoControls.btnFullscreenOn.visible = true;
		mcVideoControls.btnFullscreenOff.visible = false;

		// reset controls position
		mcVideoControls.x = 0;
		mcVideoControls.y = 330;

		// reset video display
		vidDisplay.y = 0;
		vidDisplay.x = 0;
		vidDisplay.width = 440;
		vidDisplay.height = 330;
    }
}

function playlistLoaded(e:Event):void {
	// create new xml with loaded data from loader
	xmlPlaylist = new XML(urlLoader.data);

	// set source of the first video but don&#8217;t play it
	playVid(0, false)

	// show controls
	mcVideoControls.visible = true;
}

function playVid(intVid:int = 0, bolPlay = true):void {
	if(bolPlay) {
		// stop timer
		tmrDisplay.stop();

		// play requested video
		nsStream.play(String(xmlPlaylist..vid[intVid].@src));

		// switch button visibility
		mcVideoControls.btnPause.visible	= true;
		mcVideoControls.btnPlay.visible		= false;
	} else {
		strSource = xmlPlaylist..vid[intVid].@src;
	}

	// show video display
	vidDisplay.visible					= true;

	// reset description label position and assign new description
	mcVideoControls.mcVideoDescription.lblDescription.x = 0;
	mcVideoControls.mcVideoDescription.lblDescription.htmlText = (intVid + 1) + &#8220;. <span style="color: #ffffff;">&#8221; + String(xmlPlaylist..vid[intVid].@desc) + &#8220;</span>&#8220;;

	// update active video number
	intActiveVid = intVid;
}

function playNext(e:MouseEvent = null):void {
	// check if there are video left to play and play them
	if(intActiveVid + 1 &lt; xmlPlaylist..vid.length())
		playVid(intActiveVid + 1);

}

function playPrevious(e:MouseEvent = null):void {
	// check if we&#8217;re not and the beginning of the playlist and go back
	if(intActiveVid - 1 &gt;= 0)
		playVid(intActiveVid - 1);
}

function startDescriptionScroll(e:MouseEvent):void {
	// check if description label is too long and we need to enable scrolling
	if(mcVideoControls.mcVideoDescription.lblDescription.textWidth &gt; 138)
		bolDescriptionHover = true;
}

function stopDescriptionScroll(e:MouseEvent):void {
	// disable scrolling
	bolDescriptionHover = false;
}

// ###############################
// ############# INIT PLAYER
// ###############################
initVideoPlayer();</pre>
<p class="addtoany_share_save_container">
    <a class="a2a_dd addtoany_share_save" onmouseover="a2a_show_dropdown(this)" onmouseout="a2a_onMouseOut_delay()" href="http://www.addtoany.com/share_save?sitename=The%20Tech%20Labs&amp;siteurl=http%3A%2F%2Fwww.thetechlabs.com%2F&amp;linkname=Expanding%20the%20AS3%20Videoplayer&amp;linkurl=http%3A%2F%2Fwww.thetechlabs.com%2Fvideo%2Fexpanding-the-as3-videoplayer%2F" onclick="javascript:pageTracker._trackPageview('oclicks/http://www.addtoany.com/share_save?sitename=The%20Tech%20Labs&amp;siteurl=http%3A%2F%2Fwww.thetechlabs.com%2F&amp;linkname=Expanding%20the%20AS3%20Videoplayer&amp;linkurl=http%3A%2F%2Fwww.thetechlabs.com%2Fvideo%2Fexpanding-the-as3-videoplayer%2F');"><img src="http://www.thetechlabs.com/wp-content/plugins/add-to-any/share_save_171_16.gif" width="171" height="16" alt="Share/Save/Bookmark"/></a>

	</p><img src="http://feedproxy.google.com/~r/TheTechLabs_video/~4/EgEFFZ6188o" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thetechlabs.com/video/expanding-the-as3-videoplayer/feed/</wfw:commentRss>
		<feedburner:origLink>http://www.thetechlabs.com/video/expanding-the-as3-videoplayer/</feedburner:origLink></item>
		<item>
		<title>How to build a AS3 Videoplayer</title>
		<link>http://feedproxy.google.com/~r/TheTechLabs_video/~3/OWNpvZJciPE/</link>
		<comments>http://www.thetechlabs.com/video/how-to-build-a-as3-videoplayer/#comments</comments>
		<pubDate>Wed, 30 Jul 2008 22:21:58 +0000</pubDate>
		<dc:creator>Rafael Nuenlist</dc:creator>
		
		<category><![CDATA[Flash]]></category>

		<category><![CDATA[Interfaces]]></category>

		<category><![CDATA[Video]]></category>

		<category><![CDATA[adobe flash]]></category>

		<category><![CDATA[as3]]></category>

		<category><![CDATA[how-to]]></category>

		<category><![CDATA[mute]]></category>

		<category><![CDATA[pause]]></category>

		<category><![CDATA[play]]></category>

		<category><![CDATA[Sound]]></category>

		<category><![CDATA[stop]]></category>

		<category><![CDATA[streaming]]></category>

		<category><![CDATA[tutorial]]></category>

		<category><![CDATA[volume]]></category>

		<guid isPermaLink="false">http://www.thetechlabs.com/?p=86</guid>
		<description><![CDATA[<p>Today we explain you how to create a videoplayer with some basic features like this one.<br />
These are: - Play/Pause - Stop - Preloading - Scrubbing - Volume handling There will be another tutorial, which will extend this player. So be sure to check it out.<br />
Requirements<br />
Adobe Flash CS3<br />
Try / Buy<br />
Source Files<br />
Download<br />
Constants<br />
As always, we begin with setting [...]</p>
<small><em>posted in <a href="http://www.thetechlabs.com/category/flash/">Flash</a> by Rafael Nuenlist <a href="http://www.thetechlabs.com/video/how-to-build-a-as3-videoplayer/#comments">Leave A Comment</a><br />&copy;2008 <a href="http://www.thetechlabs.com">The Tech Labs</a>. All Rights Reserved.</em></small>]]></description>
			<content:encoded><![CDATA[<p>Today we explain you how to create a videoplayer with some basic features <a title="View Demo" href="http://www.thetechlabs.com/tutorials/files/flash/rnunlist/videoplayer/videoplayer.prt1.html"  target="_blank">like this one.</a></p>
<p>These are: - Play/Pause - Stop - Preloading - Scrubbing - Volume handling There will be another tutorial, which will extend this player. So be sure to check it out.</p>
<h3>Requirements</h3>
<p><strong>Adobe Flash CS3</strong></p>
<p><a title="Try / buy" onclick="javascript:pageTracker._trackPageview('/outbound/article/http://www.adobe.com/products/flash/');" href="http://www.adobe.com/products/flash/" onclick="javascript:pageTracker._trackPageview('oclicks/http://www.adobe.com/products/flash/');" target="_blank">Try / Buy</a></p>
<p><strong>Source Files</strong></p>
<p><a title="Download Source Files" onclick="javascript:pageTracker._trackPageview('/downloads/tutorials/files/flash/fcarrera/flickrsearchengine/flickrsearchengine_sfiles.zip');" href="http://thetechlabs.com/tutorials/files/flash/rnunlist/videoplayer_prt1.zip" onclick="javascript:pageTracker._trackPageview('downloads/tutorials/files/flash/rnunlist/videoplayer_prt1.zip');" target="_self">Download</a></p>
<h3>Constants</h3>
<p>As always, we begin with setting the constants. The BUFFER_TIME constant stores the time to buffer for the video in seconds. This one will later be assigned to the netstream object</p>
<pre>const BUFFER_TIME:Number				= 8;</pre>
<p>DEFAULT_VOLUME holds the start volume when the player starts</p>
<pre>const DEFAULT_VOLUME:Number				= 0.6;</pre>
<p>Our player has a timer, that updates all the visual parts. We store the update delay in milliseconds in a constant.</p>
<pre>const DISPLAY_TIMER_UPDATE_DELAY:int			= 10;</pre>
<p>To make the video a bit smoother, we can the the Video.smoothing variable to true. This may slow down old computers. If you want to deactive the smoothing on runtime, just open the html file in the zip file, right click on the video player and choose low for the quality. This way the smoothing will automaticaly be deactivated by the flash player.</p>
<pre>const SMOOTHING:Boolean	= true;</pre>
<h3>Variables</h3>
<p>Let&#8217;s move on with the variables. We need to know, if the flv has been loaded when clicking the play button in order to load it or just play the video.</p>
<pre>var bolLoaded:Boolean	= false;</pre>
<p>For the timer function we need to know, if we&#8217;re currently scrubbing the volume or the porgress bar.</p>
<pre>var bolVolumeScrub:Boolean	= false;
var bolProgressScrub:Boolean	 = false;</pre>
<p>Our player has also a mute/unmute button. So, we need to store the last used volume. Like this when you click on unmute, the scrubber will jump back to it&#8217;s previous position as the volume does. This value is always greater than zero. We asign the value from DEFAULT_VOLUME.</p>
<pre>var intLastVolume:Number				= DEFAULT_VOLUME;</pre>
<p>Then we need to have a net connection object for net stream and of course also a net stream object.</p>
<pre>var ncConnection:NetConnection;
var nsStream:NetStream;</pre>
<p>To store the received meta data from the flv file, we create an object</p>
<pre>var objInfo:Object;</pre>
<p>You can change your url to the flv file here. We just set it to the sample movie that&#8217;s in the same folder as the swf.</p>
<pre>var strSource:String = "hancock-tsr2_h480p.flv";</pre>
<p>And last of all, we create a timer object for updating the stuff from the player.</p>
<pre>var tmrDisplay:Timer;</pre>
<h3>Functions</h3>
<p>Now we need to initialize our player. First of all, we hide the unmute and pause button. Then we set the width of the progress and preload fill to 1.Our next step is adding a global eventlistener when the mouse button is released and adding event listeners to all buttons. Then we create ourtimer object for<br />
updating all parts of the player and add the event listener. Next we create a new net connection, add the event listener and connect it to null because we don&#8217;t have a media server. The net stream object needs the net connection for initizalizing. Once we have our net stream object ready, we add the event listener, set the client property to this for handling the meta data and set buffer tim eto the value from the constant BUFFER_TIME. Then we attach the net stream to the video object on the stage and set the smoothing property to the value from the constant SMOOTHING. Last of all we set the default volume to the value from the constant DEFAULT_VOLUME. Note that only values from zero to one are allowed.</p>
<pre>function initVideoPlayer():void {
	mcVideoControls.btnUnmute.visible	= false;
	mcVideoControls.btnPause.visible	= false;

	mcVideoControls.mcProgressFill.mcFillRed.width	= 1;
	mcVideoControls.mcProgressFill.mcFillGrey.width = 1;

	stage.addEventListener( MouseEvent.MOUSE_UP, mouseReleased);

	mcVideoControls.btnPause.addEventListener(MouseEvent.CLICK, pauseClicked);
	mcVideoControls.btnPlay.addEventListener(MouseEvent.CLICK, playClicked);
	mcVideoControls.btnStop.addEventListener(MouseEvent.CLICK, stopClicked);
	mcVideoControls.btnMute.addEventListener(MouseEvent.CLICK, muteClicked);
	mcVideoControls.btnUnmute.addEventListener(MouseEvent.CLICK, unmuteClicked);
	mcVideoControls.mcVolumeScrubber.btnVolumeScrubber.addEventListener(MouseEvent.MOUSE_DOWN, volumeScrubberClicked);
	mcVideoControls.mcProgressScrubber.btnProgressScrubber.addEventListener(MouseEvent.MOUSE_DOWN, progressScrubberClicked);

	tmrDisplay = new Timer(DISPLAY_TIMER_UPDATE_DELAY);
	tmrDisplay.addEventListener(TimerEvent.TIMER, updateDisplay);

	ncConnection = new NetConnection();
	ncConnection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
	ncConnection.connect(null);

	nsStream = new NetStream(ncConnection);
	nsStream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
	nsStream.client = this;
	nsStream.bufferTime = BUFFER_TIME;

	vidDisplay.attachNetStream(nsStream);
	vidDisplay.smoothing = SMOOTHING;

	mcVideoControls.mcVolumeScrubber.x		= (53 * DEFAULT_VOLUME) + 341;
	mcVideoControls.mcVolumeFill.mcFillRed.width	= mcVideoControls.mcVolumeScrubber.x - 394 + 53;
	setVolume(DEFAULT_VOLUME);
}</pre>
<p>The most important part is now done. We&#8217;ve set all variables and added all event listeners. We&#8217;re now going to go through all button event listeners. When the user hit&#8217;s the play button, we need to check, if the flv download has already begun. If that&#8217;s the case, we resume the playback with the NetStream.resume() function. If not, we call the play() function and add the source to the flv file as the parameter.<br />
Then we show the video display object that&#8217;s on the stage. And finaly we switch the pause/play visibility.</p>
<pre>function playClicked(e:MouseEvent):void {
	if(!bolLoaded) {
		nsStream.play(strSource);
		bolLoaded = true;
	} else {
		nsStream.resume();
	}

	vidDisplay.visible			= true;

	mcVideoControls.btnPause.visible	= true;
	mcVideoControls.btnPlay.visible		= false;
}</pre>
<p>The pause button only calls the pause() function from the net stream object and switches the pause/play visibility.</p>
<pre>function pauseClicked(e:MouseEvent):void {
	nsStream.pause();

	mcVideoControls.btnPause.visible	= false;
	mcVideoControls.btnPlay.visible		= true;
}</pre>
<p>When the user click&#8217;s the play button, the stopVideoPlayer() function will be called. We&#8217;ll explain this function below.</p>
<pre>function stopClicked(e:MouseEvent):void {
	stopVideoPlayer();
}</pre>
<p>The mute button sets the volume to zero and updates the volume scrubber and fill position/width.</p>
<pre>function muteClicked(e:MouseEvent):void {
	setVolume(0);

	mcVideoControls.mcVolumeScrubber.x		= 341;
	mcVideoControls.mcVolumeFill.mcFillRed.width	= 1;
}</pre>
<p>The unmute button sets the volume to the last used volume and updates the volume scrubber and fill position/width.</p>
<pre>function unmuteClicked(e:MouseEvent):void {
	setVolume(intLastVolume);

	mcVideoControls.mcVolumeScrubber.x		= (53 * intLastVolume) + 341;
	mcVideoControls.mcVolumeFill.mcFillRed.width	= mcVideoControls.mcVolumeScrubber.x - 394 + 53;
}</pre>
<p>When the volume scrubber is clicked, the scrub flag will be set to true. Then we start dragging it withing the boundings that are described as a rectangle.<br />
Exactly the same goes for the progress scrubber except that we&#8217;re setting another flag variable and bounding.</p>
<pre>function volumeScrubberClicked(e:MouseEvent):void {
	bolVolumeScrub = true;

	mcVideoControls.mcVolumeScrubber.startDrag(false, new Rectangle(341, 19, 53, 0));
}

function progressScrubberClicked(e:MouseEvent):void {
	bolProgressScrub = true;

	mcVideoControls.mcProgressScrubber.startDrag(false, new Rectangle(0, 2, 432, 0));
}</pre>
<p>Last of all, we have our mouseReleased() handler. This function is needed for knowing, when the user stops the scrubbing. First, we set the progress/volume scrub flag to false. Then we stop all dragging actions and update the width of the progress/volume fill. And if the volume is greater than zero, we store it in the variable intLastVolume.</p>
<pre>function mouseReleased(e:MouseEvent):void {
	bolVolumeScrub		= false;
	bolProgressScrub	= false;

	mcVideoControls.mcProgressScrubber.stopDrag();
	mcVideoControls.mcVolumeScrubber.stopDrag();

	mcVideoControls.mcProgressFill.mcFillRed.width	= mcVideoControls.mcProgressScrubber.x + 5;
	mcVideoControls.mcVolumeFill.mcFillRed.width	= mcVideoControls.mcVolumeScrubber.x - 394 + 53;

	if((mcVideoControls.mcVolumeScrubber.x - 341) / 53 &gt; 0)
		intLastVolume = (mcVideoControls.mcVolumeScrubber.x - 341) / 53;
}</pre>
<p>The updateDisplay() function will be called by the timer object 100 times a second. If you want, you can decrease this value by increasing the value from the constant DISPLAY_TIMER_UPDATE_DELAY.<br />
First of all, we check if the user is scrubbing on the progress bar. If that&#8217;s the case, we seek in the video. If not, we just update the position of the scrubber according to the current time.<br />
Then we set the time and duration label. We format the values with the function formatTime() which we explain later.<br />
Now we update the width from the progress bar. The grey one displays the loading progress while the red one shows the progress from the video.<br />
Last of all we update the volume and the red fill width when the user is scrubbing.</p>
<pre>function updateDisplay(e:TimerEvent):void {
	if(bolProgressScrub)
		nsStream.seek(Math.round(mcVideoControls.mcProgressScrubber.x * objInfo.duration / 432))
	else
		mcVideoControls.mcProgressScrubber.x = nsStream.time * 432 / objInfo.duration; 

	mcVideoControls.lblTimeDuration.htmlText 	= "<span style="color: #ffffff;">&#8221; + formatTime(nsStream.time) + &#8220;</span> / &#8221; + formatTime(objInfo.duration);

	mcVideoControls.mcProgressFill.mcFillRed.width	= mcVideoControls.mcProgressScrubber.x + 5;
	mcVideoControls.mcProgressFill.mcFillGrey.width	= nsStream.bytesLoaded * 438 / nsStream.bytesTotal;

	if(bolVolumeScrub) {
		setVolume((mcVideoControls.mcVolumeScrubber.x - 341) / 53);
		mcVideoControls.mcVolumeFill.mcFillRed.width = mcVideoControls.mcVolumeScrubber.x - 394 + 53;
	}
}</pre>
<p>The onMetaDataFunction will be called as soon as the net stream object gets the meta stuff from the flv file. We store these informations in a object. Now can start the timer object because we have all the neccesary data.</p>
<pre>function onMetaData(info:Object):void {
	objInfo = info;

	tmrDisplay.start();
}</pre>
<p>Once a net status event will be fired, the netStatusHandler() function will be called. The event holds the info code of the event type. We only need to handle NetStream.Play.StreamNotFound in case the stream is not found and NetStream.Play.Stop to know, when the video reached its end to stop the player with the stopVideoPlayer() function.</p>
<pre>function netStatusHandler(event:NetStatusEvent):void {
	switch (event.info.code) {
		case "NetStream.Play.StreamNotFound":
			trace("Stream not found: " + strSource);
		break;
		case "NetStream.Play.Stop":
			stopVideoPlayer();
		break;
	}
}</pre>
<p>There are 2 ways to stop the playback: One is clicking the stop button, the other is reaching the end of the video. That&#8217;s the reason why we&#8217;ve put this in a function.<br />
First we pause the netstream and set the playback position to zero. In order to clear the display where the last frame of the video will be shown we need to set the visibility to false. We can&#8217;t use the Video.clear() function since it has a bug. But this one has already been reported to adobe. This workaround works fine. Finaly we switch the play/button visibility.</p>
<pre>function stopVideoPlayer():void {
	nsStream.pause();
	nsStream.seek(0);

	vidDisplay.visible			= false;

	mcVideoControls.btnPause.visible	= false;
	mcVideoControls.btnPlay.visible		= true;
}</pre>
<p>To set the volume of the video we use the setVolume() function.<br />
First we create a soundtransform object with the value from the parameter. Then we assign this object to nsStream.soundTransform.<br />
And we hide or show the mute and unmute according to the volume. If it&#8217;s greater than zero, we show the mute button and vice versa.</p>
<pre>function setVolume(intVolume:Number = 0):void {
	var sndTransform	= new SoundTransform(intVolume);
	nsStream.soundTransform	= sndTransform;

	if(intVolume &gt; 0) {
		mcVideoControls.btnMute.visible		= true;
		mcVideoControls.btnUnmute.visible	= false;
	} else {
		mcVideoControls.btnMute.visible		= false;
		mcVideoControls.btnUnmute.visible	= true;
	}
}</pre>
<p>The last function formatTime() is used to format the seconds to the format mm:ss.</p>
<pre>function formatTime(t:int):String {
	var s:int = Math.round(t);
	var m:int = 0;
	if (s &gt; 0) {
		while (s &gt; 59) {
			m++;
			s -= 60;
		}
		return String((m &lt; 10 ? "0" : "") + m + ":" + (s &lt; 10 ? "0" : "") + s);
	} else {
		return "00:00";
	}
}</pre>
<h3>Call init function</h3>
<p>The only thing left to do is calling the init function for the player.</p>
<pre>initVideoPlayer();</pre>
<p>We hope you liked this tutorial. If you have any question don&#8217;t hesitate to ask. Also we&#8217;ve already mentioned be sure to check back for the next part that will be coming soon.</p>
<h3>Full code with comments</h3>
<pre>// ##########################
// ############# CONSTANTS
// ##########################

// time to buffer for the video in sec.
const BUFFER_TIME:Number				= 8;
// start volume when initializing player
const DEFAULT_VOLUME:Number				= 0.6;
// update delay in milliseconds.
const DISPLAY_TIMER_UPDATE_DELAY:int	= 10;
// smoothing for video. may slow down old computers
const SMOOTHING:Boolean					= true;

// ##########################
// ############# VARIABLES
// ##########################

// flag for knowing if flv has been loaded
var bolLoaded:Boolean					= false;
// flag for volume scrubbing
var bolVolumeScrub:Boolean				= false;
// flag for progress scrubbing
var bolProgressScrub:Boolean			= false;
// holds the last used volume, but never 0
var intLastVolume:Number				= DEFAULT_VOLUME;
// net connection object for net stream
var ncConnection:NetConnection;
// net stream object
var nsStream:NetStream;
// object holds all meta data
var objInfo:Object;
// url to flv file
var strSource:String					= "hancock-tsr2_h480p.flv";
// timer for updating player (progress, volume...)
var tmrDisplay:Timer;

// ##########################
// ############# FUNCTIONS
// ##########################

// sets up the player
function initVideoPlayer():void {
	// hide buttons
	mcVideoControls.btnUnmute.visible	= false;
	mcVideoControls.btnPause.visible	= false;

	// set the progress/preload fill width to 1
	mcVideoControls.mcProgressFill.mcFillRed.width = 1;
	mcVideoControls.mcProgressFill.mcFillGrey.width = 1;

	// add global event listener when mouse is released
	stage.addEventListener( MouseEvent.MOUSE_UP, mouseReleased);

	// add event listeners to all buttons
	mcVideoControls.btnPause.addEventListener(MouseEvent.CLICK, pauseClicked);
	mcVideoControls.btnPlay.addEventListener(MouseEvent.CLICK, playClicked);
	mcVideoControls.btnStop.addEventListener(MouseEvent.CLICK, stopClicked);
	mcVideoControls.btnMute.addEventListener(MouseEvent.CLICK, muteClicked);
	mcVideoControls.btnUnmute.addEventListener(MouseEvent.CLICK, unmuteClicked);
	mcVideoControls.mcVolumeScrubber.btnVolumeScrubber.addEventListener(MouseEvent.MOUSE_DOWN, volumeScrubberClicked);
	mcVideoControls.mcProgressScrubber.btnProgressScrubber.addEventListener(MouseEvent.MOUSE_DOWN, progressScrubberClicked);

	// create timer for updating all visual parts of player and add
	// event listener
	tmrDisplay = new Timer(DISPLAY_TIMER_UPDATE_DELAY);
	tmrDisplay.addEventListener(TimerEvent.TIMER, updateDisplay);

	// create a new net connection, add event listener and connect
	// to null because we don't have a media server
	ncConnection = new NetConnection();
	ncConnection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
	ncConnection.connect(null);

	// create a new netstream with the net connection, add event
	// listener, set client to this for handling meta data and
	// set the buffer time to the value from the constant
	nsStream = new NetStream(ncConnection);
	nsStream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
	nsStream.client = this;
	nsStream.bufferTime = BUFFER_TIME;

	// attach net stream to video object on the stage
	vidDisplay.attachNetStream(nsStream);
	// set the smoothing value from the constant
	vidDisplay.smoothing = SMOOTHING;

	// set default volume
	mcVideoControls.mcVolumeScrubber.x = (52 * DEFAULT_VOLUME) + 341;
	mcVideoControls.mcVolumeFill.mcFillRed.width = mcVideoControls.mcVolumeScrubber.x - 394 + 52;
	setVolume(DEFAULT_VOLUME);
}

function playClicked(e:MouseEvent):void {
	// check's, if the flv has already begun
	// to download. if so, resume playback, else
	// load the file
	if(!bolLoaded) {
		nsStream.play(strSource);
		bolLoaded = true;
	}
	else{
		nsStream.resume();
	}

	// show video display
	vidDisplay.visible					= true;

	// switch play/pause visibility
	mcVideoControls.btnPause.visible	= true;
	mcVideoControls.btnPlay.visible		= false;
}

function pauseClicked(e:MouseEvent):void {
	// pause video
	nsStream.pause();

	// switch play/pause visibility
	mcVideoControls.btnPause.visible	= false;
	mcVideoControls.btnPlay.visible		= true;
}

function stopClicked(e:MouseEvent):void {
	// calls stop function
	stopVideoPlayer();
}

function muteClicked(e:MouseEvent):void {
	// set volume to 0
	setVolume(0);

	// update scrubber and fill position/width
	mcVideoControls.mcVolumeScrubber.x				= 341;
	mcVideoControls.mcVolumeFill.mcFillRed.width	= 1;
}

function unmuteClicked(e:MouseEvent):void {
	// set volume to last used value
	setVolume(intLastVolume);

	// update scrubber and fill position/width
	mcVideoControls.mcVolumeScrubber.x = (53 * intLastVolume) + 341;
	mcVideoControls.mcVolumeFill.mcFillRed.width = mcVideoControls.mcVolumeScrubber.x - 394 + 53;
}

function volumeScrubberClicked(e:MouseEvent):void {
	// set volume scrub flag to true
	bolVolumeScrub = true;

	// start drag
	mcVideoControls.mcVolumeScrubber.startDrag(false, new Rectangle(341, 19, 53, 0));
}

function progressScrubberClicked(e:MouseEvent):void {
	// set progress scrub flag to true
	bolProgressScrub = true;

	// start drag
	mcVideoControls.mcProgressScrubber.startDrag(false, new Rectangle(0, 2, 432, 0));
}

function mouseReleased(e:MouseEvent):void {
	// set progress/volume scrub to false
	bolVolumeScrub		= false;
	bolProgressScrub	= false;

	// stop all dragging actions
	mcVideoControls.mcProgressScrubber.stopDrag();
	mcVideoControls.mcVolumeScrubber.stopDrag();

	// update progress/volume fill
	mcVideoControls.mcProgressFill.mcFillRed.width	= mcVideoControls.mcProgressScrubber.x + 5;
	mcVideoControls.mcVolumeFill.mcFillRed.width	= mcVideoControls.mcVolumeScrubber.x - 394 + 53;

	// save the volume if it's greater than zero
	if((mcVideoControls.mcVolumeScrubber.x - 341) / 53 &gt; 0)
		intLastVolume = (mcVideoControls.mcVolumeScrubber.x - 341) / 53;
}

function updateDisplay(e:TimerEvent):void {
	// checks, if user is scrubbing. if so, seek in the video
	// if not, just update the position of the scrubber according
	// to the current time
	if(bolProgressScrub)
		nsStream.seek(Math.round(mcVideoControls.mcProgressScrubber.x * objInfo.duration / 432))
	else
		mcVideoControls.mcProgressScrubber.x = nsStream.time * 432 / objInfo.duration; 

	// set time and duration label
	mcVideoControls.lblTimeDuration.htmlText		= "<span style="color: #ffffff;">&#8221; + formatTime(nsStream.time) + &#8220;</span> / &#8221; + formatTime(objInfo.duration);

	// update the width from the progress bar. the grey one displays
	// the loading progress
	mcVideoControls.mcProgressFill.mcFillRed.width	= mcVideoControls.mcProgressScrubber.x + 5;
	mcVideoControls.mcProgressFill.mcFillGrey.width	= nsStream.bytesLoaded * 438 / nsStream.bytesTotal;

	// update volume and the red fill width when user is scrubbing
	if(bolVolumeScrub) {
		setVolume((mcVideoControls.mcVolumeScrubber.x - 341) / 53);
		mcVideoControls.mcVolumeFill.mcFillRed.width = mcVideoControls.mcVolumeScrubber.x - 394 + 53;
	}
}

function onMetaData(info:Object):void {
	// stores meta data in a object
	objInfo = info;

	// now we can start the timer because
	// we have all the neccesary data
	tmrDisplay.start();
}

function netStatusHandler(event:NetStatusEvent):void {
	// handles net status events
	switch (event.info.code) {
		// trace a messeage when the stream is not found
		case &#8220;NetStream.Play.StreamNotFound&#8221;:
			trace(&#8221;Stream not found: &#8221; + strSource);
		break;

		// when the video reaches its end, we stop the player
		case &#8220;NetStream.Play.Stop&#8221;:
			stopVideoPlayer();
		break;
	}
}

function stopVideoPlayer():void {
	// pause netstream, set time position to zero
	nsStream.pause();
	nsStream.seek(0);

	// in order to clear the display, we need to
	// set the visibility to false since the clear
	// function has a bug
	vidDisplay.visible			= false;

	// switch play/pause button visibility
	mcVideoControls.btnPause.visible	= false;
	mcVideoControls.btnPlay.visible		= true;
}

function setVolume(intVolume:Number = 0):void {
	// create soundtransform object with the volume from
	// the parameter
	var sndTransform	= new SoundTransform(intVolume);
	// assign object to netstream sound transform object
	nsStream.soundTransform	= sndTransform;
	// hides/shows mute and unmute button according to the
	// volume
	if(intVolume &gt; 0) {
		mcVideoControls.btnMute.visible		= true;
		mcVideoControls.btnUnmute.visible	= false;
	} else {
		mcVideoControls.btnMute.visible		= false;
		mcVideoControls.btnUnmute.visible	= true;
	}
}

function formatTime(t:int):String {
	// returns the minutes and seconds with leading zeros
	// for example: 70 returns 01:10
	var s:int = Math.round(t);
	var m:int = 0;
	if (s &gt; 0) {
		while (s &gt; 59) {
			m++;
			s -= 60;
		}
		return String((m &lt; 10 ? &#8220;0&#8243; : &#8220;&#8221;) + m + &#8220;:&#8221; + (s &lt; 10 ? &#8220;0&#8243; : &#8220;&#8221;) + s);
	} else {
		return &#8220;00:00&#8243;;
	}
}

// ##########################
// ############# INIT PLAYER
// ##########################
initVideoPlayer();</pre>
<p class="addtoany_share_save_container">
    <a class="a2a_dd addtoany_share_save" onmouseover="a2a_show_dropdown(this)" onmouseout="a2a_onMouseOut_delay()" href="http://www.addtoany.com/share_save?sitename=The%20Tech%20Labs&amp;siteurl=http%3A%2F%2Fwww.thetechlabs.com%2F&amp;linkname=How%20to%20build%20a%20AS3%20Videoplayer&amp;linkurl=http%3A%2F%2Fwww.thetechlabs.com%2Fvideo%2Fhow-to-build-a-as3-videoplayer%2F" onclick="javascript:pageTracker._trackPageview('oclicks/http://www.addtoany.com/share_save?sitename=The%20Tech%20Labs&amp;siteurl=http%3A%2F%2Fwww.thetechlabs.com%2F&amp;linkname=How%20to%20build%20a%20AS3%20Videoplayer&amp;linkurl=http%3A%2F%2Fwww.thetechlabs.com%2Fvideo%2Fhow-to-build-a-as3-videoplayer%2F');"><img src="http://www.thetechlabs.com/wp-content/plugins/add-to-any/share_save_171_16.gif" width="171" height="16" alt="Share/Save/Bookmark"/></a>

	</p><img src="http://feedproxy.google.com/~r/TheTechLabs_video/~4/OWNpvZJciPE" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thetechlabs.com/video/how-to-build-a-as3-videoplayer/feed/</wfw:commentRss>
		<feedburner:origLink>http://www.thetechlabs.com/video/how-to-build-a-as3-videoplayer/</feedburner:origLink></item>
		<item>
		<title>Creating A Downloader For YouTube with Flex/Air</title>
		<link>http://feedproxy.google.com/~r/TheTechLabs_video/~3/ESHudCfZYhQ/</link>
		<comments>http://www.thetechlabs.com/video/creating-a-downloader-for-youtube-with-flexair-2/#comments</comments>
		<pubDate>Thu, 12 Jun 2008 10:58:17 +0000</pubDate>
		<dc:creator>Jack Herrington</dc:creator>
		
		<category><![CDATA[AIR]]></category>

		<category><![CDATA[Flex]]></category>

		<category><![CDATA[Interfaces]]></category>

		<category><![CDATA[Video]]></category>

		<category><![CDATA[adobe air]]></category>

		<category><![CDATA[adobe flex]]></category>

		<category><![CDATA[how-to]]></category>

		<category><![CDATA[tutorial]]></category>

		<category><![CDATA[YouTube]]></category>

		<guid isPermaLink="false">http://www.thetechlabs.com/?p=61</guid>
		<description><![CDATA[<p>YouTube’s mixed easy movie access with community uploads to create a startling new service. The online problem is that you can only access YouTube when you are online. How can you access those movies when you are offline? Let’s solve that problem by building a downloader with Flex and AIR.<br />
In this article we will build a cross platform application that searches for YouTube videos and then provides a mechanism to download those videos and view them locally. You will be able to take your favorite YouTube videos with you wherever you go.</p>
<small><em>posted in <a href="http://www.thetechlabs.com/category/air/">AIR</a> by Jack Herrington <a href="http://www.thetechlabs.com/video/creating-a-downloader-for-youtube-with-flexair-2/#comments">Leave A Comment</a><br />&copy;2008 <a href="http://www.thetechlabs.com">The Tech Labs</a>. All Rights Reserved.</em></small>]]></description>
			<content:encoded><![CDATA[<p>YouTube’s mixed easy movie access with community uploads to create a startling new service. The online problem is that you can only access YouTube when you are online. How can you access those movies when you are offline? Let’s solve that problem by building a downloader with Flex and AIR.<br />
In this article we will build a cross platform application that searches for YouTube videos and then provides a mechanism to download those videos and view them locally. You will be able to take your favorite YouTube videos with you wherever you go.</p>
<h3>Requirements</h3>
<p><strong>Flex 3</strong> <a title="Try/Buy" href="http://www.adobe.com/products/flex/?promoid=BPDEQ" onclick="javascript:pageTracker._trackPageview('oclicks/http://www.adobe.com/products/flex/?promoid=BPDEQ');" target="_blank"></a></p>
<p><a title="Try/Buy" href="http://www.adobe.com/products/flex/?promoid=BPDEQ" onclick="javascript:pageTracker._trackPageview('oclicks/http://www.adobe.com/products/flex/?promoid=BPDEQ');" target="_blank">Try/Buy</a></p>
<p><strong>Sample files:</strong></p>
<p><a title="Download Sample Files" href="http://thetechlabs.com/tutorials/files/youtube/YouTube.zip" onclick="javascript:pageTracker._trackPageview('downloads/tutorials/files/youtube/YouTube.zip');">YouTube.zip</a><br />
Let’s start by building the search user interface.</p>
<h3>Searching YouTube</h3>
<p>YouTube provides a set of RSS feeds that keep you up to date with the latest videos. These feeds take lots of parameters to refine what you are looking for, one of those is an arbitrary keyword search.<br />
The Flex code below uses the YouTube search feed to request a set of videos based on the user’s search criteria. The code then uses the e4x language extensions in ActionScript 3 to parse the feed and present the video thumbnails in a TileList.</p>
<pre>&lt;?xml version="1.0" encoding="utf-8"?&gt; 

  &lt;mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"  title="YouTube  Search"&gt; 

  &lt;mx:Script&gt; 

  &lt;![CDATA[ 

  <strong>import</strong>
 mx.rpc.events.ResultEvent; 

  <strong>import</strong>
 mx.rpc.http.HTTPService; 

<strong>namespace</strong>
 atom = <strong>"http://www.w3.org/2005/Atom"</strong>
; 

    <strong>namespace</strong>
 media = <strong>"http://search.yahoo.com/mrss/"</strong>
; 

<strong>private</strong>
 <strong>function</strong>
 onSearch() : <strong>void</strong>
 { 

    <strong>var</strong>
 search:HTTPService = <strong>new</strong>
 HTTPService(); 

    search.url = <strong>"http://gdata.youtube.com/feeds/api/videos/?vq="</strong>
+escape(txtSearch.text)+<strong>"&amp;orderby=updated"</strong>
; 

    search.resultFormat = <strong>'e4x'</strong>
; 

     search.addEventListener(ResultEvent.RESULT,onSearchResult); 

    search.send(); 

  } 

  <strong>private</strong>
 <strong>function</strong>
 onSearchResult( event:ResultEvent ) : <strong>void</strong>
 { 

    <strong>use</strong>
 <strong>namespace</strong>
 atom; 

    <strong>use</strong>
 <strong>namespace</strong>
 media; 

    <strong>var</strong>
 movies:Array = []; 

    <strong>for</strong>
 <strong>each</strong>
( <strong>var</strong>
 entry:XML <strong>in</strong>
 event.result..entry ) { 

      <strong>var</strong>
 group:XML = entry.group[0]; 

      movies.push( { 

         id:group.player.@url.toString(), 

         description:entry.content.toString(), 

         thumbnail:group.thumbnail[0].@url.toString() } ); 

    } 

    thumbList.dataProvider =  movies; 

  } 

  ]]&gt; 

  &lt;/mx:Script&gt; 

  &lt;mx:HBox  width=&#8221;100%&#8221;&gt; 

  &lt;mx:TextInput  width=&#8221;100%&#8221; id=&#8221;txtSearch&#8221; /&gt; 

  &lt;mx:Button  label=&#8221;Search&#8221; click=&#8221;onSearch()&#8221; /&gt; 

  &lt;/mx:HBox&gt; 

  &lt;mx:TileList  id=&#8221;thumbList&#8221; width=&#8221;100%&#8221; height=&#8221;100%&#8221;&gt; 

  &lt;mx:itemRenderer&gt; 

  &lt;mx:Component&gt; 

  &lt;mx:HBox  paddingBottom=&#8221;5&#8243; paddingLeft=&#8221;5&#8243; paddingRight=&#8221;5&#8243;  paddingTop=&#8221;5&#8243;&gt; 

  &lt;mx:Image  source =&#8221;{data.thumbnail}&#8221; height=&#8221;100&#8243; width=&#8221;150&#8243; horizontalAlign=&#8221;center&#8221; verticalAlign=&#8221;middle&#8221; toolTip=&#8221;{data.description}&#8221;&gt; 

  &lt;/mx:Image&gt; 

  &lt;/mx:HBox&gt; 

  &lt;/mx:Component&gt; 

  &lt;/mx:itemRenderer&gt; 

  &lt;/mx:TileList&gt; 

  &lt;/mx:WindowedApplication&gt;</pre>
<p>The onSearch method is called from the search button. It creates a new HTTPService on the fly with the URL of the YouTube feed for the search. It then registers onSearchResult as an event handler for the result.<br />
The onSearchResult method uses e4x to parse through each ‘entry’ tag in the RSS feed. For each movie it builds an object with three fields. The ‘id’ field holds the URL of the HTML page for the video. The ‘description’ field holds the textual description of the video. And the ‘thumbnail’ field holds the URL of the thumbnail.<br />
When I launch this MXML in an AIR project within Flex Builder 3 I see something like Figure 1.</p>
<p align="center"><img src="http://thetechlabs.com/wp-content/uploads/2008/06/fig1.png" border="0" alt="Figure 1-1. Just the YouTube search" width="540" height="380" /></p>
<p align="center"><strong>Figure 1-1. Just the YouTube search</strong></p>
<p>In this case I’ve typed in ‘super mario’ and pressed ‘search’ to get a list of the movies that matched that criteria.<br />
From here we need to add the ability to download the flash video, as well as play it back.</p>
<h3>Downloading From YouTube</h3>
<p>The user interface for the download version of the project is going to be a lot more complex than the search interface. We need to show the search results, allow for playback, and add a Save button to save the movie locally. The finished product is shown in Figure 2.</p>
<p align="center"><img src="http://thetechlabs.com/wp-content/uploads/2008/06/fig2.png" border="0" alt="" width="545" height="603" /></p>
<p align="center"><strong>Figure 1-2. The search and the downloader</strong></p>
<p>The interface is separated into two pieces, defined by panels. One panel is for searching and the other panel is shows the downloaded movies. The vertical divider allows you to scale the size of each of these panels to your preferred size.<br />
The search section is largely the same though we will add a progress indicator (invisible in the figure) next to the search button. That progress indicator will be used during the downloads of the movies since those tend to be fairly big files.<br />
The downloaded movies panel has the list of downloaded movies on the left, and the movie player on the right. As well as a Save button that is only visible if a movie is selected. The Save button allows the user to copy the downloaded movie out of the temporary directory onto the desktop (or wherever they choose).<br />
The user interface portion of the MXML code for this example is shown below:</p>
<pre>&lt;?xml version="1.0" encoding="utf-8"?&gt; 

  &lt;mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"  title="YouTube  Downloader" 

    height="700" width="600" paddingBottom="5" paddingLeft="5"  paddingRight="5" paddingTop="5" 

     creationComplete="updateLocalVideos()"&gt; 

  &lt;mx:VDividedBox width="100%" height="100%"&gt; 

&lt;mx:Panel  title="Search" paddingBottom="5" paddingLeft="5"  paddingRight="5" paddingTop="5" width="100%"  height="50%"&gt; 

  &lt;mx:HBox  width="100%"&gt; 

  &lt;mx:TextInput  width="100%" id="txtSearch" /&gt; 

  &lt;mx:Button  label="Search" click="onSearch()" /&gt; 

  &lt;mx:ProgressBar width="100" id="downloadProgress" visible="false" /&gt; 

  &lt;/mx:HBox&gt; 

  &lt;mx:TileList  id="thumbList" width="100%" height="100%" doubleClickEnabled="true" doubleClick="onThumbClick()"&gt; 

   ... 

  &lt;/mx:TileList&gt; 

  &lt;/mx:Panel&gt; 

&lt;mx:Panel  title="Downloaded  Movies"  paddingBottom="5" paddingLeft="5" paddingRight="5"  paddingTop="5" width="100%" height="50%"&gt; 

  &lt;mx:HBox  height="100%"&gt; 

  &lt;mx:List  id="localList" width="170" height="100%" doubleClickEnabled="true" doubleClick="onMovieClick()"  &gt; 

   &lt;mx:itemRenderer&gt; 

   &lt;mx:Component&gt; 

   &lt;mx:HBox paddingBottom="2" paddingLeft="2"  paddingRight="2" paddingTop="2"&gt; 

   &lt;mx:Image source="{data.thumbnail.url}" height="100"  width="140" horizontalAlign="center" verticalAlign="middle"&gt; 

   &lt;/mx:Image&gt; 

   &lt;/mx:HBox&gt; 

   &lt;/mx:Component&gt; 

   &lt;/mx:itemRenderer&gt; 

  &lt;/mx:List&gt; 

  &lt;mx:VBox  verticalAlign="middle" horizontalAlign="center" width="100%" height="100%"&gt; 

  &lt;mx:VideoDisplay id="player" width="300" height="200" backgroundColor="black" /&gt; 

  &lt;mx:Button  id="btnSave" label="Save" visible="false" click="onSave()" /&gt; 

  &lt;/mx:VBox&gt; 

  &lt;/mx:HBox&gt; 

  &lt;/mx:Panel&gt; 

  &lt;/mx:VDividedBox&gt;</pre>
<p>To keep the code sample short I’ve omit a bit of the original code from the display of the movies found during the search.<br />
Don’t be put off by the quantity of tags. The MXML is quite straightforward. Most of the tags define the ‘itemRenderer’s used by the list control to show the video thumbnails. The other elements, the panels, the video display, the save button and so on, are just single elements with a few attributes to refine them.<br />
With the interface all laid out it’s time to dig into the code. The search code remains exactly the same, but we’ve now added a thumbClick event handler which is called when the user double clicks on a thumbnail in the search panel. The thumbClick handler starts the request for the HTML page associated with the YouTube video. The onHTMLComplete method receives the HTML code for the page as text. It then calls getFLVURL to get the URL for the FLV data for the movie.<br />
The code for this is shown below:</p>
<pre>&lt;mx:Script&gt; 

  &lt;![CDATA[ 

  <strong>import</strong>
 mx.rpc.events.ResultEvent; 

  <strong>import</strong>
 mx.rpc.http.HTTPService; 

  <strong>import</strong>
 com.adobe.serialization.json.JSONDecoder; 

<strong>namespace</strong>
 atom = <strong>"http://www.w3.org/2005/Atom"</strong>
; 

    <strong>namespace</strong>
 media = <strong>"http://search.yahoo.com/mrss/"</strong>
; 

<strong>private</strong>
 <strong>function</strong>
 onSearch() : <strong>void</strong>
 { ...} 

    <strong>private</strong>
 <strong>function</strong>
 onSearchResult( event:ResultEvent ) : <strong>void</strong>
 { ... } 

<strong>public</strong>
 <strong>function</strong>
 getFLVURL( sHTML:String ) : String { 

    <strong>var</strong>
 swfArgsFound:Array = sHTML.match( <strong>/var swfArgs =(.*?);/</strong>
 ); 

    <strong>var</strong>
 swfArgsJS:JSONDecoder = <strong>new</strong>
 JSONDecoder( swfArgsFound[1] ); 

    <strong>var</strong>
 swfArgs:Object = swfArgsJS.getValue(); 

    <strong>var</strong>
 url:String = <strong>&#8216;http://youtube.com/get_video.php&#8217;</strong>
; 

    <strong>var</strong>
 first:Boolean = <strong>true</strong>
; 

    <strong>for</strong>
( <strong>var</strong>
 k:String <strong>in</strong>
 swfArgs ) { 

      <strong>if</strong>
 ( swfArgs[k] != <strong>null</strong>
 &amp;&amp; swfArgs[k].toString().length &gt;  0 ) { 

        url += first ? <strong>&#8216;?&#8217;</strong>
 : <strong>&#8216;&amp;&#8217;</strong>
; 

        first = <strong>false</strong>
; 

        url += k+<strong>&#8216;=&#8217;</strong>
+escape(swfArgs[k]);    

      } 

     } 

    <strong>return</strong>
 url; 

  } 

<strong>private</strong>
 <strong>function</strong>
 onHTMLComplete( movie:Object, event:Event )  : <strong>void</strong>
 { 

    <strong>var</strong>
 loader:URLLoader = event.target <strong>as</strong>
 URLLoader; 

    <strong>var</strong>
 movieID:String = movie.id.split( /=/ )[1]; 

    <strong>var</strong>
 flvStream:URLStream = startRequest( movieID+<strong>&#8216;.flv&#8217;</strong>
,  getFLVURL( loader.data ) ); 

    startRequest( movieID+<strong>&#8216;.jpg&#8217;</strong>
,  movie.thumbnail ); 

   flvStream.addEventListener(Event.COMPLETE,<strong>function</strong>
(event:Event):<strong>void</strong>
 { 

      downloadProgress.visible = <strong>false</strong>
; 

      updateLocalVideos(); 

    } ); 

    downloadProgress.source =  flvStream; 

    downloadProgress.visible = <strong>true</strong>
; 

  } 

<strong>private</strong>
 <strong>function</strong>
 onThumbClick() : <strong>void</strong>
 { 

    <strong>var</strong>
 htmlLoader:URLLoader = <strong>new</strong>
 URLLoader(); 

     htmlLoader.addEventListener(Event.COMPLETE, 

      <strong>function</strong>
( event:Event ) : <strong>void</strong>
 { onHTMLComplete( thumbList.selectedItem,  event ); } ); 

    htmlLoader.load(<strong>new</strong>
 URLRequest(thumbList.selectedItem.id)); 

  }</pre>
<p>Let me step back for a second and talk briefly about Flash Video. YouTube, like any other site that uses a Flash player to show videos, uses FLV as the movie format. What YouTube does is provide their Flash video application with enough data for it to construct a ‘source’ URL for it’s video display object. That ‘source’ URL is get_video.php along with a set of parameters. Those parameters are stored in a Javascript variable called ‘swfVars’ which is embedded in the page.<br />
The GetFLVURL takes the Javascript from the page and extracts the ‘swfVars’. It then uses the JSONDecode class provided by the as3corelib (http://code.google.com/p/as3corelib/) to decode the Javascript into an ActionScript object. From there it construct the FLV URL from the parameters in the ActionScript object.<br />
The weakness in this example application is that it uses this ‘screen scraping’ technique to get to the FLV URL. Unfortunately there is no easier way to do it. If the format of the YouTube HTML changes, then this code might need to be rewritten to compensate for the changes.<br />
Once the FLV URL is constructed the onHTMLComplete method calls startRequest on both the thumbnail and the FLV to download the data and store it locally. The code for this is shown below:</p>
<pre><strong>private</strong>
 <strong>function</strong>
 onReqComplete( fileName:String, event:Event  ) : <strong>void</strong>
 { 

    <strong>var</strong>
 stream:URLStream = event.target <strong>as</strong>
 URLStream; 

    <strong>var</strong>
 byteLength:int = stream.bytesAvailable; 

    <strong>var</strong>
 bytes:ByteArray = <strong>new</strong>
 ByteArray(); 

    stream.readBytes( bytes, 0, byteLength  ); 

    stream.close(); 

  <strong>if</strong>
 ( File.applicationStorageDirectory.exists == <strong>false</strong>
 ) 

        File.applicationStorageDirectory.createDirectory(); 

    <strong>var</strong>
 f:File = <strong>new</strong>
 File(  File.applicationStorageDirectory.nativePath + File.separator + fileName ); 

    <strong>var</strong>
 fs:FileStream = <strong>new</strong>
 FileStream(); 

    fs.open( f, FileMode.WRITE ); 

    fs.writeBytes( bytes, 0,  byteLength ); 

    fs.close(); 

  } 

  <strong>private</strong>
 <strong>function</strong>
 startRequest( fileName:String, url:String )  : URLStream { 

    <strong>var</strong>
 req:URLStream = <strong>new</strong>
 URLStream(); 

    req.addEventListener(Event.COMPLETE, <strong>function</strong>
 ( event:Event ) : <strong>void</strong>
 { onReqComplete( fileName, event ); } ); 

    req.load( <strong>new</strong>
 URLRequest( url ) ); 

    <strong>return</strong>
 req; 

  }</pre>
<p>The startRequest builds a URLStream object to get the data for the given URL. It then sets up onReqComplete as an event handler for when the download is complete. The onReqComplete uses the AIR File API to store the data, which is read directly into an AIR ByteArray class, into a file stored in the application storage directory. The application storage directory is maintained by AIR automatically for you.<br />
Once the files are downloaded the updateLocalVideos method is called. This method, shown below, updates the list of local videos in the downloaded videos panel.</p>
<pre><strong>private</strong>
 <strong>function</strong>
 updateLocalVideos() : <strong>void</strong>
 { 

    <strong>var</strong>
 fileNames:Object = <strong>new</strong>
 Object(); 

    <strong>for</strong>
 <strong>each</strong>
 ( <strong>var</strong>
 file:File <strong>in</strong>
 File.applicationStorageDirectory.getDirectoryListing() ) { 

      <strong>var</strong>
 fName:String = file.name.split( <strong>/[.]/</strong>
 )[0]; 

      fileNames[ fName ] = <strong>true</strong>
; 

    } 

    <strong>var</strong>
 movieList:Array = []; 

    <strong>for</strong>
( <strong>var</strong>
 fileKey:String <strong>in</strong>
 fileNames ) { 

      <strong>var</strong>
 thumb:File = <strong>new</strong>
 File(  File.applicationStorageDirectory.nativePath + File.separator + fileKey + <strong>&#8216;.jpg&#8217;</strong>
 ); 

      <strong>var</strong>
 movie:File = <strong>new</strong>
 File(  File.applicationStorageDirectory.nativePath + File.separator + fileKey + <strong>&#8216;.flv&#8217;</strong>
 ); 

      <strong>if</strong>
 ( thumb.exists &amp;&amp; movie.exists ) 

        movieList.push( {  thumbnail: thumb, movie: movie } ); 

    } 

    localList.dataProvider =  movieList; 

  }</pre>
<p>To do that the method uses the getDirectoryListing method, provided by the AIR File API, to get all of the files in the application storage directory. It then chops off the extensions and creates a list of just the file names. From there builds the list of local movies by iterating through the file names and checking to make sure that both the ‘.flv’ file for the video, and the ‘.jpg’ file for the thumbnail, are available.<br />
With the list of local videos in hand the method sets the dataProvider of the local list to the movie list that it generated.<br />
From there the final few functions handle the playback and the saving of the FLV to the desktop. These methods are shown below:</p>
<pre><strong>private</strong>
 <strong>function</strong>
 onMovieClick() : <strong>void</strong>
 { 

    player.source =  localList.selectedItem.movie.url; 

    btnSave.visible = <strong>true</strong>
; 

  } 

  <strong>private</strong>
 <strong>function</strong>
 onSave() : <strong>void</strong>
 { 

    <strong>var</strong>
 f:File = File.desktopDirectory; 

    f.addEventListener(Event.SELECT,onSaveSelect); 

    f.browseForSave(<strong>&#8220;Save FLV&#8221;</strong>
); 

  } 

  <strong>private</strong>
 <strong>function</strong>
 onSaveSelect( event:Event ) : <strong>void</strong>
 { 

    <strong>var</strong>
 f:File = event.target <strong>as</strong>
 File; 

    <strong>var</strong>
 lf:File = localList.selectedItem.movie <strong>as</strong>
 File; 

    lf.copyTo( f, <strong>true</strong>
 ); 

  } 

  ]]&gt; 

  &lt;/mx:Script&gt; 

&lt;/mx:WindowedApplication&gt;</pre>
<p>The onMovieClick method is called when the user double clicks on a movie in the local list. It just sets the source of the VideoDisplay to the url of the local ‘.flv’ file.<br />
The onSave method is called when the user clicks the Save button. It pops up a Save dialog using the AIR File API. If the user hits Save then the onSaveSelect method is called which copies the ‘.flv’ file from the local storage directory to the location they specify. You can see the interface for this in action in Figure 3.</p>
<p align="center"><img src="http://thetechlabs.com/wp-content/uploads/2008/06/fig3.png" border="0" alt="" width="491" height="253" /></p>
<p align="center"><strong>Figure 1-3. The save window</strong></p>
<p>On it’s face it seems like a lot of code, but it’s really not all that daunting when you break it down into it’s component pieces.</p>
<h3>Your Next Steps</h3>
<p>I hope you can leverage the code that I have presented in this article in your own work. There are some good reusable fragments including the file request and storage code in on ReqComplete. The JSON parsing in GetFLVURL can also come in handy when dealing with websites that lack a web services interface. Or in this case, have a web service interface that lacks the information you require.<br />
If you do make use of this code, be sure to let me know. You can contact me directly through my website; http://jackherrington.com.</p>
<p class="addtoany_share_save_container">
    <a class="a2a_dd addtoany_share_save" onmouseover="a2a_show_dropdown(this)" onmouseout="a2a_onMouseOut_delay()" href="http://www.addtoany.com/share_save?sitename=The%20Tech%20Labs&amp;siteurl=http%3A%2F%2Fwww.thetechlabs.com%2F&amp;linkname=Creating%20A%20Downloader%20For%20YouTube%20with%20Flex%2FAir&amp;linkurl=http%3A%2F%2Fwww.thetechlabs.com%2Fvideo%2Fcreating-a-downloader-for-youtube-with-flexair-2%2F" onclick="javascript:pageTracker._trackPageview('oclicks/http://www.addtoany.com/share_save?sitename=The%20Tech%20Labs&amp;siteurl=http%3A%2F%2Fwww.thetechlabs.com%2F&amp;linkname=Creating%20A%20Downloader%20For%20YouTube%20with%20Flex%2FAir&amp;linkurl=http%3A%2F%2Fwww.thetechlabs.com%2Fvideo%2Fcreating-a-downloader-for-youtube-with-flexair-2%2F');"><img src="http://www.thetechlabs.com/wp-content/plugins/add-to-any/share_save_171_16.gif" width="171" height="16" alt="Share/Save/Bookmark"/></a>

	</p><img src="http://feedproxy.google.com/~r/TheTechLabs_video/~4/ESHudCfZYhQ" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thetechlabs.com/video/creating-a-downloader-for-youtube-with-flexair-2/feed/</wfw:commentRss>
		<feedburner:origLink>http://www.thetechlabs.com/video/creating-a-downloader-for-youtube-with-flexair-2/</feedburner:origLink></item>
	</channel>
</rss>
