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.
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);
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);
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);
}
/*
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);
videoPlayerControls.addEventListener(VideoPlayerControls.ON_SOUND_CHANGE,playerControlsSoundOnChange);
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);
youTubePlayer.addEventListener(YouTubeEvent.ON_CHANGE, onPlayerStateChange);
youTubePlayer.addEventListener(YouTubeEvent.ON_ERROR,onPlayerError);
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);
}
/*
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);
_ws.addEventListener(StandardVideoFeedEvent.STANDARD_VIDEO_DATA_RECEIVED, standardVideoFeedReady);
}
/*
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);
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);
imgLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS,onProgress);
imgLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,onImgLoaded);
imgLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
imgLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
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 (85)
great work, thanks for sharing!
Good Work. Is it Possible to display related videos in AS3 Youtube Chromeless player?
great job!
How to get the title of the video by passing the video id.
@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
OMG!!! THANK YOU!!! YOUR TUTORIAL ARE AWESOME…….