diff --git a/README.markdown b/README.markdown
index 17161a06b..c736639da 100644
--- a/README.markdown
+++ b/README.markdown
@@ -133,6 +133,9 @@ Changelog
---------
2.0.3
+- Feature: Made returning to the start at the end of the movie an option ("returnToStart").
+- Feature: Added loop option to loop movie ("loop").
+- Feature: Reorganized player API and listeners.
- Feature: Setup method now has a callback, so you can more easily work with the player after setup
- Changes: setupAllWhenReady is now just setupAll (backward compatible)
diff --git a/dev/combine_sources.sh b/dev/combine_sources.sh
index 58badc8f9..84b18b35b 100755
--- a/dev/combine_sources.sh
+++ b/dev/combine_sources.sh
@@ -3,6 +3,7 @@
cat src/_begin.js > combined.js
cat src/main.js >> combined.js
+cat src/api.js >> combined.js
cat src/html5.js >> combined.js
cat src/flash.js >> combined.js
cat src/behaviors.js >> combined.js
diff --git a/dev/flowplayer/test.html b/dev/flowplayer/test.html
index efe2e6101..a388128b2 100644
--- a/dev/flowplayer/test.html
+++ b/dev/flowplayer/test.html
@@ -5,10 +5,13 @@
HTML5 Video Player: flowplayer
-
+
+
+
+
diff --git a/dev/flowplayer/video-js.flowplayer.js b/dev/flowplayer/video-js.flowplayer.js
index a98f8e61b..086d63c41 100644
--- a/dev/flowplayer/video-js.flowplayer.js
+++ b/dev/flowplayer/video-js.flowplayer.js
@@ -20,80 +20,43 @@ VideoJS.flashPlayers.flowplayer = {
},
api: {
- play: function(){ this.flowplayer.play(); return this; },
- onPlay: function(fn){
- this.flowplayer.onStart(fn.context(this));
- this.flowplayer.onResume(fn.context(this));
- return this;
+
+ setupTriggers: function(){
+ this.flowplayer.onStart(function(e){ this.triggerListeners("play", e); }.context(this));
+ this.flowplayer.onResume(function(e){ this.triggerListeners("play", e); }.context(this));
+ this.flowplayer.onPause(function(e){ this.triggerListeners("pause", e); }.context(this));
+ this.flowplayer.onVolume(function(e){ this.triggerListeners("volumechange", e); }.context(this));
+ this.flowplayer.onError(function(e){ this.triggerListeners("error", e); }.context(this));
},
- pause: function(){ this.flowplayer.pause(); return this; },
- onPause: function(fn){
- this.flowplayer.onPause(fn.context(this));
- return this;
- },
+ play: function(){ this.flowplayer.play(); },
+ pause: function(){ this.flowplayer.pause(); },
paused: function(){
return !this.flowplayer.isPlaying(); // More accurate than isPaused
},
- currentTime: function(seconds){
- if (seconds !== undefined) {
- this.flowplayer.seek(seconds);
- return this;
- }
- return this.flowplayer.getTime();
- },
+ currentTime: function(){ return this.flowplayer.getTime(); },
+ setCurrentTime: function(seconds){ this.flowplayer.seek(seconds); },
duration: function(){
var clip = this.flowplayer.getClip();
- if (clip) { return clip.duration; }
+ return (clip) ? clip.duration : 0;
},
-
+
buffered: function(){
var status = this.flowplayer.getStatus();
- return [status.bufferStart, status.bufferEnd]
+ return this.createTimeRange(status.bufferStart, status.bufferEnd);
},
- volume: function(percentAsDecimal){
- if (percentAsDecimal !== undefined) {
- this.values.volume = parseFloat(percentAsDecimal);
- this.flowplayer.setVolume(parseInt(percentAsDecimal * 100));
- this.setLocalStorage("volume", this.values.volume);
- return this;
- }
- return this.flowplayer.getVolume() / 100;
- },
- onVolumeChange: function(fn){ this.flowplayer.onVolume(fn.context(this)); return this; },
-
- width: function(width){
- if (width !== undefined) {
- this.flashElement.width = width;
- this.box.style.width = width+"px";
- this.triggerResizeListeners();
- return this;
- }
- return parseInt(this.flashElement.getAttribute("width"));
- },
- height: function(height){
- if (height !== undefined) {
- this.flashElement.height = height;
- this.box.style.height = height+"px";
- this.triggerResizeListeners();
- return this;
- }
- return parseInt(this.flashElement.getAttribute("height"));
- },
+ volume: function(){ return this.flowplayer.getVolume() / 100; },
+ setVolume: function(percentAsDecimal){ this.flowplayer.setVolume(parseInt(percentAsDecimal * 100)); },
supportsFullScreen: function(){
return false; // Flash does not allow fullscreen through javascript
// Maybe at click listener, and say "click screen".
},
- enterFullScreen: function(){
- this.flowplayer.toggleFullscreen();
- return this;
- },
+ enterFullScreen: function(){ this.flowplayer.toggleFullscreen(); },
- onError: function(fn){ this.flowplayer.onError(fn); return this; },
readError: function(eventArguments){
var errorCode = arguments[0],
errorMessage = arguments[1];
diff --git a/dev/src/api.js b/dev/src/api.js
new file mode 100644
index 000000000..bf842b036
--- /dev/null
+++ b/dev/src/api.js
@@ -0,0 +1,150 @@
+/* Player API
+================================================================================ */
+VideoJS.fn.extend({
+
+ /* Listener types: play, pause, timeupdate, bufferedupdate, ended, volumechange, error */
+ addListener: function(type, fn){
+ if (!this.listeners[type]) { this.listeners[type] = []; }
+ this.listeners[type].push(fn);
+ },
+
+ triggerListeners: function(type, e){
+ this.each(this.listeners[type], function(listener){
+ listener.call(this, e);
+ });
+ },
+
+ removeListener: function(type, fn){
+ var listeners = this.listeners[type];
+ if (!listeners) { return; }
+ for (var i=0; i 0 && buffered.end(0) > end) {
+ end = buffered.end(0);
+ // Storing values allows them be overridden by setBufferedFromProgress
+ this.values.bufferEnd = end;
+ }
+
+ return this.createTimeRange(start, end);
+ },
+ // Mimic HTML5 TimeRange Spec.
+ createTimeRange: function(start, end){
+ return {
+ length: 1,
+ start: function() { return start; },
+ end: function() { return end; }
+ };
+ },
+ // Calculates amount of buffer is full
+ bufferedPercent: function(){ return (this.duration()) ? this.buffered().end(0) / this.duration() : 0; },
+
+ volume: function(percentAsDecimal){
+ if (percentAsDecimal !== undefined) {
+ var vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
+ this.values.volume = vol;
+ this.api.setVolume.call(this, vol);
+ this.setLocalStorage("volume", vol);
+ return this;
+ }
+ // if (this.values.volume) { return this.values.volume; }
+ return this.api.volume.call(this);
+ },
+
+ width: function(width, skipListeners){
+ if (width !== undefined) {
+ this.element.width = width; // Not using style so it can be overridden on fullscreen.
+ this.box.style.width = width+"px";
+ if (!skipListeners) { this.triggerListeners("resize"); }
+ return this;
+ }
+ return parseInt(this.element.getAttribute("width"));
+ // return this.element.offsetWidth;
+ },
+ height: function(height){
+ if (height !== undefined) {
+ this.element.height = height;
+ this.box.style.height = height+"px";
+ this.triggerListeners("resize");
+ return this;
+ }
+ return parseInt(this.element.getAttribute("height"));
+ // return this.element.offsetHeight;
+ },
+ size: function(width, height){
+ // Skip resize listeners on width for optimization
+ return this.width(width, true).height(height);
+ },
+
+ supportsFullScreen: function(){ return this.api.supportsFullScreen.call(this); },
+
+ // Turn on fullscreen (or window) mode
+ enterFullScreen: function(){
+ if (this.supportsFullScreen()) {
+ this.api.enterFullScreen.call(this);
+ } else {
+ this.enterFullWindow();
+ }
+ return this;
+ },
+
+ exitFullScreen: function(){
+ if (!this.supportsFullScreen()) {
+ this.exitFullWindow();
+ }
+ // Otherwise Shouldn't be called since native fullscreen uses own controls.
+ return this;
+ },
+
+ enterFullWindow: function(){
+ this.videoIsFullScreen = true;
+ // Storing original doc overflow value to return to when fullscreen is off
+ this.docOrigOverflow = document.documentElement.style.overflow;
+ // Add listener for esc key to exit fullscreen
+ _V_.addListener(document, "keydown", this.fullscreenOnEscKey.rEvtContext(this));
+ // Add listener for a window resize
+ _V_.addListener(window, "resize", this.fullscreenOnWindowResize.rEvtContext(this));
+ // Hide any scroll bars
+ document.documentElement.style.overflow = 'hidden';
+ // Apply fullscreen styles
+ _V_.addClass(this.box, "vjs-fullscreen");
+ // Resize the box, controller, and poster
+ this.positionAll();
+ },
+
+ exitFullWindow: function(){
+ this.videoIsFullScreen = false;
+ document.removeEventListener("keydown", this.fullscreenOnEscKey, false);
+ window.removeEventListener("resize", this.fullscreenOnWindowResize, false);
+ // Unhide scroll bars.
+ document.documentElement.style.overflow = this.docOrigOverflow;
+ // Remove fullscreen styles
+ _V_.removeClass(this.box, "vjs-fullscreen");
+ // Resize the box, controller, and poster to original sizes
+ this.positionAll();
+ }
+});
\ No newline at end of file
diff --git a/dev/src/behaviors.js b/dev/src/behaviors.js
index 1a3298c18..d75f05016 100644
--- a/dev/src/behaviors.js
+++ b/dev/src/behaviors.js
@@ -6,82 +6,61 @@
/* Player Behaviors - How VideoJS reacts to what the video is doing.
================================================================================ */
VideoJS.fn.newBehavior("player", function(player){
- this.onError(this.playerOnVideoError);
- // Listen for when the video is played
- this.onPlay(this.playerOnVideoPlay);
- this.onPlay(this.trackCurrentTime);
- // Listen for when the video is paused
- this.onPause(this.playerOnVideoPause);
- this.onPause(this.stopTrackingCurrentTime);
- // Listen for when the video ends
- this.onEnded(this.playerOnVideoEnded);
- // Set interval for load progress using buffer watching method
- // this.trackCurrentTime();
+ this.addListener("error", this.playerOnVideoError);
+ this.addListener("play", this.playerOnVideoPlay);
+ this.addListener("play", this.trackCurrentTime);
+ this.addListener("pause", this.stopTrackingCurrentTime);
+ this.addListener("ended", this.playerOnVideoEnded);
this.trackBuffered();
- // Buffer Full
- this.onBufferedUpdate(this.isBufferFull);
+ this.addListener("bufferedupdate", this.bufferFull);
},{
playerOnVideoError: function(event){
this.log(event);
this.log(this.video.error);
},
playerOnVideoPlay: function(event){ this.hasPlayed = true; },
- playerOnVideoPause: function(event){},
playerOnVideoEnded: function(event){
- this.currentTime(0);
- this.pause();
+ if (this.options.loop) {
+ this.currentTime(0);
+ this.play();
+ } else if (this.options.returnToStart) {
+ this.currentTime(0);
+ this.pause();
+ }
},
/* Load Tracking -------------------------------------------------------------- */
// Buffer watching method for load progress.
// Used for browsers that don't support the progress event
trackBuffered: function(){
- this.bufferedInterval = setInterval(this.triggerBufferedListeners.context(this), 500);
+ this.bufferedInterval = setInterval(function(){
+ // Don't trigger unless
+ if (this.values.bufferEnd < this.buffered().end(0)) {
+ this.triggerListeners("bufferedupdate");
+ }
+ }.context(this), 500);
},
stopTrackingBuffered: function(){ clearInterval(this.bufferedInterval); },
- bufferedListeners: [],
- onBufferedUpdate: function(fn){
- this.bufferedListeners.push(fn);
- },
- triggerBufferedListeners: function(){
- this.isBufferFull();
- this.each(this.bufferedListeners, function(listener){
- (listener.context(this))();
- });
- },
- isBufferFull: function(){
- if (this.bufferedPercent() == 1) { this.stopTrackingBuffered(); }
+ bufferFull: function(){
+ if (this.bufferedPercent() == 1) {
+ this.stopTrackingBuffered();
+ }
},
/* Time Tracking -------------------------------------------------------------- */
trackCurrentTime: function(){
if (this.currentTimeInterval) { clearInterval(this.currentTimeInterval); }
- this.currentTimeInterval = setInterval(this.triggerCurrentTimeListeners.context(this), 100); // 42 = 24 fps
+
+ this.currentTimeInterval = setInterval(function(){
+ this.triggerListeners("timeupdate");
+ }.context(this), 100); // 42 = 24 fps
+
this.trackingCurrentTime = true;
},
// Turn off play progress tracking (when paused or dragging)
stopTrackingCurrentTime: function(){
clearInterval(this.currentTimeInterval);
this.trackingCurrentTime = false;
- },
- currentTimeListeners: [],
- // onCurrentTimeUpdate is in API section now
- triggerCurrentTimeListeners: function(late, newTime){ // FF passes milliseconds late as the first argument
- this.each(this.currentTimeListeners, function(listener){
- (listener.context(this))(newTime || this.currentTime());
- });
- },
-
- /* Resize Tracking -------------------------------------------------------------- */
- resizeListeners: [],
- onResize: function(fn){
- this.resizeListeners.push(fn);
- },
- // Trigger anywhere the video/box size is changed.
- triggerResizeListeners: function(){
- this.each(this.resizeListeners, function(listener){
- (listener.context(this))();
- });
}
}
);
@@ -116,8 +95,8 @@ VideoJS.fn.newBehavior("box", function(element){
this.positionBox();
_V_.addClass(element, "vjs-paused");
this.activateElement(element, "mouseOverVideoReporter");
- this.onPlay(this.boxOnVideoPlay);
- this.onPause(this.boxOnVideoPause);
+ this.addListener("play", this.boxOnVideoPlay);
+ this.addListener("pause", this.boxOnVideoPause);
},{
boxOnVideoPlay: function(){
_V_.removeClass(this.box, "vjs-paused");
@@ -134,9 +113,9 @@ VideoJS.fn.newBehavior("box", function(element){
VideoJS.fn.newBehavior("poster", function(element){
this.activateElement(element, "mouseOverVideoReporter");
this.activateElement(element, "playButton");
- this.onPlay(this.hidePoster);
- this.onEnded(this.showPoster);
- this.onResize(this.positionPoster);
+ this.addListener("play", this.hidePoster);
+ this.addListener("ended", this.showPoster);
+ this.addListener("resize", this.positionPoster);
},{
showPoster: function(){
if (!this.poster) { return; }
@@ -168,7 +147,7 @@ VideoJS.fn.newBehavior("poster", function(element){
VideoJS.fn.newBehavior("controlBar", function(element){
if (!this.controlBars) {
this.controlBars = [];
- this.onResize(this.positionControlBars);
+ this.addListener("resize", this.positionControlBars);
}
this.controlBars.push(element);
_V_.addListener(element, "mousemove", this.onControlBarsMouseMove.context(this));
@@ -177,6 +156,7 @@ VideoJS.fn.newBehavior("controlBar", function(element){
showControlBars: function(){
if (!this.options.controlsAtStart && !this.hasPlayed) { return; }
this.each(this.controlBars, function(bar){
+ // bar.style.opacity = 1;
bar.style.display = "block";
});
},
@@ -188,6 +168,7 @@ VideoJS.fn.newBehavior("controlBar", function(element){
hideControlBars: function(){
if (this.options.controlsHiding && !this.mouseIsOverControls) {
this.each(this.controlBars, function(bar){
+ // bar.style.opacity = 0;
bar.style.display = "none";
});
}
@@ -205,8 +186,8 @@ VideoJS.fn.newBehavior("controlBar", function(element){
VideoJS.fn.newBehavior("playToggle", function(element){
if (!this.elements.playToggles) {
this.elements.playToggles = [];
- this.onPlay(this.playTogglesOnPlay);
- this.onPause(this.playTogglesOnPause);
+ this.addListener("play", this.playTogglesOnPlay);
+ this.addListener("pause", this.playTogglesOnPause);
}
this.elements.playToggles.push(element);
_V_.addListener(element, "click", this.onPlayToggleClick.context(this));
@@ -249,17 +230,17 @@ VideoJS.fn.newBehavior("pauseButton", function(element){
/* Play Progress Bar Behaviors
================================================================================ */
VideoJS.fn.newBehavior("playProgressBar", function(element){
- if (!this.playProgressBars) {
- this.playProgressBars = [];
- this.onCurrentTimeUpdate(this.updatePlayProgressBars);
+ if (!this.elements.playProgressBars) {
+ this.elements.playProgressBars = [];
+ this.addListener("timeupdate", this.updatePlayProgressBars);
}
- this.playProgressBars.push(element);
+ this.elements.playProgressBars.push(element);
},{
// Ajust the play progress bar's width based on the current play time
updatePlayProgressBars: function(newTime){
- var progress = (newTime !== undefined) ? newTime / this.duration() : this.currentTime() / this.duration();
+ var progress = (this.scrubbing) ? this.values.currentTime / this.duration() : this.currentTime() / this.duration();
if (isNaN(progress)) { progress = 0; }
- this.each(this.playProgressBars, function(bar){
+ this.each(this.elements.playProgressBars, function(bar){
if (bar.style) { bar.style.width = _V_.round(progress * 100, 2) + "%"; }
});
}
@@ -268,12 +249,12 @@ VideoJS.fn.newBehavior("playProgressBar", function(element){
/* Load Progress Bar Behaviors
================================================================================ */
VideoJS.fn.newBehavior("loadProgressBar", function(element){
- if (!this.loadProgressBars) { this.loadProgressBars = []; }
- this.loadProgressBars.push(element);
- this.onBufferedUpdate(this.updateLoadProgressBars);
+ if (!this.elements.loadProgressBars) { this.elements.loadProgressBars = []; }
+ this.elements.loadProgressBars.push(element);
+ this.addListener("bufferedupdate", this.updateLoadProgressBars);
},{
updateLoadProgressBars: function(){
- this.each(this.loadProgressBars, function(bar){
+ this.each(this.elements.loadProgressBars, function(bar){
if (bar.style) { bar.style.width = _V_.round(this.bufferedPercent() * 100, 2) + "%"; }
});
}
@@ -283,18 +264,17 @@ VideoJS.fn.newBehavior("loadProgressBar", function(element){
/* Current Time Display Behaviors
================================================================================ */
VideoJS.fn.newBehavior("currentTimeDisplay", function(element){
- if (!this.currentTimeDisplays) {
- this.currentTimeDisplays = [];
- this.onCurrentTimeUpdate(this.updateCurrentTimeDisplays);
+ if (!this.elements.currentTimeDisplays) {
+ this.elements.currentTimeDisplays = [];
+ this.addListener("timeupdate", this.updateCurrentTimeDisplays);
}
- this.currentTimeDisplays.push(element);
+ this.elements.currentTimeDisplays.push(element);
},{
// Update the displayed time (00:00)
updateCurrentTimeDisplays: function(newTime){
- if (!this.currentTimeDisplays) { return; }
// Allows for smooth scrubbing, when player can't keep up.
- var time = (newTime) ? newTime : this.currentTime();
- this.each(this.currentTimeDisplays, function(dis){
+ var time = (this.scrubbing) ? this.values.currentTime : this.currentTime();
+ this.each(this.elements.currentTimeDisplays, function(dis){
dis.innerHTML = _V_.formatTime(time);
});
}
@@ -304,15 +284,14 @@ VideoJS.fn.newBehavior("currentTimeDisplay", function(element){
/* Duration Display Behaviors
================================================================================ */
VideoJS.fn.newBehavior("durationDisplay", function(element){
- if (!this.durationDisplays) {
- this.durationDisplays = [];
- this.onCurrentTimeUpdate(this.updateDurationDisplays);
+ if (!this.elements.durationDisplays) {
+ this.elements.durationDisplays = [];
+ this.addListener("timeupdate", this.updateDurationDisplays);
}
- this.durationDisplays.push(element);
+ this.elements.durationDisplays.push(element);
},{
updateDurationDisplays: function(){
- if (!this.durationDisplays) { return; }
- this.each(this.durationDisplays, function(dis){
+ this.each(this.elements.durationDisplays, function(dis){
if (this.duration()) { dis.innerHTML = _V_.formatTime(this.duration()); }
});
}
@@ -330,6 +309,7 @@ VideoJS.fn.newBehavior("currentTimeScrubber", function(element){
this.currentScrubber = scrubber;
this.stopTrackingCurrentTime(); // Allows for smooth scrubbing
+ this.scrubbing = true;
this.videoWasPlaying = !this.paused();
this.pause();
@@ -346,6 +326,7 @@ VideoJS.fn.newBehavior("currentTimeScrubber", function(element){
_V_.unblockTextSelection();
document.removeEventListener("mousemove", this.onCurrentTimeScrubberMouseMove, false);
document.removeEventListener("mouseup", this.onCurrentTimeScrubberMouseUp, false);
+ this.scrubbing = false;
if (this.videoWasPlaying) {
this.play();
this.trackCurrentTime();
@@ -354,28 +335,28 @@ VideoJS.fn.newBehavior("currentTimeScrubber", function(element){
setCurrentTimeWithScrubber: function(event){
var newProgress = _V_.getRelativePosition(event.pageX, this.currentScrubber);
var newTime = newProgress * this.duration();
- this.triggerCurrentTimeListeners(0, newTime); // Allows for smooth scrubbing
// Don't let video end while scrubbing.
if (newTime == this.duration()) { newTime = newTime - 0.1; }
this.currentTime(newTime);
+ this.triggerListeners("timeupdate");
}
}
);
/* Volume Display Behaviors
================================================================================ */
VideoJS.fn.newBehavior("volumeDisplay", function(element){
- if (!this.volumeDisplays) {
- this.volumeDisplays = [];
- this.onVolumeChange(this.updateVolumeDisplays);
+ if (!this.elements.volumeDisplays) {
+ this.elements.volumeDisplays = [];
+ this.addListener("volumechange", this.updateVolumeDisplays);
}
- this.volumeDisplays.push(element);
+ this.elements.volumeDisplays.push(element);
this.updateVolumeDisplay(element); // Set the display to the initial volume
},{
// Update the volume control display
// Unique to these default controls. Uses borders to create the look of bars.
updateVolumeDisplays: function(){
- if (!this.volumeDisplays) { return; }
- this.each(this.volumeDisplays, function(dis){
+ if (!this.elements.volumeDisplays) { return; }
+ this.each(this.elements.volumeDisplays, function(dis){
this.updateVolumeDisplay(dis);
});
},
@@ -450,14 +431,12 @@ VideoJS.fn.newBehavior("fullscreenToggle", function(element){
VideoJS.fn.newBehavior("bigPlayButton", function(element){
if (!this.elements.bigPlayButtons) {
this.elements.bigPlayButtons = [];
- this.onPlay(this.bigPlayButtonsOnPlay);
- this.onEnded(this.bigPlayButtonsOnEnded);
+ this.addListener("play", this.hideBigPlayButtons);
+ this.addListener("ended", this.showBigPlayButtons);
}
this.elements.bigPlayButtons.push(element);
this.activateElement(element, "playButton");
},{
- bigPlayButtonsOnPlay: function(event){ this.hideBigPlayButtons(); },
- bigPlayButtonsOnEnded: function(event){ this.showBigPlayButtons(); },
showBigPlayButtons: function(){
this.each(this.elements.bigPlayButtons, function(element){
element.style.display = "block";
@@ -475,6 +454,7 @@ VideoJS.fn.newBehavior("bigPlayButton", function(element){
VideoJS.fn.newBehavior("spinner", function(element){
if (!this.spinners) {
this.spinners = [];
+ this.spinnersRotated = 0;
_V_.addListener(this.video, "loadeddata", this.spinnersOnVideoLoadedData.context(this));
_V_.addListener(this.video, "loadstart", this.spinnersOnVideoLoadStart.context(this));
_V_.addListener(this.video, "seeking", this.spinnersOnVideoSeeking.context(this));
@@ -502,7 +482,6 @@ VideoJS.fn.newBehavior("spinner", function(element){
});
clearInterval(this.spinnerInterval);
},
- spinnersRotated: 0,
rotateSpinners: function(){
this.each(this.spinners, function(spinner){
// spinner.style.transform = 'scale(0.5) rotate('+this.spinnersRotated+'deg)';
@@ -520,7 +499,7 @@ VideoJS.fn.newBehavior("spinner", function(element){
spinnersOnVideoCanPlayThrough: function(event){ this.hideSpinners(); },
spinnersOnVideoWaiting: function(event){
// Safari sometimes triggers waiting inappropriately
- // Like after video has played, any you play again.
+ // Like after video has played, and you play again.
this.showSpinners();
},
spinnersOnVideoStalled: function(event){},
@@ -535,16 +514,17 @@ VideoJS.fn.newBehavior("spinner", function(element){
/* Subtitles
================================================================================ */
VideoJS.fn.newBehavior("subtitlesDisplay", function(element){
- if (!this.subtitleDisplays) {
- this.subtitleDisplays = [];
- this.onCurrentTimeUpdate(this.subtitleDisplaysOnVideoTimeUpdate);
- this.onEnded(function() { this.lastSubtitleIndex = 0; }.context(this));
+ if (!this.elements.subtitleDisplays) {
+ this.elements.subtitleDisplays = [];
+ this.addListener("timeupdate", this.subtitleDisplaysOnVideoTimeUpdate);
+ this.addListener("ended", function() { this.lastSubtitleIndex = 0; }.context(this));
}
- this.subtitleDisplays.push(element);
+ this.elements.subtitleDisplays.push(element);
},{
- subtitleDisplaysOnVideoTimeUpdate: function(time){
+ subtitleDisplaysOnVideoTimeUpdate: function(){
// Assuming all subtitles are in order by time, and do not overlap
if (this.subtitles) {
+ var time = this.currentTime();
// If current subtitle should stay showing, don't do anything. Otherwise, find new subtitle.
if (!this.currentSubtitle || this.currentSubtitle.start >= time || this.currentSubtitle.end < time) {
var newSubIndex = false,
@@ -588,7 +568,7 @@ VideoJS.fn.newBehavior("subtitlesDisplay", function(element){
}
},
updateSubtitleDisplays: function(val){
- this.each(this.subtitleDisplays, function(disp){
+ this.each(this.elements.subtitleDisplays, function(disp){
disp.innerHTML = val;
});
}
diff --git a/dev/src/flash.js b/dev/src/flash.js
index 30d2cc0c0..53a7c1dea 100644
--- a/dev/src/flash.js
+++ b/dev/src/flash.js
@@ -12,9 +12,10 @@ VideoJS.fn.extend({
this.replaceWithFlash();
this.element = this.flashElement;
this.video.src = ""; // Stop video from downloading if HTML5 is still supported
- var flashPlayerType = VideoJS.flashPlayers[this.options.flashPlayer];
- this.extend(flashPlayerType.api);
- (flashPlayerType.init.context(this))();
+ var flashPlayer = VideoJS.flashPlayers[this.options.flashPlayer];
+ flashPlayer.init.call(this);
+ this.api = flashPlayer.api;
+ this.api.setupTriggers.call(this);
},
// Get Flash Fallback object element from Embed Code
@@ -43,32 +44,12 @@ VideoJS.fn.extend({
}
});
-/* Flash Object Fallback (Flash Player Type)
+/* Flash Object Fallback (Flash Player)
================================================================================ */
VideoJS.flashPlayers = {};
VideoJS.flashPlayers.htmlObject = {
flashPlayerVersion: 9,
init: function() { return true; },
- api: { // No video API available with HTML Object embed method
+ api: {} // No video API available with HTML Object embed method
+};
- width: function(width){
- if (width !== undefined) {
- this.element.width = width;
- this.box.style.width = width+"px";
- this.triggerResizeListeners();
- return this;
- }
- return this.element.width;
- },
-
- height: function(height){
- if (height !== undefined) {
- this.element.height = height;
- this.box.style.height = height+"px";
- this.triggerResizeListeners();
- return this;
- }
- return this.element.height;
- }
- }
-};
\ No newline at end of file
diff --git a/dev/src/html5.js b/dev/src/html5.js
index fc30133bb..1564b7b8f 100644
--- a/dev/src/html5.js
+++ b/dev/src/html5.js
@@ -12,6 +12,8 @@ VideoJS.fn.extend({
html5Init: function(){
this.element = this.video;
+ this.api = this.html5API;
+ this.api.setupTriggers.call(this);
this.fixPreloading(); // Support old browsers that used autobuffer
this.supportProgressEvents(); // Support browsers that don't use 'buffered'
@@ -104,15 +106,18 @@ VideoJS.fn.extend({
// Listen for Video Load Progress (currently does not if html file is local)
// Buffered does't work in all browsers, so watching progress as well
supportProgressEvents: function(e){
- _V_.addListener(this.video, 'progress', this.playerOnVideoProgress.context(this));
- },
- playerOnVideoProgress: function(event){
- this.setBufferedFromProgress(event);
+ _V_.addListener(this.video, 'progress', this.setBufferedFromProgress.context(this));
},
+ // playerOnVideoProgress: function(event){
+ // this.setBufferedFromProgress(event);
+ // },
setBufferedFromProgress: function(event){ // HTML5 Only
- if(event.total > 0) {
+ if(event.total > 0 && this.duration()) {
var newBufferEnd = (event.loaded / event.total) * this.duration();
- if (newBufferEnd > this.values.bufferEnd) { this.values.bufferEnd = newBufferEnd; }
+ if (newBufferEnd > this.values.bufferEnd) {
+ this.values.bufferEnd = newBufferEnd;
+ this.triggerListeners("bufferedupdate");
+ }
}
},
@@ -411,153 +416,55 @@ VideoJS.fn.extend({
/* Player API - Translate functionality from player to video
================================================================================ */
- html5AddVideoListener: function(type, fn){ _V_.addListener(this.video, type, fn.rEvtContext(this)); },
+ html5API: {
+ setupTriggers: function(){
+ // Make video events trigger player events
+ // May seem verbose here, but makes other APIs possible.
+ var types = ["play", "pause", "ended", "volumechange", "error"],
+ player = this, i, type;
+ for (i = 0; i 0) {
- var newEnd = this.video.buffered.end(0);
- if (newEnd > this.values.bufferEnd) { this.values.bufferEnd = newEnd; }
- }
- return [this.values.bufferStart, this.values.bufferEnd];
- },
+ width: function(){ return this.video.offsetWidth; },
+ height: function(){ return this.video.offsetHeight; },
- volume: function(percentAsDecimal){
- if (percentAsDecimal !== undefined) {
- // Force value to between 0 and 1
- this.values.volume = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
- this.video.volume = this.values.volume;
- this.setLocalStorage("volume", this.values.volume);
- return this;
- }
- if (this.values.volume) { return this.values.volume; }
- return this.video.volume;
- },
- onVolumeChange: function(fn){ _V_.addListener(this.video, 'volumechange', fn.rEvtContext(this)); },
-
- width: function(width){
- if (width !== undefined) {
- this.video.width = width; // Not using style so it can be overridden on fullscreen.
- this.box.style.width = width+"px";
- this.triggerResizeListeners();
- return this;
- }
- return this.video.offsetWidth;
- },
- height: function(height){
- if (height !== undefined) {
- this.video.height = height;
- this.box.style.height = height+"px";
- this.triggerResizeListeners();
- return this;
- }
- return this.video.offsetHeight;
- },
-
- supportsFullScreen: function(){
- if(typeof this.video.webkitEnterFullScreen == 'function') {
- // Seems to be broken in Chromium/Chrome
- if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) {
- return true;
+ supportsFullScreen: function(){
+ if(typeof this.video.webkitEnterFullScreen == 'function') {
+ // Seems to be broken in Chromium/Chrome && Safari in Leopard
+ if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) {
+ return true;
+ }
+ }
+ return false;
+ },
+ enterFullScreen: function(){
+ try {
+ this.video.webkitEnterFullScreen();
+ } catch (e) {
+ if (e.code == 11) { this.warning(VideoJS.warnings.videoNotReady); }
}
}
- return false;
- },
-
- html5EnterNativeFullScreen: function(){
- try {
- this.video.webkitEnterFullScreen();
- } catch (e) {
- if (e.code == 11) { this.warning(VideoJS.warnings.videoNotReady); }
- }
- return this;
- },
-
- // Turn on fullscreen (window) mode
- // Real fullscreen isn't available in browsers quite yet.
- enterFullScreen: function(){
- if (this.supportsFullScreen()) {
- this.html5EnterNativeFullScreen();
- } else {
- this.enterFullWindow();
- }
- },
-
- exitFullScreen: function(){
- if (this.supportsFullScreen()) {
- // Shouldn't be called
- } else {
- this.exitFullWindow();
- }
- },
-
- enterFullWindow: function(){
- this.videoIsFullScreen = true;
- // Storing original doc overflow value to return to when fullscreen is off
- this.docOrigOverflow = document.documentElement.style.overflow;
- // Add listener for esc key to exit fullscreen
- _V_.addListener(document, "keydown", this.fullscreenOnEscKey.rEvtContext(this));
- // Add listener for a window resize
- _V_.addListener(window, "resize", this.fullscreenOnWindowResize.rEvtContext(this));
- // Hide any scroll bars
- document.documentElement.style.overflow = 'hidden';
- // Apply fullscreen styles
- _V_.addClass(this.box, "vjs-fullscreen");
- // Resize the box, controller, and poster
- this.positionAll();
- },
-
- // Turn off fullscreen (window) mode
- exitFullWindow: function(){
- this.videoIsFullScreen = false;
- document.removeEventListener("keydown", this.fullscreenOnEscKey, false);
- window.removeEventListener("resize", this.fullscreenOnWindowResize, false);
- // Unhide scroll bars.
- document.documentElement.style.overflow = this.docOrigOverflow;
- // Remove fullscreen styles
- _V_.removeClass(this.box, "vjs-fullscreen");
- // Resize the box, controller, and poster to original sizes
- this.positionAll();
- },
-
- onError: function(fn){ this.html5AddVideoListener("error", fn); return this; },
+ }
});
diff --git a/dev/src/lib.js b/dev/src/lib.js
index 71989a71f..dcfd24113 100644
--- a/dev/src/lib.js
+++ b/dev/src/lib.js
@@ -75,6 +75,9 @@ VideoJS.extend({
},
get: function(url, onSuccess){
+ // if (netscape.security.PrivilegeManager.enablePrivilege) {
+ // netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
+ // }
if (typeof XMLHttpRequest == "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
@@ -91,7 +94,11 @@ VideoJS.extend({
onSuccess(request.responseText);
}
}.context(this);
- request.send();
+ try {
+ request.send();
+ } catch(e) {
+ // Can't access file.
+ }
},
trim: function(string){ return string.toString().replace(/^\s+/, "").replace(/\s+$/, ""); },
diff --git a/dev/src/main.js b/dev/src/main.js
index 488353a61..3c0943b91 100644
--- a/dev/src/main.js
+++ b/dev/src/main.js
@@ -14,17 +14,24 @@ var VideoJS = JRClass.extend({
} else {
this.video = element;
}
- // Store reference to player on the video element.
- // So you can acess the player later: document.getElementById("video_id").player.play();
- this.video.player = this;
+
+ this.video.player = this; // Store reference to player on the video element.
+ this.box = this.video.parentNode; // Container element for controls positioning
this.values = {}; // Cache video values.
this.elements = {}; // Store refs to controls elements.
- this.api = {}; // API to video functions
+ this.listeners = {}; // Store video event listeners.
+ this.api = {}; // Current API to video functions (changes with player type)
+
+ // Hide Links. Will be shown again if "links" player is used
+ this.linksFallback = this.getLinksFallback();
+ this.hideLinksFallback();
// Default Options
this.options = {
autoplay: false,
preload: true,
+ loop: false,
+ returnToStart: true,
useBuiltInControls: false, // Use the browser's controls (iPhone)
controlsBelow: false, // Display control bar below video vs. in front of
controlsAtStart: false, // Make controls visible when page loads
@@ -42,11 +49,6 @@ var VideoJS = JRClass.extend({
if (this.getPreloadAttribute() !== undefined) { this.options.preload = this.getPreloadAttribute(); }
if (this.getAutoplayAttribute() !== undefined) { this.options.autoplay = this.getAutoplayAttribute(); }
- // Store reference to embed code pieces
- this.box = this.video.parentNode;
- this.linksFallback = this.getLinksFallback();
- this.hideLinksFallback(); // Will be shown again if "links" player is used
-
// Loop through the player names list in options, "html5" etc.
// For each player name, initialize the player with that name under VideoJS.players
// If the player successfully initializes, we're done
@@ -122,8 +124,6 @@ var VideoJS = JRClass.extend({
return true;
}
},
- // Calculates amoutn of buffer is full
- bufferedPercent: function(){ return (this.duration()) ? this.buffered()[1] / this.duration() : 0; },
// Each that maintains player as context
// Break if true is returned
each: function(arr, fn){
@@ -132,6 +132,7 @@ var VideoJS = JRClass.extend({
if (fn.call(this, arr[i], i)) { break; }
}
},
+ // Add functions to the player
extend: function(obj){
for (var attrname in obj) {
if (obj.hasOwnProperty(attrname)) { this[attrname]=obj[attrname]; }
diff --git a/dev/src/video-js.jquery.js b/dev/src/video-js.jquery.js
index 0a12acb13..cb43320b6 100644
--- a/dev/src/video-js.jquery.js
+++ b/dev/src/video-js.jquery.js
@@ -1,4 +1,5 @@
-// jQuery Plugin
+/* jQuery Plugin
+================================================================================ */
if (window.jQuery) {
(function($) {
diff --git a/dev/test.html b/dev/test.html
index b823ebb77..88196f67c 100644
--- a/dev/test.html
+++ b/dev/test.html
@@ -5,8 +5,10 @@
HTML5 Video Player
-
+
+
+
@@ -80,11 +82,11 @@