Category: ActionScript

Simplifying StageVideo with StageVideoProxy

Adobe recently took a large step in changing the way video gets rendered in the Flash player. For the most part the player has always taken a large role in rendering video to the screen. With the release of Flash player 10.2 this has all changed. Now if you explicitly tell the player to, you can offload the video completely to the hardware which decreases CPU usage (literally to 0%), lowers memory usage, enables higher frame rates, and overall enables greater pixel fidelity and video quality.

Obviously with all the upcoming phones, tablets, and connected TVs this has an even higher impact on a video applications experience. At Dreamsocket we’ve been fortunate enough to be an early adopter with projects on these platforms. From developing video player’s tailored for Android phones to creating Cartoon Network and Adult Swim’s GoogleTV apps, we’ve been able to see the impact this new change has, in addition to figuring out the best way to integrate it into existing applications.

All that said, the one thing that really struck us was how unique the new StageVideo API is. I say unique because it is a completely new API and is completely different than working with the Video object. I understand the differences, however do you really need to force someone to go back and recode everything just to use it? I know it forces a user to explicitly think and spend time putting StageVideo into an app, but why? It should be EASY. I mean really EASY.

Enter StageVideoProxy. We wanted to go into all of our video applications and simply swap out a Video object reference for a new Class that would use StageVideo if present and fallback to using a standard Video object if it failed. By having a simple class that extended Video, we could just swap out the instance and proxy all the calls to StageVideo when it was available. This made it EXTREMELY easy to retro fit any existing application with StageVideo functionality. In addition, we kept all 10.2 API references in places that would not get called in players below 10.2, making the code backwards compatible.

Super right? Well we did find a few limitations. First, StageVideo doesn’t really have a way to clear the video, so we had set the size to 0 width and 0 height to “clear” it. Second, we don’t really know depth of display objects vs other display objects easily, so we don’t have a way to determine z depth for the videos. The second step doesn’t really matter though, given the implementation currently only supports a single StageVideo instance. I’d like to add support for multi videos, but I’m still unclear on how many I can have and what dictates that. I understand how to know when they are there and when you hit your limit, but I’d still like to fully understand it before I add it in.

Regardless, this is super useful code so merry nerdmas! Here you go in all its MIT licensed glory

/**
* Copyright (c) 2010 Dreamsocket Incorporated.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**/


package com.dreamsocket.media 
{
	import flash.events.Event;
	import flash.events.StageVideoEvent;
	import flash.events.StageVideoAvailabilityEvent;
	import flash.events.VideoEvent;
	import flash.geom.Rectangle;
	import flash.media.StageVideo;
	import flash.media.StageVideoAvailability;
	import flash.media.Video;
	import flash.net.NetStream;
	
	public class StageVideoProxy extends Video
	{
		protected var m_netStream:NetStream;
		protected var m_stageVideo:Object;
		
		
		public function StageVideoProxy (p_width:int = 320, p_height:int = 240)
		{
			super(p_width, p_height);

			this.addEventListener(Event.ADDED_TO_STAGE, this.onAddedToStage);
			this.addEventListener(Event.REMOVED_FROM_STAGE, this.onRemovedFromStage);
		}
	
	
		override public function set height(p_value:Number):void
		{
			if(p_value != this.height)
			{
				super.height = p_value;
				this.layoutView();
			}
		}
		
		
		override public function get videoHeight():int
		{
			return this.m_stageVideo ? this.m_stageVideo.videoHeight : super.videoHeight;
		}
		
				
		override public function get videoWidth():int
		{
			return this.m_stageVideo ? this.m_stageVideo.videoWidth : super.videoWidth;
		}
	
			
		override public function set width(p_value:Number):void
		{
			if(p_value != this.width)
			{
				super.width = p_value;
				this.layoutView();
			}
		}
		
				
		override public function set x(p_value:Number):void
		{
			if(p_value != this.x)
			{
				super.x = p_value;
				this.layoutView();
			}
		}
		
		
		override public function set y(p_value:Number):void
		{
			if(p_value != this.y)
			{
				super.y = p_value;
				this.layoutView();
			}
		}

		
		override public function attachNetStream(p_stream:NetStream):void
		{
			if(p_stream != this.m_netStream)
			{
				this.m_netStream = p_stream;
				this.teardownStream();
				this.setupStageVideo();
			}
		}
		
		protected function setupSpriteVideo():void
		{		
			this.m_stageVideo = null;
			super.attachNetStream(this.m_netStream);		
		}
		
		
		protected function setupStageVideo():void
		{	// only setup the view when video is on stage and there is a netstream attached
			// this helps prevent as much as possible the time when a StageVideo is initialized
			if(!this.stage || !this.m_netStream) return;
			
			try
			{
				if(!this.m_stageVideo && this.stage.stageVideos.length >= 1)
				{
					this.m_stageVideo = this.stage.stageVideos[0];
					this.m_stageVideo.addEventListener(StageVideoEvent.RENDER_STATE, this.onRenderStateChanged);
					this.layoutView();
				}
				
				if(this.m_stageVideo)
				{
					this.m_stageVideo.attachNetStream(this.m_netStream);
				}
				else
				{
					this.setupSpriteVideo();
				}
			}
			catch(error:Error)
			{
				this.setupSpriteVideo();
			}
		}
		
		
		protected function teardownStream():void
		{	
			try
			{
				if(this.m_stageVideo)
				{
					this.m_stageVideo.viewPort = new Rectangle(this.x, this.y, 0, 0);
					this.m_stageVideo.attachNetStream(null);
				}
				else if(this.m_netStream)
				{
					super.attachNetStream(null);
					this.clear();
				}
				
			}
			catch(error:Error){}
		}	
		
			
		protected function layoutView():void
		{
			if(this.m_stageVideo)
				this.m_stageVideo.viewPort = new Rectangle(this.x, this.y, this.width, this.height);
		}

		
		protected function onAddedToStage(p_event:Event):void
		{
			//this.stage.addEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, this.onStageVideoAvailabilityChanged);
			this.setupStageVideo();
			this.layoutView();
		}
		
		
		protected function onRemovedFromStage(p_event:Event):void
		{	
			//this.stage.removeEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, this.onStageVideoAvailabilityChanged);
			this.teardownStream();
		}
		
				
		protected function onRenderStateChanged(p_event:Event):void
		{
			switch(StageVideoEvent(p_event).status)
			{
				case VideoEvent.RENDER_STATUS_UNAVAILABLE: 
					this.teardownStream();
					this.setupStageVideo();
					break;
			}
		}
		
		
		protected function onStageVideoAvailabilityChanged(p_event:Event):void
		{
			this.teardownStream();
			this.setupStageVideo();
		}
	}
}

Embedding JavaScript into a SWF

Developers that have been using Flex should be pretty familiar by now with the Embed metadata tag which allows you to embed assets into a SWF (like images, SWFs, XML, etc). This option has also been added to CS4 (CS4 uses the Flex SDK to complete this task).

What some of you may not know or though of, is that you can actually embed JavaScript libraries into your SWF and have your SWF write those libs to the pages DOM. We’ve been using this technique for a while. However, when someone asked for a reference on the subject, I searched the web and didn’t find one. So here we are :).

This technique is quite simple. The basics of it are that you embed the JavaScript library using the embed syntax, then create a Class that references it when instantiated, instantiate that class, get the string representation of the instance, and send that string to the page to be embedded using a JavaScript eval statement.

To illustrate this, lets create a JavaScript file called hello.js which has a single function hello that throws an JS alert.

Hello.js

function hello()
{
     alert("hello");
}

Now lets create the ActionScript file that embeds the JS into a SWF, writes it to the page, and calls the hello function in the lib.

package 
{
	import flash.display.Sprite;
	import flash.external.ExternalInterface;

	public class EmbeddedJavaScriptExample extends Sprite
	{
		// embed the JavaScript into the SWF
		[Embed(source="hello.js", mimeType="application/octet-stream")]
		// create a class that can instantiate the JavaScript for embedding
		private static const HelloJS:Class;
		
		public function EmbeddedJavaScriptExample()
		{
			if (ExternalInterface.available)
			{
				// embed the JavaScript to the page
				ExternalInterface.call("eval", new HelloJS().toString());
				
				// the embedded JavaScript has a function call named hello
				// now that it has been embedded to the page call it
				ExternalInterface.call("hello");
			}
		}
	}
}

You can download the full example here.

Pretty nifty eh? Hope this shows you a neat little trick to use in distributing some of your SWF/JS libraries.

Event type naming: qualifying vs simple

I have put a lot of thought into event naming recently. In my research, I’ve seen a few developers fully qualifying their event type names. This is something I actually debated myself when writing our Media Framework, but opted not to do. The subject is debatable, so let me describe what I mean by fully qualified names and why I decided not to use them. Based on the points I outline you can make your own decision of whether to use them yourself.

What is a fully qualified event type name?
ActionScript 3 has a formal event framework where objects dispatch events and others subscribe to them and react accordingly. Each event dispatched is represented by an event object. All of the native AS3 event objects follow a formal convention of defining the types of events they can be dispatched as. This convention places a static constant representing the type name directly on their class to allow for strict typing. For example, Event.RESIZE denotes an event type of “resize” for the flash.events.Event object. The property value itself equates to a simple string. In all native AS3 objects these strings are simple and only represent the action (ex: “resize”).

Some programmers are actually fully qualifying these strings. Instead of Event.RESIZE representing the string “resize”, it is equal to “flash.events.Event.RESIZE”. Now why do this? Well say you had another event ComponentEvent which had a resize event. If you fully qualified it as well, you would have ComponentEvent.RESIZE equating to “com.dreamsocket.events.ComponentEvent.RESIZE”. Notice that now both events could be thrown from the same object and subscribed to distinctly. If they both represented the string “resize” then you would run into cases where you thought you subscribed to one event but would potentially receive both.

Why I chose not to use full qualifed names
Even though fully qualifying the string that represents the event type resolves subscription conflicts, for the most part you can resolve them just by prepending your event’s name to the type. ComponentEvent.RESIZE could be “componentResize”. In a sense this allows you to qualifying it without having a very long unique string. This is what I opted to do. One of the reasons I did this was for less advanced users and for code spiking. Simply, if you want to do things fast, it is easier just to type in a short magic string when listening to an event vs actually importing in the class and typing it out statically.

foo.addEventListener("componentResize", this.onEvent);

vs

import com.dreamsocket.events.ComponentEvent.RESIZE;
foo.addEventListener(ComponentEvent.RESIZE, this.onEvent);

Yes, this is kind of the lazy approach and one might also say its bad practice since without strict typing it could result in a “magic error”. However, I do it when I’m trying to spike an idea real fast, and I know others do it as well. I’d even venture to say designer/developer hybrids are especially prone to use it since it serves as a comfort concept they brought over from ActionScript 2. It is a practice that exists and I can’t say whether it is right or wrong.

Summary
In summary, fully qualified event types have a purpose and serve their purpose well. In our case, we wanted to take into account developers who were used to exploiting existing conventions. I feel even bad habits have advantages if it creates productivity, so the coin toss resulted in a choice not to take them away.

What are your thoughts?

Importance of Event.clone

In Actionscript 3, the Event object has a method called clone.  This method does exactly what it says, it clones or duplicates the Event object, returning an entire new instance with the same values for all properties. The importance of this method comes into play not when you call it directly, but when the player calls it behind the scenes. As stated in the docs,

“When creating your own custom Event class, you must override the inherited Event.clone() method in order for it to duplicate the properties of your custom class. If you do not set all the properties that you add in your event subclass, those properties will not have the correct values when listeners handle the redispatched event.”

After yesterday I feel like I don’t pay attention well, because for some reason I was thinking that clone only got called by the player when bubbling an event in the display list. Yet as clear as it is stated in the docs, this occurs in non display objects which subscribe to an event, then redispatch it using their own dispatchEvent method.  In these cases, if you don’t implement clone, then your custom event will cast to the base Event class and will not contain any of it’s properties. This will in turn throw runtime errors.

I always implement the clone method in my Event sub classes, but somehow missed it in one and was wondering why it was casting to Event. Though it confused me for a minute, at least it opened my eyes to a simple concept I overlooked. Hopefully this post opens anyone else’s who didn’t realize the player’s behavior with clone.

Event type naming: Tense

I tend to be a real stickler when it comes to naming elements of code that I know are going to be part of a widely used API. It’s odd to me that there is not, or I have just not been privileged to find, a good resource on the subject.

Events are a programming paradigm that exist in several different languages. However, even though the concept is exactly the same, the naming tends to differ in tense a lot. In many languages you see things with past tense like Closed versus Flash which has a present or transient tense like Close (Event.CLOSE).

Which is right?

Well neither is right or wrong, but I do believe one is better. If you take the example Close, there are actually different event states, Closing and Closed. By using just Close you have no idea of what state that truly represents.

What impact does this tense have?

If you look at the function that is creating the Close event, where does it emmit it? Is it at the top of the function or at the bottom? If the event is cancellable and that cancel has effects (like it stops executing the rest of the function), then tense is very important to know. For example, you may have a dialog closing, but if you haven’t saved information in it yet you want to stop it from closing.  If it is ok to close, then you may want to allow it to close, but clean up information on close. In this case you want detailed information of the Event breakdown. The present tense Close does not denote that. Therefore, it is better to use Closing, which gives you the option of an additional Closed to represent the same event in a latter state. This holds true with all Event types.

There is a lot of power in just how you name things, so give it a little thought.