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