Commit 4e710423 authored by Dan Rossi's avatar Dan Rossi
Browse files

- updates to HLS http streaming plugin, adding mbr switching support, requires...

- updates to HLS http streaming plugin, adding mbr switching support, requires refactored apple-osmf library
parent cba60886
......@@ -8,7 +8,7 @@
<property name="plugin-libs" value=""/>
<property name="extra-sources" value="../../lib/common/src/actionscript ${osmf-dir} ${apple-osmf-dir}"/>
<property name="library-path" value=""/>
<property name="compiler-defines" value="-define+=CONFIG::LOGGING,true -define+=CONFIG::FLASH_10_1,'true'"/>
<property name="compiler-defines" value="-define+=CONFIG::LOGGING,'true' -define+=CONFIG::FLASH_10_1,'true'"/>
<property file="${devkit-dir}/plugin-build.properties" />
<import file="${devkit-dir}/plugin-build.xml"/>
......
......@@ -2,27 +2,13 @@
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--
Flowplayer website, forums & jQuery Tools by Tero Piirainen
Prefer web standards over Flash. Video is the only exception.
-->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Flowplayer - Flash Video Player for the Web</title>
<meta name="Description" content="Embed video streams to your website. Superior alternative to YouTube. Open Sourced under the GPL license. No other software makes this smoother." />
<meta name="Keywords" content="video player for the web, flash video player,web media player,free video player,free flv player,mp4 flash player,mp4 player,open source video player,flash 9 video,flash video,web video,flv player,flv video" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="Distribution" content="Global" />
<meta name="Author" content="Tero Piirainen" />
<meta name="Robots" content="index,follow" />
......@@ -76,7 +62,7 @@
<a name="anchor"></a>
<h2><em>Basic</em> example with a single bitrate</h2>
<p>
......@@ -84,7 +70,7 @@
<div style="width:425px;height:300px;margin:30px auto">
<!-- player container-->
......@@ -102,25 +88,119 @@
flowplayer("player", "@PLAYER_SWF@", {
// configure the required plugins
plugins: {
httpstreaming: {
url: '@MAIN_PLAYER_SWF@'
}
},
clip: {
url: "http://184.72.239.149/vod/smil:bigbuckbunnyiphone.smil/chunklist-b400000.m3u8",
ipadUrl: "http://184.72.239.149/vod/smil:bigbuckbunnyiphone.smil/chunklist-b400000.m3u8",
//url: "http://thread-life.herokuapp.com/threads/369/alternates.m3u8?low_res=true",
//ipadUrl: "http://thread-life.herokuapp.com/threads/369/alternates.m3u8?low_res=true",
urlResolvers: "httpstreaming",
provider: "httpstreaming"
provider: "httpstreaming",
autoPlay: false
},
log: {
level: 'debug',
filter: 'org.electroteque.m3u8.*'
filter: 'org.osmf.*, org.electroteque.m3u8.*, org.flowplayer.bitrateselect.*'
}
}).ipad();
</script>
</div>
<div class="box code">
<pre><code class="javascript">
flowplayer("player", "@PLAYER_SWF@", {
// configure the required plugins
plugins: {
httpstreaming: {
url: '@MAIN_PLAYER_SWF@'
}
},
clip: {
url: "http://184.72.239.149/vod/smil:bigbuckbunnyiphone.smil/chunklist-b400000.m3u8",
ipadUrl: "http://184.72.239.149/vod/smil:bigbuckbunnyiphone.smil/chunklist-b400000.m3u8",
urlResolvers: ["httpstreaming","brselect"],
provider: "httpstreaming",
autoPlay: false
},
log: {
level: 'debug',
filter: 'org.osmf.*, org.electroteque.m3u8.*, org.flowplayer.bitrateselect.*'
}
}).ipad();
</pre>
</div>
<h2><em>Multi</em> bitrate example with menu selection.</h2>
<p>
</p>
<div style="width:425px;height:300px;margin:30px auto">
<a
href=""
style="display:block;width:425x;height:300px;"
id="bitrate">
</a>
<!-- Flowplayer installation and configuration -->
<script type="text/javascript" >
flowplayer("bitrate", "@PLAYER_SWF@", {
// configure the required plugins
plugins: {
httpstreaming: {
url: '@MAIN_PLAYER_SWF@',
},
menu: {
url: "flowplayer.menu.swf",
items: [
// you can have an optional label as the first item
// the bitrate specific items are filled here based on the clip's bitrates
{ label: "select bitrate:", enabled: false }
]
},
brselect: {
url: "flowplayer.bitrateselect.swf",
menu: true
}
},
clip: {
url: "http://184.72.239.149/vod/smil:bigbuckbunnyiphone.smil/playlist.m3u8",
ipadUrl: "http://184.72.239.149/vod/smil:bigbuckbunnyiphone.smil/playlist.m3u8",
urlResolvers: ["httpstreaming","brselect"],
provider: "httpstreaming",
autoPlay: false,
bitrates: {
labels: { "300": "Low", "400": "Medium", "600": "High" },
default: 600
},
},
log: {
level: 'debug',
filter: 'org.osmf.*, org.electroteque.m3u8.*, org.flowplayer.bitrateselect.*'
}
}).ipad();
......@@ -129,6 +209,54 @@ flowplayer("player", "@PLAYER_SWF@", {
</div>
<div class="box code">
<pre><code class="javascript">
flowplayer("player", "@PLAYER_SWF@", {
// configure the required plugins
plugins: {
httpstreaming: {
url: '@MAIN_PLAYER_SWF@',
},
menu: {
url: "flowplayer.menu.swf",
items: [
// you can have an optional label as the first item
// the bitrate specific items are filled here based on the clip's bitrates
{ label: "select bitrate:", enabled: false }
]
},
brselect: {
url: "flowplayer.bitrateselect.swf",
menu: true
}
},
clip: {
url: "http://184.72.239.149/vod/smil:bigbuckbunnyiphone.smil/playlist.m3u8",
ipadUrl: "http://184.72.239.149/vod/smil:bigbuckbunnyiphone.smil/playlist.m3u8",
urlResolvers: ["httpstreaming","brselect"],
provider: "httpstreaming",
autoPlay: false,
bitrates: {
labels: { "300": "Low", "400": "Medium", "600": "High" },
default: 600
},
},
log: {
level: 'debug',
filter: 'org.osmf.*, org.electroteque.m3u8.*, org.flowplayer.bitrateselect.*'
}
}).ipad();
</pre>
</div>
......
/*
*
* By: Daniel Rossi, <electroteque@gmail.com>
* Copyright (c) 2012 Electroteque Multimedia
* Copyright (c) 2012 Electroteque Media
*
* Released under the MIT License:
* http://www.opensource.org/licenses/mit-license.php
......@@ -9,7 +9,27 @@
package org.electroteque.m3u8 {
public class Config {
private var _retryInterval:int = 10;
private var _maxRetries:int = 100;
public function set retryInterval(value:int):void
{
_retryInterval = value;
}
public function get retryInterval():int
{
return _retryInterval * 1000;
}
public function set maxRetries(value:int):void
{
_maxRetries = value;
}
public function get maxRetries():int
{
return _maxRetries;
}
}
}
/*
*
* By: Daniel Rossi, <electroteque@gmail.com>
* Copyright (c) 2012 Electroteque Multimedia
* Copyright (c) 2012 Electroteque Media
*
* Released under the MIT License:
* http://www.opensource.org/licenses/mit-license.php
......
/*
*
* By: Daniel Rossi, <electroteque@gmail.com>
* Copyright (c) 2012 Electroteque Multimedia
* Copyright (c) 2012 Electroteque Media
*
* The Initial Developer of the Apple-OSMF feature is Matthew Kaufman http://code.google.com/p/apple-http-osmf/
*
......@@ -11,6 +11,8 @@
package org.electroteque.m3u8 {
import org.flowplayer.model.Clip;
import org.flowplayer.model.ClipError;
import org.flowplayer.model.ErrorCode;
import org.flowplayer.model.ClipType;
import org.flowplayer.model.ClipEventType;
import org.flowplayer.model.ClipEvent;
......@@ -18,29 +20,53 @@ package org.electroteque.m3u8 {
import org.flowplayer.model.Plugin;
import org.flowplayer.model.PluginModel;
import org.flowplayer.util.PropertyBinder;
import org.flowplayer.view.ErrorHandler;
import org.flowplayer.view.Flowplayer;
import org.flowplayer.controller.StreamProvider;
import org.flowplayer.controller.ClipURLResolver;
import org.flowplayer.controller.ResourceLoader;
import org.flowplayer.net.BitrateItem;
import flash.events.NetStatusEvent;
import flash.net.NetStream;
import flash.net.NetConnection;
import flash.events.TimerEvent;
import flash.utils.Timer;
import org.osmf.net.StreamingURLResource;
import at.matthew.httpstreaming.parser.HttpStreamingM3U8ManifestParser;
import at.matthew.httpstreaming.model.HttpStreamingM3U8Manifest;
import at.matthew.httpstreaming.HTTPStreamingM3U8Factory;
import org.osmf.media.MediaResourceBase;
import org.osmf.media.URLResource;
import org.osmf.net.httpstreaming.HTTPNetStream;
import at.matthew.httpstreaming.*;
import org.osmf.events.ParseEvent;
import org.osmf.net.DynamicStreamingResource;
import org.osmf.net.DynamicStreamingItem;
import org.osmf.net.StreamingURLResource;
public class HttpStreamingHlsProvider extends NetStreamControllingStreamProvider implements ClipURLResolver, Plugin {
private var _bufferStart:Number;
private var _config:Config;
private var _startSeekDone:Boolean;
private var _model:PluginModel;
private var _currentClip:Clip;
private var _previousClip:Clip;
private var netResource:URLResource;
import org.osmf.net.httpstreaming.HTTPNetStream;
public class HttpStreamingHlsProvider extends NetStreamControllingStreamProvider implements ClipURLResolver, ErrorHandler, Plugin {
protected var _bufferStart:Number;
protected var _config:Config;
protected var _startSeekDone:Boolean;
protected var _model:PluginModel;
protected var _player:Flowplayer;
protected var _clip:Clip;
protected var _currentClip:Clip;
protected var _previousClip:Clip;
protected var manifest:HttpStreamingM3U8Manifest;
protected var parser:HttpStreamingM3U8ManifestParser;
protected var resource:MediaResourceBase;
protected var netResource:URLResource;
protected var dynResource:DynamicStreamingResource;
protected var streamResource:StreamingURLResource;
protected var _isDynamicStreamResource:Boolean = false;
protected var _successListener:Function;
protected var _retryTimer:Timer;
protected var _retryCount:int;
/**
* @inherit
......@@ -57,6 +83,7 @@ package org.electroteque.m3u8 {
*/
override public function onLoad(player:Flowplayer):void {
log.info("onLoad()");
_player = player;
_model.dispatchOnLoad();
}
......@@ -88,9 +115,233 @@ package org.electroteque.m3u8 {
* @param clip
* @param successListener
*/
public function resolve(provider:StreamProvider, clip:Clip, successListener:Function):void {
netResource = new StreamingURLResource(clip.url);
successListener(clip);
_clip = clip;
_successListener = successListener;
loadM3U8(_clip.completeUrl, onM3U8Loaded);
}
/**
* Loads the M3u8 playlist
* @param m3u8Url
* @param loadedCallback
*/
protected function loadM3U8(m3u8Url:String, loadedCallback:Function):void {
if (!_player) return;
log.debug("connect(), loading M3U8 file from " + m3u8Url);
var loader:ResourceLoader = _player.createLoader();
loader.errorHandler = this;
loader.load(m3u8Url, function(loader:ResourceLoader):void {
log.debug("M3U8 file received");
loadedCallback(String(loader.getContent()));
}, true);
}
/**
* M3u8 playlist loaded and parsing the manifest
* @param m3u8Content
*/
protected function onM3U8Loaded(m3u8Content:String):void {
stopM3U8Reload();
parseM3U8Manifest(m3u8Content);
}
/**
* formats the streaming items
* @param streamItems
* @return
*/
protected function formatStreamItems(streamItems:Vector.<DynamicStreamingItem>):Vector.<DynamicStreamingItem> {
var bitrateItems:Vector.<DynamicStreamingItem> = new Vector.<DynamicStreamingItem>();
var bitrateOptions:Object = {};
if (_clip.getCustomProperty("bitrates")) {
bitrateOptions = _clip.getCustomProperty("bitrates");
} else {
bitrateOptions.default = dynResource.streamItems[0].bitrate;
if (dynResource.streamItems.length == 2) {
bitrateOptions.sd = dynResource.streamItems[0].bitrate;
bitrateOptions.hd = dynResource.streamItems[dynResource.streamItems.length - 1].bitrate;
}
}
for (var index:int = 0; index < dynResource.streamItems.length; index++) {
var item:DynamicStreamingItem = streamItems[index];
var bitrateItem:BitrateItem = new BitrateItem();
bitrateItem.url = item.streamName;
bitrateItem.bitrate = item.bitrate / 1000;
bitrateItem.index = index;
bitrateItem.width = item.width;
bitrateItem.height = item.height;
if (bitrateOptions.default == bitrateItem.bitrate) bitrateItem.isDefault = true;
if (bitrateOptions.sd == bitrateItem.bitrate) bitrateItem.sd = true;
if (bitrateOptions.hd == bitrateItem.bitrate) bitrateItem.hd = true;
if (bitrateOptions.labels) bitrateItem.label = bitrateOptions.labels[bitrateItem.bitrate];
bitrateItems.push(bitrateItem);
}
return bitrateItems;
}
/**
* M3u8 manifest parsing has finished
*/
protected function onM3U8Finished():void
{
log.debug("M3U8 Manifest Finished");
try
{
resource = parser.createResource(manifest, new URLResource(_clip.completeUrl));
//dynamic streaming resource
if (resource is DynamicStreamingResource) {
dynResource = resource as DynamicStreamingResource;
//formats the stream items to be ready for the bwcheck plugin
dynResource.streamItems = formatStreamItems(dynResource.streamItems);
_isDynamicStreamResource = true;
_clip.setCustomProperty("bitrateItems", dynResource.streamItems);
_clip.setCustomProperty("urlResource", dynResource);
} else {
//single bitrate resource
streamResource = resource as StreamingURLResource;
log.debug("Manifest parsed with a single stream " + manifest.media[0].url);
_clip.setResolvedUrl(this, streamResource.url);
_clip.setCustomProperty("urlResource", streamResource);
}
_clip.setCustomProperty("manifestInfo",manifest);
if (_successListener != null) {
_successListener(_clip);
}
}
catch (error:Error)
{
handleStreamNotFound(error.message);
}
}
/**
* Handle stream not found errors, for live streams use connection reattempts until it becomes available.
* @param message
*/
private function handleStreamNotFound(message:String):void
{
log.error(message);
if (_clip.live) {
retryM3U8Load();
return;
}
_clip.dispatchError(ClipError.STREAM_NOT_FOUND, message);
}
/**
* Stop the playlist loading timer
*/
protected function stopM3U8Reload():void
{
if (_retryTimer) {
_retryTimer.stop();
_retryTimer.removeEventListener(TimerEvent.TIMER, onM3U8LoadRetry);
_retryTimer.reset();
_retryTimer = null;
_retryCount = 0;
}
}
/**
* Attempt to reconnect or stop if the retry count has reached it's limit.
*/
private function retryM3U8Load():void
{
if (!_retryTimer) {
_retryTimer = new Timer(_config.retryInterval);
_retryTimer.addEventListener(TimerEvent.TIMER, onM3U8LoadRetry);
_retryTimer.start();
log.error("Reattempting to load media from M3U8 manifest " + _clip.completeUrl);
}
_retryCount++;
if (_retryCount > _config.maxRetries) {
stopM3U8Reload();
}
}
/**
* Reload the M3U8 feed after a set interval.
* @param event
*/
private function onM3U8LoadRetry(event:TimerEvent):void
{
log.error("Reattempting to load media from M3U8 manifest " + _clip.completeUrl);
loadM3U8(_clip.completeUrl, onM3U8Loaded);
}
/**
* Formats the baseUrl
* @param url
* @return
*/
protected function getRootUrl(url:String):String
{
var path:String = url.substr(0, url.lastIndexOf("/"));
return path;
}
private function parseM3U8Manifest(M3U8Content:String):void {
// log.debug("M3U8 Content: " + M3U8Content);
log.debug("Parsing M3U8 Manifest");
parser = new HttpStreamingM3U8ManifestParser();
parser.addEventListener(ParseEvent.PARSE_COMPLETE, onParserLoadComplete);
parser.addEventListener(ParseEvent.PARSE_ERROR, onParserLoadError);
try
{
parser.parse(M3U8Content, getRootUrl(_clip.completeUrl), _clip.completeUrl);
}
catch (parseError:Error)
{
log.error(parseError.errorID + " " + parseError.getStackTrace());
}
}
/**
* M3u8 parser completed