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