Jul 032011
 

When doing games intended for viral distribution on the web, integrating with Facebook can be a bit cumbersome. The API seems to be primarily meant for used on a site embedded on a Facebook page, and whats worse, with a viral game you will not have control over the embedding page.

Here are a few tricks that will help you overcome those issues. However, there are still a few limitations:

  • Sending invites directly to a friend will not work, since loading the friend selection selection dialog will not be possible unless the embedding page is hosted on the canvas URL
  • It relies on use of ExternalInterface, and hence will not work if allowScriptAccess is set to “never”. The majority of gaming portals do allow external script access, but Kongregate doesn’t and very recently Newgrounds seems to have changed it so they don’t. Mindjolt does allow external script access, but asked me to remove the Facebook functionality.

Also, this is not a complete guide. You probably need decent knowledge of integrating Facebook with Flash to make much sense of it, but hopefully it could give you some ideas if you are struggling with how to get around the various issues involved with accessing the API when you have an application not hosted by yourself.

First of all, unless you plan to only have your swf only on sites that does allow script access you need to check if ExternalInterface works, and if it doesn’t disable your Facebook functionality. One would think that using ExternalInterface.available would let you know that, but it doesn’t tell you if the security sandbox will actually let you use ExternalInterface.
So instead I use the following code:

if (ExternalInterface.call("Date"))
{
	hasScriptAccess = true;
}

If ExternalInterface is available you can include the JS API in the embedding page and add the “fb-root” div. Also, I use a JS function to open the login pop-up. I use the following function to add the necessary JS to the embedding page :

private function addFBJS():void
{
	const script_js:XML =
		<script>
			<![CDATA[
			function () {						
				var body = document.getElementsByTagName('body')[0];
				var head = document.getElementsByTagName('head')[0]; 
				
				var fbDiv = document.createElement('div');
				fbDiv.setAttribute('id', 'fb-root');
				body.insertBefore(fbDiv, body.firstChild);
				
				var fbScript = document.createElement('script');
				fbScript.setAttribute('async', '');
				fbScript.setAttribute('type' ,'text/javascript');
				fbScript.setAttribute('src', 'http://connect.facebook.net/en_US/all.js');
				fbDiv.appendChild(fbScript);

				var fbWinScript = document.createElement('script');
				fbWinScript.setAttribute('type' , 'text/javascript');
				fbWinScript.text = "var fbWin = null; function fbWinIsClosed(){ return (fbWin == null || fbWin.closed); } function openLogin(url) { if (fbWinIsClosed()) { fbWin = window.open(url, 'fbwin', 'toolbar=0,menubar=0,resizable=1,width=800,height=480'); }}";
				head.appendChild(fbWinScript);
			}
			
			]]>
		</script>;
	ExternalInterface.call(script_js);
}

Just make sure to call that function before accessing the API.

One issue that turned out to be problematic was that when using Chrome I kept getting lots of warnings about x-domain access. To solve that you need to make sure that it uses the Flash protocol, instead of the default “fragments”.

I call the following function to handle browser detection and set the protocols as required. I have tested on IE, FF, Chrome and Opera. You might have to switch protocol on Safari as well, but I have not tested it and don’t know what will be used as default. Actual browser detection code is from http://www.quirksmode.org/js/detect.html

private function setXD():void
{
	const script_js:XML =
		<script>
			<![CDATA[
			var BrowserDetect = {
				init: function () {
					this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
					this.version = this.searchVersion(navigator.userAgent)
						|| this.searchVersion(navigator.appVersion)
						|| "an unknown version";
					this.OS = this.searchString(this.dataOS) || "an unknown OS";
				},
				searchString: function (data) {
					for (var i=0;i<data.length;i++)	{
						var dataString = data[i].string;
						var dataProp = data[i].prop;
						this.versionSearchString = data[i].versionSearch || data[i].identity;
						if (dataString) {
							if (dataString.indexOf(data[i].subString) != -1)
								return data[i].identity;
						}
						else if (dataProp)
							return data[i].identity;
					}
				},
				searchVersion: function (dataString) {
					var index = dataString.indexOf(this.versionSearchString);
					if (index == -1) return;
					return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
				},
				dataBrowser: [
					{
						string: navigator.userAgent,
						subString: "Chrome",
						identity: "Chrome"
					},
					{ 	string: navigator.userAgent,
						subString: "OmniWeb",
						versionSearch: "OmniWeb/",
						identity: "OmniWeb"
					},
					{
						string: navigator.vendor,
						subString: "Apple",
						identity: "Safari",
						versionSearch: "Version"
					},
					{
						prop: window.opera,
						identity: "Opera"
					},
					{
						string: navigator.vendor,
						subString: "iCab",
						identity: "iCab"
					},
					{
						string: navigator.vendor,
						subString: "KDE",
						identity: "Konqueror"
					},
					{
						string: navigator.userAgent,
						subString: "Firefox",
						identity: "Firefox"
					},
					{
						string: navigator.vendor,
						subString: "Camino",
						identity: "Camino"
					},
					{		// for newer Netscapes (6+)
						string: navigator.userAgent,
						subString: "Netscape",
						identity: "Netscape"
					},
					{
						string: navigator.userAgent,
						subString: "MSIE",
						identity: "Explorer",
						versionSearch: "MSIE"
					},
					{
						string: navigator.userAgent,
						subString: "Gecko",
						identity: "Mozilla",
						versionSearch: "rv"
					},
					{ 		// for older Netscapes (4-)
						string: navigator.userAgent,
						subString: "Mozilla",
						identity: "Netscape",
						versionSearch: "Mozilla"
					}
				],
				dataOS : [
					{
						string: navigator.platform,
						subString: "Win",
						identity: "Windows"
					},
					{
						string: navigator.platform,
						subString: "Mac",
						identity: "Mac"
					},
					{
						   string: navigator.userAgent,
						   subString: "iPhone",
						   identity: "iPhone/iPod"
					},
					{
						string: navigator.platform,
						subString: "Linux",
						identity: "Linux"
					}
				]

			};
			BrowserDetect.init();

			function() {
				if (BrowserDetect.browser == 'Chrome') {
					FB.XD._origin = window.location.protocol + '//' + document.domain + '/' + FB.guid();
					FB.XD.Flash.init();
					FB.XD._transport = 'flash';
					
				} else if (BrowserDetect.browser == 'Opera') {
					FB.XD._transport = 'fragment';
					FB.XD.Fragment._channelUrl = window.location.protocol + '//' + window.location.host + '/'
				} 
			}
			]]>
		</script>;
	ExternalInterface.call(script_js);
}

To handle the login you need to host a script on the host used for the canvas URL. It’s unfortunate that one needs external dependencies, but otherwise you need to include you app secret in your swf, which is not a great idea.
I use the following PHP script:

<?php
    // your app id
    $app_id = "xxxxxxxxxxxxxxxxxx"; 
    // your app secret
    $app_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 
    // path to this script, located under the app URL
    $my_url = "http://example.com/facebookLogin.php";

    session_start();
	
    $code = $_REQUEST["code"];
    if (empty($code) && !isset($_GET["getstatus"])) 
	{
        $_SESSION['state'] = md5(uniqid(rand(), TRUE)); //CSRF protection
		
		$dialog_url = "https://www.facebook.com/dialog/oauth?client_id=" 
			. $app_id . "&redirect_uri=" . urlencode($my_url) . "&state="
							. $_SESSION['state'];
		echo("<script> top.location.href='" . $dialog_url . "'</script>");
    } else if ($_SESSION['state']) {
			if (!empty($code))
			{
				$_SESSION['code'] = $code;
			}
			if($_SESSION['code'])
			{
				$token_url = "https://graph.facebook.com/oauth/access_token?"
				  . "client_id=" . $app_id . "&redirect_uri=" . urlencode($my_url)
				  . "&client_secret=" . $app_secret . "&code=" . $_SESSION['code'];

				$response = file_get_contents($token_url);
				
				$params = null;
				parse_str($response, $params);

				$graph_url = "https://graph.facebook.com/me?access_token=" 
				  . $params["access_token"];

				$user = json_decode(file_get_contents($graph_url));
				if(isset($_GET["getstatus"]))
				{
					echo "session=true&uid=".$user->id."&userName=".$user->name;
				} else
				{	
					echo("<script>setTimeout(top.location.href='channel.html', 1000);</script>");
				}
			} else
			{
				echo "session=false";
			}
	} else 
	{
		if(isset($_GET["getstatus"]))
		{
			echo "session=false";
		} else
		{
			echo("<script>setTimeout(top.location.href='channel.html', 1000);</script>");
		}
	}
?>

Notice the “channel.html”. This is a simple HTML file used for two purposes. It acts as a cross-domain scripting channel, and it self-closes the window.
The contents of the file looks like this:

<script src="http://connect.facebook.net/en_US/all.js"></script>
<script>self.close();</script>

Name that “channel.html” and put it at your canvas URL.

Now, to log in in your flash application you have to first initialize:

Facebook.init(_appId, onFBInit, {channelUrl:_appUrl + "channel.html"} );

protected function onFBInit(result:Object,fail:Object):void
{
	if (!result) return;
	_session = result as FacebookSession;
	if (result && result.user)
	{
		_loggedIn = true;
		_session = new FacebookSession();
		_session.user = { name:result.user.name, id:result.user.id };
	} 	
}

_appId is of course the application id you got from Facebook, and _appUrl is the canvas URL where you uploaded “channel.html”.

The Facebook.init callback can be a bit unreliable, but if it works onFBInit will store any current session to avoid the need to log in again.

Then to log in the user, check if the session user is set, otherwise open the popup. Also set a timer to check with PHP when the session cookie has been set.
I use TweenLite for the timer, AS3Signals for events and Destroytoday’s promise to handle async responses. But this is just to give you an idea about how the login process will work, and of course you can use the built in timer and events instead:

public function login(caller:String=null):Promise
{
	_loginPromise = new Promise();
	_checkLoginAttempt = 0;
	if (!_session || !_session.user) 
	{
		openLogin(_appUrl + "facebookLogin.php");
	}
	TweenLite.delayedCall(8, checkLogin);
	return _loginPromise;
}

private function openLogin(loginUrl:String):void
{
	ExternalInterface.call("openLogin", loginUrl);
}

private function checkLogin():void
{
	if (!_session || !_session.user)
	{
		var req:URLRequest = new URLRequest(_appUrl + "facebookLogin.php?getstatus=1");
		var loader:URLLoader = new URLLoader(req);
		var loadedSignal:NativeSignal = new NativeSignal(loader, Event.COMPLETE);
		loadedSignal.add(onCheckLogin);
	} else
	{
		start();
	}
}

private function onCheckLogin(e:Event):void
{
	var loader:URLLoader = e.currentTarget as URLLoader;
	var vars:URLVariables;
	if (_checkLoginAttempt == 5)
	{
		var winClosed:Boolean = ExternalInterface.call("fbWinIsClosed");
		if (winClosed == true)
		{
			_loginPromise.dispatchError("Could not read Facebook cookie. Please try again.");
			_loginPromise.dispose();
			return;
		}
	}
	if (loader.data) vars = new URLVariables(loader.data);
	if (vars && vars.session == "false" && vars.isset == "true")
	{
		_loginPromise.dispatchError("Could not log in to Facebook. Cookie not valid and has been reset. Please try again.");
		_loginPromise.dispose();
		return;
	}
	if (!vars || !vars.session || vars.session == "false")
	{
		_checkLoginAttempt++;
		TweenLite.delayedCall(2, checkLogin);
	} else
	{
		_session = new FacebookSession();
		_session.user = { name:vars.userName, id:vars.uid };
		start();
	}
}

Now everything should be set up and you can do your API calls as usual. For example to post to the wall:

public function postToWall(name:String, 
				    prompt:String, 
				    message:String, 
				    caption:String, 
				    link:String, 
				    imagePath:String):void
{
	var o:Object = 
	{
		user_message_prompt: prompt,
		message: message,
		attachment: 
		{
			media: [{
				type: "image",
				href: link,
				src: imagePath
			}],
			name: name,
			href: link,
			caption: caption,
			description: ""
		},
		action_links: 
		[{ 
		   text: prompt, 
		   href: link 
		}],
		next:_appUrl + "channel.html"
	};
	Facebook.ui("stream.publish", o, null, "popup");
}

As you can see it’s all very obvious, simple and straightforward ;)

I just hope Google+ will take over from Facebook in the not too distant future, since I can’t imagine that their API will be anywhere nearly as tedious to work with.
And of course, keep in mind that this information will likely be outdated in a few months when Facebook decides to make changes to their API once again.

Share/Bookmark
Nov 032010
 

I have been working on a little application which requires mixing of a number of audio tracks. It should be multi-screen and run on PC’s as well as Android devices.
Ideally I would like it to be able to mix at least some 12 tracks, but making that happen on Android is proving to be very difficult. There are a few ways to do the calculations, and the benefits of different techniques varies depending on the machine running the code.

In a previous post I showed some code for a audio mixer made with Pixel Bender.
It works great on my PC, drastically reducing the CPU load compared to simply using ByteArray.readFloat and ByteArray.writeFloat.
Unfortunately it does not provide the same performance gains when I run the application on Android, either in the Flash Player or as an AIR app. In fact it does slightly worse that simply mixing in AS. I’m not sure why that is, but I guess it has something to do with threading.

So I have been thinking of possible solutions to squeeze out a few more tracks on Android. My first idea was that since there is no need for stereo audio, there could be some way to do the mixing in mono. But since Sound.extract will only produce a stereo output and one needs to write to SampleDataEvent.data in stereo it does not seem possible to reduce CPU strain much that way. When mixing in pure AS it removes the need for multiplying every second sample with the volume value, but that is pretty much it. You still need to increment the pointer when reading the audio data on every second sample, which seems to be about as taxing as reading an extra float that one does not use. And of course one needs to write twice to the SampleDataEvent.data. So any gains is very marginal. If Sound.extract had a flag to extract audio as mono it might have been possible to make a noticeable increase in track count by mixing in mono.
Also, Pixel Bender does not support single channel inputs and outputs, so I could not figure out a way to make a shader for mono mixing that reduces CPU load.

Another option I considered was using fast memory solutions such as Azoth or Apparat. It turns out to be a no-go since it requires the ByteArray to be little endian, and SampleDataEvent.data is big endian. Having to flip the bits for each sample would surely turn out to negate any benefits of the faster memory access.

Also Vector is faster than ByteArray, and I have seen claims that on Android it’s even faster than Alchemy memory. But since Sound.extract returns a ByteArray there is nothing to be gained by using a Vector for straight mixing since the samples are only accessed once.

So no matter what I try I seem to be hitting a dead end. The best track count I get on Android is still using basic mixing in AS, and on the PC Pixel Bender performs a lot better. So currently I have decided to keep two different engines in the application and switch between them depending on the device used.
Of course, if anyone can think of a solution I have not explored or are aware of any errors in my conclusions, your input would be greatly appreciated.

Oct 232010
 

Pixel Bender can be a great tool for number crunching, but has a couple of limitations when trying to create an audio mixer.

  • The number of inputs is limited when using PixelBender toolkit to compile .pbj files.
  • Even if one overcomes the limitation by writing Pixel Bender assembly, the track count is fixed to a predetermined number determined by the number of inputs you have in the .pbj.

In some cases you might want your application to act as a normal audio app where you can add as many tracks as your CPU can handle, but not use more CPU than needed for the current track count.

What we need to accomlish that is a way to dynamically create a shader with the desired number of inputs. James Ward has created pbjAS, a library that enables you to create Pixel Bender shaders at runtime, based on Nicolas Cannasees haXe library.
Also, Tinic Uro has posted Pixel Bender assembly code to create a mixer.

So I set out to use pbjAS to recreate Tinics code with a dynamic number of channels.
The result is this class:

package com.blixtsystems.audio
{
	import flash.display.Shader;
	import flash.display.ShaderJob;
	import flash.utils.ByteArray;
	import pbjAS.ops.OpAdd;
	import pbjAS.ops.OpMul;
	import pbjAS.ops.OpSampleNearest;
	import pbjAS.params.Parameter;
	import pbjAS.params.Texture;
	import pbjAS.PBJ;
	import pbjAS.PBJAssembler;
	import pbjAS.PBJChannel;
	import pbjAS.PBJParam;
	import pbjAS.PBJType;
	import pbjAS.regs.RFloat;
	/**
	 * Shader to mix audio with a dynamic number of channels
	 * @author leo@blixtsystems.com
	 */
	public class MixerShader
	{
		private var _bufferSize:int;

		private var _pbj:PBJ = new PBJ();
		private var _shader:Shader;
		private var _buffer:Vector.<ByteArray> = new Vector.<ByteArray>();

		private var _numTracks:int;

		/**
		 * Constructor
		 * @param	numTracks	track count
		 */
		public function MixerShader(numTracks:int, bufferSize:int=2048)
		{
			_numTracks = numTracks;
			_bufferSize = bufferSize;
		}

		/*-----------------------------------------------------------
		Public methods
		-------------------------------------------------------------*/
		/**
		 * Mix audio
		 * @param	data	ByteArray in which to store the result, probably SampleDataEvent.data
		 */
		public function mix(data:ByteArray):void
		{
			var mixerJob:ShaderJob = new ShaderJob(_shader, data, 1024, _bufferSize/1024);
			mixerJob.start(true);
		}

		/*-----------------------------------------------------------
		Private methods
		-------------------------------------------------------------*/
		private function assembleShader():void
		{
			var channels:Array = [PBJChannel.R, PBJChannel.G];
			var chanStr:String = "rg";
			_pbj.version = 1;
			_pbj.name = "SoundMixer";
			_pbj.parameters =
			[
				new PBJParam
				(
					"_OutCoord",
					new Parameter
					(
						PBJType.TFloat2,
						false,
						new RFloat(	0, channels)
					)
				)

			];
			_pbj.code =
			[
				new OpSampleNearest
				(
					new RFloat(1, channels),
					new RFloat(0, channels),
					0
				),
				new OpMul
				(
					new RFloat(1, channels),
					new RFloat(3, channels)
				)
			];
			var i:int;
			for (i = 0; i < _numTracks; i++)
			{
				_pbj.parameters.push
				(
					new PBJParam
					(
						"track" + i,
						new Texture(2, i)
					)
				);
			}
			for (i = 0; i < _numTracks; i++)
			{
				_pbj.parameters.push
				(
					new PBJParam
					(
						"volume" + i,
						new Parameter
						(
							PBJType.TFloat2,
							false,
							new RFloat(i + 3, channels)
						)
					)
				);
			}
			for (i = 0; i < _numTracks-1; i++)
			{
				_pbj.code.push
				(
					new OpSampleNearest
					(
						new RFloat(2, channels),
						new RFloat(0, channels),
						i+1
					),
					new OpMul
					(
						new RFloat(2, channels),
						new RFloat(i+4, channels)
					),
					new OpAdd
					(
						new RFloat(1, channels),
						new RFloat(2, channels)
					)
				);
			}
			_pbj.parameters.push
			(
				new PBJParam
				(
					"output",
					new Parameter
					(
						PBJType.TFloat2,
						true,
						new RFloat(1, channels)
					)
				)
			);

			var pbjBytes:ByteArray = PBJAssembler.assemble(_pbj);
			_shader = new Shader(pbjBytes);
			_buffer = new Vector.<ByteArray>(_numTracks);

			// initialize the shader inputs
			for (i = 0; i < _numTracks; i++) {
				_buffer[i] = new ByteArray();
				_buffer[i].length = _bufferSize * 4 * 2;
				_shader.data["track" + i]["width"] = 1024;
				_shader.data["track" + i]["height"] = _bufferSize / 1024;
				_shader.data["track" + i]["input"] = _buffer[i];
				_shader.data["volume" + i]["value"] = [1, 1];
			}

		}

		/*-----------------------------------------------------------
		Getters/Setters
		-------------------------------------------------------------*/
		public function get numTracks():int { return _numTracks; }
		public function set numTracks(value:int):void
		{
			// needs to be at least one input, and no point reassembling pbj if track count has not changed
			if (_numTracks < 1 && _numTracks == value) return;

			_numTracks = value;
			assembleShader();
		}

		public function get buffer():Vector.<ByteArray> { return _buffer; }

	}

}

To use it you need to download pjbAS from James Ward and include the swc in your project.

Then in your audio engine do the following:

static public const BUFFER_SIZE:int = 2048;

// number of tracks (minimum 1)
private var _numTracks:int = 1;
// instantiate mixer shader
private var _mixerShader:MixerShader;
_mixerShader = new MixerShader(_numTracks, BUFFER_SIZE);

// here you have your sound objects stored
// adding or removing objects will change number of tracks in the shader
public var sounds:Vector.<Sound> = new Vector.<Sound>();

// SampleDataEvent callback
private function samplesCallback(e:SampleDataEvent):void
{
	_numTracks = sounds.length;

	// update number of track for the shader, causing pbj to recompile
	_mixerShader.numTracks = _numTracks;

	// extract audio data into the shader buffers
	for (var i:int = 0; i <  _numTracks; i++)
	{
		_mixerShader.buffer[i].position = 0;
		sound[i].extract(_mixerShader.buffer[i], BUFFER_SIZE);
		_mixerShader.buffer[i].position = 0;
	}
	// do shader job
	_mixerShader.mix(e.data);
}

According to Tinic Uro, the number of inputs in a shader is limited to 15, so adding more tracks than that will probably not work.

In my tests, playing back eight tracks using pure AS3 will often cause CPU peak usage over 16% of one 2.3 GHz core on my quad-core machine with the debug version of the Flash Player.
If I instead use Pixel Bender the peaks are rarely above 6%. Adding or removing tracks which causes the pbj to recompile, does not cause any noticeable spikes in CPU usage.
So using Pixel Bender will cut CPU usage to around 1/3 on my machine!

Big thanks to Tinic Uro, Nicolas Cannasse and James Ward who made it easy to accomplish!

Oct 112010
 

When passing numeric values to an application using a URL, sometimes one wants to keep them short and have them alphanumeric rather than numeric. Base64 contains a couple of characters that are not URL friendly, and if you remove them you are left with Base62.
I needed classes to handle that in both AS3 and PHP with static access, and based on this snippet I came up with the following:

PHP:

<?php
class Base62
{
	public static $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
	public static $base = 62;
	public static function encode($val) 
	{
		// can't handle numbers larger than 2^31-1 = 2147483647
		$str = '';
		do {
			$i = $val % self::$base;
			$str = self::$chars[$i] . $str;
			$val = ($val - $i) / self::$base;
		} while($val > 0);
		return $str;
	}

	public static function decode($str) 
	{
		$len = strlen($str);
		$val = 0;
		$arr = array_flip(str_split(self::$chars));
		for ($i = 0; $i < $len; ++$i) 
		{
			$val += $arr[$str[$i]] * pow(self::$base, $len-$i-1);
		}
		return $val;
	}
}
?>

AS3:

package com.blixtsystems.utils 
{

	/**
	 * Encode/Decode Base62
	 * @author leo@blixtsystems.com
	 */
	public class Base62
	{	
		public static var chars:String = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
		public static var base:int = 62;
		
		public function Base62()
		{
		}
		
		public static function encode(val:Number):String 
		{
			var str:String = '';
			var i:int;
			while(val > 0)
			{
				i = val % base;
				str = chars.substr(i,1) + str;
				val = (val - i) / base;
			}
			return str;
		}
		
		public static function decode(str:String):Number
		{
			var len:int = str.length;
			var val:int = 0;
			for (var i:int = 0; i < len; ++i) 
			{
				val += chars.indexOf(str.substr(i,1)) * Math.pow(base, len-i-1);
			}
			return val;
		}
	}
}
Oct 102010
 

A common requirement in applications is to have a small icon that can be used to indicate that some processing or a request is carried out, but not an actual preloader indicating progress.
On most projects lately I have been supplied graphical assets for the typical circle of lines with a rotating fade effect. In most cases a simple class not requiring any graphical assets would have sufficed, so I decided to make one:

package com.blixtsystems.utils 
{
	import com.greensock.plugins.TintPlugin;
	import com.greensock.plugins.TweenPlugin;
	import com.greensock.TweenLite;
	import flash.display.Sprite;
	import flash.events.TimerEvent;
	import flash.geom.ColorTransform;
	import flash.utils.Timer;
	/**
	 * Circle of lines with rotating fade effect
	 * @author leo@blixtsystems.com
	 */
	public class PreloadIcon extends Sprite
	{
		private var _color1:uint;
		private var _color2:uint;
		private var _w:int;
		private var _speed:int;
		private var _numLines:int;
		private var _thickness:Number;
		private var _lineLength:Number;
		private var _rotationStep:Number;
		private var _currLine:int = 0;
		/**
		 * Create preload icon
		 * @param	color1			color
		 * @param	color2			highlight color
		 * @param	w				width of central circle
		 * @param	lineLength		length of lines
		 * @param	speed			number of seconds for one rotation
		 * @param	lines			number of lines
		 * @param	thickness		thickness of lines
		 */
		public function PreloadIcon(color1:uint, color2:uint, w:int, lineLength:Number, speed:Number=2, lines:int=12, thickness:Number=6) 
		{
			_color1 = color1;
			_color2 = color2;
			_w = w;
			_lineLength = lineLength;
			_speed = speed;
			_numLines = lines;
			_thickness = thickness;
			_rotationStep = 360 / _numLines;
			draw();
		}
		
		private function draw():void
		{
			TweenPlugin.activate([TintPlugin]);
			var line:Sprite;
			var step:Number = (Math.PI * 2) / _numLines;
			var theta:Number;
			var angle:Number;
			for (var i:int = 0; i < _numLines; i++) 
			{
				line = new Sprite();
				line.graphics.lineStyle(_thickness, _color1);
				line.graphics.lineTo( 0, _lineLength);
				line.name = "line_" + i;
				addChild(line);
				theta = step * i;
				line.x = _w * Math.cos(theta);
				line.y = _w * Math.sin(theta);
				line.rotation = (i * _rotationStep)-90;
			}
			var timer:Timer = new Timer((_speed/_numLines)*1000);
			timer.addEventListener(TimerEvent.TIMER, handleTimer, false, 0, true);
			timer.start();
		}
		
		private function handleTimer(e:TimerEvent):void 
		{
			var line:Sprite = getChildByName("line_" + _currLine) as Sprite;
			var ct:ColorTransform = new ColorTransform();
			ct.color = _color2;
			line.transform.colorTransform = ct;
			TweenLite.to(line, _speed, { tint:_color1 } );
			_currLine++;
			if (_currLine == _numLines)
			{
				_currLine = 0;
			}
		}
		
	}

}

You do need TweenLite included in your project. Input parameters are explained in the source.

Sep 272010
 

Reading discussions on mobile operating systems, Android is many times pitched directly against iOS and now Windows Phone 7, and the success is measured by comparing the current market share. While that certainly currently is relevant comparison, looking into the situation long term it’s easy to underestimate what Android will mean for mobile computing.

What might have spurred Google into creating Android and what would the long term goals with the project could be?
On his first day at Google, Vic Gundotra asked Andy Rubin about the reasons for creating yet another mobile OS, since it was not really obvious why the market needed it and why Google should take that on. Andy Rubin explained that it was critical to create a free, open operating system that would enable innovation of the stack. Rubin also told him that if “Google did not act we faced a Draconian future, a future where one man, one company, one device, one carrier would be our only choice.”

Of course Google does not simply act to save humanity from Draconian dictatorship over mobile operating systems, but they realize that would be a scary future for their business. They live of search and advertising, and it was not hard to see that with the current path Apple was taking with iOS, it could be possible that they would be loosing ground when it comes to mobile users. And it seems like those fears would have been justified considering that Apple indeed tried to ban 3rd party advertising in iOS.
So by ensuring that there was a viable alternative to tightly controlled mobile operating systems, they could ensure that they would not be locked out from mobile devices. Microsoft could very well turn out to be determined to lock Google out of search and advertising as well, which would be a problem if they would be able to get back some market share. And Symbian Foundation could not keep up with innovation.

One important factor which many times seems to be ignored is that there was a very good reason why Google needed to create an open source OS for mobile devices. Currently there is a lot of hype around mobile, but in reality it’s a minuscule market share compared to people using computers. It’s currently in the region of 2% that access the Internet from a mobile device. Sure, there is a rapid growth, but for most people in industrial nations the mobile devices are a complement to computers, and mobile devices are currently very far from becoming the main means of accessing the internet in those markets.

The reason why mobile devices are predicted to rapidly go from a tiny minority of usage to dominating the web is emerging markets.
In countries where most people do not have their own phone line and computer, getting internet access used to be too expensive for ordinary people. With mobile devices they will be able to get what in practice will be a computer connected to the Internet and telephone for a very reasonable sum. We already see Android devices with a reasonable specification for less than €100 without a contract here in the EU.

For devices to take off in emerging markets there needs to be a capable operating system that can be used without licensing costs. Obviously Apple will not sell an iOS device that is affordable for the masses in those markets, and Windows Phone 7 has prohibitive licensing costs. So looking at the situation globally, Google certainly has a very clever strategy. They are now in an excellent position to ensure that their brand becomes dominant in these emerging markets, and that the use of their services becomes widespread.
I would guess that they are actually a bit stumped by the massive success Android quickly has become in developed markets, and that they planned to get the proper return on investment a few years down the line when Android will be by far the most common OS used in emerging markets.

So iOS might stay relevant for a small but affluent group of users on devices they use to do part of their browsing, playing and working. Windows Phone 7 will most likely give Microsoft at least a reasonable foothold on mobile devices in developed nations. But neither of them currently seem to have a chance of becoming a dominant platform globally. Android currently seems have nearly no competition when it comes to taking that position when the real adaptation of mobile devices starts in emerging markets.

Jun 132010
 

I happened to come across the SublimeVideo player some time back and thought it was quite a decent player, especially since the HTML5 version is a lot more fully featured than most alternatives with nice controls, and full-screen emulation.

It also provides a nice opportunity to compare performance of HTML5 and Flash video.
Not surprisingly since I’m on a windows machine the HTML5 version performed a lot worse, which is something we have seen from various benchmarks already.

They do mention that one can disable Flash fallback if desired:

Open web standard “purists” who do not care about compatibility with old browsers will be able to completely disable the Flash fallback to provide a pure HTML5 video experience.

So it’s great that one can make the contents only available to a small share of users to emulate that authentic 90′s feeling where you have to download a new browser to view some content. There is however no mention of being able to make Flash the default. I got curious how they reason when they want to give over 90% of users a worse experience than what their player actually can deliver, so I posted a question on their blog:

http://blog.jilion.com/2010/04/07/sublimevideo-flash-mode

That was the 28th of May, and it’s still not accepted or denied, but “awaiting moderation”.
This is what I wrote:

“On my Win7 machine the HTML5 version uses 25% CPU (one core) in fullsceen and otherwise 15%. With Flash, fullscreen takes 15% and otherwise 6%.

Since majority of users have windows machines it seems like a very bad choice to default to HTML5 unless the user is on a Mac. I don’t see why I would use your player if it means that the majority of my users will need almost double the CPU power to view the content.

You mention you have an option to not use Flash as a fallback. Do you have an option to make Flash the default and use HTML5 as a fallback, you know like all the major videosites do to provide users with the optimal experience?
Or is SublimeVideo player targeted at fanboys who are happy to disregard user experience to instead go with the latest fad?”

I can see that the last sentence was a bit harsh, but that is the honest impression I get when a company chooses to provide 90% of users with a worse experience than they need to, not because of technical issues or any effort needed in development. To me it seems like we are going back to the dark ages of the Internet where we make users suffer due to ignorance of developers. Providing the optimal experience for as many users as possible is something I thought was the obvious thing to do nowadays, but it seems like there are still developers that think that their views on what is the better browser or technology should be forced upon users.

UPDATE:
I got a mail from Zeno Crivelli at Jilion, and he assures me that the reason my post in their blog was not approved was a mistake, possibly due to a server migration. Their policy is to accept all posts, also those containing criticism.

He also goes on to say:

Concerning your question about making Flash the default (and use HTML5 as a fallback): it’s definitely possible and already implemented: you’ll have to add the “flash” or “force_flash” class to your video tag and we’ll handle that for you…

So, although it in my opinion would make more sense to make HTML5 the fallback, at least on Windows machines, I must take back my criticism. When setting that option their player is indeed capable of delivering the best video experience for as many users as possible.

Jun 092010
 

There has been a lot of talk about Flash security lately. First Jobs made some claims about it in his “thoughts on Flash” rant,  and now with the recent vulnerability a lot of people seem to think that he has been proven right.

But is there any truth in Flash being unusually insecure?
Jobs try to back up his FUD by mentioning a Symantec report which he claims “highlighted Flash for having one of the worst security records in 2009″. Of course he doesn’t quote anything from the report, nor provide a link. But if you actually read the report you will see that his claim is a blatant lie.

Nowhere in the report Flash is highlighted, and there is just no data in it to support that conclusion. In fact the report shows that Flash was the browser plug-in with the least reported vulnerabilities.

In the section about web browser plugins they write the following:

In 2009, Symantec documented 321 vulnerabilities affecting plug-ins for Web browsers (figure 9).
ActiveX technologies were affected by 134 vulnerabilities, which was the highest among the plug-in technologies examined. Of the remaining technologies, Java SE had 84 vulnerabilities, Adobe reader  had 49 vulnerabilities, Quicktime had 27 vulnerabilities, and Adobe Flash player was subject to 23 vulnerabilities. The remaining four vulnerabilities affected extensions for Firefox.

Considering how widespread the Flash Player is, combined with the fact that it runs scripts and has a lot of connectivity features, it’s obviously an extremely attractive target. So the fact that it has the least vulnerabilities is something that Adobe should get credit for.
So we can conclude that Jobs claim has nothing to do with the number of vulnerabilities.

Could it have to do with the severity of the vulnerabilities then?
Indeed Flash is mentioned again in the report:

Among the vulnerabilities discovered in 2009, a vulnerability affecting both Adobe reader and Flash player was the second most attacked vulnerability.

Considering that Flash is installed of some 98% of computers it is hardly surprising to find that any vulnerability will be exploited to it’s fullest. But that one of the top five attacked vulnerabilities was involving Flash can hardly be used to support Jobs claim of Flash “having one of the worst security records in 2009″.

Search trough the document for when Flash is mentioned and you notice that that there is no other mentions of the security record of Flash other than what I quoted. There is a mention in the section about browsers, where Symantec provides advice about how to secure the browser:

Browser security features and add-ons should be employed wherever possible to disable JavaScript™, Adobe Flash player, and other content that may present a risk to the user when visiting untrusted sites. Organizations should consider adopting a policy of identifying a list of whitelisted, trusted, or authorized websites and block access to all other sites. Whitelists must be actively maintained due to the risk presented when trusted sites are compromised and used to host attacks or malicious software.

Of course this is sound advice for enterprises with sensitive data to protect.
If security is vital you should only allow whatever is really necessary to run on your machines, and that obviously includes Flash. Some people try to interpret this as Symantec trying to imply that Flash is a major threat, but what it means is that Flash is a threat, just like any other piece of software, and for optimal security it should only be used when needed.

Note that they recommend disabling JS as well, meaning that HTML5 , which Jobs seems to imply would be a more secure alternative, should be avoided as well. Disabling both JS and Flash and only allowing access to trusted sites does not make for a great Internet experience, but obviously that is not particularly desirable on enterprise workstations anyway.

So in conclusion there is absolutely no basis for Jobs claims in the report he mentions, and instead it shows a very good security record for the Flash player. But the features and obliquity of the Flash Player will of course mean that hackers will do everything they can to find vulnerabilities, and when they do it has the potential to affect many people.

So the claim that Flash was highlighted is a blatant lie, and it’s not a subject up for discussion. Symantec has a section with highlights in which Flash is not mentioned once. It’s interesting to note what does get highlighted in the report though.

This is what they say on Safari in the highlight section:

Of all browsers Symantec analyzed in 2009, Safari had the longest window of exposure (the time between the release of exploit code for a vulnerability and a vendor releasing a patch), with a 13-day average; Internet Explorer, Firefox, and Opera had the shortest windows of exposure in  2009, averaging less than one day each.

Compared to other browsers this is a truly appalling security record. Not only did Safari have the second most vulnerabilities of the browsers, but average window of exposure was 13 days. Second place was grabbed by Chrome with 2 days.

Also they have the following to say about Safari:

Additionally, all browsers except Safari either remained status quo or showed an improvement in the window of exposure. This demonstrates an increased effort by vendors to minimize the
amount of time that users are exposed to exploits.

It seems quite clear that the way Jobs reads the report, according to him obscurity equals security. With a market share of a few percent obviously Safari will not make it in to any list of the most attacked vulnerabilities, but that does not make it’s security record any better. The record instead shows that Apple does not seem to care much about security when it comes to their own products.

While it’s very important that Adobe ensures that Flash is secure, there is simply no basis in fact for the claims that it has a particularly bad security record. The latest incident is unfortunate, both since it seems to be a fairly severe vulnerability, and because of the timing.

But according to Adobes security team there should be a patch to the release version tomorrow.

For those of you interested in details about the work of Adobes security team,  I recommend reading this interview.

EDIT:
I just noted that the severity of the current vulnerability is classified to “Risk Level 1: Very Low” by Symantec. Secunia on the other hand is classifying it as “extremely critical”, so I guess the jury is still out on how severe it is. But it does seem like it’s not being actively exploited on large scale yet. According to Symantec the number of infections are 0-49 and the damage level is low as well.

Jun 032010
 

Flash vs. WHATWGHeated arguments and benchmarks can be both entertaining and educating, but as they say, the proof is in the pudding.

So I thought it would be a good idea to make a list with examples of applications available today using the competing technologies. Have a look for yourself how they compare when it comes to performance, features and stability.

First of all I will use a very vague or downright incorrect definition of HTML5 since there is no good name for various combinations of HTML and JS.  So I will list good DHTML or AJAX applications as well.  I will also include examples using new functionality not yet part of the HTML5 specification when I cannot find an alternative that does adhere to the specification.

My intention is to list the best and most popular applications in each category without bias, and if you don’t agree with the examples please let me know if you can think about more representative examples.

I will update with categories and examples as I find more. Of course it’s very much appreciated with any suggestions,  so leave a comment and I will update the list as appropriate.

Paint:

Flash:


Sumopaint

HTML5:


Sketchpad

Image Editing:

Flash:


Aviary Phoenix

HTML5:


Apple demo

Slideshow Makers:

Flash:


SlideRoll

HTML5:


280Slides

Wireframe Prototyping:

Flash:


iPlotz

HTML5:


Mockingbird

Audio Players:

Flash:


EP Player

HTML5:


JPlayer

Audio Editors:

Flash:


Aviary Myna

HTML5:
N/A

Audio Synthesis:

Flash:


Audiotool

HTML5:


Clouddev Synth

Video Players:

Flash:


Strobe Media Player

HTML5:


YouTube HTML5 experiment

Video Editors:

Flash:


Creaza MovieEditor

HTML5:
N/A

Video Chat:

Flash:


Chatroulette

HTML5:
N/A

Word Processors:

Flash:


Acrobat.com

HTML5:


Google Docs

Maps:

Flash:


Yell

HTML5:


Google Maps

Data Visualization:

Flash:


IS Parade

HTML5:


HTML5 Readiness

Motion Detection:

Flash:


Free Your Dance

HTML5:
N/A

VR:

Flash:


Vatican Sistine Chapel

HTML5:


Apple VR demo

Strategy Games:

Flash:


War Of Legends

HTML5:


Freeciv

3D Action Games:

Flash:


Tanki

HTML5:


Quake II

MMORPG:

Flash:


Dofus

HTML5:


Lord Of Ultima

May 192010
 

So, now it’s official. VP8 will be open sourced and support will added to the Flash player: http://webmproject.blogspot.com/

It’s was not a difficult prediction that I mention in an earlier post:
http://www.blixtsystems.com/2010/05/why-google-bought-on2/

Notice the absence of Apple and Microsoft in the list of partners.

So what does this mean?
Like I mentioned in my earlier post, contrary to what many people seems to believe this puts the Flash Player in an excellent position when it comes to video delivery in the future. It will take a long time before most users have browsers that support VP8, while a Flash Player with VP8 can become widespread very quickly.
And with their vested interest in H.264, question is if and when Apple and Microsoft will decide to add the decoder to Safari and IE.

Also, it’s great to see many hardware partners, which means that hopefully it should not take too long before we see hardware accelerated playback of VP8 video.

Switch to our mobile site