feedback
Nov 1 2007

Flash Streaming with Akamai in AS3

by John Dyer

We use Akamai (formerly ninesystems) for all of our video streaming (media page, online education, grad stories, etc.) and we tend to use the FLVPlayback component for our various video players. When we hit an Akamai stream URL (http://dts.edgeboss.net/xxxxx.flv), it is an XML file with information on servers based on the user's location. This XML must be parsed in order to construct a stream URL to play. Ninesystems provided a sample player, but it used Flash 7 style coding which is very hard to manage. I've developed a little AS3 class that encapsulates all the functionality.

Here is the usage (assuming an FLVPlayback component called: flvPlayback);

using edu.dts.video.AkamaiPlayer;
var akamaiPlayer:AkamaiPlayer = new AkamaiPlayer(flvPlayback);
akamaiPlayer.playStream("http://edgeboss.net/xxxxx.flv");

That's it. The full source code is below:

package edu.dts.video
{
    
    import flash.display.MovieClip;

    import flash.events.EventDispatcher;
    import flash.events.Event;
    import flash.events.TimerEvent;

    
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    
    import flash.utils.Timer;
    import fl.video.FLVPlayback;
    import fl.video.VideoEvent;    
    
    public class AkamaiPlayer extends EventDispatcher
    {
        private var _player:FLVPlayback;
        
        private var _protocols_array:Array = [
            {protocol:"rtmp", port:1935}, 
            {protocol:"rtmp", port:80} 
        ];
        private var _connections_array:Array = new Array();

        private var _duration:Number = 0;    

        private var _connectionIndex:Number = 0;
        private var _connectTimeout = null;

        private var _akamaiUrl:String = "";
        private var _akamaiConn:String = "";
        private var _connTimeout = 10000;        
        
        private var _streamXml:XML;

        private var _timer:Timer;
        
        public var connectionFailure:Function;
            
        public function AkamaiPlayer(player:FLVPlayback)
        {
            _player = player;
            _player.addEventListener(VideoEvent.READY, playerReady);
        }
        
        public function playStatic(flvUrl:String) {
            trace("AkaimiPlayer playing " +  flvUrl);
            _player.play(flvUrl);
        }
        
        public function playStream(akamaiUrl:String) {
            trace("AkaimiPlayer playing " +  akamaiUrl);
            
            // check for XML version (xmlvers)
            
            if (akamaiUrl.indexOf("?") == -1) {
                akamaiUrl += "&xmlvers=2";
            } 
            else
            {
                if (akamaiUrl.indexOf("xmlvers=2") == -1) {
                    akamaiUrl += "&xmlvers=2";
                }                
            }
            
            
            _akamaiUrl = akamaiUrl;
            _akamaiConn = "";
        
            if (_player.playing)
                _player.stop();
        
            var xmlLoader:URLLoader = new URLLoader();
            xmlLoader.addEventListener(Event.COMPLETE, loadComplete);
            xmlLoader.load(new URLRequest(_akamaiUrl));
        }
        
        private function loadComplete(event:Event):void {
            trace("FLV XML is Loaded");
            
            // get XML data
            _streamXml = new XML(event.currentTarget.data);
            
            // clear out existing data
            _connectionIndex = 0;
            _connections_array = new Array();
            
            // find //             
            var entries:XMLList = _streamXml.stream.entry;
        
        
            var item_xml:XML;
            
            // loop through the array of matching XMLNodes and remap the child nodes into an tempObject
            for each (item_xml in entries) {
                var tempObj:Object = new Object();
                var node:XML;
                for each (node in item_xml.children()) {
                    tempObj[node.localName()] = node.text();
                }
                
                // create connection string array entries
                for ( var i = 0; i < _protocols_array.length; i++ ) {
                    var connString:String = 
                        _protocols_array[i].protocol + "://" + 
                                tempObj["serverName"] + ":" + _protocols_array[i].port + "/" + 
                                tempObj["appName"]+  "/" + tempObj["streamName"];
                    _connections_array.push( connString );
                    
                    trace("added: " + connString);
                }                                                            
            }
            
            _timer = new Timer(_connTimeout);
            _timer.addEventListener("timer", timerHandler);
            _timer.start();
        
            tryNextConnection();

        }
        
        private function tryNextConnection():void {

            var connString:String = _connections_array[_connectionIndex];
            
            if (_connectionIndex == _connections_array.length ) {
                
                // make sure to reset to last connection
                connString = _connections_array[_connectionIndex-1];
                 _timer.stop();
                 
                if (connectionFailure != null)
                    connectionFailure();
                 
                trace("tried all protocols with no luck");
                return;
            }
                    
            // just in case it happens to start playing from another source
            if (_player.playing)
                return;
                    
            trace("connecting to " + (_connectionIndex+1).toString() + " of " + (_connections_array.length).toString() + ": " + connString + " ; count: " + _timer.currentCount.toString());
            _player.autoPlay = true;
            _player.play(connString);
        
            
            // iterate the connection so that if it fails, we go to the next one
            _connectionIndex++;            
        }
                
        private function timerHandler(event:TimerEvent):void {
            tryNextConnection();                    
        }
        
        private function stopConnectionAttempts():void {
            _timer.stop();
        }        
        
        private function playerReady(event:VideoEvent):void {
            // successful play
            stopConnectionAttempts();
        }        
    }    
}

Comments

John May 15. 2008 10:42

Thank you so much for posting this. I found it really useful.

I did have to do a little bit of debugging to get it working, though. If anyone else is having trouble, I had to change the following:

- Changed "public class AkamaiPlayer extends EventDispatcher" to "public class AkamaiPlayer extends MovieClip".

- In function "playStream", I had to remove the whole "if (akamaiUrl.indexOf("?") == -1)" block. The concatenation of "&xmlvers=2" was causing Akamai to return a 404 page.

John

John May 15. 2008 10:45

Whoops, I forgot to include a couple of other changes

- Removed "var entries:XMLList = _streamXml.stream.entry;"

- Changed "for each (item_xml in entries) {" to "for each (item_xml in _streamXml) {"

John

John Dyer May 15. 2008 15:41

@John, glad to be of help. I think I have done some modifications to it since then. You might try enabling the version=2 as it give you additional connections.
John Dyer's last post: Papervision3D Bookshelf

John Dyer

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading