1/*! 2Video.js - HTML5 Video Player 3Version 3.2.0 4 5LGPL v3 LICENSE INFO 6This file is part of Video.js. Copyright 2011 Zencoder, Inc. 7 8Video.js is free software: you can redistribute it and/or modify 9it under the terms of the GNU Lesser General Public License as published by 10the Free Software Foundation, either version 3 of the License, or 11(at your option) any later version. 12 13Video.js is distributed in the hope that it will be useful, 14but WITHOUT ANY WARRANTY; without even the implied warranty of 15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16GNU Lesser General Public License for more details. 17 18You should have received a copy of the GNU Lesser General Public License 19along with Video.js. If not, see <http://www.gnu.org/licenses/>. 20*/ 21 22// Self-executing function to prevent global vars and help with minification 23;(function(window, undefined){ 24 var document = window.document;// HTML5 Shiv. Must be in <head> to support older browsers. 25document.createElement("video");document.createElement("audio"); 26 27var VideoJS = function(id, addOptions, ready){ 28 var tag; // Element of ID 29 30 // Allow for element or ID to be passed in 31 // String ID 32 if (typeof id == "string") { 33 34 // Adjust for jQuery ID syntax 35 if (id.indexOf("#") === 0) { 36 id = id.slice(1); 37 } 38 39 // If a player instance has already been created for this ID return it. 40 if (_V_.players[id]) { 41 return _V_.players[id]; 42 43 // Otherwise get element for ID 44 } else { 45 tag = _V_.el(id) 46 } 47 48 // ID is a media element 49 } else { 50 tag = id; 51 } 52 53 // Check for a useable element 54 if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also 55 throw new TypeError("The element or ID supplied is not valid. (VideoJS)"); // Returns 56 } 57 58 // Element may have a player attr referring to an already created player instance. 59 // If not, set up a new player and return the instance. 60 return tag.player || new _V_.Player(tag, addOptions, ready); 61}, 62 63// Shortcut 64_V_ = VideoJS, 65 66// CDN Version. Used to target right flash swf. 67CDN_VERSION = "3.2"; 68 69VideoJS.players = {}; 70 71VideoJS.options = { 72 73 // Default order of fallback technology 74 techOrder: ["html5","flash"], 75 // techOrder: ["flash","html5"], 76 77 html5: {}, 78 flash: { swf: "http://vjs.zencdn.net/c/video-js.swf" }, 79 80 // Default of web browser is 300x150. Should rely on source width/height. 81 width: "auto", 82 height: "auto", 83 84 // defaultVolume: 0.85, 85 defaultVolume: 0.00, // The freakin seaguls are driving me crazy! 86 87 // Included control sets 88 components: { 89 "posterImage": {}, 90 "textTrackDisplay": {}, 91 "loadingSpinner": {}, 92 "bigPlayButton": {}, 93 "controlBar": {} 94 } 95 96 // components: [ 97 // "poster", 98 // "loadingSpinner", 99 // "bigPlayButton", 100 // { name: "controlBar", options: { 101 // components: [ 102 // "playToggle", 103 // "fullscreenToggle", 104 // "currentTimeDisplay", 105 // "timeDivider", 106 // "durationDisplay", 107 // "remainingTimeDisplay", 108 // { name: "progressControl", options: { 109 // components: [ 110 // { name: "seekBar", options: { 111 // components: [ 112 // "loadProgressBar", 113 // "playProgressBar", 114 // "seekHandle" 115 // ]} 116 // } 117 // ]} 118 // }, 119 // { name: "volumeControl", options: { 120 // components: [ 121 // { name: "volumeBar", options: { 122 // components: [ 123 // "volumeLevel", 124 // "volumeHandle" 125 // ]} 126 // } 127 // ]} 128 // }, 129 // "muteToggle" 130 // ] 131 // }}, 132 // "subtitlesDisplay"/*, "replay"*/ 133 // ] 134}; 135 136// Set CDN Version of swf 137if (CDN_VERSION != "GENERATED_CDN_VSN") { 138 _V_.options.flash.swf = "http://vjs.zencdn.net/"+CDN_VERSION+"/video-js.swf" 139}_V_.merge = function(obj1, obj2, safe){ 140 // Make sure second object exists 141 if (!obj2) { obj2 = {}; }; 142 143 for (var attrname in obj2){ 144 if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) { obj1[attrname]=obj2[attrname]; } 145 } 146 return obj1; 147};
148_V_.extend = function(obj){ this.merge(this, obj, true); }; 149 150_V_.extend({ 151 tech: {}, // Holder for playback technology settings 152 controlSets: {}, // Holder for control set definitions 153 154 // Device Checks 155 isIE: function(){ return !+"\v1"; }, 156 isFF: function(){ return !!_V_.ua.match("Firefox") }, 157 isIPad: function(){ return navigator.userAgent.match(/iPad/i) !== null; }, 158 isIPhone: function(){ return navigator.userAgent.match(/iPhone/i) !== null; }, 159 isIOS: function(){ return VideoJS.isIPhone() || VideoJS.isIPad(); }, 160 iOSVersion: function() { 161 var match = navigator.userAgent.match(/OS (\d+)_/i); 162 if (match && match[1]) { return match[1]; } 163 }, 164 isAndroid: function(){ return navigator.userAgent.match(/Android.*AppleWebKit/i) !== null; }, 165 androidVersion: function() { 166 var match = navigator.userAgent.match(/Android (\d+)\./i); 167 if (match && match[1]) { return match[1]; } 168 }, 169 170 testVid: document.createElement("video"), 171 ua: navigator.userAgent, 172 support: {}, 173 174 each: function(arr, fn){ 175 if (!arr || arr.length === 0) { return; } 176 for (var i=0,j=arr.length; i<j; i++) { 177 fn.call(this, arr[i], i); 178 } 179 }, 180 181 eachProp: function(obj, fn){ 182 if (!obj) { return; } 183 for (var name in obj) { 184 if (obj.hasOwnProperty(name)) { 185 fn.call(this, name, obj[name]); 186 } 187 } 188 }, 189 190 el: function(id){ return document.getElementById(id); }, 191 createElement: function(tagName, attributes){ 192 var el = document.createElement(tagName), 193 attrname; 194 for (attrname in attributes){ 195 if (attributes.hasOwnProperty(attrname)) { 196 if (attrname.indexOf("-") !== -1) { 197 el.setAttribute(attrname, attributes[attrname]); 198 } else { 199 el[attrname] = attributes[attrname]; 200 } 201 } 202 } 203 return el; 204 }, 205 206 insertFirst: function(node, parent){ 207 if (parent.firstChild) { 208 parent.insertBefore(node, parent.firstChild); 209 } else { 210 parent.appendChild(node); 211 } 212 }, 213 214 addClass: function(element, classToAdd){ 215 if ((" "+element.className+" ").indexOf(" "+classToAdd+" ") == -1) { 216 element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd; 217 } 218 }, 219 220 removeClass: function(element, classToRemove){ 221 if (element.className.indexOf(classToRemove) == -1) { return; } 222 var classNames = element.className.split(" "); 223 classNames.splice(classNames.indexOf(classToRemove),1); 224 element.className = classNames.join(" "); 225 }, 226 227 remove: function(item, array){ 228 if (!array) return; 229 var i = array.indexOf(item); 230 if (i != -1) { 231 return array.splice(i, 1) 232 }; 233 }, 234 235 // Attempt to block the ability to select text while dragging controls 236 blockTextSelection: function(){ 237 document.body.focus(); 238 document.onselectstart = function () { return false; }; 239 }, 240 // Turn off text selection blocking 241 unblockTextSelection: function(){ document.onselectstart = function () { return true; }; }, 242 243 // Return seconds as H:MM:SS or M:SS 244 // Supplying a guide (in seconds) will include enough leading zeros to cover the length of the guide 245 formatTime: function(seconds, guide) { 246 guide = guide || seconds; // Default to using seconds as guide 247 var s = Math.floor(seconds % 60), 248 m = Math.floor(seconds / 60 % 60), 249 h = Math.floor(seconds / 3600), 250 gm = Math.floor(guide / 60 % 60), 251 gh = Math.floor(guide / 3600); 252 253 // Check if we need to show hours 254 h = (h > 0 || gh > 0) ? h + ":" : ""; 255 256 // If hours are showing, we may need to add a leading zero. 257 // Always show at least one digit of minutes. 258 m = (((h || gm >= 10) && m < 10) ? "0" + m : m) + ":"; 259 260 // Check if leading zero is need for seconds 261 s = (s < 10) ? "0" + s : s; 262 263 return h + m + s; 264 }, 265 266 uc: function(string){ 267 return string.charAt(0).toUpperCase() + string.slice(1); 268 }, 269 270 // Return the relative horizonal position of an event as a value from 0-1 271 getRelativePosition: function(x, relativeElement){ 272 return Math.max(0, Math.min(1, (x - _V_.findPosX(relativeElement)) / relativeElement.offsetWidth)); 273 }, 274
275 getComputedStyleValue: function(element, style){ 276 return window.getComputedStyle(element, null).getPropertyValue(style); 277 }, 278 279 trim: function(string){ return string.toString().replace(/^\s+/, "").replace(/\s+$/, ""); }, 280 round: function(num, dec) { 281 if (!dec) { dec = 0; } 282 return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec); 283 }, 284 285 isEmpty: function(object) { 286 for (var prop in object) { 287 return false; 288 } 289 return true; 290 }, 291 292 // Mimic HTML5 TimeRange Spec. 293 createTimeRange: function(start, end){ 294 return { 295 length: 1, 296 start: function() { return start; }, 297 end: function() { return end; } 298 }; 299 }, 300 301 /* Element Data Store. Allows for binding data to an element without putting it directly on the element. 302 Ex. Event listneres are stored here. 303 (also from jsninja.com) 304 ================================================================================ */ 305 cache: {}, // Where the data is stored 306 guid: 1, // Unique ID for the element 307 expando: "vdata" + (new Date).getTime(), // Unique attribute to store element's guid in 308 309 // Returns the cache object where data for the element is stored 310 getData: function(elem){ 311 var id = elem[_V_.expando]; 312 if (!id) { 313 id = elem[_V_.expando] = _V_.guid++; 314 _V_.cache[id] = {}; 315 } 316 return _V_.cache[id]; 317 }, 318 // Delete data for the element from the cache and the guid attr from element 319 removeData: function(elem){ 320 var id = elem[_V_.expando]; 321 if (!id) { return; } 322 // Remove all stored data 323 delete _V_.cache[id]; 324 // Remove the expando property from the DOM node 325 try { 326 delete elem[_V_.expando]; 327 } catch(e) { 328 if (elem.removeAttribute) { 329 elem.removeAttribute(_V_.expando); 330 } else { 331 // IE doesn't appear to support removeAttribute on the document element 332 elem[_V_.expando] = null; 333 } 334 } 335 }, 336 337 /* Proxy (a.k.a Bind or Context). A simple method for changing the context of a function 338 It also stores a unique id on the function so it can be easily removed from events 339 ================================================================================ */ 340 proxy: function(context, fn, uid) { 341 // Make sure the function has a unique ID 342 if (!fn.guid) { fn.guid = _V_.guid++; } 343 344 // Create the new function that changes the context 345 var ret = function() { 346 return fn.apply(context, arguments); 347 } 348 349 // Allow for the ability to individualize this function 350 // Needed in the case where multiple objects might share the same prototype 351 // IF both items add an event listener with the same function, then you try to remove just one 352 // it will remove both because they both have the same guid. 353 // when using this, you need to use the proxy method when you remove the listener as well. 354 ret.guid = (uid) ? uid + "_" + fn.guid : fn.guid; 355 356 return ret; 357 }, 358 359 get: function(url, onSuccess, onError){ 360 // if (netscape.security.PrivilegeManager.enablePrivilege) { 361 // netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); 362 // } 363 364 var local = (url.indexOf("file:") == 0 || (window.location.href.indexOf("file:") == 0 && url.indexOf("http:") == -1)); 365 366 if (typeof XMLHttpRequest == "undefined") { 367 XMLHttpRequest = function () { 368 try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {} 369 try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (f) {} 370 try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (g) {} 371 throw new Error("This browser does not support XMLHttpRequest."); 372 }; 373 } 374 375 var request = new XMLHttpRequest(); 376 377 try { 378 request.open("GET", url); 379 } catch(e) { 380 _V_.log("VideoJS XMLHttpRequest (open)", e); 381 // onError(e); 382 return false; 383 } 384 385 request.onreadystatechange = _V_.proxy(this, function() { 386 if (request.readyState == 4) { 387 if (request.status == 200 || local && request.status == 0) { 388 onSuccess(request.responseText); 389 } else { 390 if (onError) { 391 onError(); 392 } 393 } 394 } 395 }); 396 397 try { 398 request.send(); 399 } catch(e) { 400 _V_.log("VideoJS XMLHttpRequest (send)", e); 401 if (onError) { 402 onError(e); 403 } 404 } 405 }, 406 407 /* Local Storage 408 ================================================================================ */ 409 setLocalStorage: function(key, value){ 410 // IE was throwing errors referencing the var anywhere without this 411 var localStorage = window.localStorage || false; 412 if (!localStorage) { return; } 413 try { 414 localStorage[key] = value; 415 } catch(e) { 416 if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014 417 _V_.log("LocalStorage Full (VideoJS)", e); 418 } else { 419 _V_.log("LocalStorage Error (VideoJS)", e); 420 } 421 } 422 }, 423 424 // Get abosolute version of relative URL. Used to tell flash correct URL. 425 // http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 426 getAbsoluteURL: function(url){ 427 428 // Check if absolute URL 429 if (!url.match(/^https?:\/\//)) { 430 // Convert to absolute URL. Flash hosted off-site needs an absolute URL. 431 url = _V_.createElement('div', { 432 innerHTML: '<a href="'+url+'">x</a>' 433 }).firstChild.href; 434 } 435 436 return url; 437 } 438 439}); 440 441// usage: log('inside coolFunc', this, arguments); 442// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/ 443_V_.log = function(){ 444 _V_.log.history = _V_.log.history || [];// store logs to an array for reference 445 _V_.log.history.push(arguments); 446 if(window.console) { 447 arguments.callee = arguments.callee.caller; 448 var newarr = [].slice.call(arguments); 449 (typeof console.log === 'object' ? _V_.log.apply.call(console.log, console, newarr) : console.log.apply(console, newarr)); 450 } 451}; 452 453// make it safe to use console.log always 454(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try 455{console.log();return window.console;}catch(err){return window.console={};}})()); 456 457// Offset Left
458// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/ 459if ("getBoundingClientRect" in document.documentElement) { 460 _V_.findPosX = function(el) { 461 var box; 462 463 try { 464 box = el.getBoundingClientRect(); 465 } catch(e) {} 466 467 if (!box) { return 0; } 468 469 var docEl = document.documentElement, 470 body = document.body, 471 clientLeft = docEl.clientLeft || body.clientLeft || 0, 472 scrollLeft = window.pageXOffset || body.scrollLeft, 473 left = box.left + scrollLeft - clientLeft; 474 475 return left; 476 }; 477} else { 478 _V_.findPosX = function(el) { 479 var curleft = el.offsetLeft; 480 // _V_.log(obj.className, obj.offsetLeft) 481 while(el = obj.offsetParent) { 482 if (el.className.indexOf("video-js") == -1) { 483 // _V_.log(el.offsetParent, "OFFSETLEFT", el.offsetLeft) 484 // _V_.log("-webkit-full-screen", el.webkitMatchesSelector("-webkit-full-screen")); 485 // _V_.log("-webkit-full-screen", el.querySelectorAll(".video-js:-webkit-full-screen")); 486 } else { 487 } 488 curleft += el.offsetLeft; 489 } 490 return curleft; 491 }; 492}// Using John Resig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/ 493// (function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; _V_.Class = function(){}; _V_.Class.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 Class() { if ( !initializing && this.init ) this.init.apply(this, arguments); } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class;};})(); 494(function(){ 495 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 496 _V_.Class = function(){}; 497 _V_.Class.extend = function(prop) { 498 var _super = this.prototype; 499 initializing = true; 500 var prototype = new this(); 501 initializing = false; 502 for (var name in prop) { 503 prototype[name] = typeof prop[name] == "function" && 504 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 505 (function(name, fn){ 506 return function() { 507 var tmp = this._super; 508 this._super = _super[name]; 509 var ret = fn.apply(this, arguments); 510 this._super = tmp; 511 return ret; 512 }; 513 })(name, prop[name]) : 514 prop[name]; 515 } 516 function Class() { 517 if ( !initializing && this.init ) { 518 return this.init.apply(this, arguments); 519 520 // Attempting to recreate accessing function form of class. 521 } else if (!initializing) { 522 return arguments.callee.prototype.init() 523 } 524 } 525 Class.prototype = prototype; 526 Class.constructor = Class; 527 Class.extend = arguments.callee; 528 return Class; 529 }; 530})(); 531 532/* Player Component- Base class for all UI objects 533================================================================================ */ 534_V_.Component = _V_.Class.extend({ 535 536 init: function(player, options){ 537 this.player = player; 538 539 // Allow for overridding default component options 540 options = this.options = _V_.merge(this.options || {}, options); 541 542 // Create element if one wasn't provided in options 543 if (options.el) { 544 this.el = options.el; 545 } else { 546 this.el = this.createElement(); 547 } 548 549 // Add any components in options 550 this.initComponents(); 551 }, 552 553 destroy: function(){}, 554 555 createElement: function(type, attrs){ 556 return _V_.createElement(type || "div", attrs); 557 }, 558 559 buildCSSClass: function(){ 560 // Child classes can include a function that does: 561 // return "CLASS NAME" + this._super(); 562 return ""; 563 }, 564 565 initComponents: function(){ 566 var options = this.options; 567 if (options && options.components) { 568 // Loop through components and add them to the player 569 this.eachProp(options.components, function(name, opts){ 570 571 // Allow waiting to add components until a specific event is called 572 var tempAdd = this.proxy(function(){ 573 // Set property name on player. Could cause conflicts with other prop n
573ames, but it's worth making refs easy. 574 this[name] = this.addComponent(name, opts); 575 }); 576 577 if (opts.loadEvent) { 578 this.one(opts.loadEvent, tempAdd) 579 } else { 580 tempAdd(); 581 } 582 }); 583 } 584 }, 585 586 // Add child components to this component. 587 // Will generate a new child component and then append child component's element to this component's element. 588 // Takes either the name of the UI component class, or an object that contains a name, UI Class, and options. 589 addComponent: function(name, options){ 590 var component, componentClass; 591 592 // If string, create new component with options 593 if (typeof name == "string") { 594 595 // Make sure options is at least an empty object to protect against errors 596 options = options || {}; 597 598 // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.) 599 componentClass = options.componentClass || _V_.uc(name); 600 601 // Create a new object & element for this controls set 602 // If there's no .player, this is a player 603 component = new _V_[componentClass](this.player || this, options); 604 605 } else { 606 component = name; 607 } 608 609 // Add the UI object's element to the container div (box) 610 this.el.appendChild(component.el); 611 612 // Return so it can stored on parent object if desired. 613 return component; 614 }, 615 616 removeComponent: function(component){ 617 this.el.removeChild(component.el); 618 }, 619 620 /* Display 621 ================================================================================ */ 622 show: function(){ 623 this.el.style.display = "block"; 624 }, 625 626 hide: function(){ 627 this.el.style.display = "none"; 628 }, 629 630 fadeIn: function(){ 631 this.removeClass("vjs-fade-out"); 632 this.addClass("vjs-fade-in"); 633 }, 634 635 fadeOut: function(){ 636 this.removeClass("vjs-fade-in"); 637 this.addClass("vjs-fade-out"); 638 }, 639 640 lockShowing: function(){ 641 var style = this.el.style; 642 style.display = "block"; 643 style.opacity = 1; 644 style.visiblity = "visible"; 645 }, 646 647 unlockShowing: function(){ 648 var style = this.el.style; 649 style.display = ""; 650 style.opacity = ""; 651 style.visiblity = ""; 652 }, 653 654 addClass: function(classToAdd){ 655 _V_.addClass(this.el, classToAdd); 656 }, 657 658 removeClass: function(classToRemove){ 659 _V_.removeClass(this.el, classToRemove); 660 }, 661 662 /* Events 663 ================================================================================ */ 664 addEvent: function(type, fn, uid){ 665 return _V_.addEvent(this.el, type, _V_.proxy(this, fn)); 666 }, 667 removeEvent: function(type, fn){ 668 return _V_.removeEvent(this.el, type, fn); 669 }, 670 triggerEvent: function(type, e){ 671 return _V_.triggerEvent(this.el, type, e); 672 }, 673 one: function(type, fn) { 674 _V_.one(this.el, type, _V_.proxy(this, fn)); 675 }, 676 677 /* Ready - Trigger functions when component is ready 678 ================================================================================ */ 679 ready: function(fn){ 680 if (!fn) return this; 681 682 if (this.isReady) { 683 fn.call(this); 684 } else { 685 if (this.readyQueue === undefined) { 686 this.readyQueue = []; 687 } 688 this.readyQueue.push(fn); 689 } 690 691 return this; 692 }, 693 694 triggerReady: function(){ 695 this.isReady = true; 696 if (this.readyQueue && this.readyQueue.length > 0) { 697 // Call all functions in ready queue 698 this.each(this.readyQueue, function(fn){ 699 fn.call(this); 700 }); 701 702 // Reset Ready Queue 703 this.readyQueue = []; 704 705 // Allow for using event listeners also, in case you want to do something everytime a source is ready. 706 this.triggerEvent("ready"); 707 } 708 }, 709 710 /* Utility 711 ================================================================================ */ 712 each: function(arr, fn){ _V_.each.call(this, arr, fn); }, 713 714 eachProp: function(obj, fn){ _V_.eachProp.call(this, obj, fn); }, 715 716 extend: function(obj){ _V_.merge(this, obj) }, 717 718 // More easily attach 'this' to functions 719 proxy: function(fn, uid){ return _V_.proxy(this, fn, uid); } 720 721});/* Control - Base class for all control elements 722================================================================================ */ 723_V_.Control = _V_.Component.extend({ 724 725 buildCSSClass: function(){ 726 return "vjs-control " + this._super(); 727 } 728 729}); 730 731/* Control Bar 732================================================================================ */ 733_V_.ControlBar = _V_.Component.extend({ 734 735 options: { 736 loadEvent: "play", 737 components: { 738 "playToggle": {}, 739 "fullscreenToggle": {}, 740 "currentTimeDisplay": {}, 741 "timeDivider": {}, 742 "durationDisplay": {}, 743 "remainingTimeDisplay": {}, 744 "progressControl": {}, 745 "volumeControl": {}, 746 "muteToggle": {} 747 } 748 }, 749 750 init: function(player, options){ 751 this._super(player, options); 752 753 player.addEvent("play", this.proxy(function(){ 754 this.fadeIn(); 755 this.player.addEvent("mouseover", this.proxy(this.fadeIn)); 756 this.player.addEvent("mouseout", this.proxy(this.fadeOut)); 757 })); 758 759 }, 760 761 createElement: function(){ 762 return _V_.createElement("div", { 763 className: "vjs-controls" 764 }); 765 }, 766 767 fadeIn: function(){ 768 this._super(); 769 this.player.triggerEvent("controlsvisible"); 770 }, 771 772 fadeOut: function(){ 773 this._super(); 774 this.player.triggerEvent("controlshidden"); 775 }, 776 777 lockShowing: function(){ 778 this.el.style.opacity = "1"; 779 } 780 781}); 782 783/* Button - Base class for all buttons 784================================================================================ */ 785_V_.Button = _V_.Control.extend({ 786 787 init: function(player, options){ 788 this._super(player, options); 789 790 this.addEvent("click", this.onClick); 791 this.addEvent("focus", this.onFocus); 792 this.addEvent("blur", this.onBlur); 793 }, 794 795 createElement: function(type, attrs){ 796 // Add standard Aria and Tabindex info 797 attrs = _V_.merge({ 798 className: this.buildCSSClass(), 799 innerHTML: '<div><span class="vjs-control-text">' + (this.buttonText || "Need Text") + '</span></div>', 800 role: "button", 801 tabIndex: 0 802 }, attrs); 803 804 return this._super(type, attrs); 805 }, 806 807 // Click - Override with specific functionality for button 808 onClick: function(){}, 809 810 // Focus - Add keyboard functionality to element 811 onFocus: function(){ 812 _V_.addEvent(document, "keyup", _V_.proxy(this, this.onKeyPress)); 813 }, 814 815 // KeyPress (document level) - Trigger click when keys are pressed 816 onKeyPress: function(event){ 817 // Check for space bar (32) or enter (13) keys 818 if (event.which == 32 || event.which == 13) { 819 event.preventDefault(); 820 this.onClick(); 821 } 822 }, 823 824 // Blur - Remove keyboard triggers 825 onBlur: function(){ 826 _V_.removeEvent(document, "keyup", _V_.proxy(this, this.onKeyPress)); 827 } 828 829}); 830 831/* Play Button 832================================================================================ */ 833_V_.PlayButton = _V_.Button.extend({ 834 835 buttonText: "Play", 836 837 buildCSSClass: function(){ 838 return "vjs-play-button " + this._super(); 839 }, 840 841 onClick: function(){ 842 this.player.play(); 843 } 844 845}); 846 847/* Pause Button 848================================================================================ */ 849_V_.PauseButton = _V_.Button.extend({ 850 851 buttonText: "Pause", 852 853 buildCSSClass: function(){ 854 return "vjs-pause-button " + this._super(); 855 }, 856 857 onClick: function(){ 858 this.player.pause(); 859 } 860 861}); 862 863/* Play Toggle - Play or Pause Media 864================================================================================ */ 865_V_.PlayToggle = _V_.Button.extend({ 866 867 buttonText: "Play", 868 869 init: function(player, options){ 870 this._super(player, options); 871 872 player.addEvent("play", _V_.proxy(this, this.onPlay)); 873 player.addEvent("pause", _V_.proxy(this, this.onPause)); 874 }, 875 876 buildCSSClass: function(){ 877 return "vjs-play-control " + this._super(); 878 }, 879 880 // OnClick - Toggle between play and pause 881 onClick: function(){ 882 if (this.player.paused()) { 883 this.player.play(); 884 } else { 885 this.player.pause(); 886 } 887 }, 888 889 // OnPlay - Add the vjs-playing class to the element so it can change appearance 890 onPlay: function(){ 891 _V_.removeClass(this.el, "vjs-paused"); 892 _V_.addClass(this.el, "vjs-playing"); 893 }, 894 895 // OnPause - Add the vjs-paused class to the element so it can change appearance 896 onPause: function(){ 897 _V_.removeClass(this.el, "vjs-playing"); 898 _V_.addClass(this.el, "vjs-paused"); 899 } 900 901}); 902 903 904/* Fullscreen Toggle Behaviors 905================================================================================ */ 906_V_.FullscreenToggle = _V_.Button.extend({ 907 908 buttonText: "Fullscreen", 909 910 buildCSSClass: function(){ 911 return "vjs-fullscreen-control " + this._super(); 912 }, 913 914 onClick: function(){ 915 if (!this.player.isFullScreen) { 916 this.player.requestFullScreen(); 917 } else { 918 this.player.cancelFullScreen(); 919 } 920 } 921 922}); 923 924/* Big Play Button 925================================================================================ */ 926_V_.BigPlayButton = _V_.Button.extend({ 927 init: function(player, options){ 928 this._super(player, options); 929 930 player.addEvent("play", _V_.proxy(this, this.hide)); 931 player.addEvent("ended", _V_.proxy(this, this.show)); 932 }, 933 934 createElement: function(){ 935 return this._super("div", { 936 className: "vjs-big-play-button", 937 innerHTML: "<span></span>" 938 }); 939 }, 940 941 onClick: function(){ 942 // Go back to the beginning if big play button is showing at the end. 943 // Have to check for current time otherwise it might throw a 'not ready' error. 944 if(this.player.currentTime()) { 945 this.player.currentTime(0); 946 } 947 this.player.play(); 948 } 949}); 950 951/* Loading Spinner 952================================================================================ */ 953_V_.LoadingSpinner = _V_.Component.extend({ 954 init: function(player, options){ 955 this._super(player, options); 956 957 player.addEvent("canplay", _V_.proxy(this, this.hide)); 958 player.addEvent("canplaythrough", _V_.proxy(this, this.hide)); 959 player.addEvent("playing", _V_.proxy(this, this.hide)); 960 961 player.addEvent("seeking", _V_.proxy(this, this.show)); 962 player.addEvent("error", _V_.proxy(this, this.show)); 963 964 // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner. 965 // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-s
965howing 966 // player.addEvent("stalled", _V_.proxy(this, this.show)); 967 968 player.addEvent("waiting", _V_.proxy(this, this.show)); 969 }, 970 971 createElement: function(){ 972 973 var classNameSpinner, innerHtmlSpinner; 974 975 if ( typeof this.player.el.style.WebkitBorderRadius == "string" 976 || typeof this.player.el.style.MozBorderRadius == "string" 977 || typeof this.player.el.style.KhtmlBorderRadius == "string" 978 || typeof this.player.el.style.borderRadius == "string") 979 { 980 classNameSpinner = "vjs-loading-spinner"; 981 innerHtmlSpinner = "<div class='ball1'></div><div class='ball2'></div><div class='ball3'></div><div class='ball4'></div><div class='ball5'></div><div class='ball6'></div><div class='ball7'></div><div class='ball8'></div>"; 982 } else { 983 classNameSpinner = "vjs-loading-spinner-fallback"; 984 innerHtmlSpinner = ""; 985 } 986 987 return this._super("div", { 988 className: classNameSpinner, 989 innerHTML: innerHtmlSpinner 990 }); 991 } 992}); 993 994/* Time 995================================================================================ */ 996_V_.CurrentTimeDisplay = _V_.Component.extend({ 997 998 init: function(player, options){ 999 this._super(player, options); 1000 1001 player.addEvent("timeupdate", _V_.proxy(this, this.updateContent)); 1002 }, 1003 1004 createElement: function(){ 1005 var el = this._super("div", { 1006 className: "vjs-current-time vjs-time-controls vjs-control" 1007 }); 1008 1009 this.content = _V_.createElement("div", { 1010 className: "vjs-current-time-display", 1011 innerHTML: '0:00' 1012 }); 1013 1014 el.appendChild(_V_.createElement("div").appendChild(this.content)); 1015 return el; 1016 }, 1017 1018 updateContent: function(){ 1019 // Allows for smooth scrubbing, when player can't keep up. 1020 var time = (this.player.scrubbing) ? this.player.values.currentTime : this.player.currentTime(); 1021 this.content.innerHTML = _V_.formatTime(time, this.player.duration()); 1022 } 1023 1024}); 1025 1026_V_.DurationDisplay = _V_.Component.extend({ 1027 1028 init: function(player, options){ 1029 this._super(player, options); 1030 1031 player.addEvent("timeupdate", _V_.proxy(this, this.updateContent)); 1032 }, 1033 1034 createElement: function(){ 1035 var el = this._super("div", { 1036 className: "vjs-duration vjs-time-controls vjs-control" 1037 }); 1038 1039 this.content = _V_.createElement("div", { 1040 className: "vjs-duration-display", 1041 innerHTML: '0:00' 1042 }); 1043 1044 el.appendChild(_V_.createElement("div").appendChild(this.content)); 1045 return el; 1046 }, 1047 1048 updateContent: function(){ 1049 if (this.player.duration()) { this.content.innerHTML = _V_.formatTime(this.player.duration()); } 1050 } 1051 1052}); 1053 1054// Time Separator (Not used in main skin, but still available, and could be used as a 'spare element') 1055_V_.TimeDivider = _V_.Component.extend({ 1056 1057 createElement: function(){ 1058 return this._super("div", { 1059 className: "vjs-time-divider", 1060 innerHTML: '<div><span>/</span></div>' 1061 }); 1062 } 1063 1064}); 1065 1066_V_.RemainingTimeDisplay = _V_.Component.extend({ 1067 1068 init: function(player, options){ 1069 this._super(player, options); 1070 1071 player.addEvent("timeupdate", _V_.proxy(this, this.updateContent)); 1072 }, 1073 1074 createElement: function(){ 1075 var el = this._super("div", { 1076 className: "vjs-remaining-time vjs-time-controls vjs-control" 1077 }); 1078 1079 this.content = _V_.createElement("div", { 1080 className: "vjs-remaining-time-display", 1081 innerHTML: '-0:00' 1082 }); 1083 1084 el.appendChild(_V_.createElement("div").appendChild(this.content)); 1085 return el; 1086 }, 1087 1088 updateContent: function(){ 1089 if (this.player.duration()) { this.content.innerHTML = "-"+_V_.formatTime(this.player.remainingTime()); } 1090 1091 // Allows for smooth scrubbing, when player can't keep up. 1092 // var time = (this.player.scrubbing) ? this.player.values.currentTime : this.player.currentTime(); 1093 // this.content.innerHTML = _V_.formatTime(time, this.player.duration()); 1094 } 1095 1096}); 1097 1098/* Slider - Parent for seek bar and volume slider 1099================================================================================ */ 1100_V_.Slider = _V_.Component.extend({ 1101 1102 init: function(player, options){ 1103 this._super(player, options); 1104 1105 player.addEvent(this.playerEvent, _V_.proxy(this, this.update)); 1106 1107 this.addEvent("mousedown", this.onMouseDown); 1108 this.addEvent("focus", this.onFocus); 1109 this.addEvent("blur", this.onBlur); 1110 1111 this.player.addEvent("controlsvisible", this.proxy(this.update)); 1112 1113 // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520 1114 // this.player.one("timeupdate", this.proxy(this.update)); 1115 1116 this.update(); 1117 }, 1118 1119 createElement: function(type, attrs) { 1120 attrs = _V_.merge({ 1121 role: "slider", 1122 "aria-valuenow": 0, 1123 "aria-valuemin": 0, 1124 "aria-valuemax": 100, 1125 tabIndex: 0 1126 }, attrs); 1127 1128 return this._super(type, attrs); 1129 }, 1130 1131 onMouseDown: function(event){ 1132 event.preventDefault(); 1133 _V_.blockTextSelection(); 1134 1135 _V_.addEvent(document, "mousemove", _V_.proxy(this, this.onMouseMove)); 1136 _V_.addEvent(document, "mouseup", _V_.proxy(this, this.onMouseUp)); 1137 1138 this.onMouseMove(event); 1139 }, 1140 1141 onMouseUp: function(event) { 1142 _V_.unblockTextSelection(); 1143 _V_.removeEvent(document, "mousemove", this.onMouseMove, false); 1144 _V_.removeEvent(document, "mouseup", this.onMouseUp, false); 1145 1146 this.update(); 1147 }, 1148 1149 update: function(){ 1150 // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse. 1151 // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later. 1152 // var progress = (this.player.scrubbing) ? this.player.values.currentTime / this.player.duration() : this.player.currentTime() / this.player.duration(); 1153 1154 var barProgress, 1155 progress = this.getPercent(); 1156 handle = this.handle, 1157 bar = this.bar; 1158 1159 // Protect against no duration and other division issues 1160 if (isNaN(progress)) { progress = 0; } 1161 1162 barProgress = progress; 1163 1164 // If there is a handle, we need to account for the handle in our
1164calculation for progress bar 1165 // so that it doesn't fall short of or extend past the handle. 1166 if (handle) { 1167 1168 var box = this.el, 1169 boxWidth = box.offsetWidth, 1170 1171 handleWidth = handle.el.offsetWidth, 1172 1173 // The width of the handle in percent of the containing box 1174 // In IE, widths may not be ready yet causing NaN 1175 handlePercent = (handleWidth) ? handleWidth / boxWidth : 0, 1176 1177 // Get the adjusted size of the box, considering that the handle's center never touches the left or right side. 1178 // There is a margin of half the handle's width on both sides. 1179 boxAdjustedPercent = 1 - handlePercent; 1180 1181 // Adjust the progress that we'll use to set widths to the new adjusted box width 1182 adjustedProgress = progress * boxAdjustedPercent, 1183 1184 // The bar does reach the left side, so we need to account for this in the bar's width 1185 barProgress = adjustedProgress + (handlePercent / 2); 1186 1187 // Move the handle from the left based on the adjected progress 1188 handle.el.style.left = _V_.round(adjustedProgress * 100, 2) + "%"; 1189 } 1190 1191 // Set the new bar width 1192 bar.el.style.width = _V_.round(barProgress * 100, 2) + "%"; 1193 }, 1194 1195 calculateDistance: function(event){ 1196 var box = this.el, 1197 boxX = _V_.findPosX(box), 1198 boxW = box.offsetWidth, 1199 handle = this.handle; 1200 1201 if (handle) { 1202 var handleW = handle.el.offsetWidth; 1203 1204 // Adjusted X and Width, so handle doesn't go outside the bar 1205 boxX = boxX + (handleW / 2); 1206 boxW = boxW - handleW; 1207 } 1208 1209 // Percent that the click is through the adjusted area 1210 return Math.max(0, Math.min(1, (event.pageX - boxX) / boxW)); 1211 }, 1212 1213 onFocus: function(event){ 1214 _V_.addEvent(document, "keyup", _V_.proxy(this, this.onKeyPress)); 1215 }, 1216 1217 onKeyPress: function(event){ 1218 if (event.which == 37) { // Left Arrow 1219 event.preventDefault(); 1220 this.stepBack(); 1221 } else if (event.which == 39) { // Right Arrow 1222 event.preventDefault(); 1223 this.stepForward(); 1224 } 1225 }, 1226 1227 onBlur: function(event){ 1228 _V_.removeEvent(document, "keyup", _V_.proxy(this, this.onKeyPress)); 1229 } 1230}); 1231 1232 1233/* Progress 1234================================================================================ */ 1235 1236// Progress Control: Seek, Load Progress, and Play Progress 1237_V_.ProgressControl = _V_.Component.extend({ 1238 1239 options: { 1240 components: { 1241 "seekBar": {} 1242 } 1243 }, 1244 1245 createElement: function(){ 1246 return this._super("div", { 1247 className: "vjs-progress-control vjs-control" 1248 }); 1249 } 1250 1251}); 1252 1253// Seek Bar and holder for the progress bars 1254_V_.SeekBar = _V_.Slider.extend({ 1255 1256 options: { 1257 components: { 1258 "loadProgressBar": {}, 1259 1260 // Set property names to bar and handle to match with the parent Slider class is looking for 1261 "bar": { componentClass: "PlayProgressBar" }, 1262 "handle": { componentClass: "SeekHandle" } 1263 } 1264 }, 1265 1266 playerEvent: "timeupdate", 1267 1268 init: function(player, options){ 1269 this._super(player, options); 1270 }, 1271 1272 createElement: function(){ 1273 return this._super("div", { 1274 className: "vjs-progress-holder" 1275 }); 1276 }, 1277 1278 getPercent: function(){ 1279 return this.player.currentTime() / this.player.duration(); 1280 }, 1281 1282 onMouseDown: function(event){ 1283 this._super(event); 1284 1285 this.player.scrubbing = true; 1286 1287 this.videoWasPlaying = !this.player.paused(); 1288 this.player.pause(); 1289 }, 1290 1291 onMouseMove: function(event){ 1292 var newTime = this.calculateDistance(event) * this.player.duration(); 1293 1294 // Don't let video end while scrubbing. 1295 if (newTime == this.player.duration()) { newTime = newTime - 0.1; } 1296 1297 // Set new time (tell player to seek to new time) 1298 this.player.currentTime(newTime); 1299 }, 1300 1301 onMouseUp: function(event){ 1302 this._super(event); 1303 1304 this.player.scrubbing = false; 1305 if (this.videoWasPlaying) { 1306 this.player.play(); 1307 } 1308 }, 1309 1310 stepForward: function(){ 1311 this.player.currentTime(this.player.currentTime() + 1); 1312 }, 1313 1314 stepBack: function(){ 1315 this.player.currentTime(this.player.currentTime() - 1); 1316 } 1317 1318}); 1319 1320// Load Progress Bar 1321_V_.LoadProgressBar = _V_.Component.extend({ 1322 1323 init: function(player, options){ 1324 this._super(player, options); 1325 player.addEvent("progress", _V_.proxy(this, this.update)); 1326 }, 1327 1328 createElement: function(){ 1329 return this._super("div", { 1330 className: "vjs-load-progress", 1331 innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>' 1332 }); 1333 }, 1334 1335 update: function(){ 1336 if (this.el.style) { this.el.style.width = _V_.round(this.player.bufferedPercent() * 100, 2) + "%"; } 1337 } 1338 1339}); 1340 1341// Play Progress Bar 1342_V_.PlayProgressBar = _V_.Component.extend({ 1343 1344 createElement: function(){ 1345 return this._super("div", { 1346 className: "vjs-play-progress", 1347 innerHTML: '<span class="vjs-control-text">Progress: 0%</span>' 1348 }); 1349 } 1350 1351}); 1352 1353// Seek Handle 1354// SeekBar Behavior includes play progress bar, and seek handle 1355// Needed so it can determine seek position based on handle position/size 1356_V_.SeekHandle = _V_.Component.extend({ 1357 1358 createElement: function(){ 1359 return this._super("div", { 1360 className: "vjs-seek-handle", 1361 innerHTML: '<span class="vjs-control-text">00:00</span>' 1362 }); 1363 } 1364 1365}); 1366 1367/* Volume Scrubber 1368================================================================================ */ 1369// Progress Control: Seek, Load Progress, and Play Progress 1370_V_.VolumeControl = _V_.Component.extend({ 1371 1372 options: { 1373 components: { 1374 "volumeBar": {} 1375 } 1376 }, 1377 1378 createElement: function(){ 1379 return this._super("div", { 1380 className: "vjs-volume-control vjs-control" 1381 }); 1382 } 1383 1384}); 1385 1386_V_.VolumeBar = _V_.Slider.extend({ 1387 1388 options: { 1389 components: { 1390 "bar": { componentClass: "VolumeLevel" }, 1391 "handle": { componentClass: "VolumeHandle" } 1392 } 1393 }, 1394 1395 playerEvent: "volumechange", 1396 1397 createElement: function(){ 1398 return this._super("div", { 1399 className: "vjs-volume-bar" 1400 }); 1401 }, 1402 1403 onMouseMove: function(event) { 1404 this.player.volume(this.calculateDistance(event)); 1405 }, 1406 1407 getPercent: function(){ 1408 return this.player.volume(); 1409 }, 1410 1411 stepForward: function(){ 1412 this.player.volume(this.player.volume() + 0.1); 1413 }, 1414 1415 stepBack: function(){ 1416 this.player.volume(this.player.volume() - 0.1); 1417 } 1418}); 1419 1420_V_.VolumeLevel = _V_.Component.extend({ 1421 1422 createElement: function(){ 1423 return this._super("div", { 1424 className: "vjs-volume-level", 1425 innerHTML: '<span class="vjs-control-text"></span>' 1426 }); 1427 } 1428 1429}); 1430 1431_V_.VolumeHandle = _V_.Component.extend({ 1432 1433 createElement: function(){ 1434 return this._super("div", { 1435 className: "vjs-volume-handle", 1436 innerHTML: '<span class="vjs-control-text"></span>' 1437 // tabindex: 0, 1438 // role: "slider", "aria-valuenow": 0, "aria-valuemin": 0, "aria-valuemax": 100 1439 }); 1440 } 1441 1442}); 1443 1444_V_.MuteToggle = _V_.Button.extend({ 1445 1446 init: function(player, options){ 1447 this._super(player, options); 1448 1449 player.addEvent("volumechange", _V_.proxy(this, this.update)); 1450 }, 1451 1452 createElement: function(){ 1453 return this._super("div", { 1454 className: "vjs-mute-control vjs-control", 1455 innerHTML: '<div><span class="vjs-control-text">Mute</span></div>' 1456 }); 1457 }, 1458 1459 onClick: function(event){ 1460 this.player.muted( this.player.muted() ? false : true ); 1461 }, 1462 1463 update: function(event){ 1464 var vol = this.player.volume(), 1465 level = 3; 1466 1467 if (vol == 0 || this.player.muted()) { 1468 level = 0; 1469 } else if (vol < 0.33) { 1470 level = 1; 1471 } else if (vol < 0.67) { 1472 level = 2; 1473 } 1474 1475 /* TODO improve muted icon classes */ 1476 _V_.each.call(this, [0,1,2,3], function(i){ 1477 _V_.removeClass(this.el, "vjs-vol-"+i); 1478 }); 1479 _V_.addClass(this.el, "vjs-vol-"+level); 1480 } 1481 1482}); 1483 1484 1485/* Poster Image 1486================================================================================ */ 1487_V_.PosterImage = _V_.Button.extend({ 1488 init: function(player, options){ 1489 this._super(player, options); 1490 1491 if (!this.player.options.poster) { 1492 this.hide(); 1493 } 1494 1495 player.addEvent("play", _V_.proxy(this, this.hide)); 1496 }, 1497 1498 createElement: function(){ 1499 return _V_.createElement("img", { 1500 className: "vjs-poster", 1501 src: this.player.options.poster, 1502 1503 // Don't want poster to be tabbable. 1504 tabIndex: -1 1505 }); 1506 }, 1507 1508 onClick: function(){ 1509 this.player.play(); 1510 } 1511}); 1512
1513/* Menu 1514================================================================================ */ 1515// The base for text track and settings menu buttons. 1516_V_.Menu = _V_.Component.extend({ 1517 1518 init: function(player, options){ 1519 this._super(player, options); 1520 }, 1521 1522 addItem: function(component){ 1523 this.addComponent(component); 1524 component.addEvent("click", this.proxy(function(){ 1525 this.unlockShowing(); 1526 })); 1527 }, 1528 1529 createElement: function(){ 1530 return this._super("ul", { 1531 className: "vjs-menu" 1532 }); 1533 } 1534 1535}); 1536 1537_V_.MenuItem = _V_.Button.extend({ 1538 1539 init: function(player, options){ 1540 this._super(player, options); 1541 1542 if (options.selected) { 1543 this.addClass("vjs-selected"); 1544 } 1545 }, 1546 1547 createElement: function(type, attrs){ 1548 return this._super("li", _V_.merge({ 1549 className: "vjs-menu-item", 1550 innerHTML: this.options.label 1551 }, attrs)); 1552 }, 1553 1554 onClick: function(){ 1555 this.selected(true); 1556 }, 1557 1558 selected: function(selected){ 1559 if (selected) { 1560 this.addClass("vjs-selected"); 1561 } else { 1562 this.removeClass("vjs-selected") 1563 } 1564 } 1565 1566});// ECMA-262 is the standard for javascript. 1567// The following methods are impelemented EXACTLY as described in the standard (according to Mozilla Docs), and do not override the default method if one exists. 1568// This may conflict with other libraries that modify the array prototype, but those libs should update to use the standard. 1569 1570// [].indexOf 1571// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf 1572if (!Array.prototype.indexOf) { 1573 Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { 1574 "use strict"; 1575 if (this === void 0 || this === null) { 1576 throw new TypeError(); 1577 } 1578 var t = Object(this); 1579 var len = t.length >>> 0; 1580 if (len === 0) { 1581 return -1; 1582 } 1583 var n = 0; 1584 if (arguments.length > 0) { 1585 n = Number(arguments[1]); 1586 if (n !== n) { // shortcut for verifying if it's NaN 1587 n = 0; 1588 } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { 1589 n = (n > 0 || -1) * Math.floor(Math.abs(n)); 1590 } 1591 } 1592 if (n >= len) { 1593 return -1; 1594 } 1595 var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); 1596 for (; k < len; k++) { 1597 if (k in t && t[k] === searchElement) { 1598 return k; 1599 } 1600 } 1601 return -1; 1602 } 1603} 1604 1605// NOT NEEDED YET 1606// [].lastIndexOf 1607// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf 1608// if (!Array.prototype.lastIndexOf) 1609// { 1610// Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) 1611// { 1612// "use strict";
1613// 1614// if (this === void 0 || this === null) 1615// throw new TypeError(); 1616// 1617// var t = Object(this); 1618// var len = t.length >>> 0; 1619// if (len === 0) 1620// return -1; 1621// 1622// var n = len; 1623// if (arguments.length > 1) 1624// { 1625// n = Number(arguments[1]); 1626// if (n !== n) 1627// n = 0; 1628// else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) 1629// n = (n > 0 || -1) * Math.floor(Math.abs(n)); 1630// } 1631// 1632// var k = n >= 0 1633// ? Math.min(n, len - 1) 1634// : len - Math.abs(n); 1635// 1636// for (; k >= 0; k--) 1637// { 1638// if (k in t && t[k] === searchElement) 1639// return k; 1640// } 1641// return -1; 1642// }; 1643// } 1644 1645 1646// NOT NEEDED YET 1647// Array forEach per ECMA standard https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach 1648// Production steps of ECMA-262, Edition 5, 15.4.4.18 1649// Reference: http://es5.github.com/#x15.4.4.18 1650// if ( !Array.prototype.forEach ) { 1651// 1652// Array.prototype.forEach = function( callback, thisArg ) { 1653// 1654// var T, k; 1655// 1656// if ( this == null ) { 1657// throw new TypeError( " this is null or not defined" ); 1658// } 1659// 1660// // 1. Let O be the result of calling ToObject passing the |this| value as the argument. 1661// var O = Object(this); 1662// 1663// // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". 1664// // 3. Let len be ToUint32(lenValue). 1665// var len = O.length >>> 0; 1666// 1667// // 4. If IsCallable(callback) is false, throw a TypeError exception. 1668// // See: http://es5.github.com/#x9.11 1669// if ( {}.toString.call(callback) != "[object Function]" ) { 1670// throw new TypeError( callback + " is not a function" ); 1671// } 1672// 1673// // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. 1674// if ( thisArg ) { 1675// T = thisArg; 1676// } 1677// 1678// // 6. Let k be 0 1679// k = 0; 1680// 1681// // 7. Repeat, while k < len 1682// while( k < len ) { 1683// 1684// var kValue; 1685// 1686// // a. Let Pk be ToString(k). 1687// // This is implicit for LHS operands of the in operator 1688// // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. 1689// // This step can be combined with c 1690// // c. If kPresent is true, then 1691// if ( k in O ) { 1692// 1693// // i. Let kValue be the result of calling the Get internal method of O with argument Pk. 1694// kValue = O[ Pk ]; 1695// 1696// // ii. Call the Call internal method of callback with T as the this value and 1697// // argument list containing kValue, k, and O. 1698// callback.call( T, kValue, k, O ); 1699// } 1700// // d. Increase k by 1. 1701// k++; 1702// } 1703// // 8. return undefined 1704// }; 1705// } 1706 1707 1708// NOT NEEDED YET 1709// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map 1710// Production steps of ECMA-262, Edition 5, 15.4.4.19 1711// Reference: http://es5.github.com/#x15.4.4.19 1712// if (!Array.prototype.map) { 1713// Array.prototype.map = function(callback, thisArg) { 1714// 1715// var T, A, k; 1716// 1717// if (this == null) { 1718// throw new TypeError(" this is null or not defined"); 1719// } 1720// 1721// // 1. Let O be the result of calling ToObject passing the |this| value as the argument. 1722// var O = Object(this); 1723// 1724// // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". 1725// // 3. Let len be ToUint32(lenValue). 1726// var len = O.length >>> 0; 1727// 1728// // 4. If IsCallable(callback) is false, throw a TypeError exception. 1729// // See: http://es5.github.com/#x9.11 1730// if ({}.toString.call(callback) != "[object Function]") { 1731// throw new TypeError(callback + " is not a function"); 1732// } 1733// 1734// // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. 1735// if (thisArg) { 1736// T = thisArg; 1737// } 1738// 1739// // 6. Let A be a new array created as if by the expression new Array(len) where Array is 1740// // the standard built-in constructor with that name and len is the value of len. 1741// A = new Array(len); 1742// 1743// // 7. Let k be 0 1744// k = 0; 1745// 1746// // 8. Repeat, while k < len 1747// while(k < len) { 1748// 1749// var kValue, mappedValue; 1750// 1751// // a. Let Pk be ToString(k). 1752// // This is implicit for LHS operands of the in operator
1753// // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. 1754// // This step can be combined with c 1755// // c. If kPresent is true, then 1756// if (k in O) { 1757// 1758// // i. Let kValue be the result of calling the Get internal method of O with argument Pk. 1759// kValue = O[ k ]; 1760// 1761// // ii. Let mappedValue be the result of calling the Call internal method of callback 1762// // with T as the this value and argument list containing kValue, k, and O. 1763// mappedValue = callback.call(T, kValue, k, O); 1764// 1765// // iii. Call the DefineOwnProperty internal method of A with arguments 1766// // Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true}, 1767// // and false. 1768// 1769// // In browsers that support Object.defineProperty, use the following: 1770// // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true }); 1771// 1772// // For best browser support, use the following: 1773// A[ k ] = mappedValue; 1774// } 1775// // d. Increase k by 1. 1776// k++; 1777// } 1778// 1779// // 9. return A 1780// return A; 1781// }; 1782// } 1783// Event System (J.Resig - Secrets of a JS Ninja http://jsninja.com/ [Go read it, really]) 1784// (Book version isn't completely usable, so fixed some things and borrowed from jQuery where it's working) 1785// 1786// This should work very similarly to jQuery's events, however it's based off the book version which isn't as 1787// robust as jquery's, so there's probably some differences. 1788// 1789// When you add an event listener using _V_.addEvent, 1790// it stores the handler function in seperate cache object, 1791// and adds a generic handler to the element's event, 1792// along with a unique id (guid) to the element. 1793 1794_V_.extend({ 1795 1796 // Add an event listener to element 1797 // It stores the handler function in a separate cache object 1798 // and adds a generic handler to the element's event, 1799 // along with a unique id (guid) to the element. 1800 addEvent: function(elem, type, fn){ 1801 var data = _V_.getData(elem), handlers; 1802 1803 // We only need to generate one handler per element 1804 if (data && !data.handler) { 1805 // Our new meta-handler that fixes the event object and the context 1806 data.handler = function(event){ 1807 event = _V_.fixEvent(event); 1808 var handlers = _V_.getData(elem).events[event.type]; 1809 // Go through and call all the real bound handlers 1810 if (handlers) { 1811 1812 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. 1813 var handlersCopy = []; 1814 _V_.each(handlers, function(handler, i){ 1815 handlersCopy[i] = handler; 1816 }) 1817 1818 for (var i = 0, l = handlersCopy.length; i < l; i++) { 1819 handlersCopy[i].call(elem, event); 1820 } 1821 } 1822 }; 1823 } 1824 1825 // We need a place to store all our event data 1826 if (!data.events) { data.events = {}; } 1827 1828 // And a place to store the handlers for this event type 1829 handlers = data.events[type]; 1830 1831 if (!handlers) { 1832 handlers = data.events[ type ] = []; 1833 1834 // Attach our meta-handler to the element, since one doesn't exist 1835 if (document.addEventListener) { 1836 elem.addEventListener(type, data.handler, false); 1837 } else if (document.attachEvent) { 1838 elem.attachEvent("on" + type, data.handler); 1839 } 1840 } 1841 1842 if (!fn.guid) { fn.guid = _V_.guid++; } 1843 1844 handlers.push(fn); 1845 }, 1846 1847 removeEvent: function(elem, type, fn) { 1848 var data = _V_.getData(elem), handlers; 1849 // If no events exist, nothing to unbind 1850 if (!data.events) { return; } 1851 1852 // Are we removing all bound events? 1853 if (!type) { 1854 for (type in data.events) { 1855 _V_.cleanUpEvents(elem, type); 1856 } 1857 return; 1858 } 1859 1860 // And a place to store the handlers for this event type 1861 handlers = data.events[type]; 1862 1863 // If no handlers exist, nothing to unbind 1864 if (!handlers) { return; } 1865 1866 // See if we're only removing a single handler 1867 if (fn && fn.guid) { 1868 for (var i = 0; i < handlers.length; i++) { 1869 // We found a match (don't stop here, there could be a couple bound) 1870 if (handlers[i].guid === fn.guid) { 1871 // Remove the handler from the array of handlers 1872 handlers.splice(i--, 1); 1873 } 1874 } 1875 } 1876 1877 _V_.cleanUpEvents(elem, type); 1878 }, 1879 1880 cleanUpEvents: function(elem, type) { 1881 var data = _V_.getData(elem); 1882 // Remove the events of a particular type if there are none left 1883 1884 if (data.events[type].length === 0) { 1885 delete data.events[type]; 1886 1887 // Remove the meta-handler from the element 1888 if (document.removeEventListener) { 1889 elem.removeEventListener(type, data.handler, false); 1890 } else if (document.detachEvent) { 1891 elem.detachEvent("on" + type, data.handler); 1892 } 1893 } 1894 1895 // Remove the events object if there are no types left 1896 if (_V_.isEmpty(data.events)) { 1897 delete data.events; 1898 delete data.handler; 1899 } 1900 1901 // Finally remove the expando if there is no data left 1902 if (_V_.isEmpty(data)) { 1903 _V_.removeData(elem); 1904 } 1905 }, 1906 1907 fixEvent: function(event) { 1908 if (event[_V_.expando]) { return event; } 1909 // store a copy of the original event object 1910 // and "clone" to set read-only properties 1911 var originalEvent = event; 1912 event = new _V_.Event(originalEvent); 1913 1914 for ( var i = _V_.Event.props.length, prop; i; ) { 1915 prop = _V_.Event.props[ --i ]; 1916 event[prop] = originalEvent[prop]; 1917 } 1918 1919 // Fix target property, if necessary 1920 if (!event.target) { event.target = event.srcElement || document; } 1921 1922 // check if target is a textnode (safari) 1923 if (event.target.nodeType === 3) { event.target = event.target.parentNode; } 1924 1925 // Add relatedTarget, if necessary 1926 if (!event.relatedTarget && event.fromElement) { 1927 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 1928 } 1929 1930 // Calculate pageX/Y if missing and clientX/Y available 1931 if ( event.pageX == null && event.clientX != null ) { 1932 var eventDocument = event.target.ownerDocument || document, 1933 doc = eventDocument.documentElement, 1934 body = eventDocument.body; 1935 1936 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 1937 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 1938 } 1939 1940 // Add which for key events 1941 if (event.which == null && (event.charCode != null || event.keyCode != null)) { 1942 event.which = event.charCode != null ? event.charCode : event.keyCode; 1943 } 1944 1945 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) 1946 if ( !event.metaKey && event.ctrlKey ) { 1947 event.metaKey = event.ctrlKey; 1948 } 1949 1950 // Add which for click: 1 === left; 2 === middle; 3 === right 1951 // Note: button is not normalized, so don't use it 1952 if ( !event.which && event.button !== undefined ) { 1953 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); 1954 } 1955 1956 return event; 1957 }, 1958 1959 triggerEvent: function(elem, event) { 1960 var data = _V_.getData(elem), 1961 parent = elem.parentNode || elem.ownerDocument, 1962 type = event.type || event, 1963 handler; 1964 1965 if (data) { handler = data.handler } 1966 1967 // Added in attion to book. Book code was broke. 1968 event = typeof event === "object" ? 1969 event[_V_.expando] ? 1970 event : 1971 new _V_.Event(type, event) : 1972 new _V_.Event(type); 1973 1974 event.type = type; 1975 if (handler) { 1976 handler.call(elem, event); 1977 } 1978 1979 // Clean up the event in case it is being reused 1980 event.result = undefined; 1981 event.target = elem; 1982 1983 // Bubble the event up the tree to the document, 1984 // Unless it's been explicitly stopped 1985 // if (parent && !event.isPropagationStopped()) { 1986 // _V_.triggerEvent(parent, event); 1987 // 1988 // // We're at the top document so trigger the default action 1989 // } else if (!parent && !event.isDefaultPrevented()) { 1990 // // log(type); 1991 // var targetData = _V_.getData(event.target); 1992 // // log(targetData); 1993 // var targetHandler = targetData.handler; 1994 // // log("2"); 1995 // if (event.target[event.type]) { 1996 // // Temporarily disable the bound handler, 1997 // // don't want to execute it twice 1998 // if (targetHandler) { 1999 // targetData.handler = function(){}; 2000 // } 2001 // 2002 // // Trigger the native event (click, focus, blur) 2003 // event.target[event.type](); 2004 // 2005 // // Restore the handler 2006 // if (targetHandler) { 2007 // targetData.handler = targetHandler; 2008 // } 2009 // } 2010 // } 2011 }, 2012 2013 one: function(elem, type, fn) { 2014 _V_.addEvent(elem, type, function(){ 2015 _V_.removeEvent(elem, type, arguments.callee) 2016 fn.apply(this, arguments); 2017 }); 2018 } 2019}); 2020 2021// Custom Event object for standardizing event objects between browsers. 2022_V_.Event = function(src, props){ 2023 // Event object 2024 if (src && src.type) { 2025 this.originalEvent = src; 2026 this.type = src.type; 2027 2028 // Events bubbling up the document may have been marked as prevented 2029 // by a handler lower down the tree; reflect the correct value. 2030 this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || 2031 src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; 2032 2033 // Event type 2034 } else { 2035 this.type = src; 2036 } 2037 2038 // Put explicitly provided properties onto the event object 2039 if (props) { _V_.merge(this, props); } 2040 2041 this.timeStamp = (new Date).getTime(); 2042 2043 // Mark it as fixed 2044 this[_V_.expando] = true; 2045}; 2046 2047_V_.Event.prototype = { 2048 preventDefault: function() { 2049 this.isDefaultPrevented = returnTrue; 2050 2051 var e = this.originalEvent; 2052 if (!e) { return; } 2053 2054 // if preventDefault exists run it on the original event 2055 if (e.preventDefault) { 2056 e.preventDefault(); 2057 // otherwise set the returnValue property of the original event to false (IE) 2058 } else { 2059 e.returnValue = false;
2060 } 2061 }, 2062 stopPropagation: function() { 2063 this.isPropagationStopped = returnTrue; 2064 2065 var e = this.originalEvent; 2066 if (!e) { return; } 2067 // if stopPropagation exists run it on the original event 2068 if (e.stopPropagation) { e.stopPropagation(); } 2069 // otherwise set the cancelBubble property of the original event to true (IE) 2070 e.cancelBubble = true; 2071 }, 2072 stopImmediatePropagation: function() { 2073 this.isImmediatePropagationStopped = returnTrue; 2074 this.stopPropagation(); 2075 }, 2076 isDefaultPrevented: returnFalse, 2077 isPropagationStopped: returnFalse, 2078 isImmediatePropagationStopped: returnFalse 2079}; 2080_V_.Event.props = "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "); 2081 2082function returnTrue(){ return true; } 2083function returnFalse(){ return false; } 2084 2085// Javascript JSON implementation 2086// (Parse Method Only) 2087// https://github.com/douglascrockford/JSON-js/blob/master/json2.js 2088 2089var JSON; 2090if (!JSON) { JSON = {}; } 2091 2092(function(){ 2093 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; 2094 2095 if (typeof JSON.parse !== 'function') { 2096 JSON.parse = function (text, reviver) { 2097 var j; 2098 2099 function walk(holder, key) { 2100 var k, v, value = holder[key]; 2101 if (value && typeof value === 'object') { 2102 for (k in value) { 2103 if (Object.prototype.hasOwnProperty.call(value, k)) { 2104 v = walk(value, k); 2105 if (v !== undefined) { 2106 value[k] = v; 2107 } else { 2108 delete value[k]; 2109 } 2110 } 2111 } 2112 } 2113 return reviver.call(holder, key, value); 2114 } 2115 text = String(text); 2116 cx.lastIndex = 0; 2117 if (cx.test(text)) { 2118 text = text.replace(cx, function (a) { 2119 return '\\u' + 2120 ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 2121 }); 2122 } 2123 2124 if (/^[\],:{}\s]*$/ 2125 .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 2126 .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 2127 .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 2128 2129 j = eval('(' + text + ')'); 2130 2131 return typeof reviver === 'function' ? 2132 walk({'': j}, '') : j; 2133 } 2134 2135 throw new SyntaxError('JSON.parse'); 2136 }; 2137 } 2138}()); 2139/* UI Component- Base class for all UI objects 2140================================================================================ */ 2141_V_.Player = _V_.Component.extend({ 2142 2143 init: function(tag, addOptions, ready){ 2144 2145 this.tag = tag; // Store the original tag used to set options 2146 2147 var el = this.el = _V_.createElement("div"), // Div to contain video and controls 2148 options = this.options = {}, 2149 width = options.width = tag.getAttribute('width'), 2150 height = options.height = tag.getAttribute('height'), 2151 2152 // Browsers default to 300x150 if there's no width/height or video size data. 2153 initWidth = width || 300, 2154 initHeight = height || 150; 2155 2156 // Make player findable on elements 2157 tag.player = el.player = this; 2158 2159 // Add callback to ready queue 2160 this.ready(ready); 2161 2162 // Wrap video tag in div (el/box) container 2163 tag.parentNode.insertBefore(el, tag); 2164 el.appendChild(tag); // Breaks iPhone, fixed in HTML5 setup. 2165 2166 // Give video tag properties to box 2167 el.id = this.id = tag.id; // ID will now reference box, not the video tag 2168 el.className = tag.className; 2169 // Update tag id/class for use as HTML5 playback tech 2170 tag.id += "_html5_api"; 2171 tag.className = "vjs-tech"; 2172 2173 // Make player easily findable by ID 2174 _V_.players[el.id] = this; 2175 2176 // Make box use width/height of tag, or default 300x150 2177 el.setAttribute("width", initWidth); 2178 el.setAttribute("height", initHeight); 2179 // Enforce with CSS since width/height attrs don't work on divs 2180 el.style.width = initWidth+"px"; 2181 el.style.height = initHeight+"px"; 2182 // Remove width/height attrs from tag so CSS can make it 100% width/height 2183 tag.removeAttribute("width"); 2184 tag.removeAttribute("height"); 2185 2186 // Set Options 2187 _V_.merge(options, _V_.options); // Copy Global Defaults 2188 _V_.merge(options, this.getVideoTagSettings()); // Override with Video Tag Options 2189 _V_.merge(options, addOptions); // Override/extend with options from setup call 2190 2191 // Store controls setting, and then remove immediately so native controls don't flash. 2192 tag.removeAttribute("controls"); 2193 2194 // Poster will be handled by a manual <img> 2195 tag.removeAttribute("poster"); 2196 2197 // Empty video tag sources and tracks so the built in player doesn't use them also. 2198 if (tag.hasChildNodes()) { 2199 for (var i=0,j=tag.childNodes;i<j.length;i++) { 2200 if (j[i].nodeName == "SOURCE" || j[i].nodeName == "TRACK") { 2201 tag.removeChild(j[i]); 2202 } 2203 } 2204 } 2205 2206 // Cache for video property values. 2207 this.values = {}; 2208 2209 this.addClass("vjs-paused"); 2210 2211 this.addEvent("ended", this.onEnded); 2212 this.addEvent("play", this.onPlay); 2213 this.addEvent("pause", this.onPause); 2214 this.addEvent("progress", this.onProgress); 2215 this.addEvent("error", this.onError); 2216 2217 // When the API is ready, loop through the components and add to the player. 2218 if (options.controls) { 2219 this.ready(function(){ 2220 this.initComponents(); 2221 }); 2222 } 2223 2224 // Tracks defined in tracks.js 2225 this.textTracks = []; 2226 if (options.tracks && options.tracks.length > 0) { 2227 this.addTextTracks(options.tracks); 2228 } 2229 2230 // If there are no sources when the player is initialized, 2231 // load the first supported playback technology. 2232 if (!options.sources || options.sources.length == 0) { 2233 for (var i=0,j=options.techOrder; i<j.length; i++) { 2234 var techName = j[i], 2235 tech = _V_[techName]; 2236 2237 // Check if the browser supports this technology 2238 if (tech.isSupported()) { 2239 this.loadTech(techName); 2240 break; 2241 } 2242 } 2243 } else { 2244 // Loop through playback technologies (HTML5, Flash) and check for support. Then load the best source. 2245 // A few assumptions here: 2246 // All playback technologies respect preload false. 2247 this.src(options.sources); 2248 } 2249 }, 2250 2251 // Cache for video property values. 2252 values: {}, 2253 2254 destroy: function(){ 2255 // Ensure that tracking progress and time progress will stop and plater deleted 2256 this.stopTrackingProgress(); 2257 this.stopTrackingCurrentTime(); 2258 _V_.players[this.id] = null; 2259 delete _V_.players[this.id]; 2260 this.tech.destroy(); 2261 this.el.parentNode.removeChild(this.el); 2262 }, 2263 2264 createElement: function(type, options){}, 2265 2266 getVideoTagSettings: function(){ 2267 var options = { 2268 sources: [], 2269 tracks: [] 2270 }; 2271 2272 options.src = this.tag.getAttribute("src"); 2273 options.controls = this.tag.getAttribute("controls") !== null; 2274 options.poster = this.tag.getAttribute("poster"); 2275 options.preload = this.tag.getAttribute("preload"); 2276 options.autoplay = this.tag.getAttribute("autoplay") !== null; // hasAttribute not IE <8 compatible 2277 options.loop = this.tag.getAttribute("loop") !== null; 2278 options.muted = this.tag.getAttribute("muted") !== null; 2279 2280 if (this.tag.hasChildNodes()) { 2281 for (var c,i=0,j=this.tag.childNodes;i<j.length;i++) { 2282 c = j[i]; 2283 if (c.nodeName == "SOURCE") { 2284 options.sources.push({ 2285 src: c.getAttribute('src'), 2286 type: c.getAttribute('type'), 2287 media: c.getAttribute('media'), 2288 title: c.getAttribute('title') 2289 }); 2290 } 2291 if (c.nodeName == "TRACK") { 2292 options.tracks.push({ 2293 src: c.getAttribute("src"), 2294 kind: c.getAttribute("kind"), 2295 srclang: c.getAttribute("srclang"), 2296 label: c.getAttribute("label"), 2297 'default': c.getAttribute("default") !== null, 2298 title: c.getAttribute("title") 2299 }); 2300 } 2301 } 2302 } 2303 return options; 2304 }, 2305 2306 /* PLayback Technology (tech) 2307 ================================================================================ */
2308 // Load/Create an instance of playback technlogy including element and API methods 2309 // And append playback element in player div. 2310 loadTech: function(techName, source){ 2311 2312 // Pause and remove current playback technology 2313 if (this.tech) { 2314 this.unloadTech(); 2315 2316 // If the first time loading, HTML5 tag will exist but won't be initialized 2317 // So we need to remove it if we're not loading HTML5 2318 } else if (techName != "html5" && this.tag) { 2319 this.el.removeChild(this.tag); 2320 this.tag = false; 2321 } 2322 2323 this.techName = techName; 2324 2325 // Turn off API access because we're loading a new tech that might load asynchronously 2326 this.isReady = false; 2327 2328 var techReady = function(){ 2329 this.player.triggerReady(); 2330 2331 // Manually track progress in cases where the browser/flash player doesn't report it. 2332 if (!this.support.progressEvent) { 2333 this.player.manualProgressOn(); 2334 } 2335 2336 // Manually track timeudpates in cases where the browser/flash player doesn't report it. 2337 if (!this.support.timeupdateEvent) { 2338 this.player.manualTimeUpdatesOn(); 2339 } 2340 } 2341 2342 // Grab tech-specific options from player options and add source and parent element to use. 2343 var techOptions = _V_.merge({ source: source, parentEl: this.el }, this.options[techName]) 2344 2345 if (source) { 2346 if (source.src == this.values.src && this.values.currentTime > 0) { 2347 techOptions.startTime = this.values.currentTime; 2348 } 2349 2350 this.values.src = source.src; 2351 } 2352 2353 // Initialize tech instance 2354 this.tech = new _V_[techName](this, techOptions); 2355 this.tech.ready(techReady); 2356 }, 2357 2358 unloadTech: function(){ 2359 this.tech.destroy(); 2360 2361 // Turn off any manual progress or timeupdate tracking 2362 if (this.manualProgress) { this.manualProgressOff(); } 2363 2364 if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); } 2365 2366 this.tech = false; 2367 }, 2368 2369 // There's many issues around changing the size of a Flash (or other plugin) object. 2370 // First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268 2371 // Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen. 2372 // To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized. 2373 // reloadTech: function(betweenFn){ 2374 // _V_.log("unloadingTech") 2375 // this.unloadTech(); 2376 // _V_.log("unloadedTech") 2377 // if (betweenFn) { betweenFn.call(); } 2378 // _V_.log("LoadingTech") 2379 // this.loadTech(this.techName, { src: this.values.src }) 2380 // _V_.log("loadedTech") 2381 // }, 2382 2383 /* Fallbacks for unsupported event types 2384 ================================================================================ */ 2385 // Manually trigger progress events based on changes to the buffered amount 2386 // Many flash players and older HTML5 browsers don't send progress or progress-like events 2387 manualProgressOn: function(){ 2388 this.manualProgress = true; 2389 2390 // Trigger progress watching when a source begins loading 2391 this.trackProgress(); 2392 2393 // Watch for a native progress event call on the tech element 2394 // In HTML5, some older versions don't support the progress event 2395 // So we're assuming they don't, and turning off manual progress if they do. 2396 this.tech.addEvent("progress", function(){ 2397 2398 // Remove this listener from the element 2399 this.removeEvent("progress", arguments.callee); 2400 2401 // Update known progress support for this playback technology 2402 this.support.progressEvent = true; 2403 2404 // Turn off manual progress tracking 2405 this.player.manualProgressOff(); 2406 }); 2407 }, 2408 2409 manualProgressOff: function(){ 2410 this.manualProgress = false; 2411 this.stopTrackingProgress(); 2412 }, 2413 2414 trackProgress: function(){ 2415 this.progressInterval = setInterval(_V_.proxy(this, function(){ 2416 // Don't trigger unless buffered amount is greater than last time 2417 // log(this.values.bufferEnd, this.buffered().end(0), this.duration()) 2418 /* TODO: update for multiple buffered regions */ 2419 if (this.values.bufferEnd < this.buffered().end(0)) { 2420 this.triggerEvent("progress"); 2421 } else if (this.bufferedPercent() == 1) { 2422 this.stopTrackingProgress(); 2423 this.triggerEvent("progress"); // Last update 2424 } 2425 }), 500); 2426 }, 2427 stopTrackingProgress: function(){ clearInterval(this.progressInterval); }, 2428 2429 /* Time Tracking -------------------------------------------------------------- */ 2430 manualTimeUpdatesOn: function(){ 2431 this.manualTimeUpdates = true; 2432 2433 this.addEvent("play", this.trackCurrentTime); 2434 this.addEvent("pause", this.stopTrackingCurrentTime); 2435 // timeupdate is also called by .currentTime whenever current time is set 2436 2437 // Watch for native timeupdate event 2438 this.tech.addEvent("timeupdate", function(){ 2439 2440 // Remove this listener from the element 2441 this.removeEvent("timeupdate", arguments.callee); 2442
2443 // Update known progress support for this playback technology 2444 this.support.timeupdateEvent = true; 2445 2446 // Turn off manual progress tracking 2447 this.player.manualTimeUpdatesOff(); 2448 }); 2449 }, 2450 2451 manualTimeUpdatesOff: function(){ 2452 this.manualTimeUpdates = false; 2453 this.stopTrackingCurrentTime(); 2454 this.removeEvent("play", this.trackCurrentTime); 2455 this.removeEvent("pause", this.stopTrackingCurrentTime); 2456 }, 2457 2458 trackCurrentTime: function(){ 2459 if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); } 2460 this.currentTimeInterval = setInterval(_V_.proxy(this, function(){ 2461 this.triggerEvent("timeupdate"); 2462 }), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 2463 }, 2464 2465 // Turn off play progress tracking (when paused or dragging) 2466 stopTrackingCurrentTime: function(){ clearInterval(this.currentTimeInterval); }, 2467 2468 /* Player event handlers (how the player reacts to certain events) 2469 ================================================================================ */ 2470 onEnded: function(){ 2471 if (this.options.loop) { 2472 this.currentTime(0); 2473 this.play(); 2474 } else { 2475 this.pause(); 2476 this.currentTime(0); 2477 this.pause(); 2478 } 2479 }, 2480 2481 onPlay: function(){ 2482 _V_.removeClass(this.el, "vjs-paused"); 2483 _V_.addClass(this.el, "vjs-playing"); 2484 }, 2485 2486 onPause: function(){ 2487 _V_.removeClass(this.el, "vjs-playing"); 2488 _V_.addClass(this.el, "vjs-paused"); 2489 }, 2490 2491 onProgress: function(){ 2492 // Add custom event for when source is finished downloading. 2493 if (this.bufferedPercent() == 1) { 2494 this.triggerEvent("loadedalldata"); 2495 } 2496 }, 2497 2498 onError: function(e) { 2499 _V_.log("Video Error", e); 2500 }, 2501 2502/* Player API 2503================================================================================ */ 2504 2505 // Pass values to the playback tech 2506 techCall: function(method, arg){ 2507 2508 // If it's not ready yet, call method when it is 2509 if (!this.tech.isReady) { 2510 this.tech.ready(function(){ 2511 this[method](arg); 2512 }); 2513 2514 // Otherwise call method now 2515 } else { 2516 try { 2517 this.tech[method](arg); 2518 } catch(e) { 2519 _V_.log(e); 2520 } 2521 } 2522 }, 2523 2524 // Get calls can't wait for the tech, and sometimes don't need to. 2525 techGet: function(method){ 2526 2527 // Make sure tech is ready 2528 if (this.tech.isReady) { 2529 2530 // Flash likes to die and reload when you hide or reposition it. 2531 // In these cases the object methods go away and we get errors. 2532 // When that happens we'll catch the errors and inform tech that it's not ready any more.
2533 try { 2534 return this.tech[method](); 2535 } catch(e) { 2536 2537 // When building additional tech libs, an expected method may not be defined yet 2538 if (this.tech[method] === undefined) { 2539 _V_.log("Video.js: " + method + " method not defined for "+this.techName+" playback technology.", e); 2540 2541 } else { 2542 2543 // When a method isn't available on the object it throws a TypeError 2544 if (e.name == "TypeError") { 2545 _V_.log("Video.js: " + method + " unavailable on "+this.techName+" playback technology element.", e); 2546 this.tech.isReady = false; 2547 2548 } else { 2549 _V_.log(e); 2550 } 2551 } 2552 } 2553 } 2554 2555 return; 2556 }, 2557 2558 // Method for calling methods on the current playback technology 2559 // techCall: function(method, arg){ 2560 // 2561 // // if (this.isReady) { 2562 // // 2563 // // } else { 2564 // // _V_.log("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]", arguments.callee.caller.arguments.callee.caller.arguments.callee.caller) 2565 // // return false; 2566 // // // throw new Error("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]"); 2567 // // } 2568 // }, 2569 2570 // http://dev.w3.org/html5/spec/video.html#dom-media-play 2571 play: function(){ 2572 this.techCall("play"); 2573 return this; 2574 }, 2575 2576 // http://dev.w3.org/html5/spec/video.html#dom-media-pause 2577 pause: function(){ 2578 this.techCall("pause"); 2579 return this; 2580 }, 2581
2582 // http://dev.w3.org/html5/spec/video.html#dom-media-paused 2583 // The initial state of paused should be true (in Safari it's actually false) 2584 paused: function(){ 2585 return (this.techGet("paused") === false) ? false : true; 2586 }, 2587 2588 // http://dev.w3.org/html5/spec/video.html#dom-media-currenttime 2589 currentTime: function(seconds){ 2590 if (seconds !== undefined) { 2591 2592 // Cache the last set value for smoother scrubbing. 2593 this.values.lastSetCurrentTime = seconds; 2594 2595 this.techCall("setCurrentTime", seconds); 2596 2597 // Improve the accuracy of manual timeupdates 2598 if (this.manualTimeUpdates) { this.triggerEvent("timeupdate"); } 2599 2600 return this; 2601 } 2602 2603 // Cache last currentTime and return 2604 // Default to 0 seconds 2605 return this.values.currentTime = (this.techGet("currentTime") || 0); 2606 }, 2607 2608 // http://dev.w3.org/html5/spec/video.html#dom-media-duration 2609 // Duration should return NaN if not available. ParseFloat will turn false-ish values to NaN. 2610 duration: function(){ 2611 return parseFloat(this.techGet("duration")); 2612 }, 2613 2614 // Calculates how much time is left. Not in spec, but useful. 2615 remainingTime: function(){ 2616 return this.duration() - this.currentTime(); 2617 }, 2618 2619 // http://dev.w3.org/html5/spec/video.html#dom-media-buffered 2620 // Buffered returns a timerange object. Kind of like an array of portions of the video that have been downloaded. 2621 // So far no browsers return more than one range (portion) 2622 buffered: function(){ 2623 var buffered = this.techGet("buffered"), 2624 start = 0, 2625 end = this.values.bufferEnd = this.values.bufferEnd || 0, // Default end to 0 and store in values 2626 timeRange; 2627 2628 if (buffered && buffered.length > 0 && buffered.end(0) !== end) { 2629 end = buffered.end(0); 2630 // Storing values allows them be overridden by setBufferedFromProgress 2631 this.values.bufferEnd = end; 2632 } 2633 2634 return _V_.createTimeRange(start, end); 2635 }, 2636 2637 // Calculates amount of buffer is full. Not in spec but useful. 2638 bufferedPercent: function(){ 2639 return (this.duration()) ? this.buffered().end(0) / this.duration() : 0; 2640 }, 2641 2642 // http://dev.w3.org/html5/spec/video.html#dom-media-volume 2643 volume: function(percentAsDecimal){ 2644 var vol; 2645 2646 if (percentAsDecimal !== undefined) { 2647 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1 2648 this.values.volume = vol; 2649 this.techCall("setVolume", vol); 2650 _V_.setLocalStorage("volume", vol); 2651 return this; 2652 } 2653 2654 // Default to 1 when returning current volume. 2655 vol = parseFloat(this.techGet("volume")); 2656 return (isNaN(vol)) ? 1 : vol; 2657 }, 2658 2659 // http://dev.w3.org/html5/spec/video.html#attr-media-muted 2660 muted: function(muted){ 2661 if (muted !== undefined) { 2662 this.techCall("setMuted", muted); 2663 return this; 2664 } 2665 return this.techGet("muted") || false; // Default to false 2666 }, 2667 2668 // http://dev.w3.org/html5/spec/dimension-attributes.html#attr-dim-height 2669 // Video tag width/height only work in pixels. No percents. 2670 // We could potentially allow percents but won't for now until we can do testing around it. 2671 width: function(width, skipListeners){ 2672 if (width !== undefined) { 2673 this.el.width = width; 2674 this.el.style.width = width+"px"; 2675 2676 // skipListeners allows us to avoid triggering the resize event when setting both width and height 2677 if (!skipListeners) { this.triggerEvent("resize"); } 2678 return this; 2679 } 2680 return parseInt(this.el.getAttribute("width")); 2681 }, 2682 height: function(height){ 2683 if (height !== undefined) { 2684 this.el.height = height; 2685 this.el.style.height = height+"px"; 2686 this.triggerEvent("resize"); 2687 return this; 2688 } 2689 return parseInt(this.el.getAttribute("height")); 2690 }, 2691 // Set both width and height at the same time. 2692 size: function(width, height){ 2693 // Skip resize listeners on width for optimization 2694 return this.width(width, true).height(height); 2695 }, 2696 2697 // Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf) 2698 supportsFullScreen: function(){ return this.techGet("supportsFullScreen") || false; }, 2699 2700 // Turn on fullscreen (or window) mode 2701 requestFullScreen: function(){ 2702 var requestFullScreen = _V_.support.requestFullScreen; 2703 2704 this.isFullScreen = true; 2705 2706 // Check for browser element fullscreen support 2707 if (requestFullScreen) { 2708 2709 // Trigger fullscreenchange event after change 2710 _V_.addEvent(document, requestFullScreen.eventName, this.proxy(function(){ 2711 this.isFullScreen = document[requestFullScreen.isFullScreen]; 2712 2713 // If cancelling fullscreen, remove event listener. 2714 if (this.isFullScreen == false) { 2715 _V_.removeEvent(document, requestFullScreen.eventName, arguments.callee); 2716 } 2717 2718 this.triggerEvent("fullscreenchange"); 2719 })); 2720 2721 // Flash and other plugins get reloaded when you take their parent to fullscreen. 2722 // To fix that we'll remove the tech, and reload it after the resize has finished. 2723 if (this.tech.support.fullscreenResize === false && this.options.flash.iFrameMode != true) { 2724 2725 this.pause(); 2726 this.unloadTech(); 2727 2728 _V_.addEvent(document, requestFullScreen.eventName, this.proxy(function(){ 2729 _V_.removeEvent(document, requestFullScreen.eventName, arguments.callee); 2730 this.loadTech(this.techName, { src: this.values.src }); 2731 })); 2732 2733 this.el[requestFullScreen.requestFn](); 2734 2735 } else { 2736 this.el[requestFullScreen.requestFn](); 2737 } 2738 2739 } else if (this.tech.supportsFullScreen()) { 2740 this.triggerEvent("fullscreenchange"); 2741 this.techCall("enterFullScreen"); 2742 2743 } else { 2744 this.triggerEvent("fullscreenchange"); 2745 this.enterFullWindow(); 2746 } 2747 2748 return this; 2749 }, 2750 2751 cancelFullScreen: function(){ 2752 var requestFullScreen = _V_.support.requestFullScreen; 2753 2754 this.isFullScreen = false;
2755 2756 // Check for browser element fullscreen support 2757 if (requestFullScreen) { 2758 2759 // Flash and other plugins get reloaded when you take their parent to fullscreen. 2760 // To fix that we'll remove the tech, and reload it after the resize has finished. 2761 if (this.tech.support.fullscreenResize === false && this.options.flash.iFrameMode != true) { 2762 2763 this.pause(); 2764 this.unloadTech(); 2765 2766 _V_.addEvent(document, requestFullScreen.eventName, this.proxy(function(){ 2767 _V_.removeEvent(document, requestFullScreen.eventName, arguments.callee); 2768 this.loadTech(this.techName, { src: this.values.src }) 2769 })); 2770 2771 document[requestFullScreen.cancelFn](); 2772 2773 } else { 2774 document[requestFullScreen.cancelFn](); 2775 } 2776 2777 } else if (this.tech.supportsFullScreen()) { 2778 this.techCall("exitFullScreen"); 2779 this.triggerEvent("fullscreenchange"); 2780 2781 } else { 2782 this.exitFullWindow(); 2783 this.triggerEvent("fullscreenchange"); 2784 } 2785 2786 return this; 2787 }, 2788 2789 // When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us. 2790 enterFullWindow: function(){ 2791 this.isFullWindow = true; 2792 2793 // Storing original doc overflow value to return to when fullscreen is off 2794 this.docOrigOverflow = document.documentElement.style.overflow; 2795 2796 // Add listener for esc key to exit fullscreen 2797 _V_.addEvent(document, "keydown", _V_.proxy(this, this.fullWindowOnEscKey)); 2798 2799 // Hide any scroll bars 2800 document.documentElement.style.overflow = 'hidden'; 2801 2802 // Apply fullscreen styles 2803 _V_.addClass(document.body, "vjs-full-window"); 2804 _V_.addClass(this.el, "vjs-fullscreen"); 2805 2806 this.triggerEvent("enterFullWindow"); 2807 }, 2808 fullWindowOnEscKey: function(event){ 2809 if (event.keyCode == 27) { 2810 if (this.isFullScreen == true) { 2811 this.cancelFullScreen(); 2812 } else { 2813 this.exitFullWindow(); 2814 } 2815 } 2816 }, 2817 2818 exitFullWindow: function(){ 2819 this.isFullWindow = false; 2820 _V_.removeEvent(document, "keydown", this.fullWindowOnEscKey); 2821 2822 // Unhide scroll bars. 2823 document.documentElement.style.overflow = this.docOrigOverflow; 2824 2825 // Remove fullscreen styles 2826 _V_.removeClass(document.body, "vjs-full-window"); 2827 _V_.removeClass(this.el, "vjs-fullscreen"); 2828 2829 // Resize the box, controller, and poster to original sizes 2830 // this.positionAll(); 2831 this.triggerEvent("exitFullWindow"); 2832 }, 2833 2834 selectSource: function(sources){ 2835 2836 // Loop through each playback technology in the options order 2837 for (var i=0,j=this.options.techOrder;i<j.length;i++) { 2838 var techName = j[i], 2839 tech = _V_[techName]; 2840 // tech = _V_.tech[techName]; 2841 2842 // Check if the browser supports this technology 2843 if (tech.isSupported()) { 2844 2845 // Loop through each source object 2846 for (var a=0,b=sources;a<b.length;a++) { 2847 var source = b[a]; 2848 2849 // Check if source can be played with this technology 2850 if (tech.canPlaySource.call(this, source)) { 2851 2852 return { source: source, tech: techName }; 2853 2854 } 2855 } 2856 } 2857 } 2858 2859 return false; 2860 }, 2861 2862 // src is a pretty powerful function 2863 // If you pass it an array of source objects, it will find the best source to play and use that object.src 2864 // If the new source requires a new playback technology, it will switch to that. 2865 // If you pass it an object, it will set the source to object.src 2866 // If you pass it anything else (url string) it will set the video source to that 2867 src: function(source){ 2868 // Case: Array of source objects to choose from and pick the best to play 2869 if (source instanceof Array) { 2870 2871 var sourceTech = this.selectSource(source), 2872 source, 2873 techName; 2874 2875 if (sourceTech) { 2876 source = sourceTech.source; 2877 techName = sourceTech.tech; 2878 2879 // If this technology is already loaded, set source 2880 if (techName == this.techName) { 2881 this.src(source); // Passing the source object 2882 2883 // Otherwise load this technology with chosen source 2884 } else { 2885 this.loadTech(techName, source); 2886 } 2887 } else { 2888 _V_.log("No compatible source and playback technology were found.") 2889 } 2890 2891 // Case: Source object { src: "", type: "" ... } 2892 } else if (source instanceof Object) { 2893 2894 if (_V_[this.techName].canPlaySource(source)) { 2895 this.src(source.src); 2896 } else { 2897 // Send through tech loop to check for a compatible technology. 2898 this.src([source]); 2899 } 2900 2901 // Case: URL String (http://myvideo...) 2902 } else { 2903 // Cache for getting last set source 2904 this.values.src = source; 2905 2906 if (!this.isReady) { 2907 this.ready(function(){ 2908 this.src(source); 2909 }); 2910 } else { 2911 this.techCall("src", source); 2912 if (this.options.preload == "auto") { 2913 this.load(); 2914 } 2915 if (this.options.autoplay) { 2916 this.play(); 2917 } 2918 } 2919 } 2920 return this; 2921 }, 2922 2923 // Begin loading the src data 2924 // http://dev.w3.org/html5/spec/video.html#dom-media-load 2925 load: function(){ 2926 this.techCall("load"); 2927 return this; 2928 }, 2929 2930 // http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc 2931 currentSrc: function(){ 2932 return this.techGet("currentSrc") || this.values.src || ""; 2933 }, 2934 2935 // Attributes/Options 2936 preload: function(value){ 2937 if (value !== undefined) { 2938 this.techCall("setPreload", value); 2939 this.options.preload = value; 2940 return this; 2941 } 2942 return this.techGet("preload"); 2943 }, 2944 autoplay: function(value){ 2945 if (value !== undefined) { 2946 this.techCall("setAutoplay", value); 2947 this.options.autoplay = value; 2948 return this; 2949 } 2950 return this.techGet("autoplay", value); 2951 }, 2952 loop: function(value){ 2953 if (value !== undefined) { 2954 this.techCall("setLoop", value); 2955 this.options.loop = value; 2956 return this; 2957 } 2958 return this.techGet("loop"); 2959 }, 2960 2961 controls: function(){ return this.options.controls; }, 2962 poster: function(){ return this.techGet("poster"); }, 2963 error: function(){ return this.techGet("error"); }, 2964 ended: function(){ return this.techGet("ended"); } 2965 2966 // Methods to add support for 2967 // networkState: function(){ return this.techCall("networkState"); }, 2968 // readyState: function(){ return this.techCall("readyState"); }, 2969 // seeking: function(){ return this.techCall("seeking"); }, 2970 // initialTime: function(){ return this.techCall("initialTime"); }, 2971 // startOffsetTime: function(){ return this.techCall("startOffsetTime"); }, 2972 // played: function(){ return this.techCall("played"); }, 2973 // seekable: function(){ return this.techCall("seekable"); }, 2974 // videoTracks: function(){ return this.techCall("videoTracks"); }, 2975 // audioTracks: function(){ return this.techCall("audioTracks"); }, 2976 // videoWidth: function(){ return this.techCall("videoWidth"); }, 2977 // videoHeight: function(){ return this.techCall("videoHeight"); }, 2978 // defaultPlaybackRate: function(){ return this.techCall("defaultPlaybackRate"); }, 2979 // playbackRate: function(){ return this.techCall("playbackRate"); }, 2980 // mediaGroup: function(){ return this.techCall("mediaGroup"); }, 2981 // controller: function(){ return this.techCall("controller"); }, 2982 // defaultMuted: function(){ return this.techCall("defaultMuted"); } 2983}); 2984 2985// RequestFullscreen API 2986(function(){ 2987 var requestFn, 2988 cancelFn, 2989 eventName, 2990 isFullScreen, 2991 playerProto = _V_.Player.prototype; 2992 2993 // Current W3C Spec 2994 // http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api 2995 // Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event 2996 if (document.cancelFullscreen !== undefined) { 2997 requestFn = "requestFullscreen"; 2998 cancelFn = "exitFullscreen"; 2999 eventName = "fullscreenchange"; 3000 isFullScreen = "fullScreen"; 3001 3002 // Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementaitons 3003 // that use prefixes and vary slightly from the new W3C spec. Specifically, using 'exit' instead of 'cancel', 3004 // and lowercasing the 'S' in Fullscreen. 3005 // Other browsers don't have any hints of which version they might follow yet, so not going to try to predict by loopeing through all prefixes. 3006 } else { 3007 3008 _V_.each(["moz", "webkit"], function(prefix){ 3009 3010 // https://github.com/zencoder/video-js/pull/128 3011 if ((prefix != "moz" || document.mozFullScreenEnabled) && document[prefix + "CancelFullScreen"] !== undefined) { 3012 requestFn = prefix + "RequestFullScreen"; 3013 cancelFn = prefix + "CancelFullScreen"; 3014 eventName = prefix + "fullscreenchange"; 3015 3016 if (prefix == "webkit") { 3017 isFullScreen = prefix + "IsFullScreen"; 3018 } else { 3019 isFullScreen = prefix + "FullScreen"; 3020 } 3021 } 3022 3023 }); 3024 3025 } 3026 3027 if (requestFn) { 3028 _V_.support.requestFullScreen = { 3029 requestFn: requestFn, 3030 cancelFn: cancelFn, 3031 eventName: eventName, 3032 isFullScreen: isFullScreen 3033 }; 3034 } 3035 3036})();/* Playback Technology - Base class for playback technologies 3037================================================================================ */ 3038_V_.PlaybackTech = _V_.Component.extend({ 3039 init: function(player, options){ 3040 // this._super(player, options); 3041 3042 // Make playback element clickable 3043 // _V_.addEvent(this.el, "click", _V_.proxy(this, _V_.PlayToggle.prototype.onClick)); 3044 3045 // this.addEvent("click", this.proxy(this.onClick)); 3046 3047 // player.triggerEvent("techready"); 3048 }, 3049 // destroy: function(){}, 3050 // createElement: function(){}, 3051 onClick: function(){ 3052 if (this.player.options.controls) { 3053 _V_.PlayToggle.prototype.onClick.call(this); 3054 } 3055 } 3056}); 3057 3058// Create placeholder methods for each that warn when a method isn't supported by the current playback technology 3059_V_.apiMethods = "play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,set
3059Loop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted".split(","); 3060_V_.each(_V_.apiMethods, function(methodName){ 3061 _V_.PlaybackTech.prototype[methodName] = function(){ 3062 throw new Error("The '"+methodName+"' method is not available on the playback technology's API"); 3063 } 3064}); 3065 3066/* HTML5 Playback Technology - Wrapper for HTML5 Media API 3067================================================================================ */ 3068_V_.html5 = _V_.PlaybackTech.extend({ 3069 3070 init: function(player, options, ready){ 3071 this.player = player; 3072 this.el = this.createElement(); 3073 this.ready(ready); 3074 3075 this.addEvent("click", this.proxy(this.onClick)); 3076 3077 var source = options.source; 3078 3079 // If the element source is already set, we may have missed the loadstart event, and want to trigger it. 3080 // We don't want to set the source again and interrupt playback. 3081 if (source && this.el.currentSrc == source.src) { 3082 player.triggerEvent("loadstart"); 3083 3084 // Otherwise set the source if one was provided. 3085 } else if (source) { 3086 this.el.src = source.src; 3087 } 3088 3089 // Chrome and Safari both have issues with autoplay. 3090 // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work. 3091 // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays) 3092 // This fixes both issues. Need to wait for API, so it updates displays correctly 3093 player.ready(function(){ 3094 if (this.options.autoplay && this.paused()) { 3095 this.tag.poster = null; // Chrome Fix. Fixed in Chrome v16. 3096 this.play(); 3097 } 3098 }); 3099 3100 this.setupTriggers(); 3101 3102 this.triggerReady(); 3103 }, 3104 3105 destroy: function(){ 3106 this.player.tag = false; 3107 this.removeTriggers(); 3108 this.el.parentNode.removeChild(this.el); 3109 }, 3110 3111 createElement: function(){ 3112 var html5 = _V_.html5, 3113 player = this.player, 3114 3115 // If possible, reuse original tag for HTML5 playback technology element 3116 el = player.tag, 3117 newEl; 3118 3119 // Check if this browser supports moving the element into the box. 3120 // On the iPhone video will break if you move the element, 3121 // So we have to create a brand new element. 3122 if (!el || this.support.movingElementInDOM === false) { 3123 3124 // If the original tag is still there, remove it. 3125 if (el) { 3126 player.el.removeChild(el); 3127 } 3128 3129 newEl = _V_.createElement("video", { 3130 id: el.id || player.el.id + "_html5_api", 3131 className: el.className || "vjs-tech" 3132 }); 3133 3134 el = newEl; 3135 _V_.insertFirst(el, player.el); 3136 } 3137 3138 // Update tag settings, in case they were overridden 3139 _V_.each(["autoplay","preload","loop","muted"], function(attr){ // ,"poster" 3140 if (player.options[attr] !== null) { 3141 el[attr] = player.options[attr]; 3142 } 3143 }, this); 3144 3145 return el; 3146 }, 3147 3148 // Make video events trigger player events 3149 // May seem verbose here, but makes other APIs possible. 3150 setupTriggers: function(){ 3151 _V_.each.call(this, _V_.html5.events, function(type){ 3152 _V_.addEvent(this.el, type, _V_.proxy(this.player, this.eventHandler)); 3153 }); 3154 }, 3155 removeTriggers: function(){ 3156 _V_.each.call(this, _V_.html5.events, function(type){ 3157 _V_.removeEvent(this.el, type, _V_.proxy(this.player, this.eventHandler)); 3158 }); 3159 }, 3160 eventHandler: function(e){ 3161 e.stopPropagation(); 3162 this.triggerEvent(e); 3163 }, 3164 3165 play: function(){ this.el.play(); }, 3166 pause: function(){ this.el.pause(); }, 3167 paused: function(){ return this.el.paused; }, 3168 3169 currentTime: function(){ return this.el.currentTime; }, 3170 setCurrentTime: function(seconds){ 3171 try { 3172 this.el.currentTime = seconds; 3173 } catch(e) { 3174 _V_.log(e, "Video isn't ready. (VideoJS)"); 3175 // this.warning(VideoJS.warnings.videoNotReady); 3176 } 3177 }, 3178 3179 duration: function(){ return this.el.duration || 0; }, 3180 buffered: function(){ return this.el.buffered; }, 3181 3182 volume: function(){ return this.el.volume; }, 3183 setVolume: function(percentAsDecimal){ this.el.volume = percentAsDecimal; }, 3184 muted: function(){ return this.el.muted; }, 3185 setMuted: function(muted){ this.el.muted = muted }, 3186 3187 width: function(){ return this.el.offsetWidth; }, 3188 height: function(){ return this.el.offsetHeight; }, 3189 3190 supportsFullScreen: function(){ 3191 if (typeof this.el.webkitEnterFullScreen == 'function') { 3192 3193 // Seems to be broken in Chromium/Chrome && Safari in Leopard 3194 if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) { 3195 return true; 3196 } 3197 } 3198 return false;
3199 }, 3200 3201 enterFullScreen: function(){ 3202 try { 3203 this.el.webkitEnterFullScreen(); 3204 } catch (e) { 3205 if (e.code == 11) { 3206 // this.warning(VideoJS.warnings.videoNotReady); 3207 _V_.log("VideoJS: Video not ready.") 3208 } 3209 } 3210 }, 3211 src: function(src){ this.el.src = src; }, 3212 load: function(){ this.el.load(); }, 3213 currentSrc: function(){ return this.el.currentSrc; }, 3214 3215 preload: function(){ return this.el.preload; }, 3216 setPreload: function(val){ this.el.preload = val; }, 3217 autoplay: function(){ return this.el.autoplay; }, 3218 setAutoplay: function(val){ this.el.autoplay = val; }, 3219 loop: function(){ return this.el.loop; }, 3220 setLoop: function(val){ this.el.loop = val; }, 3221 3222 error: function(){ return this.el.error; }, 3223 // networkState: function(){ return this.el.networkState; }, 3224 // readyState: function(){ return this.el.readyState; }, 3225 seeking: function(){ return this.el.seeking; }, 3226 // initialTime: function(){ return this.el.initialTime; }, 3227 // startOffsetTime: function(){ return this.el.startOffsetTime; }, 3228 // played: function(){ return this.el.played; }, 3229 // seekable: function(){ return this.el.seekable; }, 3230 ended: function(){ return this.el.ended; }, 3231 // videoTracks: function(){ return this.el.videoTracks; }, 3232 // audioTracks: function(){ return this.el.audioTracks; }, 3233 // videoWidth: function(){ return this.el.videoWidth; }, 3234 // videoHeight: function(){ return this.el.videoHeight; }, 3235 // textTracks: function(){ return this.el.textTracks; }, 3236 // defaultPlaybackRate: function(){ return this.el.defaultPlaybackRate; }, 3237 // playbackRate: function(){ return this.el.playbackRate; }, 3238 // mediaGroup: function(){ return this.el.mediaGroup; }, 3239 // controller: function(){ return this.el.controller; }, 3240 controls: function(){ return this.player.options.controls; }, 3241 defaultMuted: function(){ return this.el.defaultMuted; } 3242}); 3243 3244/* HTML5 Support Testing -------------------------------------------------------- */ 3245 3246_V_.html5.isSupported = function(){ 3247 return !!document.createElement("video").canPlayType; 3248}; 3249 3250_V_.html5.canPlaySource = function(srcObj){ 3251 return !!document.createElement("video").canPlayType(srcObj.type); 3252 // TODO: Check Type 3253 // If no Type, check ext 3254 // Check Media Type 3255}; 3256 3257// List of all HTML5 events (various uses). 3258_V_.html5.events = "loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange".split(","); 3259 3260/* HTML5 Device Fixes ---------------------------------------------------------- */ 3261 3262_V_.html5.prototype.support = { 3263 3264 // Support for tech specific full screen. (webkitEnterFullScreen, not requestFullscreen) 3265 // http://developer.apple.com/library/safari/#documentation/AudioVideo/Reference/HTMLVideoElementClassReference/HTMLVideoElement/HTMLVideoElement.html 3266 // Seems to be broken in Chromium/Chrome && Safari in Leopard 3267 fullscreen: (typeof _V_.testVid.webkitEnterFullScreen !== undefined) ? (!_V_.ua.match("Chrome") && !_V_.ua.match("Mac OS X 10.5") ? true : false) : false, 3268 3269 // In iOS, if you move a video element in the DOM, it breaks video playback. 3270 movingElementInDOM: !_V_.isIOS() 3271 3272}; 3273 3274// Android 3275if (_V_.isAndroid()) { 3276 3277 // Override Android 2.2 and less canPlayType method which is broken 3278 if (_V_.androidVersion() < 3) { 3279 document.createElement("video").constructor.prototype.canPlayType = function(type){ 3280 return (type && type.toLowerCase().indexOf("video/mp4") != -1) ? "maybe" : ""; 3281 }; 3282 } 3283} 3284 3285 3286/* VideoJS-SWF - Custom Flash Player with HTML5-ish API - https://github.com/zencoder/video-js-swf 3287================================================================================ */ 3288_V_.flash = _V_.PlaybackTech.extend({ 3289 3290 init: function(player, options){ 3291 this.player = player; 3292 3293 var source = options.source, 3294 3295 // Which element to embed in 3296 parentEl = options.parentEl, 3297 3298 // Create a temporary element to be replaced by swf object 3299 placeHolder = this.el = _V_.createElement("div", { id: parentEl.id + "_temp_flash" }), 3300 3301 // Generate ID for swf object 3302 objId = player.el.id+"_flash_api", 3303 3304 // Store player options in local var for optimization 3305 playerOptions = player.options, 3306 3307 // Merge default flashvars with ones passed in to init 3308 flashVars = _V_.merge({ 3309 3310 // SWF Callback Functions 3311 readyFunction: "_V_.flash.onReady", 3312 eventProxyFunction: "_V_.flash.onEvent", 3313 errorEventProxyFunction: "_V_.flash.onError", 3314 3315 // Player Settings 3316 autoplay: playerOptions.autoplay, 3317 preload: playerOptions.preload, 3318 loop: playerOptions.loop, 3319 muted: playerOptions.muted 3320 3321 }, options.flashVars), 3322 3323 // Merge default parames with ones passed in 3324 params = _V_.merge({ 3325 wmode: "opaque", // Opaque is needed to overlay controls, but can affect playback performance 3326 bgcolor: "#000000" // Using bgcolor prevents a white flash when the object is loading 3327 }, options.params), 3328 3329 // Merge default attributes with ones passed in 3330 attributes = _V_.merge({ 3331 id: objId, 3332 name: objId, // Both ID and Name needed or swf to identifty itself 3333 'class': 'vjs-tech' 3334 }, options.attributes) 3335 ; 3336 3337 // If source was supplied pass as a flash var. 3338 if (source) { 3339 flashVars.src = encodeURIComponent(_V_.getAbsoluteURL(source.src)); 3340 } 3341 3342 // Add placeholder to player div 3343 _V_.insertFirst(placeHolder, parentEl); 3344 3345 // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers 3346 // This allows resetting the playhead when we catch the reload 3347 if (options.startTime) { 3348 this.ready(function(){ 3349 this.load(); 3350 this.play(); 3351 this.currentTime(options.startTime); 3352 }); 3353 } 3354 3355 // Flash iFrame Mode 3356 // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload. 3357 // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13) 3358 // - Webkit when hiding the plugin 3359 // - Webkit and Firefox when using requestFullScreen on a parent element 3360 // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues. 3361 // Issues that remain include hiding the element and requestFullScreen in Firefox specifically 3362 3363 // There's on particularly annoying issue with this method which is that Firefox throws a
3363security error on an offsite Flash object loaded into a dynamically created iFrame. 3364 // Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file. 3365 // I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain. 3366 // Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe 3367 // In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object. 3368 // The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is. 3369 3370 // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame 3371 // Firefox 9 throws a security error, unleess you call location.href right before doc.write. 3372 // Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page. 3373 // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop. 3374 3375 if (options.iFrameMode == true && !_V_.isFF) { 3376 3377 // Create iFrame with vjs-tech class so it's 100% width/height 3378 var iFrm = _V_.createElement("iframe", { 3379 id: objId + "_iframe", 3380 name: objId + "_iframe", 3381 className: "vjs-tech", 3382 scrolling: "no", 3383 marginWidth: 0, 3384 marginHeight: 0, 3385 frameBorder: 0 3386 }); 3387 3388 // Update ready function names in flash vars for iframe window 3389 flashVars.readyFunction = "ready"; 3390 flashVars.eventProxyFunction = "events"; 3391 flashVars.errorEventProxyFunction = "errors"; 3392 3393 // Tried multiple methods to get this to work in all browsers 3394 3395 // Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object. 3396 // The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error 3397 // var newObj = _V_.flash.embed(options.swf, placeHolder, flashVars, params, attributes); 3398 // (in onload) 3399 // var temp = _V_.createElement("a", { id:"asdf", innerHTML: "asdf" } ); 3400 // iDoc.body.appendChild(temp); 3401 3402 // Tried embedding the flash object through javascript in the iframe source. 3403 // This works in webkit but still triggers the firefox security error 3404 // iFrm.src = "javascript: document.write('"+_V_.flash.getEmbedCode(options.swf, flashVars, params, attributes)+"');"; 3405 3406 // Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe 3407 // We should add an option to host the iframe locally though, because it could help a lot of issues. 3408 // iFrm.src = "iframe.html"; 3409 3410 // Wait until iFrame has loaded to write into it. 3411 _V_.addEvent(iFrm, "load", _V_.proxy(this, function(){ 3412 3413 var iDoc, objTag, swfLoc, 3414 iWin = iFrm.contentWindow, 3415 varString = ""; 3416 3417 3418 // The one working method I found was to use the iframe's document.write() to create the swf object 3419 // This got around the security issue in all browsers except firefox. 3420 // I did find a hack where if I call the iframe's window.location.href="", it would get around the security error 3421 // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop) 3422 // Plus Firefox 3.6 didn't work no matter what I tried. 3423 // if (_V_.ua.match("Firefox")) { 3424 // iWin.location.href = ""; 3425 // } 3426 3427 // Get the iFrame's document depending on what the browser supports 3428 iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document; 3429 3430 // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue. 3431 // Even tried adding /. that was mentioned in a browser security writeup 3432 // document.domain = document.domain+"/."; 3433 // iDoc.domain = document.domain+"/."; 3434 3435 // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers. 3436 // iDoc.body.innerHTML = swfObjectHTML; 3437 3438 // Tried appending the object to the iframe doc's body. Security error in all browsers. 3439 // iDoc.body.appendChild(swfObject); 3440 3441 // Using document.write actually got around the security error that browsers were throwing. 3442 // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf. 3443 // Not sure why that's a security issue, but apparently it is. 3444 iDoc.write(_V_.flash.getEmbedCode(options.swf, flashVars, params, attributes)); 3445 3446 // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers 3447 // So far no issues with swf ready event being called before it's set on the window. 3448 iWin.player = this.player; 3449 3450 // Create swf ready function for iFrame window 3451 iWin.ready = _V_.proxy(this.player, function(currSwf){ 3452 var el = iDoc.getElementById(currSwf), 3453 player = this, 3454 tech = player.tech; 3455 3456 // Update reference to playback technology element 3457 tech.el = el; 3458 3459 // Now that the element is ready, make a click on the swf play the video 3460 _V_.addEvent(el, "click", tech.proxy(tech.onClick)); 3461 3462 // Make sure swf is actually ready. Sometimes the API isn't actually yet. 3463 _V_.flash.checkReady(tech); 3464 }); 3465 3466 // Create event listener for all swf events 3467 iWin.events = _V_.proxy(this.player, function(swfID, eventName, other){ 3468 var player = this; 3469 if (player && player.techName == "flash") { 3470 player.triggerEvent(eventName); 3471 } 3472 }); 3473 3474 // Create error listener for all swf errors 3475 iWin.errors = _V_.proxy(this.player, function(swfID, eventName){ 3476 _V_.log("Flash Error", eventName); 3477 }); 3478 3479 })); 3480 3481 // Replace placeholder with iFrame (it will load now) 3482 placeHolder.parentNode.replaceChild(iFrm, placeHolder); 3483 3484 // If not using iFrame mode, embed as normal object 3485 } else { 3486 _V_.flash.embed(options.swf, placeHolder, flashVars, params, attributes); 3487 } 3488 }, 3489 3490 destroy: function(){ 3491 this.el.parentNode.removeChild(this.el); 3492 }, 3493 3494 // setupTriggers: function(){}, // Using global onEvent func to distribute events 3495 3496 play: function(){ this.el.vjs_play(); }, 3497 pause: function(){ this.el.vjs_pause(); }, 3498 src: function(src){ 3499 // Make sure source URL is abosolute. 3500 src = _V_.getAbsoluteURL(src); 3501 3502 this.el.vjs_src(src); 3503 3504 // Currently the SWF doesn't autoplay if you load a source later. 3505 // e.g. Load player w/ no source, wait 2s, set src. 3506 if (this.player.autoplay()) { 3507 var tech = this; 3508 setTimeout(function(){ tech.play(); }, 0); 3509 } 3510 }, 3511 load: function(){ this.el.vjs_load(); }, 3512 poster: function(){ this.el.vjs_getProperty("poster"); }, 3513 3514 buffered: function(){ 3515 return _V_.createTimeRange(0, this.el.vjs_getProperty("buffered")); 3516 }, 3517 3518 supportsFullScreen: function(){ 3519 return false; // Flash does not allow fullscreen through javascript 3520 }, 3521 enterFullScreen: function(){ 3522 return false;
3523 } 3524}); 3525 3526// Create setters and getters for attributes 3527(function(){ 3528 3529 var api = _V_.flash.prototype, 3530 readWrite = "preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted".split(","), 3531 readOnly = "error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks".split(","), 3532 callOnly = "load,play,pause".split(","); 3533 // Overridden: buffered 3534 3535 createSetter = function(attr){ 3536 var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1); 3537 api["set"+attrUpper] = function(val){ return this.el.vjs_setProperty(attr, val); }; 3538 }, 3539 3540 createGetter = function(attr){ 3541 api[attr] = function(){ return this.el.vjs_getProperty(attr); }; 3542 } 3543 ; 3544 3545 // Create getter and setters for all read/write attributes 3546 _V_.each(readWrite, function(attr){ 3547 createGetter(attr); 3548 createSetter(attr); 3549 }); 3550 3551 // Create getters for read-only attributes 3552 _V_.each(readOnly, function(attr){ 3553 createGetter(attr); 3554 }); 3555 3556})(); 3557 3558/* Flash Support Testing -------------------------------------------------------- */ 3559 3560_V_.flash.isSupported = function(){ 3561 return _V_.flash.version()[0] >= 10; 3562 // return swfobject.hasFlashPlayerVersion("10"); 3563}; 3564 3565_V_.flash.canPlaySource = function(srcObj){ 3566 if (srcObj.type in _V_.flash.prototype.support.formats) { return "maybe"; } 3567}; 3568 3569_V_.flash.prototype.support = { 3570 formats: { 3571 "video/flv": "FLV", 3572 "video/x-flv": "FLV", 3573 "video/mp4": "MP4", 3574 "video/m4v": "MP4" 3575 }, 3576 3577 // Optional events that we can manually mimic with timers 3578 progressEvent: false, 3579 timeupdateEvent: false, 3580 3581 // Resizing plugins using request fullscreen reloads the plugin 3582 fullscreenResize: false, 3583 3584 // Resizing plugins in Firefox always reloads the plugin (e.g. full window mode) 3585 parentResize: !(_V_.ua.match("Firefox")) 3586}; 3587 3588_V_.flash.onReady = function(currSwf){ 3589 3590 var el = _V_.el(currSwf); 3591 3592 // Get player from box 3593 // On firefox reloads, el might already have a player 3594 var player = el.player || el.parentNode.player, 3595 tech = player.tech; 3596 3597 // Reference player on tech element 3598 el.player = player; 3599 3600 // Update reference to playback technology element 3601 tech.el = el; 3602 3603 // Now that the element is ready, make a click on the swf play the video 3604 tech.addEvent("click", tech.onClick); 3605 3606 _V_.flash.checkReady(tech); 3607}; 3608 3609// The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object. 3610// If it's not ready, we set a timeout to check again shortly. 3611_V_.flash.checkReady = function(tech){ 3612 3613 // Check if API property exists 3614 if (tech.el.vjs_getProperty) { 3615 3616 // If so, tell tech it's ready 3617 tech.triggerReady(); 3618 3619 // Otherwise wait longer. 3620 } else { 3621 3622 setTimeout(function(){ 3623 _V_.flash.checkReady(tech); 3624 }, 50); 3625 3626 } 3627}; 3628 3629// Trigger events from the swf on the player 3630_V_.flash.onEvent = function(swfID, eventName){ 3631 var player = _V_.el(swfID).player; 3632 player.triggerEvent(eventName); 3633}; 3634 3635// Log errors from the swf 3636_V_.flash.onError = function(swfID, err){ 3637 var player = _V_.el(swfID).player; 3638 player.triggerEvent("error"); 3639 _V_.log("Flash Error", err, swfID); 3640}; 3641 3642// Flash Version Check 3643_V_.flash.version = function(){ 3644 var version = '0,0,0' 3645 3646 // IE 3647 try { 3648 version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1]; 3649 3650 // other browsers 3651 } catch(e) { 3652 try { 3653 if (navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){ 3654 version = (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g, ",").match(/^,?(.+),?$/)[1]; 3655 } 3656 } catch(e) {} 3657 } 3658 return version.split(","); 3659} 3660 3661// Flash embedding method. Only used in non-iframe mode 3662_V_.flash.embed = function(swf, placeHolder, flashVars, params, attributes){ 3663 var code = _V_.flash.getEmbedCode(swf, flashVars, params, attributes), 3664 3665 // Get element by embedding code and retrieving created element 3666 obj = _V_.createElement("div", { innerHTML: code }).childNodes[0], 3667 3668 par = placeHolder.parentNode 3669 ; 3670 3671 placeHolder.parentNode.replaceChild(obj, placeHolder); 3672 3673 // IE6 seems to have an issue where it won't initialize the swf object after injecting it. 3674 // This is a dumb temporary fix 3675 if (_V_.isIE()) { 3676 var newObj = par.childNodes[0]; 3677 setTimeout(function(){ 3678 newObj.style.display = "block"; 3679 }, 1000); 3680 } 3681 3682 return obj; 3683 3684}; 3685 3686_V_.flash.getEmbedCode = function(swf, flashVars, params, attributes){ 3687 3688 var objTag = '<object type="application/x-shockwave-flash"', 3689 flashVarsString = '', 3690 paramsString = '' 3691 attrsString = ''; 3692 3693 // Convert flash vars to string 3694 if (flashVars) { 3695 _V_.eachProp(flashVars, function(key, val){ 3696 flashVarsString += (key + "=" + val + "&"); 3697 }); 3698 } 3699 3700 // Add swf, flashVars, and other default params 3701 params = _V_.merge({ 3702 movie: swf, 3703 flashvars: flashVarsString, 3704 allowScriptAccess: "always", // Required to talk to swf 3705 allowNetworking: "all" // All should be default, but having security issues. 3706 }, params); 3707 3708 // Create param tags string 3709 _V_.eachProp(params, function(key, val){ 3710 paramsString += '<param name="'+key+'" value="'+val+'" />'; 3711 }); 3712 3713 attributes = _V_.merge({ 3714 // Add swf to attributes (need both for IE and Others to work) 3715 data: swf, 3716
3717 // Default to 100% width/height 3718 width: "100%", 3719 height: "100%" 3720 3721 }, attributes); 3722 3723 // Create Attributes string 3724 _V_.eachProp(attributes, function(key, val){ 3725 attrsString += (key + '="' + val + '" '); 3726 }); 3727 3728 return objTag + attrsString + '>' + paramsString + '</object>'; 3729} 3730// TEXT TRACKS 3731// Text tracks are tracks of timed text events. 3732// Captions - text displayed over the video for the hearing impared 3733// Subtitles - text displayed over the video for those who don't understand langauge in the video 3734// Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video 3735// Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device 3736 3737// Player Track Functions - Functions add to the player object for easier access to tracks 3738_V_.merge(_V_.Player.prototype, { 3739 3740 // Add an array of text tracks. captions, subtitles, chapters, descriptions 3741 // Track objects will be stored in the player.textTracks array 3742 addTextTracks: function(trackObjects){ 3743 var tracks = this.textTracks = (this.textTracks) ? this.textTracks : [], 3744 i = 0, j = trackObjects.length, track, Kind; 3745 3746 for (;i<j;i++) { 3747 // HTML5 Spec says default to subtitles. 3748 // Uppercase (uc) first letter to match class names 3749 Kind = _V_.uc(trackObjects[i].kind || "subtitles"); 3750 3751 // Create correct texttrack class. CaptionsTrack, etc. 3752 track = new _V_[Kind + "Track"](this, trackObjects[i]); 3753 3754 tracks.push(track); 3755 3756 // If track.default is set, start showing immediately 3757 // TODO: Add a process to deterime the best track to show for the specific kind 3758 // Incase there are mulitple defaulted tracks of the same kind 3759 // Or the user has a set preference of a specific language that should override the default 3760 if (track['default']) { 3761 this.ready(_V_.proxy(track, track.show)); 3762 } 3763 } 3764 3765 // Return the track so it can be appended to the display component 3766 return this; 3767 }, 3768 3769 // Show a text track 3770 // disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.) 3771 showTextTrack: function(id, disableSameKind){ 3772 var tracks = this.textTracks, 3773 i = 0, 3774 j = tracks.length, 3775 track, showTrack, kind; 3776 3777 // Find Track with same ID 3778 for (;i<j;i++) { 3779 track = tracks[i]; 3780 if (track.id === id) { 3781 track.show(); 3782 showTrack = track; 3783 3784 // Disable tracks of the same kind 3785 } else if (disableSameKind && track.kind == disableSameKind && track.mode > 0) { 3786 track.disable(); 3787 } 3788 } 3789 3790 // Get track kind from shown track or disableSameKind 3791 kind = (showTrack) ? showTrack.kind : ((disableSameKind) ? disableSameKind : false); 3792 3793 // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc. 3794 if (kind) { 3795 this.triggerEvent(kind+"trackchange"); 3796 } 3797 3798 return this; 3799 } 3800 3801}); 3802 3803// Track Class 3804// Contains track methods for loading, showing, parsing cues of tracks 3805_V_.Track = _V_.Component.extend({ 3806 3807 init: function(player, options){ 3808 this._super(player, options); 3809 3810 // Apply track info to track object 3811 // Options will often be a track element 3812 _V_.merge(this, { 3813 // Build ID if one doesn't exist 3814 id: options.id || ("vjs_" + options.kind + "_" + options.language + "_" + _V_.guid++), 3815 3816 src: options.src, 3817 3818 // If default is used, subtitles/captions to start showing 3819 "default": options["default"], // 'default' is reserved-ish 3820 title: options.title, 3821 3822 // Language - two letter string to represent track language, e.g. "en" for English 3823 // readonly attribute DOMString language; 3824 language: options.srclang, 3825 3826 // Track label e.g. "English" 3827 // readonly attribute DOMString label; 3828 label: options.label, 3829 3830 // All cues of the track. Cues have a startTime, endTime, text, and other properties. 3831 // readonly attribute TextTrackCueList cues; 3832 cues: [], 3833 3834 // ActiveCues is all cues that are currently showing 3835 // readonly attribute TextTrackCueList activeCues; 3836 activeCues: [], 3837 3838 // ReadyState describes if the text file has been loaded 3839 // const unsigned short NONE = 0; 3840 // const unsigned short LOADING = 1; 3841 // const unsigned short LOADED = 2; 3842 // const unsigned short ERROR = 3; 3843 // readonly attribute unsigned short readyState; 3844 readyState: 0, 3845 3846 // Mode describes if the track is showing, hidden, or disabled 3847 // const unsigned short OFF = 0; 3848 // const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible) 3849 // const unsigned short SHOWING = 2; 3850 // attribute unsigned short mode; 3851 mode: 0 3852 }); 3853 }, 3854 3855 // Create basic div to hold cue text 3856 createElement: function(){ 3857 return this._super("div", { 3858 className: "vjs-" + this.kind + " vjs-text-track" 3859 }); 3860 }, 3861 3862 // Show: Mode Showing (2) 3863 // Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily. 3864 // The user agent is maintaining a list of which cues are active, and events are being fired accordingly. 3865 // In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate; 3866 // for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a
3866non-visual fashion; 3867 // and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue. 3868 // The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute. 3869 // This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences. 3870 show: function(){ 3871 this.activate(); 3872 3873 this.mode = 2; 3874 3875 // Show element. 3876 this._super(); 3877 }, 3878 3879 // Hide: Mode Hidden (1) 3880 // Indicates that the text track is active, but that the user agent is not actively displaying the cues. 3881 // If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily. 3882 // The user agent is maintaining a list of which cues are active, and events are being fired accordingly. 3883 hide: function(){ 3884 // When hidden, cues are still triggered. Disable to stop triggering. 3885 this.activate(); 3886 3887 this.mode = 1; 3888 3889 // Hide element. 3890 this._super(); 3891 }, 3892 3893 // Disable: Mode Off/Disable (0) 3894 // Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track. 3895 // No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues. 3896 disable: function(){ 3897 // If showing, hide. 3898 if (this.mode == 2) { this.hide(); } 3899 3900 // Stop triggering cues 3901 this.deactivate(); 3902 3903 // Switch Mode to Off 3904 this.mode = 0; 3905 }, 3906 3907 // Turn on cue tracking. Tracks that are showing OR hidden are active. 3908 activate: function(){ 3909 // Load text file if it hasn't been yet. 3910 if (this.readyState == 0) { this.load(); } 3911 3912 // Only activate if not already active. 3913 if (this.mode == 0) { 3914 // Update current cue on timeupdate 3915 // Using unique ID for proxy function so other tracks don't remove listener 3916 this.player.addEvent("timeupdate", this.proxy(this.update, this.id)); 3917 3918 // Reset cue time on media end 3919 this.player.addEvent("ended", this.proxy(this.reset, this.id)); 3920 3921 // Add to display 3922 if (this.kind == "captions" || this.kind == "subtitles") { 3923 this.player.textTrackDisplay.addComponent(this); 3924 } 3925 } 3926 }, 3927 3928 // Turn off cue tracking. 3929 deactivate: function(){ 3930 // Using unique ID for proxy function so other tracks don't remove listener 3931 this.player.removeEvent("timeupdate", this.proxy(this.update, this.id)); 3932 this.player.removeEvent("ended", this.proxy(this.reset, this.id)); 3933 this.reset(); // Reset 3934 3935 // Remove from display 3936 this.player.textTrackDisplay.removeComponent(this); 3937 }, 3938 3939 // A readiness state 3940 // One of the following: 3941 // 3942 // Not loaded 3943 // Indicates that the text track is known to exist (e.g. it has been declared with a track element), but its cues have not been obtained. 3944 // 3945 // Loading 3946 // Indicates that the text track is loading and there have been no fatal errors encountered so far. Further cues might still be added to the track. 3947 // 3948 // Loaded 3949 // Indicates that the text track has been loaded with no fatal errors. No new cues will be added to the track except if the text track corresponds to a MutableTextTrack object. 3950 // 3951 // Failed to load 3952 // Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained. 3953 load: function(){ 3954 3955 // Only load if not loaded yet. 3956 if (this.readyState == 0) { 3957 this.readyState = 1; 3958 _V_.get(this.src, this.proxy(this.parseCues), this.proxy(this.onError)); 3959 } 3960 3961 }, 3962 3963 onError: function(err){ 3964 this.error = err; 3965 this.readyState = 3; 3966 this.triggerEvent("error"); 3967 }, 3968 3969 // Parse the WebVTT text format for cue times. 3970 // TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP) 3971 parseCues: function(srcContent) { 3972 var cue, time, text, 3973 lines = srcContent.split("\n"), 3974 line = "", id; 3975 3976 for (var i=1, j=lines.length; i<j; i++) { 3977 // Line 0 should be 'WEBVTT', so skipping i=0 3978 3979 line = _V_.trim(lines[i]);
3979 // Trim whitespace and linebreaks 3980 3981 if (line) { // Loop until a line with content 3982 3983 // First line could be an optional cue ID 3984 // Check if line has the time separator 3985 if (line.indexOf("-->") == -1) { 3986 id = line; 3987 // Advance to next line for timing. 3988 line = _V_.trim(lines[++i]); 3989 } else { 3990 id = this.cues.length; 3991 } 3992 3993 // First line - Number 3994 cue = { 3995 id: id, // Cue Number 3996 index: this.cues.length // Position in Array 3997 }; 3998 3999 // Timing line 4000 time = line.split(" --> "); 4001 cue.startTime = this.parseCueTime(time[0]); 4002 cue.endTime = this.parseCueTime(time[1]); 4003 4004 // Additional lines - Cue Text 4005 text = []; 4006 4007 // Loop until a blank line or end of lines 4008 // Assumeing trim("") returns false for blank lines 4009 while (lines[++i] && (line = _V_.trim(lines[i]))) { 4010 text.push(line); 4011 } 4012 4013 cue.text = text.join('<br/>'); 4014 4015 // Add this cue 4016 this.cues.push(cue); 4017 } 4018 } 4019 4020 this.readyState = 2; 4021 this.triggerEvent("loaded"); 4022 }, 4023 4024 parseCueTime: function(timeText) { 4025 var parts = timeText.split(':'), 4026 time = 0, 4027 hours, minutes, other, seconds, ms, flags; 4028 4029 // Check if optional hours place is included 4030 // 00:00:00.000 vs. 00:00.000 4031 if (parts.length == 3) { 4032 hours = parts[0]; 4033 minutes = parts[1]; 4034 other = parts[2]; 4035 } else { 4036 hours = 0; 4037 minutes = parts[0]; 4038 other = parts[1]; 4039 } 4040 4041 // Break other (seconds, milliseconds, and flags) by spaces 4042 // TODO: Make additional cue layout settings work with flags 4043 other = other.split(/\s+/) 4044 // Remove seconds. Seconds is the first part before any spaces. 4045 seconds = other.splice(0,1)[0]; 4046 // Could use either . or , for decimal 4047 seconds = seconds.split(/\.|,/); 4048 // Get milliseconds 4049 ms = parseFloat(seconds[1]); 4050 seconds = seconds[0]; 4051 4052 // hours => seconds 4053 time += parseFloat(hours) * 3600; 4054 // minutes => seconds 4055 time += parseFloat(minutes) * 60; 4056 // Add seconds 4057 time += parseFloat(seconds); 4058 // Add milliseconds 4059 if (ms) { time += ms/1000; } 4060 4061 return time; 4062 }, 4063 4064 // Update active cues whenever timeupdate events are triggered on the player. 4065 update: function(){ 4066 if (this.cues.length > 0) { 4067 4068 // Get curent player time 4069 var time = this.player.currentTime(); 4070 4071 // Check if the new time is outside the time box created by the the last update. 4072 if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) { 4073 var cues = this.cues, 4074 4075 // Create a new time box for this state. 4076 newNextChange = this.player.duration(), // Start at beginning of the timeline 4077 newPrevChange = 0, // Start at end 4078 4079 reverse = false, // Set the direction of the loop through the cues. Optimized the cue check. 4080 newCues = [], // Store new active cues. 4081 4082 // Store where in the loop the current active cues are, to provide a smart starting point for the next loop. 4083 firstActiveIndex, lastActiveIndex, 4084 4085 html = "", // Create cue text HTML to add to the display 4086 cue, i, j; // Loop vars 4087 4088 // Check if time is going forwards or backwards (scrubbing/rewinding) 4089 // If we know the direction we can optimize the starting position and direction of the loop through the cues array. 4090 if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen 4091 // Forwards, so start at the index of the first active cue and loop forward 4092 i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0; 4093 } else { 4094 // Backwards, so start at the index of the last active cue and loop backward 4095 reverse = true; 4096 i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1; 4097 } 4098 4099 while (true) { // Loop until broken 4100 cue = cues[i]; 4101 4102 // Cue ended at this point 4103 if (cue.endTime <= time) { 4104 newPrevChange = Math.max(newPrevChange, cue.endTime); 4105 4106 if (cue.active) { 4107 cue.active = false;
4108 } 4109 4110 // No earlier cues should have an active start time. 4111 // Nevermind. Assume first cue could have a duration the same as the video. 4112 // In that case we need to loop all the way back to the beginning. 4113 // if (reverse && cue.startTime) { break; } 4114 4115 // Cue hasn't started 4116 } else if (time < cue.startTime) { 4117 newNextChange = Math.min(newNextChange, cue.startTime); 4118 4119 if (cue.active) { 4120 cue.active = false; 4121 } 4122 4123 // No later cues should have an active start time. 4124 if (!reverse) { break; } 4125 4126 // Cue is current 4127 } else { 4128 4129 if (reverse) { 4130 // Add cue to front of array to keep in time order 4131 newCues.splice(0,0,cue); 4132 4133 // If in reverse, the first current cue is our lastActiveCue 4134 if (lastActiveIndex === undefined) { lastActiveIndex = i; } 4135 firstActiveIndex = i; 4136 } else { 4137 // Add cue to end of array 4138 newCues.push(cue); 4139 4140 // If forward, the first current cue is our firstActiveIndex 4141 if (firstActiveIndex === undefined) { firstActiveIndex = i; } 4142 lastActiveIndex = i; 4143 } 4144 4145 newNextChange = Math.min(newNextChange, cue.endTime); 4146 newPrevChange = Math.max(newPrevChange, cue.startTime); 4147 4148 cue.active = true; 4149 } 4150 4151 if (reverse) { 4152 // Reverse down the array of cues, break if at first 4153 if (i === 0) { break; } else { i--; } 4154 } else { 4155 // Walk up the array fo cues, break if at last 4156 if (i === cues.length - 1) { break; } else { i++; } 4157 } 4158 4159 } 4160 4161 this.activeCues = newCues; 4162 this.nextChange = newNextChange; 4163 this.prevChange = newPrevChange; 4164 this.firstActiveIndex = firstActiveIndex; 4165 this.lastActiveIndex = lastActiveIndex; 4166 4167 this.updateDisplay(); 4168 4169 this.triggerEvent("cuechange"); 4170 } 4171 } 4172 }, 4173 4174 // Add cue HTML to display 4175 updateDisplay: function(){ 4176 var cues = this.activeCues, 4177 html = "", 4178 i=0,j=cues.length; 4179 4180 for (;i<j;i++) { 4181 html += "<span class='vjs-tt-cue'>"+cues[i].text+"</span>"; 4182 } 4183 4184 this.el.innerHTML = html; 4185 }, 4186 4187 // Set all loop helper values back 4188 reset: function(){ 4189 this.nextChange = 0; 4190 this.prevChange = this.player.duration(); 4191 this.firstActiveIndex = 0; 4192 this.lastActiveIndex = 0; 4193 } 4194 4195}); 4196 4197// Create specific track types 4198_V_.CaptionsTrack = _V_.Track.extend({ 4199 kind: "captions" 4200}); 4201 4202_V_.SubtitlesTrack = _V_.Track.extend({ 4203 kind: "subtitles" 4204}); 4205 4206_V_.ChaptersTrack = _V_.Track.extend({ 4207 kind: "chapters" 4208}); 4209 4210 4211/* Text Track Display 4212================================================================================ */ 4213// Global container for both subtitle and captions text. Simple div container. 4214_V_.TextTrackDisplay = _V_.Component.extend({ 4215 4216 createElement: function(){ 4217 return this._super("div", { 4218 className: "vjs-text-track-display" 4219 }); 4220 } 4221 4222}); 4223 4224/* Text Track Menu Items 4225================================================================================ */ 4226_V_.TextTrackMenuItem = _V_.MenuItem.extend({ 4227 4228 init: function(player, options){ 4229 var track = this.track = options.track; 4230 4231 // Modify options for parent MenuItem class's init. 4232 options.label = track.label; 4233 options.selected = track["default"]; 4234 this._super(player, options); 4235 4236 this.player.addEvent(track.kind + "trackchange", _V_.proxy(this, this.update)); 4237 }, 4238 4239 onClick: function(){ 4240 this._super(); 4241 this.player.showTextTrack(this.track.id, this.track.kind); 4242 }, 4243 4244 update: function(){ 4245 if (this.track.mode == 2) { 4246 this.selected(true); 4247 } else { 4248 this.selected(false); 4249 } 4250 } 4251 4252}); 4253 4254_V_.OffTextTrackMenuItem = _V_.TextTrackMenuItem.extend({ 4255 4256 init: function(player, options){ 4257 // Create pseudo track info 4258 // Requires options.kind 4259 options.track = { kind: options.kind, player: player, label: "Off" } 4260 this._super(player, options); 4261 }, 4262 4263 onClick: function(){ 4264 this._super(); 4265 this.player.showTextTrack(this.track.id, this.track.kind); 4266 }, 4267 4268 update: function(){ 4269 var tracks = this.player.textTracks, 4270 i=0, j=tracks.length, track, 4271 off = true; 4272 4273 for (;i<j;i++) { 4274 track = tracks[i]; 4275 if (track.kind == this.track.kind && track.mode == 2) { 4276 off = false;
4277 } 4278 } 4279 4280 if (off) { 4281 this.selected(true); 4282 } else { 4283 this.selected(false); 4284 } 4285 } 4286 4287}); 4288 4289/* Captions Button 4290================================================================================ */ 4291_V_.TextTrackButton = _V_.Button.extend({ 4292 4293 init: function(player, options){ 4294 this._super(player, options); 4295 4296 this.menu = this.createMenu(); 4297 4298 if (this.items.length === 0) { 4299 this.hide(); 4300 } 4301 }, 4302 4303 createMenu: function(){ 4304 var menu = new _V_.Menu(this.player); 4305 4306 // Add a title list item to the top 4307 menu.el.appendChild(_V_.createElement("li", { 4308 className: "vjs-menu-title", 4309 innerHTML: _V_.uc(this.kind) 4310 })); 4311 4312 // Add an OFF menu item to turn all tracks off 4313 menu.addItem(new _V_.OffTextTrackMenuItem(this.player, { kind: this.kind })) 4314 4315 this.items = this.createItems(); 4316 4317 // Add menu items to the menu 4318 this.each(this.items, function(item){ 4319 menu.addItem(item); 4320 }); 4321 4322 // Add list to element 4323 this.addComponent(menu); 4324 4325 return menu; 4326 }, 4327 4328 // Create a menu item for each text track 4329 createItems: function(){ 4330 var items = []; 4331 this.each(this.player.textTracks, function(track){ 4332 if (track.kind === this.kind) { 4333 items.push(new _V_.TextTrackMenuItem(this.player, { 4334 track: track 4335 })); 4336 } 4337 }); 4338 return items; 4339 }, 4340 4341 buildCSSClass: function(){ 4342 return this.className + " vjs-menu-button " + this._super(); 4343 }, 4344 4345 // Focus - Add keyboard functionality to element 4346 onFocus: function(){ 4347 // Show the menu, and keep showing when the menu items are in focus 4348 this.menu.lockShowing(); 4349 // this.menu.el.style.display = "block"; 4350 4351 // When tabbing through, the menu should hide when focus goes from the last menu item to the next tabbed element. 4352 _V_.one(this.menu.el.childNodes[this.menu.el.childNodes.length - 1], "blur", this.proxy(function(){ 4353 this.menu.unlockShowing(); 4354 })); 4355 }, 4356 // Can't turn off list display that we turned on with focus, because list would go away. 4357 onBlur: function(){}, 4358 4359 onClick: function(){ 4360 // When you click the button it adds focus, which will show the menu indefinitely. 4361 // So we'll remove focus when the mouse leaves the button. 4362 // Focus is needed for tab navigation. 4363 this.one("mouseout", this.proxy(function(){ 4364 this.menu.unlockShowing(); 4365 this.el.blur(); 4366 })); 4367 } 4368 4369}); 4370 4371_V_.CaptionsButton = _V_.TextTrackButton.extend({ 4372 kind: "captions", 4373 buttonText: "Captions", 4374 className: "vjs-captions-button" 4375}); 4376 4377_V_.SubtitlesButton = _V_.TextTrackButton.extend({ 4378 kind: "subtitles", 4379 buttonText: "Subtitles", 4380 className: "vjs-subtitles-button" 4381}); 4382 4383// Chapters act much differently than other text tracks 4384// Cues are navigation vs. other tracks of alternative languages 4385_V_.ChaptersButton = _V_.TextTrackButton.extend({ 4386 kind: "chapters", 4387 buttonText: "Chapters", 4388 className: "vjs-chapters-button", 4389 4390 // Create a menu item for each text track 4391 createItems: function(chaptersTrack){ 4392 var items = []; 4393 4394 this.each(this.player.textTracks, function(track){ 4395 if (track.kind === this.kind) { 4396 items.push(new _V_.TextTrackMenuItem(this.player, { 4397 track: track 4398 })); 4399 } 4400 }); 4401 4402 return items; 4403 }, 4404 4405 createMenu: function(){ 4406 var tracks = this.player.textTracks, 4407 i = 0, 4408 j = tracks.length, 4409 track, chaptersTrack, 4410 items = this.items = []; 4411 4412 for (;i<j;i++) { 4413 track = tracks[i]; 4414 if (track.kind == this.kind && track["default"]) { 4415 if (track.readyState < 2) { 4416 this.chaptersTrack = track; 4417 track.addEvent("loaded", this.proxy(this.createMenu)); 4418 return; 4419 } else { 4420 chaptersTrack = track; 4421 break; 4422 } 4423 } 4424 } 4425 4426 var menu = this.menu = new _V_.Menu(this.player); 4427 4428 menu.el.appendChild(_V_.createElement("li", { 4429 className: "vjs-menu-title", 4430 innerHTML: _V_.uc(this.kind) 4431 })); 4432 4433 if (chaptersTrack) { 4434 var cues = chaptersTrack.cues, 4435 i = 0, j = cues.length, cue, mi; 4436 4437 for (;i<j;i++) { 4438 cue = cues[i]; 4439 4440 mi = new _V_.ChaptersTrackMenuItem(this.player, { 4441 track: chaptersTrack,
4442 cue: cue 4443 }); 4444 4445 items.push(mi); 4446 4447 menu.addComponent(mi); 4448 } 4449 } 4450 4451 // Add list to element 4452 this.addComponent(menu); 4453 4454 if (this.items.length > 0) { 4455 this.show(); 4456 } 4457 4458 return menu; 4459 } 4460 4461}); 4462 4463_V_.ChaptersTrackMenuItem = _V_.MenuItem.extend({ 4464 4465 init: function(player, options){ 4466 var track = this.track = options.track, 4467 cue = this.cue = options.cue, 4468 currentTime = player.currentTime(); 4469 4470 // Modify options for parent MenuItem class's init. 4471 options.label = cue.text; 4472 options.selected = (cue.startTime <= currentTime && currentTime < cue.endTime); 4473 this._super(player, options); 4474 4475 track.addEvent("cuechange", _V_.proxy(this, this.update)); 4476 }, 4477 4478 onClick: function(){ 4479 this._super(); 4480 this.player.currentTime(this.cue.startTime); 4481 this.update(this.cue.startTime); 4482 }, 4483 4484 update: function(time){ 4485 var cue = this.cue, 4486 currentTime = this.player.currentTime(); 4487 4488 // _V_.log(currentTime, cue.startTime); 4489 if (cue.startTime <= currentTime && currentTime < cue.endTime) { 4490 this.selected(true); 4491 } else { 4492 this.selected(false); 4493 } 4494 } 4495 4496}); 4497 4498// Add Buttons to controlBar 4499_V_.merge(_V_.ControlBar.prototype.options.components, { 4500 "subtitlesButton": {}, 4501 "captionsButton": {}, 4502 "chaptersButton": {} 4503}); 4504 4505// _V_.Cue = _V_.Component.extend({ 4506// init: function(player, options){ 4507// this._super(player, options); 4508// } 4509// });// Automatically set up any tags that have a data-setup attribute 4510_V_.autoSetup = function(){ 4511 var options, vid, player, 4512 vids = document.getElementsByTagName("video"); 4513 4514 // Check if any media elements exist 4515 if (vids && vids.length > 0) { 4516 4517 for (var i=0,j=vids.length; i<j; i++) { 4518 vid = vids[i]; 4519 4520 // Check if element exists, has getAttribute func. 4521 // IE seems to consider typeof el.getAttribute == "object" instead of "function" like expected, at least when loading the player immediately. 4522 if (vid && vid.getAttribute) { 4523 4524 // Make sure this player hasn't already been set up. 4525 if (vid.player === undefined) { 4526 options = vid.getAttribute("data-setup"); 4527 4528 // Check if data-setup attr exists. 4529 // We only auto-setup if they've added the data-setup attr. 4530 if (options !== null) { 4531 4532 // Parse options JSON 4533 // If empty string, make it a parsable json object. 4534 options = JSON.parse(options || "{}"); 4535 4536 // Create new video.js instance. 4537 player = _V_(vid, options); 4538 } 4539 } 4540 4541 // If getAttribute isn't defined, we need to wait for the DOM. 4542 } else { 4543 _V_.autoSetupTimeout(1); 4544 break; 4545 } 4546 } 4547 4548 // No videos were found, so keep looping unless page is finisehd loading. 4549 } else if (!_V_.windowLoaded) { 4550 _V_.autoSetupTimeout(1); 4551 } 4552}; 4553 4554// Pause to let the DOM keep processing 4555_V_.autoSetupTimeout = function(wait){ 4556 setTimeout(_V_.autoSetup, wait); 4557}; 4558 4559_V_.addEvent(window, "load", function(){ 4560 _V_.windowLoaded = true; 4561}); 4562 4563// Run Auto-load players 4564_V_.autoSetup(); 4565// Expose to global 4566window.VideoJS = window._V_ = VideoJS; 4567 4568// End self-executing function 4569})(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.