AS3 YouTube Chromeless player and search API

If you read my previous post on the AS3 Lyricsfly API and my last post AS3 YouTube Chromeless API you will know that I’m creating an online Video Karaoke application for fun.

FIRST and foremost I must thank Martin Legris for providing the search functionality. Martin has wrapped up the YouTube API including the parsing of its feeds. He’s even gone out of his way to develop a proxy using AMFPHP which supports updating or deleting:

* video responses
* comments
* ratings
* subscriptions
* profile information
* contacts
* video information.
and you can post ratings, comments, create subscriptions and add contacts. I believe he will be submitting the code base to Google so they can make the AS3 API official.
SO ONCE AGAIN…THANK YOU MARTIN!

As mentioned, this is the Part 2 to Part 1 of the YouTube AS3 API. The last post focused on using the chromeless player and introduced you to creating your very own video controls. This post is focused on searching YouTube for videos and displaying it using your own custom scroller.
In the example below I used the standard feeds provided by the YouTube API and the ability to search for video content on YouTube. I also created a custom scroller out of MovieClip elements that designers can re-use and re-design. I’m not going to post a tutorial on the subject. I’m just offering the basics.

Get Adobe Flash player

Let’s break it down!
The document class Main.as simply creates the view. It is similar to its predecessor but it has been revised to adapt to the search functionality. It also instantiates the new YouTubeSearch.as class and the PanelScroller.as class, which are the main focus of this posting. Most of the code is rudimentary, its simply sets up the flash components adds event listeners and implements methods that handle the changes. The new methods which set up the search form and handles the video data are createSearchForm() and createVideoScroller().

Main.as

/****************************
* Manuel Gonzalez           *
* design@stheory.com        *
* www.stheory.com           *
* www.codingcolor.com       *
*****************************/

package {

  import flash.display.MovieClip;
  import flash.display.Sprite;
  import flash.display.Shape;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import fl.controls.TextInput;
  import fl.controls.RadioButton;
  import fl.controls.Button;
  import fl.controls.ButtonLabelPlacement;
  import fl.controls.RadioButtonGroup;
  import com.YouTube.*;
  import com.video.*;
  import com.view.PanelScroller;

  public class Main extends Sprite {
    private var youTubePlayer:YouTubePlayer;
    private var youTubeSearch:YouTubeSearch;
    private var videoPlayerContainer:MovieClip;
    private var videoPlayerControls:VideoPlayerControls;
    private var videoPlayerWidth:Number=320;
    private var videoPlayerHeight:Number=240;
    private var defaultVideoByID:String ="0UjsXo9l6I8";
    private var currentSearchKeyword:String;
    private var videoBkg:Sprite;
    private var searchButton:Button;
    private var ti1:TextInput;
    private var searchInput:TextInput;
    private var videoScroller:PanelScroller;
    private var circleAnimation:MovieClip;
    private var searchRB:RadioButton;
    private var topRB:RadioButton;
    private var featuredRB:RadioButton;
    private var mostDiscussedRB:RadioButton;
    private var standardFeedMaxResults:Number = 25;

    /*Constructor*/
    public function Main() {
      createVideoBkg();
      setupInputFields();
      videoPlayerContainer = new MovieClip();
      videoPlayerContainer.x = 25;
      videoPlayerContainer.y = 10;
      addChild(videoPlayerContainer);
      createYouTubePlayer();
      createVideoPlayerControls();
      createSearchForm();
      createVideoScroller();
      createRadioButton();
    }
    /*
    Method:createVideoScroller
    Parameters:
    Return:
    */

    private function createVideoScroller():void {
      videoScroller = new PanelScroller();
      videoScroller.visible=false;
      videoScroller.x= searchInput.x;
      videoScroller.y = (searchInput.y + searchInput.height) + 50;
      videoScroller.addEventListener(Event.CHANGE,videoSelectedFromScroller,false,0,true);
      addChild(videoScroller);
    }
    /*
    Method:videoSelectedFromScroller
    Parameters:
    event:Event
    Returns:
    */

    private function videoSelectedFromScroller(event:Event):void {
      var videoObject = videoScroller.currentPanel;
      defaultVideoByID = videoObject.id;
      youTubePlayer.loadVideoById(videoObject.id);
      ti1.text = "Loading Video : ID: " +  defaultVideoByID;
    }
    /*
    Method:createSearchForm
    Parameters:
    Return:
    */

    private function createSearchForm():void {
      youTubeSearch = YouTubeSearch.getInstance();
      searchInput = new TextInput();
      searchInput.move(videoBkg.x,(videoBkg.y + videoBkg.height) + 50);
      searchInput.width = 200;
      addChild(searchInput);
      searchButton = new Button();
      searchButton.x = (videoBkg.x + videoBkg.width) - searchButton .width;
      searchButton.y = (videoBkg.y + videoBkg.height)+50;
      searchButton.label = "Search";
      addChild(searchButton);
      addSearchFormListeners();
    }
    /*
    Method:removeSearchFormListeners
    Parameters:
    Returns:
    */

    private function removeSearchFormListeners():void {
      searchButton.removeEventListener(MouseEvent.CLICK, startVideoSearch);
      searchButton.enabled = false;
      searchInput.removeEventListener(Event.CHANGE,updateSearchKeywords);
      searchInput.enabled = false;
    }
    /*
    Method:addSearchFormListeners
    Parameters:
    Returns:
    */

    private function addSearchFormListeners():void {
      searchButton.addEventListener(MouseEvent.CLICK, startVideoSearch);
      searchButton.enabled = true;
      searchInput.addEventListener(Event.CHANGE,updateSearchKeywords,false,0,true);
      searchInput.enabled = true;
    }
    /*
    Method: updateSearchKeywords
    Parameters:
    event:Event
    Returns:
    */

    private function updateSearchKeywords(event:Event):void {
      if (searchInput.text !="") {
        currentSearchKeyword = searchInput.text;
      }
    }
    /*
    Method: addLoadingAnimation
    Parameters:
    Returns:
    */

    private function addLoadingAnimation():void {

      circleAnimation = new CircleAnimation();
      circleAnimation.x = 185 - circleAnimation.width;
      circleAnimation.y = (videoBkg.y + videoBkg.height)+250;
      addChild(circleAnimation);
    }
    /*
    Method: removeLoadingAnimation
    Parameters:
    Returns:
    */

    private function removeLoadingAnimation():void {
      removeChild(circleAnimation);
    }
    /*
    Method: startVideoSearch
    Parameters:
    event:Event
    Returns:
    */

    private function startVideoSearch(event:Event):void {

      addLoadingAnimation();
      removeSearchFormListeners();
      videoScroller.visible=false;
      youTubeSearch.startSearch(currentSearchKeyword);
      youTubeSearch.addEventListener(YouTubeSearchEvent.ON_VIDEO_DATA_READY,refreshDataScroller,false,0,true);
    }
    /*
    Method:refreshDataScroller
    Parameters:
    event:YouTubeSearchEvent
    Returns:
    */

    private function refreshDataScroller(event:YouTubeSearchEvent):void {
      removeLoadingAnimation();
      addSearchFormListeners();
      videoScroller.visible=true;
      var dArray:Array = event.data;
      videoScroller.dataprovider = dArray;
    }
    /*
    Method: destroy
    Parameters:
    Returns:
    */

    private function destroy():void {
      videoPlayerControls.destroy();
      youTubePlayer.destroy();
      removeChild(videoPlayerContainer);
      removeChild(videoPlayerControls);
    }
    /*
    Method: createVideoBkg
    Parameters:
    Returns:
    */

    private function createVideoBkg():void {
      videoBkg = new Sprite();
      videoBkg.x = 25;
      videoBkg.y = 10;
      createRectangle(videoBkg,0x000000,videoPlayerWidth,videoPlayerHeight);
      addChild(videoBkg);
    }
    /*
    Method: createVideoPlayerControls
    Parameters:
    Returns:
    */

    private function createVideoPlayerControls():void {
      videoPlayerControls = new VideoPlayerControls();
      videoPlayerControls.visible=false;
      videoPlayerControls.videoWidth = videoPlayerWidth;
      videoPlayerControls.videoHeight = videoPlayerHeight;
      videoPlayerControls.addEventListener(VideoPlayerControls.ON_PLAYBACK_STATE_CHANGE,playerControlsOnChange,false,0,true);
      videoPlayerControls.addEventListener(VideoPlayerControls.ON_SOUND_CHANGE,playerControlsSoundOnChange,false,0,true);
      videoPlayerControls.x = videoPlayerContainer.x;
      videoPlayerControls.y = videoPlayerContainer.y + (videoPlayerHeight + 8);
      videoPlayerControls.init();
      addChild(videoPlayerControls);

    }
    /*
    Method: createYouTubePlayer
    Parameters:
    Returns:
    */

    private function createYouTubePlayer():void {
      youTubePlayer = new YouTubePlayer();
      youTubePlayer.addEventListener(YouTubeEvent.ON_READY, playerReady,false,0,true);
      youTubePlayer.addEventListener(YouTubeEvent.ON_CHANGE, onPlayerStateChange,false,0,true);
      youTubePlayer.addEventListener(YouTubeEvent.ON_ERROR,onPlayerError,false,0,true);
      youTubePlayer.createPlayer(videoPlayerContainer);
    }
    /*
    Method: onPlayerError
    Parameters:
    event:YouTubeEvent
    Returns:
    */

    private function onPlayerError(event:YouTubeEvent):void {
      trace(event.data);
      var msg:String;
      switch (event.data) {
        case 100 :
          msg = "Video was not found or is private";
          break;
        case 101 :
          msg = "Video not allowed playback in embedded players";
          break;
        case 150 :
          msg = "Video not allowed playback in embedded players";
          break;
      }
      ti1.text =  msg;
    }
    /*
    Method:playerControlsOnChange
    Parameters:
    event:VideoControlsEvent
    Returns:
    */

    private function playerControlsOnChange(event:VideoControlsEvent):void {

      var buttonState:String = event.data.state;
      switch (buttonState) {
        case "play" :
          youTubePlayer.playVideo();
          break;
        case "pause" :
          youTubePlayer.pauseVideo();
          break;
        case "seek" :
          youTubePlayer.seekTo(event.data.val,true);
          break;
      }
    }
    /*
    Method:playerControlsSoundOnChange
    Parameters:
    event:VideoControlsEvent
    Returns:
    */

    private function playerControlsSoundOnChange(event:VideoControlsEvent):void {

      var buttonState:String = event.data.state;
      switch (buttonState) {
        case "mute" :
          youTubePlayer.mute();
          break;
        case "unmute" :
          youTubePlayer.unMute();
          break;
        case "adjust_volume" :
          youTubePlayer.setVolume(event.data.val);
          removeEventListener(Event.ENTER_FRAME,trackProgress);
          break;
      }
    }
    /*
    Method: playerReady
    Parameters:
    event:YouTubeEvent
    Returns:
    */

    private function playerReady(event:YouTubeEvent):void {
      youTubePlayer.setSize(videoPlayerWidth,videoPlayerHeight);
      youTubePlayer.cueVideoById(defaultVideoByID);
    }
    /*
    Method: onPlayerStateChange
    Parameters:
    event:Event
    Returns:
    */

    private function onPlayerStateChange(event:Event):void {
      var _state = event.data;
      switch (_state) {
        case -1 :
          //trace("unstarted");
          break;
        case 0 :
          //trace("ended");
          videoPlayerControls.setPlayPauseControls(youTubePlayer.PlayerState);
          removeEventListener(Event.ENTER_FRAME,trackProgress);
          break;
        case 1 :
          //trace("playing" + youTubePlayer.volume);
          ti1.text = "Playing : ID : " + defaultVideoByID;
          youTubePlayer.setVolume(50);
          videoPlayerControls.setPlayPauseControls(youTubePlayer.PlayerState);
          videoPlayerControls.setMuteControl(youTubePlayer.isMuted());
          videoPlayerControls.setSeekDuration(youTubePlayer.duration);
          addEventListener(Event.ENTER_FRAME,trackProgress);
          break;
        case 2 :
          //trace("paused");
          break;
        case 3 :
          //trace("buffering");

          break;
        case 5 :
          //trace("video cued");
          videoPlayerControls.visible=true;
          ti1.text = "Video Cued : ID : "+ defaultVideoByID;
          break;
      }
    }
    /*
    Method:trackProgress
    Parameters:
    event:Event
    Returns:
    */

    private function trackProgress(event:Event):void {
      var _bytesLoaded = youTubePlayer.VideoBytesLoaded;
      var _bytesTotal = youTubePlayer.VideoBytesTotal;

      videoPlayerControls.setLoadingProgress({bytes:_bytesLoaded,total:_bytesTotal});
      videoPlayerControls.setSeekCurrentTime(youTubePlayer.CurrentTime);

    }
    /*
    Method: setupInputFields
    Parameters:
    Returns:
    */

    private function setupInputFields():void {
      ti1 = new TextInput();
      ti1.move(videoBkg.x,725);
      ti1.editable = false;
      ti1.width = videoBkg.width;
      ti1.text = "Video ID Info";
      addChild(ti1);
    }
    /*
    Method:updateDefaultVideoById
    Parameters:
    event:Event
    Returns:
    */

    private function updateDefaultVideoById(event:Event):void {
      defaultVideoByID = ti1.text;
    }
    /*
    Method: startStandardFeedSearch
    Parameters:
    inStr:String
    Returns:
    */

    private function startStandardFeedSearch(inStr:String):void {

      addLoadingAnimation();
      removeSearchFormListeners();
      videoScroller.visible=false;
      switch (inStr) {
        case "toprated" :
          youTubeSearch.loadTopRatedFeed(1,standardFeedMaxResults);
          break;
        case "featured" :
          youTubeSearch.loadRecentlyFeaturedFeed(1,standardFeedMaxResults);
          break;
        case "mostdiscussed" :
          youTubeSearch.loadMostDiscussedFeed();
          break;
      }
      youTubeSearch.addEventListener(YouTubeSearchEvent.ON_STANDARD_VIDEO_DATA_FEED_READY,standardFeedLoaded,false,0,true);
    }
    /*
    Method:standardFeedLoaded
    Parameters:
    event:YouTubeSearchEvent
    Returns:
    */

    private function standardFeedLoaded(event:YouTubeSearchEvent):void {
      removeLoadingAnimation();
      videoScroller.visible=true;
      var dArray:Array = event.data;
      videoScroller.dataprovider = dArray;
    }
    /*
    Method: radioButtonGroupChangeState
    Parameters:
    event:Event
    Returns:
    */

    private function radioButtonGroupChangeState(event:Event):void {
      var rbg:RadioButtonGroup = event.target as RadioButtonGroup;
      var rb:RadioButton = rbg.selection;
      var currentInstanceName:String = rb.name;
      switch (currentInstanceName) {
        case "search" :
          addSearchFormListeners();
          break;
        case "toprated" :
          startStandardFeedSearch(currentInstanceName);
          break;
        case "featured" :
          startStandardFeedSearch(currentInstanceName);
          break;
        case "mostdiscussed" :
          startStandardFeedSearch(currentInstanceName);
          break;

      }
    }
    /*
    Method: createRadioButton()
    Parameters:
    Returns:
    */

    private function createRadioButton():void {
      var rbg:RadioButtonGroup = new RadioButtonGroup("searchgroup");
      searchRB = new RadioButton();
      searchRB.name = "search";
      searchRB.group = rbg;
      searchRB.label="Search";
      searchRB.labelPlacement=ButtonLabelPlacement.BOTTOM;
      searchRB.selected = true;
      searchRB.move(0, (searchInput.y + searchInput.height) + 15);


      topRB = new RadioButton();
      topRB.name = "toprated";
      topRB.group = rbg;
      topRB.label="Top Rated";
      topRB.labelPlacement=ButtonLabelPlacement.BOTTOM;
      topRB.selected = false;
      topRB.move(70, (searchInput.y + searchInput.height) + 15);

      featuredRB = new RadioButton();
      featuredRB.name = "featured";
      featuredRB.group = rbg;
      featuredRB.label="Recently Featured";
      featuredRB.labelPlacement=ButtonLabelPlacement.BOTTOM;
      featuredRB.selected = false;
      featuredRB.move(160, (searchInput.y + searchInput.height) + 15);

      mostDiscussedRB = new RadioButton();
      mostDiscussedRB.name = "mostdiscussed";
      mostDiscussedRB.group = rbg;
      mostDiscussedRB.label="Most Discussed";
      mostDiscussedRB.labelPlacement=ButtonLabelPlacement.BOTTOM;
      mostDiscussedRB.selected = false;
      mostDiscussedRB.move(270, (searchInput.y + searchInput.height) + 15);


      addChild(searchRB);
      addChild(topRB);
      addChild(featuredRB);
      addChild(mostDiscussedRB);
      rbg.addEventListener(Event.CHANGE, radioButtonGroupChangeState);
    }
    /*
    Method:createRectangle
    Parameters:
    inSrc:*
    inColor:Number
    inW:Number;
    inH:Number;
    Return:
    */

    private function createRectangle(inSrc:*,inColor:Number=0x999999,inW:Number=80,inH:Number=50):void {
      var rect:Shape=new Shape();
      rect.graphics.clear();
      rect.graphics.beginFill(inColor);
      rect.graphics.drawRect(0,0,inW,inH);
      rect.graphics.endFill();
      inSrc.addChild(rect);
    }

  }
}

If you read the AS3 YouTube Chromeless API post, you will be familiar with the YouTubePlayer.as class and the VideoPlayerControls.as class. If you downloaded the source, I advise you to update your files, since I did some code cleanup.

The YouTubeSearch.as class is a singleton that handles the search functionality including the instantiation of Martin Legris YouTubeClient. YouTubeSearch extends EventDispatcher which gives us the ability to dispatch a custom event, passing along the search result data. The class also builds an array which contains objects for each individual video data that is passed back to the video panel scroller. You can extend the functionality by adding new methods to support your application. For example, you can add a login to your application which enables the ability to access your YouTube Profile and retrieve your list of videos, comments, ect. As I mentioned Martin’s YouTubeClient rocks, but for my application I just need the simple search functionality.

YouTubeSearch.as

/****************************
* Manuel Gonzalez           *
* design@stheory.com        *
* www.stheory.com           *
* www.codingcolor.com       *
*****************************/

package com.YouTube
{
  import com.newcommerce.youtube.data.ThumbnailData;
  import com.newcommerce.youtube.data.VideoData;
  import com.newcommerce.youtube.events.VideoFeedEvent;
  import com.newcommerce.youtube.events.StandardVideoFeedEvent;
  import com.newcommerce.youtube.feeds.VideoFeed;
  import com.newcommerce.youtube.iterators.ThumbnailIterator;
  import com.newcommerce.youtube.webservice.YouTubeClient;
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.events.IEventDispatcher;

  public class YouTubeSearch extends EventDispatcher
  {
    private static  var _instance:YouTubeSearch;
    protected var _ws:YouTubeClient;   
    protected var _requestId:Number;
   
   
    public function YouTubeSearch(singletonEnforcer:SingletonEnforcer) {
      var target:IEventDispatcher=null
      super(target);
    }
   
    public static function getInstance():YouTubeSearch {
      if (_instance == null) {
        _instance=new YouTubeSearch(new SingletonEnforcer  );
        _instance.init();
      }
      return _instance;
    }
    /*
    Method:init
    Parameters:
    Returns:
    */

    public function init():void{
     
      _ws = YouTubeClient.getInstance();
      _ws.addEventListener(VideoFeedEvent.VIDEO_DATA_RECEIVED, videoSearchResults,false,0,true);
      _ws.addEventListener(StandardVideoFeedEvent.STANDARD_VIDEO_DATA_RECEIVED, standardVideoFeedReady,false,0,true);
    }
    /*
    Method:startSearch
    Parameters:
    inSearchString:String
    Returns:
    */

    public function startSearch(inSearchString:String):void
    {
      _requestId = _ws.getVideos(inSearchString, "", null, null, ["music"], ["videos"], YouTubeClient.ORDER_BY_VIEWCOUNT, YouTubeClient.RACY_INCLUDE);
    }
    /*
    Method:loadTopRatedFeed
    Parameters:
    inStartIndex:Number=1
    inMaxResults:Number=10
    Returns:
    */

    public function loadTopRatedFeed(inStartIndex:Number=1, inMaxResults:Number=10):void
    {
      _requestId = _ws.getStandardFeed(YouTubeClient.STD_TOP_RATED, YouTubeClient.TIME_TODAY, inStartIndex,inMaxResults);
    }
    /*
    Method:loadRecentlyFeaturedFeed
    Parameters:
    inStartIndex:Number=1
    inMaxResults:Number=10
    Returns:
    */

    public function loadRecentlyFeaturedFeed(inStartIndex:Number=1, inMaxResults:Number=10):void
    {
     
      _requestId  = _ws.getStandardFeed(YouTubeClient.STD_RECENTLY_FEATURED, YouTubeClient.TIME_TODAY, inStartIndex,inMaxResults);
    }
    /*
    Method:loadMostDiscussedFeed
    Parameters:
    Returns:
    */

    public function loadMostDiscussedFeed():void
    {
      _requestId  = _ws.getStandardFeed(YouTubeClient.STD_MOST_DISCUSSED, YouTubeClient.TIME_TODAY);
    }
    /*
    Method:videoBuildVideoArray
    Parameters:
    inEvent:*
    Returns:Array
    */

    private function videoBuildVideoArray(inEvent:*):Array
    {
        var feed:VideoFeed = inEvent.feed;
        var video:VideoData; 
        var videoArray:Array = new Array()
     
        while(video = feed.next())
        {
       
          var videoObject = new Object();
          // video title
          videoObject.title = video.title;
          videoObject.url = video.id;
          videoObject.id = video.actualId;
          videoObject.count = video.viewCount;
          /*
          trace("Title : " + video.title)
          trace("ID " + video.id);
          trace("Actual ID " + video.actualId);
          trace("View Count " + video.viewCount);
          */

          var tnIt:ThumbnailIterator = video.media.thumbnails;
          var tn:ThumbnailData;
         
          while(tn = tnIt.next())
          {
            if(checkIsFirstImage(tn.url))
            {
              videoObject.tnURL = tn.url;
              //trace("tn " + tn.url + " w " + tn.width + " h " + tn.height)
            }
          }
          videoArray.push(videoObject);
        }
       
        return videoArray;
    }
    /*
    Method:standardVideoFeedReady
    Parameters:
    event:StandardVideoFeedEvent
    Returns:
    */

    private function standardVideoFeedReady(event:StandardVideoFeedEvent):void
    {
      if(_requestId == event.requestId){
      var returnArray = videoBuildVideoArray(event);
       
      }else{
        trace("this call:"+event.requestId+" isn't ours. We'll wait for the next one...");
      }
      dispatchEvent(new YouTubeSearchEvent(YouTubeSearchEvent.ON_STANDARD_VIDEO_DATA_FEED_READY,returnArray));
    }
    /*
    Method:videoSearchResults
    Parameters:
    event:VideoFeedEvent
    Returns:
    */

    private function videoSearchResults(event:VideoFeedEvent):void
    {
      if(_requestId == event.requestId)
      {
      var returnArray = videoBuildVideoArray(event);
       
      }else{
        trace("this call:"+event.requestId+" isn't ours. We'll wait for the next one...");
      }
      dispatchEvent(new YouTubeSearchEvent(YouTubeSearchEvent.ON_VIDEO_DATA_READY,returnArray));
    }
   
    /*
    Method:checkIsFirstImage
    Parameters:
    inUrl:String
    Returns: Boolean
    */

    private function checkIsFirstImage(inUrl:String):Boolean {
      var isFirstImage:Boolean;
     
      var vIndex:Number = inUrl.lastIndexOf("/");
     
      if (vIndex == -1) {
        //no last index
        isFirstImage = false;
      } else {
        if(inUrl.substr(vIndex + 1,inUrl.length) == "1.jpg"){
          isFirstImage = true;
        }else{
          isFirstImage = false;
        }
       
      }
     
      return isFirstImage;
    }
   
  }
}
internal class SingletonEnforcer {
}

The PanelScroller .as class handles the visual display for the video data. The class instantiates a slider control and builds the individual panels for each search result. Up until now, any visual asset we loaded from Google has been handled by the AS3 chromeless player.
Since we want to see individual thumbnails to represent the videos, we need to load a crossdomain.xml file: Security.loadPolicyFile(“http://i.ytimg.com/crossdomain.xml”).
With out this line of code, you would never see your thumbnails in the scroller. I’m not going to cover Flash and its Secuirty issues but you can read up on it here.

PanelScroller.as

/****************************
* Manuel Gonzalez           *
* design@stheory.com        *
* www.stheory.com           *
* www.codingcolor.com       *
*****************************/

package com.view{
  import flash.display.Sprite;
  import flash.display.MovieClip;
  import flash.events.Event;
  import flash.geom.Rectangle;
  import flash.system.*;

  public class PanelScroller extends Sprite {

    private var slider:Slider;
    private var dProvider:Array;
    private var tnArray:Array;
    private var panelsCreated:Boolean = false;
    private var panelHeight:Number;
    private var content:Sprite;
    private var contentHeight:Number = 0;
    private var contentMask:MovieClip;
    private var contentRectangle:Rectangle= new Rectangle( 0, 0, 310, 350 );
    private var selectedPanel:Object;
   
   
    public function get currentPanel():Object
    {
      return selectedPanel;
    }
    public function set dataprovider(inData:Array):void {
      dProvider = inData;
      buildContainer();
    }
    public function get dataprovider():Array {
      return dProvider;
    }
    /*Constructor*/
    public function PanelScroller() {
      Security.allowInsecureDomain("*");
      Security.allowDomain("*");
      Security.loadPolicyFile("http://i.ytimg.com/crossdomain.xml");
      slider = sliderControl;
      slider.visible = false;
      slider.addEventListener(Event.CHANGE,slideUpdate,false,0,true);
      content = new Sprite();
      content.x = 0;
      addChild(content);
    }
    /*
    Method:buildContainer
    Parameters:
    Returns:
    */

    private function buildContainer():void {
      if(panelsCreated)
      {
        var child:int = content.numChildren;
        while( child -- )
        {
          content.removeChildAt(child);
        }
        slider.percent = 0;
        contentHeight = 0;
      }
     
      tnArray = new Array();
      for (var i:uint = 0; i < dProvider.length; i++) {
        tn = new VideoPanel();
        tn.control = this;
        tn.vidTitle = dProvider[i].title;
        tn.id = dProvider[i].id;
        tn.url =dProvider[i].url;
        tn.viewCount = dProvider[i].count;
        tn.tnUrl = dProvider[i].tnURL;
        content.addChild(tn);
        tnArray.push(tn);
      }
      panelsCreated = true;
      adjustLayout();
    }
    /*
    Method:adjustLayout
    Parameters:
    Returns:
    */

    private function adjustLayout():void {
      var tnHeight:Number
      for (var j:uint = 0; j < tnArray.length; j++) {
        tnArray[j].y = (tnArray[j].y+ tnArray[j].height)*j;
        tnHeight = tnArray[j].height;//I'm doing this because I didn't want to hard code the panel height
        tnArray[j].init();
      }
     
      contentHeight = tnHeight*tnArray.length;
      content.scrollRect = contentRectangle;
      if (contentHeight > slider.trackHeight) {
        slider.visible = true;
      }
    }
    /*
    Method:slideUpdate
    Parameters:
    event:Event
    Returns:
    */

    private function slideUpdate(event:Event):void {
      var pcent:Number = slider.percent;
      var scrollable:Number = contentHeight - content.scrollRect.height;
      var sr:Rectangle = content.scrollRect.clone();
      sr.y = scrollable * pcent;
      content.scrollRect = sr;
    }
    /*
    Method:setSelectedPanel
    Parameters:
    inObj:Object
    Returns:
    */

    public function setSelectedPanel(inObj:Object):void
    {
      selectedPanel = inObj;
      dispatchEvent(new Event(Event.CHANGE));
    }



  }
}

And last but not least the VideoPanel.as class. The VideoPanel class is a MovieClip which displays and loads the video data. It also has a few utility methods to format the text data. There is not much to it.

VideoPanel.as

/****************************
* Manuel Gonzalez           *
* design@stheory.com        *
* www.stheory.com           *
* www.codingcolor.com       *
*****************************/

package com.view{
  import flash.display.MovieClip;
  import flash.display.Sprite;
  import flash.display.Shape;
  import flash.display.Loader;
  import flash.display.Bitmap;
  import flash.events.MouseEvent;
  import flash.events.Event;
  import flash.events.ProgressEvent;
  import flash.events.IOErrorEvent;
  import flash.net.URLRequest;
  import flash.text.TextField;
  import flash.system.Security;
  import flash.events.SecurityErrorEvent;


  public class VideoPanel extends MovieClip {

    private var videoTitle:String;
    private var videoId:String;
    private var videoUrl:String;
    private var videoViewCount:String;
    private var tnPath:String;
    private var controller:Object;
    private var imgLoader:Loader;
    private var preloadBar:MovieClip;
    private var container:Sprite;
    private var contMask:Sprite;
    private var titleMaxChar:Number=35;
    private var statusTextField:TextField;

    public function set control(inObj:Object):void {
      controller = inObj;
    }
    public function get control():Object {
      return contoller;
    }
    public function set id(inStr:String):void {
      videoId = inStr;
    }
    public function get id():String {
      return videoId;
    }
    public function set url(inStr:String):void {
      videoUrl = inStr;
    }
    public function get url():String {
      return videoUrl;
    }
    public function set vidTitle(inStr:String):void {
      videoTitle = inStr.toLowerCase();
    }
    public function get vidTitle():String {
      return videoTitle;
    }
    public function set viewCount(inStr:String):void {
      videoViewCount = inStr;
    }
    public function get viewCount():String {
      return videoViewCount;
    }
    public function set tnUrl(inStr:String):void {
      tnPath = inStr;
    }
    public function get tnUrl():String {
      return tnPath;
    }
    /*Constructor*/
    public function VideoPanel() {
      super();
      preloadBar = preloader.bar;
      preloadBar.scaleX = 0;
      preloader.alpha = 0;
    }
    /*
    Method:init
    Parameters:
    Returns:
    */

    public function init():void {
      container = new Sprite();
      container.x = 10;
      container.y = 10 ;
      contMask = new Sprite();
      contMask.x = container.x;
      contMask.y = container.y;
      createRectangle(contMask,0x000000,120,90);
      addChild(contMask);
      addChild(container);
      container.mask = contMask;
      this.buttonMode = true;
      this.addEventListener(MouseEvent.CLICK,onClick);
      displayTextData();
      loadTn();
    }
    /*
    Method:displayTextData
    Parameters:
    Returns:
    */

    private function displayTextData():void {
      titleF.text = truncate(videoTitle,titleMaxChar);
      viewF.text = "views : " + formatNumberWithCommas(videoViewCount);
    }
    /*
    Method:loadTn
    Parameters:
    Returns:
    */

    private function loadTn():void {
      preloader.alpha = 1;
      imgLoader = new Loader();
      imgLoader.contentLoaderInfo.addEventListener(Event.INIT,initImage,false,0,true);
      imgLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS,onProgress,false,0,true);
      imgLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,onImgLoaded,false,0,true);
      imgLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler,false,0,true);
      imgLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler,false,0,true);

      imgLoader.load(new URLRequest(tnPath));
    }
    /*
    Method:securityErrorHandler
    Parameters:
    event:Event
    Returns:
    */

    private function securityErrorHandler(event:SecurityErrorEvent):void {
      statusTextField = new TextField();
      statusTextField.x = preloader.x + preloader.width/2;
      statusTextField.y = (preloader.y + preloader.height) + 2;
      statusTextField.text ="security error"+ event;
      addChild(statusTextField);
    }
    /*
    Method:ioErrorHandler
    Parameters:
    event:Event
    Returns:
    */

    private function ioErrorHandler(event:Event):void {
      statusTextField = new TextField();
      statusTextField.x = preloader.x + preloader.width/2;
      statusTextField.y = (preloader.y + preloader.height) + 2;
      statusTextField.text ="loading error"+ event;
      addChild(statusTextField);
    }
    /*
    Method:initImage
    Parameters:
    event:Event
    Returns:
    */

    private function initImage(event:Event):void {
      var image : Bitmap = imgLoader.contentLoaderInfo.content as Bitmap;
      container.addChild(image);
    }
    /*
    Method:onImgLoaded
    Parameters:
    event:Event
    Returns:
    */

    private function onImgLoaded(event:Event):void {
      preloader.alpha = 0;
      imgLoader.contentLoaderInfo.removeEventListener(Event.INIT,initImage);
      imgLoader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS,onProgress);
      imgLoader.contentLoaderInfo.removeEventListener(Event.COMPLETE,onImgLoaded);
      imgLoader = null;
    }
    /*
    Method:onProgress
    Parameters:
    event:ProgressEvent
    Returns:
    */

    private function onProgress(event:ProgressEvent):void {
      var loaded:Number = event.bytesLoaded;
      var total:Number = event.bytesTotal;
      var percLoaded:Number = loaded/total;
      preloadBar.scaleX = percLoaded;
      //trace("onProgress : " + percLoaded)
    }
    /*
    Method:onClick
    Parameters:
    event:MouseEvent
    Returns:
    */

    private function onClick(event:MouseEvent):void {
      controller.setSelectedPanel(this);
    }
    /*
    Method:createRectangle
    Parameters:
    inSrc:*
    inColor:Number
    inW:Number;
    inH:Number;
    Return:
    */

    private function createRectangle(inSrc:*,inColor:Number=0x999999,inW:Number=80,inH:Number=50):void {
      var rect:Shape=new Shape();
      rect.graphics.clear();
      rect.graphics.beginFill(inColor);
      rect.graphics.drawRect(0,0,inW,inH);
      rect.graphics.endFill();
      inSrc.addChild(rect);
    }
    /*
    Method:truncate
    Parameters:
    p_string:String
    p_len:uint
    p_suffix:String
    Returns:
    */

    private function truncate(p_string:String, p_len:uint, p_suffix:String = "..."):String {
      if (p_string == null) {
        return '';
      }
      p_len -= p_suffix.length;
      var trunc:String = p_string;
      if (trunc.length > p_len) {
        trunc = trunc.substr(0, p_len);
        if (/[^\s]/.test(p_string.charAt(p_len))) {
          trunc = trimRight(trunc.replace(/\w+$|\s+$/, ''));
        }
        trunc += p_suffix;
      }

      return trunc;
    }
    /*
    Method:trimRight
    Parameters:
    p_string:String
    Returns:
    */

    private function trimRight(p_string:String):String {
      if (p_string == null) {
        return '';
      }
      return p_string.replace(/\s+$/,'');
    }
    /*
    Method:formatNumberWithCommas
    Parameters:
    Returns:
    */

    private function formatNumberWithCommas(number:Number):String {

      var negNum:String = "";
      if (number<0) {
        negNum = "-";
        number = Math.abs(number);
      }
      var num:String = String(number);
      var results:Array = num.split(/\./);
      num=results[0];

      if (num.length>3) {
        var mod:Number = num.length%3;
        var output:String = num.substr(0, mod);
        for (var i:Number = mod; i<num.length; i += 3) {
          output += ((mod == 0 && i == 0) ? "" : ",")+num.substr(i, 3);
        }
        if (results.length>1) {
          if (results[1].length == 1) {
            return negNum + output + "." + results[1] + "0";

          } else {
            return negNum + output + "." + results[1];
          }
        } else {
          return negNum + output;
        }
      }

      if (results.length>1) {
        if (results[1].length == 1) {
          return negNum + num + "." + results[1] + "0";

        } else {
          return negNum + num + "." + results[1];
        }
      } else {
        return negNum + num;
      }
    }
  }
}

Well that concludes my brief overview of the search application. I didn’t cover the Slider.as class becuase its pretty straight forward, download the source and crack it open. I included Martin’s YouTubeClient with my source but as always check for new releases here. If you have any question or improvements, leave me some comments.
Enjoy!

Download Source:
AS3 YouTube Chromeless player & Search API (710)

19 thoughts on “AS3 YouTube Chromeless player and search API

  • January 23, 2010 at 10:53 am
    Permalink

    great work, thanks for sharing!

    Reply
  • May 28, 2010 at 4:41 am
    Permalink

    Good Work. Is it Possible to display related videos in AS3 Youtube Chromeless player?

    Reply
  • June 10, 2010 at 12:34 am
    Permalink

    great job!

    How to get the title of the video by passing the video id.

    Reply
    • June 10, 2010 at 5:38 pm
      Permalink

      @bijoy. Did you load the video feed? Was there a feed object which was returned? What exactly are you trying to do? This would help me…help you.
      M

      Reply
  • June 25, 2010 at 9:36 pm
    Permalink

    OMG!!! THANK YOU!!! YOUR TUTORIAL ARE AWESOME…….

    Reply
  • January 21, 2011 at 5:58 am
    Permalink

    Hi, can the vido be playing in fullScreen mode ? If can – How to do it?

    Reply
    • January 22, 2011 at 12:41 pm
      Permalink

      I believe so. Have you tried it yet?
      Here a snippet of code that will enable flash fullscreen

      import flash.display.StageDisplayState;
      stage.scaleMode = StageScaleMode.NO_SCALE;
      private function onFullScreenClicked(e:MouseEvent):void
      {
      if (stage.displayState == StageDisplayState.NORMAL) {
      stage.displayState=StageDisplayState.FULL_SCREEN;
      } else {
      stage.displayState=StageDisplayState.NORMAL;
      }
      }

      // Now create a button and add a MouseEvent

      yourFullscreenButton.addEventListener(MouseEvent.CLICK, onFullScreenClicked,false,0,true);

      //In the HTML make sure that your object embed tag attribute is set to allowFullScreen and set the parameters to:

      That should do the trick.

      Reply
  • September 4, 2011 at 6:14 am
    Permalink

    Thank you very much. I have searched it for ages.

    Reply
  • September 21, 2011 at 12:59 pm
    Permalink

    Could you explain me how do i put your script on my website please? 😀

    Reply
    • May 2, 2012 at 8:10 pm
      Permalink

      This is a flash application. It’s not a plug in play solution.

      Reply
  • December 12, 2011 at 5:04 pm
    Permalink

    I have an embeddable player on my website which listed all my uploaded videos but unfortunately this is now not working because of the latest youtube update. When I installed the player first off it was just a matter of copying the code fom youtube and inserting it into my website. I have hunted high and low for the new code and cannot find it anywhere. I have come across many examples like yours but as I am in no way a code developer I wouldn’t even know whwere to start. Is there a code you know of that I can just copy and paste that will enable my videos to be viewed?
    Thanks
    Kev

    Reply
    • May 2, 2012 at 7:59 pm
      Permalink

      Sorry Kev, I don’t. Goodluck!

      Reply
  • January 13, 2012 at 2:39 am
    Permalink

    hi
    martin
    🙂
    do u know how to dispose or clear youtube video

    Reply
  • January 13, 2012 at 10:25 am
    Permalink

    Great demo!!!

    One question:

    Is it possible to make a search for a Youtube Brand Channel?

    Reply
  • June 26, 2012 at 12:51 pm
    Permalink

    Hi ,

    Thank you for your nice work !!
    I have one question about this search list. I tried this on OPERA explorer and the thumbnails do no appear in your playlist . Do you know why ?

    Thank you very much for your answer.

    Joseph

    Reply
    • February 1, 2013 at 1:55 pm
      Permalink

      Hopefully you figured this out. Sorry for the late reply, I don’t ever test on OPERA, your best bet is to sniff the packets and determine if its a security issue or if the images are failing to load based off of server permissions.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: