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 @@
-