root / okeanos_site / static / okeanos_static / video / video.js @ 4beadc2f
History | View | Annotate | Download (66.2 kB)
1 |
/*
|
---|---|
2 |
VideoJS - HTML5 Video Player
|
3 |
v2.0.2
|
4 |
|
5 |
This file is part of VideoJS. Copyright 2010 Zencoder, Inc.
|
6 |
|
7 |
VideoJS is free software: you can redistribute it and/or modify
|
8 |
it under the terms of the GNU Lesser General Public License as published by
|
9 |
the Free Software Foundation, either version 3 of the License, or
|
10 |
(at your option) any later version.
|
11 |
|
12 |
VideoJS is distributed in the hope that it will be useful,
|
13 |
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15 |
GNU Lesser General Public License for more details.
|
16 |
|
17 |
You should have received a copy of the GNU Lesser General Public License
|
18 |
along with VideoJS. If not, see <http://www.gnu.org/licenses/>.
|
19 |
*/
|
20 |
|
21 |
// Self-executing function to prevent global vars and help with minification
|
22 |
(function(window, undefined){ |
23 |
var document = window.document;
|
24 |
|
25 |
// Using jresig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/
|
26 |
(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;};})(); |
27 |
|
28 |
// Video JS Player Class
|
29 |
var VideoJS = JRClass.extend({
|
30 |
|
31 |
// Initialize the player for the supplied video tag element
|
32 |
// element: video tag
|
33 |
init: function(element, setOptions){ |
34 |
|
35 |
// Allow an ID string or an element
|
36 |
if (typeof element == 'string') { |
37 |
this.video = document.getElementById(element);
|
38 |
} else {
|
39 |
this.video = element;
|
40 |
} |
41 |
// Store reference to player on the video element.
|
42 |
// So you can acess the player later: document.getElementById("video_id").player.play();
|
43 |
this.video.player = this; |
44 |
this.values = {}; // Cache video values. |
45 |
this.elements = {}; // Store refs to controls elements. |
46 |
|
47 |
// Default Options
|
48 |
this.options = {
|
49 |
autoplay: false, |
50 |
preload: true, |
51 |
useBuiltInControls: false, // Use the browser's controls (iPhone) |
52 |
controlsBelow: false, // Display control bar below video vs. in front of |
53 |
controlsAtStart: false, // Make controls visible when page loads |
54 |
controlsHiding: true, // Hide controls when not over the video |
55 |
defaultVolume: 0.85, // Will be overridden by localStorage volume if available |
56 |
playerFallbackOrder: ["html5", "flash", "links"], // Players and order to use them |
57 |
flashPlayer: "htmlObject", |
58 |
flashPlayerVersion: false // Required flash version for fallback |
59 |
}; |
60 |
// Override default options with global options
|
61 |
if (typeof VideoJS.options == "object") { _V_.merge(this.options, VideoJS.options); } |
62 |
// Override default & global options with options specific to this player
|
63 |
if (typeof setOptions == "object") { _V_.merge(this.options, setOptions); } |
64 |
// Override preload & autoplay with video attributes
|
65 |
if (this.getPreloadAttribute() !== undefined) { this.options.preload = this.getPreloadAttribute(); } |
66 |
if (this.getAutoplayAttribute() !== undefined) { this.options.autoplay = this.getAutoplayAttribute(); } |
67 |
|
68 |
// Store reference to embed code pieces
|
69 |
this.box = this.video.parentNode; |
70 |
this.linksFallback = this.getLinksFallback(); |
71 |
this.hideLinksFallback(); // Will be shown again if "links" player is used |
72 |
|
73 |
// Loop through the player names list in options, "html5" etc.
|
74 |
// For each player name, initialize the player with that name under VideoJS.players
|
75 |
// If the player successfully initializes, we're done
|
76 |
// If not, try the next player in the list
|
77 |
this.each(this.options.playerFallbackOrder, function(playerType){ |
78 |
if (this[playerType+"Supported"]()) { // Check if player type is supported |
79 |
this[playerType+"Init"](); // Initialize player type |
80 |
return true; // Stop looping though players |
81 |
} |
82 |
}); |
83 |
|
84 |
// Start Global Listeners - API doesn't exist before now
|
85 |
this.activateElement(this, "player"); |
86 |
this.activateElement(this.box, "box"); |
87 |
}, |
88 |
/* Behaviors
|
89 |
================================================================================ */
|
90 |
behaviors: {},
|
91 |
newBehavior: function(name, activate, functions){ |
92 |
this.behaviors[name] = activate;
|
93 |
this.extend(functions);
|
94 |
}, |
95 |
activateElement: function(element, behavior){ |
96 |
// Allow passing and ID string
|
97 |
if (typeof element == "string") { element = document.getElementById(element); } |
98 |
this.behaviors[behavior].call(this, element); |
99 |
}, |
100 |
/* Errors/Warnings
|
101 |
================================================================================ */
|
102 |
errors: [], // Array to track errors |
103 |
warnings: [],
|
104 |
warning: function(warning){ |
105 |
this.warnings.push(warning);
|
106 |
this.log(warning);
|
107 |
}, |
108 |
/* History of errors/events (not quite there yet)
|
109 |
================================================================================ */
|
110 |
history: [],
|
111 |
log: function(event){ |
112 |
if (!event) { return; } |
113 |
if (typeof event == "string") { event = { type: event }; } |
114 |
if (event.type) { this.history.push(event.type); } |
115 |
if (this.history.length >= 50) { this.history.shift(); } |
116 |
try { console.log(event.type); } catch(e) { try { opera.postError(event.type); } catch(e){} } |
117 |
}, |
118 |
/* Local Storage
|
119 |
================================================================================ */
|
120 |
setLocalStorage: function(key, value){ |
121 |
if (!localStorage) { return; } |
122 |
try {
|
123 |
localStorage[key] = value; |
124 |
} catch(e) {
|
125 |
if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014 |
126 |
this.warning(VideoJS.warnings.localStorageFull);
|
127 |
} |
128 |
} |
129 |
}, |
130 |
/* Helpers
|
131 |
================================================================================ */
|
132 |
getPreloadAttribute: function(){ |
133 |
if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("preload")) { |
134 |
var preload = this.video.getAttribute("preload"); |
135 |
// Only included the attribute, thinking it was boolean
|
136 |
if (preload === "" || preload === "true") { return "auto"; } |
137 |
if (preload === "false") { return "none"; } |
138 |
return preload;
|
139 |
} |
140 |
}, |
141 |
getAutoplayAttribute: function(){ |
142 |
if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("autoplay")) { |
143 |
var autoplay = this.video.getAttribute("autoplay"); |
144 |
if (autoplay === "false") { return false; } |
145 |
return true; |
146 |
} |
147 |
}, |
148 |
// Calculates amoutn of buffer is full
|
149 |
bufferedPercent: function(){ return (this.duration()) ? this.buffered()[1] / this.duration() : 0; }, |
150 |
// Each that maintains player as context
|
151 |
// Break if true is returned
|
152 |
each: function(arr, fn){ |
153 |
if (!arr || arr.length === 0) { return; } |
154 |
for (var i=0,j=arr.length; i<j; i++) { |
155 |
if (fn.call(this, arr[i], i)) { break; } |
156 |
} |
157 |
}, |
158 |
extend: function(obj){ |
159 |
for (var attrname in obj) { |
160 |
if (obj.hasOwnProperty(attrname)) { this[attrname]=obj[attrname]; } |
161 |
} |
162 |
} |
163 |
}); |
164 |
VideoJS.player = VideoJS.prototype; |
165 |
|
166 |
////////////////////////////////////////////////////////////////////////////////
|
167 |
// Player Types
|
168 |
////////////////////////////////////////////////////////////////////////////////
|
169 |
|
170 |
/* Flash Object Fallback (Player Type)
|
171 |
================================================================================ */
|
172 |
VideoJS.player.extend({ |
173 |
flashSupported: function(){ |
174 |
if (!this.flashElement) { this.flashElement = this.getFlashElement(); } |
175 |
// Check if object exists & Flash Player version is supported
|
176 |
if (this.flashElement && this.flashPlayerVersionSupported()) { |
177 |
return true; |
178 |
} else {
|
179 |
return false; |
180 |
} |
181 |
}, |
182 |
flashInit: function(){ |
183 |
this.replaceWithFlash();
|
184 |
this.element = this.flashElement; |
185 |
this.video.src = ""; // Stop video from downloading if HTML5 is still supported |
186 |
var flashPlayerType = VideoJS.flashPlayers[this.options.flashPlayer]; |
187 |
this.extend(VideoJS.flashPlayers[this.options.flashPlayer].api); |
188 |
(flashPlayerType.init.context(this))();
|
189 |
}, |
190 |
// Get Flash Fallback object element from Embed Code
|
191 |
getFlashElement: function(){ |
192 |
var children = this.video.children; |
193 |
for (var i=0,j=children.length; i<j; i++) { |
194 |
if (children[i].className == "vjs-flash-fallback") { |
195 |
return children[i];
|
196 |
} |
197 |
} |
198 |
}, |
199 |
// Used to force a browser to fall back when it's an HTML5 browser but there's no supported sources
|
200 |
replaceWithFlash: function(){ |
201 |
// this.flashElement = this.video.removeChild(this.flashElement);
|
202 |
if (this.flashElement) { |
203 |
this.box.insertBefore(this.flashElement, this.video); |
204 |
this.video.style.display = "none"; // Removing it was breaking later players |
205 |
} |
206 |
}, |
207 |
// Check if browser can use this flash player
|
208 |
flashPlayerVersionSupported: function(){ |
209 |
var playerVersion = (this.options.flashPlayerVersion) ? this.options.flashPlayerVersion : VideoJS.flashPlayers[this.options.flashPlayer].flashPlayerVersion; |
210 |
return VideoJS.getFlashVersion() >= playerVersion;
|
211 |
} |
212 |
}); |
213 |
VideoJS.flashPlayers = {}; |
214 |
VideoJS.flashPlayers.htmlObject = { |
215 |
flashPlayerVersion: 9, |
216 |
init: function() { return true; }, |
217 |
api: { // No video API available with HTML Object embed method |
218 |
width: function(width){ |
219 |
if (width !== undefined) { |
220 |
this.element.width = width;
|
221 |
this.box.style.width = width+"px"; |
222 |
this.triggerResizeListeners();
|
223 |
return this; |
224 |
} |
225 |
return this.element.width; |
226 |
}, |
227 |
height: function(height){ |
228 |
if (height !== undefined) { |
229 |
this.element.height = height;
|
230 |
this.box.style.height = height+"px"; |
231 |
this.triggerResizeListeners();
|
232 |
return this; |
233 |
} |
234 |
return this.element.height; |
235 |
} |
236 |
} |
237 |
}; |
238 |
|
239 |
|
240 |
/* Download Links Fallback (Player Type)
|
241 |
================================================================================ */
|
242 |
VideoJS.player.extend({ |
243 |
linksSupported: function(){ return true; }, |
244 |
linksInit: function(){ |
245 |
this.showLinksFallback();
|
246 |
this.element = this.video; |
247 |
}, |
248 |
// Get the download links block element
|
249 |
getLinksFallback: function(){ return this.box.getElementsByTagName("P")[0]; }, |
250 |
// Hide no-video download paragraph
|
251 |
hideLinksFallback: function(){ |
252 |
if (this.linksFallback) { this.linksFallback.style.display = "none"; } |
253 |
}, |
254 |
// Hide no-video download paragraph
|
255 |
showLinksFallback: function(){ |
256 |
if (this.linksFallback) { this.linksFallback.style.display = "block"; } |
257 |
} |
258 |
}); |
259 |
|
260 |
////////////////////////////////////////////////////////////////////////////////
|
261 |
// Class Methods
|
262 |
// Functions that don't apply to individual videos.
|
263 |
////////////////////////////////////////////////////////////////////////////////
|
264 |
|
265 |
// Combine Objects - Use "safe" to protect from overwriting existing items
|
266 |
VideoJS.merge = function(obj1, obj2, safe){ |
267 |
for (var attrname in obj2){ |
268 |
if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) { obj1[attrname]=obj2[attrname]; }
|
269 |
} |
270 |
return obj1;
|
271 |
}; |
272 |
VideoJS.extend = function(obj){ this.merge(this, obj, true); }; |
273 |
|
274 |
VideoJS.extend({ |
275 |
// Add VideoJS to all video tags with the video-js class when the DOM is ready
|
276 |
setupAllWhenReady: function(options){ |
277 |
// Options is stored globally, and added ot any new player on init
|
278 |
VideoJS.options = options; |
279 |
VideoJS.DOMReady(VideoJS.setup); |
280 |
}, |
281 |
|
282 |
// Run the supplied function when the DOM is ready
|
283 |
DOMReady: function(fn){ |
284 |
VideoJS.addToDOMReady(fn); |
285 |
}, |
286 |
|
287 |
// Set up a specific video or array of video elements
|
288 |
// "video" can be:
|
289 |
// false, undefined, or "All": set up all videos with the video-js class
|
290 |
// A video tag ID or video tag element: set up one video and return one player
|
291 |
// An array of video tag elements/IDs: set up each and return an array of players
|
292 |
setup: function(videos, options){ |
293 |
var returnSingular = false, |
294 |
playerList = [], |
295 |
videoElement; |
296 |
|
297 |
// If videos is undefined or "All", set up all videos with the video-js class
|
298 |
if (!videos || videos == "All") { |
299 |
videos = VideoJS.getVideoJSTags(); |
300 |
// If videos is not an array, add to an array
|
301 |
} else if (typeof videos != 'object' || videos.nodeType == 1) { |
302 |
videos = [videos]; |
303 |
returnSingular = true;
|
304 |
} |
305 |
|
306 |
// Loop through videos and create players for them
|
307 |
for (var i=0; i<videos.length; i++) { |
308 |
if (typeof videos[i] == 'string') { |
309 |
videoElement = document.getElementById(videos[i]); |
310 |
} else { // assume DOM object |
311 |
videoElement = videos[i]; |
312 |
} |
313 |
playerList.push(new VideoJS(videoElement, options));
|
314 |
} |
315 |
|
316 |
// Return one or all depending on what was passed in
|
317 |
return (returnSingular) ? playerList[0] : playerList; |
318 |
}, |
319 |
|
320 |
// Find video tags with the video-js class
|
321 |
getVideoJSTags: function() { |
322 |
var videoTags = document.getElementsByTagName("video"), |
323 |
videoJSTags = [], videoTag; |
324 |
|
325 |
for (var i=0,j=videoTags.length; i<j; i++) { |
326 |
videoTag = videoTags[i]; |
327 |
if (videoTag.className.indexOf("video-js") != -1) { |
328 |
videoJSTags.push(videoTag); |
329 |
} |
330 |
} |
331 |
return videoJSTags;
|
332 |
}, |
333 |
|
334 |
// Check if the browser supports video.
|
335 |
browserSupportsVideo: function() { |
336 |
if (typeof VideoJS.videoSupport != "undefined") { return VideoJS.videoSupport; } |
337 |
VideoJS.videoSupport = !!document.createElement('video').canPlayType;
|
338 |
return VideoJS.videoSupport;
|
339 |
}, |
340 |
|
341 |
getFlashVersion: function(){ |
342 |
// Cache Version
|
343 |
if (typeof VideoJS.flashVersion != "undefined") { return VideoJS.flashVersion; } |
344 |
var version = 0, desc; |
345 |
if (typeof navigator.plugins != "undefined" && typeof navigator.plugins["Shockwave Flash"] == "object") { |
346 |
desc = navigator.plugins["Shockwave Flash"].description;
|
347 |
if (desc && !(typeof navigator.mimeTypes != "undefined" && navigator.mimeTypes["application/x-shockwave-flash"] && !navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin)) { |
348 |
version = parseInt(desc.match(/^.*\s+([^\s]+)\.[^\s]+\s+[^\s]+$/)[1], 10); |
349 |
} |
350 |
} else if (typeof window.ActiveXObject != "undefined") { |
351 |
try {
|
352 |
var testObject = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); |
353 |
if (testObject) {
|
354 |
version = parseInt(testObject.GetVariable("$version").match(/^[^\s]+\s(\d+)/)[1], 10); |
355 |
} |
356 |
} |
357 |
catch(e) {}
|
358 |
} |
359 |
VideoJS.flashVersion = version; |
360 |
return VideoJS.flashVersion;
|
361 |
}, |
362 |
|
363 |
// Browser & Device Checks
|
364 |
isIE: function(){ return !+"\v1"; }, |
365 |
isIPad: function(){ return navigator.userAgent.match(/iPad/i) !== null; }, |
366 |
isIPhone: function(){ return navigator.userAgent.match(/iPhone/i) !== null; }, |
367 |
isIOS: function(){ return VideoJS.isIPhone() || VideoJS.isIPad(); }, |
368 |
iOSVersion: function() { |
369 |
var match = navigator.userAgent.match(/OS (\d+)_/i); |
370 |
if (match && match[1]) { return match[1]; } |
371 |
}, |
372 |
isAndroid: function(){ return navigator.userAgent.match(/Android/i) !== null; }, |
373 |
androidVersion: function() { |
374 |
var match = navigator.userAgent.match(/Android (\d+)\./i); |
375 |
if (match && match[1]) { return match[1]; } |
376 |
}, |
377 |
|
378 |
warnings: {
|
379 |
// Safari errors if you call functions on a video that hasn't loaded yet
|
380 |
videoNotReady: "Video is not ready yet (try playing the video first).", |
381 |
// Getting a QUOTA_EXCEEDED_ERR when setting local storage occasionally
|
382 |
localStorageFull: "Local Storage is Full" |
383 |
} |
384 |
}); |
385 |
|
386 |
// Shim to make Video tag valid in IE
|
387 |
if(VideoJS.isIE()) { document.createElement("video"); } |
388 |
|
389 |
// Expose to global
|
390 |
window.VideoJS = window._V_ = VideoJS; |
391 |
|
392 |
/* HTML5 Player Type
|
393 |
================================================================================ */
|
394 |
VideoJS.player.extend({ |
395 |
html5Supported: function(){ |
396 |
if (VideoJS.browserSupportsVideo() && this.canPlaySource()) { |
397 |
return true; |
398 |
} else {
|
399 |
return false; |
400 |
} |
401 |
}, |
402 |
html5Init: function(){ |
403 |
this.element = this.video; |
404 |
|
405 |
this.fixPreloading(); // Support old browsers that used autobuffer |
406 |
this.supportProgressEvents(); // Support browsers that don't use 'buffered' |
407 |
|
408 |
// Set to stored volume OR 85%
|
409 |
this.volume((localStorage && localStorage.volume) || this.options.defaultVolume); |
410 |
|
411 |
// Update interface for device needs
|
412 |
if (VideoJS.isIOS()) {
|
413 |
this.options.useBuiltInControls = true; |
414 |
this.iOSInterface();
|
415 |
} else if (VideoJS.isAndroid()) { |
416 |
this.options.useBuiltInControls = true; |
417 |
this.androidInterface();
|
418 |
} |
419 |
|
420 |
// Add VideoJS Controls
|
421 |
if (!this.options.useBuiltInControls) { |
422 |
this.video.controls = false; |
423 |
|
424 |
if (this.options.controlsBelow) { _V_.addClass(this.box, "vjs-controls-below"); } |
425 |
|
426 |
// Make a click on th video act as a play button
|
427 |
this.activateElement(this.video, "playToggle"); |
428 |
|
429 |
// Build Interface
|
430 |
this.buildStylesCheckDiv(); // Used to check if style are loaded |
431 |
this.buildAndActivatePoster();
|
432 |
this.buildBigPlayButton();
|
433 |
this.buildAndActivateSpinner();
|
434 |
this.buildAndActivateControlBar();
|
435 |
this.loadInterface(); // Show everything once styles are loaded |
436 |
this.getSubtitles();
|
437 |
} |
438 |
}, |
439 |
/* Source Managemet
|
440 |
================================================================================ */
|
441 |
canPlaySource: function(){ |
442 |
// Cache Result
|
443 |
if (this.canPlaySourceResult) { return this.canPlaySourceResult; } |
444 |
// Loop through sources and check if any can play
|
445 |
var children = this.video.children; |
446 |
for (var i=0,j=children.length; i<j; i++) { |
447 |
if (children[i].tagName.toUpperCase() == "SOURCE") { |
448 |
var canPlay = this.video.canPlayType(children[i].type) || this.canPlayExt(children[i].src); |
449 |
if (canPlay == "probably" || canPlay == "maybe") { |
450 |
this.firstPlayableSource = children[i];
|
451 |
this.canPlaySourceResult = true; |
452 |
return true; |
453 |
} |
454 |
} |
455 |
} |
456 |
this.canPlaySourceResult = false; |
457 |
return false; |
458 |
}, |
459 |
// Check if the extention is compatible, for when type won't work
|
460 |
canPlayExt: function(src){ |
461 |
if (!src) { return ""; } |
462 |
var match = src.match(/\.([^\.]+)$/); |
463 |
if (match && match[1]) { |
464 |
var ext = match[1].toLowerCase(); |
465 |
// Android canPlayType doesn't work
|
466 |
if (VideoJS.isAndroid()) {
|
467 |
if (ext == "mp4" || ext == "m4v") { return "maybe"; } |
468 |
// Allow Apple HTTP Streaming for iOS
|
469 |
} else if (VideoJS.isIOS()) { |
470 |
if (ext == "m3u8") { return "maybe"; } |
471 |
} |
472 |
} |
473 |
return ""; |
474 |
}, |
475 |
// Force the video source - Helps fix loading bugs in a handful of devices, like the iPad/iPhone poster bug
|
476 |
// And iPad/iPhone javascript include location bug. And Android type attribute bug
|
477 |
forceTheSource: function(){ |
478 |
this.video.src = this.firstPlayableSource.src; // From canPlaySource() |
479 |
this.video.load();
|
480 |
}, |
481 |
/* Device Fixes
|
482 |
================================================================================ */
|
483 |
// Support older browsers that used "autobuffer"
|
484 |
fixPreloading: function(){ |
485 |
if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("preload") && this.video.preload != "none") { |
486 |
this.video.autobuffer = true; // Was a boolean |
487 |
} else {
|
488 |
this.video.autobuffer = false; |
489 |
this.video.preload = "none"; |
490 |
} |
491 |
}, |
492 |
|
493 |
// Listen for Video Load Progress (currently does not if html file is local)
|
494 |
// Buffered does't work in all browsers, so watching progress as well
|
495 |
supportProgressEvents: function(e){ |
496 |
_V_.addListener(this.video, 'progress', this.playerOnVideoProgress.context(this)); |
497 |
}, |
498 |
playerOnVideoProgress: function(event){ |
499 |
this.setBufferedFromProgress(event);
|
500 |
}, |
501 |
setBufferedFromProgress: function(event){ // HTML5 Only |
502 |
if(event.total > 0) { |
503 |
var newBufferEnd = (event.loaded / event.total) * this.duration(); |
504 |
if (newBufferEnd > this.values.bufferEnd) { this.values.bufferEnd = newBufferEnd; } |
505 |
} |
506 |
}, |
507 |
|
508 |
iOSInterface: function(){ |
509 |
if(VideoJS.iOSVersion() < 4) { this.forceTheSource(); } // Fix loading issues |
510 |
if(VideoJS.isIPad()) { // iPad could work with controlsBelow |
511 |
this.buildAndActivateSpinner(); // Spinner still works well on iPad, since iPad doesn't have one |
512 |
} |
513 |
}, |
514 |
|
515 |
// Fix android specific quirks
|
516 |
// Use built-in controls, but add the big play button, since android doesn't have one.
|
517 |
androidInterface: function(){ |
518 |
this.forceTheSource(); // Fix loading issues |
519 |
_V_.addListener(this.video, "click", function(){ this.play(); }); // Required to play |
520 |
this.buildBigPlayButton(); // But don't activate the normal way. Pause doesn't work right on android. |
521 |
_V_.addListener(this.bigPlayButton, "click", function(){ this.play(); }.context(this)); |
522 |
this.positionBox();
|
523 |
this.showBigPlayButtons();
|
524 |
}, |
525 |
/* Wait for styles (TODO: move to _V_)
|
526 |
================================================================================ */
|
527 |
loadInterface: function(){ |
528 |
if(!this.stylesHaveLoaded()) { |
529 |
// Don't want to create an endless loop either.
|
530 |
if (!this.positionRetries) { this.positionRetries = 1; } |
531 |
if (this.positionRetries++ < 100) { |
532 |
setTimeout(this.loadInterface.context(this),10); |
533 |
return;
|
534 |
} |
535 |
} |
536 |
this.hideStylesCheckDiv();
|
537 |
this.showPoster();
|
538 |
if (this.video.paused !== false) { this.showBigPlayButtons(); } |
539 |
if (this.options.controlsAtStart) { this.showControlBars(); } |
540 |
this.positionAll();
|
541 |
}, |
542 |
/* Control Bar
|
543 |
================================================================================ */
|
544 |
buildAndActivateControlBar: function(){ |
545 |
/* Creating this HTML
|
546 |
<div class="vjs-controls">
|
547 |
<div class="vjs-play-control">
|
548 |
<span></span>
|
549 |
</div>
|
550 |
<div class="vjs-progress-control">
|
551 |
<div class="vjs-progress-holder">
|
552 |
<div class="vjs-load-progress"></div>
|
553 |
<div class="vjs-play-progress"></div>
|
554 |
</div>
|
555 |
</div>
|
556 |
<div class="vjs-time-control">
|
557 |
<span class="vjs-current-time-display">00:00</span><span> / </span><span class="vjs-duration-display">00:00</span>
|
558 |
</div>
|
559 |
<div class="vjs-volume-control">
|
560 |
<div>
|
561 |
<span></span><span></span><span></span><span></span><span></span><span></span>
|
562 |
</div>
|
563 |
</div>
|
564 |
<div class="vjs-fullscreen-control">
|
565 |
<div>
|
566 |
<span></span><span></span><span></span><span></span>
|
567 |
</div>
|
568 |
</div>
|
569 |
</div>
|
570 |
*/
|
571 |
|
572 |
// Create a div to hold the different controls
|
573 |
this.controls = _V_.createElement("div", { className: "vjs-controls" }); |
574 |
// Add the controls to the video's container
|
575 |
this.box.appendChild(this.controls); |
576 |
this.activateElement(this.controls, "controlBar"); |
577 |
this.activateElement(this.controls, "mouseOverVideoReporter"); |
578 |
|
579 |
// Build the play control
|
580 |
this.playControl = _V_.createElement("div", { className: "vjs-play-control", innerHTML: "<span></span>" }); |
581 |
this.controls.appendChild(this.playControl); |
582 |
this.activateElement(this.playControl, "playToggle"); |
583 |
|
584 |
// Build the progress control
|
585 |
this.progressControl = _V_.createElement("div", { className: "vjs-progress-control" }); |
586 |
this.controls.appendChild(this.progressControl); |
587 |
|
588 |
// Create a holder for the progress bars
|
589 |
this.progressHolder = _V_.createElement("div", { className: "vjs-progress-holder" }); |
590 |
this.progressControl.appendChild(this.progressHolder); |
591 |
this.activateElement(this.progressHolder, "currentTimeScrubber"); |
592 |
|
593 |
// Create the loading progress display
|
594 |
this.loadProgressBar = _V_.createElement("div", { className: "vjs-load-progress" }); |
595 |
this.progressHolder.appendChild(this.loadProgressBar); |
596 |
this.activateElement(this.loadProgressBar, "loadProgressBar"); |
597 |
|
598 |
// Create the playing progress display
|
599 |
this.playProgressBar = _V_.createElement("div", { className: "vjs-play-progress" }); |
600 |
this.progressHolder.appendChild(this.playProgressBar); |
601 |
this.activateElement(this.playProgressBar, "playProgressBar"); |
602 |
|
603 |
// Create the progress time display (00:00 / 00:00)
|
604 |
this.timeControl = _V_.createElement("div", { className: "vjs-time-control" }); |
605 |
this.controls.appendChild(this.timeControl); |
606 |
|
607 |
// Create the current play time display
|
608 |
this.currentTimeDisplay = _V_.createElement("span", { className: "vjs-current-time-display", innerHTML: "00:00" }); |
609 |
this.timeControl.appendChild(this.currentTimeDisplay); |
610 |
this.activateElement(this.currentTimeDisplay, "currentTimeDisplay"); |
611 |
|
612 |
// Add time separator
|
613 |
this.timeSeparator = _V_.createElement("span", { innerHTML: " / " }); |
614 |
this.timeControl.appendChild(this.timeSeparator); |
615 |
|
616 |
// Create the total duration display
|
617 |
this.durationDisplay = _V_.createElement("span", { className: "vjs-duration-display", innerHTML: "00:00" }); |
618 |
this.timeControl.appendChild(this.durationDisplay); |
619 |
this.activateElement(this.durationDisplay, "durationDisplay"); |
620 |
|
621 |
// Create the volumne control
|
622 |
this.volumeControl = _V_.createElement("div", { |
623 |
className: "vjs-volume-control", |
624 |
innerHTML: "<div><span></span><span></span><span></span><span></span><span></span><span></span></div>" |
625 |
}); |
626 |
this.controls.appendChild(this.volumeControl); |
627 |
this.activateElement(this.volumeControl, "volumeScrubber"); |
628 |
|
629 |
this.volumeDisplay = this.volumeControl.children[0]; |
630 |
this.activateElement(this.volumeDisplay, "volumeDisplay"); |
631 |
|
632 |
// Crete the fullscreen control
|
633 |
this.fullscreenControl = _V_.createElement("div", { |
634 |
className: "vjs-fullscreen-control", |
635 |
innerHTML: "<div><span></span><span></span><span></span><span></span></div>" |
636 |
}); |
637 |
this.controls.appendChild(this.fullscreenControl); |
638 |
this.activateElement(this.fullscreenControl, "fullscreenToggle"); |
639 |
}, |
640 |
/* Poster Image
|
641 |
================================================================================ */
|
642 |
buildAndActivatePoster: function(){ |
643 |
this.updatePosterSource();
|
644 |
if (this.video.poster) { |
645 |
this.poster = document.createElement("img"); |
646 |
// Add poster to video box
|
647 |
this.box.appendChild(this.poster); |
648 |
|
649 |
// Add poster image data
|
650 |
this.poster.src = this.video.poster; |
651 |
// Add poster styles
|
652 |
this.poster.className = "vjs-poster"; |
653 |
this.activateElement(this.poster, "poster"); |
654 |
} else {
|
655 |
this.poster = false; |
656 |
} |
657 |
}, |
658 |
/* Big Play Button
|
659 |
================================================================================ */
|
660 |
buildBigPlayButton: function(){ |
661 |
/* Creating this HTML
|
662 |
<div class="vjs-big-play-button"><span></span></div>
|
663 |
*/
|
664 |
this.bigPlayButton = _V_.createElement("div", { |
665 |
className: "vjs-big-play-button", |
666 |
innerHTML: "<span></span>" |
667 |
}); |
668 |
this.box.appendChild(this.bigPlayButton); |
669 |
this.activateElement(this.bigPlayButton, "bigPlayButton"); |
670 |
}, |
671 |
/* Spinner (Loading)
|
672 |
================================================================================ */
|
673 |
buildAndActivateSpinner: function(){ |
674 |
this.spinner = _V_.createElement("div", { |
675 |
className: "vjs-spinner", |
676 |
innerHTML: "<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>" |
677 |
}); |
678 |
this.box.appendChild(this.spinner); |
679 |
this.activateElement(this.spinner, "spinner"); |
680 |
}, |
681 |
/* Styles Check - Check if styles are loaded (move ot _V_)
|
682 |
================================================================================ */
|
683 |
// Sometimes the CSS styles haven't been applied to the controls yet
|
684 |
// when we're trying to calculate the height and position them correctly.
|
685 |
// This causes a flicker where the controls are out of place.
|
686 |
buildStylesCheckDiv: function(){ |
687 |
this.stylesCheckDiv = _V_.createElement("div", { className: "vjs-styles-check" }); |
688 |
this.stylesCheckDiv.style.position = "absolute"; |
689 |
this.box.appendChild(this.stylesCheckDiv); |
690 |
}, |
691 |
hideStylesCheckDiv: function(){ this.stylesCheckDiv.style.display = "none"; }, |
692 |
stylesHaveLoaded: function(){ |
693 |
if (this.stylesCheckDiv.offsetHeight != 5) { |
694 |
return false; |
695 |
} else {
|
696 |
return true; |
697 |
} |
698 |
}, |
699 |
/* VideoJS Box - Holds all elements
|
700 |
================================================================================ */
|
701 |
positionAll: function(){ |
702 |
this.positionBox();
|
703 |
this.positionControlBars();
|
704 |
this.positionPoster();
|
705 |
}, |
706 |
positionBox: function(){ |
707 |
// Set width based on fullscreen or not.
|
708 |
if (this.videoIsFullScreen) { |
709 |
this.box.style.width = ""; |
710 |
this.element.style.height=""; |
711 |
this.box.style.height=""; |
712 |
if (this.options.controlsBelow) { |
713 |
this.box.style.height = ""; |
714 |
this.element.style.height = (this.box.offsetHeight - this.controls.offsetHeight) + "px"; |
715 |
} |
716 |
} else {
|
717 |
this.box.style.width = this.width() + "px"; |
718 |
this.element.style.height=this.height()+"px"; |
719 |
if (this.options.controlsBelow) { |
720 |
this.element.style.height = ""; |
721 |
// this.box.style.height = this.video.offsetHeight + this.controls.offsetHeight + "px";
|
722 |
} |
723 |
} |
724 |
}, |
725 |
/* Subtitles
|
726 |
================================================================================ */
|
727 |
getSubtitles: function(){ |
728 |
var tracks = this.video.getElementsByTagName("TRACK"); |
729 |
for (var i=0,j=tracks.length; i<j; i++) { |
730 |
if (tracks[i].getAttribute("kind") == "subtitles" && tracks[i].getAttribute("src")) { |
731 |
this.subtitlesSource = tracks[i].getAttribute("src"); |
732 |
this.loadSubtitles();
|
733 |
this.buildSubtitles();
|
734 |
} |
735 |
} |
736 |
}, |
737 |
loadSubtitles: function() { _V_.get(this.subtitlesSource, this.parseSubtitles.context(this)); }, |
738 |
parseSubtitles: function(subText) { |
739 |
var lines = subText.split("\n"), |
740 |
line = "",
|
741 |
subtitle, time, text; |
742 |
this.subtitles = [];
|
743 |
this.currentSubtitle = false; |
744 |
this.lastSubtitleIndex = 0; |
745 |
|
746 |
for (var i=0; i<lines.length; i++) { |
747 |
line = _V_.trim(lines[i]); // Trim whitespace and linebreaks
|
748 |
if (line) { // Loop until a line with content |
749 |
|
750 |
// First line - Number
|
751 |
subtitle = { |
752 |
id: line, // Subtitle Number |
753 |
index: this.subtitles.length // Position in Array |
754 |
}; |
755 |
|
756 |
// Second line - Time
|
757 |
line = _V_.trim(lines[++i]); |
758 |
time = line.split(" --> ");
|
759 |
subtitle.start = this.parseSubtitleTime(time[0]); |
760 |
subtitle.end = this.parseSubtitleTime(time[1]); |
761 |
|
762 |
// Additional lines - Subtitle Text
|
763 |
text = []; |
764 |
for (var j=i; j<lines.length; j++) { // Loop until a blank line or end of lines |
765 |
line = _V_.trim(lines[++i]); |
766 |
if (!line) { break; } |
767 |
text.push(line); |
768 |
} |
769 |
subtitle.text = text.join('<br/>');
|
770 |
|
771 |
// Add this subtitle
|
772 |
this.subtitles.push(subtitle);
|
773 |
} |
774 |
} |
775 |
}, |
776 |
|
777 |
parseSubtitleTime: function(timeText) { |
778 |
var parts = timeText.split(':'), |
779 |
time = 0;
|
780 |
// hours => seconds
|
781 |
time += parseFloat(parts[0])*60*60; |
782 |
// minutes => seconds
|
783 |
time += parseFloat(parts[1])*60; |
784 |
// get seconds
|
785 |
var seconds = parts[2].split(/\.|,/); // Either . or , |
786 |
time += parseFloat(seconds[0]);
|
787 |
// add miliseconds
|
788 |
ms = parseFloat(seconds[1]);
|
789 |
if (ms) { time += ms/1000; } |
790 |
return time;
|
791 |
}, |
792 |
|
793 |
buildSubtitles: function(){ |
794 |
/* Creating this HTML
|
795 |
<div class="vjs-subtitles"></div>
|
796 |
*/
|
797 |
this.subtitlesDisplay = _V_.createElement("div", { className: 'vjs-subtitles' }); |
798 |
this.box.appendChild(this.subtitlesDisplay); |
799 |
this.activateElement(this.subtitlesDisplay, "subtitlesDisplay"); |
800 |
}, |
801 |
|
802 |
/* Player API - Translate functionality from player to video
|
803 |
================================================================================ */
|
804 |
addVideoListener: function(type, fn){ _V_.addListener(this.video, type, fn.rEvtContext(this)); }, |
805 |
|
806 |
play: function(){ |
807 |
this.video.play();
|
808 |
return this; |
809 |
}, |
810 |
onPlay: function(fn){ this.addVideoListener("play", fn); return this; }, |
811 |
|
812 |
pause: function(){ |
813 |
this.video.pause();
|
814 |
return this; |
815 |
}, |
816 |
onPause: function(fn){ this.addVideoListener("pause", fn); return this; }, |
817 |
paused: function() { return this.video.paused; }, |
818 |
|
819 |
currentTime: function(seconds){ |
820 |
if (seconds !== undefined) { |
821 |
try { this.video.currentTime = seconds; } |
822 |
catch(e) { this.warning(VideoJS.warnings.videoNotReady); } |
823 |
this.values.currentTime = seconds;
|
824 |
return this; |
825 |
} |
826 |
return this.video.currentTime; |
827 |
}, |
828 |
onCurrentTimeUpdate: function(fn){ |
829 |
this.currentTimeListeners.push(fn);
|
830 |
}, |
831 |
|
832 |
duration: function(){ |
833 |
return this.video.duration; |
834 |
}, |
835 |
|
836 |
buffered: function(){ |
837 |
// Storing values allows them be overridden by setBufferedFromProgress
|
838 |
if (this.values.bufferStart === undefined) { |
839 |
this.values.bufferStart = 0; |
840 |
this.values.bufferEnd = 0; |
841 |
} |
842 |
if (this.video.buffered && this.video.buffered.length > 0) { |
843 |
var newEnd = this.video.buffered.end(0); |
844 |
if (newEnd > this.values.bufferEnd) { this.values.bufferEnd = newEnd; } |
845 |
} |
846 |
return [this.values.bufferStart, this.values.bufferEnd]; |
847 |
}, |
848 |
|
849 |
volume: function(percentAsDecimal){ |
850 |
if (percentAsDecimal !== undefined) { |
851 |
// Force value to between 0 and 1
|
852 |
this.values.volume = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); |
853 |
this.video.volume = this.values.volume; |
854 |
this.setLocalStorage("volume", this.values.volume); |
855 |
return this; |
856 |
} |
857 |
if (this.values.volume) { return this.values.volume; } |
858 |
return this.video.volume; |
859 |
}, |
860 |
onVolumeChange: function(fn){ _V_.addListener(this.video, 'volumechange', fn.rEvtContext(this)); }, |
861 |
|
862 |
width: function(width){ |
863 |
if (width !== undefined) { |
864 |
this.video.width = width; // Not using style so it can be overridden on fullscreen. |
865 |
this.box.style.width = width+"px"; |
866 |
this.triggerResizeListeners();
|
867 |
return this; |
868 |
} |
869 |
return this.video.offsetWidth; |
870 |
}, |
871 |
height: function(height){ |
872 |
if (height !== undefined) { |
873 |
this.video.height = height;
|
874 |
this.box.style.height = height+"px"; |
875 |
this.triggerResizeListeners();
|
876 |
return this; |
877 |
} |
878 |
return this.video.offsetHeight; |
879 |
}, |
880 |
|
881 |
supportsFullScreen: function(){ |
882 |
if(typeof this.video.webkitEnterFullScreen == 'function') { |
883 |
// Seems to be broken in Chromium/Chrome
|
884 |
if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) { |
885 |
return true; |
886 |
} |
887 |
} |
888 |
return false; |
889 |
}, |
890 |
|
891 |
html5EnterNativeFullScreen: function(){ |
892 |
try {
|
893 |
this.video.webkitEnterFullScreen();
|
894 |
} catch (e) {
|
895 |
if (e.code == 11) { this.warning(VideoJS.warnings.videoNotReady); } |
896 |
} |
897 |
return this; |
898 |
}, |
899 |
|
900 |
// Turn on fullscreen (window) mode
|
901 |
// Real fullscreen isn't available in browsers quite yet.
|
902 |
enterFullScreen: function(){ |
903 |
if (this.supportsFullScreen()) { |
904 |
this.html5EnterNativeFullScreen();
|
905 |
} else {
|
906 |
this.enterFullWindow();
|
907 |
} |
908 |
}, |
909 |
|
910 |
exitFullScreen: function(){ |
911 |
if (this.supportsFullScreen()) { |
912 |
// Shouldn't be called
|
913 |
} else {
|
914 |
this.exitFullWindow();
|
915 |
} |
916 |
}, |
917 |
|
918 |
enterFullWindow: function(){ |
919 |
this.videoIsFullScreen = true; |
920 |
// Storing original doc overflow value to return to when fullscreen is off
|
921 |
this.docOrigOverflow = document.documentElement.style.overflow;
|
922 |
// Add listener for esc key to exit fullscreen
|
923 |
_V_.addListener(document, "keydown", this.fullscreenOnEscKey.rEvtContext(this)); |
924 |
// Add listener for a window resize
|
925 |
_V_.addListener(window, "resize", this.fullscreenOnWindowResize.rEvtContext(this)); |
926 |
// Hide any scroll bars
|
927 |
document.documentElement.style.overflow = 'hidden';
|
928 |
// Apply fullscreen styles
|
929 |
_V_.addClass(this.box, "vjs-fullscreen"); |
930 |
// Resize the box, controller, and poster
|
931 |
this.positionAll();
|
932 |
}, |
933 |
|
934 |
// Turn off fullscreen (window) mode
|
935 |
exitFullWindow: function(){ |
936 |
this.videoIsFullScreen = false; |
937 |
document.removeEventListener("keydown", this.fullscreenOnEscKey, false); |
938 |
window.removeEventListener("resize", this.fullscreenOnWindowResize, false); |
939 |
// Unhide scroll bars.
|
940 |
document.documentElement.style.overflow = this.docOrigOverflow;
|
941 |
// Remove fullscreen styles
|
942 |
_V_.removeClass(this.box, "vjs-fullscreen"); |
943 |
// Resize the box, controller, and poster to original sizes
|
944 |
this.positionAll();
|
945 |
}, |
946 |
|
947 |
onError: function(fn){ this.addVideoListener("error", fn); return this; }, |
948 |
onEnded: function(fn){ |
949 |
this.addVideoListener("ended", fn); return this; |
950 |
} |
951 |
}); |
952 |
|
953 |
////////////////////////////////////////////////////////////////////////////////
|
954 |
// Element Behaviors
|
955 |
// Tell elements how to act or react
|
956 |
////////////////////////////////////////////////////////////////////////////////
|
957 |
|
958 |
/* Player Behaviors - How VideoJS reacts to what the video is doing.
|
959 |
================================================================================ */
|
960 |
VideoJS.player.newBehavior("player", function(player){ |
961 |
this.onError(this.playerOnVideoError); |
962 |
// Listen for when the video is played
|
963 |
this.onPlay(this.playerOnVideoPlay); |
964 |
this.onPlay(this.trackCurrentTime); |
965 |
// Listen for when the video is paused
|
966 |
this.onPause(this.playerOnVideoPause); |
967 |
this.onPause(this.stopTrackingCurrentTime); |
968 |
// Listen for when the video ends
|
969 |
this.onEnded(this.playerOnVideoEnded); |
970 |
// Set interval for load progress using buffer watching method
|
971 |
// this.trackCurrentTime();
|
972 |
this.trackBuffered();
|
973 |
// Buffer Full
|
974 |
this.onBufferedUpdate(this.isBufferFull); |
975 |
},{ |
976 |
playerOnVideoError: function(event){ |
977 |
this.log(event);
|
978 |
this.log(this.video.error); |
979 |
}, |
980 |
playerOnVideoPlay: function(event){ this.hasPlayed = true; }, |
981 |
playerOnVideoPause: function(event){}, |
982 |
playerOnVideoEnded: function(event){ |
983 |
this.currentTime(0); |
984 |
this.pause();
|
985 |
}, |
986 |
|
987 |
/* Load Tracking -------------------------------------------------------------- */
|
988 |
// Buffer watching method for load progress.
|
989 |
// Used for browsers that don't support the progress event
|
990 |
trackBuffered: function(){ |
991 |
this.bufferedInterval = setInterval(this.triggerBufferedListeners.context(this), 500); |
992 |
}, |
993 |
stopTrackingBuffered: function(){ clearInterval(this.bufferedInterval); }, |
994 |
bufferedListeners: [],
|
995 |
onBufferedUpdate: function(fn){ |
996 |
this.bufferedListeners.push(fn);
|
997 |
}, |
998 |
triggerBufferedListeners: function(){ |
999 |
this.isBufferFull();
|
1000 |
this.each(this.bufferedListeners, function(listener){ |
1001 |
(listener.context(this))();
|
1002 |
}); |
1003 |
}, |
1004 |
isBufferFull: function(){ |
1005 |
if (this.bufferedPercent() == 1) { this.stopTrackingBuffered(); } |
1006 |
}, |
1007 |
|
1008 |
/* Time Tracking -------------------------------------------------------------- */
|
1009 |
trackCurrentTime: function(){ |
1010 |
if (this.currentTimeInterval) { clearInterval(this.currentTimeInterval); } |
1011 |
this.currentTimeInterval = setInterval(this.triggerCurrentTimeListeners.context(this), 100); // 42 = 24 fps |
1012 |
this.trackingCurrentTime = true; |
1013 |
}, |
1014 |
// Turn off play progress tracking (when paused or dragging)
|
1015 |
stopTrackingCurrentTime: function(){ |
1016 |
clearInterval(this.currentTimeInterval);
|
1017 |
this.trackingCurrentTime = false; |
1018 |
}, |
1019 |
currentTimeListeners: [],
|
1020 |
// onCurrentTimeUpdate is in API section now
|
1021 |
triggerCurrentTimeListeners: function(late, newTime){ // FF passes milliseconds late as the first argument |
1022 |
this.each(this.currentTimeListeners, function(listener){ |
1023 |
(listener.context(this))(newTime || this.currentTime()); |
1024 |
}); |
1025 |
}, |
1026 |
|
1027 |
/* Resize Tracking -------------------------------------------------------------- */
|
1028 |
resizeListeners: [],
|
1029 |
onResize: function(fn){ |
1030 |
this.resizeListeners.push(fn);
|
1031 |
}, |
1032 |
// Trigger anywhere the video/box size is changed.
|
1033 |
triggerResizeListeners: function(){ |
1034 |
this.each(this.resizeListeners, function(listener){ |
1035 |
(listener.context(this))();
|
1036 |
}); |
1037 |
} |
1038 |
} |
1039 |
); |
1040 |
/* Mouse Over Video Reporter Behaviors - i.e. Controls hiding based on mouse location
|
1041 |
================================================================================ */
|
1042 |
VideoJS.player.newBehavior("mouseOverVideoReporter", function(element){ |
1043 |
// Listen for the mouse move the video. Used to reveal the controller.
|
1044 |
_V_.addListener(element, "mousemove", this.mouseOverVideoReporterOnMouseMove.context(this)); |
1045 |
// Listen for the mouse moving out of the video. Used to hide the controller.
|
1046 |
_V_.addListener(element, "mouseout", this.mouseOverVideoReporterOnMouseOut.context(this)); |
1047 |
},{ |
1048 |
mouseOverVideoReporterOnMouseMove: function(){ |
1049 |
this.showControlBars();
|
1050 |
clearInterval(this.mouseMoveTimeout);
|
1051 |
this.mouseMoveTimeout = setTimeout(this.hideControlBars.context(this), 4000); |
1052 |
}, |
1053 |
mouseOverVideoReporterOnMouseOut: function(event){ |
1054 |
// Prevent flicker by making sure mouse hasn't left the video
|
1055 |
var parent = event.relatedTarget;
|
1056 |
while (parent && parent !== this.box) { |
1057 |
parent = parent.parentNode; |
1058 |
} |
1059 |
if (parent !== this.box) { |
1060 |
this.hideControlBars();
|
1061 |
} |
1062 |
} |
1063 |
} |
1064 |
); |
1065 |
/* Mouse Over Video Reporter Behaviors - i.e. Controls hiding based on mouse location
|
1066 |
================================================================================ */
|
1067 |
VideoJS.player.newBehavior("box", function(element){ |
1068 |
this.positionBox();
|
1069 |
_V_.addClass(element, "vjs-paused");
|
1070 |
this.activateElement(element, "mouseOverVideoReporter"); |
1071 |
this.onPlay(this.boxOnVideoPlay); |
1072 |
this.onPause(this.boxOnVideoPause); |
1073 |
},{ |
1074 |
boxOnVideoPlay: function(){ |
1075 |
_V_.removeClass(this.box, "vjs-paused"); |
1076 |
_V_.addClass(this.box, "vjs-playing"); |
1077 |
}, |
1078 |
boxOnVideoPause: function(){ |
1079 |
_V_.removeClass(this.box, "vjs-playing"); |
1080 |
_V_.addClass(this.box, "vjs-paused"); |
1081 |
} |
1082 |
} |
1083 |
); |
1084 |
/* Poster Image Overlay
|
1085 |
================================================================================ */
|
1086 |
VideoJS.player.newBehavior("poster", function(element){ |
1087 |
this.activateElement(element, "mouseOverVideoReporter"); |
1088 |
this.activateElement(element, "playButton"); |
1089 |
this.onPlay(this.hidePoster); |
1090 |
this.onEnded(this.showPoster); |
1091 |
this.onResize(this.positionPoster); |
1092 |
},{ |
1093 |
showPoster: function(){ |
1094 |
if (!this.poster) { return; } |
1095 |
this.poster.style.display = "block"; |
1096 |
this.positionPoster();
|
1097 |
}, |
1098 |
positionPoster: function(){ |
1099 |
// Only if the poster is visible
|
1100 |
if (!this.poster || this.poster.style.display == 'none') { return; } |
1101 |
this.poster.style.height = this.height() + "px"; // Need incase controlsBelow |
1102 |
this.poster.style.width = this.width() + "px"; // Could probably do 100% of box |
1103 |
}, |
1104 |
hidePoster: function(){ |
1105 |
if (!this.poster) { return; } |
1106 |
this.poster.style.display = "none"; |
1107 |
}, |
1108 |
// Update poster source from attribute or fallback image
|
1109 |
// iPad breaks if you include a poster attribute, so this fixes that
|
1110 |
updatePosterSource: function(){ |
1111 |
if (!this.video.poster) { |
1112 |
var images = this.video.getElementsByTagName("img"); |
1113 |
if (images.length > 0) { this.video.poster = images[0].src; } |
1114 |
} |
1115 |
} |
1116 |
} |
1117 |
); |
1118 |
/* Control Bar Behaviors
|
1119 |
================================================================================ */
|
1120 |
VideoJS.player.newBehavior("controlBar", function(element){ |
1121 |
if (!this.controlBars) { |
1122 |
this.controlBars = [];
|
1123 |
this.onResize(this.positionControlBars); |
1124 |
} |
1125 |
this.controlBars.push(element);
|
1126 |
_V_.addListener(element, "mousemove", this.onControlBarsMouseMove.context(this)); |
1127 |
_V_.addListener(element, "mouseout", this.onControlBarsMouseOut.context(this)); |
1128 |
},{ |
1129 |
showControlBars: function(){ |
1130 |
if (!this.options.controlsAtStart && !this.hasPlayed) { return; } |
1131 |
this.each(this.controlBars, function(bar){ |
1132 |
bar.style.display = "block";
|
1133 |
}); |
1134 |
}, |
1135 |
// Place controller relative to the video's position (now just resizing bars)
|
1136 |
positionControlBars: function(){ |
1137 |
this.updatePlayProgressBars();
|
1138 |
this.updateLoadProgressBars();
|
1139 |
}, |
1140 |
hideControlBars: function(){ |
1141 |
if (this.options.controlsHiding && !this.mouseIsOverControls) { |
1142 |
this.each(this.controlBars, function(bar){ |
1143 |
bar.style.display = "none";
|
1144 |
}); |
1145 |
} |
1146 |
}, |
1147 |
// Block controls from hiding when mouse is over them.
|
1148 |
onControlBarsMouseMove: function(){ this.mouseIsOverControls = true; }, |
1149 |
onControlBarsMouseOut: function(event){ |
1150 |
this.mouseIsOverControls = false; |
1151 |
} |
1152 |
} |
1153 |
); |
1154 |
/* PlayToggle, PlayButton, PauseButton Behaviors
|
1155 |
================================================================================ */
|
1156 |
// Play Toggle
|
1157 |
VideoJS.player.newBehavior("playToggle", function(element){ |
1158 |
if (!this.elements.playToggles) { |
1159 |
this.elements.playToggles = [];
|
1160 |
this.onPlay(this.playTogglesOnPlay); |
1161 |
this.onPause(this.playTogglesOnPause); |
1162 |
} |
1163 |
this.elements.playToggles.push(element);
|
1164 |
_V_.addListener(element, "click", this.onPlayToggleClick.context(this)); |
1165 |
},{ |
1166 |
onPlayToggleClick: function(event){ |
1167 |
if (this.paused()) { |
1168 |
this.play();
|
1169 |
} else {
|
1170 |
this.pause();
|
1171 |
} |
1172 |
}, |
1173 |
playTogglesOnPlay: function(event){ |
1174 |
this.each(this.elements.playToggles, function(toggle){ |
1175 |
_V_.removeClass(toggle, "vjs-paused");
|
1176 |
_V_.addClass(toggle, "vjs-playing");
|
1177 |
}); |
1178 |
}, |
1179 |
playTogglesOnPause: function(event){ |
1180 |
this.each(this.elements.playToggles, function(toggle){ |
1181 |
_V_.removeClass(toggle, "vjs-playing");
|
1182 |
_V_.addClass(toggle, "vjs-paused");
|
1183 |
}); |
1184 |
} |
1185 |
} |
1186 |
); |
1187 |
// Play
|
1188 |
VideoJS.player.newBehavior("playButton", function(element){ |
1189 |
_V_.addListener(element, "click", this.onPlayButtonClick.context(this)); |
1190 |
},{ |
1191 |
onPlayButtonClick: function(event){ this.play(); } |
1192 |
} |
1193 |
); |
1194 |
// Pause
|
1195 |
VideoJS.player.newBehavior("pauseButton", function(element){ |
1196 |
_V_.addListener(element, "click", this.onPauseButtonClick.context(this)); |
1197 |
},{ |
1198 |
onPauseButtonClick: function(event){ this.pause(); } |
1199 |
} |
1200 |
); |
1201 |
/* Play Progress Bar Behaviors
|
1202 |
================================================================================ */
|
1203 |
VideoJS.player.newBehavior("playProgressBar", function(element){ |
1204 |
if (!this.playProgressBars) { |
1205 |
this.playProgressBars = [];
|
1206 |
this.onCurrentTimeUpdate(this.updatePlayProgressBars); |
1207 |
} |
1208 |
this.playProgressBars.push(element);
|
1209 |
},{ |
1210 |
// Ajust the play progress bar's width based on the current play time
|
1211 |
updatePlayProgressBars: function(newTime){ |
1212 |
var progress = (newTime !== undefined) ? newTime / this.duration() : this.currentTime() / this.duration(); |
1213 |
if (isNaN(progress)) { progress = 0; } |
1214 |
this.each(this.playProgressBars, function(bar){ |
1215 |
if (bar.style) { bar.style.width = _V_.round(progress * 100, 2) + "%"; } |
1216 |
}); |
1217 |
} |
1218 |
} |
1219 |
); |
1220 |
/* Load Progress Bar Behaviors
|
1221 |
================================================================================ */
|
1222 |
VideoJS.player.newBehavior("loadProgressBar", function(element){ |
1223 |
if (!this.loadProgressBars) { this.loadProgressBars = []; } |
1224 |
this.loadProgressBars.push(element);
|
1225 |
this.onBufferedUpdate(this.updateLoadProgressBars); |
1226 |
},{ |
1227 |
updateLoadProgressBars: function(){ |
1228 |
this.each(this.loadProgressBars, function(bar){ |
1229 |
if (bar.style) { bar.style.width = _V_.round(this.bufferedPercent() * 100, 2) + "%"; } |
1230 |
}); |
1231 |
} |
1232 |
} |
1233 |
); |
1234 |
|
1235 |
/* Current Time Display Behaviors
|
1236 |
================================================================================ */
|
1237 |
VideoJS.player.newBehavior("currentTimeDisplay", function(element){ |
1238 |
if (!this.currentTimeDisplays) { |
1239 |
this.currentTimeDisplays = [];
|
1240 |
this.onCurrentTimeUpdate(this.updateCurrentTimeDisplays); |
1241 |
} |
1242 |
this.currentTimeDisplays.push(element);
|
1243 |
},{ |
1244 |
// Update the displayed time (00:00)
|
1245 |
updateCurrentTimeDisplays: function(newTime){ |
1246 |
if (!this.currentTimeDisplays) { return; } |
1247 |
// Allows for smooth scrubbing, when player can't keep up.
|
1248 |
var time = (newTime) ? newTime : this.currentTime(); |
1249 |
this.each(this.currentTimeDisplays, function(dis){ |
1250 |
dis.innerHTML = _V_.formatTime(time); |
1251 |
}); |
1252 |
} |
1253 |
} |
1254 |
); |
1255 |
|
1256 |
/* Duration Display Behaviors
|
1257 |
================================================================================ */
|
1258 |
VideoJS.player.newBehavior("durationDisplay", function(element){ |
1259 |
if (!this.durationDisplays) { |
1260 |
this.durationDisplays = [];
|
1261 |
this.onCurrentTimeUpdate(this.updateDurationDisplays); |
1262 |
} |
1263 |
this.durationDisplays.push(element);
|
1264 |
},{ |
1265 |
updateDurationDisplays: function(){ |
1266 |
if (!this.durationDisplays) { return; } |
1267 |
this.each(this.durationDisplays, function(dis){ |
1268 |
if (this.duration()) { dis.innerHTML = _V_.formatTime(this.duration()); } |
1269 |
}); |
1270 |
} |
1271 |
} |
1272 |
); |
1273 |
|
1274 |
/* Current Time Scrubber Behaviors
|
1275 |
================================================================================ */
|
1276 |
VideoJS.player.newBehavior("currentTimeScrubber", function(element){ |
1277 |
_V_.addListener(element, "mousedown", this.onCurrentTimeScrubberMouseDown.rEvtContext(this)); |
1278 |
},{ |
1279 |
// Adjust the play position when the user drags on the progress bar
|
1280 |
onCurrentTimeScrubberMouseDown: function(event, scrubber){ |
1281 |
event.preventDefault(); |
1282 |
this.currentScrubber = scrubber;
|
1283 |
|
1284 |
this.stopTrackingCurrentTime(); // Allows for smooth scrubbing |
1285 |
|
1286 |
this.videoWasPlaying = !this.paused(); |
1287 |
this.pause();
|
1288 |
|
1289 |
_V_.blockTextSelection(); |
1290 |
this.setCurrentTimeWithScrubber(event);
|
1291 |
_V_.addListener(document, "mousemove", this.onCurrentTimeScrubberMouseMove.rEvtContext(this)); |
1292 |
_V_.addListener(document, "mouseup", this.onCurrentTimeScrubberMouseUp.rEvtContext(this)); |
1293 |
}, |
1294 |
onCurrentTimeScrubberMouseMove: function(event){ // Removeable |
1295 |
this.setCurrentTimeWithScrubber(event);
|
1296 |
}, |
1297 |
onCurrentTimeScrubberMouseUp: function(event){ // Removeable |
1298 |
_V_.unblockTextSelection(); |
1299 |
document.removeEventListener("mousemove", this.onCurrentTimeScrubberMouseMove, false); |
1300 |
document.removeEventListener("mouseup", this.onCurrentTimeScrubberMouseUp, false); |
1301 |
if (this.videoWasPlaying) { |
1302 |
this.play();
|
1303 |
this.trackCurrentTime();
|
1304 |
} |
1305 |
}, |
1306 |
setCurrentTimeWithScrubber: function(event){ |
1307 |
var newProgress = _V_.getRelativePosition(event.pageX, this.currentScrubber); |
1308 |
var newTime = newProgress * this.duration(); |
1309 |
this.triggerCurrentTimeListeners(0, newTime); // Allows for smooth scrubbing |
1310 |
// Don't let video end while scrubbing.
|
1311 |
if (newTime == this.duration()) { newTime = newTime - 0.1; } |
1312 |
this.currentTime(newTime);
|
1313 |
} |
1314 |
} |
1315 |
); |
1316 |
/* Volume Display Behaviors
|
1317 |
================================================================================ */
|
1318 |
VideoJS.player.newBehavior("volumeDisplay", function(element){ |
1319 |
if (!this.volumeDisplays) { |
1320 |
this.volumeDisplays = [];
|
1321 |
this.onVolumeChange(this.updateVolumeDisplays); |
1322 |
} |
1323 |
this.volumeDisplays.push(element);
|
1324 |
this.updateVolumeDisplay(element); // Set the display to the initial volume |
1325 |
},{ |
1326 |
// Update the volume control display
|
1327 |
// Unique to these default controls. Uses borders to create the look of bars.
|
1328 |
updateVolumeDisplays: function(){ |
1329 |
if (!this.volumeDisplays) { return; } |
1330 |
this.each(this.volumeDisplays, function(dis){ |
1331 |
this.updateVolumeDisplay(dis);
|
1332 |
}); |
1333 |
}, |
1334 |
updateVolumeDisplay: function(display){ |
1335 |
var volNum = Math.ceil(this.volume() * 6); |
1336 |
this.each(display.children, function(child, num){ |
1337 |
if (num < volNum) {
|
1338 |
_V_.addClass(child, "vjs-volume-level-on");
|
1339 |
} else {
|
1340 |
_V_.removeClass(child, "vjs-volume-level-on");
|
1341 |
} |
1342 |
}); |
1343 |
} |
1344 |
} |
1345 |
); |
1346 |
/* Volume Scrubber Behaviors
|
1347 |
================================================================================ */
|
1348 |
VideoJS.player.newBehavior("volumeScrubber", function(element){ |
1349 |
_V_.addListener(element, "mousedown", this.onVolumeScrubberMouseDown.rEvtContext(this)); |
1350 |
},{ |
1351 |
// Adjust the volume when the user drags on the volume control
|
1352 |
onVolumeScrubberMouseDown: function(event, scrubber){ |
1353 |
// event.preventDefault();
|
1354 |
_V_.blockTextSelection(); |
1355 |
this.currentScrubber = scrubber;
|
1356 |
this.setVolumeWithScrubber(event);
|
1357 |
_V_.addListener(document, "mousemove", this.onVolumeScrubberMouseMove.rEvtContext(this)); |
1358 |
_V_.addListener(document, "mouseup", this.onVolumeScrubberMouseUp.rEvtContext(this)); |
1359 |
}, |
1360 |
onVolumeScrubberMouseMove: function(event){ |
1361 |
this.setVolumeWithScrubber(event);
|
1362 |
}, |
1363 |
onVolumeScrubberMouseUp: function(event){ |
1364 |
this.setVolumeWithScrubber(event);
|
1365 |
_V_.unblockTextSelection(); |
1366 |
document.removeEventListener("mousemove", this.onVolumeScrubberMouseMove, false); |
1367 |
document.removeEventListener("mouseup", this.onVolumeScrubberMouseUp, false); |
1368 |
}, |
1369 |
setVolumeWithScrubber: function(event){ |
1370 |
var newVol = _V_.getRelativePosition(event.pageX, this.currentScrubber); |
1371 |
this.volume(newVol);
|
1372 |
} |
1373 |
} |
1374 |
); |
1375 |
/* Fullscreen Toggle Behaviors
|
1376 |
================================================================================ */
|
1377 |
VideoJS.player.newBehavior("fullscreenToggle", function(element){ |
1378 |
_V_.addListener(element, "click", this.onFullscreenToggleClick.context(this)); |
1379 |
},{ |
1380 |
// When the user clicks on the fullscreen button, update fullscreen setting
|
1381 |
onFullscreenToggleClick: function(event){ |
1382 |
if (!this.videoIsFullScreen) { |
1383 |
this.enterFullScreen();
|
1384 |
} else {
|
1385 |
this.exitFullScreen();
|
1386 |
} |
1387 |
}, |
1388 |
|
1389 |
fullscreenOnWindowResize: function(event){ // Removeable |
1390 |
this.positionControlBars();
|
1391 |
}, |
1392 |
// Create listener for esc key while in full screen mode
|
1393 |
fullscreenOnEscKey: function(event){ // Removeable |
1394 |
if (event.keyCode == 27) { |
1395 |
this.exitFullScreen();
|
1396 |
} |
1397 |
} |
1398 |
} |
1399 |
); |
1400 |
/* Big Play Button Behaviors
|
1401 |
================================================================================ */
|
1402 |
VideoJS.player.newBehavior("bigPlayButton", function(element){ |
1403 |
if (!this.elements.bigPlayButtons) { |
1404 |
this.elements.bigPlayButtons = [];
|
1405 |
this.onPlay(this.bigPlayButtonsOnPlay); |
1406 |
this.onEnded(this.bigPlayButtonsOnEnded); |
1407 |
} |
1408 |
this.elements.bigPlayButtons.push(element);
|
1409 |
this.activateElement(element, "playButton"); |
1410 |
},{ |
1411 |
bigPlayButtonsOnPlay: function(event){ this.hideBigPlayButtons(); }, |
1412 |
bigPlayButtonsOnEnded: function(event){ this.showBigPlayButtons(); }, |
1413 |
showBigPlayButtons: function(){ |
1414 |
this.each(this.elements.bigPlayButtons, function(element){ |
1415 |
element.style.display = "block";
|
1416 |
}); |
1417 |
}, |
1418 |
hideBigPlayButtons: function(){ |
1419 |
this.each(this.elements.bigPlayButtons, function(element){ |
1420 |
element.style.display = "none";
|
1421 |
}); |
1422 |
} |
1423 |
} |
1424 |
); |
1425 |
/* Spinner
|
1426 |
================================================================================ */
|
1427 |
VideoJS.player.newBehavior("spinner", function(element){ |
1428 |
if (!this.spinners) { |
1429 |
this.spinners = [];
|
1430 |
_V_.addListener(this.video, "loadeddata", this.spinnersOnVideoLoadedData.context(this)); |
1431 |
_V_.addListener(this.video, "loadstart", this.spinnersOnVideoLoadStart.context(this)); |
1432 |
_V_.addListener(this.video, "seeking", this.spinnersOnVideoSeeking.context(this)); |
1433 |
_V_.addListener(this.video, "seeked", this.spinnersOnVideoSeeked.context(this)); |
1434 |
_V_.addListener(this.video, "canplay", this.spinnersOnVideoCanPlay.context(this)); |
1435 |
_V_.addListener(this.video, "canplaythrough", this.spinnersOnVideoCanPlayThrough.context(this)); |
1436 |
_V_.addListener(this.video, "waiting", this.spinnersOnVideoWaiting.context(this)); |
1437 |
_V_.addListener(this.video, "stalled", this.spinnersOnVideoStalled.context(this)); |
1438 |
_V_.addListener(this.video, "suspend", this.spinnersOnVideoSuspend.context(this)); |
1439 |
_V_.addListener(this.video, "playing", this.spinnersOnVideoPlaying.context(this)); |
1440 |
_V_.addListener(this.video, "timeupdate", this.spinnersOnVideoTimeUpdate.context(this)); |
1441 |
} |
1442 |
this.spinners.push(element);
|
1443 |
},{ |
1444 |
showSpinners: function(){ |
1445 |
this.each(this.spinners, function(spinner){ |
1446 |
spinner.style.display = "block";
|
1447 |
}); |
1448 |
clearInterval(this.spinnerInterval);
|
1449 |
this.spinnerInterval = setInterval(this.rotateSpinners.context(this), 100); |
1450 |
}, |
1451 |
hideSpinners: function(){ |
1452 |
this.each(this.spinners, function(spinner){ |
1453 |
spinner.style.display = "none";
|
1454 |
}); |
1455 |
clearInterval(this.spinnerInterval);
|
1456 |
}, |
1457 |
spinnersRotated: 0, |
1458 |
rotateSpinners: function(){ |
1459 |
this.each(this.spinners, function(spinner){ |
1460 |
// spinner.style.transform = 'scale(0.5) rotate('+this.spinnersRotated+'deg)';
|
1461 |
spinner.style.WebkitTransform = 'scale(0.5) rotate('+this.spinnersRotated+'deg)'; |
1462 |
spinner.style.MozTransform = 'scale(0.5) rotate('+this.spinnersRotated+'deg)'; |
1463 |
}); |
1464 |
if (this.spinnersRotated == 360) { this.spinnersRotated = 0; } |
1465 |
this.spinnersRotated += 45; |
1466 |
}, |
1467 |
spinnersOnVideoLoadedData: function(event){ this.hideSpinners(); }, |
1468 |
spinnersOnVideoLoadStart: function(event){ this.showSpinners(); }, |
1469 |
spinnersOnVideoSeeking: function(event){ /* this.showSpinners(); */ }, |
1470 |
spinnersOnVideoSeeked: function(event){ /* this.hideSpinners(); */ }, |
1471 |
spinnersOnVideoCanPlay: function(event){ /* this.hideSpinners(); */ }, |
1472 |
spinnersOnVideoCanPlayThrough: function(event){ this.hideSpinners(); }, |
1473 |
spinnersOnVideoWaiting: function(event){ |
1474 |
// Safari sometimes triggers waiting inappropriately
|
1475 |
// Like after video has played, any you play again.
|
1476 |
this.showSpinners();
|
1477 |
}, |
1478 |
spinnersOnVideoStalled: function(event){}, |
1479 |
spinnersOnVideoSuspend: function(event){}, |
1480 |
spinnersOnVideoPlaying: function(event){ this.hideSpinners(); }, |
1481 |
spinnersOnVideoTimeUpdate: function(event){ |
1482 |
// Safari sometimes calls waiting and doesn't recover
|
1483 |
if(this.spinner.style.display == "block") { this.hideSpinners(); } |
1484 |
} |
1485 |
} |
1486 |
); |
1487 |
/* Subtitles
|
1488 |
================================================================================ */
|
1489 |
VideoJS.player.newBehavior("subtitlesDisplay", function(element){ |
1490 |
if (!this.subtitleDisplays) { |
1491 |
this.subtitleDisplays = [];
|
1492 |
this.onCurrentTimeUpdate(this.subtitleDisplaysOnVideoTimeUpdate); |
1493 |
this.onEnded(function() { this.lastSubtitleIndex = 0; }.context(this)); |
1494 |
} |
1495 |
this.subtitleDisplays.push(element);
|
1496 |
},{ |
1497 |
subtitleDisplaysOnVideoTimeUpdate: function(time){ |
1498 |
// Assuming all subtitles are in order by time, and do not overlap
|
1499 |
if (this.subtitles) { |
1500 |
// If current subtitle should stay showing, don't do anything. Otherwise, find new subtitle.
|
1501 |
if (!this.currentSubtitle || this.currentSubtitle.start >= time || this.currentSubtitle.end < time) { |
1502 |
var newSubIndex = false, |
1503 |
// Loop in reverse if lastSubtitle is after current time (optimization)
|
1504 |
// Meaning the user is scrubbing in reverse or rewinding
|
1505 |
reverse = (this.subtitles[this.lastSubtitleIndex].start > time), |
1506 |
// If reverse, step back 1 becase we know it's not the lastSubtitle
|
1507 |
i = this.lastSubtitleIndex - (reverse) ? 1 : 0; |
1508 |
while (true) { // Loop until broken |
1509 |
if (reverse) { // Looping in reverse |
1510 |
// Stop if no more, or this subtitle ends before the current time (no earlier subtitles should apply)
|
1511 |
if (i < 0 || this.subtitles[i].end < time) { break; } |
1512 |
// End is greater than time, so if start is less, show this subtitle
|
1513 |
if (this.subtitles[i].start < time) { |
1514 |
newSubIndex = i; |
1515 |
break;
|
1516 |
} |
1517 |
i--; |
1518 |
} else { // Looping forward |
1519 |
// Stop if no more, or this subtitle starts after time (no later subtitles should apply)
|
1520 |
if (i >= this.subtitles.length || this.subtitles[i].start > time) { break; } |
1521 |
// Start is less than time, so if end is later, show this subtitle
|
1522 |
if (this.subtitles[i].end > time) { |
1523 |
newSubIndex = i; |
1524 |
break;
|
1525 |
} |
1526 |
i++; |
1527 |
} |
1528 |
} |
1529 |
|
1530 |
// Set or clear current subtitle
|
1531 |
if (newSubIndex !== false) { |
1532 |
this.currentSubtitle = this.subtitles[newSubIndex]; |
1533 |
this.lastSubtitleIndex = newSubIndex;
|
1534 |
this.updateSubtitleDisplays(this.currentSubtitle.text); |
1535 |
} else if (this.currentSubtitle) { |
1536 |
this.currentSubtitle = false; |
1537 |
this.updateSubtitleDisplays(""); |
1538 |
} |
1539 |
} |
1540 |
} |
1541 |
}, |
1542 |
updateSubtitleDisplays: function(val){ |
1543 |
this.each(this.subtitleDisplays, function(disp){ |
1544 |
disp.innerHTML = val; |
1545 |
}); |
1546 |
} |
1547 |
} |
1548 |
); |
1549 |
|
1550 |
////////////////////////////////////////////////////////////////////////////////
|
1551 |
// Convenience Functions (mini library)
|
1552 |
// Functions not specific to video or VideoJS and could probably be replaced with a library like jQuery
|
1553 |
////////////////////////////////////////////////////////////////////////////////
|
1554 |
|
1555 |
VideoJS.extend({ |
1556 |
|
1557 |
addClass: function(element, classToAdd){ |
1558 |
if ((" "+element.className+" ").indexOf(" "+classToAdd+" ") == -1) { |
1559 |
element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd; |
1560 |
} |
1561 |
}, |
1562 |
removeClass: function(element, classToRemove){ |
1563 |
if (element.className.indexOf(classToRemove) == -1) { return; } |
1564 |
var classNames = element.className.split(/\s+/); |
1565 |
classNames.splice(classNames.lastIndexOf(classToRemove),1);
|
1566 |
element.className = classNames.join(" ");
|
1567 |
}, |
1568 |
createElement: function(tagName, attributes){ |
1569 |
return this.merge(document.createElement(tagName), attributes); |
1570 |
}, |
1571 |
|
1572 |
// Attempt to block the ability to select text while dragging controls
|
1573 |
blockTextSelection: function(){ |
1574 |
document.body.focus(); |
1575 |
document.onselectstart = function () { return false; }; |
1576 |
}, |
1577 |
// Turn off text selection blocking
|
1578 |
unblockTextSelection: function(){ document.onselectstart = function () { return true; }; }, |
1579 |
|
1580 |
// Return seconds as MM:SS
|
1581 |
formatTime: function(secs) { |
1582 |
var seconds = Math.round(secs);
|
1583 |
var minutes = Math.floor(seconds / 60); |
1584 |
minutes = (minutes >= 10) ? minutes : "0" + minutes; |
1585 |
seconds = Math.floor(seconds % 60);
|
1586 |
seconds = (seconds >= 10) ? seconds : "0" + seconds; |
1587 |
return minutes + ":" + seconds; |
1588 |
}, |
1589 |
|
1590 |
// Return the relative horizonal position of an event as a value from 0-1
|
1591 |
getRelativePosition: function(x, relativeElement){ |
1592 |
return Math.max(0, Math.min(1, (x - this.findPosX(relativeElement)) / relativeElement.offsetWidth)); |
1593 |
}, |
1594 |
// Get an objects position on the page
|
1595 |
findPosX: function(obj) { |
1596 |
var curleft = obj.offsetLeft;
|
1597 |
while(obj = obj.offsetParent) {
|
1598 |
curleft += obj.offsetLeft; |
1599 |
} |
1600 |
return curleft;
|
1601 |
}, |
1602 |
getComputedStyleValue: function(element, style){ |
1603 |
return window.getComputedStyle(element, null).getPropertyValue(style); |
1604 |
}, |
1605 |
|
1606 |
round: function(num, dec) { |
1607 |
if (!dec) { dec = 0; } |
1608 |
return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec); |
1609 |
}, |
1610 |
|
1611 |
addListener: function(element, type, handler){ |
1612 |
if (element.addEventListener) {
|
1613 |
element.addEventListener(type, handler, false);
|
1614 |
} else if (element.attachEvent) { |
1615 |
element.attachEvent("on"+type, handler);
|
1616 |
} |
1617 |
}, |
1618 |
removeListener: function(element, type, handler){ |
1619 |
if (element.removeEventListener) {
|
1620 |
element.removeEventListener(type, handler, false);
|
1621 |
} else if (element.attachEvent) { |
1622 |
element.detachEvent("on"+type, handler);
|
1623 |
} |
1624 |
}, |
1625 |
|
1626 |
get: function(url, onSuccess){ |
1627 |
if (typeof XMLHttpRequest == "undefined") { |
1628 |
XMLHttpRequest = function () { |
1629 |
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {} |
1630 |
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (f) {} |
1631 |
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (g) {} |
1632 |
//Microsoft.XMLHTTP points to Msxml2.XMLHTTP.3.0 and is redundant
|
1633 |
throw new Error("This browser does not support XMLHttpRequest."); |
1634 |
}; |
1635 |
} |
1636 |
var request = new XMLHttpRequest(); |
1637 |
request.open("GET",url);
|
1638 |
request.onreadystatechange = function() { |
1639 |
if (request.readyState == 4 && request.status == 200) { |
1640 |
onSuccess(request.responseText); |
1641 |
} |
1642 |
}.context(this);
|
1643 |
request.send(); |
1644 |
}, |
1645 |
|
1646 |
trim: function(string){ return string.toString().replace(/^\s+/, "").replace(/\s+$/, ""); }, |
1647 |
|
1648 |
// DOM Ready functionality adapted from jQuery. http://jquery.com/
|
1649 |
bindDOMReady: function(){ |
1650 |
if (document.readyState === "complete") { |
1651 |
return VideoJS.onDOMReady();
|
1652 |
} |
1653 |
if (document.addEventListener) {
|
1654 |
document.addEventListener("DOMContentLoaded", VideoJS.DOMContentLoaded, false); |
1655 |
window.addEventListener("load", VideoJS.onDOMReady, false); |
1656 |
} else if (document.attachEvent) { |
1657 |
document.attachEvent("onreadystatechange", VideoJS.DOMContentLoaded);
|
1658 |
window.attachEvent("onload", VideoJS.onDOMReady);
|
1659 |
} |
1660 |
}, |
1661 |
|
1662 |
DOMContentLoaded: function(){ |
1663 |
if (document.addEventListener) {
|
1664 |
document.removeEventListener( "DOMContentLoaded", VideoJS.DOMContentLoaded, false); |
1665 |
VideoJS.onDOMReady(); |
1666 |
} else if ( document.attachEvent ) { |
1667 |
if ( document.readyState === "complete" ) { |
1668 |
document.detachEvent("onreadystatechange", VideoJS.DOMContentLoaded);
|
1669 |
VideoJS.onDOMReady(); |
1670 |
} |
1671 |
} |
1672 |
}, |
1673 |
|
1674 |
// Functions to be run once the DOM is loaded
|
1675 |
DOMReadyList: [],
|
1676 |
addToDOMReady: function(fn){ |
1677 |
if (VideoJS.DOMIsReady) {
|
1678 |
fn.call(document); |
1679 |
} else {
|
1680 |
VideoJS.DOMReadyList.push(fn); |
1681 |
} |
1682 |
}, |
1683 |
|
1684 |
DOMIsReady: false, |
1685 |
onDOMReady: function(){ |
1686 |
if (VideoJS.DOMIsReady) { return; } |
1687 |
if (!document.body) { return setTimeout(VideoJS.onDOMReady, 13); } |
1688 |
VideoJS.DOMIsReady = true;
|
1689 |
if (VideoJS.DOMReadyList) {
|
1690 |
for (var i=0; i<VideoJS.DOMReadyList.length; i++) { |
1691 |
VideoJS.DOMReadyList[i].call(document); |
1692 |
} |
1693 |
VideoJS.DOMReadyList = null;
|
1694 |
} |
1695 |
} |
1696 |
}); |
1697 |
VideoJS.bindDOMReady(); |
1698 |
|
1699 |
// Allows for binding context to functions
|
1700 |
// when using in event listeners and timeouts
|
1701 |
Function.prototype.context = function(obj){ |
1702 |
var method = this, |
1703 |
temp = function(){ |
1704 |
return method.apply(obj, arguments); |
1705 |
}; |
1706 |
return temp;
|
1707 |
}; |
1708 |
|
1709 |
// Like context, in that it creates a closure
|
1710 |
// But insteaad keep "this" intact, and passes the var as the second argument of the function
|
1711 |
// Need for event listeners where you need to know what called the event
|
1712 |
// Only use with event callbacks
|
1713 |
Function.prototype.evtContext = function(obj){ |
1714 |
var method = this, |
1715 |
temp = function(){ |
1716 |
var origContext = this; |
1717 |
return method.call(obj, arguments[0], origContext); |
1718 |
}; |
1719 |
return temp;
|
1720 |
}; |
1721 |
|
1722 |
// Removeable Event listener with Context
|
1723 |
// Replaces the original function with a version that has context
|
1724 |
// So it can be removed using the original function name.
|
1725 |
// In order to work, a version of the function must already exist in the player/prototype
|
1726 |
Function.prototype.rEvtContext = function(obj, funcParent){ |
1727 |
if (this.hasContext === true) { return this; } |
1728 |
if (!funcParent) { funcParent = obj; }
|
1729 |
for (var attrname in funcParent) { |
1730 |
if (funcParent[attrname] == this) { |
1731 |
funcParent[attrname] = this.evtContext(obj);
|
1732 |
funcParent[attrname].hasContext = true;
|
1733 |
return funcParent[attrname];
|
1734 |
} |
1735 |
} |
1736 |
return this.evtContext(obj); |
1737 |
}; |
1738 |
|
1739 |
// jQuery Plugin
|
1740 |
if (window.jQuery) {
|
1741 |
(function($) { |
1742 |
$.fn.VideoJS = function(options) { |
1743 |
this.each(function() { |
1744 |
VideoJS.setup(this, options);
|
1745 |
}); |
1746 |
return this; |
1747 |
}; |
1748 |
$.fn.player = function() { |
1749 |
return this[0].player; |
1750 |
}; |
1751 |
})(jQuery); |
1752 |
} |
1753 |
|
1754 |
|
1755 |
// Expose to global
|
1756 |
window.VideoJS = window._V_ = VideoJS; |
1757 |
|
1758 |
// End self-executing function
|
1759 |
})(window); |