Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Using the YouTube Player API with ActionScript 3.0

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

Though more and more of us are developing with AS3, certain technologies remain in AS2. This forces us to find fast, simple ways to use these technologies within our projects. One such technology is YouTube.

The current YouTube player is still in AS2 and until it's updated to AS3, we have to create a wrapper to allow us to load videos into our projects. Remember, stealing the FLV feed is against YouTube's terms and conditions, so let's do it the right way using the chromed or chromeless player.

It may not look like much, but what you've got there is the YouTube Player (chromed and chromless) loaded into an AS3 application.

Before we start...

This is a tutorial for intermediate to advanced Actionscripters (but don't be intimidated if you're neither, there's no time like the present to start learning) as we're going to be using both AS2 and AS3. It's worth having a quick read of the points below:

  • It's worth checking out the documentation of the YouTube Player API. It's available on Google Code and it has a good explanation of how to use the Player API and also has examples of AS2 and AS3 implementation.
  • This tutorial is going to show how to build a set of components you can use with your AS3 projects in the future. This is much more handy than just showing you how you can bring the player into an application and then leaving you to it.

Step 1 - The Plan

The way we're going to get this to work is by creating a SWF built with AS2 that will load in the Player. We will then create a set of AS3 classes that will allow us to use the code as simple components we can add into our applications. One downside of having to use a wrapper is that as well as importing the classes we're going to make in AS3, we will also need to bring in the SWF wrapper so that we can load it through our classes.

Step 2 - Starting the Wrapper

The first thing we need to do is create our AS2 wrapper. I'm going to show you how to build the wrapper using a class, this means that you can either compile it using the Flash IDE or MTASC. So open up your favourite Actionscript 2 editor and let's begin writing the AS2 class. We're going to start by creating a YouTube Player class that we'll then use in our wrapper, so create a new file called YouTubePlayer.as and start by adding a simple class:

			class YouTubePlayer
			{
				public function YouTubePlayer()
				{

				}
			}

The plan here is to have a class that allows us to simply load in a YouTube player then easily access and change certain properties such as auto play, HD/HQ and so on. Additionally, we'll be using our class to stop and destroy the player when we're ready to load in another one. First thing we do is create some public vars that will allow us to configure the player:

		class YouTubePlayer
		{
			public var autoPlay:Boolean									= true;
			public var chromeless:Boolean								= false;
			public var pars:String										= 'autoplay=0&loop=0&rel=0&showsearch=0&hd=1';
			public var playerWidth:Number								= 425;
			public var playerHeight:Number								= 344;

			public function YouTubePlayer()
			{

			}
		}

Step 3 - Handling Sandbox and Security Errors

Since we're going to be loading in a SWF from YouTube, we need to make sure that our SWF allows the YouTube Player SWF to access certain properties within our wrapper. In order to do this, we use a function called System.security.allowDomain() within our constructor:

		class YouTubePlayer
		{
			public var autoPlay:Boolean									= true;
			public var chromeless:Boolean								= false;
			public var pars:String										= 'autoplay=0&loop=0&rel=0&showsearch=0&hd=1';
			public var playerWidth:Number								= 425;
			public var playerHeight:Number								= 344;

			public function YouTubePlayer()
			{
				System.security.allowDomain('www.youtube.com');
				System.security.allowDomain('youtube.com');
				System.security.allowDomain('s.ytimg.com');
				System.security.allowDomain('i.ytimg.com');
			}
		}

Step 4 - Preparing to Load the Player

In AS2 we can only create display objects by adding them to the stage. We'll also change our constructor and add a new private var that will allow us to pass in a movie clip as the parent into which we'll load the player:

		class YouTubePlayer
		{
			public var autoPlay:Boolean									= true;
			public var chromeless:Boolean								= false;
			public var pars:String										= 'autoplay=0&loop=0&rel=0&showsearch=0&hd=1';
			public var playerWidth:Number								= 425;
			public var playerHeight:Number								= 344;

			private var parent:MovieClip;

			public function YouTubePlayer(parent:MovieClip)
			{
				System.security.allowDomain('www.youtube.com');
				System.security.allowDomain('youtube.com');
				System.security.allowDomain('s.ytimg.com');
				System.security.allowDomain('i.ytimg.com');

				this.parent = parent;
			}
		}

Now we need to create a new private var for a movie clip loader and create a new instance of the MovieClipLoader() class; we'll be using this to load the YouTube Player. We also add a new private var called player as this will be our reference to the YouTube Player, a private var called videoId, a private var called removePlayer, a public var called addEventListener, a private var called dispatchEvent and another private var called intervals which will initialised as a new array:

		class YouTubePlayer
		{
			public var autoPlay:Boolean									= true;
			public var chromeless:Boolean								= false;
			public var pars:String										= 'autoplay=0&loop=0&rel=0&showsearch=0&hd=1';
			public var playerWidth:Number								= 425;
			public var playerHeight:Number								= 344;
			
			public var addEventListener:Function;

			private var intervals:Array									= [ ];

			private var parent:MovieClip;
			private var loader:MovieClipLoader;
			private var player:MovieClip;
			private var videoId:String;
			private var removePlayer:Boolean;
			private var dispatchEvent:Function;

			public function YouTubePlayer(parent:MovieClip)
			{
				System.security.allowDomain('www.youtube.com');
				System.security.allowDomain('youtube.com');
				System.security.allowDomain('s.ytimg.com');
				System.security.allowDomain('i.ytimg.com');

				this.parent = parent;

				loader = new MovieClipLoader();
			}
		}

So now we're ready to start building our function that we'll use to load a YouTube video. We'll pass in a string that will contain the YouTube video's Id.

Step 5 - Creating the Initialising Function

Let's create a new function called init(). This function will accept one parameter: the YouTube video's Id:

		public function init(videoId:String):Void
		{
			this.videoId = videoId;

			if ( player )
			{
				removePlayer = true;
				
				if ( player.getPlayerState() != 1 )
				{
					// destroyPlayer();
				}
				else
				{
					player.stopVideo();
				}
			} 
			else 
			{		
				loadVideo(); 
			}
		}

So the first thing we do in this function is save the video id in our class variable. Since this class will be handling the creation and destroying of the player we need to check if the player already exists before creating. If it does exist, we check if it's playing (ie the player will have a state of 1); if it is, we tell it to stop; otherwise we destroy the player. We set our class var removePlayer to true as this will be used later on in our class.

Step 6 - Loading the Player

We'll start by loading in the player, then go into destroying it. Let's create a function called loadVideo():

		private function loadVideo():Void
		{
			var url:String = ( chromeless ? 'http://www.youtube.com/apiplayer' : 'http://www.youtube.com/v/' + videoId + '&' + pars );

			player = parent.createEmptyMovieClip( 'Player' + ( new Date().getTime() ), parent.getNextHighestDepth() );

			parent._visible = false;

			loader.addListener({ onLoadInit: loadInit() });

			loader.unloadClip( player );

			loader.loadClip( url, player );
		}

The first thing we do is determine whether we're loading in the chromed or chromless player, we use the public class var chromeless to determine this. We then create a new movie clip within our parent that we'll use to put the player into. AS2 sometimes has issues with creating movie clips when they've just been removed (this movie clip will be removed before another player can be loaded), so I use a little trick that adds a timestamp to the name of the movie clip so Flash doesn't complain. I then hide the parent as we prepare to load the player.

We now need to initialise the loader, we add a listener to it, remove any reference that the loader has to our player (if we had already created it) and then we get the loader to load the YouTube player into our player movie clip.

We'll now create our loadInit() function that will be fired once the SWF has been loaded and is ready:

		private function loadInit():Void
		{
			intervals.push( setInterval( Delegate.create( this, checkPlayerLoaded ) , 500 ) );
		}

One beauty of AS3 is that most classes and components have the ability to dispatch events, allow you to add event listeners to that class. However, in AS2 this wasn't as popular. The same stands with the YouTube Player, so therefore we need to run a function at intervals so we can check when the YouTube Player is ready.

You'll notice that I've used a function called Delegate.create(). You'll need to import this from mx.utils.Delegate and it's quite important. The Delegate class allows you to load a function that's within a certain scope. You see in AS2 when you fire a function (such as through a timeout, interval or onRelease) it is fired from the scope of the caller. If a movie clip has an onRelease function and within that function it calls another function, AS2 won't look for that function within the class, rather within the movie clip. You therefore need to use a Delegate to tell AS2 where to look for this function. Fun.

We now create the function checkPlayerLoaded() which will check to see if the YouTube Player is ready:

		private function checkPlayerLoaded():Void
		{
			if ( player.isPlayerLoaded() )
			{			
				if ( chromeless )
				{
					player.loadVideoById( videoId );
				}

				player.addEventListener( 'onStateChange', Delegate.create( this, onPlayerStateChanged ) ); 
				player.addEventListener( 'onError', Delegate.create( this, onPlayerError ) );

				resizePlayer( playerWidth, playerHeight );

				parent._visible = true;

				for ( var i:Number = 0; i < intervals.length; i++ )
				{
					clearInterval( intervals[ i ] );
				}

				onPlayerReady();
			}
		}

The first thing we do is check that the player is loaded. We use the YouTube Player's isPlayerLoaded() to check if the player is ready. If it's not, the interval will just run this function again until the player is ready. Once it is, we check to see if we've loaded the chromeless player and if we have, we use the function loadVideoById() to load the video as the chromeless player has a few more functions.

We then assign some event listeners to the player as we want to know if there are any errors and if the player's state has changed. We then resize the player to our desired height and width, then show the parent, then remove all the intervals and finally run a function that declares our player is ready.

So working down, we'll first create the state change listener function onPlayerStateChanged():

		private function onPlayerStateChanged(state:Number):Void
		{
			switch ( state )
			{
				case 0:
				// ended

				if ( removePlayer )
				{
					// destroyPlayer();
				}

				break;

				case 1:
				// playing

				break;

				case 2:
				// paused

				break;

				case 3:
				// buffering

				break;

				case 4:
				// fudge knows

				break;

				case 5:
				// video queued

				break;
			}
		}

Now the next state of our class depends on whether you like to code. Since this function listens to the state of the player, some may just want to listen to ones that matter to them and create functions relating to the state. Others, like me, will dispatch events, making this a very useful component. We'll need to make some changes to the class now and import the EventDispatcher class by importing it from 'mx.events.EventDispatcher'. We then also need to add 'EventDispatcher.initialize( this )' to the constructor, so the class should look like this:

		import mx.events.EventDispatcher;
		import mx.utils.Delegate;

		class YouTubePlayer
		{
			public var autoPlay:Boolean									= true;
			public var chromeless:Boolean								= false;
			public var pars:String										= 'autoplay=0&loop=0&rel=0&showsearch=0&hd=1';
			public var playerWidth:Number								= 425;
			public var playerHeight:Number								= 344;
			
			public var addEventListener:Function;
			
			private var intervals:Array									= [ ];

			private var parent:MovieClip;
			private var loader:MovieClipLoader;
			private var player:MovieClip;
			private var videoId:String;
			private var removePlayer:Boolean;
			private var dispatchEvent:Function;

			public function YouTubePlayer(parent:MovieClip)
			{
				EventDispatcher.initialize( this );

				System.security.allowDomain( '*' );
				System.security.allowDomain( 'www.youtube.com' );
				System.security.allowDomain( 'youtube.com' );
				System.security.allowDomain( 's.ytimg.com' );
				System.security.allowDomain( 'i.ytimg.com' );

				this.parent = parent;

				loader = new MovieClipLoader();
			}

			public function init(videoId:String):Void
			{
				this.videoId = videoId;

				if ( player )
				{
					removePlayer = true;

					if ( player.getPlayerState() != 1 )
					{
						// destroyPlayer();
					}
					else
					{
						player.stopVideo();
					}
				} 
				else 
				{		
					loadVideo(); 
				}
			}

			private function loadVideo():Void
			{
				var url:String = ( chromeless ? 'http://www.youtube.com/apiplayer' : 'http://www.youtube.com/v/' + videoId + '&' + pars );

				player = parent.createEmptyMovieClip( 'Player' + ( new Date().getTime() ), parent.getNextHighestDepth() );

				parent._visible = false;

				loader.addListener({ onLoadInit: loadInit() });

				loader.unloadClip( player );

				loader.loadClip( url, player );
			}

			private function loadInit():Void
			{
				intervals.push( setInterval( Delegate.create( this, checkPlayerLoaded ) , 500 ) );
			}

			private function checkPlayerLoaded():Void
			{
				if ( player.isPlayerLoaded() )
				{			
					if ( chromeless )
					{
						player.loadVideoById( videoId );
					}

					player.addEventListener( 'onStateChange', Delegate.create( this, onPlayerStateChanged ) ); 
					player.addEventListener( 'onError', Delegate.create( this, onPlayerError ) );

					resizePlayer( playerWidth, playerHeight );

					parent._visible = true;

					for ( var i:Number = 0; i < intervals.length; i++ )
					{
						clearInterval( intervals[ i ] );
					}

					onPlayerReady();
				}
			}

			private function onPlayerStateChanged(state:Number):Void
			{
				switch ( state )
				{
					case 0:
					// ended

					if ( removePlayer )
					{
						// destroyPlayer();
					}

					break;

					case 1:
					// playing

					break;

					case 2:
					// paused

					break;

					case 3:
					// buffering

					break;

					case 5:
					// video queued

					break;
				}
			}
		}

Now we're ready to create some events.

Step 7 - Dealing With Events (in AS2)

Since we're trying to make this code as 'nice' as possible, it makes sense to store our events as static strings within a class rather than writing them by hand. Create a new file called YouTubePlayerEvents.as and use the following code for it:

		class YouTubePlayerEvent
		{
			public static var STATE_CHANGED:String						= 'onStateChange';
			public static var ERROR:String								= 'onError';
			public static var PLAYER_PLAYING:String						= 'playerPlaying';
			public static var PLAYER_ENDED:String						= 'playerEnded';
			public static var PLAYER_PAUSED:String						= 'playerPaused';
			public static var PLAYER_QUEUED:String						= 'playerQueued';
			public static var PLAYER_BUFFERING:String					= 'playerBuffering';
			public static var PLAYER_NOT_STARTED:String					= 'playerNotStarted';

			public var type:String;

			public function YouTubePlayerEvent(type:String)
			{	
				this.type = type;
			}
		}

Now that we have all the player's events in a class, the first thing we should do is update our checkPlayerLoaded() function code:

		player.addEventListener( YouTubePlayerEvent.STATE_CHANGED, Delegate.create( this, onPlayerStateChanged ) ); 
		player.addEventListener( YouTubePlayerEvent.ERROR, Delegate.create( this, onPlayerError ) );

Now we need to dispatch events every time the player's state has changed or if there's an error, so we update onPlayerStateChanged():

		private function onPlayerStateChanged(state:Number):Void
		{
			switch ( state )
			{
				case 0:
				// ended

				if ( removePlayer )
				{
					// destroyPlayer();
				}

				dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_ENDED ) );

				break;

				case 1:
				// playing

				dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_PLAYING ) );

				break;

				case 2:
				// paused

				dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_PAUSED ) );

				break;

				case 3:
				// buffering

				dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_BUFFERING ) );

				break;

				case 5:
				// video queued

				dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_QUEUED ) );

				break;
			}
		}

Before we finish, we need to add the onPlayerError() function and get that to dispatch an event:

		private function onPlayerError():Void
		{
			dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.ERROR ) );
		}

Step 8 - Resizing the Player

Now we'll create the function that handles the resizing of our player. This is handy because from time to you, you'll need to resize your player, so create a resizePlayer() and put the following inside:

		public function resizePlayer(width:Number, height:Number):Void
		{
			if ( player.isPlayerLoaded() )
			{
				player.setSize( width, height );
			}
		}

We create this function and make sure it's available publicly. This is because, once again, you may create a project that requires the player's size to be changed while it's playing or without loading another video. Again, we use YouTube Player's API function that allows us to resize the player by simply calling setSize().

Step 9 - Now the Player's Ready

Now we get on to our final creation function which is onPlayerReady(). This function will simply check to see if we want our player to autoplay and if it does, start playing. You'll notice that you can pass a variable 'autoplay' when you load the player, however, this only applies to the chromed player. I therefore found it easier just setting it to '0' and using a public boolean to set whether the player plays automatically or not:

		private function onPlayerReady():Void
		{
			if ( autoPlay )
			{
				player.playVideo();
			}
		}

Ok, so now we've got to a point where we can test the player. As you can see, I've left the function destroyPlayer() commented out so it could be tested without Flash complaining. So here's our class so far:

		import mx.events.EventDispatcher;
		import mx.utils.Delegate;
		
		import YouTubePlayerEvent;

		class YouTubePlayer
		{
			public var autoPlay:Boolean									= true;
			public var chromeless:Boolean								= false;
			public var pars:String										= 'autoplay=0&loop=0&rel=0&showsearch=0&hd=1';
			public var playerWidth:Number								= 425;
			public var playerHeight:Number								= 344;

			public var addEventListener:Function;

			private var intervals:Array									= [ ];

			private var parent:MovieClip;
			private var loader:MovieClipLoader;
			private var player:MovieClip;
			private var videoId:String;
			private var removePlayer:Boolean;
			private var dispatchEvent:Function;

			public function YouTubePlayer(parent:MovieClip)
			{
				EventDispatcher.initialize( this );
				
				System.security.allowDomain( '*' );
				System.security.allowDomain( 'www.youtube.com' );
				System.security.allowDomain( 'youtube.com' );
				System.security.allowDomain( 's.ytimg.com' );
				System.security.allowDomain( 'i.ytimg.com' );

				this.parent = parent;

				loader = new MovieClipLoader();
			}

			public function init(videoId:String):Void
			{
				this.videoId = videoId;

				if ( player )
				{
					removePlayer = true;

					if ( player.getPlayerState() != 1 )
					{
						//destroyPlayer();
					}
					else
					{
						player.stopVideo();
					}
				} 
				else 
				{		
					loadVideo(); 
				}
			}

			private function loadVideo():Void
			{
				var url:String = ( chromeless ? 'http://www.youtube.com/apiplayer' : 'http://www.youtube.com/v/' + videoId + '&' + pars );

				player = parent.createEmptyMovieClip( 'Player' + ( new Date().getTime() ), parent.getNextHighestDepth() );

				parent._visible = false;

				loader.addListener({ onLoadInit: loadInit() });

				loader.unloadClip( player );

				loader.loadClip( url, player );
			}

			private function loadInit():Void
			{
				intervals.push( setInterval( Delegate.create( this, checkPlayerLoaded ) , 500 ) );
			}

			private function checkPlayerLoaded():Void
			{
				if ( player.isPlayerLoaded() )
				{			
					if ( chromeless )
					{
						player.loadVideoById( videoId );
					}

					player.addEventListener( YouTubePlayerEvent.STATE_CHANGED, Delegate.create( this, onPlayerStateChanged ) ); 
					player.addEventListener( YouTubePlayerEvent.ERROR, Delegate.create( this, onPlayerError ) );

					resizePlayer( playerWidth, playerHeight );

					parent._visible = true;

					for ( var i:Number = 0; i < intervals.length; i++ )
					{
						clearInterval( intervals[ i ] );
					}

					onPlayerReady();
				}
			}

			private function onPlayerStateChanged(state:Number):Void
			{
				switch ( state )
				{
					case 0:
					// ended

					if ( removePlayer )
					{
						//destroyPlayer();
					}

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_ENDED ) );

					break;

					case 1:
					// playing

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_PLAYING ) );

					break;

					case 2:
					// paused

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_PAUSED ) );

					break;

					case 3:
					// buffering

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_BUFFERING ) );

					break;

					case 5:
					// video queued

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_QUEUED ) );

					break;
				}
			}

			private function onPlayerError():Void
			{
				dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.ERROR ) );
			}

			public function resizePlayer(width:Number, height:Number):Void
			{
				if ( player.isPlayerLoaded() )
				{
					player.setSize( width, height );
				}
			}

			private function onPlayerReady():Void
			{
				if ( autoPlay )
				{
					player.playVideo();
				}
			}
		}

Now using MTASC or the IDE, quickly create a class/keyframe and add this to it:

		var player = new YouTubePlayer( this );

		player.autoPlay = false;
		player.playerWidth = Stage.width;
		player.playerHeight = Stage.height;

		player.init( 'R7yfISlGLNU' );

You should see something like this:

So there you should see a nice YouTube Player with Lonely Island's "I'm on a boat" (worth a watch). You'll notice that as soon as you start playing it, it'll automatically turn on HD, nice eh?

Now that we've created our player, we now need the ability to change the video, so we need to be able to destroy the player.

Step 10 - Destroying the Player

A lot of YouTube AS3 player wrappers rely on AS3 to remove the player and add it again. However AS3 has really bad garbage collection, this means errors occur, like the sound doesn't stop playing or you can't load in a new player because the previous one hasn't been removed yet. I do this all in AS2 as it's much easier, cleaner and quicker.

So we simply need our AS2 wrapper to destroy the current player so that we can then load in a new one, so let's create the destroyPlayer() function:

		private function destroyPlayer():Void
		{	
			intervals.push( setInterval( Delegate.create( this, checkPlayerDestroyed ), 500 ) );

			removePlayer = false;
		}

Now this looks sort of the same as the loadInit() function but we're not going to start destroying the player until the interval is run, so create a new function called checkPlayerDestroyed():

		private function checkPlayerDestroyed():Void
		{
			if ( !player )
			{
				for ( var i:Number = 0; i < intervals.length; i++ )
				{
					clearInterval( intervals[ i ] );
				}

				init( videoId, true );
			}
			else
			{
				for ( var players:String in parent )
				{
					parent[ players ].destroy();

					removeMovieClip( parent[ players ] );
					unloadMovie( parent[ players ] );
				}

				player = null;
			}
		}

What we do here is check if the player exists, if it does, we then go through our parent and destroy every instance of it (using YouTube Player's destroy() function). Once again, because AS2 is a bit slow at this sort of thing, we declare the player as null. Once the interval is run again and the player has been removed, we remove the intervals and we then run the init() function passing the video ID.

So now we can modify our test file we used in the first milestone and set it up with a button so that when that button is pressed another video will be loaded:

		var holder = this.createEmptyMovieClip( 'holder', this.getNextHighestDepth() );
		var button = this.createEmptyMovieClip( 'button', this.getNextHighestDepth() );
		var player = new YouTubePlayer( holder );

		with ( button )
		{
			beginFill( 0xFF0000 );
			moveTo( 0, 0 );
			lineTo( 100, 0 );
			lineTo( 100, 100 );
			lineTo( 0, 100 );
			lineTo( 0, 0 );
			endFill();
			_x = 500;
		}

		button.onRelease = function():Void
		{
			player.init( 'sGTAnXqn9Jc' );
		}

		player.autoPlay = false;
		player.playerWidth = 500;
		player.playerHeight = Stage.height;

		player.init( 'R7yfISlGLNU' );

So we have:

Step 11 - Stopping the Video

Last but by no means least, we need to be able to stop the player from playing, so we add a function called stop() to the bottom of our class, like so:

		import mx.events.EventDispatcher;
		import mx.utils.Delegate;

		import YouTubePlayerEvent;

		class YouTubePlayer
		{
			public var autoPlay:Boolean									= true;
			public var chromeless:Boolean								= false;
			public var pars:String										= 'autoplay=0&loop=0&rel=0&showsearch=0&hd=1';
			public var playerWidth:Number								= 425;
			public var playerHeight:Number								= 344;

			public var addEventListener:Function;

			private var intervals:Array									= [ ];

			private var parent:MovieClip;
			private var loader:MovieClipLoader;
			private var player:MovieClip;
			private var videoId:String;
			private var removePlayer:Boolean;
			private var dispatchEvent:Function;

			public function YouTubePlayer(parent:MovieClip)
			{
				EventDispatcher.initialize( this );

				System.security.allowDomain( '*' );
				System.security.allowDomain( 'www.youtube.com' );
				System.security.allowDomain( 'youtube.com' );
				System.security.allowDomain( 's.ytimg.com' );
				System.security.allowDomain( 'i.ytimg.com' );

				this.parent = parent;

				loader = new MovieClipLoader();
			}

			public function init(videoId:String):Void
			{
				this.videoId = videoId;

				if ( player )
				{
					removePlayer = true;

					if ( player.getPlayerState() != 1 )
					{
						destroyPlayer();
					}
					else
					{
						player.stopVideo();
					}
				} 
				else 
				{		
					loadVideo(); 
				}
			}

			private function loadVideo():Void
			{
				var url:String = ( chromeless ? 'http://www.youtube.com/apiplayer' : 'http://www.youtube.com/v/' + videoId + '&' + pars );

				player = parent.createEmptyMovieClip( 'Player' + ( new Date().getTime() ), parent.getNextHighestDepth() );

				parent._visible = false;

				loader.addListener({ onLoadInit: loadInit() });

				loader.unloadClip( player );

				loader.loadClip( url, player );
			}

			private function loadInit():Void
			{
				intervals.push( setInterval( Delegate.create( this, checkPlayerLoaded ) , 500 ) );
			}

			private function checkPlayerLoaded():Void
			{
				if ( player.isPlayerLoaded() )
				{			
					if ( chromeless )
					{
						player.loadVideoById( videoId );
					}

					player.addEventListener( YouTubePlayerEvent.STATE_CHANGED, Delegate.create( this, onPlayerStateChanged ) ); 
					player.addEventListener( YouTubePlayerEvent.ERROR, Delegate.create( this, onPlayerError ) );

					resizePlayer( playerWidth, playerHeight );

					parent._visible = true;

					for ( var i:Number = 0; i < intervals.length; i++ )
					{
						clearInterval( intervals[ i ] );
					}

					onPlayerReady();
				}
			}

			private function onPlayerStateChanged(state:Number):Void
			{
				switch ( state )
				{
					case 0:
					// ended

					if ( removePlayer )
					{
						destroyPlayer();
					}

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_ENDED ) );

					break;

					case 1:
					// playing

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_PLAYING ) );

					break;

					case 2:
					// paused

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_PAUSED ) );

					break;

					case 3:
					// buffering

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_BUFFERING ) );

					break;

					case 5:
					// video queued

					dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.PLAYER_QUEUED ) );

					break;
				}
			}

			private function onPlayerError():Void
			{
				dispatchEvent( new YouTubePlayerEvent( YouTubePlayerEvent.ERROR ) );
			}

			public function resizePlayer(width:Number, height:Number):Void
			{
				if ( player.isPlayerLoaded() )
				{
					player.setSize( width, height );
				}
			}

			private function onPlayerReady():Void
			{
				if ( autoPlay )
				{
					player.playVideo();
				}
			}

			private function destroyPlayer():Void
			{	
				intervals.push( setInterval( Delegate.create( this, checkPlayerDestroyed ), 500 ) );

				removePlayer = false;
			}

			private function checkPlayerDestroyed():Void
			{
				if ( !player )
				{
					for ( var i:Number = 0; i < intervals.length; i++ )
					{
						clearInterval( intervals[ i ] );
					}

					init( videoId );
				}
				else
				{
					for ( var players:String in parent )
					{
						parent[ players ].destroy();

						removeMovieClip( parent[ players ] );
						unloadMovie( parent[ players ] );
					}

					player = null;
				}
			}

			public function stop():Void
			{
				removePlayer = false;
				
				player.stopVideo();
			}
		}

So there we have our YouTube Player class! Whenever you need to use the YouTube Player in AS2, you can use this! Simples.

Step 12 - Creating the Wrapper

Now that our class is ready, we just need to create the wrapper we're going to load through AS3 so we can use the player. The communication between the wrapper (built in AS2) and the AS3 code will be done through using Flash Player's 'LocalConnection'. Now instead of writing my own local connection class, I use gSkinner.com's excellent class SWFBridge. So get yourself a copy, and get ready to create one final AS2 class.

Let's create a new class file called YouTubePlayerWrapper.as and set up the class:

		class YouTubePlayerWrapper
		{
			public function YouTubePlayerWrapper(parent:MovieClip)
			{

			}
		}

So we'll begin by setting up our class's variables and constructor:

		import com.gskinner.utils.SWFBridgeAS2;

		import mx.utils.Delegate;

		import YouTubePlayer;
		import YouTubePlayerEvent;

		class YouTubePlayerWrapper
		{
			static var BRIDGE_NAME:String								= 'YouTubePlayerWrapperBridge';

			private var parent:MovieClip;
			private var player:YouTubePlayer;
			private var bridge:SWFBridgeAS2;

			public function YouTubePlayerWrapper(parent:MovieClip)
			{
				Stage.scaleMode = 'noScale';
				Stage.align = 'TL';

				System.security.allowDomain( '*' );
				System.security.allowDomain( 'www.youtube.com' );
				System.security.allowDomain( 'youtube.com' );
				System.security.allowDomain( 's.ytimg.com' );
				System.security.allowDomain( 'i.ytimg.com' );

				this.parent = parent;

				init();
			}
		}

The first var we create is a static reference to the local connection bridge name. We then create private vars that reference the parent, the player and the bridge we're going to create. Since this wrapper will be used to create any size player, we need to set the stage's scale mode and align so that resize is simple and won't stretch the player. Again, we need to add the system security settings so that Flash Player doesn't complain. Finally we add the class's parent reference and then run the init() function.

Now we'll create our init() function:

		private function init():Void
		{
			var playerHolder:MovieClip = parent.createEmptyMovieClip( 'playerHolder', parent.getNextHighestDepth() );

			bridge = new SWFBridgeAS2( BRIDGE_NAME, this );

			bridge.addEventListener( 'connect', this );

			player = new YouTubePlayer( playerHolder );

			player.addEventListener( YouTubePlayerEvent.PLAYER_ENDED, Delegate.create( this, sendEvent ) );
			player.addEventListener( YouTubePlayerEvent.PLAYER_PLAYING, Delegate.create( this, sendEvent ) );
			player.addEventListener( YouTubePlayerEvent.PLAYER_PAUSED, Delegate.create( this, sendEvent ) );
			player.addEventListener( YouTubePlayerEvent.PLAYER_BUFFERING, Delegate.create( this, sendEvent ) );
			player.addEventListener( YouTubePlayerEvent.PLAYER_QUEUED, Delegate.create( this, sendEvent ) );
			player.addEventListener( YouTubePlayerEvent.PLAYER_NOT_STARTED, Delegate.create( this, sendEvent ) );
		}

Here's just a quick run down of the function: we create a little holder for our player, we then initialise the bridge with the first variable being the bridge's name and the second the scope of the functions the bridge can launch, that's this class. We then initialise the player and apply event listeners to it. This is where the events dispatched by the player are handy as we're then able to bubble these events up to our AS3 code.

Now we create the sendEvent() function that will pass the events up to our AS3 code:

		private function sendEvent(e:YouTubePlayerEvent):Void
		{	
			bridge.send( 'sendEvent', e.type );
		}

Now we need to add the functions we will need to call from the AS3 code, so we add a playVideo(), resizePlayer() and stopVideo():

		public function playVideo(videoId:String, playerWidth:Number, playerHeight:Number, autoPlay:Boolean, pars:String, chromeless:Boolean):Void
		{		
			player.autoPlay = ( autoPlay !== false ? true : false );
			player.chromeless = ( chromeless ? chromeless : false );
			player.pars = ( pars ? pars : player.pars );
			player.playerWidth = ( playerWidth ? playerWidth : player.playerWidth );
			player.playerHeight = ( playerHeight ? playerHeight : player.playerHeight );

			player.init( videoId );
		}

		public function resizePlayer(width:Number, height:Number):Void
		{
			player.resizePlayer( width, height );
		}

		public function stopVideo():Void
		{
			player.stop();
		}

As you can see, we're allowing our wrapper to pass all the public vars from AS3 to our wrapper and to also resize the player when it's needed.

It appears that our wrapper is ready, so just like we did with our milestone tests, we need to compile this wrapper into a SWF. Let's do just that and call it 'YouTubePlayerWrapper.swf'. The wrapper class should be like this:

		import com.gskinner.utils.SWFBridgeAS2;

		import mx.utils.Delegate;

		import YouTubePlayer;
		import YouTubePlayerEvent;

		class YouTubePlayerWrapper
		{
			static var BRIDGE_NAME:String								= 'YouTubePlayerWrapperBridge';

			private var parent:MovieClip;
			private var player:YouTubePlayer;
			private var bridge:SWFBridgeAS2;

			public function YouTubePlayerWrapper(parent:MovieClip)
			{
				Stage.scaleMode = 'noScale';
				Stage.align = 'TL';

				System.security.allowDomain( '*' );
				System.security.allowDomain( 'www.youtube.com' );
				System.security.allowDomain( 'youtube.com' );
				System.security.allowDomain( 's.ytimg.com' );
				System.security.allowDomain( 'i.ytimg.com' );

				this.parent = parent;

				init();
			}

			private function init():Void
			{
				var playerHolder:MovieClip = parent.createEmptyMovieClip( 'playerHolder', parent.getNextHighestDepth() );

				bridge = new SWFBridgeAS2( BRIDGE_NAME, this );

				bridge.addEventListener( 'connect', this );

				player = new YouTubePlayer( playerHolder );

				player.addEventListener( YouTubePlayerEvent.PLAYER_ENDED, Delegate.create( this, sendEvent ) );
				player.addEventListener( YouTubePlayerEvent.PLAYER_PLAYING, Delegate.create( this, sendEvent ) );
				player.addEventListener( YouTubePlayerEvent.PLAYER_PAUSED, Delegate.create( this, sendEvent ) );
				player.addEventListener( YouTubePlayerEvent.PLAYER_BUFFERING, Delegate.create( this, sendEvent ) );
				player.addEventListener( YouTubePlayerEvent.PLAYER_QUEUED, Delegate.create( this, sendEvent ) );
				player.addEventListener( YouTubePlayerEvent.PLAYER_NOT_STARTED, Delegate.create( this, sendEvent ) );
			}

			private function sendEvent(e:YouTubePlayerEvent):Void
			{	
				bridge.send( 'sendEvent', e.type );
			}

			public function playVideo(videoId:String, playerWidth:Number, playerHeight:Number, autoPlay:Boolean, pars:String, chromeless:Boolean):Void
			{		
				player.autoPlay = ( autoPlay !== false ? true : false );
				player.chromeless = ( chromeless ? chromeless : false );
				player.pars = ( pars ? pars : player.pars );
				player.playerWidth = ( playerWidth ? playerWidth : player.playerWidth );
				player.playerHeight = ( playerHeight ? playerHeight : player.playerHeight );

				player.init( videoId );
			}

			public function resizePlayer(width:Number, height:Number):Void
			{
				player.resizePlayer( width, height );
			}

			public function stopVideo():Void
			{
				player.stop();
			}
		}

Step 13 - Using the Wrapper

We're on the home straight now! So, to simply test this bad boy out, we just need to create a little class in AS3 to use this wrapper. I'd create a new project in your favourite AS3 editor and get a copy of your 'YouTubePlayerWrapper.swf' into your AS3 project folder. So then we simply create a class that loads the SWF, creates a connection and then sends vars for the playVideo() function. Don't forget to import gSkinner.com SWFBridge into the AS3 code:

		package 
		{
			import com.gskinner.utils.SWFBridgeAS3;

			import flash.display.Loader;
			import flash.display.Sprite;
			import flash.events.Event;
			import flash.net.URLRequest;

			[SWF( width='600', height='400', frameRate='30', backgroundColor='#000000' )]

			public class App extends Sprite
			{
				public static const BRIDGE_NAME:String			= 'YouTubePlayerWrapperBridge';

				private var player:Loader;
				private var bridge:SWFBridgeAS3;
				private var videoId:String;
				private var loaded:Boolean;

				public function App()
				{
					init( 'f8nNOQOh7dc' );
				}

				public function init(videoId:String):void
				{
					this.videoId = videoId;

					if ( !loaded )
					{
						var request:URLRequest = new URLRequest( 'assets/swf/YouTubePlayerWrapper.swf' );

						player = new Loader();

						addChild( player );

						player.contentLoaderInfo.addEventListener( Event.INIT, handlePlayerLoadedComplete );

						player.load( request );
					}
					else
					{
						handlePlayerLoadedComplete();
					}
				}	

				public function play(videoId:String):void
				{
					bridge.send( 'playVideo', videoId, stage.stageWidth, stage.stageHeight, false, null, false );
				}

				public function stop():void
				{
					bridge.send( 'stopVideo' );
				}

				public function handlePlayerLoadedComplete(e:Event=null):void
				{						
					if ( bridge )
					{
						handleBridgeConnect(); 
					}
					else
					{
						bridge = new SWFBridgeAS3( BRIDGE_NAME, this );

						bridge.addEventListener( Event.CONNECT, handleBridgeConnect );				
					}
				}

				public function sendEvent(e:String):void
				{
					trace( e );
				}

				private function handleBridgeConnect(e:Event=null):void
				{			
					loaded = true;

					play( videoId );
				}	
			}
		}

This is code that you should understand, but just a quick run through: the main function that loads the player is called init(), I've put this in the constructor so that it actually loads a video rather than waiting for it to be fired. The init() function places the video ID within the class's variable, it then checks to see if the player has already been loaded. If it has, it then continues to send a command via the connection, otherwise it just loads it using Flash's standard Loader. Once the player has loaded and initialised, we then create the connection and once that's ready, pass the initial video ID to the player. There we have our YouTube Player available as an AS3 component!

Conclusion

So there we have our AS3 YouTube Player Component, but the fun shouldn't stop there. Remember that the AS2 wrapper passes all events up to the local connection, I've left the sendEvent() to just trace the event as you can now customise it to do your own doing. I would, for example, get it to dispatch events and that would help a lot when it comes to coding properly within AS3. Enjoy.

Advertisement