Home  >  

Facebook App Case Study: Administration module

Author photo
AddThis Social Bookmark Button
     Last time we talked about the way how to work with YouTube videos and how to implement those into our app. We worked hard to implement typical features that can be found on most video sites like play, pause, seek etc. We even have the feature to seek through the video using the Seek Bar that we built from scratch. As we already know, there is one external library used in our application that allows us to load videos into the swf and manipulate the video using ActionScript 3.0. This is a huge advantage since the official YouTube library is written in AS2 and it does not fit our project requirements. Using the library, we were able to create video controls using pure ActionScript 3.0.
Now it’s turn to actually work on the admin part of the app. What is meant with administration? With admin we talk about the feature to allow us and all our application users (players) to add their own videos along with mistakes. This way the application will much more extensible. There are lots of facebook users who are movie fans and we will give them the ability to contribute.
One important thing to note is that the videos sent by users will not be immediately available. There is the possibility that some user can submit crap videos that are not suitable for the application. As prevention against that we need to be able to check the videos being sent. We need to implement a mail() function that will notify us any time somebody has submitted the video. Then we will check the video, see if the user has added a valid movie clip with movie mistakes added to the clip, then we decide if the clip really deserves to be on the application database.

article18_img1.png
It looks a bit complicated but in fact it is not. If we examine the image in more detail, we will be able to develop the part of the app much easier because we already have the goal in our mind. On the image, we can see the familiar YouTube player we worked on in the last article. There are some small changes that can be observed. At the top of the video window, we can see the text field with the white border. On the middle of the screen, we see the yellow circle. First of all, there is the text field on the top. Let's say, the user wants to add a video to our application. He finds the video on YouTube and copies the URL directly into the upper text field. By pressing enter, the application loads the video from YouTube and starts playing the video. We are now in control of the video. Now that we have the video, we need a way to add mistakes to the video and this is where the magical yellow circle comes to play.
The circle has a special purpose. When somebody comes to our app and adds a new video to the database, he will also be prompted to mark the mistakes from the movie. Let’s say, in the movie clip, the equipment is visible somewhere. The user will mark the location with the circle. Along with the location of mistake, he will also add hints about the mistake, so that the potential player uses the hints to find the goofs more easily. We can add hints like "Take a look at the head of Angelina Jolie", "The goof is somewhere in the beginning of the clip" etc. If you do not get the idea, here is the screenshot:

article18_img2.png
Open main.fla. So far we loaded the YouTube video. Create a new MovieClip called “AddVideoClip”.

article18_img3.png
In the linkage options, add “classes.AddVideoClip” as the class that the clip is linked to. We will create the class later. Let's check the clip in the edit mode:

article18_img4.png
First we need to create 2 layers. Name the upper one “TextButton”, the lower one “Text”.

article18_img5.png
Place an input text field on 0, 0 and name it "youTube_txt”.

article18_img6.png
Right beside it, place a second text field. This time it needs to be dynamic. In the property inspector, name it “warning_txt".

article18_img7.png
The upper controls are ok. Now we need to add some text at the bottom of the clip. At position x: 510 and y: 400, place a normal static text field that contains the following text "Do you want to add your own clip? Click here”. Mark the final word "here" blue. Now convert the static text field to a movieclip and name it "addVideoText_mc".

article18_img9.png
Make sure the clip is at the right position like on the image. Now create a simple rectangular button with the alpha of the inner shape set to 0 and place it on the “TextButton” layer just above the word “here”, like in the image.

article18_img8.png
Name the button “gotoAddClip_btn”.

article18_img11.png
Now that we have all the elements ready for the clip, we need to review if they are really in place. Here is the "skeleton" of the clip we just created:

article18_img12.png
So now that we are ready with the clip, lets see how the class looks like. Here is the code of the class:

package classes{
 
 	import flash.events.*;
 	import flash.display.*;
 	import flash.text.*;
 	import classes.*;
 	import fl.controls.*;
 
 	public class AddVideoClip extends MovieClip {
  
  		public static var VIDEO_BEING_ADDED:String = "videoBeingAdded";
  		public static var VIDEO_ID_ADDED:String = "videoIdAdded";
  
  		function AddVideoClip() {
   			super();
   			youTube_txt.visible = false;
   			gotoAddClip_btn.buttonMode = true;
   			gotoAddClip_btn.addEventListener(MouseEvent.CLICK, showVideoFields);
   			youTube_txt.addEventListener(FocusEvent.FOCUS_IN, fieldFocused);
   			youTube_txt.addEventListener(FocusEvent.FOCUS_OUT, fieldFocusKilled);
   		}
  
  		private function fieldFocused(e:FocusEvent):void {
   			youTube_txt.text = "";
   			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyIsDown);
   			warning_txt.text = "Now press enter";
   		}
  
  		private function fieldFocusKilled(e:FocusEvent) {
   			stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyIsDown);
   		}
  
  		private function keyIsDown(e:KeyboardEvent):void {
   			warning_txt.text = "";
   			if(e.keyCode == 13){
    				var link:String = youTube_txt.text;
    				if(!Tools.isEmpty(link)){
     					videoLinkAdded(Tools.trim(link));
     				} else {
     					warning_txt.text = "Please add a valid YouTube link";
     				}
    			}
   		}
  
  		private function videoLinkAdded(link:String):void {
   			warning_txt.text = "Loading video...";
   			var id = Tools.getYoutubeVideoID(link);
   			var e:SuperEvent = new SuperEvent(AddVideoClip.VIDEO_ID_ADDED, true, true);
   			e.add("id", id);
   			dispatchEvent(e);
   			stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyIsDown);
   			warning_txt.text = "To cancel and add new video, paste link and press enter";
   		}
  
  		private function showVideoFields(e:MouseEvent):void {
   			dispatchEvent(new Event(AddVideoClip.VIDEO_BEING_ADDED, true, true));
   			addVideoText_mc.visible = false;
   			youTube_txt.visible = true;
   			youTube_txt.text = "Please paste a valid YouTube link here...";
   			youTube_txt.addEventListener(FocusEvent.FOCUS_IN, fieldFocused);
   		}
  
  		override public function set visible(e:Boolean):void {
   			if(!e){
    				youTube_txt.visible = false;
    				warning_txt.text = "";
    				addVideoText_mc.visible = true;
    			} else {
    				youTube_txt.visible = true;
    				youTube_txt.text = "Paste a valid YouTube link here.";
    				addVideoText_mc.visible = false;
    			}
   		}
  	
  	};
 
};
Save it as “AddVideoClip.as” in the classes folder of your project folder. Make sure you place the following code into the first frame of the clip:


//code from the previous article that loads the video
import classes.*;
import classes.choppingblock.video.*;

var videoHolder = new YouTubeLoaderPlus(); 
videoHolder.create();
addChild(videoHolder);

videoHolder.addEventListener(YouTubeLoaderPlus.PLAYER_READY, ytPlayerReady);
videoHolder.addEventListener(YouTubeLoaderPlus.VIDEO_READY, ytVideoReady);

function ytPlayerReady(e:Event):void{
 	videoHolder.loadVideoById("lPgxsGRpiuw");
}

function ytVideoReady(e:Event):void{
 	videoHolder.play();
}

var addVideoClip_mc = new AddVideoClip();
addChild(addVideoClip_mc);

addVideoClip_mc.addEventListener(AddVideoClip.VIDEO_BEING_ADDED, videoBeingAdded);
addVideoClip_mc.addEventListener(AddVideoClip.VIDEO_ID_ADDED, videoAddedForEditing);

function videoBeingAdded(e:Event):void{
 
 
 	videoHolder.stop();
 	videoHolder.visible = false;
 	
 	//
 
}

function videoAddedForEditing(e:SuperEvent){
 
 	var id = e.get("id");
 	videoHolder.loadVideoById(id);
 
}
So, ho does this look like in reality? Well, here is the sample displayed in the facebook application canvas.

article18_img13.png
We see the familiar video that was loaded last time and we see below the initial state of the clip that will allow us to add clips. Now, when we click on the "here" link, the following application state should be visible:

article18_img14.png
Now we need to search for clips that can be used as a dummy here. Now, here I found a good one (http://www.youtube.com/watch?v=StJ80M-B2v4) actually it’s a trailer but for the purpose of this article, it’s good enough. :-)
Now, let’s see if the video actually loads inside our frame. We go back to the application and paste the video link:

article18_img16.png
Cool, this works! Basically it’s very simple to explain what happened. The AddVideoClip object simply fired an event notifying the application that a new video was added. Now, as soon as the id is available, the video is loaded and played back, nothing complicated here. Check out the video controls, like play, stop etc. and you will see that it works like a normal video.
With this, one important part is done. What is missing here is the ability to save this to database. Well, we will leave that until we have enough data. Beside the video id, we also need the data about the errors in the clip. Let's say the error is at 3:15 on some position, the sample data that we need to store would be:

[0]:
x: 100px
y: 200px
startTime: 1.2343
endTime: 3.345
hint1: “Hint about the error…”
hint2: “Hint about the error 2…”
hint3: “Hint about the error 3…”

[1]:
x: 100px
y: 200px
startTime: 1.2343
endTime: 3.345
hint1: “Hint about the error…”
hint2: “Hint about the error 2…”
hint3: “Hint about the error 3…”

[3]:
x: 100px
y: 200px
startTime: 1.2343
endTime: 3.345
hint1: “Hint about the error…”
hint2: “Hint about the error 2…”
hint3: “Hint about the error 3…”
Hope you get the idea. We need an array of of data about the errors and this array will be saved in the database along with the video id.
The next we need to implement here is the yellow circle that will serve as a pointer for mistakes. It will be added in the YouTubeLoaderPlus.as class.
First let’s create the circle that will serve as a clip. Create a MovieClip called "Circle".

article18_img17.png
Make sure you have all the properties set like in the image above. Go to the editing of the clip. We need a circle width 126 and height 126 in yellow color, The circle also needs to be centered.

article18_img18.png
We are almost there!
The next we need is a clip that gives us the ability to enter hint1 about an error. Here is one the screenshot so you get the idea.

article18_img19.png
Here is what needs to be done. Create an empty movieclip with the following options set:

article18_img20.png
In the edit mode, make sure you place 4 text fields on the stage.

article18_img21.png
The first 3 ones should be input text fields, the last ones should be a dynamic text field. From top to bottom, the instance names of the clips should be hint1_txt, hint2_txt, hint3_txt and warning_text. Make the font of the bottom field red because it will contain warning messages.
That’s it. But the HintClip does have a class assotiated with it. So create a class called HintClip.as inside the classes folder and add this code:

package classes{
 
 	import flash.events.*;
 	import flash.display.*;
 	import flash.text.*;
 	import classes.*;
 	import fl.controls.*;
 
 	public class AddVideoClip extends MovieClip {
  
  		public static var VIDEO_BEING_ADDED:String = "videoBeingAdded";
  		public static var VIDEO_ID_ADDED:String = "videoIdAdded";
  
  		function AddVideoClip() {
   			super();
   			youTube_txt.visible = false;
   			gotoAddClip_btn.buttonMode = true;
   			gotoAddClip_btn.addEventListener(MouseEvent.CLICK, showVideoFields);
   			youTube_txt.addEventListener(FocusEvent.FOCUS_IN, fieldFocused);
   			youTube_txt.addEventListener(FocusEvent.FOCUS_OUT, fieldFocusKilled);
   		}
  
  		private function fieldFocused(e:FocusEvent):void {
   			youTube_txt.text = "";
   			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyIsDown);
   			warning_txt.text = "Now press enter";
   		}
  
  		private function fieldFocusKilled(e:FocusEvent) {
   			stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyIsDown);
   		}
  
  		private function keyIsDown(e:KeyboardEvent):void {
   			warning_txt.text = "";
   			if(e.keyCode == 13){
    				var link:String = youTube_txt.text;
    				if(!Tools.isEmpty(link)){
     					videoLinkAdded(Tools.trim(link));
     				} else {
     					warning_txt.text = "Please add a valid YouTube link";
     				}
    			}
   		}
  
  		private function videoLinkAdded(link:String):void {
   			warning_txt.text = "Loading video...";
   			var id = Tools.getYoutubeVideoID(link);
   			var e:SuperEvent = new SuperEvent(AddVideoClip.VIDEO_ID_ADDED, true, true);
   			e.add("id", id);
   			dispatchEvent(e);
   			stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyIsDown);
   			warning_txt.text = "To cancel and add new video, paste link and press enter";
   		}
  
  		private function showVideoFields(e:MouseEvent):void {
   			dispatchEvent(new Event(AddVideoClip.VIDEO_BEING_ADDED, true, true));
   			addVideoText_mc.visible = false;
   			youTube_txt.visible = true;
   			youTube_txt.text = "Please paste a valid YouTube link here...";
   			youTube_txt.addEventListener(FocusEvent.FOCUS_IN, fieldFocused);
   		}
  
  		override public function set visible(e:Boolean):void {
   			if(!e){
    				youTube_txt.visible = false;
    				warning_txt.text = "";
    				addVideoText_mc.visible = true;
    			} else {
    				youTube_txt.visible = true;
    				youTube_txt.text = "Paste a valid YouTube link here.";
    				addVideoText_mc.visible = false;
    			}
   		}
  	
  	};
 
};
Still we need to edit the YouTubeLoaderPlus.as class to display the new clips inside the video. Modify the code in the YouTubeLoaderPlus.as class so it matches this code:

package classes.choppingblock.video{
 
 	import flash.events.*;
 	import flash.display.*;
 	import classes.*;
 
 	public class YouTubeLoaderPlus extends YouTubeLoader {
  
  		protected var _count:Number = 0;
  		protected var _controls:YouTubeControl = new YouTubeControl();
  		protected var _error:Object = new Object();
  		protected var _circle:Circle = new Circle();
  		protected var _hintClip:MovieClip = new HintClip();
  		protected var _editable:Boolean = false;
  		protected var _editing:Boolean = false;
  		protected var _currCounter:Number = 0;
  		protected var _errors:Array;
  		protected var _errNum:Number;
  		
  		
  		public static var PLAYER_READY:String = "playerReady";
  		public static var VIDEO_READY:String = "videoReady";
  		public static var ERROR_ADDED:String = "errorAdded";
  		
  
  		function YouTubeLoaderPlus() {
   
   			super();
   
   			this.visible = false;
   
   			this.addEventListener(Event.ENTER_FRAME, checkPlayerReady);
   			this.addEventListener(YouTubeLoaderPlus.PLAYER_READY, ytPlayerReady);
   			this.addEventListener(YouTubeLoaderPlus.VIDEO_READY, ytVideoReady);
   			
   			this.addChild(_controls);
   
   			_controls.addEventListener(YouTubeControl.PLAY_PRESSED, playPressed);
   			_controls.addEventListener(YouTubeControl.PAUSE_PRESSED, stopPressed);
   			this.addEventListener(SeekBar.BAR_CLICKED, seekBarClicked);
   
   			if(_editable){
    			
    				this.addEventListener(MouseEvent.MOUSE_DOWN, mouseIsDown);
    				this.addEventListener(MouseEvent.MOUSE_UP, mouseIsUp);
    				_controls.addEventListener(MouseEvent.ROLL_OVER, controlsRolledOver);
    				_controls.addEventListener(MouseEvent.ROLL_OUT, controlsRolledOut);
    
    			}
   
   		}
  
  
  		private function hintsAdded(e:SuperEvent):void {
   			
   			var ex:SuperEvent = new SuperEvent(YouTubeLoaderPlus.ERROR_ADDED, true, true);
   			_error.hint1 = e.get("hint1");
   			_error.hint2 = e.get("hint2");
   			_error.hint3 = e.get("hint3");
   
   			ex.add("error", _error);
   			dispatchEvent(ex);
   			
   			_circle.startDrag(true);
   			_circle.visible = false;
   			_hintClip.visible = false;
   
   			this.addEventListener(MouseEvent.MOUSE_DOWN, mouseIsDown);
   			this.addEventListener(MouseEvent.MOUSE_UP, mouseIsUp);
   			_controls.addEventListener(MouseEvent.ROLL_OVER, controlsRolledOver);
   			_controls.addEventListener(MouseEvent.ROLL_OUT, controlsRolledOut);
   			
   			this.play();
   			
   		}
  
  		
  		private function mouseIsDown(e:MouseEvent):void {
   
   			if(this.getPlayerState() == "2"){
    				return;
    			}
   
   			_error.x = this.mouseX;
   			_error.y = this.mouseY;
   			_error.startTime = this.getCurrentTime();
   			_circle.visible = true;
   			_circle.stopDrag();
   		}
  
  		private function mouseIsUp(e:MouseEvent):void {
   
   			if(this.getPlayerState() == "2"){
    				return;
    			}
   
   			_error.endTime = this.getCurrentTime();
   			_hintClip.x = _circle.x;
   			_hintClip.y = _circle.y;
   			_hintClip.visible = true;
   			this.removeEventListener(MouseEvent.MOUSE_DOWN, mouseIsDown);
   			this.removeEventListener(MouseEvent.MOUSE_UP, mouseIsUp);
   			_editing = true;
   			this.pause();
   		}
  
  		private function controlsRolledOut(e:MouseEvent):void {
   			if(_editable && !_editing){
    				this.addEventListener(MouseEvent.MOUSE_DOWN, mouseIsDown);
    				this.addEventListener(MouseEvent.MOUSE_UP, mouseIsUp);
    			}
   		}
  
  		private function controlsRolledOver(e:MouseEvent):void {
   			if(_editable && !_editing){
    				this.removeEventListener(MouseEvent.MOUSE_DOWN, mouseIsDown);
    				this.removeEventListener(MouseEvent.MOUSE_UP, mouseIsUp);
    			}
   		}
  
  		private function seekBarClicked(e:SuperEvent):void {
   			var prc = e.get("prc");
   			this.seekTo(prc * this.getDuration());
   		}
  
  		private function ytPlayerReady(e:Event) {
   
   			this.setSize(450, 370);
   			_controls.y = 380;
   
   			this.addChildAt(_circle, this.numChildren);
   			_circle.startDrag(true);
   			this.visible = false;
   
   			this.addChildAt(_hintClip, this.numChildren);
   			_hintClip.visible = false;
   			_circle.visible = editable;
   
   			_hintClip.addEventListener(HintClip.HINTS_ADDED, hintsAdded);
   
   
   		}
  
  		private function ytVideoReady(e:Event) {
   			this.pause();
   			this.addEventListener(Event.ENTER_FRAME, updatePreloadStatus);
   			this.visible = true;
   		}
  
  		private function updatePreloadStatus(e:Event) {
   			var l = this.getBytesLoaded();
   			var t = this.getBytesTotal();
   			_controls.setProgress(l, t);
   			if(l >= t){
    				this.removeEventListener(Event.ENTER_FRAME, updatePreloadStatus);
    			}
   		}
  
  		private function updateSeekStatus(e:Event) {
   			
   			var curr:Number = this.getCurrentTime();
   			var dur:Number = this.getDuration();
   			
   			_controls.setSeekProgress(curr, dur);
   
   		}
  
  
  		private function checkPlayerReady(e:Event) {
   			_count++;
   			if(_count >= 50){
    				this.removeEventListener(Event.ENTER_FRAME, checkPlayerReady);
    				dispatchEvent(new Event(YouTubeLoaderPlus.PLAYER_READY));
    			}
   		}
  
  		private function checkVideoReady(e:Event) {
   			if(this.getDuration() > 0){
    				this.removeEventListener(Event.ENTER_FRAME, checkVideoReady);
    				dispatchEvent(new Event(YouTubeLoaderPlus.VIDEO_READY));
    			}
   		}
  
  		private function playPressed(e:Event):void {
   			this.play();
   		}
  
  		private function hideHintControls():void {
   			_circle.visible = false;
   			_hintClip.visible = false;
   			_circle.startDrag(true);
   		}
  
  		
  		private function stopPressed(e:Event):void {
   			this.pause();
   		}
  
  		override public function loadVideoById (id:String, startSeconds:Number = 0):void{
   			super.loadVideoById(id, startSeconds);
   			this.addEventListener(Event.ENTER_FRAME, checkVideoReady);
   			hideHintControls();
   		}
  
  		override public function play():void{
   			super.play();
   			this.addEventListener(Event.ENTER_FRAME, updateSeekStatus);
   			_editing = false;
   			hideHintControls();
   		}
  
  		public function set editable(e:Boolean):void {
   
   			_editable = e;
   			_circle.visible = e;
   
   			if(_editable){
    
    				this.addEventListener(MouseEvent.MOUSE_DOWN, mouseIsDown);
    				this.addEventListener(MouseEvent.MOUSE_UP, mouseIsUp);
    				_controls.addEventListener(MouseEvent.ROLL_OVER, controlsRolledOver);
    				_controls.addEventListener(MouseEvent.ROLL_OUT, controlsRolledOut);
    
    			} else {
    
    				this.removeEventListener(MouseEvent.MOUSE_DOWN, mouseIsDown);
    				this.removeEventListener(MouseEvent.MOUSE_UP, mouseIsUp);
    				_controls.removeEventListener(MouseEvent.ROLL_OVER, controlsRolledOver);
    				_controls.removeEventListener(MouseEvent.ROLL_OUT, controlsRolledOut);
    
    			}
   		}
  
  		public function get editable():Boolean {
   			return _editable;
   		}
  
  	
  	};
};
When tested, the screen looks like this:

article18_img22.png
The last thing we need here are two buttons. Place two buttons on the stage named save_btn and cancel_btn, respectively.
article18_img23.png
We will discuss the role of those two buttons in the next articles.
With this, the administration part is done! There are some things missing, like adding the results to database etc., but those are beyond the scope of this article. The last article of this series will deal with the issue how to insert those results into a mySQL database.
In the next article, we will deal with the front end of the application that will display the videos stored in the database. We will also work on the main gameplay of the application. But in order for that to work, we need a working admin part that was described in this article. Stay tuned until the next article!


To view the entire series please visit http://www.insideria.com/series-facebook-dev.html

Read more from Mirza Hatipovic. Mirza Hatipovic's Atom feed mirzahat on Twitter

Comments

1 Comments

Chris said:

got the following errors after creating the text fields and button:

1119: Access of possibly undefined property buttonMode through a reference with static type flash.display:SimpleButton.

Leave a comment


Tag Cloud

Question of the Week: Dream App

If you had an unlimited budget and unlimited resources what application would you build and why would you build it?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.