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