1
0
mirror of https://github.com/videojs/video.js.git synced 2025-02-04 11:43:27 +02:00

Created activateControl method for applying behaviors

Added rEvtContext for creating removable event listeners with context.
Switch to top/left form holygrail for load bar positioning.
Switched to relative/absolute for contorlsBelow instead of JS calculating heights.
Started testing maps integration.
This commit is contained in:
Steve Heffernan 2010-11-11 17:40:22 -08:00
parent 6a682af82c
commit 31f6fcdbd6
5 changed files with 380 additions and 243 deletions

13
test/map/coords.srt Executable file
View File

@ -0,0 +1,13 @@
1
00:00:02,400 --> 00:00:05,200
41.586688,-100.112915
2
00:00:15,712 --> 00:00:17,399
46.920255,3.010254
3
00:00:25,712 --> 00:00:30,399
-36.208823,-67.192383

91
test/map/map.html Normal file
View File

@ -0,0 +1,91 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>HTML5 Video Player</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<!-- Include the VideoJS Library -->
<script src="../../video.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
VideoJS.DOMReady(function(){
var myPlayer = VideoJS.setup("example_video_1", { controlsHiding: false });
});
</script>
<script type="text/javascript"
src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<script type="text/javascript">
function initialize() {
var latlng = new google.maps.LatLng(-34.397, 150.644);
var myOptions = {
zoom: 8,
center: latlng,
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"),
myOptions);
setTimeout(function() { map.panTo(new google.maps.LatLng(37.4419, -122.1419), 13) }, 2000)
}
</script>
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0px; padding: 0px }
#map_canvas { height: 100%; position: absolute; top: 0; left: 0; }
.video-js-box { position: absolute; top: 50%; left: 50%; z-index: 1; margin: -132px 0 0 -320px; }
</style>
<style type="text/css" media="screen">
#attributes { width: 300px; float: left; }
#events { width: 300px; height: 500px; float: left; }
</style>
<!-- Include the VideoJS Stylesheet -->
<link rel="stylesheet" href="../../video-js.css" type="text/css" media="screen" title="Video JS">
<link rel="stylesheet" href="../skins/tube.css" type="text/css" media="screen" title="Video JS">
<link rel="stylesheet" href="../skins/hu.css" type="text/css" media="screen" title="Video JS">
<link rel="stylesheet" href="../skins/vim.css" type="text/css" media="screen" title="Video JS">
</head>
<body onload="initialize()">
<!-- Begin VideoJS -->
<div class="video-js-box">
<!-- Using the Video for Everybody Embed Code http://camendesign.com/code/video_for_everybody -->
<video data-subtitles="../demo-subtitles.srt" id="example_video_1" class="video-js" width="640" height="264" controls="controls" preload="auto" poster="http://video-js.zencoder.com/oceans-clip.png">
<source src="http://video-js.zencoder.com/oceans-clip.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"' />
<source src="http://video-js.zencoder.com/oceans-clip.webm" type='video/webm; codecs="vp8, vorbis"' />
<source src="http://video-js.zencoder.com/oceans-clip.ogv" type='video/ogg; codecs="theora, vorbis"' />
<!-- Flash Fallback. Use any flash video player here. Make sure to keep the vjs-flash-fallback class. -->
<object id="flash_fallback_1" class="vjs-flash-fallback" width="640" height="264" type="application/x-shockwave-flash"
data="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf">
<param name="movie" value="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf" />
<param name="allowfullscreen" value="true" />
<param name="flashvars" value='config={"playlist":["http://video-js.zencoder.com/oceans-clip.png", {"url": "http://video-js.zencoder.com/oceans-clip.mp4","autoPlay":false,"autoBuffering":true}]}' />
<!-- Image Fallback. Typically the same as the poster image. -->
<img src="http://video-js.zencoder.com/oceans-clip.png" width="640" height="264" alt="Poster Image"
title="No video playback capabilities." />
</object>
</video>
<!-- Download links provided for devices that can't play video in the browser. -->
<p class="vjs-no-video"><strong>Download Video:</strong>
<a href="http://video-js.zencoder.com/oceans-clip.mp4">MP4</a>,
<a href="http://video-js.zencoder.com/oceans-clip.webm">WebM</a>,
<a href="http://video-js.zencoder.com/oceans-clip.ogv">Ogg</a><br>
<!-- Support VideoJS by keeping this link. -->
<a href="http://videojs.com">HTML5 Video Player</a> by VideoJS
</p>
</div>
<!-- End VideoJS -->
<div id="map_canvas" style="width:100%; height:100%"></div>
</body>
</html>

View File

@ -4,13 +4,12 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>HTML5 Video Player</title> <title>HTML5 Video Player</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<!-- Include the VideoJS Library --> <!-- Include the VideoJS Library -->
<script src="../video.js" type="text/javascript" charset="utf-8"></script> <script src="../video.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript"> <script type="text/javascript">
VideoJS.DOMReady(function(){ VideoJS.DOMReady(function(){
var myPlayer = VideoJS.setup("example_video_1", { controlsHiding: false }); var myPlayer = VideoJS.setup("example_video_1", { controlsHiding: false, controlsBelow: true, showControlsAtStart: true });
myPlayer.activateControl(document.getElementById("scrub"), "loadProgressBar");
var vid = document.getElementById("example_video_1"), var vid = document.getElementById("example_video_1"),
attrTable = document.getElementById("attributes"), attrTable = document.getElementById("attributes"),
attrNames = ["error", "networkState", "readyState", "preload", "buffered", attrNames = ["error", "networkState", "readyState", "preload", "buffered",
@ -66,6 +65,7 @@
<body> <body>
<!-- Begin VideoJS --> <!-- Begin VideoJS -->
<div id="scrub" style="background: #000; height: 10px;"></div>
<div class="video-js-box"> <div class="video-js-box">
<!-- Using the Video for Everybody Embed Code http://camendesign.com/code/video_for_everybody --> <!-- Using the Video for Everybody Embed Code http://camendesign.com/code/video_for_everybody -->
<video data-subtitles="../demo-subtitles.srt" id="example_video_1" class="video-js" width="640" height="264" controls="controls" preload="auto" poster="http://video-js.zencoder.com/oceans-clip.png"> <video data-subtitles="../demo-subtitles.srt" id="example_video_1" class="video-js" width="640" height="264" controls="controls" preload="auto" poster="http://video-js.zencoder.com/oceans-clip.png">

View File

@ -10,7 +10,7 @@ REQUIRED STYLES (be careful overriding)
.video-js-box { text-align: left; position: relative; vertical-align: bottom; } /* Will be set to the width of the video element */ .video-js-box { text-align: left; position: relative; vertical-align: bottom; } /* Will be set to the width of the video element */
/* Video Element */ /* Video Element */
video.video-js { background-color: #000; position: relative; } video.video-js { background-color: #000; position: relative; margin: 0 0 -1px 0; /* Firefox gap */ padding: 0; }
/* Poster Style */ /* Poster Style */
.video-js-box img.vjs-poster { display: block; position: absolute; left: 0; top: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; } .video-js-box img.vjs-poster { display: block; position: absolute; left: 0; top: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
@ -19,7 +19,7 @@ video.video-js { background-color: #000; position: relative; }
/* Fullscreen styles for main elements */ /* Fullscreen styles for main elements */
.video-js-box.vjs-fullscreen { position: fixed; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: 1000; } .video-js-box.vjs-fullscreen { position: fixed; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: 1000; }
.video-js-box.vjs-fullscreen video.video-js { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000; } .video-js-box.vjs-fullscreen video.video-js { position: relative; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000; }
.video-js-box.vjs-fullscreen img.vjs-poster { z-index: 1001; } .video-js-box.vjs-fullscreen img.vjs-poster { z-index: 1001; }
.video-js-box.vjs-fullscreen .vjs-spinner { z-index: 1001; } .video-js-box.vjs-fullscreen .vjs-spinner { z-index: 1001; }
.video-js-box.vjs-fullscreen .vjs-controls { z-index: 1003; } .video-js-box.vjs-fullscreen .vjs-controls { z-index: 1003; }
@ -35,26 +35,24 @@ Using all CSS to draw the controls. Images could be used if desired.
Instead of editing this file, I recommend creating your own skin CSS file to be included after this file, Instead of editing this file, I recommend creating your own skin CSS file to be included after this file,
so you can upgrade to newer versions easier. */ so you can upgrade to newer versions easier. */
/* Controls Layout /* Controls Layout
Using a Holy Grail type method to allow the progress bar holder to expand into all available space, Using absolute positioning to position controls */
but using abosolute positioning for individual controls. http://www.alistapart.com/articles/holygrail */
.video-js-box .vjs-controls { .video-js-box .vjs-controls {
position: absolute; margin: 0; border: none; opacity: 0.85; color: #fff; position: absolute; margin: 0; border: none; opacity: 0.85; color: #fff;
display: none; /* Start hidden */ display: none; /* Start hidden */
left: 0; right: 0; /* 100% width of video-js-box */ left: 0; right: 0; /* 100% width of video-js-box */
bottom: 5px;
height: 35px; /* Including any margin you want above or below control items */ height: 35px; /* Including any margin you want above or below control items */
padding-left: 35px; /* Width of play button + margin */
padding-right: 165px; /* Width of all the controls to the right of the progress control + margins */
padding-top: 0; padding-bottom: 0; padding-top: 0; padding-bottom: 0;
} }
/* Controls styles when below the video */ /* Controls styles when below the video */
.video-js-box.vjs-controls-below .vjs-controls { background-color: #000; } .video-js-box.vjs-controls-below .vjs-controls { position: relative; opacity: 1; background-color: #000; }
.video-js-box .vjs-controls > div { /* Direct div children of control bar */ .video-js-box .vjs-controls > div { /* Direct div children of control bar */
position: absolute; float: left; padding: 0; text-align: center; position: absolute; padding: 0; text-align: center;
height: 25px; /* Default height of individual controls */ height: 25px; /* Default height of individual controls */
margin: 5px 0 0 0; /* Top margin to put space between video and controls when controls are below */ margin: 0; top: 5px; /* Top margin to put space between video and controls when controls are below */
/* CSS Background Gradients */ /* CSS Background Gradients */
/* Default */ background-color: #0B151A; /* Default */ background-color: #0B151A;
/* Webkit */ background: #1F3744 -webkit-gradient(linear, left top, left bottom, from(#0B151A), to(#1F3744)) left 12px; /* Webkit */ background: #1F3744 -webkit-gradient(linear, left top, left bottom, from(#0B151A), to(#1F3744)) left 12px;
@ -69,7 +67,7 @@ so you can upgrade to newer versions easier. */
/* Placement of Control Items */ /* Placement of Control Items */
.vjs-controls > div.vjs-play-control { width: 25px; left: 5px; } .vjs-controls > div.vjs-play-control { width: 25px; left: 5px; }
.vjs-controls > div.vjs-progress-control { width: 100%; position: relative; } .vjs-controls > div.vjs-progress-control { left: 35px; right: 165px; } /* Using left & right so it expands with the width of the video */
.vjs-controls > div.vjs-time-control { width: 75px; right: 90px; } /* Time control and progress bar are combined to look like one */ .vjs-controls > div.vjs-time-control { width: 75px; right: 90px; } /* Time control and progress bar are combined to look like one */
.vjs-controls > div.vjs-volume-control { width: 50px; right: 35px; } .vjs-controls > div.vjs-volume-control { width: 50px; right: 35px; }
.vjs-controls > div.vjs-fullscreen-control { width: 25px; right: 5px; } .vjs-controls > div.vjs-fullscreen-control { width: 25px; right: 5px; }

491
video.js
View File

@ -21,8 +21,12 @@ along with VideoJS. If not, see <http://www.gnu.org/licenses/>.
// Using jresig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/ // Using jresig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/
(function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; this.JRClass = function(){}; JRClass.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } function JRClass() { if ( !initializing && this.init ) this.init.apply(this, arguments); } JRClass.prototype = prototype; JRClass.constructor = JRClass; JRClass.extend = arguments.callee; return JRClass;};})(); (function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; this.JRClass = function(){}; JRClass.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } function JRClass() { if ( !initializing && this.init ) this.init.apply(this, arguments); } JRClass.prototype = prototype; JRClass.constructor = JRClass; JRClass.extend = arguments.callee; return JRClass;};})();
// Self-executing function to prevent global vars and help with minification
(function(window, undefined){
var document = window.document;
// Video JS Player Class // Video JS Player Class
var VideoJS = JRClass.extend({ var VideoJS = _V_ = JRClass.extend({
// Initialize the player for the supplied video tag element // Initialize the player for the supplied video tag element
// element: video tag // element: video tag
@ -73,7 +77,8 @@ var VideoJS = JRClass.extend({
}, },
html5Init: function(){ html5Init: function(){
this.fixPreloading(); this.fixPreloading(); // Support old browsers that used autobuffer
this.percentLoaded = 0; // Store amount of video loaded
if (VideoJS.isIOS()) { if (VideoJS.isIOS()) {
this.options.useBuiltInControls = true; this.options.useBuiltInControls = true;
@ -90,7 +95,6 @@ var VideoJS = JRClass.extend({
this.video.controls = false; this.video.controls = false;
if (this.options.controlsBelow) { _V_.addClass(this.box, "vjs-controls-below"); } if (this.options.controlsBelow) { _V_.addClass(this.box, "vjs-controls-below"); }
this.percentLoaded = 0; // Store amount of video loaded
// Build Interface // Build Interface
this.buildStylesCheckDiv(); // Used to check if style are loaded this.buildStylesCheckDiv(); // Used to check if style are loaded
@ -107,12 +111,6 @@ var VideoJS = JRClass.extend({
// They are also temporary, which means they need to be removed. // They are also temporary, which means they need to be removed.
// They also need context (this) so they can call functions on their specific player. // They also need context (this) so they can call functions on their specific player.
// Adding context on initialization allows us to store a reference to them and remove them later. // Adding context on initialization allows us to store a reference to them and remove them later.
this.onEscKey = this.onEscKey.context(this);
this.onWindowResize = this.onWindowResize.context(this);
this.onProgressMouseMove = this.onProgressMouseMove.context(this);
this.onProgressMouseUp = this.onProgressMouseUp.context(this);
this.onVolumeMouseMove = this.onVolumeMouseMove.context(this);
this.onVolumeMouseUp = this.onVolumeMouseUp.context(this);
} }
}, },
@ -164,7 +162,7 @@ var VideoJS = JRClass.extend({
// Don't want to create an endless loop either. // Don't want to create an endless loop either.
if (!this.positionRetries) { this.positionRetries = 1; } if (!this.positionRetries) { this.positionRetries = 1; }
if (this.positionRetries++ < 100) { if (this.positionRetries++ < 100) {
setTimeout(this.loadInterface.context(this),0); setTimeout(this.loadInterface.context(this),10);
return; return;
} }
} }
@ -194,7 +192,7 @@ var VideoJS = JRClass.extend({
this.box.style.width = this.video.offsetWidth + "px"; this.box.style.width = this.video.offsetWidth + "px";
if (this.options.controlsBelow) { if (this.options.controlsBelow) {
this.video.style.height = ""; this.video.style.height = "";
this.box.style.height = this.video.offsetHeight + this.controls.offsetHeight + "px"; // this.box.style.height = this.video.offsetHeight + this.controls.offsetHeight + "px";
} }
} }
}, },
@ -287,6 +285,7 @@ var VideoJS = JRClass.extend({
this.activateControls(); this.activateControls();
}, },
// Set up Event Listeners // Set up Event Listeners
activateControls: function(){ activateControls: function(){
/* Activate Errors /* Activate Errors
@ -302,16 +301,14 @@ var VideoJS = JRClass.extend({
// Listen for when the video ends // Listen for when the video ends
this.video.addEventListener("ended", this.onEnded.context(this), false); this.video.addEventListener("ended", this.onEnded.context(this), false);
// Listen for clicks on the play/pause button // Listen for clicks on the play/pause button
this.activateAsPlayPauseButton(this.playControl); this.activateControl(this.playControl, "playToggle");
// Make a click on the video act like a click on the play button. // Make a click on the video act like a click on the play button.
this.activateAsPlayPauseButton(this.video); this.activateControl(this.video, "playToggle");
/* Activate Play Progress /* Activate Play Progress
================================================================================ */ ================================================================================ */
// Listen for drags on the progress bar // Listen for drags on the progress bar
this.progressHolder.addEventListener("mousedown", this.onProgressHolderMouseDown.context(this), false); this.activateControl(this.progressHolder, "timelineScrubber");
// Listen for a release on the progress bar
this.progressHolder.addEventListener("mouseup", this.onProgressHolderMouseUp.context(this), false);
/* Activate Buffering Progress /* Activate Buffering Progress
================================================================================ */ ================================================================================ */
@ -319,6 +316,8 @@ var VideoJS = JRClass.extend({
this.video.addEventListener('progress', this.onProgress.context(this), false); this.video.addEventListener('progress', this.onProgress.context(this), false);
// Set interval for load progress using buffer watching method // Set interval for load progress using buffer watching method
this.watchBuffer = setInterval(this.updateBufferedTotal.context(this), 33); this.watchBuffer = setInterval(this.updateBufferedTotal.context(this), 33);
this.activateControl(this.loadProgress, "loadProgressBar");
/* Activate Volume /* Activate Volume
================================================================================ */ ================================================================================ */
@ -328,15 +327,12 @@ var VideoJS = JRClass.extend({
this.updateVolumeDisplay(); this.updateVolumeDisplay();
// Listen for a volume change // Listen for a volume change
this.video.addEventListener('volumechange',this.onVolumeChange.context(this),false); this.video.addEventListener('volumechange',this.onVolumeChange.context(this),false);
// Listen for a drag on the volume control this.activateControl(this.volumeControl, "volumeScrubber");
this.volumeControl.addEventListener("mousedown", this.onVolumeControlMouseDown.context(this), false);
// Listen for a release on the volume control
this.volumeControl.addEventListener("mouseup", this.onVolumeControlMouseUp.context(this), false);
/* Activate Fullscreen /* Activate Fullscreen
================================================================================ */ ================================================================================ */
// Listen for clicks on the button // Listen for clicks on the button
this.fullscreenControl.addEventListener("click", this.onFullscreenControlClick.context(this), false); this.activateControl(this.fullscreenControl, "fullscreenToggle")
/* Activate Controls Movement /* Activate Controls Movement
================================================================================ */ ================================================================================ */
@ -359,11 +355,11 @@ var VideoJS = JRClass.extend({
// Make sure the controls are visible // Make sure the controls are visible
if (this.controls.style.display == 'none') { return; } if (this.controls.style.display == 'none') { return; }
if (this.options.controlsBelow) { // if (this.options.controlsBelow) {
this.controls.style.top = this.video.offsetHeight + "px"; // this.controls.style.top = this.video.offsetHeight + "px";
} else { // } else {
this.controls.style.top = (this.video.offsetHeight - this.controls.offsetHeight) + "px"; // this.controls.style.top = (this.video.offsetHeight - this.controls.offsetHeight) + "px";
} // }
this.updatePlayProgress(); this.updatePlayProgress();
this.updateLoadProgress(); this.updateLoadProgress();
}, },
@ -382,7 +378,7 @@ var VideoJS = JRClass.extend({
onVideoMouseMove: function(){ onVideoMouseMove: function(){
this.showControlBar(); this.showControlBar();
clearInterval(this.mouseMoveTimeout); clearInterval(this.mouseMoveTimeout);
this.mouseMoveTimeout = setTimeout(function(){ this.hideControlBar(); }.context(this), 4000); this.mouseMoveTimeout = setTimeout(this.hideControlBar.context(this), 4000);
}, },
onVideoMouseOut: function(event){ onVideoMouseOut: function(event){
// Prevent flicker by making sure mouse hasn't left the video // Prevent flicker by making sure mouse hasn't left the video
@ -407,11 +403,34 @@ var VideoJS = JRClass.extend({
/* Play/Pause /* Play/Pause
================================================================================ */ ================================================================================ */
activateAsPlayPauseButton: function(element){ behaviors: {
element.addEventListener("click", this.onPlayControlClick.context(this), false); playToggle: function(element){
element.addEventListener("click", this.onPlayControlClick.context(this), false);
},
playButton: function(element){
element.addEventListener("click", this.onPlayButtonClick.context(this), false);
},
pauseButton: function(element){
element.addEventListener("click", this.onPauseButtonClick.context(this), false);
},
timelineScrubber: function(element){
var player = this;
element.addEventListener("mousedown", this.onTimelineScrubberMouseDown.rEvtContext(this), false);
},
volumeScrubber: function(element){
element.addEventListener("mousedown", this.onVolumeScrubberMouseDown.rEvtContext(this), false);
},
fullscreenToggle: function(element){
element.addEventListener("click", this.onFullscreenToggleClick.context(this), false);
},
loadProgressBar: function(element){
if (!this.loadProgressBars) { this.loadProgressBars = []; }
this.loadProgressBars.push(element);
}
}, },
activateAsPlayButton: function(element){
element.addEventListener("click", this.onPlayButtonClick.context(this), false); activateControl: function(element, behavior){
this.behaviors[behavior].call(this, element);
}, },
// React to clicks on the play/pause button // React to clicks on the play/pause button
@ -448,7 +467,7 @@ var VideoJS = JRClass.extend({
// Track & display the current play progress // Track & display the current play progress
trackPlayProgress: function(){ trackPlayProgress: function(){
if(this.playProgressInterval) { clearInterval(this.playProgressInterval); } if(this.playProgressInterval) { clearInterval(this.playProgressInterval); }
this.playProgressInterval = setInterval(function(){ this.updatePlayProgress(); }.context(this), 33); this.playProgressInterval = setInterval(this.updatePlayProgress.context(this), 33);
}, },
// Turn off play progress tracking (when paused) // Turn off play progress tracking (when paused)
stopTrackingPlayProgress: function(){ clearInterval(this.playProgressInterval); }, stopTrackingPlayProgress: function(){ clearInterval(this.playProgressInterval); },
@ -469,50 +488,37 @@ var VideoJS = JRClass.extend({
// currentTime changed, reset subtitles // currentTime changed, reset subtitles
if (!this.subtitles) { this.currentSubtitlePosition = 0; } if (!this.subtitles) { this.currentSubtitlePosition = 0; }
}, },
setPlayProgressWithEvent: function(event){ setPlayProgressWithScrubber: function(event){
var newProgress = _V_.getRelativePosition(event.pageX, this.progressHolder); var newProgress = _V_.getRelativePosition(event.pageX, this.currentScrubber);
this.setPlayProgress(newProgress); this.setPlayProgress(newProgress);
}, },
// Adjust the play position when the user drags on the progress bar // Adjust the play position when the user drags on the progress bar
onProgressHolderMouseDown: function(event){ onTimelineScrubberMouseDown: function(event, scrubber){
this.stopTrackingPlayProgress(); event.preventDefault();
this.currentScrubber = scrubber;
if (this.video.paused) { this.stopTrackingPlayProgress();
this.videoWasPlaying = false;
} else { this.videoWasPlaying = !this.video.paused;
this.videoWasPlaying = true; this.video.pause();
this.video.pause();
}
_V_.blockTextSelection(); _V_.blockTextSelection();
this.setPlayProgressWithEvent(event); this.setPlayProgressWithScrubber(event);
document.addEventListener("mousemove", this.onProgressMouseMove, false); document.addEventListener("mousemove", this.onTimelineScrubberMouseMove.rEvtContext(this), false);
document.addEventListener("mouseup", this.onProgressMouseUp, false); document.addEventListener("mouseup", this.onTimelineScrubberMouseUp.rEvtContext(this), false);
}, },
onProgressMouseMove: function(event){ // Removeable onTimelineScrubberMouseMove: function(event){ // Removeable
this.setPlayProgressWithEvent(event); this.setPlayProgressWithScrubber(event);
}, },
onProgressMouseUp: function(event){ // Removeable onTimelineScrubberMouseUp: function(event){ // Removeable
_V_.unblockTextSelection(); _V_.unblockTextSelection();
document.removeEventListener("mousemove", this.onProgressMouseMove, false); document.removeEventListener("mousemove", this.onTimelineScrubberMouseMove, false);
document.removeEventListener("mouseup", this.onProgressMouseUp, false); document.removeEventListener("mouseup", this.onTimelineScrubberMouseUp, false);
if (this.videoWasPlaying) { if (this.videoWasPlaying) {
this.video.play(); this.video.play();
this.trackPlayProgress(); this.trackPlayProgress();
} }
}, },
// When the user stops dragging on the progress bar, update play position
// Backup for when the user only clicks and doesn't drag
onProgressHolderMouseUp: function(event){
// Removed. Chrome breaks (shows poster, plays audio) if you set currentTime rapidly.
// this.setPlayProgressWithEvent(event);
// Fix for a play button state issue.
if (this.video.paused) {
this.onPause();
} else {
this.onPlay();
}
},
// Update the displayed time (00:00) // Update the displayed time (00:00)
updateTimeDisplay: function(){ updateTimeDisplay: function(){
@ -550,33 +556,34 @@ var VideoJS = JRClass.extend({
} }
}, },
updateLoadProgress: function(){ updateLoadProgress: function(){
if (this.controls.style.display == 'none') { return; } for (var i=0,bars=this.loadProgressBars,j=bars.length; i<j; i++) {
this.loadProgress.style.width = (this.percentLoaded * (_V_.getComputedStyleValue(this.progressHolder, "width").replace("px", ""))) + "px"; if (bars[i].style) { bars[i].style.width = parseInt(this.percentLoaded * 100) + "%"; }
}
// this.loadProgress.style.width = (this.percentLoaded * (_V_.getComputedStyleValue(this.progressHolder, "width").replace("px", ""))) + "px";
}, },
/* Volume /* Volume
================================================================================ */ ================================================================================ */
onVolumeChange: function(event){ this.updateVolumeDisplay(); }, onVolumeChange: function(event){ this.updateVolumeDisplay(); },
// Adjust the volume when the user drags on the volume control // Adjust the volume when the user drags on the volume control
onVolumeControlMouseDown: function(event){ onVolumeScrubberMouseDown: function(event, scrubber){
event.preventDefault();
this.currentScrubber = scrubber;
_V_.blockTextSelection(); _V_.blockTextSelection();
this.setVolumeWithEvent(event); this.setVolumeWithScrubber(event);
document.addEventListener("mousemove", this.onVolumeMouseMove, false); document.addEventListener("mousemove", this.onVolumeScrubberMouseMove.rEvtContext(this), false);
document.addEventListener("mouseup", this.onVolumeMouseUp, false); document.addEventListener("mouseup", this.onVolumeScrubberMouseUp.rEvtContext(this), false);
}, },
onVolumeMouseMove: function(event){ onVolumeScrubberMouseMove: function(event){ this.setVolumeWithScrubber(event); },
this.setVolumeWithEvent(event); onVolumeScrubberMouseUp: function(event){
},
onVolumeMouseUp: function(event){
_V_.unblockTextSelection(); _V_.unblockTextSelection();
document.removeEventListener("mousemove", this.onVolumeMouseMove, false); document.removeEventListener("mousemove", this.onVolumeScrubberMouseMove, false);
document.removeEventListener("mouseup", this.onVolumeMouseUp, false); document.removeEventListener("mouseup", this.onVolumeScrubberMouseUp, false);
this.setVolumeWithScrubber(event);
}, },
// When the user stops dragging, set a new volume // When the user stops dragging, set a new volume
// Backup for when the user only clicks and doesn't drag // Backup for when the user only clicks and doesn't drag
onVolumeControlMouseUp: function(event){ // onVolumeControlMouseUp: function(event){ this.setVolumeWithScrubber(event); },
this.setVolumeWithEvent(event);
},
// Set a new volume based on where the user clicked on the volume control // Set a new volume based on where the user clicked on the volume control
setVolume: function(newVol){ setVolume: function(newVol){
@ -584,8 +591,8 @@ var VideoJS = JRClass.extend({
this.setLocalStorage("volume", this.video.volume); this.setLocalStorage("volume", this.video.volume);
}, },
setVolumeWithEvent: function(event){ setVolumeWithScrubber: function(event){
var newVol = _V_.getRelativePosition(event.pageX, this.volumeControl.children[0]); var newVol = _V_.getRelativePosition(event.pageX, this.currentScrubber);
this.setVolume(newVol); this.setVolume(newVol);
}, },
@ -605,7 +612,7 @@ var VideoJS = JRClass.extend({
/* Fullscreen / Full-window /* Fullscreen / Full-window
================================================================================ */ ================================================================================ */
// When the user clicks on the fullscreen button, update fullscreen setting // When the user clicks on the fullscreen button, update fullscreen setting
onFullscreenControlClick: function(event){ onFullscreenToggleClick: function(event){
if (!this.videoIsFullScreen) { if (!this.videoIsFullScreen) {
this.fullscreenOn(); this.fullscreenOn();
} else { } else {
@ -620,9 +627,9 @@ var VideoJS = JRClass.extend({
// Storing original doc overflow value to return to when fullscreen is off // Storing original doc overflow value to return to when fullscreen is off
this.docOrigOverflow = document.documentElement.style.overflow; this.docOrigOverflow = document.documentElement.style.overflow;
// Add listener for esc key to exit fullscreen // Add listener for esc key to exit fullscreen
document.addEventListener("keydown", this.onEscKey, false); document.addEventListener("keydown", this.onEscKey.rEvtContext(this), false);
// Add listener for a window resize // Add listener for a window resize
window.addEventListener("resize", this.onWindowResize, false); window.addEventListener("resize", this.onWindowResize.rEvtContext(this), false);
// Hide any scroll bars // Hide any scroll bars
document.documentElement.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden';
// Apply fullscreen styles // Apply fullscreen styles
@ -684,7 +691,7 @@ var VideoJS = JRClass.extend({
this.video.parentNode.appendChild(this.bigPlayButton); this.video.parentNode.appendChild(this.bigPlayButton);
}, },
activateBigPlayButton: function(){ activateBigPlayButton: function(){
this.activateAsPlayPauseButton(this.bigPlayButton); this.activateControl(this.bigPlayButton, "playToggle");
this.video.addEventListener("play", this.bigPlayButtonOnPlay.context(this), false); this.video.addEventListener("play", this.bigPlayButtonOnPlay.context(this), false);
this.video.addEventListener("ended", this.bigPlayButtonOnEnded.context(this), false); this.video.addEventListener("ended", this.bigPlayButtonOnEnded.context(this), false);
}, },
@ -719,7 +726,7 @@ var VideoJS = JRClass.extend({
showSpinner: function(){ showSpinner: function(){
this.spinner.style.display = "block"; this.spinner.style.display = "block";
clearInterval(this.spinnerInterval); clearInterval(this.spinnerInterval);
this.spinnerInterval = setInterval(function(){ this.rotateSpinner(); }.context(this), 100); this.spinnerInterval = setInterval(this.rotateSpinner.context(this), 100);
}, },
hideSpinner: function(){ hideSpinner: function(){
this.spinner.style.display = "none"; this.spinner.style.display = "none";
@ -795,7 +802,7 @@ var VideoJS = JRClass.extend({
// Listen for the mouse moving out of the poster image. Used to hide the controller. // Listen for the mouse moving out of the poster image. Used to hide the controller.
this.poster.addEventListener("mouseout", this.onVideoMouseOut.context(this), false); this.poster.addEventListener("mouseout", this.onVideoMouseOut.context(this), false);
// Make a click on the poster act like a click on the play button. // Make a click on the poster act like a click on the play button.
this.activateAsPlayButton(this.poster); this.activateControl(this.poster, "playButton");
// Hide/Show poster on video events // Hide/Show poster on video events
this.video.addEventListener("play", this.posterOnPlay.context(this), false); this.video.addEventListener("play", this.posterOnPlay.context(this), false);
this.video.addEventListener("ended", this.posterOnEnded.context(this), false); this.video.addEventListener("ended", this.posterOnEnded.context(this), false);
@ -923,7 +930,6 @@ var VideoJS = JRClass.extend({
} }
}, },
/* Device Fixes /* Device Fixes
================================================================================ */ ================================================================================ */
// Support older browsers that used "autobuffer" // Support older browsers that used "autobuffer"
@ -1045,145 +1051,6 @@ var VideoJS = JRClass.extend({
} }
}); });
////////////////////////////////////////////////////////////////////////////////
// Convenience Functions (mini library)
// Functions not specific to video or VideoJS and could probably be replaced with a library like jQuery
////////////////////////////////////////////////////////////////////////////////
var _V_ = {
addClass: function(element, classToAdd){
if (element.className.split(/\s+/).lastIndexOf(classToAdd) == -1) { element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd; }
},
removeClass: function(element, classToRemove){
if (element.className.indexOf(classToRemove) == -1) { return; }
var classNames = element.className.split(/\s+/);
classNames.splice(classNames.lastIndexOf(classToRemove),1);
element.className = classNames.join(" ");
},
merge: function(obj1, obj2){
for(var attrname in obj2){
if (obj2.hasOwnProperty(attrname)) {
obj1[attrname]=obj2[attrname];
}
}
return obj1;
},
createElement: function(tagName, attributes){
return _V_.merge(document.createElement(tagName), attributes);
},
// Attempt to block the ability to select text while dragging controls
blockTextSelection: function(){
document.body.focus();
document.onselectstart = function () { return false; };
},
// Turn off text selection blocking
unblockTextSelection: function(){
document.onselectstart = function () { return true; };
},
// Return seconds as MM:SS
formatTime: function(secs) {
var seconds = Math.round(secs);
var minutes = Math.floor(seconds / 60);
minutes = (minutes >= 10) ? minutes : "0" + minutes;
seconds = Math.floor(seconds % 60);
seconds = (seconds >= 10) ? seconds : "0" + seconds;
return minutes + ":" + seconds;
},
// Return the relative horizonal position of an event as a value from 0-1
getRelativePosition: function(x, relativeElement){
return Math.max(0, Math.min(1, (x - _V_.findPosX(relativeElement)) / relativeElement.offsetWidth));
},
// Get an objects position on the page
findPosX: function(obj) {
var curleft = obj.offsetLeft;
while(obj = obj.offsetParent) {
curleft += obj.offsetLeft;
}
return curleft;
},
getComputedStyleValue: function(element, style){
return window.getComputedStyle(element, null).getPropertyValue(style);
},
get: function(url, onSuccess){
if (typeof XMLHttpRequest == "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (f) {}
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (g) {}
//Microsoft.XMLHTTP points to Msxml2.XMLHTTP.3.0 and is redundant
throw new Error("This browser does not support XMLHttpRequest.");
};
}
var request = new XMLHttpRequest();
request.open("GET",url);
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
onSuccess(request.responseText);
}
}.context(this);
request.send();
},
// DOM Ready functionality adapted from jQuery. http://jquery.com/
bindDOMReady: function(){
if (document.readyState === "complete") {
return _V_.DOMReady();
}
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", _V_.DOMContentLoaded, false);
window.addEventListener("load", _V_.DOMReady, false);
} else if (document.attachEvent) {
document.attachEvent("onreadystatechange", _V_.DOMContentLoaded);
window.attachEvent("onload", _V_.DOMReady);
}
},
DOMContentLoaded: function(){
if (document.addEventListener) {
document.removeEventListener( "DOMContentLoaded", _V_.DOMContentLoaded, false);
_V_.DOMReady();
} else if ( document.attachEvent ) {
if ( document.readyState === "complete" ) {
document.detachEvent("onreadystatechange", _V_.DOMContentLoaded);
_V_.DOMReady();
}
}
},
// Functions to be run once the DOM is loaded
DOMReadyList: [],
addToDOMReady: function(fn){
if (_V_.DOMIsReady) {
fn.call(document);
} else {
_V_.DOMReadyList.push(fn);
}
},
DOMIsReady: false,
DOMReady: function(){
if (_V_.DOMIsReady) { return; }
if (!document.body) { return setTimeout(_V_.DOMReady, 13); }
_V_.DOMIsReady = true;
if (_V_.DOMReadyList) {
for (var i=0; i<_V_.DOMReadyList.length; i++) {
_V_.DOMReadyList[i].call(document);
}
_V_.DOMReadyList = null;
}
}
};
_V_.bindDOMReady();
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Class Methods // Class Methods
// Functions that don't apply to individual videos. // Functions that don't apply to individual videos.
@ -1230,7 +1097,7 @@ VideoJS.setupAllWhenReady = function(options){
// Run the supplied function when the DOM is ready // Run the supplied function when the DOM is ready
VideoJS.DOMReady = function(fn){ VideoJS.DOMReady = function(fn){
_V_.addToDOMReady(fn); VideoJS.addToDOMReady(fn);
}; };
// Set up a specific video or array of video elements // Set up a specific video or array of video elements
@ -1333,16 +1200,179 @@ VideoJS.warnings = {
localStorageFull: "Local Storage is Full" localStorageFull: "Local Storage is Full"
}; };
// Combine Objects
// Use "safe" to protect from overwriting existing items
VideoJS.merge = function(obj1, obj2, safe){
for(var attrname in obj2){
if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) { obj1[attrname]=obj2[attrname]; }
}
return obj1;
};
VideoJS.extend = function(obj){ this.merge(this, obj, true); }
////////////////////////////////////////////////////////////////////////////////
// Convenience Functions (mini library)
// Functions not specific to video or VideoJS and could probably be replaced with a library like jQuery
////////////////////////////////////////////////////////////////////////////////
VideoJS.extend({
addClass: function(element, classToAdd){
if (element.className.split(/\s+/).lastIndexOf(classToAdd) == -1) { element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd; }
},
removeClass: function(element, classToRemove){
if (element.className.indexOf(classToRemove) == -1) { return; }
var classNames = element.className.split(/\s+/);
classNames.splice(classNames.lastIndexOf(classToRemove),1);
element.className = classNames.join(" ");
},
createElement: function(tagName, attributes){
return this.merge(document.createElement(tagName), attributes);
},
// Attempt to block the ability to select text while dragging controls
blockTextSelection: function(){
document.body.focus();
document.onselectstart = function () { return false; };
},
// Turn off text selection blocking
unblockTextSelection: function(){ document.onselectstart = function () { return true; }; },
// Return seconds as MM:SS
formatTime: function(secs) {
var seconds = Math.round(secs);
var minutes = Math.floor(seconds / 60);
minutes = (minutes >= 10) ? minutes : "0" + minutes;
seconds = Math.floor(seconds % 60);
seconds = (seconds >= 10) ? seconds : "0" + seconds;
return minutes + ":" + seconds;
},
// Return the relative horizonal position of an event as a value from 0-1
getRelativePosition: function(x, relativeElement){
return Math.max(0, Math.min(1, (x - this.findPosX(relativeElement)) / relativeElement.offsetWidth));
},
// Get an objects position on the page
findPosX: function(obj) {
var curleft = obj.offsetLeft;
while(obj = obj.offsetParent) {
curleft += obj.offsetLeft;
}
return curleft;
},
getComputedStyleValue: function(element, style){
return window.getComputedStyle(element, null).getPropertyValue(style);
},
get: function(url, onSuccess){
if (typeof XMLHttpRequest == "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (f) {}
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (g) {}
//Microsoft.XMLHTTP points to Msxml2.XMLHTTP.3.0 and is redundant
throw new Error("This browser does not support XMLHttpRequest.");
};
}
var request = new XMLHttpRequest();
request.open("GET",url);
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
onSuccess(request.responseText);
}
}.context(this);
request.send();
},
// DOM Ready functionality adapted from jQuery. http://jquery.com/
bindDOMReady: function(){
if (document.readyState === "complete") {
return VideoJS.onDOMReady();
}
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", VideoJS.DOMContentLoaded, false);
window.addEventListener("load", VideoJS.onDOMReady, false);
} else if (document.attachEvent) {
document.attachEvent("onreadystatechange", VideoJS.DOMContentLoaded);
window.attachEvent("onload", VideoJS.onDOMReady);
}
},
DOMContentLoaded: function(){
if (document.addEventListener) {
document.removeEventListener( "DOMContentLoaded", VideoJS.DOMContentLoaded, false);
VideoJS.onDOMReady();
} else if ( document.attachEvent ) {
if ( document.readyState === "complete" ) {
document.detachEvent("onreadystatechange", VideoJS.DOMContentLoaded);
VideoJS.onDOMReady();
}
}
},
// Functions to be run once the DOM is loaded
DOMReadyList: [],
addToDOMReady: function(fn){
if (VideoJS.DOMIsReady) {
fn.call(document);
} else {
VideoJS.DOMReadyList.push(fn);
}
},
DOMIsReady: false,
onDOMReady: function(){
if (VideoJS.DOMIsReady) { return; }
if (!document.body) { return setTimeout(VideoJS.onDOMReady, 13); }
VideoJS.DOMIsReady = true;
if (VideoJS.DOMReadyList) {
for (var i=0; i<VideoJS.DOMReadyList.length; i++) {
VideoJS.DOMReadyList[i].call(document);
}
VideoJS.DOMReadyList = null;
}
}
});
VideoJS.bindDOMReady();
// Allows for binding context to functions // Allows for binding context to functions
// when using in event listeners and timeouts // when using in event listeners and timeouts
Function.prototype.context = function(obj){ Function.prototype.context = function(obj){
var method = this, temp; var method = this,
temp = function(){ temp = function(){
return method.apply(obj, arguments); return method.apply(obj, arguments);
}; };
return temp; return temp;
}; };
// Like context, in that it creates a closure
// But insteaad keep "this" intact, and passes the var as the second argument of the function
// Need for event listeners where you need to know what called the event
Function.prototype.evtContext = function(obj){
var method = this,
temp = function(){
var origContext = this;
return method.call(obj, arguments[0], origContext);
};
return temp;
};
// Removeable Event listener with Context
// Replaces the original function with a version that has context
// So it can be removed using the original function name.
// I have a feeling this one is gonna bite me in the butt some day
Function.prototype.rEvtContext = function(obj, funcParent){
if (this.hasContext == true) { return this; }
if (!funcParent) { funcParent = obj; }
for (var attrname in funcParent) {
if (funcParent[attrname] == this) {
funcParent[attrname] = this.evtContext(obj);
funcParent[attrname].hasContext = true;
return funcParent[attrname];
}
}
// Log function not found on object
};
// Shim to make Video tag valid in IE // Shim to make Video tag valid in IE
if(VideoJS.isIE()) { document.createElement("video"); } if(VideoJS.isIE()) { document.createElement("video"); }
@ -1361,4 +1391,9 @@ if (window.jQuery) {
})(jQuery); })(jQuery);
} }
// Expose to global
VideoJS.player = VideoJS.prototype;
return (window.VideoJS = window._V_ = VideoJS);
// End self-executing function
})(window);