Vimeo Moogaloop Actionscript API In Flex 4

I wanted created a video switcher for Vimeo videos using Flex 4. I found my way to the Vimeo Moogaloop API page where there seemed to be a harmless Actionscript 3 class demonstrating a Flash application. Moogaloop is the name Vimeo uses for the embedded video player.

The Moogaloop player is a Flash movie that must be loaded into your Flash movie. The example on the Moogaloop API page is an example of that process. The Moogaloop player contains the API you need to interface with the video player once it is loaded.

The example showed an interface, api_loadVideo(id), to load a video into the Moogaloop player. The way the example worked is that loading of the Moogaloop player requires a video to load. Then the interface suggests this api_loadVideo is how you load a second.

I set up an Actionscript project in Flex Builder 4 and found the api_loadVideo(id) code was not loading videos. I posted my findings on the Vimeo api forum (see Does moogaloop.api_loadVideo( id); work in Flash example work?) and fairly promptly got a reply to try it again where it now worked. So it appeared I hit a bug and it was fixed.

I found more issues with switching videos with Moogaloop. They all centered around the video controls not maintaining a consistent state. After extended two day effort of multiple trial and error sessions, I solved most of the issues. The one issue still hanging is that the auto hide state of the player controls stays on after the first video plays. So if you load a second video, the player controls will auto hide until mouse rolls over the player. The user interface expectation of a new video being keyed for a first play is to have the player controls visible until the video plays. I added this observation to the original forum post to see if we get some updates that make switching videos more seamless to the user.

At this point I just have a Flex project for downloads.


Application Class – VimeoMoogaloopEx01_Flex.mxml
This is the main MXML file. Nothing special here other than using the new SpriteVisualElement component to contain the VimeoPlayer class. There are two buttons switching the videos.

Note you must get an Application id from Vimeo and put that on line 21.

Also add Vimeo video ids on lines 25 and 29.

<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* Demo of Vimeo Moogaloop video player switching between two videos.
* */
-->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx" 
			   creationComplete="creationCompleteHandler(event)"
			   width="800" height="400">
	<fx:Script>
		<![CDATA[
			import com.vimeo.moogaloop.VimeoPlayer;
			
			import mx.events.FlexEvent;
			
			/**
			 * Vimeo developer id.
			 * */
			private const VIMEO_ID:String = "YOUR VIMEO APPLICATION ID GOES HERE";
			/**
			 * Vimeo video id for the first loaded video.
			 * */
			private const VIDEO_ID_1:int = PUT A VIMEO VIDEO ID HERE;
			/**
			 * Vimeo video id for the second video.
			 * */
			private const VIDEO_ID_2:int = PUT ANOTHER VIMEO VIDEO ID HERE;
			/**
			 * Properties for background
			 */
			private static const backgroundColor:Number = 0x333333;
			/**
			 * The Vimeo Moogaloop video player.
			 * */
			private var vimeoPlayer:VimeoPlayer;
			/**
			 * Event handler for creationComplete event.
			 * */
			protected function creationCompleteHandler(event:FlexEvent):void
			{
				// Create the Vimeo VideoPlayer class instance.
				vimeoPlayer = new VimeoPlayer(VIMEO_ID,VIDEO_ID_1,460,300);
				// Add to SpriteVisualElement
				vimeoVideoPlayer.addChild(vimeoPlayer);
				// Disable the first video switching button.
				video1.enabled = false;
			}
			/**
			 * Event handler for the first video button MouseEvent.CLICK event.
			 * */
			protected function video1_clickHandler(event:MouseEvent):void
			{
				// Load the video.
				loadVideo(VIDEO_ID_1);
				// Reset the video switching button states.
				video1.enabled = false;
				video2.enabled = true;
			}
			/**
			 * Event handler for the second video button MouseEvent.CLICK event.
			 * */
			protected function video2_clickHandler(event:MouseEvent):void
			{
				// Load the video.
				loadVideo(VIDEO_ID_2);
				// Reset the video switching button states.
				video1.enabled = true;
				video2.enabled = false;
			}
			/**
			 * Loads a new video into the Vimeo Moogaloop player.
			 * */
			private function loadVideo(videoId:int):void
			{
				vimeoPlayer.loadVideo(videoId);
			}
		]]>
	</fx:Script>
	<!--
	UI
	--> 
	<s:BorderContainer id = "background_bc"
					   width="{width}" height = "{height}"
					   backgroundColor="{backgroundColor}">
		<s:layout>
			<s:VerticalLayout horizontalAlign="center"/>
		</s:layout>
		<s:SpriteVisualElement id = "vimeoVideoPlayer" width="460" height="350"/>
		<mx:HBox> 
			<s:Button id = "video1" label="Video 1" click="video1_clickHandler(event)" />
			<s:Button id = "video2" label="Video 2" click="video2_clickHandler(event)" />		
		</mx:HBox>
	</s:BorderContainer> 
</s:Application>


VimeoPlayer Class
This is the class on the Vimeo Moogaloop API web site. It was modified to handle the mouse event override from the stage to the parent on line 108, removal of the player_mask Sprite and addition of seek and pause calls to the loadVideo method on lines 181 and 182.

Still not fully sure of the player_mask need for Flex. However upgrading the loader using Flex components is in order which I will explore if I stay with Vimeo for the project I have for a Vimeo user.

package com.vimeo.moogaloop
{
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import flash.net.URLRequest;
	import flash.system.Security;
	import flash.utils.Timer;
	/**
	 * Vimeo Moogaloop player class from the Vimeo web site: http://vimeo.com/api/docs/moogaloop.
	 * <p>Modified to handle issues with playing in Flex. Mask removed. 
	 * See comments where other changes were made.</p>
	 * */
	public class VimeoPlayer extends Sprite
	{
		/**
		 * Sprite container for the Moogaloop Flash movie.
		 * */
		private var container:Sprite = new Sprite(); 
		/**
		 * The Moogaloop player.
		 * */
		private var moogaloop:Object = false; 
		/**
		 * Default width of the player.
		 * */
		private var player_width:int = 400;
		/**
		 * Default height of the player.
		 * */
		private var player_height:int = 300;
		/**
		 * Timer to delay until the Moogaloop Flash movies is loaded. Could be switched for a Flex component.
		 * */
		private var load_timer:Timer = new Timer(200);
		/**
		 * param oauth_key Vimeo developer key
		 * param clip_id Vimeo video id.
		 * param clip_id Vimeo video id.
		 * */
		public function VimeoPlayer(oauth_key:String, 
										   clip_id:int, 
										   w:int, 
										   h:int, 
										   fp_version:int = 10)
		{
			init(oauth_key, clip_id, w, h, fp_version);
		}
		/**
		 * Initialize and load Vimeo Moogaloop player.
		 * */
		private function init(oauth_key:String,
							  clip_id:int, 
							  w:int, 
							  h:int, 
							  fp_version:int = 10):void
		{
			this.setDimensions(w, h);
			Security.allowDomain("*");
			Security.loadPolicyFile("http://vimeo.com/moogaloop/crossdomain.xml");
			var loader:Loader = new Loader();
			var request:URLRequest = new URLRequest("http://api.vimeo.com/moogaloop_api.swf?oauth_key=" + oauth_key + "&clip_id=" + clip_id + "&width=" + w + "&height=" + h + "&fullscreen=0&fp_version=" + fp_version);
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
			loader.load(request);
		}
		/**
		 * Store the dimensions.
		 */
		private function setDimensions(w:int, h:int):void 
		{
			player_width  = w;
			player_height = h;
		}
		/**
		 * Handler for the URLRequest Event.COMPLETE event. Moogaloop Flash movie
		 * loading complete.
		 */
		private function onComplete(e:Event):void 
		{
			// Add Moogaloop Flash movie to container Sprite.
			container.addChild(e.target.loader.content);
			// Provide reference to Moogaloop Flash movie API.
			moogaloop = e.target.loader.content;
			// Add the container to this.
			addChild(container);
			// Timer to wait for Moogaloop Flash movie to complete setup.
			load_timer.addEventListener(TimerEvent.TIMER, playerLoadedCheck);
			load_timer.start();
		}
		/**
		 * Handler for load_timer TimerEvent.Timer event. 
		 * Check for Moogaloop to finish setting up.
		 */
		private function playerLoadedCheck(e:TimerEvent):void 
		{
			// Moogaloop is finished configuring
			if (moogaloop.player_loaded) 
			{
				load_timer.stop();
				load_timer.removeEventListener(TimerEvent.TIMER, playerLoadedCheck);
				// remove moogaloop's mouse listeners listener
				moogaloop.disableMouseMove();
				// Add MouseEvent.MOUSE_MOVE event listener to parent.
				// Original Vimeo code added to stage.
				parent.addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);
				// Broadcast COMPLETE event for Moogaloop loaded.
				dispatchEvent(new Event(Event.COMPLETE));
			}
		}
		/**
		 * Handler for parent MouseEvent.MOUSE_MOVE event.
		 * Fake the mouse move/out events for Moogaloop.
		 */
		private function mouseMove(e:MouseEvent):void 
		{
			var pos:Point = this.parent.localToGlobal(new Point(this.x, this.y));
			// Mouse event occured in this rectangle representing the player.
			if (e.stageX >= pos.x && e.stageX <= pos.x + this.player_width 
				&&
				e.stageY >= pos.y && e.stageY <= pos.y + this.player_height) 
			{
				moogaloop.mouseMove(e); // Not documented. You have to have faith.
			}
			else 
			{
				moogaloop.mouseOut(); // Not documented. You have to have faith.
			}
		}
		/**
		 * Play video in Moogaloop player.
		 */
		public function play():void 
		{
			moogaloop.api_play();
		}
		/**
		 * Pause video in Moogaloop player.
		 */
		public function pause():void 
		{
			moogaloop.api_pause();
		}
		/**
		 * returns duration of video in seconds
		 */
		public function getDuration():int 
		{
			return moogaloop.api_getDuration();
		}
		/**
		 * Seek to specific loaded time in video (in seconds)
		 */
		public function seekTo(time:int):void 
		{
			moogaloop.api_seekTo(time);
		}
		/**
		 * Change the primary color (i.e. 00ADEF)
		 */
		public function changeColor(hex:String):void 
		{
			moogaloop.api_changeColor(hex);
		}
		/**
		 * Set dimensions of this instance and Moogaloop player.
		 */
		public function setSize(w:int, h:int):void 
		{
			this.setDimensions(w, h);
			moogaloop.api_setSize(w, h);
		}
		/**
		 * Load in a different video. 
		 */
		public function loadVideo(id:int):void 
		{
			moogaloop.api_loadVideo(id);
			moogaloop.api_seekTo(0); 	// Added to code on Vimeo web site.
			moogaloop.api_pause(); 		// Added to code on Vimeo web site.
			
		}
	}
}