diff --git a/dist/video-js.swf b/dist/video-js.swf
index 2b743568..d4d70c65 100644
Binary files a/dist/video-js.swf and b/dist/video-js.swf differ
diff --git a/sandbox/videojs.html.example b/sandbox/videojs.html.example
index c76ce0ee..b6864e8f 100644
--- a/sandbox/videojs.html.example
+++ b/sandbox/videojs.html.example
@@ -4,13 +4,13 @@
Video.js | HTML5 Video Player
-
+
-
+
diff --git a/src/VideoJS.as b/src/VideoJS.as
index 3e3d8bba..3002b7a9 100644
--- a/src/VideoJS.as
+++ b/src/VideoJS.as
@@ -5,7 +5,7 @@ package{
import com.videojs.structs.ExternalEventName;
import com.videojs.structs.ExternalErrorEventName;
import com.videojs.Base64;
-
+
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
@@ -55,6 +55,7 @@ package{
_app.model.stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
// add content-menu version info
+
var _ctxVersion:ContextMenuItem = new ContextMenuItem("VideoJS Flash Component v" + VERSION, false, false);
var _ctxAbout:ContextMenuItem = new ContextMenuItem("Copyright © 2014 Brightcove, Inc.", false, false);
var _ctxMenu:ContextMenu = new ContextMenu();
@@ -69,6 +70,7 @@ package{
try{
ExternalInterface.addCallback("vjs_appendBuffer", onAppendBufferCalled);
ExternalInterface.addCallback("vjs_echo", onEchoCalled);
+ ExternalInterface.addCallback("vjs_endOfStream", onEndOfStreamCalled);
ExternalInterface.addCallback("vjs_getProperty", onGetPropertyCalled);
ExternalInterface.addCallback("vjs_setProperty", onSetPropertyCalled);
ExternalInterface.addCallback("vjs_autoplay", onAutoplayCalled);
@@ -185,6 +187,10 @@ package{
private function onEchoCalled(pResponse:* = null):*{
return pResponse;
}
+
+ private function onEndOfStreamCalled():*{
+ _app.model.endOfStream();
+ }
private function onGetPropertyCalled(pPropertyName:String = ""):*{
@@ -274,8 +280,11 @@ package{
return null;
}
- private function onSetPropertyCalled(pPropertyName:String = "", pValue:* = null):void{
+ private function onSetPropertyCalled(pPropertyName:String = "", pValue:* = null):void{
switch(pPropertyName){
+ case "duration":
+ _app.model.duration = Number(pValue);
+ break;
case "mode":
_app.model.mode = String(pValue);
break;
diff --git a/src/com/videojs/VideoJSModel.as b/src/com/videojs/VideoJSModel.as
index 4a9e42e8..5b4ec2eb 100644
--- a/src/com/videojs/VideoJSModel.as
+++ b/src/com/videojs/VideoJSModel.as
@@ -10,7 +10,7 @@ package com.videojs{
import com.videojs.structs.ExternalEventName;
import com.videojs.structs.PlaybackType;
import com.videojs.structs.PlayerMode;
-
+
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.external.ExternalInterface;
@@ -43,9 +43,9 @@ package com.videojs{
private var _rtmpConnectionURL:String = "";
private var _rtmpStream:String = "";
private var _poster:String = "";
-
+
private static var _instance:VideoJSModel;
-
+
public function VideoJSModel(pLock:SingletonLock){
if (!pLock is SingletonLock) {
throw new Error("Invalid Singleton access. Use VideoJSModel.getInstance()!");
@@ -64,11 +64,12 @@ package com.videojs{
}
return _instance;
}
-
+
public function get mode():String{
return _mode;
}
- public function set mode(pMode:String):void{
+
+ public function set mode(pMode:String):void {
switch(pMode){
case PlayerMode.VIDEO:
_mode = pMode;
@@ -84,32 +85,36 @@ package com.videojs{
public function get jsEventProxyName():String{
return _jsEventProxyName;
}
- public function set jsEventProxyName(pName:String):void{
+ public function set jsEventProxyName(pName:String):void {
_jsEventProxyName = cleanEIString(pName);
}
public function get jsErrorEventProxyName():String{
return _jsErrorEventProxyName;
}
- public function set jsErrorEventProxyName(pName:String):void{
+ public function set jsErrorEventProxyName(pName:String):void {
_jsErrorEventProxyName = cleanEIString(pName);
}
public function get stageRect():Rectangle{
return _stageRect;
}
- public function set stageRect(pRect:Rectangle):void{
+ public function set stageRect(pRect:Rectangle):void {
_stageRect = pRect;
}
- public function appendBuffer(bytes:ByteArray):void{
+ public function appendBuffer(bytes:ByteArray):void {
_provider.appendBuffer(bytes);
}
+
+ public function endOfStream():void {
+ _provider.endOfStream();
+ }
public function get backgroundColor():Number{
return _backgroundColor;
}
- public function set backgroundColor(pColor:Number):void{
+ public function set backgroundColor(pColor:Number):void {
if(pColor < 0){
_backgroundColor = 0;
}
@@ -122,7 +127,7 @@ package com.videojs{
public function get backgroundAlpha():Number{
return _backgroundAlpha;
}
- public function set backgroundAlpha(pAlpha:Number):void{
+ public function set backgroundAlpha(pAlpha:Number):void {
if(pAlpha < 0){
_backgroundAlpha = 0;
}
@@ -134,7 +139,7 @@ package com.videojs{
public function get videoReference():Video{
return _videoReference;
}
- public function set videoReference(pVideo:Video):void{
+ public function set videoReference(pVideo:Video):void {
_videoReference = pVideo;
}
@@ -148,7 +153,7 @@ package com.videojs{
public function get volume():Number{
return _volume;
}
- public function set volume(pVolume:Number):void{
+ public function set volume(pVolume:Number):void {
if(pVolume >= 0 && pVolume <= 1){
_volume = pVolume;
}
@@ -167,11 +172,17 @@ package com.videojs{
}
return 0;
}
+
+ public function set duration(value:Number):void {
+ if(_provider && _provider is HTTPVideoProvider) {
+ (_provider as HTTPVideoProvider).duration = value;
+ }
+ }
public function get autoplay():Boolean{
return _autoplay;
}
- public function set autoplay(pValue:Boolean):void{
+ public function set autoplay(pValue:Boolean):void {
_autoplay = pValue;
}
@@ -181,7 +192,7 @@ package com.videojs{
}
return _src;
}
- public function set src(pValue:String):void{
+ public function set src(pValue:String):void {
_src = pValue;
_rtmpConnectionURL = "";
_rtmpStream = "";
@@ -199,7 +210,7 @@ package com.videojs{
public function get rtmpConnectionURL():String{
return _rtmpConnectionURL;
}
- public function set rtmpConnectionURL(pURL:String):void{
+ public function set rtmpConnectionURL(pURL:String):void {
_src = "";
_rtmpConnectionURL = pURL;
}
@@ -207,7 +218,7 @@ package com.videojs{
public function get rtmpStream():String{
return _rtmpStream;
}
- public function set rtmpStream(pValue:String):void{
+ public function set rtmpStream(pValue:String):void {
_src = "";
_rtmpStream = pValue;
broadcastEventExternally(ExternalEventName.ON_SRC_CHANGE, _src);
@@ -235,7 +246,7 @@ package com.videojs{
* @param pValue
*
*/
- public function set srcFromFlashvars(pValue:String):void{
+ public function set srcFromFlashvars(pValue:String):void {
_src = pValue;
_currentPlaybackType = PlaybackType.HTTP
initProvider();
@@ -251,7 +262,7 @@ package com.videojs{
public function get poster():String{
return _poster;
}
- public function set poster(pValue:String):void{
+ public function set poster(pValue:String):void {
_poster = pValue;
broadcastEvent(new VideoJSEvent(VideoJSEvent.POSTER_SET));
}
@@ -278,7 +289,7 @@ package com.videojs{
public function get muted():Boolean{
return (_volume == 0);
}
- public function set muted(pValue:Boolean):void{
+ public function set muted(pValue:Boolean):void {
if(pValue){
var __lastSetVolume:Number = _lastSetVolume;
volume = 0;
@@ -314,14 +325,14 @@ package com.videojs{
public function get preload():Boolean{
return _preload;
}
- public function set preload(pValue:Boolean):void{
+ public function set preload(pValue:Boolean):void {
_preload = pValue;
}
public function get loop():Boolean{
return _loop;
}
- public function set loop(pValue:Boolean):void{
+ public function set loop(pValue:Boolean):void {
_loop = pValue;
}
@@ -404,7 +415,7 @@ package com.videojs{
* @param e
*
*/
- public function broadcastEvent(e:Event):void{
+ public function broadcastEvent(e:Event):void {
dispatchEvent(e);
}
@@ -413,7 +424,7 @@ package com.videojs{
* @param args
*
*/
- public function broadcastEventExternally(... args):void{
+ public function broadcastEventExternally(... args):void {
if(_jsEventProxyName != ""){
if(ExternalInterface.available){
var __incomingArgs:* = args as Array;
@@ -428,7 +439,7 @@ package com.videojs{
* @param args
*
*/
- public function broadcastErrorEventExternally(... args):void{
+ public function broadcastErrorEventExternally(... args):void {
if(_jsErrorEventProxyName != ""){
if(ExternalInterface.available){
var __incomingArgs:* = args as Array;
@@ -442,7 +453,7 @@ package com.videojs{
* Loads the video in a paused state.
*
*/
- public function load():void{
+ public function load():void {
if(_provider){
_provider.load();
}
@@ -452,7 +463,7 @@ package com.videojs{
* Loads the video and begins playback immediately.
*
*/
- public function play():void{
+ public function play():void {
if(_provider){
_provider.play();
}
@@ -462,7 +473,7 @@ package com.videojs{
* Pauses video playback.
*
*/
- public function pause():void{
+ public function pause():void {
if(_provider){
_provider.pause();
}
@@ -472,7 +483,7 @@ package com.videojs{
* Resumes video playback.
*
*/
- public function resume():void{
+ public function resume():void {
if(_provider){
_provider.resume();
}
@@ -483,7 +494,7 @@ package com.videojs{
* @param pValue
*
*/
- public function seekBySeconds(pValue:Number):void{
+ public function seekBySeconds(pValue:Number):void {
if(_provider){
_provider.seekBySeconds(pValue);
}
@@ -494,7 +505,7 @@ package com.videojs{
* @param pValue A float from 0 to 1 that represents the desired seek percent.
*
*/
- public function seekByPercent(pValue:Number):void{
+ public function seekByPercent(pValue:Number):void {
if(_provider){
_provider.seekByPercent(pValue);
}
@@ -504,7 +515,7 @@ package com.videojs{
* Stops video playback, clears the video element, and stops any loading proceeses.
*
*/
- public function stop():void{
+ public function stop():void {
if(_provider){
_provider.stop();
}
@@ -539,7 +550,7 @@ package com.videojs{
return pString.replace(/[^A-Za-z0-9_.]/gi, "");
}
- private function initProvider():void{
+ private function initProvider():void {
if(_provider){
_provider.die();
_provider = null;
diff --git a/src/com/videojs/providers/HTTPAudioProvider.as b/src/com/videojs/providers/HTTPAudioProvider.as
index a825225f..ea7f2b22 100644
--- a/src/com/videojs/providers/HTTPAudioProvider.as
+++ b/src/com/videojs/providers/HTTPAudioProvider.as
@@ -114,6 +114,10 @@ package com.videojs.providers{
public function appendBuffer(bytes:ByteArray):void{
throw "HTTPAudioProvider does not support appendBuffer";
}
+
+ public function endOfStream():void{
+ throw "HTTPAudioProvider does not support endOfStream";
+ }
public function get buffered():Number{
if(duration > 0){
@@ -441,4 +445,4 @@ package com.videojs.providers{
_model.broadcastEventExternally(ExternalEventName.ON_METADATA, _metadata);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/com/videojs/providers/HTTPVideoProvider.as b/src/com/videojs/providers/HTTPVideoProvider.as
index 7cc4e5c5..3656bbe3 100644
--- a/src/com/videojs/providers/HTTPVideoProvider.as
+++ b/src/com/videojs/providers/HTTPVideoProvider.as
@@ -4,14 +4,13 @@ package com.videojs.providers{
import com.videojs.events.VideoPlaybackEvent;
import com.videojs.structs.ExternalErrorEventName;
import com.videojs.structs.ExternalEventName;
- import com.videojs.structs.PlaybackType;
-
import flash.events.EventDispatcher;
import flash.events.NetStatusEvent;
import flash.events.TimerEvent;
import flash.media.Video;
import flash.net.NetConnection;
import flash.net.NetStream;
+ import flash.net.NetStreamAppendBytesAction;
import flash.utils.ByteArray;
import flash.utils.Timer;
import flash.utils.getTimer;
@@ -28,6 +27,22 @@ package com.videojs.providers{
private var _loadErrored:Boolean = false;
private var _pauseOnStart:Boolean = false;
private var _pausePending:Boolean = false;
+ /**
+ * The number of seconds between the logical start of the stream and the current zero
+ * playhead position of the NetStream. During normal, file-based playback this value should
+ * always be zero. When the NetStream is in data generation mode, seeking during playback
+ * resets the zero point of the stream to the seek target. To recover the playhead position
+ * in the logical stream, this value can be added to the NetStream reported time.
+ *
+ * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/NetStream.html#play()
+ */
+ private var _startOffset:Number = 0;
+ /**
+ * If true, an empty NetStream buffer should be interpreted as the end of the video. This
+ * is probably the case because the video data is being fed to the NetStream dynamically
+ * through appendBuffer, not for traditional file download video.
+ */
+ private var _ending:Boolean = false;
private var _videoReference:Video;
/**
@@ -36,7 +51,7 @@ package com.videojs.providers{
* we cache the intended time, and use it IN PLACE OF NetStream's time when the time accessor is hit.
*/
private var _pausedSeekValue:Number = -1;
-
+
private var _src:Object;
private var _metadata:Object;
private var _isPlaying:Boolean = false;
@@ -48,6 +63,7 @@ package com.videojs.providers{
private var _hasEnded:Boolean = false;
private var _canPlayThrough:Boolean = false;
private var _loop:Boolean = false;
+ private var _durationOverride:Number;
private var _model:VideoJSModel;
@@ -56,7 +72,6 @@ package com.videojs.providers{
_metadata = {};
_throughputTimer = new Timer(250, 0);
_throughputTimer.addEventListener(TimerEvent.TIMER, onThroughputTimerTick);
-
}
public function get loop():Boolean{
@@ -73,7 +88,7 @@ package com.videojs.providers{
return _pausedSeekValue;
}
else{
- return _ns.time;
+ return _startOffset + _ns.time;
}
}
else{
@@ -84,11 +99,17 @@ package com.videojs.providers{
public function get duration():Number{
if(_metadata != null && _metadata.duration != undefined){
return Number(_metadata.duration);
+ } else if( _durationOverride && _durationOverride > 0 ) {
+ return _durationOverride;
}
else{
return 0;
}
}
+
+ public function set duration(value:Number):void {
+ _durationOverride = value;
+ }
public function get readyState():int{
// if we have metadata and a known duration
@@ -139,17 +160,28 @@ package com.videojs.providers{
}
}
+ public function appendBytesAction(action:String):void {
+ if(_ns) {
+ _ns.appendBytesAction(action);
+ }
+ }
+
public function appendBuffer(bytes:ByteArray):void{
_ns.appendBytes(bytes);
}
+
+ public function endOfStream():void{
+ _ending = true;
+ }
public function get buffered():Number{
- if(duration > 0){
+ // _src.path == null when in data generation mode
+ if(_ns && _src.path == null)
+ {
+ return _startOffset + _ns.bufferLength + _ns.time;
+ } else if(duration > 0){
return (_ns.bytesLoaded / _ns.bytesTotal) * duration;
- }
- else if (_ns){
- return _ns.bufferLength + _ns.time;
- } else{
+ } else {
return 0;
}
}
@@ -274,23 +306,29 @@ package com.videojs.providers{
}
public function seekBySeconds(pTime:Number):void{
- if(_isPlaying){
- if(duration != 0 && pTime <= duration){
- _isSeeking = true;
- _throughputTimer.stop();
- if(_isPaused){
- _pausedSeekValue = pTime;
- }
- _ns.seek(pTime);
- _isBuffering = true;
+ if(_isPlaying)
+ {
+ _isSeeking = true;
+ _throughputTimer.stop();
+ if(_isPaused)
+ {
+ _pausedSeekValue = pTime;
}
}
- else if(_hasEnded){
- _ns.seek(pTime);
+ else if(_hasEnded)
+ {
_isPlaying = true;
_hasEnded = false;
- _isBuffering = true;
}
+
+ if(_src.path === null)
+ {
+ _startOffset = pTime;
+ }
+
+ _ns.seek(pTime);
+ _isBuffering = true;
+
}
public function seekByPercent(pPercent:Number):void{
@@ -388,6 +426,7 @@ package com.videojs.providers{
_ns = null;
}
_ns = new NetStream(_nc);
+ _ns.inBufferSeek = true;
_ns.addEventListener(NetStatusEvent.NET_STATUS, onNetStreamStatus);
_ns.client = this;
_ns.bufferTime = .5;
@@ -464,6 +503,13 @@ package com.videojs.providers{
}
_loadStarted = true;
break;
+
+ case "NetStream.SeekStart.Notify":
+ if(_src.path === null) {
+ appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK);
+ }
+ _model.broadcastEventExternally(ExternalEventName.ON_SEEK_START);
+ break;
case "NetStream.Buffer.Full":
_pausedSeekValue = -1;
@@ -482,12 +528,28 @@ package com.videojs.providers{
case "NetStream.Buffer.Empty":
// should not fire if ended/paused. issue #38
if(!_isPlaying){ return; }
+
+ // reaching the end of the buffer after endOfStream has been called means we've
+ // hit the end of the video
+ if (_ending) {
+ _ending = false;
+ _isPlaying = false;
+ _isPaused = true;
+ _hasEnded = true;
+ _model.broadcastEvent(new VideoPlaybackEvent(VideoPlaybackEvent.ON_STREAM_CLOSE, {info:e.info}));
+ _model.broadcastEventExternally(ExternalEventName.ON_PAUSE);
+ _model.broadcastEventExternally(ExternalEventName.ON_PLAYBACK_COMPLETE);
+
+ _startOffset = 0;
+ _pausedSeekValue = 0;
+ break;
+ }
+
_isBuffering = true;
_model.broadcastEventExternally(ExternalEventName.ON_BUFFER_EMPTY);
break;
case "NetStream.Play.Stop":
-
if(!_loop){
_isPlaying = false;
_isPaused = true;
@@ -503,7 +565,7 @@ package com.videojs.providers{
_throughputTimer.stop();
_throughputTimer.reset();
break;
-
+
case "NetStream.Seek.Notify":
_isPlaying = true;
_isSeeking = false;
@@ -514,8 +576,7 @@ package com.videojs.providers{
_loadStartTimestamp = getTimer();
_throughputTimer.reset();
_throughputTimer.start();
-
- break;
+ break;
case "NetStream.Play.StreamNotFound":
_loadErrored = true;
@@ -539,6 +600,7 @@ package com.videojs.providers{
}
public function onMetaData(pMetaData:Object):void{
+
_metadata = pMetaData;
if(pMetaData.duration != undefined){
_isLive = false;
diff --git a/src/com/videojs/providers/IProvider.as b/src/com/videojs/providers/IProvider.as
index 0345e9c2..523c949f 100644
--- a/src/com/videojs/providers/IProvider.as
+++ b/src/com/videojs/providers/IProvider.as
@@ -30,6 +30,13 @@ package com.videojs.providers{
* @param bytes the ByteArray of data to append.
*/
function appendBuffer(bytes:ByteArray):void;
+
+ /**
+ * Indicates that no further bytes will appended to the source
+ * buffer. After this method has been called, reaching the end
+ * of buffered input is equivalent to the end of the media.
+ */
+ function endOfStream():void;
/**
* Should return an interger that reflects the closest parallel to
@@ -168,4 +175,4 @@ package com.videojs.providers{
function die():void;
}
-}
\ No newline at end of file
+}
diff --git a/src/com/videojs/providers/RTMPVideoProvider.as b/src/com/videojs/providers/RTMPVideoProvider.as
index e9ea25c8..b6d329e5 100644
--- a/src/com/videojs/providers/RTMPVideoProvider.as
+++ b/src/com/videojs/providers/RTMPVideoProvider.as
@@ -137,6 +137,10 @@ package com.videojs.providers{
public function appendBuffer(bytes:ByteArray):void{
throw "RTMPVideoProvider does not support appendBuffer";
}
+
+ public function endOfStream():void{
+ throw "RTMPVideoProvider does not support endOfStream";
+ }
public function get buffered():Number{
if(duration > 0){