Nov 06

The Problem
For many developers, this can be a no-brainer problem to start with. Most (if not all ) mashups uses videos from YouTube. There are tons of resources online that teach us how to “scrap” the FLV of youtube videos so we can play it in our own applications.

Going a few months back, YouTube had already released the chromeless player , a plain player without controls for playing youtube videos. However, there is no immediately “need” for developers to change their player to suit the chromeless player. Afterall, “if it ain’t broken, don’t fix it”. Last week however, YouYube removed some parameters in their APIs calls, effectively making some “scrapping” process not working. There are still some workarounds to get the FLV of their videos, but even if developers can “hack” to make it work, it is still against YouTube TOS. For many developers, the most straight forward solution is, well, use the chromeless player.

Chromeless Player is made in AS2 but I code in AS3
Now that the solution is pointing to Chromless Player, with the coherent and straightforward YouTube documentations, developers can easily fit the Chromeless Player into their application. For AS2 developers, it is a task of merely loading the SWF and passing commands to it. For AS3 developers, the problem arise as AS3 swfs cannot communicate with AS2 swfs directly. Thus, AS3 developers can load the Chromeless Player, but they cannot effectively communicate with it to get the play time, stop the video, pause the video etc.

AS2 Wrapper , LocalConnection to AS3
After searching around on google, many are facing this problem and the solution points down to using localconnection. Matthew Encinas posted this solution here, which works great. It involves creating an AS2 wrapper, which connects to a localconnection instance. The AS3 wrapper then load the AS2 wrapper, which in turns loads the Chromeless Player. To make things easier to understand, I have created a diagram below:

Codes from Matthew Encinas: http://groups.google.com/group/youtube-api-gdata/browse_thread/thread/3c98068961296b38/07ecb5ee1168cefb?lnk=gst&q=as3#07ecb5ee1168cefb

Help! LocalConnection naming problem!
The problem with this approach is that you are using a fixed localconnection name for your swf. If your player is a video player, which can have multiple instances at the same time, you will receive an error that the localconnection is already in used. To solve this problem, append something like ?key= when loading the AS2 Wrapper, and using that to name your localconnection name

Sample Classes
Modifying Matthew’s code, with the localconnection fix gives us the following 2 piece of codes:

AS2 Codes (the youtubewrapper.swf)

System.security.allowDomain('www.youtube.com');
System.security.allowDomain('gdata.youtube.com');
System.security.allowInsecureDomain('gdata.youtube.com');
System.security.allowInsecureDomain('www.youtube.com');
var loadInterval:Number;
var ytplayer:MovieClip = this.createEmptyMovieClip("ytplayer",
this.getNextHighestDepth());
 var key   = _root.key;
 
var swf:String = "http://www.youtube.com/apiplayer";
//This created a connection for AS3 to talk to it
var _as3_to_as2:LocalConnection = new LocalConnection();
_as3_to_as2.connect("AS3_to_AS2_" + key);
//This is to connect to a connection started by the AS3 file
var _as2_to_as3:LocalConnection = new LocalConnection();
ytPlayerLoaderListener = {};
ytPlayerLoaderListener.onLoadInit = function() {
    loadInterval = setInterval(checkPlayerLoaded, 250);
} 
 
function checkPlayerLoaded():Void {
    if (ytplayer.isPlayerLoaded()) {
                //Let the AS3 file know that the player is loaded
                // the function onSwfLoadComplete exists within the AS3 file
                _as2_to_as3.send("AS2_to_AS3_" + key, "onSwfLoadComplete");
                clearInterval(loadInterval); // IMPORTANT - kill the interval
                ytplayer.addEventListener("onStateChange", onPlayerStateChange);
                ytplayer.addEventListener("onError", onPlayerError); 
 
        }
} 
 
function onPlayerStateChange(newState:Number) {
	 // Possible values are unstarted (-1), ended (0), playing (1), paused (2), buffering (3), video cued (5)
 
	  _as2_to_as3.send("AS2_to_AS3_" + key, "onPlayerStateChange", newState); 
 
	 switch(newState)
	 {
			case -1:
		 	case "unstarted":
			break;
			case 0:
 
			case "ended":
			clearInterval(playInt)
			break;
 
			case 1:
			case "playing":
		playInt = setInterval(onPlay, 1000)
 
			break;
			case 2:
			case "paused":
			break
			case 3:
			case "buffering":
			 clearInterval(playInt)
			break
			case 5:
			case "video cued":
			break;
	 }
 
} 
 
function sendCurrentTime():Void
{
	var current = ytplayer.getCurrentTime()
 
	var duration = ytplayer.getDuration() 
 
_as2_to_as3.send("AS2_to_AS3_" + key, "onDuration", current, duration); 
 
}
function onPlay()
{
 
	sendCurrentTime();
}
function onPlayerError(errorCode:Number) {
    //do something on player error
} 
 
var ytPlayerLoader:MovieClipLoader = new MovieClipLoader();
ytPlayerLoader.addListener(ytPlayerLoaderListener);
ytPlayerLoader.loadClip(swf, ytplayer);
//////// CREATE EVENT HANDLERS
// These functions can be called from within the AS3 file
// This is the window where AS3 will access the API
// You can add any of the Javascript Api here
_as3_to_as2.pauseVideo = function(){
    ytplayer.pauseVideo();
}; 
 
_as3_to_as2.playVideo = function(){
    ytplayer.playVideo();
}; 
 
_as3_to_as2.stopVideo = function(){
    ytplayer.stopVideo();
}; 
 
_as3_to_as2.loadVideoById = function(id){
        ytplayer.loadVideoById(id, 0);
}; 
 
//setSize(width:Number, height:Number):Void
_as3_to_as2.setSize  = function(swidth:Number, sheight:Number):Void
{
	ytplayer.setSize(swidth, sheight)
}
 
_as3_to_as2.cueVideoById = function(video_id:String, second:Number):Void
{
	ytplayer.cueVideoById(video_id,  second)
}
 
function doDispatchEvent ()
{
 
}

AS3 Codes

package com.mypackage.view
{ 
 
import flash.display.Loader;
        import flash.events.Event;
        import flash.events.StatusEvent;
        import flash.net.LocalConnection;
        import flash.net.URLRequest;
        import flash.system.Security;
 
import mx.core.Application;
        import mx.core.UIComponent;
        import mx.events.FlexEvent;
        [Event(name="init", type="flash.events.Event")]
        public class YouTube extends  UIComponent
        {
                private var _loader:Loader;
                private var _as3_to_as2:LocalConnection;
                private var _as2_to_as3:LocalConnection;
                public function YouTube(){
 
Application.application.addEventListener(FlexEvent.APPLICATION_COMPLETE, init)
                }
 
public function init(e:FlexEvent){
                        Security.allowDomain('www.youtube.com');
                        Security.allowDomain('gdata.youtube.com');
                        Security.allowInsecureDomain('gdata.youtube.com');
                        Security.allowInsecureDomain('www.youtube.com');
                        _as3_to_as2 = new LocalConnection();
                        _as3_to_as2.addEventListener(StatusEvent.STATUS,
onLocalConnectionStatusChange);
                        _as2_to_as3 = new LocalConnection();
                        _as2_to_as3.addEventListener(StatusEvent.STATUS,
onLocalConnectionStatusChange);
                        _as2_to_as3.client = this;
                        //This enables the local connection to
//use functions of this class
 
_as2_to_as3.connect("AS2_to_AS3_" + key);
                        _loader = new Loader();
                        _loader.contentLoaderInfo.addEventListener(Event.INIT, onWrapperInit)
                        _loader.load(new URLRequest("youtubewrapper.swf?key=" + key));
                }
               private var key :String = generateKey()
                private function onWrapperInit(e:Event):void
                {
 
}
                private function generateKey():String
                {
                	return this.generationCharCode()
                }
 
protected function generationCharCode():String
 		{
 			var code:String = ""
 			while(code.length < 10)
 			{
 				var cc :uint = Math.floor(Math.random() * 1000 ) % 123
 				if(cc < 48 || (cc >= 58 && cc <= 64) ||  (cc >= 91 && cc <= 96))
 				{
 
continue;
 				}
 
code += String.fromCharCode(cc)
 			}
 			return code;
 
}
                public function stopVideo():void {
                	this.current = this.total = 0;
 
_as3_to_as2.send("AS3_to_AS2_" + key, "stopVideo");
                }
                public function playVideo():void {
                        _as3_to_as2.send("AS3_to_AS2_" + key, "playVideo");
                }
                public function pauseVideo():void {
                        _as3_to_as2.send("AS3_to_AS2_" + key, "pauseVideo");
                }
                public function loadVideoById(id:String):void {
                        _as3_to_as2.send("AS3_to_AS2_" + key, "loadVideoById", id);
                }
 
public function setSize(width:Number, height:Number):void
                {
                	_as3_to_as2.send("AS3_to_AS2_" + key, "setSize", width, height);
                }
                public function cueVideoById(video_id:String, seconds:Number):void
                {
                	_as3_to_as2.send("AS3_to_AS2_" + key, "cueVideoById", video_id, seconds)
 
}
 
public function onPlayerStateChange(stateName):void
                {
 
}
                public function onDuration(current, total):void
                {
 
this.current = current
                	this.total = total;
                }
 
[Bindable]    public var current:Number;
 
[Bindable]   public var total:Number;
 
public function onSwfLoadComplete():void {
                        addChild(_loader);
                        dispatchEvent(new Event(Event.INIT));
                }
 
private function onLocalConnectionStatusChange(e:StatusEvent):void{
                        // error was thrown without this handler
                }
        }
}

Happy YouTubing!