John Dyer

Technology and web development in curly bracket languages {Javascript, C#, ActionScript}

Online Education Player Design Considerations

by John Dyer 2. June 2008 18:33

Dallas Seminary has been doing video-based online education for about 5 years now. One of the initial goals was to replicate the classroom teaching experience as much as possible by including the video and the professor's illustrations (Powerpoint, Keynote, etc.). Beyond that, we also wanted to enhance the experience by adding things like a transcript and editing down the video to the most important segments.

Our initial player was created by Yahoo!'s Broadcast.com division and it used HTML frames and Windows media. This limited us to Internet Explorer, but there was no other option at the time. Then Flash 7 came out with video support and I custom built the player below. Since then, video on the web has come a long way, and the introduction of H.264 support means that we only need one format for playing on the web and on the desktop.

Here is the original design using 320x240 video and 480x360 slides:

  • image

Here are some changes we wanted to make:

  • Move from Flash 8 to 9 - use AS3 and enable fullscreen support
  • Use H.264 video - to simplify our workflow
  • Increase the video size, quality, and availability
  • Some users found the auto-scrolling transcript distracting and we need another option for them
  • Many classes don't have slides, so we need to de-emphasize those, yet also provide a good slide navigation system.
  • More narrow player - the current design is about 850px which makes it hard to have many more windows open at the same time.

Here are wireframe mockups of various design proposals

  • First player (2003) using Yahoo!'s Broadcast.com using HTML/Frames/WMV
    Obviously, WMV is limited to really working in IE, and we were stuck with Yahoo!'s platform.
    image
    old_online_ed_Yahoo_Video_Skin
  • Flash player with 320x240 video
    This player is wider since the slides are bigger, but the transcript is more narrow column which makes it easier to read.
    image 
    image

New Designs

  • Swapped video and slides sizes, controls at the bottom
    This swaps the video and slide sizes. It's nice, but the transcript is far away and the slides are hard to read. The player is also still very wide.
    image
  • Larger video size, small slides on the side
    To make the video even bigger, the transcript is now a single line like closed captioning on TV. Rather than give the slides their own dedicated space, the slides have a tray on the right and the full size slide appears over the video for a few seconds. The width is slightly smaller, but not much.
    image
     
  • Coverflow Style Player
    To make the player more narrow (for multi-tasking), we move the slides to the bottom and use the Apple CoverFlow to show a lot of them at once. This makes the player more narrow which is good, but also taller. To shorten it, we've moved all the controls on top of the video that appears/diasppears on mouse hover.

     image

    Here is a screenshot of a fairly common desktop size (1680x1050) with MS Word and a preview version of this player open. (note: I've made sure the controls are visible for the screenshot, but they are hidden normally) 
    image
    image

This seems effective, but there is a possible drawback in that the Coverflow might be somewhat distracting to some students (it can be turned off). Also, some may prefer the older style transcript with more than one line visible at once.

Links to preview the players:

Another future goal is to build the player as an AIR application so that videos can be downloaded for offline use.

Yet Another Coverflow using Papervision

by John Dyer 4. November 2007 22:00

UPDATE: Now uses Papervision 2.0 Alpha (GreatWhite). 

There are a million Apple Coverflow knockoffs (blitz, doug mccune, antti kupila, weber design) and now there are a million and one. This one is made using Papervision3d and Tweener and includes keyboard and scrollwheel support. Here are two uses, one pulling from a friend's Flickr photo stream, and the other pulling from DTS's recent media items:

 



This uses the Phunky GreatWhite branch of Papervision (1.9 or so 2.0 Alpha). Here's the setup:

var coverFlowData:Array = [{title: "item title", clickUrl:"http://mysite.com/target", imageUrl:"http://mysite.com/image.jpg"}];
var coverFlow = new CoverFlow(stage, camera, scene, coverFlowData); 

And here's the project (for Flash CS3): coverflow.zip (16.21 kb)

Flash Streaming with Akamai in AS3

by John Dyer 2. November 2007 02:10

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();
        }        
    }    
}

Is another site/URL using my Flash SWF?

by John Dyer 27. October 2007 02:40

There is not a clear way in Flash access what site is using your SWF. On DTS's media page, we allow users to embed our media player in their blogs (like all other video sites these days), but it is difficult to know the URL of the file who is pulling the file. stage.loaderInfo.url is just the URL of the SWF, but not the HTML page which embeds the SWF. The only way I found to do this is to call ExternalInterface and ask JavaScript what the URL of the page is. So here's what I came up with:

var urlReferrer:String = ExternalInterface.call("document.location.href.toString"); 

The reason for the "toString() is that ExternalInterface.call wants you to use a function rather than just calling a static property.

Update (01/08/08): Looks like someone else has caught on to this.

Real World Object Size Comparison in Papervision3D

by John Dyer 13. October 2007 03:09

While doing development in Papervision 3D again for a campus map, I thought it would be fun to mock up a dynamic size comparison tool like the one at sizeasy. The demo has the obligatory iPhone along with a BlackBerry 8700 and a deck of cards. The measurements are in millimeters, but you could change it to whatever you want, as long as all of the objects matched up. You can also add a URL to whatever image you want to use for each object:

Sizer in PV3D

TV, coffee maker, mountain dew 6-pack

XBOX, PS3, Wii

Papervision3D Experiment: 3D Campus Map

by John Dyer 1. June 2007 11:28

I've been experimenting with Papervision3D in order to make a 3D version of our campus map. On of our artists has worked up an extremely detailed map in Google Sketchup. I've rebuilt the model in Sketchup using texture maps so that its around 1000 triangles and is to scale in Google Earth. Sketchup allows exporting to Collada files (which it uses in it's own files for Google Earth) and Papervision will parse Collada. Here's the result:

 

I have not finished texturing the entire model, so there are some plain yellow walls.

UPDATE: I've also added a driving mode! 

Web Statistics