For ActionScript 3 development I have just begun exploring the Open Source Media Framework (OSMF) and the MVC based framework called RobotLegs. What better way than to use them together? The best way to learn this stuff is to dive in and apply it to real solutions. PLEASE leave a comment if I have gone astray.
You can download the source code here (based on OSMF sprint 8 and RobotLegs 1.0.2).
Here is the working proof of concept with video served by Castfire.com:
video swf did not load
Project goals:
- Build a production ready video player
- Learn OSMF
- AS3 only compiled with mxmlc
- Keep things organized
1. Build a video player
This is a longer term goal. The video player presented here is only a proof of concept. Video on the web continues to evolve and now seems like a good time to re-factor some key video player projects to bring them up to date and address new requirements.
2. Learn OSMF
Strobe Open Source Media Framework is an Adobe initiative to bring some standards to video player development. I have been itching to get my hands dirty with this new framework to decide for myself if it is worth buying into.
Some may think it is inevitable that OMSF will be over-architechted and therefore full of bloat. I can't really answer that yet. But I can say that I REALLY like the convenience methods that come with the MediaPlayer class. This build is 90k which includes RobotLegs ... acceptable.
Sure, you can build a video player that comes in at 15k. However, as soon as you start adding modern day requirements (CDN services, advert engines, analytics, chapters, xml lists etc) things quickly get complicated. The promise of OSMF is that all these things can be integrated into your video using a plug-in architecture which will greatly ease the burden of development.
3. ActionScript 3 only
Every video player I have worked on to date (other than a few Flex experiments) have been made with Flash Professional which means they need to be compiled from the Flash IDE. Using Flash Builder to compile the application de-couples the workflow from the Flash IDE which makes possible all sorts of interesting ideas.
This does not preclude the use of Flash Professional made libraries that can be used for things like the control bar. After all, the Flash IDE is still really good at some things.
4. Keep things organized
I have made probably a dozen or so custom video players to date. When building one from scratch it does not take tong before it gets complicated: video players are the mothers of all things asynchronous. I have my own particular way of using design patterns and structure but have never took the time to think through a solid framework and ultimately my projects get big enough that they no longer feel like they are on solid ground.
Someone else has thought it through and this is where RobotLegs comes in. I have had a pretty good experience with Cairngorm over the last couple years but it requires the Flex framework. Dependency Injection seems to be the hot topic recently and I have read some good things about RobotLegs so I thought I would give it a try. This video tutorial from John Lindquist helped me to put together this first implementation.
The thought here is that there are other views to manage than just the video player and control bar. RobotLegs will help make these views and OSMF plugins manageable and modular.
Some code
Here are a few classes to show how video player events flow through RobotLegs to the DetailsView so that bytes loaded and playback time can get updated. Download the source for the full meal deal.
Actionscript:
-
/**
-
* author: Randy Troppmann January 17, 2010
-
* http://www.randytroppmann.com
-
*
-
* Permission is hereby granted, free of charge, to any person
-
* obtaining a copy of this software (the "Software"),
-
* to use 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.
-
*
-
* 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 org.osmf.experiments.view{
-
import flash.display.Sprite;
-
import flash.events.Event;
-
import org.osmf.media.URLResource;
-
import org.osmf.net.NetLoader;
-
import org.osmf.utils.URL;
-
import org.osmf.video.VideoElement;
-
import org.osmf.display.MediaPlayerSprite;
-
import org.osmf.events.*;
-
import org.osmf.experiments.controller.MediaPlayerEvent;
-
import flash.text.TextField;
-
import flash.events.MouseEvent;
-
-
public class MediaPlayerView extends Sprite{
-
-
public static const TEST_PROGRESSIVE_URL:String = "http://serve.castfire.com/s:pre_post_M9Ow2/video/50082/50082_2009-01-14-235108.flv";
-
protected var media:MediaPlayerSprite;
-
protected var totalBytes:Number;
-
protected var duration:Number;
-
protected var currentTime:Number;
-
protected var playButton:PlayButton;
-
protected var isPlaying:Boolean = false;
-
-
public function MediaPlayerView(){
-
media = new MediaPlayerSprite();
-
media.setAvailableSize(320, 240);
-
media.mediaPlayer.autoPlay = false;
-
media.mediaPlayer.addEventListener(LoadEvent.BYTES_LOADED_CHANGE, onBytesLoadedChange, false, 0, true);
-
media.mediaPlayer.addEventListener(LoadEvent.BYTES_TOTAL_CHANGE, onBytesTotalChange, false, 0, true);
-
media.mediaPlayer.addEventListener( TimeEvent.DURATION_CHANGE, onDurationChange, false, 0, true );
-
media.mediaPlayer.addEventListener(TimeEvent.CURRENT_TIME_CHANGE, onPlayheadChange, false, 0, true);
-
addChild(media);
-
//// ADD PLAY BUTTON
-
playButton = new PlayButton();
-
addChild(playButton);
-
playButton.x = playButton.y = 5;
-
-
playButton.addEventListener(MouseEvent.CLICK, handlePlayClick, false, 0, true);
-
-
}
-
-
public function testProgressive():void{
-
media.element = new VideoElement(new NetLoader(), new URLResource(new URL(TEST_PROGRESSIVE_URL)));
-
}
-
-
protected function handlePlayClick(evt:MouseEvent):void{
-
isPlaying = !isPlaying;
-
if (isPlaying){
-
media.mediaPlayer.play();
-
playButton.label.text = "pause";
-
}
-
else{
-
media.mediaPlayer.pause();
-
playButton.label.text = "play";
-
}
-
}
-
-
//// OSMF event methods
-
protected function onBytesLoadedChange(evt:LoadEvent):void{
-
var mediaEvent:MediaPlayerEvent = new MediaPlayerEvent(MediaPlayerEvent.BYTES_LOADED_CHANGE);
-
mediaEvent.bytes = evt.bytes;
-
dispatchEvent(mediaEvent);
-
}
-
-
protected function onBytesTotalChange(evt:LoadEvent):void{
-
totalBytes = evt.bytes;
-
var mediaEvent:MediaPlayerEvent = new MediaPlayerEvent(MediaPlayerEvent.PLAYER_SENDS_SIZE_CHANGE);
-
mediaEvent.bytes = evt.bytes;
-
dispatchEvent(mediaEvent);
-
}
-
-
protected function onDurationChange(evt:TimeEvent):void{
-
duration = evt.time;
-
var mediaEvent:MediaPlayerEvent = new MediaPlayerEvent(MediaPlayerEvent.PLAYER_SENDS_DURATION_CHANGE);
-
mediaEvent.time = evt.time;
-
dispatchEvent(mediaEvent);
-
}
-
-
protected function onPlayheadChange(evt:TimeEvent):void{
-
currentTime = evt.time;
-
var mediaEvent:MediaPlayerEvent = new MediaPlayerEvent(MediaPlayerEvent.PLAYER_SENDS_PLAYHEAD_TIME_CHANGE);
-
mediaEvent.time = evt.time;
-
dispatchEvent(mediaEvent);
-
}
-
-
}
-
}
The MediaPlayerViewMediator class captures the events from MediaPlayerView which sets a property in the model.
Actionscript:
-
package org.osmf.experiments.view{
-
import org.robotlegs.mvcs.Mediator;
-
import org.osmf.experiments.model.PlayerModel;
-
import org.osmf.events.TimeEvent;
-
import org.osmf.experiments.controller.MediaPlayerEvent;
-
-
public class MediaPlayerViewMediator extends Mediator{
-
[Inject]
-
public var mediaPlayerView:MediaPlayerView;
-
[Inject]
-
public var model:PlayerModel;
-
-
override public function onRegister():void{
-
mediaPlayerView.addEventListener(MediaPlayerEvent.PLAYER_SENDS_DURATION_CHANGE, handleDurationChange);
-
mediaPlayerView.addEventListener(MediaPlayerEvent.PLAYER_SENDS_PLAYHEAD_TIME_CHANGE, handlePlayheadTimeChange);
-
mediaPlayerView.addEventListener(MediaPlayerEvent.PLAYER_SENDS_SIZE_CHANGE, handleTotalBytesChange);
-
mediaPlayerView.addEventListener(MediaPlayerEvent.BYTES_LOADED_CHANGE, handleBytesLoaded);
-
}
-
-
protected function handleDurationChange(p_evt:MediaPlayerEvent):void{
-
model.duration = p_evt.time;
-
}
-
-
protected function handlePlayheadTimeChange(p_evt:MediaPlayerEvent):void{
-
model.currentTime = p_evt.time;
-
}
-
-
protected function handleTotalBytesChange(p_evt:MediaPlayerEvent):void{
-
model.totalBytes = p_evt.bytes;
-
}
-
-
protected function handleBytesLoaded(p_evt:MediaPlayerEvent):void{
-
model.bytesLoaded = p_evt.bytes;
-
}
-
}
-
}
The model extends the RobotLegs Actor class can use the RobotLegs dispatch() method so that the event can be listened for from anywhere within the RobotLegs ecosystem.
Actionscript:
-
package org.osmf.experiments.model{
-
import org.robotlegs.mvcs.Actor;
-
import org.osmf.experiments.controller.MediaPlayerEvent;
-
-
public class PlayerModel extends Actor{
-
-
public var _totalBytes:Number;
-
public var _bytesLoaded:Number;
-
public var _duration:Number;
-
public var _currentTime:Number;
-
-
public function PlayerModel(){
-
}
-
-
public function get duration():Number{
-
return _duration;
-
}
-
-
public function set duration(value:Number):void{
-
_duration = value;
-
var durationEvent:MediaPlayerEvent = new MediaPlayerEvent(MediaPlayerEvent.DURATION_CHANGE);
-
durationEvent.time = duration;
-
dispatch(durationEvent);
-
}
-
-
public function get currentTime():Number{
-
return _currentTime;
-
}
-
-
public function set currentTime(value:Number):void{
-
_currentTime = value;
-
var timeEvent:MediaPlayerEvent = new MediaPlayerEvent(MediaPlayerEvent.PLAYHEAD_TIME_CHANGE);
-
timeEvent.time = value;
-
dispatch(timeEvent);
-
}
-
-
public function get totalBytes():Number{
-
return _totalBytes;
-
}
-
-
public function set totalBytes(value:Number):void{
-
_totalBytes = value;
-
var mediaEvent:MediaPlayerEvent = new MediaPlayerEvent(MediaPlayerEvent.SIZE_CHANGE);
-
mediaEvent.bytes = value;
-
dispatch(mediaEvent);
-
}
-
-
public function get bytesLoaded():Number{
-
return _bytesLoaded;
-
}
-
-
public function set bytesLoaded(value:Number):void{
-
_bytesLoaded = value;
-
var mediaEvent:MediaPlayerEvent = new MediaPlayerEvent(MediaPlayerEvent.BYTES_LOADED_CHANGE);
-
mediaEvent.bytes = value;
-
dispatch(mediaEvent);
-
}
-
}
-
}
In this experiment, DetailsViewMonitor listens for the events from the model. Since it is injected with the DetailsView instance, it can hit its property setters with new data.
Actionscript:
-
package org.osmf.experiments.view{
-
import org.robotlegs.mvcs.Mediator;
-
import org.osmf.experiments.controller.MediaPlayerEvent;
-
-
public class DetailsViewMediator extends Mediator {
-
-
[Inject]
-
public var detailsView:DetailsView;
-
-
override public function onRegister():void{
-
eventMap.mapListener(eventDispatcher, MediaPlayerEvent.DURATION_CHANGE, onDurationChange);
-
eventMap.mapListener(eventDispatcher, MediaPlayerEvent.PLAYHEAD_TIME_CHANGE, onPlayheadTimeChange);
-
eventMap.mapListener(eventDispatcher, MediaPlayerEvent.SIZE_CHANGE, onTotalBytesChange);
-
eventMap.mapListener(eventDispatcher, MediaPlayerEvent.BYTES_LOADED_CHANGE, onBytesLoadedChange);
-
}
-
-
//// EVENT handlers
-
protected function onDurationChange(evt:MediaPlayerEvent):void{
-
detailsView.duration = evt.time;
-
}
-
-
protected function onPlayheadTimeChange(evt:MediaPlayerEvent):void{
-
detailsView.currentTime = evt.time;
-
}
-
-
protected function onTotalBytesChange(evt:MediaPlayerEvent):void{
-
detailsView.size = evt.bytes;
-
}
-
-
protected function onBytesLoadedChange(evt:MediaPlayerEvent):void{
-
detailsView.bytesLoaded = evt.bytes;
-
}
-
}
-
}
Conclusion
Reading framework documentation makes my eyes bleed. The only way I can really intrinsically understand how they fit into how I build applications is to use them. RobotLegs is a winner. I have not worked with OSMF enough to have an opinion one way or the other ... it has not reached a dot oh release yet.