Intro to OSMF

Posted: 10/08/09

An image of Intro to OSMF

I gave a presentation to the Midlands Flash Platform User Group on 8th August about Adobe's new Open Source Media Framework (or OSMF for short). The talk was pitched at intermediate to advanced ActionScript 3.0 developers who may not necessarily have had experience working with the video API in Flash. I promised I'd follow-up with some examples and further reading on my blog…

The idea behind OSMF is to reduce the complexity involved in developing media players (particularly video players), and in doing so, lower the barriers-to-entry to allow many more developers to create unique and engaging players. OSMF aggregates fixes for many of the problems that other developers have toiled over, meaning you can build rapidly, safe in knowledge that your output will be stable. As a business, the standardisation of player development means that new developers should be able to join your team with relative ease, and become productive in less time. The great thing about the framework is its flexibility, and as more people start working with it, we'll rapidly see new and innovative uses for it. A lot of developers will find the MediaPlayer class serves perfectly well for the majority of scenarios, but with full access to the underlying source API, you can easily extend and enhance behaviours to niche applications - just be aware of the MPL license and how your modifications fit with it.

You can download the slides here

Getting set up

The examples I presented began with a very basic video playback class (showing both HTTP and RTMP video comsumption). To get these working for yourself you'll need:

The Open Source Media Framework libraries - OSMF.swc (formerly called MediaFramework.swc)

EITHER:

Flex Builder 3, Flash Builder 4 beta or FDT, and the Flex SDK 3.2 or newer.

OR:

Adobe Flash Professional CS4.

AND

Flash Player 10 debug

IIS or Apache running an HTTP service on localhost. If you're unfamiliar with this, I can recommend Xampp as a short-term (easy) fix (for progressive download examples).

Adobe Flash Streaming Media Development Server (Free!) (if you want to try some streaming examples)

Some media to play!

Configuring Flex Builder 3 for OSMF

Add OSMF.swc ( found inside the libs folder in the ZIP you downloaded) to your Flex Builder Library-Path (Project-> Properties-> ActionScript Build Path-> Library Path-> Add SWC…).

Configure your project to compile with a compatible SDK (Project-> Properties-> ActionScript Compiler ->Flex SDK Version-> Configure Flex SDKS).

Change the HTML wrapper to target Flash Player 10 (Project-> Properties-> ActionScript Compiler -> Require Flash Player version 10.0.0).

Configuring Flex Builder 3/Flash Builder 4 for OSMF

OSMF.swc now ships with the standard Flex 4 SDK, and should already be in the list of SWCs in Project-> Properties-> ActionScript Build Path-> Library Path->

If you don't appear to have it, follow the instructions for Flex Builder 3 above.

Configuring Flash Professional CS4 for OSMF

Import OSMF.swc to your library (easiest way is to drag the SWC file onto the stage).

Change your publish settings to target Flash Player 10 (File-> Publish Settings-> Flash-> Select Flash Player 10 from the list.

Examples

PLEASE NOTE: The framework has moved on since these examples were written, please refer to the more recent 0.93 examples.

Feel free to copy, paste and run this code at your leisure. Tinker with it to see how it all works, but bear in mind that it comes with no warranty and I accept no responsibility if it breaks (i.e. I wouldn't advise blindly pasting it into a production app!)

RTMP streaming

package

{

import flash.display.Sprite;

import org.openvideoplayer.events.LoadableStateChangeEvent;

import org.openvideoplayer.events.MediaPlayerCapabilityChangeEvent;

import org.openvideoplayer.media.MediaPlayer;

import org.openvideoplayer.media.URLResource;

import org.openvideoplayer.net.NetLoader;

import org.openvideoplayer.video.VideoElement;

public class BasicOSMFStream extends Sprite

{

private const STREAM:String = "rtmp://localhost/vod/mp4:video.mp4";

private var _player:MediaPlayer;

public function BasicOSMFStream()

{

_player = new MediaPlayer();

_player.addEventListener( MediaPlayerCapabilityChangeEvent.VIEWABLE_CHANGE, onViewable );

_player.source = new VideoElement( new NetLoader(), new URLResource( STREAM ) );

}

private function onViewable( e:MediaPlayerCapabilityChangeEvent ) :void

{

if( e.enabled )

{

addChild( _player.view );

}

}

}

}

HTTP/Progressive download

package

{

import flash.display.Sprite;

import org.openvideoplayer.events.LoadableStateChangeEvent;

import org.openvideoplayer.events.MediaPlayerCapabilityChangeEvent;

import org.openvideoplayer.media.MediaPlayer;

import org.openvideoplayer.media.URLResource;

import org.openvideoplayer.net.NetLoader;

import org.openvideoplayer.traits.ILoadable;

import org.openvideoplayer.traits.LoadState;

import org.openvideoplayer.traits.MediaTraitType;

import org.openvideoplayer.video.VideoElement;

public class BasicOSMFProgressive extends Sprite

{

private const PROGRESSIVE:String = "http://localhost/video.mp4";

private var _player:MediaPlayer;

public function BasicOSMFProgressive()

{

_player = new MediaPlayer();

_player.addEventListener( MediaPlayerCapabilityChangeEvent.VIEWABLE_CHANGE, onViewable );

_player.source = new VideoElement( new NetLoader(), new URLResource( PROGRESSIVE ) );

var loadable:ILoadable = _player.source.getTrait( MediaTraitType.LOADABLE ) as ILoadable;

loadable.addEventListener( LoadableStateChangeEvent.LOADABLE_STATE_CHANGE, onLoaded );

}

private function onLoaded( e:LoadableStateChangeEvent ) :void

{

if( e.newState == LoadState.LOADED )

{

trace( 'load completed' );

}

else if( e.newState == LoadState.LOAD_FAILED )

{

trace( 'load failed' );

}

}

private function onViewable( e:MediaPlayerCapabilityChangeEvent ) :void

{

if( e.enabled )

{

addChild( _player.view );

}

}

}

}

Next I introduced the concept of compositions, and how it's possible to construct playlists with relative ease. Here, the SerialElement object that we assign to MediaPlayer's source property will play each of the VideoElements we assigned to it sequentially:

package

{

import flash.display.Sprite;

import flash.text.TextField;

import org.openvideoplayer.composition.SerialElement;

import org.openvideoplayer.events.LoadableStateChangeEvent;

import org.openvideoplayer.events.MediaPlayerCapabilityChangeEvent;

import org.openvideoplayer.events.PlayheadChangeEvent;

import org.openvideoplayer.media.MediaPlayer;

import org.openvideoplayer.media.URLResource;

import org.openvideoplayer.net.NetLoader;

import org.openvideoplayer.traits.ILoadable;

import org.openvideoplayer.traits.LoadState;

import org.openvideoplayer.traits.MediaTraitType;

import org.openvideoplayer.video.VideoElement;

public class OSMF extends Sprite

{

private const PLAYLIST:Array = [ "http://localhost/a2.mp4", "http://localhost/pcworld.flv", "http://localhost/b2.mp4" ];

private var _player:MediaPlayer;

private var _serialElement:SerialElement;

public function OSMF()

{

init();

}

private function init() :void

{

_serialElement = new SerialElement();

for each( var item:String in PLAYLIST )

{

_serialElement.addChild( new VideoElement( new NetLoader(), new URLResource( item ) ) );

}

_player = new MediaPlayer();

_player.addEventListener( MediaPlayerCapabilityChangeEvent.VIEWABLE_CHANGE, onViewable );

_player.source = _serialElement;

var loadable:ILoadable = _player.source.getTrait( MediaTraitType.LOADABLE ) as ILoadable;

loadable.addEventListener( LoadableStateChangeEvent.LOADABLE_STATE_CHANGE, onLoaded );

}

private function onLoaded( e:LoadableStateChangeEvent ) :void

{

if( e.newState == LoadState.LOADED )

{

trace( 'media successfully loaded' );

}

else if( e.newState == LoadState.LOAD_FAILED )

{

trace( 'load failed' );

}

}

private function onViewable( e:MediaPlayerCapabilityChangeEvent ) :void

{

if( e.enabled )

{

addChild( _player.view );

}

}

}

}

A question was then raised about managing it all using external data sources, such as XML and web services. Below is an example of a basic OSMF application that takes the previous example a little further, and loads in an XML playlist, with media assigned to each hour of the day. The app parses the XML dependant on the current time on the user's system clock. So, if they start the app at 13:03, they'll be shown the media that was due to start at 1pm, and every piece of media thereafter. You'll notice that there's a mixture of Audio, Image and VideoElements:

package

{

import flash.display.Sprite;

import flash.events.Event;

import flash.events.IOErrorEvent;

import flash.net.URLLoader;

import flash.net.URLRequest;

import org.openvideoplayer.audio.AudioElement;

import org.openvideoplayer.composition.SerialElement;

import org.openvideoplayer.events.LoadableStateChangeEvent;

import org.openvideoplayer.events.MediaPlayerCapabilityChangeEvent;

import org.openvideoplayer.image.ImageElement;

import org.openvideoplayer.image.ImageLoader;

import org.openvideoplayer.media.MediaPlayer;

import org.openvideoplayer.media.URLResource;

import org.openvideoplayer.net.NetLoader;

import org.openvideoplayer.proxies.TemporalProxyElement;

import org.openvideoplayer.traits.ILoadable;

import org.openvideoplayer.traits.LoadState;

import org.openvideoplayer.traits.MediaTraitType;

import org.openvideoplayer.video.VideoElement;

public class PlaylistOSMFPlayer extends Sprite

{

private const PLAYLIST_LOCATION:String = "http://localhost/osmfplaylist.xml";

private var _composition:SerialElement;

private var _loader:URLLoader;

private var _player:MediaPlayer;

private var _playlist:XML;

public function PlaylistOSMFPlayer()

{

loadPlaylist();

}

private function loadPlaylist() :void

{

_loader = new URLLoader();

_loader.addEventListener( Event.COMPLETE, onPlaylistLoaded );

_loader.addEventListener( IOErrorEvent.IO_ERROR, onPlaylistFail );

_loader.load( new URLRequest( PLAYLIST_LOCATION ) );

}

private function onPlaylistLoaded( e:Event ) :void

{

_playlist = XML( e.target.data );

parsePlaylist();

playPlaylist();

}

private function parsePlaylist() :void

{

_composition = new SerialElement();

var currentHour:int = new Date().getHours();

for each( var media:XML in _playlist.media )

{

if( currentHour <= int( String( media.@startTime ).split(":")[0] ) )

{

switch( media.@type.toString() )

{

case "video":

_composition.addChild( new VideoElement( new NetLoader(), new URLResource( media.@source ) ) );

break;

case "advert":

_composition.addChild( new VideoElement( new NetLoader(), new URLResource( media.@source ) ) );

break;

case "image":

_composition.addChild( new TemporalProxyElement( 5, new ImageElement( new ImageLoader(), new URLResource( media.@source ) ) ) );

break;

case "audio":

_composition.addChild( new AudioElement( new NetLoader(), new URLResource( media.@source ) ) );

break;

}

}

}

}

private function playPlaylist() :void

{

if( _composition.numChildren )

{

_player = new MediaPlayer();

_player.addEventListener( MediaPlayerCapabilityChangeEvent.VIEWABLE_CHANGE, onViewable );

_player.addEventListener( MediaPlayerCapabilityChangeEvent.SPATIAL_CHANGE, onSpatial );

_player.source = _composition;

var loadable:ILoadable = _player.source.getTrait( MediaTraitType.LOADABLE ) as ILoadable;

loadable.addEventListener( LoadableStateChangeEvent.LOADABLE_STATE_CHANGE, onLoaded );

}

}

private function onLoaded( e:LoadableStateChangeEvent ) :void

{

if( e.newState == LoadState.LOADED )

{

// Logic for when a loadable object loads

}

else if( e.newState == LoadState.LOAD_FAILED )

{

// Logic for when a load fails

}

}

private function onPlaylistFail( e:IOErrorEvent ) :void

{}

private function onSpatial( e:MediaPlayerCapabilityChangeEvent ) :void

{

// Hook up spatial components here

}

private function onViewable( e:MediaPlayerCapabilityChangeEvent ) :void

{

if( e.enabled )

{

addChild( _player.view );

}

else

{

if( contains( _player.view ) )

{

removeChild( _player.view );

}

}

}

}

}

Below is the XML playlist that is consumed by the application:

<?xml version="1.0" encoding="utf-8"?>

<playlist>

<media startTime="00:00" type="video" source="http://localhost/a2.mp4" title="video1"/>

<media startTime="01:00" type="advert" source="http://localhost/pcworld.flv" title="advert1"/>

<media startTime="02:00" type="video" source="http://localhost/b2.mp4" title="video2"/>

<media startTime="03:00" type="audio" source="http://localhost/2009_25a.mp3" title="audio1"/>

<media startTime="04:00" type="video" source="http://localhost/a2.mp4" title="video1"/>

<media startTime="05:00" type="advert" source="http://localhost/pcworld.flv" title="advert1"/>

<media startTime="06:00" type="video" source="http://localhost/b2.mp4" title="video2"/>

<media startTime="07:00" type="audio" source="http://localhost/2009_25a.mp3" title="audio1"/>

<media startTime="08:00" type="video" source="http://localhost/a2.mp4" title="video1"/>

<media startTime="09:00" type="advert" source="http://localhost/pcworld.flv" title="advert1"/>

<media startTime="11:00" type="video" source="http://localhost/b2.mp4" title="video2"/>

<media startTime="12:00" type="audio" source="http://localhost/2009_25a.mp3" title="image1"/>

<media startTime="13:00" type="video" source="http://localhost/a2.mp4" title="video1"/>

<media startTime="14:00" type="advert" source="http://localhost/pcworld.flv" title="advert1"/>

<media startTime="15:00" type="image" source="http://localhost/welcome.png" title="image1"/>

<media startTime="16:00" type="video" source="http://localhost/a2.mp4" title="video1"/>

<media startTime="17:00" type="audio" source="http://localhost/2009_25a.mp3" title="audio1"/>

<media startTime="18:00" type="advert" source="http://localhost/pcworld.flv" title="advert1"/>

<media startTime="19:00" type="image" source="http://localhost/welcome.png" title="image1"/>

<media startTime="20:00" type="audio" source="http://localhost/2009_25a.mp3" title="audio1"/>

<media startTime="21:00" type="video" source="http://localhost/a2.mp4" title="video1"/>

<media startTime="22:00" type="advert" source="http://localhost/pcworld.flv" title="advert1"/>

<media startTime="23:00" type="video" source="http://localhost/b2.mp4" title="video2"/>

</playlist>

Clearly, ImageElement doesn't (and shouldn't) have a Temporal (time) trait, so it would never 'end' and the playlist wouldn't progress beyond an element of this type; however, at 15:00 you notice that an image appears in the sequence… This is because the framework lets you wrap objects in TemporalProxyElements, to give them a Temporal trait, and allow images to appear in display lists for 'n' seconds before moving on to the next. So with minimal code, I'm able to create a playlist of varying types of media (sound, images and video), and will also be able to present SWFs as media in the next sprint - useful hey!

Whilst by no means exhaustive examples of the many uses for OSMF, I hope it's given enough of an insight to get started with building your own applications on this handy framework. One thing I'm interested in looking into more is being able to use media of varying types in a playlist like this.

Also, one last thing… Please raise any bugs you find at https://bugs.adobe.com/jira/browse/FM as this is a massive help to the team and means they'll be addressed quickly.

Loads of composition examples here

Keywords for this post: open source media framework, osmf, adobe, actionscript 3, flash, flex, video, fms