You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7046 lines
223 KiB

2 years ago
  1. /**
  2. * @fileoverview Main function src.
  3. */
  4. // HTML5 Shiv. Must be in <head> to support older browsers.
  5. document.createElement('video');
  6. document.createElement('audio');
  7. document.createElement('track');
  8. /**
  9. * Doubles as the main function for users to create a player instance and also
  10. * the main library object.
  11. *
  12. * **ALIASES** videojs, _V_ (deprecated)
  13. *
  14. * The `vjs` function can be used to initialize or retrieve a player.
  15. *
  16. * var myPlayer = vjs('my_video_id');
  17. *
  18. * @param {String|Element} id Video element or video element ID
  19. * @param {Object=} options Optional options object for config/settings
  20. * @param {Function=} ready Optional ready callback
  21. * @return {vjs.Player} A player instance
  22. * @namespace
  23. */
  24. var vjs = function (id, options, ready) {
  25. var tag; // Element of ID
  26. // Allow for element or ID to be passed in
  27. // String ID
  28. if (typeof id === 'string') {
  29. // Adjust for jQuery ID syntax
  30. if (id.indexOf('#') === 0) {
  31. id = id.slice(1);
  32. }
  33. // If a player instance has already been created for this ID return it.
  34. if (vjs.players[id]) {
  35. return vjs.players[id];
  36. // Otherwise get element for ID
  37. } else {
  38. tag = vjs.el(id);
  39. }
  40. // ID is a media element
  41. } else {
  42. tag = id;
  43. }
  44. // Check for a useable element
  45. if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also
  46. throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns
  47. }
  48. // Element may have a player attr referring to an already created player instance.
  49. // If not, set up a new player and return the instance.
  50. return tag['player'] || new vjs.Player(tag, options, ready);
  51. };
  52. // Extended name, also available externally, window.videojs
  53. var videojs = vjs;
  54. window.videojs = window.vjs = vjs;
  55. // CDN Version. Used to target right flash swf.
  56. vjs.CDN_VERSION = '4.3';
  57. vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
  58. /**
  59. * Global Player instance options, surfaced from vjs.Player.prototype.options_
  60. * vjs.options = vjs.Player.prototype.options_
  61. * All options should use string keys so they avoid
  62. * renaming by closure compiler
  63. * @type {Object}
  64. */
  65. vjs.options = {
  66. // Default order of fallback technology
  67. 'techOrder': ['html5', 'flash'],
  68. // techOrder: ['flash','html5'],
  69. 'html5': {},
  70. 'flash': {},
  71. // Default of web browser is 300x150. Should rely on source width/height.
  72. 'width': 300,
  73. 'height': 150,
  74. // defaultVolume: 0.85,
  75. 'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!
  76. // Included control sets
  77. 'children': {
  78. 'mediaLoader': {},
  79. 'posterImage': {},
  80. 'textTrackDisplay': {},
  81. 'loadingSpinner': {},
  82. 'bigPlayButton': {},
  83. 'controlBar': {}
  84. },
  85. // Default message to show when a video cannot be played.
  86. 'notSupportedMessage': 'Sorry, no compatible source and playback ' +
  87. 'technology were found for this video. Try using another browser ' +
  88. 'like <a href="http://bit.ly/ccMUEC">Chrome</a> or download the ' +
  89. 'latest <a href="http://adobe.ly/mwfN1">Adobe Flash Player</a>.'
  90. };
  91. // Set CDN Version of swf
  92. // The added (+) blocks the replace from changing this 4.3 string
  93. if (vjs.CDN_VERSION !== 'GENERATED' + '_CDN_VSN') {
  94. videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/' + vjs.CDN_VERSION + '/video-js.swf';
  95. }
  96. /**
  97. * Global player list
  98. * @type {Object}
  99. */
  100. vjs.players = {};
  101. /**
  102. * Core Object/Class for objects that use inheritance + contstructors
  103. *
  104. * To create a class that can be subclassed itself, extend the CoreObject class.
  105. *
  106. * var Animal = CoreObject.extend();
  107. * var Horse = Animal.extend();
  108. *
  109. * The constructor can be defined through the init property of an object argument.
  110. *
  111. * var Animal = CoreObject.extend({
  112. * init: function(name, sound){
  113. * this.name = name;
  114. * }
  115. * });
  116. *
  117. * Other methods and properties can be added the same way, or directly to the
  118. * prototype.
  119. *
  120. * var Animal = CoreObject.extend({
  121. * init: function(name){
  122. * this.name = name;
  123. * },
  124. * getName: function(){
  125. * return this.name;
  126. * },
  127. * sound: '...'
  128. * });
  129. *
  130. * Animal.prototype.makeSound = function(){
  131. * alert(this.sound);
  132. * };
  133. *
  134. * To create an instance of a class, use the create method.
  135. *
  136. * var fluffy = Animal.create('Fluffy');
  137. * fluffy.getName(); // -> Fluffy
  138. *
  139. * Methods and properties can be overridden in subclasses.
  140. *
  141. * var Horse = Animal.extend({
  142. * sound: 'Neighhhhh!'
  143. * });
  144. *
  145. * var horsey = Horse.create('Horsey');
  146. * horsey.getName(); // -> Horsey
  147. * horsey.makeSound(); // -> Alert: Neighhhhh!
  148. *
  149. * @class
  150. * @constructor
  151. */
  152. vjs.CoreObject = vjs['CoreObject'] = function () { };
  153. // Manually exporting vjs['CoreObject'] here for Closure Compiler
  154. // because of the use of the extend/create class methods
  155. // If we didn't do this, those functions would get flattend to something like
  156. // `a = ...` and `this.prototype` would refer to the global object instead of
  157. // CoreObject
  158. /**
  159. * Create a new object that inherits from this Object
  160. *
  161. * var Animal = CoreObject.extend();
  162. * var Horse = Animal.extend();
  163. *
  164. * @param {Object} props Functions and properties to be applied to the
  165. * new object's prototype
  166. * @return {vjs.CoreObject} An object that inherits from CoreObject
  167. * @this {*}
  168. */
  169. vjs.CoreObject.extend = function (props) {
  170. var init, subObj;
  171. props = props || {};
  172. // Set up the constructor using the supplied init method
  173. // or using the init of the parent object
  174. // Make sure to check the unobfuscated version for external libs
  175. init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function () { };
  176. // In Resig's simple class inheritance (previously used) the constructor
  177. // is a function that calls `this.init.apply(arguments)`
  178. // However that would prevent us from using `ParentObject.call(this);`
  179. // in a Child constuctor because the `this` in `this.init`
  180. // would still refer to the Child and cause an inifinite loop.
  181. // We would instead have to do
  182. // `ParentObject.prototype.init.apply(this, argumnents);`
  183. // Bleh. We're not creating a _super() function, so it's good to keep
  184. // the parent constructor reference simple.
  185. subObj = function () {
  186. init.apply(this, arguments);
  187. };
  188. // Inherit from this object's prototype
  189. subObj.prototype = vjs.obj.create(this.prototype);
  190. // Reset the constructor property for subObj otherwise
  191. // instances of subObj would have the constructor of the parent Object
  192. subObj.prototype.constructor = subObj;
  193. // Make the class extendable
  194. subObj.extend = vjs.CoreObject.extend;
  195. // Make a function for creating instances
  196. subObj.create = vjs.CoreObject.create;
  197. // Extend subObj's prototype with functions and other properties from props
  198. for (var name in props) {
  199. if (props.hasOwnProperty(name)) {
  200. subObj.prototype[name] = props[name];
  201. }
  202. }
  203. return subObj;
  204. };
  205. /**
  206. * Create a new instace of this Object class
  207. *
  208. * var myAnimal = Animal.create();
  209. *
  210. * @return {vjs.CoreObject} An instance of a CoreObject subclass
  211. * @this {*}
  212. */
  213. vjs.CoreObject.create = function () {
  214. // Create a new object that inherits from this object's prototype
  215. var inst = vjs.obj.create(this.prototype);
  216. // Apply this constructor function to the new object
  217. this.apply(inst, arguments);
  218. // Return the new object
  219. return inst;
  220. };
  221. /**
  222. * @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
  223. * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
  224. * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
  225. * robust as jquery's, so there's probably some differences.
  226. */
  227. /**
  228. * Add an event listener to element
  229. * It stores the handler function in a separate cache object
  230. * and adds a generic handler to the element's event,
  231. * along with a unique id (guid) to the element.
  232. * @param {Element|Object} elem Element or object to bind listeners to
  233. * @param {String} type Type of event to bind to.
  234. * @param {Function} fn Event listener.
  235. * @private
  236. */
  237. vjs.on = function (elem, type, fn) {
  238. var data = vjs.getData(elem);
  239. // We need a place to store all our handler data
  240. if (!data.handlers) data.handlers = {};
  241. if (!data.handlers[type]) data.handlers[type] = [];
  242. if (!fn.guid) fn.guid = vjs.guid++;
  243. data.handlers[type].push(fn);
  244. if (!data.dispatcher) {
  245. data.disabled = false;
  246. data.dispatcher = function (event) {
  247. if (data.disabled) return;
  248. event = vjs.fixEvent(event);
  249. var handlers = data.handlers[event.type];
  250. if (handlers) {
  251. // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
  252. var handlersCopy = handlers.slice(0);
  253. for (var m = 0, n = handlersCopy.length; m < n; m++) {
  254. if (event.isImmediatePropagationStopped()) {
  255. break;
  256. } else {
  257. handlersCopy[m].call(elem, event);
  258. }
  259. }
  260. }
  261. };
  262. }
  263. if (data.handlers[type].length == 1) {
  264. if (document.addEventListener) {
  265. elem.addEventListener(type, data.dispatcher, false);
  266. } else if (document.attachEvent) {
  267. elem.attachEvent('on' + type, data.dispatcher);
  268. }
  269. }
  270. };
  271. /**
  272. * Removes event listeners from an element
  273. * @param {Element|Object} elem Object to remove listeners from
  274. * @param {String=} type Type of listener to remove. Don't include to remove all events from element.
  275. * @param {Function} fn Specific listener to remove. Don't incldue to remove listeners for an event type.
  276. * @private
  277. */
  278. vjs.off = function (elem, type, fn) {
  279. // Don't want to add a cache object through getData if not needed
  280. if (!vjs.hasData(elem)) return;
  281. var data = vjs.getData(elem);
  282. // If no events exist, nothing to unbind
  283. if (!data.handlers) { return; }
  284. // Utility function
  285. var removeType = function (t) {
  286. data.handlers[t] = [];
  287. vjs.cleanUpEvents(elem, t);
  288. };
  289. // Are we removing all bound events?
  290. if (!type) {
  291. for (var t in data.handlers) removeType(t);
  292. return;
  293. }
  294. var handlers = data.handlers[type];
  295. // If no handlers exist, nothing to unbind
  296. if (!handlers) return;
  297. // If no listener was provided, remove all listeners for type
  298. if (!fn) {
  299. removeType(type);
  300. return;
  301. }
  302. // We're only removing a single handler
  303. if (fn.guid) {
  304. for (var n = 0; n < handlers.length; n++) {
  305. if (handlers[n].guid === fn.guid) {
  306. handlers.splice(n--, 1);
  307. }
  308. }
  309. }
  310. vjs.cleanUpEvents(elem, type);
  311. };
  312. /**
  313. * Clean up the listener cache and dispatchers
  314. * @param {Element|Object} elem Element to clean up
  315. * @param {String} type Type of event to clean up
  316. * @private
  317. */
  318. vjs.cleanUpEvents = function (elem, type) {
  319. var data = vjs.getData(elem);
  320. // Remove the events of a particular type if there are none left
  321. if (data.handlers[type].length === 0) {
  322. delete data.handlers[type];
  323. // data.handlers[type] = null;
  324. // Setting to null was causing an error with data.handlers
  325. // Remove the meta-handler from the element
  326. if (document.removeEventListener) {
  327. elem.removeEventListener(type, data.dispatcher, false);
  328. } else if (document.detachEvent) {
  329. elem.detachEvent('on' + type, data.dispatcher);
  330. }
  331. }
  332. // Remove the events object if there are no types left
  333. if (vjs.isEmpty(data.handlers)) {
  334. delete data.handlers;
  335. delete data.dispatcher;
  336. delete data.disabled;
  337. // data.handlers = null;
  338. // data.dispatcher = null;
  339. // data.disabled = null;
  340. }
  341. // Finally remove the expando if there is no data left
  342. if (vjs.isEmpty(data)) {
  343. vjs.removeData(elem);
  344. }
  345. };
  346. /**
  347. * Fix a native event to have standard property values
  348. * @param {Object} event Event object to fix
  349. * @return {Object}
  350. * @private
  351. */
  352. vjs.fixEvent = function (event) {
  353. function returnTrue() { return true; }
  354. function returnFalse() { return false; }
  355. // Test if fixing up is needed
  356. // Used to check if !event.stopPropagation instead of isPropagationStopped
  357. // But native events return true for stopPropagation, but don't have
  358. // other expected methods like isPropagationStopped. Seems to be a problem
  359. // with the Javascript Ninja code. So we're just overriding all events now.
  360. if (!event || !event.isPropagationStopped) {
  361. var old = event || window.event;
  362. event = {};
  363. // Clone the old object so that we can modify the values event = {};
  364. // IE8 Doesn't like when you mess with native event properties
  365. // Firefox returns false for event.hasOwnProperty('type') and other props
  366. // which makes copying more difficult.
  367. // TODO: Probably best to create a whitelist of event props
  368. for (var key in old) {
  369. // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
  370. if (key !== 'layerX' && key !== 'layerY') {
  371. event[key] = old[key];
  372. }
  373. }
  374. // The event occurred on this element
  375. if (!event.target) {
  376. event.target = event.srcElement || document;
  377. }
  378. // Handle which other element the event is related to
  379. event.relatedTarget = event.fromElement === event.target ?
  380. event.toElement :
  381. event.fromElement;
  382. // Stop the default browser action
  383. event.preventDefault = function () {
  384. if (old.preventDefault) {
  385. old.preventDefault();
  386. }
  387. event.returnValue = false;
  388. event.isDefaultPrevented = returnTrue;
  389. };
  390. event.isDefaultPrevented = returnFalse;
  391. // Stop the event from bubbling
  392. event.stopPropagation = function () {
  393. if (old.stopPropagation) {
  394. old.stopPropagation();
  395. }
  396. event.cancelBubble = true;
  397. event.isPropagationStopped = returnTrue;
  398. };
  399. event.isPropagationStopped = returnFalse;
  400. // Stop the event from bubbling and executing other handlers
  401. event.stopImmediatePropagation = function () {
  402. if (old.stopImmediatePropagation) {
  403. old.stopImmediatePropagation();
  404. }
  405. event.isImmediatePropagationStopped = returnTrue;
  406. event.stopPropagation();
  407. };
  408. event.isImmediatePropagationStopped = returnFalse;
  409. // Handle mouse position
  410. if (event.clientX != null) {
  411. var doc = document.documentElement, body = document.body;
  412. event.pageX = event.clientX +
  413. (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
  414. (doc && doc.clientLeft || body && body.clientLeft || 0);
  415. event.pageY = event.clientY +
  416. (doc && doc.scrollTop || body && body.scrollTop || 0) -
  417. (doc && doc.clientTop || body && body.clientTop || 0);
  418. }
  419. // Handle key presses
  420. event.which = event.charCode || event.keyCode;
  421. // Fix button for mouse clicks:
  422. // 0 == left; 1 == middle; 2 == right
  423. if (event.button != null) {
  424. event.button = (event.button & 1 ? 0 :
  425. (event.button & 4 ? 1 :
  426. (event.button & 2 ? 2 : 0)));
  427. }
  428. }
  429. // Returns fixed-up instance
  430. return event;
  431. };
  432. /**
  433. * Trigger an event for an element
  434. * @param {Element|Object} elem Element to trigger an event on
  435. * @param {String} event Type of event to trigger
  436. * @private
  437. */
  438. vjs.trigger = function (elem, event) {
  439. // Fetches element data and a reference to the parent (for bubbling).
  440. // Don't want to add a data object to cache for every parent,
  441. // so checking hasData first.
  442. var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {};
  443. var parent = elem.parentNode || elem.ownerDocument;
  444. // type = event.type || event,
  445. // handler;
  446. // If an event name was passed as a string, creates an event out of it
  447. if (typeof event === 'string') {
  448. event = { type: event, target: elem };
  449. }
  450. // Normalizes the event properties.
  451. event = vjs.fixEvent(event);
  452. // If the passed element has a dispatcher, executes the established handlers.
  453. if (elemData.dispatcher) {
  454. elemData.dispatcher.call(elem, event);
  455. }
  456. // Unless explicitly stopped or the event does not bubble (e.g. media events)
  457. // recursively calls this function to bubble the event up the DOM.
  458. if (parent && !event.isPropagationStopped() && event.bubbles !== false) {
  459. vjs.trigger(parent, event);
  460. // If at the top of the DOM, triggers the default action unless disabled.
  461. } else if (!parent && !event.isDefaultPrevented()) {
  462. var targetData = vjs.getData(event.target);
  463. // Checks if the target has a default action for this event.
  464. if (event.target[event.type]) {
  465. // Temporarily disables event dispatching on the target as we have already executed the handler.
  466. targetData.disabled = true;
  467. // Executes the default action.
  468. if (typeof event.target[event.type] === 'function') {
  469. event.target[event.type]();
  470. }
  471. // Re-enables event dispatching.
  472. targetData.disabled = false;
  473. }
  474. }
  475. // Inform the triggerer if the default was prevented by returning false
  476. return !event.isDefaultPrevented();
  477. /* Original version of js ninja events wasn't complete.
  478. * We've since updated to the latest version, but keeping this around
  479. * for now just in case.
  480. */
  481. // // Added in attion to book. Book code was broke.
  482. // event = typeof event === 'object' ?
  483. // event[vjs.expando] ?
  484. // event :
  485. // new vjs.Event(type, event) :
  486. // new vjs.Event(type);
  487. // event.type = type;
  488. // if (handler) {
  489. // handler.call(elem, event);
  490. // }
  491. // // Clean up the event in case it is being reused
  492. // event.result = undefined;
  493. // event.target = elem;
  494. };
  495. /**
  496. * Trigger a listener only once for an event
  497. * @param {Element|Object} elem Element or object to
  498. * @param {String} type
  499. * @param {Function} fn
  500. * @private
  501. */
  502. vjs.one = function (elem, type, fn) {
  503. var func = function () {
  504. vjs.off(elem, type, func);
  505. fn.apply(this, arguments);
  506. };
  507. func.guid = fn.guid = fn.guid || vjs.guid++;
  508. vjs.on(elem, type, func);
  509. };
  510. var hasOwnProp = Object.prototype.hasOwnProperty;
  511. /**
  512. * Creates an element and applies properties.
  513. * @param {String=} tagName Name of tag to be created.
  514. * @param {Object=} properties Element properties to be applied.
  515. * @return {Element}
  516. * @private
  517. */
  518. vjs.createEl = function (tagName, properties) {
  519. var el, propName;
  520. el = document.createElement(tagName || 'div');
  521. for (propName in properties) {
  522. if (hasOwnProp.call(properties, propName)) {
  523. //el[propName] = properties[propName];
  524. // Not remembering why we were checking for dash
  525. // but using setAttribute means you have to use getAttribute
  526. // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.
  527. // The additional check for "role" is because the default method for adding attributes does not
  528. // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although
  529. // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.
  530. // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.
  531. if (propName.indexOf('aria-') !== -1 || propName == 'role') {
  532. el.setAttribute(propName, properties[propName]);
  533. } else {
  534. el[propName] = properties[propName];
  535. }
  536. }
  537. }
  538. return el;
  539. };
  540. /**
  541. * Uppercase the first letter of a string
  542. * @param {String} string String to be uppercased
  543. * @return {String}
  544. * @private
  545. */
  546. vjs.capitalize = function (string) {
  547. return string.charAt(0).toUpperCase() + string.slice(1);
  548. };
  549. /**
  550. * Object functions container
  551. * @type {Object}
  552. * @private
  553. */
  554. vjs.obj = {};
  555. /**
  556. * Object.create shim for prototypal inheritance
  557. *
  558. * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
  559. *
  560. * @function
  561. * @param {Object} obj Object to use as prototype
  562. * @private
  563. */
  564. vjs.obj.create = Object.create || function (obj) {
  565. //Create a new function called 'F' which is just an empty object.
  566. function F() { }
  567. //the prototype of the 'F' function should point to the
  568. //parameter of the anonymous function.
  569. F.prototype = obj;
  570. //create a new constructor function based off of the 'F' function.
  571. return new F();
  572. };
  573. /**
  574. * Loop through each property in an object and call a function
  575. * whose arguments are (key,value)
  576. * @param {Object} obj Object of properties
  577. * @param {Function} fn Function to be called on each property.
  578. * @this {*}
  579. * @private
  580. */
  581. vjs.obj.each = function (obj, fn, context) {
  582. for (var key in obj) {
  583. if (hasOwnProp.call(obj, key)) {
  584. fn.call(context || this, key, obj[key]);
  585. }
  586. }
  587. };
  588. /**
  589. * Merge two objects together and return the original.
  590. * @param {Object} obj1
  591. * @param {Object} obj2
  592. * @return {Object}
  593. * @private
  594. */
  595. vjs.obj.merge = function (obj1, obj2) {
  596. if (!obj2) { return obj1; }
  597. for (var key in obj2) {
  598. if (hasOwnProp.call(obj2, key)) {
  599. obj1[key] = obj2[key];
  600. }
  601. }
  602. return obj1;
  603. };
  604. /**
  605. * Merge two objects, and merge any properties that are objects
  606. * instead of just overwriting one. Uses to merge options hashes
  607. * where deeper default settings are important.
  608. * @param {Object} obj1 Object to override
  609. * @param {Object} obj2 Overriding object
  610. * @return {Object} New object. Obj1 and Obj2 will be untouched.
  611. * @private
  612. */
  613. vjs.obj.deepMerge = function (obj1, obj2) {
  614. var key, val1, val2;
  615. // make a copy of obj1 so we're not ovewriting original values.
  616. // like prototype.options_ and all sub options objects
  617. obj1 = vjs.obj.copy(obj1);
  618. for (key in obj2) {
  619. if (hasOwnProp.call(obj2, key)) {
  620. val1 = obj1[key];
  621. val2 = obj2[key];
  622. // Check if both properties are pure objects and do a deep merge if so
  623. if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) {
  624. obj1[key] = vjs.obj.deepMerge(val1, val2);
  625. } else {
  626. obj1[key] = obj2[key];
  627. }
  628. }
  629. }
  630. return obj1;
  631. };
  632. /**
  633. * Make a copy of the supplied object
  634. * @param {Object} obj Object to copy
  635. * @return {Object} Copy of object
  636. * @private
  637. */
  638. vjs.obj.copy = function (obj) {
  639. return vjs.obj.merge({}, obj);
  640. };
  641. /**
  642. * Check if an object is plain, and not a dom node or any object sub-instance
  643. * @param {Object} obj Object to check
  644. * @return {Boolean} True if plain, false otherwise
  645. * @private
  646. */
  647. vjs.obj.isPlain = function (obj) {
  648. return !!obj
  649. && typeof obj === 'object'
  650. && obj.toString() === '[object Object]'
  651. && obj.constructor === Object;
  652. };
  653. /**
  654. * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
  655. It also stores a unique id on the function so it can be easily removed from events
  656. * @param {*} context The object to bind as scope
  657. * @param {Function} fn The function to be bound to a scope
  658. * @param {Number=} uid An optional unique ID for the function to be set
  659. * @return {Function}
  660. * @private
  661. */
  662. vjs.bind = function (context, fn, uid) {
  663. // Make sure the function has a unique ID
  664. if (!fn.guid) { fn.guid = vjs.guid++; }
  665. // Create the new function that changes the context
  666. var ret = function () {
  667. return fn.apply(context, arguments);
  668. };
  669. // Allow for the ability to individualize this function
  670. // Needed in the case where multiple objects might share the same prototype
  671. // IF both items add an event listener with the same function, then you try to remove just one
  672. // it will remove both because they both have the same guid.
  673. // when using this, you need to use the bind method when you remove the listener as well.
  674. // currently used in text tracks
  675. ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid;
  676. return ret;
  677. };
  678. /**
  679. * Element Data Store. Allows for binding data to an element without putting it directly on the element.
  680. * Ex. Event listneres are stored here.
  681. * (also from jsninja.com, slightly modified and updated for closure compiler)
  682. * @type {Object}
  683. * @private
  684. */
  685. vjs.cache = {};
  686. /**
  687. * Unique ID for an element or function
  688. * @type {Number}
  689. * @private
  690. */
  691. vjs.guid = 1;
  692. /**
  693. * Unique attribute name to store an element's guid in
  694. * @type {String}
  695. * @constant
  696. * @private
  697. */
  698. vjs.expando = 'vdata' + (new Date()).getTime();
  699. /**
  700. * Returns the cache object where data for an element is stored
  701. * @param {Element} el Element to store data for.
  702. * @return {Object}
  703. * @private
  704. */
  705. vjs.getData = function (el) {
  706. var id = el[vjs.expando];
  707. if (!id) {
  708. id = el[vjs.expando] = vjs.guid++;
  709. vjs.cache[id] = {};
  710. }
  711. return vjs.cache[id];
  712. };
  713. /**
  714. * Returns the cache object where data for an element is stored
  715. * @param {Element} el Element to store data for.
  716. * @return {Object}
  717. * @private
  718. */
  719. vjs.hasData = function (el) {
  720. var id = el[vjs.expando];
  721. return !(!id || vjs.isEmpty(vjs.cache[id]));
  722. };
  723. /**
  724. * Delete data for the element from the cache and the guid attr from getElementById
  725. * @param {Element} el Remove data for an element
  726. * @private
  727. */
  728. vjs.removeData = function (el) {
  729. var id = el[vjs.expando];
  730. if (!id) { return; }
  731. // Remove all stored data
  732. // Changed to = null
  733. // http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
  734. // vjs.cache[id] = null;
  735. delete vjs.cache[id];
  736. // Remove the expando property from the DOM node
  737. try {
  738. delete el[vjs.expando];
  739. } catch (e) {
  740. if (el.removeAttribute) {
  741. el.removeAttribute(vjs.expando);
  742. } else {
  743. // IE doesn't appear to support removeAttribute on the document element
  744. el[vjs.expando] = null;
  745. }
  746. }
  747. };
  748. /**
  749. * Check if an object is empty
  750. * @param {Object} obj The object to check for emptiness
  751. * @return {Boolean}
  752. * @private
  753. */
  754. vjs.isEmpty = function (obj) {
  755. for (var prop in obj) {
  756. // Inlude null properties as empty.
  757. if (obj[prop] !== null) {
  758. return false;
  759. }
  760. }
  761. return true;
  762. };
  763. /**
  764. * Add a CSS class name to an element
  765. * @param {Element} element Element to add class name to
  766. * @param {String} classToAdd Classname to add
  767. * @private
  768. */
  769. vjs.addClass = function (element, classToAdd) {
  770. if ((' ' + element.className + ' ').indexOf(' ' + classToAdd + ' ') == -1) {
  771. element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;
  772. }
  773. };
  774. /**
  775. * Remove a CSS class name from an element
  776. * @param {Element} element Element to remove from class name
  777. * @param {String} classToAdd Classname to remove
  778. * @private
  779. */
  780. vjs.removeClass = function (element, classToRemove) {
  781. var classNames, i;
  782. if (element.className.indexOf(classToRemove) == -1) { return; }
  783. classNames = element.className.split(' ');
  784. // no arr.indexOf in ie8, and we don't want to add a big shim
  785. for (i = classNames.length - 1; i >= 0; i--) {
  786. if (classNames[i] === classToRemove) {
  787. classNames.splice(i, 1);
  788. }
  789. }
  790. element.className = classNames.join(' ');
  791. };
  792. /**
  793. * Element for testing browser HTML5 video capabilities
  794. * @type {Element}
  795. * @constant
  796. * @private
  797. */
  798. vjs.TEST_VID = vjs.createEl('video');
  799. /**
  800. * Useragent for browser testing.
  801. * @type {String}
  802. * @constant
  803. * @private
  804. */
  805. vjs.USER_AGENT = navigator.userAgent;
  806. /**
  807. * Device is an iPhone
  808. * @type {Boolean}
  809. * @constant
  810. * @private
  811. */
  812. vjs.IS_IPHONE = (/iPhone/i).test(vjs.USER_AGENT);
  813. vjs.IS_IPAD = (/iPad/i).test(vjs.USER_AGENT);
  814. vjs.IS_IPOD = (/iPod/i).test(vjs.USER_AGENT);
  815. vjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD;
  816. vjs.IOS_VERSION = (function () {
  817. var match = vjs.USER_AGENT.match(/OS (\d+)_/i);
  818. if (match && match[1]) { return match[1]; }
  819. })();
  820. vjs.IS_ANDROID = (/Android/i).test(vjs.USER_AGENT);
  821. vjs.ANDROID_VERSION = (function () {
  822. // This matches Android Major.Minor.Patch versions
  823. // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  824. var match = vjs.USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i),
  825. major,
  826. minor;
  827. if (!match) {
  828. return null;
  829. }
  830. major = match[1] && parseFloat(match[1]);
  831. minor = match[2] && parseFloat(match[2]);
  832. if (major && minor) {
  833. return parseFloat(match[1] + '.' + match[2]);
  834. } else if (major) {
  835. return major;
  836. } else {
  837. return null;
  838. }
  839. })();
  840. // Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
  841. vjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.ANDROID_VERSION < 2.3;
  842. vjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT);
  843. vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);
  844. vjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
  845. /**
  846. * Get an element's attribute values, as defined on the HTML tag
  847. * Attributs are not the same as properties. They're defined on the tag
  848. * or with setAttribute (which shouldn't be used with HTML)
  849. * This will return true or false for boolean attributes.
  850. * @param {Element} tag Element from which to get tag attributes
  851. * @return {Object}
  852. * @private
  853. */
  854. vjs.getAttributeValues = function (tag) {
  855. var obj, knownBooleans, attrs, attrName, attrVal;
  856. obj = {};
  857. // known boolean attributes
  858. // we can check for matching boolean properties, but older browsers
  859. // won't know about HTML5 boolean attributes that we still read from
  860. knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ',';
  861. if (tag && tag.attributes && tag.attributes.length > 0) {
  862. attrs = tag.attributes;
  863. for (var i = attrs.length - 1; i >= 0; i--) {
  864. attrName = attrs[i].name;
  865. attrVal = attrs[i].value;
  866. // check for known booleans
  867. // the matching element property will return a value for typeof
  868. if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
  869. // the value of an included boolean attribute is typically an empty
  870. // string ('') which would equal false if we just check for a false value.
  871. // we also don't want support bad code like autoplay='false'
  872. attrVal = (attrVal !== null) ? true : false;
  873. }
  874. obj[attrName] = attrVal;
  875. }
  876. }
  877. return obj;
  878. };
  879. /**
  880. * Get the computed style value for an element
  881. * From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
  882. * @param {Element} el Element to get style value for
  883. * @param {String} strCssRule Style name
  884. * @return {String} Style value
  885. * @private
  886. */
  887. vjs.getComputedDimension = function (el, strCssRule) {
  888. var strValue = '';
  889. if (document.defaultView && document.defaultView.getComputedStyle) {
  890. strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule);
  891. } else if (el.currentStyle) {
  892. // IE8 Width/Height support
  893. strValue = el['client' + strCssRule.substr(0, 1).toUpperCase() + strCssRule.substr(1)] + 'px';
  894. }
  895. return strValue;
  896. };
  897. /**
  898. * Insert an element as the first child node of another
  899. * @param {Element} child Element to insert
  900. * @param {[type]} parent Element to insert child into
  901. * @private
  902. */
  903. vjs.insertFirst = function (child, parent) {
  904. if (parent.firstChild) {
  905. parent.insertBefore(child, parent.firstChild);
  906. } else {
  907. parent.appendChild(child);
  908. }
  909. };
  910. /**
  911. * Object to hold browser support information
  912. * @type {Object}
  913. * @private
  914. */
  915. vjs.support = {};
  916. /**
  917. * Shorthand for document.getElementById()
  918. * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
  919. * @param {String} id Element ID
  920. * @return {Element} Element with supplied ID
  921. * @private
  922. */
  923. vjs.el = function (id) {
  924. if (id.indexOf('#') === 0) {
  925. id = id.slice(1);
  926. }
  927. return document.getElementById(id);
  928. };
  929. /**
  930. * Format seconds as a time string, H:MM:SS or M:SS
  931. * Supplying a guide (in seconds) will force a number of leading zeros
  932. * to cover the length of the guide
  933. * @param {Number} seconds Number of seconds to be turned into a string
  934. * @param {Number} guide Number (in seconds) to model the string after
  935. * @return {String} Time formatted as H:MM:SS or M:SS
  936. * @private
  937. */
  938. vjs.formatTime = function (seconds, guide) {
  939. // Default to using seconds as guide
  940. guide = guide || seconds;
  941. var s = Math.floor(seconds % 60),
  942. m = Math.floor(seconds / 60 % 60),
  943. h = Math.floor(seconds / 3600),
  944. gm = Math.floor(guide / 60 % 60),
  945. gh = Math.floor(guide / 3600);
  946. // handle invalid times
  947. if (isNaN(seconds) || seconds === Infinity) {
  948. // '-' is false for all relational operators (e.g. <, >=) so this setting
  949. // will add the minimum number of fields specified by the guide
  950. h = m = s = '-';
  951. }
  952. // Check if we need to show hours
  953. h = (h > 0 || gh > 0) ? h + ':' : '';
  954. // If hours are showing, we may need to add a leading zero.
  955. // Always show at least one digit of minutes.
  956. m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';
  957. // Check if leading zero is need for seconds
  958. s = (s < 10) ? '0' + s : s;
  959. return h + m + s;
  960. };
  961. // Attempt to block the ability to select text while dragging controls
  962. vjs.blockTextSelection = function () {
  963. document.body.focus();
  964. document.onselectstart = function () { return false; };
  965. };
  966. // Turn off text selection blocking
  967. vjs.unblockTextSelection = function () { document.onselectstart = function () { return true; }; };
  968. /**
  969. * Trim whitespace from the ends of a string.
  970. * @param {String} string String to trim
  971. * @return {String} Trimmed string
  972. * @private
  973. */
  974. vjs.trim = function (str) {
  975. return (str + '').replace(/^\s+|\s+$/g, '');
  976. };
  977. /**
  978. * Should round off a number to a decimal place
  979. * @param {Number} num Number to round
  980. * @param {Number} dec Number of decimal places to round to
  981. * @return {Number} Rounded number
  982. * @private
  983. */
  984. vjs.round = function (num, dec) {
  985. if (!dec) { dec = 0; }
  986. return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
  987. };
  988. /**
  989. * Should create a fake TimeRange object
  990. * Mimics an HTML5 time range instance, which has functions that
  991. * return the start and end times for a range
  992. * TimeRanges are returned by the buffered() method
  993. * @param {Number} start Start time in seconds
  994. * @param {Number} end End time in seconds
  995. * @return {Object} Fake TimeRange object
  996. * @private
  997. */
  998. vjs.createTimeRange = function (start, end) {
  999. return {
  1000. length: 1,
  1001. start: function () { return start; },
  1002. end: function () { return end; }
  1003. };
  1004. };
  1005. /**
  1006. * Simple http request for retrieving external files (e.g. text tracks)
  1007. * @param {String} url URL of resource
  1008. * @param {Function=} onSuccess Success callback
  1009. * @param {Function=} onError Error callback
  1010. * @private
  1011. */
  1012. vjs.get = function (url, onSuccess, onError) {
  1013. var local, request;
  1014. if (typeof XMLHttpRequest === 'undefined') {
  1015. window.XMLHttpRequest = function () {
  1016. try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) { }
  1017. try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) { }
  1018. try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) { }
  1019. throw new Error('This browser does not support XMLHttpRequest.');
  1020. };
  1021. }
  1022. request = new XMLHttpRequest();
  1023. try {
  1024. request.open('GET', url);
  1025. } catch (e) {
  1026. onError(e);
  1027. }
  1028. local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));
  1029. request.onreadystatechange = function () {
  1030. if (request.readyState === 4) {
  1031. if (request.status === 200 || local && request.status === 0) {
  1032. onSuccess(request.responseText);
  1033. } else {
  1034. if (onError) {
  1035. onError();
  1036. }
  1037. }
  1038. }
  1039. };
  1040. try {
  1041. request.send();
  1042. } catch (e) {
  1043. if (onError) {
  1044. onError(e);
  1045. }
  1046. }
  1047. };
  1048. /**
  1049. * Add to local storage (may removeable)
  1050. * @private
  1051. */
  1052. vjs.setLocalStorage = function (key, value) {
  1053. try {
  1054. // IE was throwing errors referencing the var anywhere without this
  1055. var localStorage = window.localStorage || false;
  1056. if (!localStorage) { return; }
  1057. localStorage[key] = value;
  1058. } catch (e) {
  1059. if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014
  1060. vjs.log('LocalStorage Full (VideoJS)', e);
  1061. } else {
  1062. if (e.code == 18) {
  1063. vjs.log('LocalStorage not allowed (VideoJS)', e);
  1064. } else {
  1065. vjs.log('LocalStorage Error (VideoJS)', e);
  1066. }
  1067. }
  1068. }
  1069. };
  1070. /**
  1071. * Get abosolute version of relative URL. Used to tell flash correct URL.
  1072. * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
  1073. * @param {String} url URL to make absolute
  1074. * @return {String} Absolute URL
  1075. * @private
  1076. */
  1077. vjs.getAbsoluteURL = function (url) {
  1078. // Check if absolute URL
  1079. if (!url.match(/^https?:\/\//)) {
  1080. // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
  1081. url = vjs.createEl('div', {
  1082. innerHTML: '<a href="' + url + '">x</a>'
  1083. }).firstChild.href;
  1084. }
  1085. return url;
  1086. };
  1087. // usage: log('inside coolFunc',this,arguments);
  1088. // http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
  1089. vjs.log = function () {
  1090. vjs.log.history = vjs.log.history || []; // store logs to an array for reference
  1091. vjs.log.history.push(arguments);
  1092. if (window.console) {
  1093. window.console.log(Array.prototype.slice.call(arguments));
  1094. }
  1095. };
  1096. // Offset Left
  1097. // getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
  1098. vjs.findPosition = function (el) {
  1099. var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;
  1100. if (el.getBoundingClientRect && el.parentNode) {
  1101. box = el.getBoundingClientRect();
  1102. }
  1103. if (!box) {
  1104. return {
  1105. left: 0,
  1106. top: 0
  1107. };
  1108. }
  1109. docEl = document.documentElement;
  1110. body = document.body;
  1111. clientLeft = docEl.clientLeft || body.clientLeft || 0;
  1112. scrollLeft = window.pageXOffset || body.scrollLeft;
  1113. left = box.left + scrollLeft - clientLeft;
  1114. clientTop = docEl.clientTop || body.clientTop || 0;
  1115. scrollTop = window.pageYOffset || body.scrollTop;
  1116. top = box.top + scrollTop - clientTop;
  1117. return {
  1118. left: left,
  1119. top: top
  1120. };
  1121. };
  1122. /**
  1123. * @fileoverview Player Component - Base class for all UI objects
  1124. *
  1125. */
  1126. /**
  1127. * Base UI Component class
  1128. *
  1129. * Components are embeddable UI objects that are represented by both a
  1130. * javascript object and an element in the DOM. They can be children of other
  1131. * components, and can have many children themselves.
  1132. *
  1133. * // adding a button to the player
  1134. * var button = player.addChild('button');
  1135. * button.el(); // -> button element
  1136. *
  1137. * <div class="video-js">
  1138. * <div class="vjs-button">Button</div>
  1139. * </div>
  1140. *
  1141. * Components are also event emitters.
  1142. *
  1143. * button.on('click', function(){
  1144. * console.log('Button Clicked!');
  1145. * });
  1146. *
  1147. * button.trigger('customevent');
  1148. *
  1149. * @param {Object} player Main Player
  1150. * @param {Object=} options
  1151. * @class
  1152. * @constructor
  1153. * @extends vjs.CoreObject
  1154. */
  1155. vjs.Component = vjs.CoreObject.extend({
  1156. /**
  1157. * the constructor funciton for the class
  1158. *
  1159. * @constructor
  1160. */
  1161. init: function (player, options, ready) {
  1162. this.player_ = player;
  1163. // Make a copy of prototype.options_ to protect against overriding global defaults
  1164. this.options_ = vjs.obj.copy(this.options_);
  1165. // Updated options with supplied options
  1166. options = this.options(options);
  1167. // Get ID from options, element, or create using player ID and unique ID
  1168. this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++);
  1169. this.name_ = options['name'] || null;
  1170. // Create element if one wasn't provided in options
  1171. this.el_ = options['el'] || this.createEl();
  1172. this.children_ = [];
  1173. this.childIndex_ = {};
  1174. this.childNameIndex_ = {};
  1175. // Add any child components in options
  1176. this.initChildren();
  1177. this.ready(ready);
  1178. // Don't want to trigger ready here or it will before init is actually
  1179. // finished for all children that run this constructor
  1180. }
  1181. });
  1182. /**
  1183. * Dispose of the component and all child components
  1184. */
  1185. vjs.Component.prototype.dispose = function () {
  1186. this.trigger('dispose');
  1187. // Dispose all children.
  1188. if (this.children_) {
  1189. for (var i = this.children_.length - 1; i >= 0; i--) {
  1190. if (this.children_[i].dispose) {
  1191. this.children_[i].dispose();
  1192. }
  1193. }
  1194. }
  1195. // Delete child references
  1196. this.children_ = null;
  1197. this.childIndex_ = null;
  1198. this.childNameIndex_ = null;
  1199. // Remove all event listeners.
  1200. this.off();
  1201. // Remove element from DOM
  1202. if (this.el_.parentNode) {
  1203. this.el_.parentNode.removeChild(this.el_);
  1204. }
  1205. vjs.removeData(this.el_);
  1206. this.el_ = null;
  1207. };
  1208. /**
  1209. * Reference to main player instance
  1210. *
  1211. * @type {vjs.Player}
  1212. * @private
  1213. */
  1214. vjs.Component.prototype.player_ = true;
  1215. /**
  1216. * Return the component's player
  1217. *
  1218. * @return {vjs.Player}
  1219. */
  1220. vjs.Component.prototype.player = function () {
  1221. return this.player_;
  1222. };
  1223. /**
  1224. * The component's options object
  1225. *
  1226. * @type {Object}
  1227. * @private
  1228. */
  1229. vjs.Component.prototype.options_;
  1230. /**
  1231. * Deep merge of options objects
  1232. *
  1233. * Whenever a property is an object on both options objects
  1234. * the two properties will be merged using vjs.obj.deepMerge.
  1235. *
  1236. * This is used for merging options for child components. We
  1237. * want it to be easy to override individual options on a child
  1238. * component without having to rewrite all the other default options.
  1239. *
  1240. * Parent.prototype.options_ = {
  1241. * children: {
  1242. * 'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },
  1243. * 'childTwo': {},
  1244. * 'childThree': {}
  1245. * }
  1246. * }
  1247. * newOptions = {
  1248. * children: {
  1249. * 'childOne': { 'foo': 'baz', 'abc': '123' }
  1250. * 'childTwo': null,
  1251. * 'childFour': {}
  1252. * }
  1253. * }
  1254. *
  1255. * this.options(newOptions);
  1256. *
  1257. * RESULT
  1258. *
  1259. * {
  1260. * children: {
  1261. * 'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' },
  1262. * 'childTwo': null, // Disabled. Won't be initialized.
  1263. * 'childThree': {},
  1264. * 'childFour': {}
  1265. * }
  1266. * }
  1267. *
  1268. * @param {Object} obj Object whose values will be overwritten
  1269. * @return {Object} NEW merged object. Does not return obj1.
  1270. */
  1271. vjs.Component.prototype.options = function (obj) {
  1272. if (obj === undefined) return this.options_;
  1273. return this.options_ = vjs.obj.deepMerge(this.options_, obj);
  1274. };
  1275. /**
  1276. * The DOM element for the component
  1277. *
  1278. * @type {Element}
  1279. * @private
  1280. */
  1281. vjs.Component.prototype.el_;
  1282. /**
  1283. * Create the component's DOM element
  1284. *
  1285. * @param {String=} tagName Element's node type. e.g. 'div'
  1286. * @param {Object=} attributes An object of element attributes that should be set on the element
  1287. * @return {Element}
  1288. */
  1289. vjs.Component.prototype.createEl = function (tagName, attributes) {
  1290. return vjs.createEl(tagName, attributes);
  1291. };
  1292. /**
  1293. * Get the component's DOM element
  1294. *
  1295. * var domEl = myComponent.el();
  1296. *
  1297. * @return {Element}
  1298. */
  1299. vjs.Component.prototype.el = function () {
  1300. return this.el_;
  1301. };
  1302. /**
  1303. * An optional element where, if defined, children will be inserted instead of
  1304. * directly in `el_`
  1305. *
  1306. * @type {Element}
  1307. * @private
  1308. */
  1309. vjs.Component.prototype.contentEl_;
  1310. /**
  1311. * Return the component's DOM element for embedding content.
  1312. * Will either be el_ or a new element defined in createEl.
  1313. *
  1314. * @return {Element}
  1315. */
  1316. vjs.Component.prototype.contentEl = function () {
  1317. return this.contentEl_ || this.el_;
  1318. };
  1319. /**
  1320. * The ID for the component
  1321. *
  1322. * @type {String}
  1323. * @private
  1324. */
  1325. vjs.Component.prototype.id_;
  1326. /**
  1327. * Get the component's ID
  1328. *
  1329. * var id = myComponent.id();
  1330. *
  1331. * @return {String}
  1332. */
  1333. vjs.Component.prototype.id = function () {
  1334. return this.id_;
  1335. };
  1336. /**
  1337. * The name for the component. Often used to reference the component.
  1338. *
  1339. * @type {String}
  1340. * @private
  1341. */
  1342. vjs.Component.prototype.name_;
  1343. /**
  1344. * Get the component's name. The name is often used to reference the component.
  1345. *
  1346. * var name = myComponent.name();
  1347. *
  1348. * @return {String}
  1349. */
  1350. vjs.Component.prototype.name = function () {
  1351. return this.name_;
  1352. };
  1353. /**
  1354. * Array of child components
  1355. *
  1356. * @type {Array}
  1357. * @private
  1358. */
  1359. vjs.Component.prototype.children_;
  1360. /**
  1361. * Get an array of all child components
  1362. *
  1363. * var kids = myComponent.children();
  1364. *
  1365. * @return {Array} The children
  1366. */
  1367. vjs.Component.prototype.children = function () {
  1368. return this.children_;
  1369. };
  1370. /**
  1371. * Object of child components by ID
  1372. *
  1373. * @type {Object}
  1374. * @private
  1375. */
  1376. vjs.Component.prototype.childIndex_;
  1377. /**
  1378. * Returns a child component with the provided ID
  1379. *
  1380. * @return {vjs.Component}
  1381. */
  1382. vjs.Component.prototype.getChildById = function (id) {
  1383. return this.childIndex_[id];
  1384. };
  1385. /**
  1386. * Object of child components by name
  1387. *
  1388. * @type {Object}
  1389. * @private
  1390. */
  1391. vjs.Component.prototype.childNameIndex_;
  1392. /**
  1393. * Returns a child component with the provided ID
  1394. *
  1395. * @return {vjs.Component}
  1396. */
  1397. vjs.Component.prototype.getChild = function (name) {
  1398. return this.childNameIndex_[name];
  1399. };
  1400. /**
  1401. * Adds a child component inside this component
  1402. *
  1403. * myComponent.el();
  1404. * // -> <div class='my-component'></div>
  1405. * myComonent.children();
  1406. * // [empty array]
  1407. *
  1408. * var myButton = myComponent.addChild('MyButton');
  1409. * // -> <div class='my-component'><div class="my-button">myButton<div></div>
  1410. * // -> myButton === myComonent.children()[0];
  1411. *
  1412. * Pass in options for child constructors and options for children of the child
  1413. *
  1414. * var myButton = myComponent.addChild('MyButton', {
  1415. * text: 'Press Me',
  1416. * children: {
  1417. * buttonChildExample: {
  1418. * buttonChildOption: true
  1419. * }
  1420. * }
  1421. * });
  1422. *
  1423. * @param {String|vjs.Component} child The class name or instance of a child to add
  1424. * @param {Object=} options Options, including options to be passed to children of the child.
  1425. * @return {vjs.Component} The child component (created by this process if a string was used)
  1426. * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}
  1427. */
  1428. vjs.Component.prototype.addChild = function (child, options) {
  1429. var component, componentClass, componentName, componentId;
  1430. // If string, create new component with options
  1431. if (typeof child === 'string') {
  1432. componentName = child;
  1433. // Make sure options is at least an empty object to protect against errors
  1434. options = options || {};
  1435. // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
  1436. componentClass = options['componentClass'] || vjs.capitalize(componentName);
  1437. // Set name through options
  1438. options['name'] = componentName;
  1439. // Create a new object & element for this controls set
  1440. // If there's no .player_, this is a player
  1441. // Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly.
  1442. // Every class should be exported, so this should never be a problem here.
  1443. component = new window['videojs'][componentClass](this.player_ || this, options);
  1444. // child is a component instance
  1445. } else {
  1446. component = child;
  1447. }
  1448. this.children_.push(component);
  1449. if (typeof component.id === 'function') {
  1450. this.childIndex_[component.id()] = component;
  1451. }
  1452. // If a name wasn't used to create the component, check if we can use the
  1453. // name function of the component
  1454. componentName = componentName || (component.name && component.name());
  1455. if (componentName) {
  1456. this.childNameIndex_[componentName] = component;
  1457. }
  1458. // Add the UI object's element to the container div (box)
  1459. // Having an element is not required
  1460. if (typeof component['el'] === 'function' && component['el']()) {
  1461. this.contentEl().appendChild(component['el']());
  1462. }
  1463. // Return so it can stored on parent object if desired.
  1464. return component;
  1465. };
  1466. /**
  1467. * Remove a child component from this component's list of children, and the
  1468. * child component's element from this component's element
  1469. *
  1470. * @param {vjs.Component} component Component to remove
  1471. */
  1472. vjs.Component.prototype.removeChild = function (component) {
  1473. if (typeof component === 'string') {
  1474. component = this.getChild(component);
  1475. }
  1476. if (!component || !this.children_) return;
  1477. var childFound = false;
  1478. for (var i = this.children_.length - 1; i >= 0; i--) {
  1479. if (this.children_[i] === component) {
  1480. childFound = true;
  1481. this.children_.splice(i, 1);
  1482. break;
  1483. }
  1484. }
  1485. if (!childFound) return;
  1486. this.childIndex_[component.id] = null;
  1487. this.childNameIndex_[component.name] = null;
  1488. var compEl = component.el();
  1489. if (compEl && compEl.parentNode === this.contentEl()) {
  1490. this.contentEl().removeChild(component.el());
  1491. }
  1492. };
  1493. /**
  1494. * Add and initialize default child components from options
  1495. *
  1496. * // when an instance of MyComponent is created, all children in options
  1497. * // will be added to the instance by their name strings and options
  1498. * MyComponent.prototype.options_.children = {
  1499. * myChildComponent: {
  1500. * myChildOption: true
  1501. * }
  1502. * }
  1503. */
  1504. vjs.Component.prototype.initChildren = function () {
  1505. var options = this.options_;
  1506. if (options && options['children']) {
  1507. var self = this;
  1508. // Loop through components and add them to the player
  1509. vjs.obj.each(options['children'], function (name, opts) {
  1510. // Allow for disabling default components
  1511. // e.g. vjs.options['children']['posterImage'] = false
  1512. if (opts === false) return;
  1513. // Allow waiting to add components until a specific event is called
  1514. var tempAdd = function () {
  1515. // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
  1516. self[name] = self.addChild(name, opts);
  1517. };
  1518. if (opts['loadEvent']) {
  1519. // this.one(opts.loadEvent, tempAdd)
  1520. } else {
  1521. tempAdd();
  1522. }
  1523. });
  1524. }
  1525. };
  1526. /**
  1527. * Allows sub components to stack CSS class names
  1528. *
  1529. * @return {String} The constructed class name
  1530. */
  1531. vjs.Component.prototype.buildCSSClass = function () {
  1532. // Child classes can include a function that does:
  1533. // return 'CLASS NAME' + this._super();
  1534. return '';
  1535. };
  1536. /* Events
  1537. ============================================================================= */
  1538. /**
  1539. * Add an event listener to this component's element
  1540. *
  1541. * var myFunc = function(){
  1542. * var myPlayer = this;
  1543. * // Do something when the event is fired
  1544. * };
  1545. *
  1546. * myPlayer.on("eventName", myFunc);
  1547. *
  1548. * The context will be the component.
  1549. *
  1550. * @param {String} type The event type e.g. 'click'
  1551. * @param {Function} fn The event listener
  1552. * @return {vjs.Component} self
  1553. */
  1554. vjs.Component.prototype.on = function (type, fn) {
  1555. vjs.on(this.el_, type, vjs.bind(this, fn));
  1556. return this;
  1557. };
  1558. /**
  1559. * Remove an event listener from the component's element
  1560. *
  1561. * myComponent.off("eventName", myFunc);
  1562. *
  1563. * @param {String=} type Event type. Without type it will remove all listeners.
  1564. * @param {Function=} fn Event listener. Without fn it will remove all listeners for a type.
  1565. * @return {vjs.Component}
  1566. */
  1567. vjs.Component.prototype.off = function (type, fn) {
  1568. vjs.off(this.el_, type, fn);
  1569. return this;
  1570. };
  1571. /**
  1572. * Add an event listener to be triggered only once and then removed
  1573. *
  1574. * @param {String} type Event type
  1575. * @param {Function} fn Event listener
  1576. * @return {vjs.Component}
  1577. */
  1578. vjs.Component.prototype.one = function (type, fn) {
  1579. vjs.one(this.el_, type, vjs.bind(this, fn));
  1580. return this;
  1581. };
  1582. /**
  1583. * Trigger an event on an element
  1584. *
  1585. * myComponent.trigger('eventName');
  1586. *
  1587. * @param {String} type The event type to trigger, e.g. 'click'
  1588. * @param {Event|Object} event The event object to be passed to the listener
  1589. * @return {vjs.Component} self
  1590. */
  1591. vjs.Component.prototype.trigger = function (type, event) {
  1592. vjs.trigger(this.el_, type, event);
  1593. return this;
  1594. };
  1595. /* Ready
  1596. ================================================================================ */
  1597. /**
  1598. * Is the component loaded
  1599. * This can mean different things depending on the component.
  1600. *
  1601. * @private
  1602. * @type {Boolean}
  1603. */
  1604. vjs.Component.prototype.isReady_;
  1605. /**
  1606. * Trigger ready as soon as initialization is finished
  1607. *
  1608. * Allows for delaying ready. Override on a sub class prototype.
  1609. * If you set this.isReadyOnInitFinish_ it will affect all components.
  1610. * Specially used when waiting for the Flash player to asynchrnously load.
  1611. *
  1612. * @type {Boolean}
  1613. * @private
  1614. */
  1615. vjs.Component.prototype.isReadyOnInitFinish_ = true;
  1616. /**
  1617. * List of ready listeners
  1618. *
  1619. * @type {Array}
  1620. * @private
  1621. */
  1622. vjs.Component.prototype.readyQueue_;
  1623. /**
  1624. * Bind a listener to the component's ready state
  1625. *
  1626. * Different from event listeners in that if the ready event has already happend
  1627. * it will trigger the function immediately.
  1628. *
  1629. * @param {Function} fn Ready listener
  1630. * @return {vjs.Component}
  1631. */
  1632. vjs.Component.prototype.ready = function (fn) {
  1633. if (fn) {
  1634. if (this.isReady_) {
  1635. fn.call(this);
  1636. } else {
  1637. if (this.readyQueue_ === undefined) {
  1638. this.readyQueue_ = [];
  1639. }
  1640. this.readyQueue_.push(fn);
  1641. }
  1642. }
  1643. return this;
  1644. };
  1645. /**
  1646. * Trigger the ready listeners
  1647. *
  1648. * @return {vjs.Component}
  1649. */
  1650. vjs.Component.prototype.triggerReady = function () {
  1651. this.isReady_ = true;
  1652. var readyQueue = this.readyQueue_;
  1653. if (readyQueue && readyQueue.length > 0) {
  1654. for (var i = 0, j = readyQueue.length; i < j; i++) {
  1655. readyQueue[i].call(this);
  1656. }
  1657. // Reset Ready Queue
  1658. this.readyQueue_ = [];
  1659. // Allow for using event listeners also, in case you want to do something everytime a source is ready.
  1660. this.trigger('ready');
  1661. }
  1662. };
  1663. /* Display
  1664. ============================================================================= */
  1665. /**
  1666. * Add a CSS class name to the component's element
  1667. *
  1668. * @param {String} classToAdd Classname to add
  1669. * @return {vjs.Component}
  1670. */
  1671. vjs.Component.prototype.addClass = function (classToAdd) {
  1672. vjs.addClass(this.el_, classToAdd);
  1673. return this;
  1674. };
  1675. /**
  1676. * Remove a CSS class name from the component's element
  1677. *
  1678. * @param {String} classToRemove Classname to remove
  1679. * @return {vjs.Component}
  1680. */
  1681. vjs.Component.prototype.removeClass = function (classToRemove) {
  1682. vjs.removeClass(this.el_, classToRemove);
  1683. return this;
  1684. };
  1685. /**
  1686. * Show the component element if hidden
  1687. *
  1688. * @return {vjs.Component}
  1689. */
  1690. vjs.Component.prototype.show = function () {
  1691. this.el_.style.display = 'block';
  1692. return this;
  1693. };
  1694. /**
  1695. * Hide the component element if hidden
  1696. *
  1697. * @return {vjs.Component}
  1698. */
  1699. vjs.Component.prototype.hide = function () {
  1700. this.el_.style.display = 'none';
  1701. return this;
  1702. };
  1703. /**
  1704. * Lock an item in its visible state
  1705. * To be used with fadeIn/fadeOut.
  1706. *
  1707. * @return {vjs.Component}
  1708. * @private
  1709. */
  1710. vjs.Component.prototype.lockShowing = function () {
  1711. this.addClass('vjs-lock-showing');
  1712. return this;
  1713. };
  1714. /**
  1715. * Unlock an item to be hidden
  1716. * To be used with fadeIn/fadeOut.
  1717. *
  1718. * @return {vjs.Component}
  1719. * @private
  1720. */
  1721. vjs.Component.prototype.unlockShowing = function () {
  1722. this.removeClass('vjs-lock-showing');
  1723. return this;
  1724. };
  1725. /**
  1726. * Disable component by making it unshowable
  1727. */
  1728. vjs.Component.prototype.disable = function () {
  1729. this.hide();
  1730. this.show = function () { };
  1731. };
  1732. /**
  1733. * Set or get the width of the component (CSS values)
  1734. *
  1735. * Video tag width/height only work in pixels. No percents.
  1736. * But allowing limited percents use. e.g. width() will return number+%, not computed width
  1737. *
  1738. * @param {Number|String=} num Optional width number
  1739. * @param {Boolean} skipListeners Skip the 'resize' event trigger
  1740. * @return {vjs.Component} Returns 'this' if width was set
  1741. * @return {Number|String} Returns the width if nothing was set
  1742. */
  1743. vjs.Component.prototype.width = function (num, skipListeners) {
  1744. return this.dimension('width', num, skipListeners);
  1745. };
  1746. /**
  1747. * Get or set the height of the component (CSS values)
  1748. *
  1749. * @param {Number|String=} num New component height
  1750. * @param {Boolean=} skipListeners Skip the resize event trigger
  1751. * @return {vjs.Component} The component if the height was set
  1752. * @return {Number|String} The height if it wasn't set
  1753. */
  1754. vjs.Component.prototype.height = function (num, skipListeners) {
  1755. return this.dimension('height', num, skipListeners);
  1756. };
  1757. /**
  1758. * Set both width and height at the same time
  1759. *
  1760. * @param {Number|String} width
  1761. * @param {Number|String} height
  1762. * @return {vjs.Component} The component
  1763. */
  1764. vjs.Component.prototype.dimensions = function (width, height) {
  1765. // Skip resize listeners on width for optimization
  1766. return this.width(width, true).height(height);
  1767. };
  1768. /**
  1769. * Get or set width or height
  1770. *
  1771. * This is the shared code for the width() and height() methods.
  1772. * All for an integer, integer + 'px' or integer + '%';
  1773. *
  1774. * Known issue: Hidden elements officially have a width of 0. We're defaulting
  1775. * to the style.width value and falling back to computedStyle which has the
  1776. * hidden element issue. Info, but probably not an efficient fix:
  1777. * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
  1778. *
  1779. * @param {String} widthOrHeight 'width' or 'height'
  1780. * @param {Number|String=} num New dimension
  1781. * @param {Boolean=} skipListeners Skip resize event trigger
  1782. * @return {vjs.Component} The component if a dimension was set
  1783. * @return {Number|String} The dimension if nothing was set
  1784. * @private
  1785. */
  1786. vjs.Component.prototype.dimension = function (widthOrHeight, num, skipListeners) {
  1787. if (num !== undefined) {
  1788. // Check if using css width/height (% or px) and adjust
  1789. if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
  1790. this.el_.style[widthOrHeight] = num;
  1791. } else if (num === 'auto') {
  1792. this.el_.style[widthOrHeight] = '';
  1793. } else {
  1794. this.el_.style[widthOrHeight] = num + 'px';
  1795. }
  1796. // skipListeners allows us to avoid triggering the resize event when setting both width and height
  1797. if (!skipListeners) { this.trigger('resize'); }
  1798. // Return component
  1799. return this;
  1800. }
  1801. // Not setting a value, so getting it
  1802. // Make sure element exists
  1803. if (!this.el_) return 0;
  1804. // Get dimension value from style
  1805. var val = this.el_.style[widthOrHeight];
  1806. var pxIndex = val.indexOf('px');
  1807. if (pxIndex !== -1) {
  1808. // Return the pixel value with no 'px'
  1809. return parseInt(val.slice(0, pxIndex), 10);
  1810. // No px so using % or no style was set, so falling back to offsetWidth/height
  1811. // If component has display:none, offset will return 0
  1812. // TODO: handle display:none and no dimension style using px
  1813. } else {
  1814. return parseInt(this.el_['offset' + vjs.capitalize(widthOrHeight)], 10);
  1815. // ComputedStyle version.
  1816. // Only difference is if the element is hidden it will return
  1817. // the percent value (e.g. '100%'')
  1818. // instead of zero like offsetWidth returns.
  1819. // var val = vjs.getComputedStyleValue(this.el_, widthOrHeight);
  1820. // var pxIndex = val.indexOf('px');
  1821. // if (pxIndex !== -1) {
  1822. // return val.slice(0, pxIndex);
  1823. // } else {
  1824. // return val;
  1825. // }
  1826. }
  1827. };
  1828. /**
  1829. * Fired when the width and/or height of the component changes
  1830. * @event resize
  1831. */
  1832. vjs.Component.prototype.onResize;
  1833. /**
  1834. * Emit 'tap' events when touch events are supported
  1835. *
  1836. * This is used to support toggling the controls through a tap on the video.
  1837. *
  1838. * We're requireing them to be enabled because otherwise every component would
  1839. * have this extra overhead unnecessarily, on mobile devices where extra
  1840. * overhead is especially bad.
  1841. * @private
  1842. */
  1843. vjs.Component.prototype.emitTapEvents = function () {
  1844. var touchStart, touchTime, couldBeTap, noTap;
  1845. // Track the start time so we can determine how long the touch lasted
  1846. touchStart = 0;
  1847. this.on('touchstart', function (event) {
  1848. // Record start time so we can detect a tap vs. "touch and hold"
  1849. touchStart = new Date().getTime();
  1850. // Reset couldBeTap tracking
  1851. couldBeTap = true;
  1852. });
  1853. noTap = function () {
  1854. couldBeTap = false;
  1855. };
  1856. // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
  1857. this.on('touchmove', noTap);
  1858. this.on('touchleave', noTap);
  1859. this.on('touchcancel', noTap);
  1860. // When the touch ends, measure how long it took and trigger the appropriate
  1861. // event
  1862. this.on('touchend', function () {
  1863. // Proceed only if the touchmove/leave/cancel event didn't happen
  1864. if (couldBeTap === true) {
  1865. // Measure how long the touch lasted
  1866. touchTime = new Date().getTime() - touchStart;
  1867. // The touch needs to be quick in order to consider it a tap
  1868. if (touchTime < 250) {
  1869. this.trigger('tap');
  1870. // It may be good to copy the touchend event object and change the
  1871. // type to tap, if the other event properties aren't exact after
  1872. // vjs.fixEvent runs (e.g. event.target)
  1873. }
  1874. }
  1875. });
  1876. };
  1877. /* Button - Base class for all buttons
  1878. ================================================================================ */
  1879. /**
  1880. * Base class for all buttons
  1881. * @param {vjs.Player|Object} player
  1882. * @param {Object=} options
  1883. * @class
  1884. * @constructor
  1885. */
  1886. vjs.Button = vjs.Component.extend({
  1887. /**
  1888. * @constructor
  1889. * @inheritDoc
  1890. */
  1891. init: function (player, options) {
  1892. vjs.Component.call(this, player, options);
  1893. var touchstart = false;
  1894. this.on('touchstart', function (event) {
  1895. // Stop click and other mouse events from triggering also
  1896. event.preventDefault();
  1897. touchstart = true;
  1898. });
  1899. this.on('touchmove', function () {
  1900. touchstart = false;
  1901. });
  1902. var self = this;
  1903. this.on('touchend', function (event) {
  1904. if (touchstart) {
  1905. self.onClick(event);
  1906. }
  1907. event.preventDefault();
  1908. });
  1909. this.on('click', this.onClick);
  1910. this.on('focus', this.onFocus);
  1911. this.on('blur', this.onBlur);
  1912. }
  1913. });
  1914. vjs.Button.prototype.createEl = function (type, props) {
  1915. // Add standard Aria and Tabindex info
  1916. props = vjs.obj.merge({
  1917. className: this.buildCSSClass(),
  1918. innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">' + (this.buttonText || 'Need Text') + '</span></div>',
  1919. role: 'button',
  1920. 'aria-live': 'polite', // let the screen reader user know that the text of the button may change
  1921. tabIndex: 0
  1922. }, props);
  1923. return vjs.Component.prototype.createEl.call(this, type, props);
  1924. };
  1925. vjs.Button.prototype.buildCSSClass = function () {
  1926. // TODO: Change vjs-control to vjs-button?
  1927. return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);
  1928. };
  1929. // Click - Override with specific functionality for button
  1930. vjs.Button.prototype.onClick = function () { };
  1931. // Focus - Add keyboard functionality to element
  1932. vjs.Button.prototype.onFocus = function () {
  1933. vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
  1934. };
  1935. // KeyPress (document level) - Trigger click when keys are pressed
  1936. vjs.Button.prototype.onKeyPress = function (event) {
  1937. // Check for space bar (32) or enter (13) keys
  1938. if (event.which == 32 || event.which == 13) {
  1939. event.preventDefault();
  1940. this.onClick();
  1941. }
  1942. };
  1943. // Blur - Remove keyboard triggers
  1944. vjs.Button.prototype.onBlur = function () {
  1945. vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
  1946. };
  1947. /* Slider
  1948. ================================================================================ */
  1949. /**
  1950. * The base functionality for sliders like the volume bar and seek bar
  1951. *
  1952. * @param {vjs.Player|Object} player
  1953. * @param {Object=} options
  1954. * @constructor
  1955. */
  1956. vjs.Slider = vjs.Component.extend({
  1957. /** @constructor */
  1958. init: function (player, options) {
  1959. vjs.Component.call(this, player, options);
  1960. // Set property names to bar and handle to match with the child Slider class is looking for
  1961. this.bar = this.getChild(this.options_['barName']);
  1962. this.handle = this.getChild(this.options_['handleName']);
  1963. player.on(this.playerEvent, vjs.bind(this, this.update));
  1964. this.on('mousedown', this.onMouseDown);
  1965. this.on('touchstart', this.onMouseDown);
  1966. this.on('focus', this.onFocus);
  1967. this.on('blur', this.onBlur);
  1968. this.on('click', this.onClick);
  1969. this.player_.on('controlsvisible', vjs.bind(this, this.update));
  1970. // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520
  1971. // this.player_.one('timeupdate', vjs.bind(this, this.update));
  1972. player.ready(vjs.bind(this, this.update));
  1973. this.boundEvents = {};
  1974. }
  1975. });
  1976. vjs.Slider.prototype.createEl = function (type, props) {
  1977. props = props || {};
  1978. // Add the slider element class to all sub classes
  1979. props.className = props.className + ' vjs-slider';
  1980. props = vjs.obj.merge({
  1981. role: 'slider',
  1982. 'aria-valuenow': 0,
  1983. 'aria-valuemin': 0,
  1984. 'aria-valuemax': 100,
  1985. tabIndex: 0
  1986. }, props);
  1987. return vjs.Component.prototype.createEl.call(this, type, props);
  1988. };
  1989. vjs.Slider.prototype.onMouseDown = function (event) {
  1990. event.preventDefault();
  1991. vjs.blockTextSelection();
  1992. this.boundEvents.move = vjs.bind(this, this.onMouseMove);
  1993. this.boundEvents.end = vjs.bind(this, this.onMouseUp);
  1994. vjs.on(document, 'mousemove', this.boundEvents.move);
  1995. vjs.on(document, 'mouseup', this.boundEvents.end);
  1996. vjs.on(document, 'touchmove', this.boundEvents.move);
  1997. vjs.on(document, 'touchend', this.boundEvents.end);
  1998. this.onMouseMove(event);
  1999. };
  2000. vjs.Slider.prototype.onMouseUp = function () {
  2001. vjs.unblockTextSelection();
  2002. vjs.off(document, 'mousemove', this.boundEvents.move, false);
  2003. vjs.off(document, 'mouseup', this.boundEvents.end, false);
  2004. vjs.off(document, 'touchmove', this.boundEvents.move, false);
  2005. vjs.off(document, 'touchend', this.boundEvents.end, false);
  2006. this.update();
  2007. };
  2008. vjs.Slider.prototype.update = function () {
  2009. // In VolumeBar init we have a setTimeout for update that pops and update to the end of the
  2010. // execution stack. The player is destroyed before then update will cause an error
  2011. if (!this.el_) return;
  2012. // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
  2013. // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
  2014. // var progress = (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
  2015. var barProgress,
  2016. progress = this.getPercent(),
  2017. handle = this.handle,
  2018. bar = this.bar;
  2019. // Protect against no duration and other division issues
  2020. if (isNaN(progress)) { progress = 0; }
  2021. barProgress = progress;
  2022. // If there is a handle, we need to account for the handle in our calculation for progress bar
  2023. // so that it doesn't fall short of or extend past the handle.
  2024. if (handle) {
  2025. var box = this.el_,
  2026. boxWidth = box.offsetWidth,
  2027. handleWidth = handle.el().offsetWidth,
  2028. // The width of the handle in percent of the containing box
  2029. // In IE, widths may not be ready yet causing NaN
  2030. handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,
  2031. // Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
  2032. // There is a margin of half the handle's width on both sides.
  2033. boxAdjustedPercent = 1 - handlePercent,
  2034. // Adjust the progress that we'll use to set widths to the new adjusted box width
  2035. adjustedProgress = progress * boxAdjustedPercent;
  2036. // The bar does reach the left side, so we need to account for this in the bar's width
  2037. barProgress = adjustedProgress + (handlePercent / 2);
  2038. // Move the handle from the left based on the adjected progress
  2039. handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';
  2040. }
  2041. // Set the new bar width
  2042. bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
  2043. };
  2044. vjs.Slider.prototype.calculateDistance = function (event) {
  2045. var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY;
  2046. el = this.el_;
  2047. box = vjs.findPosition(el);
  2048. boxW = boxH = el.offsetWidth;
  2049. handle = this.handle;
  2050. if (this.options_.vertical) {
  2051. boxY = box.top;
  2052. if (event.changedTouches) {
  2053. pageY = event.changedTouches[0].pageY;
  2054. } else {
  2055. pageY = event.pageY;
  2056. }
  2057. if (handle) {
  2058. var handleH = handle.el().offsetHeight;
  2059. // Adjusted X and Width, so handle doesn't go outside the bar
  2060. boxY = boxY + (handleH / 2);
  2061. boxH = boxH - handleH;
  2062. }
  2063. // Percent that the click is through the adjusted area
  2064. return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
  2065. } else {
  2066. boxX = box.left;
  2067. if (event.changedTouches) {
  2068. pageX = event.changedTouches[0].pageX;
  2069. } else {
  2070. pageX = event.pageX;
  2071. }
  2072. if (handle) {
  2073. var handleW = handle.el().offsetWidth;
  2074. // Adjusted X and Width, so handle doesn't go outside the bar
  2075. boxX = boxX + (handleW / 2);
  2076. boxW = boxW - handleW;
  2077. }
  2078. // Percent that the click is through the adjusted area
  2079. return Math.max(0, Math.min(1, (pageX - boxX) / boxW));
  2080. }
  2081. };
  2082. vjs.Slider.prototype.onFocus = function () {
  2083. vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
  2084. };
  2085. vjs.Slider.prototype.onKeyPress = function (event) {
  2086. if (event.which == 37) { // Left Arrow
  2087. event.preventDefault();
  2088. this.stepBack();
  2089. } else if (event.which == 39) { // Right Arrow
  2090. event.preventDefault();
  2091. this.stepForward();
  2092. }
  2093. };
  2094. vjs.Slider.prototype.onBlur = function () {
  2095. vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
  2096. };
  2097. /**
  2098. * Listener for click events on slider, used to prevent clicks
  2099. * from bubbling up to parent elements like button menus.
  2100. * @param {Object} event Event object
  2101. */
  2102. vjs.Slider.prototype.onClick = function (event) {
  2103. event.stopImmediatePropagation();
  2104. event.preventDefault();
  2105. };
  2106. /**
  2107. * SeekBar Behavior includes play progress bar, and seek handle
  2108. * Needed so it can determine seek position based on handle position/size
  2109. * @param {vjs.Player|Object} player
  2110. * @param {Object=} options
  2111. * @constructor
  2112. */
  2113. vjs.SliderHandle = vjs.Component.extend();
  2114. /**
  2115. * Default value of the slider
  2116. *
  2117. * @type {Number}
  2118. * @private
  2119. */
  2120. vjs.SliderHandle.prototype.defaultValue = 0;
  2121. /** @inheritDoc */
  2122. vjs.SliderHandle.prototype.createEl = function (type, props) {
  2123. props = props || {};
  2124. // Add the slider element class to all sub classes
  2125. props.className = props.className + ' vjs-slider-handle';
  2126. props = vjs.obj.merge({
  2127. innerHTML: '<span class="vjs-control-text">' + this.defaultValue + '</span>'
  2128. }, props);
  2129. return vjs.Component.prototype.createEl.call(this, 'div', props);
  2130. };
  2131. /* Menu
  2132. ================================================================================ */
  2133. /**
  2134. * The Menu component is used to build pop up menus, including subtitle and
  2135. * captions selection menus.
  2136. *
  2137. * @param {vjs.Player|Object} player
  2138. * @param {Object=} options
  2139. * @class
  2140. * @constructor
  2141. */
  2142. vjs.Menu = vjs.Component.extend();
  2143. /**
  2144. * Add a menu item to the menu
  2145. * @param {Object|String} component Component or component type to add
  2146. */
  2147. vjs.Menu.prototype.addItem = function (component) {
  2148. this.addChild(component);
  2149. component.on('click', vjs.bind(this, function () {
  2150. this.unlockShowing();
  2151. }));
  2152. };
  2153. /** @inheritDoc */
  2154. vjs.Menu.prototype.createEl = function () {
  2155. var contentElType = this.options().contentElType || 'ul';
  2156. this.contentEl_ = vjs.createEl(contentElType, {
  2157. className: 'vjs-menu-content'
  2158. });
  2159. var el = vjs.Component.prototype.createEl.call(this, 'div', {
  2160. append: this.contentEl_,
  2161. className: 'vjs-menu'
  2162. });
  2163. el.appendChild(this.contentEl_);
  2164. // Prevent clicks from bubbling up. Needed for Menu Buttons,
  2165. // where a click on the parent is significant
  2166. vjs.on(el, 'click', function (event) {
  2167. event.preventDefault();
  2168. event.stopImmediatePropagation();
  2169. });
  2170. return el;
  2171. };
  2172. /**
  2173. * The component for a menu item. `<li>`
  2174. *
  2175. * @param {vjs.Player|Object} player
  2176. * @param {Object=} options
  2177. * @class
  2178. * @constructor
  2179. */
  2180. vjs.MenuItem = vjs.Button.extend({
  2181. /** @constructor */
  2182. init: function (player, options) {
  2183. vjs.Button.call(this, player, options);
  2184. this.selected(options['selected']);
  2185. }
  2186. });
  2187. /** @inheritDoc */
  2188. vjs.MenuItem.prototype.createEl = function (type, props) {
  2189. return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({
  2190. className: 'vjs-menu-item',
  2191. innerHTML: this.options_['label']
  2192. }, props));
  2193. };
  2194. /**
  2195. * Handle a click on the menu item, and set it to selected
  2196. */
  2197. vjs.MenuItem.prototype.onClick = function () {
  2198. this.selected(true);
  2199. };
  2200. /**
  2201. * Set this menu item as selected or not
  2202. * @param {Boolean} selected
  2203. */
  2204. vjs.MenuItem.prototype.selected = function (selected) {
  2205. if (selected) {
  2206. this.addClass('vjs-selected');
  2207. this.el_.setAttribute('aria-selected', true);
  2208. } else {
  2209. this.removeClass('vjs-selected');
  2210. this.el_.setAttribute('aria-selected', false);
  2211. }
  2212. };
  2213. /**
  2214. * A button class with a popup menu
  2215. * @param {vjs.Player|Object} player
  2216. * @param {Object=} options
  2217. * @constructor
  2218. */
  2219. vjs.MenuButton = vjs.Button.extend({
  2220. /** @constructor */
  2221. init: function (player, options) {
  2222. vjs.Button.call(this, player, options);
  2223. this.menu = this.createMenu();
  2224. // Add list to element
  2225. this.addChild(this.menu);
  2226. // Automatically hide empty menu buttons
  2227. if (this.items && this.items.length === 0) {
  2228. this.hide();
  2229. }
  2230. this.on('keyup', this.onKeyPress);
  2231. this.el_.setAttribute('aria-haspopup', true);
  2232. this.el_.setAttribute('role', 'button');
  2233. }
  2234. });
  2235. /**
  2236. * Track the state of the menu button
  2237. * @type {Boolean}
  2238. * @private
  2239. */
  2240. vjs.MenuButton.prototype.buttonPressed_ = false;
  2241. vjs.MenuButton.prototype.createMenu = function () {
  2242. var menu = new vjs.Menu(this.player_);
  2243. // Add a title list item to the top
  2244. if (this.options().title) {
  2245. menu.el().appendChild(vjs.createEl('li', {
  2246. className: 'vjs-menu-title',
  2247. innerHTML: vjs.capitalize(this.kind_),
  2248. tabindex: -1
  2249. }));
  2250. }
  2251. this.items = this['createItems']();
  2252. if (this.items) {
  2253. // Add menu items to the menu
  2254. for (var i = 0; i < this.items.length; i++) {
  2255. menu.addItem(this.items[i]);
  2256. }
  2257. }
  2258. return menu;
  2259. };
  2260. /**
  2261. * Create the list of menu items. Specific to each subclass.
  2262. */
  2263. vjs.MenuButton.prototype.createItems = function () { };
  2264. /** @inheritDoc */
  2265. vjs.MenuButton.prototype.buildCSSClass = function () {
  2266. return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);
  2267. };
  2268. // Focus - Add keyboard functionality to element
  2269. // This function is not needed anymore. Instead, the keyboard functionality is handled by
  2270. // treating the button as triggering a submenu. When the button is pressed, the submenu
  2271. // appears. Pressing the button again makes the submenu disappear.
  2272. vjs.MenuButton.prototype.onFocus = function () { };
  2273. // Can't turn off list display that we turned on with focus, because list would go away.
  2274. vjs.MenuButton.prototype.onBlur = function () { };
  2275. vjs.MenuButton.prototype.onClick = function () {
  2276. // When you click the button it adds focus, which will show the menu indefinitely.
  2277. // So we'll remove focus when the mouse leaves the button.
  2278. // Focus is needed for tab navigation.
  2279. this.one('mouseout', vjs.bind(this, function () {
  2280. this.menu.unlockShowing();
  2281. this.el_.blur();
  2282. }));
  2283. if (this.buttonPressed_) {
  2284. this.unpressButton();
  2285. } else {
  2286. this.pressButton();
  2287. }
  2288. };
  2289. vjs.MenuButton.prototype.onKeyPress = function (event) {
  2290. event.preventDefault();
  2291. // Check for space bar (32) or enter (13) keys
  2292. if (event.which == 32 || event.which == 13) {
  2293. if (this.buttonPressed_) {
  2294. this.unpressButton();
  2295. } else {
  2296. this.pressButton();
  2297. }
  2298. // Check for escape (27) key
  2299. } else if (event.which == 27) {
  2300. if (this.buttonPressed_) {
  2301. this.unpressButton();
  2302. }
  2303. }
  2304. };
  2305. vjs.MenuButton.prototype.pressButton = function () {
  2306. this.buttonPressed_ = true;
  2307. this.menu.lockShowing();
  2308. this.el_.setAttribute('aria-pressed', true);
  2309. if (this.items && this.items.length > 0) {
  2310. this.items[0].el().focus(); // set the focus to the title of the submenu
  2311. }
  2312. };
  2313. vjs.MenuButton.prototype.unpressButton = function () {
  2314. this.buttonPressed_ = false;
  2315. this.menu.unlockShowing();
  2316. this.el_.setAttribute('aria-pressed', false);
  2317. };
  2318. /**
  2319. * An instance of the `vjs.Player` class is created when any of the Video.js setup methods are used to initialize a video.
  2320. *
  2321. * ```js
  2322. * var myPlayer = videojs('example_video_1');
  2323. * ```
  2324. *
  2325. * In the follwing example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.
  2326. *
  2327. * ```html
  2328. * <video id="example_video_1" data-setup='{}' controls>
  2329. * <source src="my-source.mp4" type="video/mp4">
  2330. * </video>
  2331. * ```
  2332. *
  2333. * After an instance has been created it can be accessed globally using `Video('example_video_1')`.
  2334. *
  2335. * @class
  2336. * @extends vjs.Component
  2337. */
  2338. vjs.Player = vjs.Component.extend({
  2339. /**
  2340. * player's constructor function
  2341. *
  2342. * @constructs
  2343. * @method init
  2344. * @param {Element} tag The original video tag used for configuring options
  2345. * @param {Object=} options Player options
  2346. * @param {Function=} ready Ready callback function
  2347. */
  2348. init: function (tag, options, ready) {
  2349. this.tag = tag; // Store the original tag used to set options
  2350. // Set Options
  2351. // The options argument overrides options set in the video tag
  2352. // which overrides globally set options.
  2353. // This latter part coincides with the load order
  2354. // (tag must exist before Player)
  2355. options = vjs.obj.merge(this.getTagSettings(tag), options);
  2356. // Cache for video property values.
  2357. this.cache_ = {};
  2358. // Set poster
  2359. this.poster_ = options['poster'];
  2360. // Set controls
  2361. this.controls_ = options['controls'];
  2362. // Original tag settings stored in options
  2363. // now remove immediately so native controls don't flash.
  2364. // May be turned back on by HTML5 tech if nativeControlsForTouch is true
  2365. tag.controls = false;
  2366. // Run base component initializing with new options.
  2367. // Builds the element through createEl()
  2368. // Inits and embeds any child components in opts
  2369. vjs.Component.call(this, this, options, ready);
  2370. // Update controls className. Can't do this when the controls are initially
  2371. // set because the element doesn't exist yet.
  2372. if (this.controls()) {
  2373. this.addClass('vjs-controls-enabled');
  2374. } else {
  2375. this.addClass('vjs-controls-disabled');
  2376. }
  2377. // TODO: Make this smarter. Toggle user state between touching/mousing
  2378. // using events, since devices can have both touch and mouse events.
  2379. // if (vjs.TOUCH_ENABLED) {
  2380. // this.addClass('vjs-touch-enabled');
  2381. // }
  2382. // Firstplay event implimentation. Not sold on the event yet.
  2383. // Could probably just check currentTime==0?
  2384. this.one('play', function (e) {
  2385. var fpEvent = { type: 'firstplay', target: this.el_ };
  2386. // Using vjs.trigger so we can check if default was prevented
  2387. var keepGoing = vjs.trigger(this.el_, fpEvent);
  2388. if (!keepGoing) {
  2389. e.preventDefault();
  2390. e.stopPropagation();
  2391. e.stopImmediatePropagation();
  2392. }
  2393. });
  2394. this.on('ended', this.onEnded);
  2395. this.on('play', this.onPlay);
  2396. this.on('firstplay', this.onFirstPlay);
  2397. this.on('pause', this.onPause);
  2398. this.on('progress', this.onProgress);
  2399. this.on('durationchange', this.onDurationChange);
  2400. this.on('error', this.onError);
  2401. this.on('fullscreenchange', this.onFullscreenChange);
  2402. // Make player easily findable by ID
  2403. vjs.players[this.id_] = this;
  2404. if (options['plugins']) {
  2405. vjs.obj.each(options['plugins'], function (key, val) {
  2406. this[key](val);
  2407. }, this);
  2408. }
  2409. this.listenForUserActivity();
  2410. }
  2411. });
  2412. /**
  2413. * Player instance options, surfaced using vjs.options
  2414. * vjs.options = vjs.Player.prototype.options_
  2415. * Make changes in vjs.options, not here.
  2416. * All options should use string keys so they avoid
  2417. * renaming by closure compiler
  2418. * @type {Object}
  2419. * @private
  2420. */
  2421. vjs.Player.prototype.options_ = vjs.options;
  2422. /**
  2423. * Destroys the video player and does any necessary cleanup
  2424. *
  2425. * myPlayer.dispose();
  2426. *
  2427. * This is especially helpful if you are dynamically adding and removing videos
  2428. * to/from the DOM.
  2429. */
  2430. vjs.Player.prototype.dispose = function () {
  2431. this.trigger('dispose');
  2432. // prevent dispose from being called twice
  2433. this.off('dispose');
  2434. // Kill reference to this player
  2435. vjs.players[this.id_] = null;
  2436. if (this.tag && this.tag['player']) { this.tag['player'] = null; }
  2437. if (this.el_ && this.el_['player']) { this.el_['player'] = null; }
  2438. // Ensure that tracking progress and time progress will stop and plater deleted
  2439. this.stopTrackingProgress();
  2440. this.stopTrackingCurrentTime();
  2441. if (this.tech) { this.tech.dispose(); }
  2442. // Component dispose
  2443. vjs.Component.prototype.dispose.call(this);
  2444. };
  2445. vjs.Player.prototype.getTagSettings = function (tag) {
  2446. var options = {
  2447. 'sources': [],
  2448. 'tracks': []
  2449. };
  2450. vjs.obj.merge(options, vjs.getAttributeValues(tag));
  2451. // Get tag children settings
  2452. if (tag.hasChildNodes()) {
  2453. var children, child, childName, i, j;
  2454. children = tag.childNodes;
  2455. for (i = 0, j = children.length; i < j; i++) {
  2456. child = children[i];
  2457. // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
  2458. childName = child.nodeName.toLowerCase();
  2459. if (childName === 'source') {
  2460. options['sources'].push(vjs.getAttributeValues(child));
  2461. } else if (childName === 'track') {
  2462. options['tracks'].push(vjs.getAttributeValues(child));
  2463. }
  2464. }
  2465. }
  2466. return options;
  2467. };
  2468. vjs.Player.prototype.createEl = function () {
  2469. var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
  2470. var tag = this.tag;
  2471. // Remove width/height attrs from tag so CSS can make it 100% width/height
  2472. tag.removeAttribute('width');
  2473. tag.removeAttribute('height');
  2474. // Empty video tag tracks so the built-in player doesn't use them also.
  2475. // This may not be fast enough to stop HTML5 browsers from reading the tags
  2476. // so we'll need to turn off any default tracks if we're manually doing
  2477. // captions and subtitles. videoElement.textTracks
  2478. if (tag.hasChildNodes()) {
  2479. var nodes, nodesLength, i, node, nodeName, removeNodes;
  2480. nodes = tag.childNodes;
  2481. nodesLength = nodes.length;
  2482. removeNodes = [];
  2483. while (nodesLength--) {
  2484. node = nodes[nodesLength];
  2485. nodeName = node.nodeName.toLowerCase();
  2486. if (nodeName === 'track') {
  2487. removeNodes.push(node);
  2488. }
  2489. }
  2490. for (i = 0; i < removeNodes.length; i++) {
  2491. tag.removeChild(removeNodes[i]);
  2492. }
  2493. }
  2494. // Make sure tag ID exists
  2495. tag.id = tag.id || 'vjs_video_' + vjs.guid++;
  2496. // Give video tag ID and class to player div
  2497. // ID will now reference player box, not the video tag
  2498. el.id = tag.id;
  2499. el.className = tag.className;
  2500. // Update tag id/class for use as HTML5 playback tech
  2501. // Might think we should do this after embedding in container so .vjs-tech class
  2502. // doesn't flash 100% width/height, but class only applies with .video-js parent
  2503. tag.id += '_html5_api';
  2504. tag.className = 'vjs-tech';
  2505. // Make player findable on elements
  2506. tag['player'] = el['player'] = this;
  2507. // Default state of video is paused
  2508. this.addClass('vjs-paused');
  2509. // Make box use width/height of tag, or rely on default implementation
  2510. // Enforce with CSS since width/height attrs don't work on divs
  2511. this.width(this.options_['width'], true); // (true) Skip resize listener on load
  2512. this.height(this.options_['height'], true);
  2513. // Wrap video tag in div (el/box) container
  2514. if (tag.parentNode) {
  2515. tag.parentNode.insertBefore(el, tag);
  2516. }
  2517. vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
  2518. return el;
  2519. };
  2520. // /* Media Technology (tech)
  2521. // ================================================================================ */
  2522. // Load/Create an instance of playback technlogy including element and API methods
  2523. // And append playback element in player div.
  2524. vjs.Player.prototype.loadTech = function (techName, source) {
  2525. // Pause and remove current playback technology
  2526. if (this.tech) {
  2527. this.unloadTech();
  2528. // if this is the first time loading, HTML5 tag will exist but won't be initialized
  2529. // so we need to remove it if we're not loading HTML5
  2530. } else if (techName !== 'Html5' && this.tag) {
  2531. vjs.Html5.disposeMediaElement(this.tag);
  2532. this.tag = null;
  2533. }
  2534. this.techName = techName;
  2535. // Turn off API access because we're loading a new tech that might load asynchronously
  2536. this.isReady_ = false;
  2537. var techReady = function () {
  2538. this.player_.triggerReady();
  2539. // Manually track progress in cases where the browser/flash player doesn't report it.
  2540. if (!this.features['progressEvents']) {
  2541. this.player_.manualProgressOn();
  2542. }
  2543. // Manually track timeudpates in cases where the browser/flash player doesn't report it.
  2544. if (!this.features['timeupdateEvents']) {
  2545. this.player_.manualTimeUpdatesOn();
  2546. }
  2547. };
  2548. // Grab tech-specific options from player options and add source and parent element to use.
  2549. var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);
  2550. if (source) {
  2551. if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
  2552. techOptions['startTime'] = this.cache_.currentTime;
  2553. }
  2554. this.cache_.src = source.src;
  2555. }
  2556. // Initialize tech instance
  2557. this.tech = new window['videojs'][techName](this, techOptions);
  2558. this.tech.ready(techReady);
  2559. };
  2560. vjs.Player.prototype.unloadTech = function () {
  2561. this.isReady_ = false;
  2562. this.tech.dispose();
  2563. // Turn off any manual progress or timeupdate tracking
  2564. if (this.manualProgress) { this.manualProgressOff(); }
  2565. if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
  2566. this.tech = false;
  2567. };
  2568. // There's many issues around changing the size of a Flash (or other plugin) object.
  2569. // First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
  2570. // Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
  2571. // To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
  2572. // reloadTech: function(betweenFn){
  2573. // vjs.log('unloadingTech')
  2574. // this.unloadTech();
  2575. // vjs.log('unloadedTech')
  2576. // if (betweenFn) { betweenFn.call(); }
  2577. // vjs.log('LoadingTech')
  2578. // this.loadTech(this.techName, { src: this.cache_.src })
  2579. // vjs.log('loadedTech')
  2580. // },
  2581. /* Fallbacks for unsupported event types
  2582. ================================================================================ */
  2583. // Manually trigger progress events based on changes to the buffered amount
  2584. // Many flash players and older HTML5 browsers don't send progress or progress-like events
  2585. vjs.Player.prototype.manualProgressOn = function () {
  2586. this.manualProgress = true;
  2587. // Trigger progress watching when a source begins loading
  2588. this.trackProgress();
  2589. // Watch for a native progress event call on the tech element
  2590. // In HTML5, some older versions don't support the progress event
  2591. // So we're assuming they don't, and turning off manual progress if they do.
  2592. // As opposed to doing user agent detection
  2593. this.tech.one('progress', function () {
  2594. // Update known progress support for this playback technology
  2595. this.features['progressEvents'] = true;
  2596. // Turn off manual progress tracking
  2597. this.player_.manualProgressOff();
  2598. });
  2599. };
  2600. vjs.Player.prototype.manualProgressOff = function () {
  2601. this.manualProgress = false;
  2602. this.stopTrackingProgress();
  2603. };
  2604. vjs.Player.prototype.trackProgress = function () {
  2605. this.progressInterval = setInterval(vjs.bind(this, function () {
  2606. // Don't trigger unless buffered amount is greater than last time
  2607. // log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())
  2608. /* TODO: update for multiple buffered regions */
  2609. if (this.cache_.bufferEnd < this.buffered().end(0)) {
  2610. this.trigger('progress');
  2611. } else if (this.bufferedPercent() == 1) {
  2612. this.stopTrackingProgress();
  2613. this.trigger('progress'); // Last update
  2614. }
  2615. }), 500);
  2616. };
  2617. vjs.Player.prototype.stopTrackingProgress = function () { clearInterval(this.progressInterval); };
  2618. /*! Time Tracking -------------------------------------------------------------- */
  2619. vjs.Player.prototype.manualTimeUpdatesOn = function () {
  2620. this.manualTimeUpdates = true;
  2621. this.on('play', this.trackCurrentTime);
  2622. this.on('pause', this.stopTrackingCurrentTime);
  2623. // timeupdate is also called by .currentTime whenever current time is set
  2624. // Watch for native timeupdate event
  2625. this.tech.one('timeupdate', function () {
  2626. // Update known progress support for this playback technology
  2627. this.features['timeupdateEvents'] = true;
  2628. // Turn off manual progress tracking
  2629. this.player_.manualTimeUpdatesOff();
  2630. });
  2631. };
  2632. vjs.Player.prototype.manualTimeUpdatesOff = function () {
  2633. this.manualTimeUpdates = false;
  2634. this.stopTrackingCurrentTime();
  2635. this.off('play', this.trackCurrentTime);
  2636. this.off('pause', this.stopTrackingCurrentTime);
  2637. };
  2638. vjs.Player.prototype.trackCurrentTime = function () {
  2639. if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
  2640. this.currentTimeInterval = setInterval(vjs.bind(this, function () {
  2641. this.trigger('timeupdate');
  2642. }), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  2643. };
  2644. // Turn off play progress tracking (when paused or dragging)
  2645. vjs.Player.prototype.stopTrackingCurrentTime = function () { clearInterval(this.currentTimeInterval); };
  2646. // /* Player event handlers (how the player reacts to certain events)
  2647. // ================================================================================ */
  2648. /**
  2649. * Fired when the user agent begins looking for media data
  2650. * @event loadstart
  2651. */
  2652. vjs.Player.prototype.onLoadStart;
  2653. /**
  2654. * Fired when the player has initial duration and dimension information
  2655. * @event loadedmetadata
  2656. */
  2657. vjs.Player.prototype.onLoadedMetaData;
  2658. /**
  2659. * Fired when the player has downloaded data at the current playback position
  2660. * @event loadeddata
  2661. */
  2662. vjs.Player.prototype.onLoadedData;
  2663. /**
  2664. * Fired when the player has finished downloading the source data
  2665. * @event loadedalldata
  2666. */
  2667. vjs.Player.prototype.onLoadedAllData;
  2668. /**
  2669. * Fired whenever the media begins or resumes playback
  2670. * @event play
  2671. */
  2672. vjs.Player.prototype.onPlay = function () {
  2673. vjs.removeClass(this.el_, 'vjs-paused');
  2674. vjs.addClass(this.el_, 'vjs-playing');
  2675. };
  2676. /**
  2677. * Fired the first time a video is played
  2678. *
  2679. * Not part of the HLS spec, and we're not sure if this is the best
  2680. * implementation yet, so use sparingly. If you don't have a reason to
  2681. * prevent playback, use `myPlayer.one('play');` instead.
  2682. *
  2683. * @event firstplay
  2684. */
  2685. vjs.Player.prototype.onFirstPlay = function () {
  2686. //If the first starttime attribute is specified
  2687. //then we will start at the given offset in seconds
  2688. if (this.options_['starttime']) {
  2689. this.currentTime(this.options_['starttime']);
  2690. }
  2691. this.addClass('vjs-has-started');
  2692. };
  2693. /**
  2694. * Fired whenever the media has been paused
  2695. * @event pause
  2696. */
  2697. vjs.Player.prototype.onPause = function () {
  2698. vjs.removeClass(this.el_, 'vjs-playing');
  2699. vjs.addClass(this.el_, 'vjs-paused');
  2700. };
  2701. /**
  2702. * Fired when the current playback position has changed
  2703. *
  2704. * During playback this is fired every 15-250 milliseconds, depnding on the
  2705. * playback technology in use.
  2706. * @event timeupdate
  2707. */
  2708. vjs.Player.prototype.onTimeUpdate;
  2709. /**
  2710. * Fired while the user agent is downloading media data
  2711. * @event progress
  2712. */
  2713. vjs.Player.prototype.onProgress = function () {
  2714. // Add custom event for when source is finished downloading.
  2715. if (this.bufferedPercent() == 1) {
  2716. this.trigger('loadedalldata');
  2717. }
  2718. };
  2719. /**
  2720. * Fired when the end of the media resource is reached (currentTime == duration)
  2721. * @event ended
  2722. */
  2723. vjs.Player.prototype.onEnded = function () {
  2724. if (this.options_['loop']) {
  2725. this.currentTime(0);
  2726. this.play();
  2727. }
  2728. };
  2729. /**
  2730. * Fired when the duration of the media resource is first known or changed
  2731. * @event durationchange
  2732. */
  2733. vjs.Player.prototype.onDurationChange = function () {
  2734. // Allows for cacheing value instead of asking player each time.
  2735. this.duration(this.techGet('duration'));
  2736. };
  2737. /**
  2738. * Fired when the volume changes
  2739. * @event volumechange
  2740. */
  2741. vjs.Player.prototype.onVolumeChange;
  2742. /**
  2743. * Fired when the player switches in or out of fullscreen mode
  2744. * @event fullscreenchange
  2745. */
  2746. vjs.Player.prototype.onFullscreenChange = function () {
  2747. if (this.isFullScreen) {
  2748. this.addClass('vjs-fullscreen');
  2749. } else {
  2750. this.removeClass('vjs-fullscreen');
  2751. }
  2752. };
  2753. /**
  2754. * Fired when there is an error in playback
  2755. * @event error
  2756. */
  2757. vjs.Player.prototype.onError = function (e) {
  2758. vjs.log('Video Error', e);
  2759. };
  2760. // /* Player API
  2761. // ================================================================================ */
  2762. /**
  2763. * Object for cached values.
  2764. * @private
  2765. */
  2766. vjs.Player.prototype.cache_;
  2767. vjs.Player.prototype.getCache = function () {
  2768. return this.cache_;
  2769. };
  2770. // Pass values to the playback tech
  2771. vjs.Player.prototype.techCall = function (method, arg) {
  2772. // If it's not ready yet, call method when it is
  2773. if (this.tech && !this.tech.isReady_) {
  2774. this.tech.ready(function () {
  2775. this[method](arg);
  2776. });
  2777. // Otherwise call method now
  2778. } else {
  2779. try {
  2780. this.tech[method](arg);
  2781. } catch (e) {
  2782. vjs.log(e);
  2783. throw e;
  2784. }
  2785. }
  2786. };
  2787. // Get calls can't wait for the tech, and sometimes don't need to.
  2788. vjs.Player.prototype.techGet = function (method) {
  2789. if (this.tech && this.tech.isReady_) {
  2790. // Flash likes to die and reload when you hide or reposition it.
  2791. // In these cases the object methods go away and we get errors.
  2792. // When that happens we'll catch the errors and inform tech that it's not ready any more.
  2793. try {
  2794. return this.tech[method]();
  2795. } catch (e) {
  2796. // When building additional tech libs, an expected method may not be defined yet
  2797. if (this.tech[method] === undefined) {
  2798. vjs.log('Video.js: ' + method + ' method not defined for ' + this.techName + ' playback technology.', e);
  2799. } else {
  2800. // When a method isn't available on the object it throws a TypeError
  2801. if (e.name == 'TypeError') {
  2802. vjs.log('Video.js: ' + method + ' unavailable on ' + this.techName + ' playback technology element.', e);
  2803. this.tech.isReady_ = false;
  2804. } else {
  2805. vjs.log(e);
  2806. }
  2807. }
  2808. throw e;
  2809. }
  2810. }
  2811. return;
  2812. };
  2813. /**
  2814. * start media playback
  2815. *
  2816. * myPlayer.play();
  2817. *
  2818. * @return {vjs.Player} self
  2819. */
  2820. vjs.Player.prototype.play = function () {
  2821. this.techCall('play');
  2822. return this;
  2823. };
  2824. /**
  2825. * Pause the video playback
  2826. *
  2827. * myPlayer.pause();
  2828. *
  2829. * @return {vjs.Player} self
  2830. */
  2831. vjs.Player.prototype.pause = function () {
  2832. this.techCall('pause');
  2833. return this;
  2834. };
  2835. /**
  2836. * Check if the player is paused
  2837. *
  2838. * var isPaused = myPlayer.paused();
  2839. * var isPlaying = !myPlayer.paused();
  2840. *
  2841. * @return {Boolean} false if the media is currently playing, or true otherwise
  2842. */
  2843. vjs.Player.prototype.paused = function () {
  2844. // The initial state of paused should be true (in Safari it's actually false)
  2845. return (this.techGet('paused') === false) ? false : true;
  2846. };
  2847. /**
  2848. * Get or set the current time (in seconds)
  2849. *
  2850. * // get
  2851. * var whereYouAt = myPlayer.currentTime();
  2852. *
  2853. * // set
  2854. * myPlayer.currentTime(120); // 2 minutes into the video
  2855. *
  2856. * @param {Number|String=} seconds The time to seek to
  2857. * @return {Number} The time in seconds, when not setting
  2858. * @return {vjs.Player} self, when the current time is set
  2859. */
  2860. vjs.Player.prototype.currentTime = function (seconds) {
  2861. if (seconds !== undefined) {
  2862. // cache the last set value for smoother scrubbing
  2863. this.cache_.lastSetCurrentTime = seconds;
  2864. this.techCall('setCurrentTime', seconds);
  2865. // improve the accuracy of manual timeupdates
  2866. if (this.manualTimeUpdates) { this.trigger('timeupdate'); }
  2867. return this;
  2868. }
  2869. // cache last currentTime and return
  2870. // default to 0 seconds
  2871. return this.cache_.currentTime = (this.techGet('currentTime') || 0);
  2872. };
  2873. /**
  2874. * Get the length in time of the video in seconds
  2875. *
  2876. * var lengthOfVideo = myPlayer.duration();
  2877. *
  2878. * **NOTE**: The video must have started loading before the duration can be
  2879. * known, and in the case of Flash, may not be known until the video starts
  2880. * playing.
  2881. *
  2882. * @return {Number} The duration of the video in seconds
  2883. */
  2884. vjs.Player.prototype.duration = function (seconds) {
  2885. if (seconds !== undefined) {
  2886. // cache the last set value for optimiized scrubbing (esp. Flash)
  2887. this.cache_.duration = parseFloat(seconds);
  2888. return this;
  2889. }
  2890. if (this.cache_.duration === undefined) {
  2891. this.onDurationChange();
  2892. }
  2893. return this.cache_.duration;
  2894. };
  2895. // Calculates how much time is left. Not in spec, but useful.
  2896. vjs.Player.prototype.remainingTime = function () {
  2897. return this.duration() - this.currentTime();
  2898. };
  2899. // http://dev.w3.org/html5/spec/video.html#dom-media-buffered
  2900. // Buffered returns a timerange object.
  2901. // Kind of like an array of portions of the video that have been downloaded.
  2902. // So far no browsers return more than one range (portion)
  2903. /**
  2904. * Get a TimeRange object with the times of the video that have been downloaded
  2905. *
  2906. * If you just want the percent of the video that's been downloaded,
  2907. * use bufferedPercent.
  2908. *
  2909. * // Number of different ranges of time have been buffered. Usually 1.
  2910. * numberOfRanges = bufferedTimeRange.length,
  2911. *
  2912. * // Time in seconds when the first range starts. Usually 0.
  2913. * firstRangeStart = bufferedTimeRange.start(0),
  2914. *
  2915. * // Time in seconds when the first range ends
  2916. * firstRangeEnd = bufferedTimeRange.end(0),
  2917. *
  2918. * // Length in seconds of the first time range
  2919. * firstRangeLength = firstRangeEnd - firstRangeStart;
  2920. *
  2921. * @return {Object} A mock TimeRange object (following HTML spec)
  2922. */
  2923. vjs.Player.prototype.buffered = function () {
  2924. var buffered = this.techGet('buffered'),
  2925. start = 0,
  2926. buflast = buffered.length - 1,
  2927. // Default end to 0 and store in values
  2928. end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
  2929. if (buffered && buflast >= 0 && buffered.end(buflast) !== end) {
  2930. end = buffered.end(buflast);
  2931. // Storing values allows them be overridden by setBufferedFromProgress
  2932. this.cache_.bufferEnd = end;
  2933. }
  2934. return vjs.createTimeRange(start, end);
  2935. };
  2936. /**
  2937. * Get the percent (as a decimal) of the video that's been downloaded
  2938. *
  2939. * var howMuchIsDownloaded = myPlayer.bufferedPercent();
  2940. *
  2941. * 0 means none, 1 means all.
  2942. * (This method isn't in the HTML5 spec, but it's very convenient)
  2943. *
  2944. * @return {Number} A decimal between 0 and 1 representing the percent
  2945. */
  2946. vjs.Player.prototype.bufferedPercent = function () {
  2947. return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
  2948. };
  2949. /**
  2950. * Get or set the current volume of the media
  2951. *
  2952. * // get
  2953. * var howLoudIsIt = myPlayer.volume();
  2954. *
  2955. * // set
  2956. * myPlayer.volume(0.5); // Set volume to half
  2957. *
  2958. * 0 is off (muted), 1.0 is all the way up, 0.5 is half way.
  2959. *
  2960. * @param {Number} percentAsDecimal The new volume as a decimal percent
  2961. * @return {Number} The current volume, when getting
  2962. * @return {vjs.Player} self, when setting
  2963. */
  2964. vjs.Player.prototype.volume = function (percentAsDecimal) {
  2965. var vol;
  2966. if (percentAsDecimal !== undefined) {
  2967. vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
  2968. this.cache_.volume = vol;
  2969. this.techCall('setVolume', vol);
  2970. vjs.setLocalStorage('volume', vol);
  2971. return this;
  2972. }
  2973. // Default to 1 when returning current volume.
  2974. vol = parseFloat(this.techGet('volume'));
  2975. return (isNaN(vol)) ? 1 : vol;
  2976. };
  2977. /**
  2978. * Get the current muted state, or turn mute on or off
  2979. *
  2980. * // get
  2981. * var isVolumeMuted = myPlayer.muted();
  2982. *
  2983. * // set
  2984. * myPlayer.muted(true); // mute the volume
  2985. *
  2986. * @param {Boolean=} muted True to mute, false to unmute
  2987. * @return {Boolean} True if mute is on, false if not, when getting
  2988. * @return {vjs.Player} self, when setting mute
  2989. */
  2990. vjs.Player.prototype.muted = function (muted) {
  2991. if (muted !== undefined) {
  2992. this.techCall('setMuted', muted);
  2993. return this;
  2994. }
  2995. return this.techGet('muted') || false; // Default to false
  2996. };
  2997. // Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)
  2998. vjs.Player.prototype.supportsFullScreen = function () { return this.techGet('supportsFullScreen') || false; };
  2999. /**
  3000. * Increase the size of the video to full screen
  3001. *
  3002. * myPlayer.requestFullScreen();
  3003. *
  3004. * In some browsers, full screen is not supported natively, so it enters
  3005. * "full window mode", where the video fills the browser window.
  3006. * In browsers and devices that support native full screen, sometimes the
  3007. * browser's default controls will be shown, and not the Video.js custom skin.
  3008. * This includes most mobile devices (iOS, Android) and older versions of
  3009. * Safari.
  3010. *
  3011. * @return {vjs.Player} self
  3012. */
  3013. vjs.Player.prototype.requestFullScreen = function () {
  3014. var requestFullScreen = vjs.support.requestFullScreen;
  3015. this.isFullScreen = true;
  3016. if (requestFullScreen) {
  3017. // the browser supports going fullscreen at the element level so we can
  3018. // take the controls fullscreen as well as the video
  3019. // Trigger fullscreenchange event after change
  3020. // We have to specifically add this each time, and remove
  3021. // when cancelling fullscreen. Otherwise if there's multiple
  3022. // players on a page, they would all be reacting to the same fullscreen
  3023. // events
  3024. vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function (e) {
  3025. this.isFullScreen = document[requestFullScreen.isFullScreen];
  3026. // If cancelling fullscreen, remove event listener.
  3027. if (this.isFullScreen === false) {
  3028. vjs.off(document, requestFullScreen.eventName, arguments.callee);
  3029. }
  3030. this.trigger('fullscreenchange');
  3031. }));
  3032. this.el_[requestFullScreen.requestFn]();
  3033. } else if (this.tech.supportsFullScreen()) {
  3034. // we can't take the video.js controls fullscreen but we can go fullscreen
  3035. // with native controls
  3036. this.techCall('enterFullScreen');
  3037. } else {
  3038. // fullscreen isn't supported so we'll just stretch the video element to
  3039. // fill the viewport
  3040. this.enterFullWindow();
  3041. this.trigger('fullscreenchange');
  3042. }
  3043. return this;
  3044. };
  3045. /**
  3046. * Return the video to its normal size after having been in full screen mode
  3047. *
  3048. * myPlayer.cancelFullScreen();
  3049. *
  3050. * @return {vjs.Player} self
  3051. */
  3052. vjs.Player.prototype.cancelFullScreen = function () {
  3053. var requestFullScreen = vjs.support.requestFullScreen;
  3054. this.isFullScreen = false;
  3055. // Check for browser element fullscreen support
  3056. if (requestFullScreen) {
  3057. document[requestFullScreen.cancelFn]();
  3058. } else if (this.tech.supportsFullScreen()) {
  3059. this.techCall('exitFullScreen');
  3060. } else {
  3061. this.exitFullWindow();
  3062. this.trigger('fullscreenchange');
  3063. }
  3064. return this;
  3065. };
  3066. // When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
  3067. vjs.Player.prototype.enterFullWindow = function () {
  3068. this.isFullWindow = true;
  3069. // Storing original doc overflow value to return to when fullscreen is off
  3070. this.docOrigOverflow = document.documentElement.style.overflow;
  3071. // Add listener for esc key to exit fullscreen
  3072. vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));
  3073. // Hide any scroll bars
  3074. document.documentElement.style.overflow = 'hidden';
  3075. // Apply fullscreen styles
  3076. vjs.addClass(document.body, 'vjs-full-window');
  3077. this.trigger('enterFullWindow');
  3078. };
  3079. vjs.Player.prototype.fullWindowOnEscKey = function (event) {
  3080. if (event.keyCode === 27) {
  3081. if (this.isFullScreen === true) {
  3082. this.cancelFullScreen();
  3083. } else {
  3084. this.exitFullWindow();
  3085. }
  3086. }
  3087. };
  3088. vjs.Player.prototype.exitFullWindow = function () {
  3089. this.isFullWindow = false;
  3090. vjs.off(document, 'keydown', this.fullWindowOnEscKey);
  3091. // Unhide scroll bars.
  3092. document.documentElement.style.overflow = this.docOrigOverflow;
  3093. // Remove fullscreen styles
  3094. vjs.removeClass(document.body, 'vjs-full-window');
  3095. // Resize the box, controller, and poster to original sizes
  3096. // this.positionAll();
  3097. this.trigger('exitFullWindow');
  3098. };
  3099. vjs.Player.prototype.selectSource = function (sources) {
  3100. // Loop through each playback technology in the options order
  3101. for (var i = 0, j = this.options_['techOrder']; i < j.length; i++) {
  3102. var techName = vjs.capitalize(j[i]),
  3103. tech = window['videojs'][techName];
  3104. // Check if the browser supports this technology
  3105. if (tech.isSupported()) {
  3106. // Loop through each source object
  3107. for (var a = 0, b = sources; a < b.length; a++) {
  3108. var source = b[a];
  3109. // Check if source can be played with this technology
  3110. if (tech['canPlaySource'](source)) {
  3111. return { source: source, tech: techName };
  3112. }
  3113. }
  3114. }
  3115. }
  3116. return false;
  3117. };
  3118. /**
  3119. * The source function updates the video source
  3120. *
  3121. * There are three types of variables you can pass as the argument.
  3122. *
  3123. * **URL String**: A URL to the the video file. Use this method if you are sure
  3124. * the current playback technology (HTML5/Flash) can support the source you
  3125. * provide. Currently only MP4 files can be used in both HTML5 and Flash.
  3126. *
  3127. * myPlayer.src("http://www.example.com/path/to/video.mp4");
  3128. *
  3129. * **Source Object (or element):** A javascript object containing information
  3130. * about the source file. Use this method if you want the player to determine if
  3131. * it can support the file using the type information.
  3132. *
  3133. * myPlayer.src({ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" });
  3134. *
  3135. * **Array of Source Objects:** To provide multiple versions of the source so
  3136. * that it can be played using HTML5 across browsers you can use an array of
  3137. * source objects. Video.js will detect which version is supported and load that
  3138. * file.
  3139. *
  3140. * myPlayer.src([
  3141. * { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" },
  3142. * { type: "video/webm", src: "http://www.example.com/path/to/video.webm" },
  3143. * { type: "video/ogg", src: "http://www.example.com/path/to/video.ogv" }
  3144. * ]);
  3145. *
  3146. * @param {String|Object|Array=} source The source URL, object, or array of sources
  3147. * @return {vjs.Player} self
  3148. */
  3149. vjs.Player.prototype.src = function (source) {
  3150. // Case: Array of source objects to choose from and pick the best to play
  3151. if (source instanceof Array) {
  3152. var sourceTech = this.selectSource(source),
  3153. techName;
  3154. if (sourceTech) {
  3155. source = sourceTech.source;
  3156. techName = sourceTech.tech;
  3157. // If this technology is already loaded, set source
  3158. if (techName == this.techName) {
  3159. this.src(source); // Passing the source object
  3160. // Otherwise load this technology with chosen source
  3161. } else {
  3162. this.loadTech(techName, source);
  3163. }
  3164. } else {
  3165. this.el_.appendChild(vjs.createEl('p', {
  3166. innerHTML: this.options()['notSupportedMessage']
  3167. }));
  3168. }
  3169. // Case: Source object { src: '', type: '' ... }
  3170. } else if (source instanceof Object) {
  3171. if (window['videojs'][this.techName]['canPlaySource'](source)) {
  3172. this.src(source.src);
  3173. } else {
  3174. // Send through tech loop to check for a compatible technology.
  3175. this.src([source]);
  3176. }
  3177. // Case: URL String (http://myvideo...)
  3178. } else {
  3179. // Cache for getting last set source
  3180. this.cache_.src = source;
  3181. if (!this.isReady_) {
  3182. this.ready(function () {
  3183. this.src(source);
  3184. });
  3185. } else {
  3186. this.techCall('src', source);
  3187. if (this.options_['preload'] == 'auto') {
  3188. this.load();
  3189. }
  3190. if (this.options_['autoplay']) {
  3191. this.play();
  3192. }
  3193. }
  3194. }
  3195. return this;
  3196. };
  3197. // Begin loading the src data
  3198. // http://dev.w3.org/html5/spec/video.html#dom-media-load
  3199. vjs.Player.prototype.load = function () {
  3200. this.techCall('load');
  3201. return this;
  3202. };
  3203. // http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
  3204. vjs.Player.prototype.currentSrc = function () {
  3205. return this.techGet('currentSrc') || this.cache_.src || '';
  3206. };
  3207. // Attributes/Options
  3208. vjs.Player.prototype.preload = function (value) {
  3209. if (value !== undefined) {
  3210. this.techCall('setPreload', value);
  3211. this.options_['preload'] = value;
  3212. return this;
  3213. }
  3214. return this.techGet('preload');
  3215. };
  3216. vjs.Player.prototype.autoplay = function (value) {
  3217. if (value !== undefined) {
  3218. this.techCall('setAutoplay', value);
  3219. this.options_['autoplay'] = value;
  3220. return this;
  3221. }
  3222. return this.techGet('autoplay', value);
  3223. };
  3224. vjs.Player.prototype.loop = function (value) {
  3225. if (value !== undefined) {
  3226. this.techCall('setLoop', value);
  3227. this.options_['loop'] = value;
  3228. return this;
  3229. }
  3230. return this.techGet('loop');
  3231. };
  3232. /**
  3233. * the url of the poster image source
  3234. * @type {String}
  3235. * @private
  3236. */
  3237. vjs.Player.prototype.poster_;
  3238. /**
  3239. * get or set the poster image source url
  3240. *
  3241. * ##### EXAMPLE:
  3242. *
  3243. * // getting
  3244. * var currentPoster = myPlayer.poster();
  3245. *
  3246. * // setting
  3247. * myPlayer.poster('http://example.com/myImage.jpg');
  3248. *
  3249. * @param {String=} [src] Poster image source URL
  3250. * @return {String} poster URL when getting
  3251. * @return {vjs.Player} self when setting
  3252. */
  3253. vjs.Player.prototype.poster = function (src) {
  3254. if (src !== undefined) {
  3255. this.poster_ = src;
  3256. return this;
  3257. }
  3258. return this.poster_;
  3259. };
  3260. /**
  3261. * Whether or not the controls are showing
  3262. * @type {Boolean}
  3263. * @private
  3264. */
  3265. vjs.Player.prototype.controls_;
  3266. /**
  3267. * Get or set whether or not the controls are showing.
  3268. * @param {Boolean} controls Set controls to showing or not
  3269. * @return {Boolean} Controls are showing
  3270. */
  3271. vjs.Player.prototype.controls = function (bool) {
  3272. if (bool !== undefined) {
  3273. bool = !!bool; // force boolean
  3274. // Don't trigger a change event unless it actually changed
  3275. if (this.controls_ !== bool) {
  3276. this.controls_ = bool;
  3277. if (bool) {
  3278. this.removeClass('vjs-controls-disabled');
  3279. this.addClass('vjs-controls-enabled');
  3280. this.trigger('controlsenabled');
  3281. } else {
  3282. this.removeClass('vjs-controls-enabled');
  3283. this.addClass('vjs-controls-disabled');
  3284. this.trigger('controlsdisabled');
  3285. }
  3286. }
  3287. return this;
  3288. }
  3289. return this.controls_;
  3290. };
  3291. vjs.Player.prototype.usingNativeControls_;
  3292. /**
  3293. * Toggle native controls on/off. Native controls are the controls built into
  3294. * devices (e.g. default iPhone controls), Flash, or other techs
  3295. * (e.g. Vimeo Controls)
  3296. *
  3297. * **This should only be set by the current tech, because only the tech knows
  3298. * if it can support native controls**
  3299. *
  3300. * @param {Boolean} bool True signals that native controls are on
  3301. * @return {vjs.Player} Returns the player
  3302. * @private
  3303. */
  3304. vjs.Player.prototype.usingNativeControls = function (bool) {
  3305. if (bool !== undefined) {
  3306. bool = !!bool; // force boolean
  3307. // Don't trigger a change event unless it actually changed
  3308. if (this.usingNativeControls_ !== bool) {
  3309. this.usingNativeControls_ = bool;
  3310. if (bool) {
  3311. this.addClass('vjs-using-native-controls');
  3312. /**
  3313. * player is using the native device controls
  3314. *
  3315. * @event usingnativecontrols
  3316. * @memberof vjs.Player
  3317. * @instance
  3318. * @private
  3319. */
  3320. this.trigger('usingnativecontrols');
  3321. } else {
  3322. this.removeClass('vjs-using-native-controls');
  3323. /**
  3324. * player is using the custom HTML controls
  3325. *
  3326. * @event usingcustomcontrols
  3327. * @memberof vjs.Player
  3328. * @instance
  3329. * @private
  3330. */
  3331. this.trigger('usingcustomcontrols');
  3332. }
  3333. }
  3334. return this;
  3335. }
  3336. return this.usingNativeControls_;
  3337. };
  3338. vjs.Player.prototype.error = function () { return this.techGet('error'); };
  3339. vjs.Player.prototype.ended = function () { return this.techGet('ended'); };
  3340. vjs.Player.prototype.seeking = function () { return this.techGet('seeking'); };
  3341. // When the player is first initialized, trigger activity so components
  3342. // like the control bar show themselves if needed
  3343. vjs.Player.prototype.userActivity_ = true;
  3344. vjs.Player.prototype.reportUserActivity = function (event) {
  3345. this.userActivity_ = true;
  3346. };
  3347. vjs.Player.prototype.userActive_ = true;
  3348. vjs.Player.prototype.userActive = function (bool) {
  3349. if (bool !== undefined) {
  3350. bool = !!bool;
  3351. if (bool !== this.userActive_) {
  3352. this.userActive_ = bool;
  3353. if (bool) {
  3354. // If the user was inactive and is now active we want to reset the
  3355. // inactivity timer
  3356. this.userActivity_ = true;
  3357. this.removeClass('vjs-user-inactive');
  3358. this.addClass('vjs-user-active');
  3359. this.trigger('useractive');
  3360. } else {
  3361. // We're switching the state to inactive manually, so erase any other
  3362. // activity
  3363. this.userActivity_ = false;
  3364. // Chrome/Safari/IE have bugs where when you change the cursor it can
  3365. // trigger a mousemove event. This causes an issue when you're hiding
  3366. // the cursor when the user is inactive, and a mousemove signals user
  3367. // activity. Making it impossible to go into inactive mode. Specifically
  3368. // this happens in fullscreen when we really need to hide the cursor.
  3369. //
  3370. // When this gets resolved in ALL browsers it can be removed
  3371. // https://code.google.com/p/chromium/issues/detail?id=103041
  3372. this.tech.one('mousemove', function (e) {
  3373. e.stopPropagation();
  3374. e.preventDefault();
  3375. });
  3376. this.removeClass('vjs-user-active');
  3377. this.addClass('vjs-user-inactive');
  3378. this.trigger('userinactive');
  3379. }
  3380. }
  3381. return this;
  3382. }
  3383. return this.userActive_;
  3384. };
  3385. vjs.Player.prototype.listenForUserActivity = function () {
  3386. var onMouseActivity, onMouseDown, mouseInProgress, onMouseUp,
  3387. activityCheck, inactivityTimeout;
  3388. onMouseActivity = this.reportUserActivity;
  3389. onMouseDown = function () {
  3390. onMouseActivity();
  3391. // For as long as the they are touching the device or have their mouse down,
  3392. // we consider them active even if they're not moving their finger or mouse.
  3393. // So we want to continue to update that they are active
  3394. clearInterval(mouseInProgress);
  3395. // Setting userActivity=true now and setting the interval to the same time
  3396. // as the activityCheck interval (250) should ensure we never miss the
  3397. // next activityCheck
  3398. mouseInProgress = setInterval(vjs.bind(this, onMouseActivity), 250);
  3399. };
  3400. onMouseUp = function (event) {
  3401. onMouseActivity();
  3402. // Stop the interval that maintains activity if the mouse/touch is down
  3403. clearInterval(mouseInProgress);
  3404. };
  3405. // Any mouse movement will be considered user activity
  3406. this.on('mousedown', onMouseDown);
  3407. this.on('mousemove', onMouseActivity);
  3408. this.on('mouseup', onMouseUp);
  3409. // Listen for keyboard navigation
  3410. // Shouldn't need to use inProgress interval because of key repeat
  3411. this.on('keydown', onMouseActivity);
  3412. this.on('keyup', onMouseActivity);
  3413. // Consider any touch events that bubble up to be activity
  3414. // Certain touches on the tech will be blocked from bubbling because they
  3415. // toggle controls
  3416. this.on('touchstart', onMouseDown);
  3417. this.on('touchmove', onMouseActivity);
  3418. this.on('touchend', onMouseUp);
  3419. this.on('touchcancel', onMouseUp);
  3420. // Run an interval every 250 milliseconds instead of stuffing everything into
  3421. // the mousemove/touchmove function itself, to prevent performance degradation.
  3422. // `this.reportUserActivity` simply sets this.userActivity_ to true, which
  3423. // then gets picked up by this loop
  3424. // http://ejohn.org/blog/learning-from-twitter/
  3425. activityCheck = setInterval(vjs.bind(this, function () {
  3426. // Check to see if mouse/touch activity has happened
  3427. if (this.userActivity_) {
  3428. // Reset the activity tracker
  3429. this.userActivity_ = false;
  3430. // If the user state was inactive, set the state to active
  3431. this.userActive(true);
  3432. // Clear any existing inactivity timeout to start the timer over
  3433. clearTimeout(inactivityTimeout);
  3434. // In X seconds, if no more activity has occurred the user will be
  3435. // considered inactive
  3436. inactivityTimeout = setTimeout(vjs.bind(this, function () {
  3437. // Protect against the case where the inactivityTimeout can trigger just
  3438. // before the next user activity is picked up by the activityCheck loop
  3439. // causing a flicker
  3440. if (!this.userActivity_) {
  3441. this.userActive(false);
  3442. }
  3443. }), 2000);
  3444. }
  3445. }), 250);
  3446. // Clean up the intervals when we kill the player
  3447. this.on('dispose', function () {
  3448. clearInterval(activityCheck);
  3449. clearTimeout(inactivityTimeout);
  3450. });
  3451. };
  3452. // Methods to add support for
  3453. // networkState: function(){ return this.techCall('networkState'); },
  3454. // readyState: function(){ return this.techCall('readyState'); },
  3455. // seeking: function(){ return this.techCall('seeking'); },
  3456. // initialTime: function(){ return this.techCall('initialTime'); },
  3457. // startOffsetTime: function(){ return this.techCall('startOffsetTime'); },
  3458. // played: function(){ return this.techCall('played'); },
  3459. // seekable: function(){ return this.techCall('seekable'); },
  3460. // videoTracks: function(){ return this.techCall('videoTracks'); },
  3461. // audioTracks: function(){ return this.techCall('audioTracks'); },
  3462. // videoWidth: function(){ return this.techCall('videoWidth'); },
  3463. // videoHeight: function(){ return this.techCall('videoHeight'); },
  3464. // defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
  3465. // playbackRate: function(){ return this.techCall('playbackRate'); },
  3466. // mediaGroup: function(){ return this.techCall('mediaGroup'); },
  3467. // controller: function(){ return this.techCall('controller'); },
  3468. // defaultMuted: function(){ return this.techCall('defaultMuted'); }
  3469. // TODO
  3470. // currentSrcList: the array of sources including other formats and bitrates
  3471. // playList: array of source lists in order of playback
  3472. // RequestFullscreen API
  3473. (function () {
  3474. var prefix, requestFS, div;
  3475. div = document.createElement('div');
  3476. requestFS = {};
  3477. // Current W3C Spec
  3478. // http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api
  3479. // Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event
  3480. // New: https://dvcs.w3.org/hg/fullscreen/raw-file/529a67b8d9f3/Overview.html
  3481. if (div.cancelFullscreen !== undefined) {
  3482. requestFS.requestFn = 'requestFullscreen';
  3483. requestFS.cancelFn = 'exitFullscreen';
  3484. requestFS.eventName = 'fullscreenchange';
  3485. requestFS.isFullScreen = 'fullScreen';
  3486. // Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementations
  3487. // that use prefixes and vary slightly from the new W3C spec. Specifically,
  3488. // using 'exit' instead of 'cancel', and lowercasing the 'S' in Fullscreen.
  3489. // Other browsers don't have any hints of which version they might follow yet,
  3490. // so not going to try to predict by looping through all prefixes.
  3491. } else {
  3492. if (document.mozCancelFullScreen) {
  3493. prefix = 'moz';
  3494. requestFS.isFullScreen = prefix + 'FullScreen';
  3495. } else {
  3496. prefix = 'webkit';
  3497. requestFS.isFullScreen = prefix + 'IsFullScreen';
  3498. }
  3499. if (div[prefix + 'RequestFullScreen']) {
  3500. requestFS.requestFn = prefix + 'RequestFullScreen';
  3501. requestFS.cancelFn = prefix + 'CancelFullScreen';
  3502. }
  3503. requestFS.eventName = prefix + 'fullscreenchange';
  3504. }
  3505. if (document[requestFS.cancelFn]) {
  3506. vjs.support.requestFullScreen = requestFS;
  3507. }
  3508. })();
  3509. /**
  3510. * Container of main controls
  3511. * @param {vjs.Player|Object} player
  3512. * @param {Object=} options
  3513. * @class
  3514. * @constructor
  3515. * @extends vjs.Component
  3516. */
  3517. vjs.ControlBar = vjs.Component.extend();
  3518. vjs.ControlBar.prototype.options_ = {
  3519. loadEvent: 'play',
  3520. children: {
  3521. 'playToggle': {},
  3522. 'currentTimeDisplay': {},
  3523. 'timeDivider': {},
  3524. 'durationDisplay': {},
  3525. 'remainingTimeDisplay': {},
  3526. 'progressControl': {},
  3527. 'fullscreenToggle': {},
  3528. 'volumeControl': {},
  3529. 'muteToggle': {}
  3530. // 'volumeMenuButton': {}
  3531. }
  3532. };
  3533. vjs.ControlBar.prototype.createEl = function () {
  3534. return vjs.createEl('div', {
  3535. className: 'vjs-control-bar'
  3536. });
  3537. };
  3538. /**
  3539. * Button to toggle between play and pause
  3540. * @param {vjs.Player|Object} player
  3541. * @param {Object=} options
  3542. * @class
  3543. * @constructor
  3544. */
  3545. vjs.PlayToggle = vjs.Button.extend({
  3546. /** @constructor */
  3547. init: function (player, options) {
  3548. vjs.Button.call(this, player, options);
  3549. player.on('play', vjs.bind(this, this.onPlay));
  3550. player.on('pause', vjs.bind(this, this.onPause));
  3551. }
  3552. });
  3553. vjs.PlayToggle.prototype.buttonText = 'Play';
  3554. vjs.PlayToggle.prototype.buildCSSClass = function () {
  3555. return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this);
  3556. };
  3557. // OnClick - Toggle between play and pause
  3558. vjs.PlayToggle.prototype.onClick = function () {
  3559. if (this.player_.paused()) {
  3560. this.player_.play();
  3561. } else {
  3562. this.player_.pause();
  3563. }
  3564. };
  3565. // OnPlay - Add the vjs-playing class to the element so it can change appearance
  3566. vjs.PlayToggle.prototype.onPlay = function () {
  3567. vjs.removeClass(this.el_, 'vjs-paused');
  3568. vjs.addClass(this.el_, 'vjs-playing');
  3569. this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to "Pause"
  3570. };
  3571. // OnPause - Add the vjs-paused class to the element so it can change appearance
  3572. vjs.PlayToggle.prototype.onPause = function () {
  3573. vjs.removeClass(this.el_, 'vjs-playing');
  3574. vjs.addClass(this.el_, 'vjs-paused');
  3575. this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to "Play"
  3576. };
  3577. /**
  3578. * Displays the current time
  3579. * @param {vjs.Player|Object} player
  3580. * @param {Object=} options
  3581. * @constructor
  3582. */
  3583. vjs.CurrentTimeDisplay = vjs.Component.extend({
  3584. /** @constructor */
  3585. init: function (player, options) {
  3586. vjs.Component.call(this, player, options);
  3587. player.on('timeupdate', vjs.bind(this, this.updateContent));
  3588. }
  3589. });
  3590. vjs.CurrentTimeDisplay.prototype.createEl = function () {
  3591. var el = vjs.Component.prototype.createEl.call(this, 'div', {
  3592. className: 'vjs-current-time vjs-time-controls vjs-control'
  3593. });
  3594. this.content = vjs.createEl('div', {
  3595. className: 'vjs-current-time-display',
  3596. innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00', // label the current time for screen reader users
  3597. 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
  3598. });
  3599. el.appendChild(vjs.createEl('div').appendChild(this.content));
  3600. return el;
  3601. };
  3602. vjs.CurrentTimeDisplay.prototype.updateContent = function () {
  3603. // Allows for smooth scrubbing, when player can't keep up.
  3604. var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
  3605. this.content.innerHTML = '<span class="vjs-control-text">Current Time </span>' + vjs.formatTime(time, this.player_.duration());
  3606. };
  3607. /**
  3608. * Displays the duration
  3609. * @param {vjs.Player|Object} player
  3610. * @param {Object=} options
  3611. * @constructor
  3612. */
  3613. vjs.DurationDisplay = vjs.Component.extend({
  3614. /** @constructor */
  3615. init: function (player, options) {
  3616. vjs.Component.call(this, player, options);
  3617. player.on('timeupdate', vjs.bind(this, this.updateContent)); // this might need to be changes to 'durationchange' instead of 'timeupdate' eventually, however the durationchange event fires before this.player_.duration() is set, so the value cannot be written out using this method. Once the order of durationchange and this.player_.duration() being set is figured out, this can be updated.
  3618. }
  3619. });
  3620. vjs.DurationDisplay.prototype.createEl = function () {
  3621. var el = vjs.Component.prototype.createEl.call(this, 'div', {
  3622. className: 'vjs-duration vjs-time-controls vjs-control'
  3623. });
  3624. this.content = vjs.createEl('div', {
  3625. className: 'vjs-duration-display',
  3626. innerHTML: '<span class="vjs-control-text">Duration Time </span>' + '0:00', // label the duration time for screen reader users
  3627. 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
  3628. });
  3629. el.appendChild(vjs.createEl('div').appendChild(this.content));
  3630. return el;
  3631. };
  3632. vjs.DurationDisplay.prototype.updateContent = function () {
  3633. var duration = this.player_.duration();
  3634. if (duration) {
  3635. this.content.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(duration); // label the duration time for screen reader users
  3636. }
  3637. };
  3638. /**
  3639. * The separator between the current time and duration
  3640. *
  3641. * Can be hidden if it's not needed in the design.
  3642. *
  3643. * @param {vjs.Player|Object} player
  3644. * @param {Object=} options
  3645. * @constructor
  3646. */
  3647. vjs.TimeDivider = vjs.Component.extend({
  3648. /** @constructor */
  3649. init: function (player, options) {
  3650. vjs.Component.call(this, player, options);
  3651. }
  3652. });
  3653. vjs.TimeDivider.prototype.createEl = function () {
  3654. return vjs.Component.prototype.createEl.call(this, 'div', {
  3655. className: 'vjs-time-divider',
  3656. innerHTML: '<div><span>/</span></div>'
  3657. });
  3658. };
  3659. /**
  3660. * Displays the time left in the video
  3661. * @param {vjs.Player|Object} player
  3662. * @param {Object=} options
  3663. * @constructor
  3664. */
  3665. vjs.RemainingTimeDisplay = vjs.Component.extend({
  3666. /** @constructor */
  3667. init: function (player, options) {
  3668. vjs.Component.call(this, player, options);
  3669. player.on('timeupdate', vjs.bind(this, this.updateContent));
  3670. }
  3671. });
  3672. vjs.RemainingTimeDisplay.prototype.createEl = function () {
  3673. var el = vjs.Component.prototype.createEl.call(this, 'div', {
  3674. className: 'vjs-remaining-time vjs-time-controls vjs-control'
  3675. });
  3676. this.content = vjs.createEl('div', {
  3677. className: 'vjs-remaining-time-display',
  3678. innerHTML: '<span class="vjs-control-text">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users
  3679. 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
  3680. });
  3681. el.appendChild(vjs.createEl('div').appendChild(this.content));
  3682. return el;
  3683. };
  3684. vjs.RemainingTimeDisplay.prototype.updateContent = function () {
  3685. if (this.player_.duration()) {
  3686. this.content.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-' + vjs.formatTime(this.player_.remainingTime());
  3687. }
  3688. // Allows for smooth scrubbing, when player can't keep up.
  3689. // var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
  3690. // this.content.innerHTML = vjs.formatTime(time, this.player_.duration());
  3691. };
  3692. /**
  3693. * Toggle fullscreen video
  3694. * @param {vjs.Player|Object} player
  3695. * @param {Object=} options
  3696. * @class
  3697. * @extends vjs.Button
  3698. */
  3699. vjs.FullscreenToggle = vjs.Button.extend({
  3700. /**
  3701. * @constructor
  3702. * @memberof vjs.FullscreenToggle
  3703. * @instance
  3704. */
  3705. init: function (player, options) {
  3706. vjs.Button.call(this, player, options);
  3707. }
  3708. });
  3709. vjs.FullscreenToggle.prototype.buttonText = 'Fullscreen';
  3710. vjs.FullscreenToggle.prototype.buildCSSClass = function () {
  3711. return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this);
  3712. };
  3713. vjs.FullscreenToggle.prototype.onClick = function () {
  3714. if (!this.player_.isFullScreen) {
  3715. this.player_.requestFullScreen();
  3716. this.el_.children[0].children[0].innerHTML = 'Non-Fullscreen'; // change the button text to "Non-Fullscreen"
  3717. } else {
  3718. this.player_.cancelFullScreen();
  3719. this.el_.children[0].children[0].innerHTML = 'Fullscreen'; // change the button to "Fullscreen"
  3720. }
  3721. };
  3722. /**
  3723. * The Progress Control component contains the seek bar, load progress,
  3724. * and play progress
  3725. *
  3726. * @param {vjs.Player|Object} player
  3727. * @param {Object=} options
  3728. * @constructor
  3729. */
  3730. vjs.ProgressControl = vjs.Component.extend({
  3731. /** @constructor */
  3732. init: function (player, options) {
  3733. vjs.Component.call(this, player, options);
  3734. }
  3735. });
  3736. vjs.ProgressControl.prototype.options_ = {
  3737. children: {
  3738. 'seekBar': {}
  3739. }
  3740. };
  3741. vjs.ProgressControl.prototype.createEl = function () {
  3742. return vjs.Component.prototype.createEl.call(this, 'div', {
  3743. className: 'vjs-progress-control vjs-control'
  3744. });
  3745. };
  3746. /**
  3747. * Seek Bar and holder for the progress bars
  3748. *
  3749. * @param {vjs.Player|Object} player
  3750. * @param {Object=} options
  3751. * @constructor
  3752. */
  3753. vjs.SeekBar = vjs.Slider.extend({
  3754. /** @constructor */
  3755. init: function (player, options) {
  3756. vjs.Slider.call(this, player, options);
  3757. player.on('timeupdate', vjs.bind(this, this.updateARIAAttributes));
  3758. player.ready(vjs.bind(this, this.updateARIAAttributes));
  3759. }
  3760. });
  3761. vjs.SeekBar.prototype.options_ = {
  3762. children: {
  3763. 'loadProgressBar': {},
  3764. 'playProgressBar': {},
  3765. 'seekHandle': {}
  3766. },
  3767. 'barName': 'playProgressBar',
  3768. 'handleName': 'seekHandle'
  3769. };
  3770. vjs.SeekBar.prototype.playerEvent = 'timeupdate';
  3771. vjs.SeekBar.prototype.createEl = function () {
  3772. return vjs.Slider.prototype.createEl.call(this, 'div', {
  3773. className: 'vjs-progress-holder',
  3774. 'aria-label': 'video progress bar'
  3775. });
  3776. };
  3777. vjs.SeekBar.prototype.updateARIAAttributes = function () {
  3778. // Allows for smooth scrubbing, when player can't keep up.
  3779. var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
  3780. this.el_.setAttribute('aria-valuenow', vjs.round(this.getPercent() * 100, 2)); // machine readable value of progress bar (percentage complete)
  3781. this.el_.setAttribute('aria-valuetext', vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
  3782. };
  3783. vjs.SeekBar.prototype.getPercent = function () {
  3784. var currentTime;
  3785. // Flash RTMP provider will not report the correct time
  3786. // immediately after a seek. This isn't noticeable if you're
  3787. // seeking while the video is playing, but it is if you seek
  3788. // while the video is paused.
  3789. if (this.player_.techName === 'Flash' && this.player_.seeking()) {
  3790. var cache = this.player_.getCache();
  3791. if (cache.lastSetCurrentTime) {
  3792. currentTime = cache.lastSetCurrentTime;
  3793. }
  3794. else {
  3795. currentTime = this.player_.currentTime();
  3796. }
  3797. }
  3798. else {
  3799. currentTime = this.player_.currentTime();
  3800. }
  3801. return currentTime / this.player_.duration();
  3802. };
  3803. vjs.SeekBar.prototype.onMouseDown = function (event) {
  3804. vjs.Slider.prototype.onMouseDown.call(this, event);
  3805. this.player_.scrubbing = true;
  3806. this.videoWasPlaying = !this.player_.paused();
  3807. this.player_.pause();
  3808. };
  3809. vjs.SeekBar.prototype.onMouseMove = function (event) {
  3810. var newTime = this.calculateDistance(event) * this.player_.duration();
  3811. // Don't let video end while scrubbing.
  3812. if (newTime == this.player_.duration()) { newTime = newTime - 0.1; }
  3813. // Set new time (tell player to seek to new time)
  3814. this.player_.currentTime(newTime);
  3815. };
  3816. vjs.SeekBar.prototype.onMouseUp = function (event) {
  3817. debugger
  3818. vjs.Slider.prototype.onMouseUp.call(this, event);
  3819. this.player_.scrubbing = false;
  3820. if (this.videoWasPlaying) {
  3821. debugger
  3822. this.player_.play();
  3823. }
  3824. };
  3825. vjs.SeekBar.prototype.stepForward = function () {
  3826. this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
  3827. };
  3828. vjs.SeekBar.prototype.stepBack = function () {
  3829. this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
  3830. };
  3831. /**
  3832. * Shows load progress
  3833. *
  3834. * @param {vjs.Player|Object} player
  3835. * @param {Object=} options
  3836. * @constructor
  3837. */
  3838. vjs.LoadProgressBar = vjs.Component.extend({
  3839. /** @constructor */
  3840. init: function (player, options) {
  3841. vjs.Component.call(this, player, options);
  3842. player.on('progress', vjs.bind(this, this.update));
  3843. }
  3844. });
  3845. vjs.LoadProgressBar.prototype.createEl = function () {
  3846. return vjs.Component.prototype.createEl.call(this, 'div', {
  3847. className: 'vjs-load-progress',
  3848. innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>'
  3849. });
  3850. };
  3851. vjs.LoadProgressBar.prototype.update = function () {
  3852. if (this.el_.style) { this.el_.style.width = vjs.round(this.player_.bufferedPercent() * 100, 2) + '%'; }
  3853. };
  3854. /**
  3855. * Shows play progress
  3856. *
  3857. * @param {vjs.Player|Object} player
  3858. * @param {Object=} options
  3859. * @constructor
  3860. */
  3861. vjs.PlayProgressBar = vjs.Component.extend({
  3862. /** @constructor */
  3863. init: function (player, options) {
  3864. vjs.Component.call(this, player, options);
  3865. }
  3866. });
  3867. vjs.PlayProgressBar.prototype.createEl = function () {
  3868. return vjs.Component.prototype.createEl.call(this, 'div', {
  3869. className: 'vjs-play-progress',
  3870. innerHTML: '<span class="vjs-control-text">Progress: 0%</span>'
  3871. });
  3872. };
  3873. /**
  3874. * The Seek Handle shows the current position of the playhead during playback,
  3875. * and can be dragged to adjust the playhead.
  3876. *
  3877. * @param {vjs.Player|Object} player
  3878. * @param {Object=} options
  3879. * @constructor
  3880. */
  3881. vjs.SeekHandle = vjs.SliderHandle.extend();
  3882. /**
  3883. * The default value for the handle content, which may be read by screen readers
  3884. *
  3885. * @type {String}
  3886. * @private
  3887. */
  3888. vjs.SeekHandle.prototype.defaultValue = '00:00';
  3889. /** @inheritDoc */
  3890. vjs.SeekHandle.prototype.createEl = function () {
  3891. return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
  3892. className: 'vjs-seek-handle'
  3893. });
  3894. };
  3895. /**
  3896. * The component for controlling the volume level
  3897. *
  3898. * @param {vjs.Player|Object} player
  3899. * @param {Object=} options
  3900. * @constructor
  3901. */
  3902. vjs.VolumeControl = vjs.Component.extend({
  3903. /** @constructor */
  3904. init: function (player, options) {
  3905. vjs.Component.call(this, player, options);
  3906. // hide volume controls when they're not supported by the current tech
  3907. if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {
  3908. this.addClass('vjs-hidden');
  3909. }
  3910. player.on('loadstart', vjs.bind(this, function () {
  3911. if (player.tech.features && player.tech.features['volumeControl'] === false) {
  3912. this.addClass('vjs-hidden');
  3913. } else {
  3914. this.removeClass('vjs-hidden');
  3915. }
  3916. }));
  3917. }
  3918. });
  3919. vjs.VolumeControl.prototype.options_ = {
  3920. children: {
  3921. 'volumeBar': {}
  3922. }
  3923. };
  3924. vjs.VolumeControl.prototype.createEl = function () {
  3925. return vjs.Component.prototype.createEl.call(this, 'div', {
  3926. className: 'vjs-volume-control vjs-control'
  3927. });
  3928. };
  3929. /**
  3930. * The bar that contains the volume level and can be clicked on to adjust the level
  3931. *
  3932. * @param {vjs.Player|Object} player
  3933. * @param {Object=} options
  3934. * @constructor
  3935. */
  3936. vjs.VolumeBar = vjs.Slider.extend({
  3937. /** @constructor */
  3938. init: function (player, options) {
  3939. vjs.Slider.call(this, player, options);
  3940. player.on('volumechange', vjs.bind(this, this.updateARIAAttributes));
  3941. player.ready(vjs.bind(this, this.updateARIAAttributes));
  3942. setTimeout(vjs.bind(this, this.update), 0); // update when elements is in DOM
  3943. }
  3944. });
  3945. vjs.VolumeBar.prototype.updateARIAAttributes = function () {
  3946. // Current value of volume bar as a percentage
  3947. this.el_.setAttribute('aria-valuenow', vjs.round(this.player_.volume() * 100, 2));
  3948. this.el_.setAttribute('aria-valuetext', vjs.round(this.player_.volume() * 100, 2) + '%');
  3949. };
  3950. vjs.VolumeBar.prototype.options_ = {
  3951. children: {
  3952. 'volumeLevel': {},
  3953. 'volumeHandle': {}
  3954. },
  3955. 'barName': 'volumeLevel',
  3956. 'handleName': 'volumeHandle'
  3957. };
  3958. vjs.VolumeBar.prototype.playerEvent = 'volumechange';
  3959. vjs.VolumeBar.prototype.createEl = function () {
  3960. return vjs.Slider.prototype.createEl.call(this, 'div', {
  3961. className: 'vjs-volume-bar',
  3962. 'aria-label': 'volume level'
  3963. });
  3964. };
  3965. vjs.VolumeBar.prototype.onMouseMove = function (event) {
  3966. if (this.player_.muted()) {
  3967. this.player_.muted(false);
  3968. }
  3969. this.player_.volume(this.calculateDistance(event));
  3970. };
  3971. vjs.VolumeBar.prototype.getPercent = function () {
  3972. if (this.player_.muted()) {
  3973. return 0;
  3974. } else {
  3975. return this.player_.volume();
  3976. }
  3977. };
  3978. vjs.VolumeBar.prototype.stepForward = function () {
  3979. this.player_.volume(this.player_.volume() + 0.1);
  3980. };
  3981. vjs.VolumeBar.prototype.stepBack = function () {
  3982. this.player_.volume(this.player_.volume() - 0.1);
  3983. };
  3984. /**
  3985. * Shows volume level
  3986. *
  3987. * @param {vjs.Player|Object} player
  3988. * @param {Object=} options
  3989. * @constructor
  3990. */
  3991. vjs.VolumeLevel = vjs.Component.extend({
  3992. /** @constructor */
  3993. init: function (player, options) {
  3994. vjs.Component.call(this, player, options);
  3995. }
  3996. });
  3997. vjs.VolumeLevel.prototype.createEl = function () {
  3998. return vjs.Component.prototype.createEl.call(this, 'div', {
  3999. className: 'vjs-volume-level',
  4000. innerHTML: '<span class="vjs-control-text"></span>'
  4001. });
  4002. };
  4003. /**
  4004. * The volume handle can be dragged to adjust the volume level
  4005. *
  4006. * @param {vjs.Player|Object} player
  4007. * @param {Object=} options
  4008. * @constructor
  4009. */
  4010. vjs.VolumeHandle = vjs.SliderHandle.extend();
  4011. vjs.VolumeHandle.prototype.defaultValue = '00:00';
  4012. /** @inheritDoc */
  4013. vjs.VolumeHandle.prototype.createEl = function () {
  4014. return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
  4015. className: 'vjs-volume-handle'
  4016. });
  4017. };
  4018. /**
  4019. * A button component for muting the audio
  4020. *
  4021. * @param {vjs.Player|Object} player
  4022. * @param {Object=} options
  4023. * @constructor
  4024. */
  4025. vjs.MuteToggle = vjs.Button.extend({
  4026. /** @constructor */
  4027. init: function (player, options) {
  4028. vjs.Button.call(this, player, options);
  4029. player.on('volumechange', vjs.bind(this, this.update));
  4030. // hide mute toggle if the current tech doesn't support volume control
  4031. if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {
  4032. this.addClass('vjs-hidden');
  4033. }
  4034. player.on('loadstart', vjs.bind(this, function () {
  4035. if (player.tech.features && player.tech.features['volumeControl'] === false) {
  4036. this.addClass('vjs-hidden');
  4037. } else {
  4038. this.removeClass('vjs-hidden');
  4039. }
  4040. }));
  4041. }
  4042. });
  4043. vjs.MuteToggle.prototype.createEl = function () {
  4044. return vjs.Button.prototype.createEl.call(this, 'div', {
  4045. className: 'vjs-mute-control vjs-control',
  4046. innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
  4047. });
  4048. };
  4049. vjs.MuteToggle.prototype.onClick = function () {
  4050. this.player_.muted(this.player_.muted() ? false : true);
  4051. };
  4052. vjs.MuteToggle.prototype.update = function () {
  4053. var vol = this.player_.volume(),
  4054. level = 3;
  4055. if (vol === 0 || this.player_.muted()) {
  4056. level = 0;
  4057. } else if (vol < 0.33) {
  4058. level = 1;
  4059. } else if (vol < 0.67) {
  4060. level = 2;
  4061. }
  4062. // Don't rewrite the button text if the actual text doesn't change.
  4063. // This causes unnecessary and confusing information for screen reader users.
  4064. // This check is needed because this function gets called every time the volume level is changed.
  4065. if (this.player_.muted()) {
  4066. if (this.el_.children[0].children[0].innerHTML != 'Unmute') {
  4067. this.el_.children[0].children[0].innerHTML = 'Unmute'; // change the button text to "Unmute"
  4068. }
  4069. } else {
  4070. if (this.el_.children[0].children[0].innerHTML != 'Mute') {
  4071. this.el_.children[0].children[0].innerHTML = 'Mute'; // change the button text to "Mute"
  4072. }
  4073. }
  4074. /* TODO improve muted icon classes */
  4075. for (var i = 0; i < 4; i++) {
  4076. vjs.removeClass(this.el_, 'vjs-vol-' + i);
  4077. }
  4078. vjs.addClass(this.el_, 'vjs-vol-' + level);
  4079. };
  4080. /**
  4081. * Menu button with a popup for showing the volume slider.
  4082. * @constructor
  4083. */
  4084. vjs.VolumeMenuButton = vjs.MenuButton.extend({
  4085. /** @constructor */
  4086. init: function (player, options) {
  4087. vjs.MenuButton.call(this, player, options);
  4088. // Same listeners as MuteToggle
  4089. player.on('volumechange', vjs.bind(this, this.update));
  4090. // hide mute toggle if the current tech doesn't support volume control
  4091. if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
  4092. this.addClass('vjs-hidden');
  4093. }
  4094. player.on('loadstart', vjs.bind(this, function () {
  4095. if (player.tech.features && player.tech.features.volumeControl === false) {
  4096. this.addClass('vjs-hidden');
  4097. } else {
  4098. this.removeClass('vjs-hidden');
  4099. }
  4100. }));
  4101. this.addClass('vjs-menu-button');
  4102. }
  4103. });
  4104. vjs.VolumeMenuButton.prototype.createMenu = function () {
  4105. var menu = new vjs.Menu(this.player_, {
  4106. contentElType: 'div'
  4107. });
  4108. var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({ vertical: true }, this.options_.volumeBar));
  4109. menu.addChild(vc);
  4110. return menu;
  4111. };
  4112. vjs.VolumeMenuButton.prototype.onClick = function () {
  4113. vjs.MuteToggle.prototype.onClick.call(this);
  4114. vjs.MenuButton.prototype.onClick.call(this);
  4115. };
  4116. vjs.VolumeMenuButton.prototype.createEl = function () {
  4117. return vjs.Button.prototype.createEl.call(this, 'div', {
  4118. className: 'vjs-volume-menu-button vjs-menu-button vjs-control',
  4119. innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
  4120. });
  4121. };
  4122. vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
  4123. /* Poster Image
  4124. ================================================================================ */
  4125. /**
  4126. * The component that handles showing the poster image.
  4127. *
  4128. * @param {vjs.Player|Object} player
  4129. * @param {Object=} options
  4130. * @constructor
  4131. */
  4132. vjs.PosterImage = vjs.Button.extend({
  4133. /** @constructor */
  4134. init: function (player, options) {
  4135. vjs.Button.call(this, player, options);
  4136. if (!player.poster() || !player.controls()) {
  4137. this.hide();
  4138. }
  4139. player.on('play', vjs.bind(this, this.hide));
  4140. }
  4141. });
  4142. vjs.PosterImage.prototype.createEl = function () {
  4143. var el = vjs.createEl('div', {
  4144. className: 'vjs-poster',
  4145. // Don't want poster to be tabbable.
  4146. tabIndex: -1
  4147. }),
  4148. poster = this.player_.poster();
  4149. if (poster) {
  4150. if ('backgroundSize' in el.style) {
  4151. el.style.backgroundImage = 'url("' + poster + '")';
  4152. } else {
  4153. el.appendChild(vjs.createEl('img', { src: poster }));
  4154. }
  4155. }
  4156. return el;
  4157. };
  4158. vjs.PosterImage.prototype.onClick = function () {
  4159. // Only accept clicks when controls are enabled
  4160. if (this.player().controls()) {
  4161. this.player_.play();
  4162. }
  4163. };
  4164. /* Loading Spinner
  4165. ================================================================================ */
  4166. /**
  4167. * Loading spinner for waiting events
  4168. * @param {vjs.Player|Object} player
  4169. * @param {Object=} options
  4170. * @class
  4171. * @constructor
  4172. */
  4173. vjs.LoadingSpinner = vjs.Component.extend({
  4174. /** @constructor */
  4175. init: function (player, options) {
  4176. vjs.Component.call(this, player, options);
  4177. player.on('canplay', vjs.bind(this, this.hide));
  4178. player.on('canplaythrough', vjs.bind(this, this.hide));
  4179. player.on('playing', vjs.bind(this, this.hide));
  4180. player.on('seeked', vjs.bind(this, this.hide));
  4181. player.on('seeking', vjs.bind(this, this.show));
  4182. // in some browsers seeking does not trigger the 'playing' event,
  4183. // so we also need to trap 'seeked' if we are going to set a
  4184. // 'seeking' event
  4185. player.on('seeked', vjs.bind(this, this.hide));
  4186. player.on('error', vjs.bind(this, this.show));
  4187. // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
  4188. // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing
  4189. // player.on('stalled', vjs.bind(this, this.show));
  4190. player.on('waiting', vjs.bind(this, this.show));
  4191. }
  4192. });
  4193. vjs.LoadingSpinner.prototype.createEl = function () {
  4194. return vjs.Component.prototype.createEl.call(this, 'div', {
  4195. className: 'vjs-loading-spinner'
  4196. });
  4197. };
  4198. /* Big Play Button
  4199. ================================================================================ */
  4200. /**
  4201. * Initial play button. Shows before the video has played. The hiding of the
  4202. * big play button is done via CSS and player states.
  4203. * @param {vjs.Player|Object} player
  4204. * @param {Object=} options
  4205. * @class
  4206. * @constructor
  4207. */
  4208. vjs.BigPlayButton = vjs.Button.extend();
  4209. vjs.BigPlayButton.prototype.createEl = function () {
  4210. return vjs.Button.prototype.createEl.call(this, 'div', {
  4211. className: 'vjs-big-play-button',
  4212. innerHTML: '<span aria-hidden="true"></span>',
  4213. 'aria-label': 'play video'
  4214. });
  4215. };
  4216. vjs.BigPlayButton.prototype.onClick = function () {
  4217. this.player_.play();
  4218. };
  4219. /**
  4220. * @fileoverview Media Technology Controller - Base class for media playback
  4221. * technology controllers like Flash and HTML5
  4222. */
  4223. /**
  4224. * Base class for media (HTML5 Video, Flash) controllers
  4225. * @param {vjs.Player|Object} player Central player instance
  4226. * @param {Object=} options Options object
  4227. * @constructor
  4228. */
  4229. vjs.MediaTechController = vjs.Component.extend({
  4230. /** @constructor */
  4231. init: function (player, options, ready) {
  4232. vjs.Component.call(this, player, options, ready);
  4233. this.initControlsListeners();
  4234. }
  4235. });
  4236. /**
  4237. * Set up click and touch listeners for the playback element
  4238. * On desktops, a click on the video itself will toggle playback,
  4239. * on a mobile device a click on the video toggles controls.
  4240. * (toggling controls is done by toggling the user state between active and
  4241. * inactive)
  4242. *
  4243. * A tap can signal that a user has become active, or has become inactive
  4244. * e.g. a quick tap on an iPhone movie should reveal the controls. Another
  4245. * quick tap should hide them again (signaling the user is in an inactive
  4246. * viewing state)
  4247. *
  4248. * In addition to this, we still want the user to be considered inactive after
  4249. * a few seconds of inactivity.
  4250. *
  4251. * Note: the only part of iOS interaction we can't mimic with this setup
  4252. * is a touch and hold on the video element counting as activity in order to
  4253. * keep the controls showing, but that shouldn't be an issue. A touch and hold on
  4254. * any controls will still keep the user active
  4255. */
  4256. vjs.MediaTechController.prototype.initControlsListeners = function () {
  4257. var player, tech, activateControls, deactivateControls;
  4258. tech = this;
  4259. player = this.player();
  4260. var activateControls = function () {
  4261. if (player.controls() && !player.usingNativeControls()) {
  4262. tech.addControlsListeners();
  4263. }
  4264. };
  4265. deactivateControls = vjs.bind(tech, tech.removeControlsListeners);
  4266. // Set up event listeners once the tech is ready and has an element to apply
  4267. // listeners to
  4268. this.ready(activateControls);
  4269. player.on('controlsenabled', activateControls);
  4270. player.on('controlsdisabled', deactivateControls);
  4271. };
  4272. vjs.MediaTechController.prototype.addControlsListeners = function () {
  4273. var preventBubble, userWasActive;
  4274. // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
  4275. // trigger mousedown/up.
  4276. // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
  4277. // Any touch events are set to block the mousedown event from happening
  4278. this.on('mousedown', this.onClick);
  4279. // We need to block touch events on the video element from bubbling up,
  4280. // otherwise they'll signal activity prematurely. The specific use case is
  4281. // when the video is playing and the controls have faded out. In this case
  4282. // only a tap (fast touch) should toggle the user active state and turn the
  4283. // controls back on. A touch and move or touch and hold should not trigger
  4284. // the controls (per iOS as an example at least)
  4285. //
  4286. // We always want to stop propagation on touchstart because touchstart
  4287. // at the player level starts the touchInProgress interval. We can still
  4288. // report activity on the other events, but won't let them bubble for
  4289. // consistency. We don't want to bubble a touchend without a touchstart.
  4290. this.on('touchstart', function (event) {
  4291. // Stop the mouse events from also happening
  4292. event.preventDefault();
  4293. event.stopPropagation();
  4294. // Record if the user was active now so we don't have to keep polling it
  4295. userWasActive = this.player_.userActive();
  4296. });
  4297. preventBubble = function (event) {
  4298. event.stopPropagation();
  4299. if (userWasActive) {
  4300. this.player_.reportUserActivity();
  4301. }
  4302. };
  4303. // Treat all touch events the same for consistency
  4304. this.on('touchmove', preventBubble);
  4305. this.on('touchleave', preventBubble);
  4306. this.on('touchcancel', preventBubble);
  4307. this.on('touchend', preventBubble);
  4308. // Turn on component tap events
  4309. this.emitTapEvents();
  4310. // The tap listener needs to come after the touchend listener because the tap
  4311. // listener cancels out any reportedUserActivity when setting userActive(false)
  4312. this.on('tap', this.onTap);
  4313. };
  4314. /**
  4315. * Remove the listeners used for click and tap controls. This is needed for
  4316. * toggling to controls disabled, where a tap/touch should do nothing.
  4317. */
  4318. vjs.MediaTechController.prototype.removeControlsListeners = function () {
  4319. // We don't want to just use `this.off()` because there might be other needed
  4320. // listeners added by techs that extend this.
  4321. this.off('tap');
  4322. this.off('touchstart');
  4323. this.off('touchmove');
  4324. this.off('touchleave');
  4325. this.off('touchcancel');
  4326. this.off('touchend');
  4327. this.off('click');
  4328. this.off('mousedown');
  4329. };
  4330. /**
  4331. * Handle a click on the media element. By default will play/pause the media.
  4332. */
  4333. vjs.MediaTechController.prototype.onClick = function (event) {
  4334. // We're using mousedown to detect clicks thanks to Flash, but mousedown
  4335. // will also be triggered with right-clicks, so we need to prevent that
  4336. if (event.button !== 0) return;
  4337. // When controls are disabled a click should not toggle playback because
  4338. // the click is considered a control
  4339. if (this.player().controls()) {
  4340. if (this.player().paused()) {
  4341. this.player().play();
  4342. } else {
  4343. this.player().pause();
  4344. }
  4345. }
  4346. };
  4347. /**
  4348. * Handle a tap on the media element. By default it will toggle the user
  4349. * activity state, which hides and shows the controls.
  4350. */
  4351. vjs.MediaTechController.prototype.onTap = function () {
  4352. this.player().userActive(!this.player().userActive());
  4353. };
  4354. vjs.MediaTechController.prototype.features = {
  4355. 'volumeControl': true,
  4356. // Resizing plugins using request fullscreen reloads the plugin
  4357. 'fullscreenResize': false,
  4358. // Optional events that we can manually mimic with timers
  4359. // currently not triggered by video-js-swf
  4360. 'progressEvents': false,
  4361. 'timeupdateEvents': false
  4362. };
  4363. vjs.media = {};
  4364. /**
  4365. * List of default API methods for any MediaTechController
  4366. * @type {String}
  4367. */
  4368. vjs.media.ApiMethods = 'play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted'.split(',');
  4369. // Create placeholder methods for each that warn when a method isn't supported by the current playback technology
  4370. function createMethod(methodName) {
  4371. return function () {
  4372. throw new Error('The "' + methodName + '" method is not available on the playback technology\'s API');
  4373. };
  4374. }
  4375. for (var i = vjs.media.ApiMethods.length - 1; i >= 0; i--) {
  4376. var methodName = vjs.media.ApiMethods[i];
  4377. vjs.MediaTechController.prototype[vjs.media.ApiMethods[i]] = createMethod(methodName);
  4378. }
  4379. /**
  4380. * @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API
  4381. */
  4382. /**
  4383. * HTML5 Media Controller - Wrapper for HTML5 Media API
  4384. * @param {vjs.Player|Object} player
  4385. * @param {Object=} options
  4386. * @param {Function=} ready
  4387. * @constructor
  4388. */
  4389. vjs.Html5 = vjs.MediaTechController.extend({
  4390. /** @constructor */
  4391. init: function (player, options, ready) {
  4392. // volume cannot be changed from 1 on iOS
  4393. this.features['volumeControl'] = vjs.Html5.canControlVolume();
  4394. // In iOS, if you move a video element in the DOM, it breaks video playback.
  4395. this.features['movingMediaElementInDOM'] = !vjs.IS_IOS;
  4396. // HTML video is able to automatically resize when going to fullscreen
  4397. this.features['fullscreenResize'] = true;
  4398. vjs.MediaTechController.call(this, player, options, ready);
  4399. var source = options['source'];
  4400. // If the element source is already set, we may have missed the loadstart event, and want to trigger it.
  4401. // We don't want to set the source again and interrupt playback.
  4402. if (source && this.el_.currentSrc === source.src && this.el_.networkState > 0) {
  4403. player.trigger('loadstart');
  4404. // Otherwise set the source if one was provided.
  4405. } else if (source) {
  4406. this.el_.src = source.src;
  4407. }
  4408. // Determine if native controls should be used
  4409. // Our goal should be to get the custom controls on mobile solid everywhere
  4410. // so we can remove this all together. Right now this will block custom
  4411. // controls on touch enabled laptops like the Chrome Pixel
  4412. if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] !== false) {
  4413. this.useNativeControls();
  4414. }
  4415. // Chrome and Safari both have issues with autoplay.
  4416. // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
  4417. // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
  4418. // This fixes both issues. Need to wait for API, so it updates displays correctly
  4419. player.ready(function () {
  4420. if (this.tag && this.options_['autoplay'] && this.paused()) {
  4421. delete this.tag['poster']; // Chrome Fix. Fixed in Chrome v16.
  4422. this.play();
  4423. }
  4424. });
  4425. this.setupTriggers();
  4426. this.triggerReady();
  4427. }
  4428. });
  4429. vjs.Html5.prototype.dispose = function () {
  4430. vjs.MediaTechController.prototype.dispose.call(this);
  4431. };
  4432. vjs.Html5.prototype.createEl = function () {
  4433. var player = this.player_,
  4434. // If possible, reuse original tag for HTML5 playback technology element
  4435. el = player.tag,
  4436. newEl,
  4437. clone;
  4438. // Check if this browser supports moving the element into the box.
  4439. // On the iPhone video will break if you move the element,
  4440. // So we have to create a brand new element.
  4441. if (!el || this.features['movingMediaElementInDOM'] === false) {
  4442. // If the original tag is still there, clone and remove it.
  4443. if (el) {
  4444. clone = el.cloneNode(false);
  4445. vjs.Html5.disposeMediaElement(el);
  4446. el = clone;
  4447. player.tag = null;
  4448. } else {
  4449. el = vjs.createEl('video', {
  4450. id: player.id() + '_html5_api',
  4451. className: 'vjs-tech'
  4452. });
  4453. }
  4454. // associate the player with the new tag
  4455. el['player'] = player;
  4456. vjs.insertFirst(el, player.el());
  4457. }
  4458. // Update specific tag settings, in case they were overridden
  4459. var attrs = ['autoplay', 'preload', 'loop', 'muted'];
  4460. for (var i = attrs.length - 1; i >= 0; i--) {
  4461. var attr = attrs[i];
  4462. if (player.options_[attr] !== null) {
  4463. el[attr] = player.options_[attr];
  4464. }
  4465. }
  4466. return el;
  4467. // jenniisawesome = true;
  4468. };
  4469. // Make video events trigger player events
  4470. // May seem verbose here, but makes other APIs possible.
  4471. vjs.Html5.prototype.setupTriggers = function () {
  4472. for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
  4473. vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this.player_, this.eventHandler));
  4474. }
  4475. };
  4476. // Triggers removed using this.off when disposed
  4477. vjs.Html5.prototype.eventHandler = function (e) {
  4478. this.trigger(e);
  4479. // No need for media events to bubble up.
  4480. e.stopPropagation();
  4481. };
  4482. vjs.Html5.prototype.useNativeControls = function () {
  4483. var tech, player, controlsOn, controlsOff, cleanUp;
  4484. tech = this;
  4485. player = this.player();
  4486. // If the player controls are enabled turn on the native controls
  4487. tech.setControls(player.controls());
  4488. // Update the native controls when player controls state is updated
  4489. controlsOn = function () {
  4490. tech.setControls(true);
  4491. };
  4492. controlsOff = function () {
  4493. tech.setControls(false);
  4494. };
  4495. player.on('controlsenabled', controlsOn);
  4496. player.on('controlsdisabled', controlsOff);
  4497. // Clean up when not using native controls anymore
  4498. cleanUp = function () {
  4499. player.off('controlsenabled', controlsOn);
  4500. player.off('controlsdisabled', controlsOff);
  4501. };
  4502. tech.on('dispose', cleanUp);
  4503. player.on('usingcustomcontrols', cleanUp);
  4504. // Update the state of the player to using native controls
  4505. player.usingNativeControls(true);
  4506. };
  4507. vjs.Html5.prototype.play = function () { this.el_.play(); };
  4508. vjs.Html5.prototype.pause = function () { this.el_.pause(); };
  4509. vjs.Html5.prototype.paused = function () { return this.el_.paused; };
  4510. vjs.Html5.prototype.currentTime = function () { return this.el_.currentTime; };
  4511. vjs.Html5.prototype.setCurrentTime = function (seconds) {
  4512. try {
  4513. this.el_.currentTime = seconds;
  4514. } catch (e) {
  4515. vjs.log(e, 'Video is not ready. (Video.js)');
  4516. // this.warning(VideoJS.warnings.videoNotReady);
  4517. }
  4518. };
  4519. vjs.Html5.prototype.duration = function () { return this.el_.duration || 0; };
  4520. vjs.Html5.prototype.buffered = function () { return this.el_.buffered; };
  4521. vjs.Html5.prototype.volume = function () { return this.el_.volume; };
  4522. vjs.Html5.prototype.setVolume = function (percentAsDecimal) { this.el_.volume = percentAsDecimal; };
  4523. vjs.Html5.prototype.muted = function () { return this.el_.muted; };
  4524. vjs.Html5.prototype.setMuted = function (muted) { this.el_.muted = muted; };
  4525. vjs.Html5.prototype.width = function () { return this.el_.offsetWidth; };
  4526. vjs.Html5.prototype.height = function () { return this.el_.offsetHeight; };
  4527. vjs.Html5.prototype.supportsFullScreen = function () {
  4528. if (typeof this.el_.webkitEnterFullScreen == 'function') {
  4529. // Seems to be broken in Chromium/Chrome && Safari in Leopard
  4530. if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {
  4531. return true;
  4532. }
  4533. }
  4534. return false;
  4535. };
  4536. vjs.Html5.prototype.enterFullScreen = function () {
  4537. var video = this.el_;
  4538. if (video.paused && video.networkState <= video.HAVE_METADATA) {
  4539. // attempt to prime the video element for programmatic access
  4540. // this isn't necessary on the desktop but shouldn't hurt
  4541. this.el_.play();
  4542. // playing and pausing synchronously during the transition to fullscreen
  4543. // can get iOS ~6.1 devices into a play/pause loop
  4544. setTimeout(function () {
  4545. video.pause();
  4546. video.webkitEnterFullScreen();
  4547. }, 0);
  4548. } else {
  4549. video.webkitEnterFullScreen();
  4550. }
  4551. };
  4552. vjs.Html5.prototype.exitFullScreen = function () {
  4553. this.el_.webkitExitFullScreen();
  4554. };
  4555. vjs.Html5.prototype.src = function (src) { this.el_.src = src; };
  4556. vjs.Html5.prototype.load = function () { this.el_.load(); };
  4557. vjs.Html5.prototype.currentSrc = function () { return this.el_.currentSrc; };
  4558. vjs.Html5.prototype.preload = function () { return this.el_.preload; };
  4559. vjs.Html5.prototype.setPreload = function (val) { this.el_.preload = val; };
  4560. vjs.Html5.prototype.autoplay = function () { return this.el_.autoplay; };
  4561. vjs.Html5.prototype.setAutoplay = function (val) { this.el_.autoplay = val; };
  4562. vjs.Html5.prototype.controls = function () { return this.el_.controls; }
  4563. vjs.Html5.prototype.setControls = function (val) { this.el_.controls = !!val; }
  4564. vjs.Html5.prototype.loop = function () { return this.el_.loop; };
  4565. vjs.Html5.prototype.setLoop = function (val) { this.el_.loop = val; };
  4566. vjs.Html5.prototype.error = function () { return this.el_.error; };
  4567. vjs.Html5.prototype.seeking = function () { return this.el_.seeking; };
  4568. vjs.Html5.prototype.ended = function () { return this.el_.ended; };
  4569. vjs.Html5.prototype.defaultMuted = function () { return this.el_.defaultMuted; };
  4570. /* HTML5 Support Testing ---------------------------------------------------- */
  4571. vjs.Html5.isSupported = function () {
  4572. return !!vjs.TEST_VID.canPlayType;
  4573. };
  4574. vjs.Html5.canPlaySource = function (srcObj) {
  4575. // IE9 on Windows 7 without MediaPlayer throws an error here
  4576. // https://github.com/videojs/video.js/issues/519
  4577. try {
  4578. return !!vjs.TEST_VID.canPlayType(srcObj.type);
  4579. } catch (e) {
  4580. return '';
  4581. }
  4582. // TODO: Check Type
  4583. // If no Type, check ext
  4584. // Check Media Type
  4585. };
  4586. vjs.Html5.canControlVolume = function () {
  4587. var volume = vjs.TEST_VID.volume;
  4588. vjs.TEST_VID.volume = (volume / 2) + 0.1;
  4589. return volume !== vjs.TEST_VID.volume;
  4590. };
  4591. // List of all HTML5 events (various uses).
  4592. vjs.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(',');
  4593. vjs.Html5.disposeMediaElement = function (el) {
  4594. if (!el) { return; }
  4595. el['player'] = null;
  4596. if (el.parentNode) {
  4597. el.parentNode.removeChild(el);
  4598. }
  4599. // remove any child track or source nodes to prevent their loading
  4600. while (el.hasChildNodes()) {
  4601. el.removeChild(el.firstChild);
  4602. }
  4603. // remove any src reference. not setting `src=''` because that causes a warning
  4604. // in firefox
  4605. el.removeAttribute('src');
  4606. // force the media element to update its loading state by calling load()
  4607. if (typeof el.load === 'function') {
  4608. el.load();
  4609. }
  4610. };
  4611. // HTML5 Feature detection and Device Fixes --------------------------------- //
  4612. // Override Android 2.2 and less canPlayType method which is broken
  4613. if (vjs.IS_OLD_ANDROID) {
  4614. document.createElement('video').constructor.prototype.canPlayType = function (type) {
  4615. return (type && type.toLowerCase().indexOf('video/mp4') != -1) ? 'maybe' : '';
  4616. };
  4617. }
  4618. /**
  4619. * @fileoverview VideoJS-SWF - Custom Flash Player with HTML5-ish API
  4620. * https://github.com/zencoder/video-js-swf
  4621. * Not using setupTriggers. Using global onEvent func to distribute events
  4622. */
  4623. /**
  4624. * Flash Media Controller - Wrapper for fallback SWF API
  4625. *
  4626. * @param {vjs.Player} player
  4627. * @param {Object=} options
  4628. * @param {Function=} ready
  4629. * @constructor
  4630. */
  4631. vjs.Flash = vjs.MediaTechController.extend({
  4632. /** @constructor */
  4633. init: function (player, options, ready) {
  4634. vjs.MediaTechController.call(this, player, options, ready);
  4635. var source = options['source'],
  4636. // Which element to embed in
  4637. parentEl = options['parentEl'],
  4638. // Create a temporary element to be replaced by swf object
  4639. placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),
  4640. // Generate ID for swf object
  4641. objId = player.id() + '_flash_api',
  4642. // Store player options in local var for optimization
  4643. // TODO: switch to using player methods instead of options
  4644. // e.g. player.autoplay();
  4645. playerOptions = player.options_,
  4646. // Merge default flashvars with ones passed in to init
  4647. flashVars = vjs.obj.merge({
  4648. // SWF Callback Functions
  4649. 'readyFunction': 'videojs.Flash.onReady',
  4650. 'eventProxyFunction': 'videojs.Flash.onEvent',
  4651. 'errorEventProxyFunction': 'videojs.Flash.onError',
  4652. // Player Settings
  4653. 'autoplay': playerOptions.autoplay,
  4654. 'preload': playerOptions.preload,
  4655. 'loop': playerOptions.loop,
  4656. 'muted': playerOptions.muted
  4657. }, options['flashVars']),
  4658. // Merge default parames with ones passed in
  4659. params = vjs.obj.merge({
  4660. 'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
  4661. 'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
  4662. }, options['params']),
  4663. // Merge default attributes with ones passed in
  4664. attributes = vjs.obj.merge({
  4665. 'id': objId,
  4666. 'name': objId, // Both ID and Name needed or swf to identifty itself
  4667. 'class': 'vjs-tech'
  4668. }, options['attributes'])
  4669. ;
  4670. // If source was supplied pass as a flash var.
  4671. if (source) {
  4672. if (source.type && vjs.Flash.isStreamingType(source.type)) {
  4673. var parts = vjs.Flash.streamToParts(source.src);
  4674. flashVars['rtmpConnection'] = encodeURIComponent(parts.connection);
  4675. flashVars['rtmpStream'] = encodeURIComponent(parts.stream);
  4676. }
  4677. else {
  4678. flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
  4679. }
  4680. }
  4681. // Add placeholder to player div
  4682. vjs.insertFirst(placeHolder, parentEl);
  4683. // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
  4684. // This allows resetting the playhead when we catch the reload
  4685. if (options['startTime']) {
  4686. this.ready(function () {
  4687. this.load();
  4688. this.play();
  4689. this.currentTime(options['startTime']);
  4690. });
  4691. }
  4692. // Flash iFrame Mode
  4693. // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
  4694. // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
  4695. // - Webkit when hiding the plugin
  4696. // - Webkit and Firefox when using requestFullScreen on a parent element
  4697. // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
  4698. // Issues that remain include hiding the element and requestFullScreen in Firefox specifically
  4699. // There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.
  4700. // 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.
  4701. // 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.
  4702. // 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
  4703. // 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.
  4704. // 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.
  4705. // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
  4706. // Firefox 9 throws a security error, unleess you call location.href right before doc.write.
  4707. // Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
  4708. // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
  4709. if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {
  4710. // Create iFrame with vjs-tech class so it's 100% width/height
  4711. var iFrm = vjs.createEl('iframe', {
  4712. 'id': objId + '_iframe',
  4713. 'name': objId + '_iframe',
  4714. 'className': 'vjs-tech',
  4715. 'scrolling': 'no',
  4716. 'marginWidth': 0,
  4717. 'marginHeight': 0,
  4718. 'frameBorder': 0
  4719. });
  4720. // Update ready function names in flash vars for iframe window
  4721. flashVars['readyFunction'] = 'ready';
  4722. flashVars['eventProxyFunction'] = 'events';
  4723. flashVars['errorEventProxyFunction'] = 'errors';
  4724. // Tried multiple methods to get this to work in all browsers
  4725. // 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.
  4726. // 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
  4727. // var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
  4728. // (in onload)
  4729. // var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );
  4730. // iDoc.body.appendChild(temp);
  4731. // Tried embedding the flash object through javascript in the iframe source.
  4732. // This works in webkit but still triggers the firefox security error
  4733. // iFrm.src = 'javascript: document.write('"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+"');";
  4734. // 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
  4735. // We should add an option to host the iframe locally though, because it could help a lot of issues.
  4736. // iFrm.src = "iframe.html";
  4737. // Wait until iFrame has loaded to write into it.
  4738. vjs.on(iFrm, 'load', vjs.bind(this, function () {
  4739. var iDoc,
  4740. iWin = iFrm.contentWindow;
  4741. // The one working method I found was to use the iframe's document.write() to create the swf object
  4742. // This got around the security issue in all browsers except firefox.
  4743. // I did find a hack where if I call the iframe's window.location.href='', it would get around the security error
  4744. // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
  4745. // Plus Firefox 3.6 didn't work no matter what I tried.
  4746. // if (vjs.USER_AGENT.match('Firefox')) {
  4747. // iWin.location.href = '';
  4748. // }
  4749. // Get the iFrame's document depending on what the browser supports
  4750. iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
  4751. // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
  4752. // Even tried adding /. that was mentioned in a browser security writeup
  4753. // document.domain = document.domain+'/.';
  4754. // iDoc.domain = document.domain+'/.';
  4755. // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
  4756. // iDoc.body.innerHTML = swfObjectHTML;
  4757. // Tried appending the object to the iframe doc's body. Security error in all browsers.
  4758. // iDoc.body.appendChild(swfObject);
  4759. // Using document.write actually got around the security error that browsers were throwing.
  4760. // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
  4761. // Not sure why that's a security issue, but apparently it is.
  4762. iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));
  4763. // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
  4764. // So far no issues with swf ready event being called before it's set on the window.
  4765. iWin['player'] = this.player_;
  4766. // Create swf ready function for iFrame window
  4767. iWin['ready'] = vjs.bind(this.player_, function (currSwf) {
  4768. var el = iDoc.getElementById(currSwf),
  4769. player = this,
  4770. tech = player.tech;
  4771. // Update reference to playback technology element
  4772. tech.el_ = el;
  4773. // Make sure swf is actually ready. Sometimes the API isn't actually yet.
  4774. vjs.Flash.checkReady(tech);
  4775. });
  4776. // Create event listener for all swf events
  4777. iWin['events'] = vjs.bind(this.player_, function (swfID, eventName) {
  4778. var player = this;
  4779. if (player && player.techName === 'flash') {
  4780. player.trigger(eventName);
  4781. }
  4782. });
  4783. // Create error listener for all swf errors
  4784. iWin['errors'] = vjs.bind(this.player_, function (swfID, eventName) {
  4785. vjs.log('Flash Error', eventName);
  4786. });
  4787. }));
  4788. // Replace placeholder with iFrame (it will load now)
  4789. placeHolder.parentNode.replaceChild(iFrm, placeHolder);
  4790. // If not using iFrame mode, embed as normal object
  4791. } else {
  4792. vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
  4793. }
  4794. }
  4795. });
  4796. vjs.Flash.prototype.dispose = function () {
  4797. vjs.MediaTechController.prototype.dispose.call(this);
  4798. };
  4799. vjs.Flash.prototype.play = function () {
  4800. this.el_.vjs_play();
  4801. };
  4802. vjs.Flash.prototype.pause = function () {
  4803. this.el_.vjs_pause();
  4804. };
  4805. vjs.Flash.prototype.src = function (src) {
  4806. if (vjs.Flash.isStreamingSrc(src)) {
  4807. src = vjs.Flash.streamToParts(src);
  4808. this.setRtmpConnection(src.connection);
  4809. this.setRtmpStream(src.stream);
  4810. }
  4811. else {
  4812. // Make sure source URL is abosolute.
  4813. src = vjs.getAbsoluteURL(src);
  4814. this.el_.vjs_src(src);
  4815. }
  4816. // Currently the SWF doesn't autoplay if you load a source later.
  4817. // e.g. Load player w/ no source, wait 2s, set src.
  4818. if (this.player_.autoplay()) {
  4819. var tech = this;
  4820. setTimeout(function () { tech.play(); }, 0);
  4821. }
  4822. };
  4823. vjs.Flash.prototype.currentSrc = function () {
  4824. var src = this.el_.vjs_getProperty('currentSrc');
  4825. // no src, check and see if RTMP
  4826. if (src == null) {
  4827. var connection = this.rtmpConnection(),
  4828. stream = this.rtmpStream();
  4829. if (connection && stream) {
  4830. src = vjs.Flash.streamFromParts(connection, stream);
  4831. }
  4832. }
  4833. return src;
  4834. };
  4835. vjs.Flash.prototype.load = function () {
  4836. this.el_.vjs_load();
  4837. };
  4838. vjs.Flash.prototype.poster = function () {
  4839. this.el_.vjs_getProperty('poster');
  4840. };
  4841. vjs.Flash.prototype.buffered = function () {
  4842. return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered'));
  4843. };
  4844. vjs.Flash.prototype.supportsFullScreen = function () {
  4845. return false; // Flash does not allow fullscreen through javascript
  4846. };
  4847. vjs.Flash.prototype.enterFullScreen = function () {
  4848. return false;
  4849. };
  4850. // Create setters and getters for attributes
  4851. var api = vjs.Flash.prototype,
  4852. readWrite = 'rtmpConnection,rtmpStream,preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
  4853. readOnly = 'error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');
  4854. // Overridden: buffered
  4855. /**
  4856. * @this {*}
  4857. * @private
  4858. */
  4859. var createSetter = function (attr) {
  4860. var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
  4861. api['set' + attrUpper] = function (val) { return this.el_.vjs_setProperty(attr, val); };
  4862. };
  4863. /**
  4864. * @this {*}
  4865. * @private
  4866. */
  4867. var createGetter = function (attr) {
  4868. api[attr] = function () { return this.el_.vjs_getProperty(attr); };
  4869. };
  4870. (function () {
  4871. var i;
  4872. // Create getter and setters for all read/write attributes
  4873. for (i = 0; i < readWrite.length; i++) {
  4874. createGetter(readWrite[i]);
  4875. createSetter(readWrite[i]);
  4876. }
  4877. // Create getters for read-only attributes
  4878. for (i = 0; i < readOnly.length; i++) {
  4879. createGetter(readOnly[i]);
  4880. }
  4881. })();
  4882. /* Flash Support Testing -------------------------------------------------------- */
  4883. vjs.Flash.isSupported = function () {
  4884. return vjs.Flash.version()[0] >= 10;
  4885. // return swfobject.hasFlashPlayerVersion('10');
  4886. };
  4887. vjs.Flash.canPlaySource = function (srcObj) {
  4888. var type;
  4889. if (!srcObj.type) {
  4890. return '';
  4891. }
  4892. type = srcObj.type.replace(/;.*/, '').toLowerCase();
  4893. if (type in vjs.Flash.formats || type in vjs.Flash.streamingFormats) {
  4894. return 'maybe';
  4895. }
  4896. };
  4897. vjs.Flash.formats = {
  4898. 'video/flv': 'FLV',
  4899. 'video/x-flv': 'FLV',
  4900. 'video/mp4': 'MP4',
  4901. 'video/m4v': 'MP4'
  4902. };
  4903. vjs.Flash.streamingFormats = {
  4904. 'rtmp/mp4': 'MP4',
  4905. 'rtmp/flv': 'FLV'
  4906. };
  4907. vjs.Flash['onReady'] = function (currSwf) {
  4908. var el = vjs.el(currSwf);
  4909. // Get player from box
  4910. // On firefox reloads, el might already have a player
  4911. var player = el['player'] || el.parentNode['player'],
  4912. tech = player.tech;
  4913. // Reference player on tech element
  4914. el['player'] = player;
  4915. // Update reference to playback technology element
  4916. tech.el_ = el;
  4917. vjs.Flash.checkReady(tech);
  4918. };
  4919. // The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.
  4920. // If it's not ready, we set a timeout to check again shortly.
  4921. vjs.Flash.checkReady = function (tech) {
  4922. // Check if API property exists
  4923. if (tech.el().vjs_getProperty) {
  4924. // If so, tell tech it's ready
  4925. tech.triggerReady();
  4926. // Otherwise wait longer.
  4927. } else {
  4928. setTimeout(function () {
  4929. vjs.Flash.checkReady(tech);
  4930. }, 50);
  4931. }
  4932. };
  4933. // Trigger events from the swf on the player
  4934. vjs.Flash['onEvent'] = function (swfID, eventName) {
  4935. var player = vjs.el(swfID)['player'];
  4936. player.trigger(eventName);
  4937. };
  4938. // Log errors from the swf
  4939. vjs.Flash['onError'] = function (swfID, err) {
  4940. var player = vjs.el(swfID)['player'];
  4941. player.trigger('error');
  4942. vjs.log('Flash Error', err, swfID);
  4943. };
  4944. // Flash Version Check
  4945. vjs.Flash.version = function () {
  4946. var version = '0,0,0';
  4947. // IE
  4948. try {
  4949. version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
  4950. // other browsers
  4951. } catch (e) {
  4952. try {
  4953. if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) {
  4954. version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
  4955. }
  4956. } catch (err) { }
  4957. }
  4958. return version.split(',');
  4959. };
  4960. // Flash embedding method. Only used in non-iframe mode
  4961. vjs.Flash.embed = function (swf, placeHolder, flashVars, params, attributes) {
  4962. var code = vjs.Flash.getEmbedCode(swf, flashVars, params, attributes),
  4963. // Get element by embedding code and retrieving created element
  4964. obj = vjs.createEl('div', { innerHTML: code }).childNodes[0],
  4965. par = placeHolder.parentNode
  4966. ;
  4967. placeHolder.parentNode.replaceChild(obj, placeHolder);
  4968. // IE6 seems to have an issue where it won't initialize the swf object after injecting it.
  4969. // This is a dumb fix
  4970. var newObj = par.childNodes[0];
  4971. setTimeout(function () {
  4972. newObj.style.display = 'block';
  4973. }, 1000);
  4974. return obj;
  4975. };
  4976. vjs.Flash.getEmbedCode = function (swf, flashVars, params, attributes) {
  4977. var objTag = '<object type="application/x-shockwave-flash"',
  4978. flashVarsString = '',
  4979. paramsString = '',
  4980. attrsString = '';
  4981. // Convert flash vars to string
  4982. if (flashVars) {
  4983. vjs.obj.each(flashVars, function (key, val) {
  4984. flashVarsString += (key + '=' + val + '&amp;');
  4985. });
  4986. }
  4987. // Add swf, flashVars, and other default params
  4988. params = vjs.obj.merge({
  4989. 'movie': swf,
  4990. 'flashvars': flashVarsString,
  4991. 'allowScriptAccess': 'always', // Required to talk to swf
  4992. 'allowNetworking': 'all' // All should be default, but having security issues.
  4993. }, params);
  4994. // Create param tags string
  4995. vjs.obj.each(params, function (key, val) {
  4996. paramsString += '<param name="' + key + '" value="' + val + '" />';
  4997. });
  4998. attributes = vjs.obj.merge({
  4999. // Add swf to attributes (need both for IE and Others to work)
  5000. 'data': swf,
  5001. // Default to 100% width/height
  5002. 'width': '100%',
  5003. 'height': '100%'
  5004. }, attributes);
  5005. // Create Attributes string
  5006. vjs.obj.each(attributes, function (key, val) {
  5007. attrsString += (key + '="' + val + '" ');
  5008. });
  5009. return objTag + attrsString + '>' + paramsString + '</object>';
  5010. };
  5011. vjs.Flash.streamFromParts = function (connection, stream) {
  5012. return connection + '&' + stream;
  5013. };
  5014. vjs.Flash.streamToParts = function (src) {
  5015. var parts = {
  5016. connection: '',
  5017. stream: ''
  5018. };
  5019. if (!src) {
  5020. return parts;
  5021. }
  5022. // Look for the normal URL separator we expect, '&'.
  5023. // If found, we split the URL into two pieces around the
  5024. // first '&'.
  5025. var connEnd = src.indexOf('&');
  5026. var streamBegin;
  5027. if (connEnd !== -1) {
  5028. streamBegin = connEnd + 1;
  5029. }
  5030. else {
  5031. // If there's not a '&', we use the last '/' as the delimiter.
  5032. connEnd = streamBegin = src.lastIndexOf('/') + 1;
  5033. if (connEnd === 0) {
  5034. // really, there's not a '/'?
  5035. connEnd = streamBegin = src.length;
  5036. }
  5037. }
  5038. parts.connection = src.substring(0, connEnd);
  5039. parts.stream = src.substring(streamBegin, src.length);
  5040. return parts;
  5041. };
  5042. vjs.Flash.isStreamingType = function (srcType) {
  5043. return srcType in vjs.Flash.streamingFormats;
  5044. };
  5045. // RTMP has four variations, any string starting
  5046. // with one of these protocols should be valid
  5047. vjs.Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
  5048. vjs.Flash.isStreamingSrc = function (src) {
  5049. return vjs.Flash.RTMP_RE.test(src);
  5050. };
  5051. /**
  5052. * The Media Loader is the component that decides which playback technology to load
  5053. * when the player is initialized.
  5054. *
  5055. * @constructor
  5056. */
  5057. vjs.MediaLoader = vjs.Component.extend({
  5058. /** @constructor */
  5059. init: function (player, options, ready) {
  5060. vjs.Component.call(this, player, options, ready);
  5061. // If there are no sources when the player is initialized,
  5062. // load the first supported playback technology.
  5063. if (!player.options_['sources'] || player.options_['sources'].length === 0) {
  5064. for (var i = 0, j = player.options_['techOrder']; i < j.length; i++) {
  5065. var techName = vjs.capitalize(j[i]),
  5066. tech = window['videojs'][techName];
  5067. // Check if the browser supports this technology
  5068. if (tech && tech.isSupported()) {
  5069. player.loadTech(techName);
  5070. break;
  5071. }
  5072. }
  5073. } else {
  5074. // // Loop through playback technologies (HTML5, Flash) and check for support.
  5075. // // Then load the best source.
  5076. // // A few assumptions here:
  5077. // // All playback technologies respect preload false.
  5078. player.src(player.options_['sources']);
  5079. }
  5080. }
  5081. });
  5082. /**
  5083. * @fileoverview Text Tracks
  5084. * Text tracks are tracks of timed text events.
  5085. * Captions - text displayed over the video for the hearing impared
  5086. * Subtitles - text displayed over the video for those who don't understand langauge in the video
  5087. * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
  5088. * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
  5089. */
  5090. // Player Additions - Functions add to the player object for easier access to tracks
  5091. /**
  5092. * List of associated text tracks
  5093. * @type {Array}
  5094. * @private
  5095. */
  5096. vjs.Player.prototype.textTracks_;
  5097. /**
  5098. * Get an array of associated text tracks. captions, subtitles, chapters, descriptions
  5099. * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
  5100. * @return {Array} Array of track objects
  5101. * @private
  5102. */
  5103. vjs.Player.prototype.textTracks = function () {
  5104. this.textTracks_ = this.textTracks_ || [];
  5105. return this.textTracks_;
  5106. };
  5107. /**
  5108. * Add a text track
  5109. * In addition to the W3C settings we allow adding additional info through options.
  5110. * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
  5111. * @param {String} kind Captions, subtitles, chapters, descriptions, or metadata
  5112. * @param {String=} label Optional label
  5113. * @param {String=} language Optional language
  5114. * @param {Object=} options Additional track options, like src
  5115. * @private
  5116. */
  5117. vjs.Player.prototype.addTextTrack = function (kind, label, language, options) {
  5118. var tracks = this.textTracks_ = this.textTracks_ || [];
  5119. options = options || {};
  5120. options['kind'] = kind;
  5121. options['label'] = label;
  5122. options['language'] = language;
  5123. // HTML5 Spec says default to subtitles.
  5124. // Uppercase first letter to match class names
  5125. var Kind = vjs.capitalize(kind || 'subtitles');
  5126. // Create correct texttrack class. CaptionsTrack, etc.
  5127. var track = new window['videojs'][Kind + 'Track'](this, options);
  5128. tracks.push(track);
  5129. // If track.dflt() is set, start showing immediately
  5130. // TODO: Add a process to deterime the best track to show for the specific kind
  5131. // Incase there are mulitple defaulted tracks of the same kind
  5132. // Or the user has a set preference of a specific language that should override the default
  5133. // if (track.dflt()) {
  5134. // this.ready(vjs.bind(track, track.show));
  5135. // }
  5136. return track;
  5137. };
  5138. /**
  5139. * Add an array of text tracks. captions, subtitles, chapters, descriptions
  5140. * Track objects will be stored in the player.textTracks() array
  5141. * @param {Array} trackList Array of track elements or objects (fake track elements)
  5142. * @private
  5143. */
  5144. vjs.Player.prototype.addTextTracks = function (trackList) {
  5145. var trackObj;
  5146. for (var i = 0; i < trackList.length; i++) {
  5147. trackObj = trackList[i];
  5148. this.addTextTrack(trackObj['kind'], trackObj['label'], trackObj['language'], trackObj);
  5149. }
  5150. return this;
  5151. };
  5152. // Show a text track
  5153. // disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)
  5154. vjs.Player.prototype.showTextTrack = function (id, disableSameKind) {
  5155. var tracks = this.textTracks_,
  5156. i = 0,
  5157. j = tracks.length,
  5158. track, showTrack, kind;
  5159. // Find Track with same ID
  5160. for (; i < j; i++) {
  5161. track = tracks[i];
  5162. if (track.id() === id) {
  5163. track.show();
  5164. showTrack = track;
  5165. // Disable tracks of the same kind
  5166. } else if (disableSameKind && track.kind() == disableSameKind && track.mode() > 0) {
  5167. track.disable();
  5168. }
  5169. }
  5170. // Get track kind from shown track or disableSameKind
  5171. kind = (showTrack) ? showTrack.kind() : ((disableSameKind) ? disableSameKind : false);
  5172. // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.
  5173. if (kind) {
  5174. this.trigger(kind + 'trackchange');
  5175. }
  5176. return this;
  5177. };
  5178. /**
  5179. * The base class for all text tracks
  5180. *
  5181. * Handles the parsing, hiding, and showing of text track cues
  5182. *
  5183. * @param {vjs.Player|Object} player
  5184. * @param {Object=} options
  5185. * @constructor
  5186. */
  5187. vjs.TextTrack = vjs.Component.extend({
  5188. /** @constructor */
  5189. init: function (player, options) {
  5190. vjs.Component.call(this, player, options);
  5191. // Apply track info to track object
  5192. // Options will often be a track element
  5193. // Build ID if one doesn't exist
  5194. this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);
  5195. this.src_ = options['src'];
  5196. // 'default' is a reserved keyword in js so we use an abbreviated version
  5197. this.dflt_ = options['default'] || options['dflt'];
  5198. this.title_ = options['title'];
  5199. this.language_ = options['srclang'];
  5200. this.label_ = options['label'];
  5201. this.cues_ = [];
  5202. this.activeCues_ = [];
  5203. this.readyState_ = 0;
  5204. this.mode_ = 0;
  5205. this.player_.on('fullscreenchange', vjs.bind(this, this.adjustFontSize));
  5206. }
  5207. });
  5208. /**
  5209. * Track kind value. Captions, subtitles, etc.
  5210. * @private
  5211. */
  5212. vjs.TextTrack.prototype.kind_;
  5213. /**
  5214. * Get the track kind value
  5215. * @return {String}
  5216. */
  5217. vjs.TextTrack.prototype.kind = function () {
  5218. return this.kind_;
  5219. };
  5220. /**
  5221. * Track src value
  5222. * @private
  5223. */
  5224. vjs.TextTrack.prototype.src_;
  5225. /**
  5226. * Get the track src value
  5227. * @return {String}
  5228. */
  5229. vjs.TextTrack.prototype.src = function () {
  5230. return this.src_;
  5231. };
  5232. /**
  5233. * Track default value
  5234. * If default is used, subtitles/captions to start showing
  5235. * @private
  5236. */
  5237. vjs.TextTrack.prototype.dflt_;
  5238. /**
  5239. * Get the track default value. ('default' is a reserved keyword)
  5240. * @return {Boolean}
  5241. */
  5242. vjs.TextTrack.prototype.dflt = function () {
  5243. return this.dflt_;
  5244. };
  5245. /**
  5246. * Track title value
  5247. * @private
  5248. */
  5249. vjs.TextTrack.prototype.title_;
  5250. /**
  5251. * Get the track title value
  5252. * @return {String}
  5253. */
  5254. vjs.TextTrack.prototype.title = function () {
  5255. return this.title_;
  5256. };
  5257. /**
  5258. * Language - two letter string to represent track language, e.g. 'en' for English
  5259. * Spec def: readonly attribute DOMString language;
  5260. * @private
  5261. */
  5262. vjs.TextTrack.prototype.language_;
  5263. /**
  5264. * Get the track language value
  5265. * @return {String}
  5266. */
  5267. vjs.TextTrack.prototype.language = function () {
  5268. return this.language_;
  5269. };
  5270. /**
  5271. * Track label e.g. 'English'
  5272. * Spec def: readonly attribute DOMString label;
  5273. * @private
  5274. */
  5275. vjs.TextTrack.prototype.label_;
  5276. /**
  5277. * Get the track label value
  5278. * @return {String}
  5279. */
  5280. vjs.TextTrack.prototype.label = function () {
  5281. return this.label_;
  5282. };
  5283. /**
  5284. * All cues of the track. Cues have a startTime, endTime, text, and other properties.
  5285. * Spec def: readonly attribute TextTrackCueList cues;
  5286. * @private
  5287. */
  5288. vjs.TextTrack.prototype.cues_;
  5289. /**
  5290. * Get the track cues
  5291. * @return {Array}
  5292. */
  5293. vjs.TextTrack.prototype.cues = function () {
  5294. return this.cues_;
  5295. };
  5296. /**
  5297. * ActiveCues is all cues that are currently showing
  5298. * Spec def: readonly attribute TextTrackCueList activeCues;
  5299. * @private
  5300. */
  5301. vjs.TextTrack.prototype.activeCues_;
  5302. /**
  5303. * Get the track active cues
  5304. * @return {Array}
  5305. */
  5306. vjs.TextTrack.prototype.activeCues = function () {
  5307. return this.activeCues_;
  5308. };
  5309. /**
  5310. * ReadyState describes if the text file has been loaded
  5311. * const unsigned short NONE = 0;
  5312. * const unsigned short LOADING = 1;
  5313. * const unsigned short LOADED = 2;
  5314. * const unsigned short ERROR = 3;
  5315. * readonly attribute unsigned short readyState;
  5316. * @private
  5317. */
  5318. vjs.TextTrack.prototype.readyState_;
  5319. /**
  5320. * Get the track readyState
  5321. * @return {Number}
  5322. */
  5323. vjs.TextTrack.prototype.readyState = function () {
  5324. return this.readyState_;
  5325. };
  5326. /**
  5327. * Mode describes if the track is showing, hidden, or disabled
  5328. * const unsigned short OFF = 0;
  5329. * const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)
  5330. * const unsigned short SHOWING = 2;
  5331. * attribute unsigned short mode;
  5332. * @private
  5333. */
  5334. vjs.TextTrack.prototype.mode_;
  5335. /**
  5336. * Get the track mode
  5337. * @return {Number}
  5338. */
  5339. vjs.TextTrack.prototype.mode = function () {
  5340. return this.mode_;
  5341. };
  5342. /**
  5343. * Change the font size of the text track to make it larger when playing in fullscreen mode
  5344. * and restore it to its normal size when not in fullscreen mode.
  5345. */
  5346. vjs.TextTrack.prototype.adjustFontSize = function () {
  5347. if (this.player_.isFullScreen) {
  5348. // Scale the font by the same factor as increasing the video width to the full screen window width.
  5349. // Additionally, multiply that factor by 1.4, which is the default font size for
  5350. // the caption track (from the CSS)
  5351. this.el_.style.fontSize = screen.width / this.player_.width() * 1.4 * 100 + '%';
  5352. } else {
  5353. // Change the font size of the text track back to its original non-fullscreen size
  5354. this.el_.style.fontSize = '';
  5355. }
  5356. };
  5357. /**
  5358. * Create basic div to hold cue text
  5359. * @return {Element}
  5360. */
  5361. vjs.TextTrack.prototype.createEl = function () {
  5362. return vjs.Component.prototype.createEl.call(this, 'div', {
  5363. className: 'vjs-' + this.kind_ + ' vjs-text-track'
  5364. });
  5365. };
  5366. /**
  5367. * Show: Mode Showing (2)
  5368. * 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.
  5369. * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
  5370. * In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;
  5371. * for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a non-visual fashion;
  5372. * 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.
  5373. * 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.
  5374. * This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.
  5375. */
  5376. vjs.TextTrack.prototype.show = function () {
  5377. this.activate();
  5378. this.mode_ = 2;
  5379. // Show element.
  5380. vjs.Component.prototype.show.call(this);
  5381. };
  5382. /**
  5383. * Hide: Mode Hidden (1)
  5384. * Indicates that the text track is active, but that the user agent is not actively displaying the cues.
  5385. * If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
  5386. * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
  5387. */
  5388. vjs.TextTrack.prototype.hide = function () {
  5389. // When hidden, cues are still triggered. Disable to stop triggering.
  5390. this.activate();
  5391. this.mode_ = 1;
  5392. // Hide element.
  5393. vjs.Component.prototype.hide.call(this);
  5394. };
  5395. /**
  5396. * Disable: Mode Off/Disable (0)
  5397. * 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.
  5398. * No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.
  5399. */
  5400. vjs.TextTrack.prototype.disable = function () {
  5401. // If showing, hide.
  5402. if (this.mode_ == 2) { this.hide(); }
  5403. // Stop triggering cues
  5404. this.deactivate();
  5405. // Switch Mode to Off
  5406. this.mode_ = 0;
  5407. };
  5408. /**
  5409. * Turn on cue tracking. Tracks that are showing OR hidden are active.
  5410. */
  5411. vjs.TextTrack.prototype.activate = function () {
  5412. // Load text file if it hasn't been yet.
  5413. if (this.readyState_ === 0) { this.load(); }
  5414. // Only activate if not already active.
  5415. if (this.mode_ === 0) {
  5416. // Update current cue on timeupdate
  5417. // Using unique ID for bind function so other tracks don't remove listener
  5418. this.player_.on('timeupdate', vjs.bind(this, this.update, this.id_));
  5419. // Reset cue time on media end
  5420. this.player_.on('ended', vjs.bind(this, this.reset, this.id_));
  5421. // Add to display
  5422. if (this.kind_ === 'captions' || this.kind_ === 'subtitles') {
  5423. this.player_.getChild('textTrackDisplay').addChild(this);
  5424. }
  5425. }
  5426. };
  5427. /**
  5428. * Turn off cue tracking.
  5429. */
  5430. vjs.TextTrack.prototype.deactivate = function () {
  5431. // Using unique ID for bind function so other tracks don't remove listener
  5432. this.player_.off('timeupdate', vjs.bind(this, this.update, this.id_));
  5433. this.player_.off('ended', vjs.bind(this, this.reset, this.id_));
  5434. this.reset(); // Reset
  5435. // Remove from display
  5436. this.player_.getChild('textTrackDisplay').removeChild(this);
  5437. };
  5438. // A readiness state
  5439. // One of the following:
  5440. //
  5441. // Not loaded
  5442. // 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.
  5443. //
  5444. // Loading
  5445. // 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.
  5446. //
  5447. // Loaded
  5448. // 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.
  5449. //
  5450. // Failed to load
  5451. // 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.
  5452. vjs.TextTrack.prototype.load = function () {
  5453. // Only load if not loaded yet.
  5454. if (this.readyState_ === 0) {
  5455. this.readyState_ = 1;
  5456. vjs.get(this.src_, vjs.bind(this, this.parseCues), vjs.bind(this, this.onError));
  5457. }
  5458. };
  5459. vjs.TextTrack.prototype.onError = function (err) {
  5460. this.error = err;
  5461. this.readyState_ = 3;
  5462. this.trigger('error');
  5463. };
  5464. // Parse the WebVTT text format for cue times.
  5465. // TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)
  5466. vjs.TextTrack.prototype.parseCues = function (srcContent) {
  5467. var cue, time, text,
  5468. lines = srcContent.split('\n'),
  5469. line = '', id;
  5470. for (var i = 1, j = lines.length; i < j; i++) {
  5471. // Line 0 should be 'WEBVTT', so skipping i=0
  5472. line = vjs.trim(lines[i]); // Trim whitespace and linebreaks
  5473. if (line) { // Loop until a line with content
  5474. // First line could be an optional cue ID
  5475. // Check if line has the time separator
  5476. if (line.indexOf('-->') == -1) {
  5477. id = line;
  5478. // Advance to next line for timing.
  5479. line = vjs.trim(lines[++i]);
  5480. } else {
  5481. id = this.cues_.length;
  5482. }
  5483. // First line - Number
  5484. cue = {
  5485. id: id, // Cue Number
  5486. index: this.cues_.length // Position in Array
  5487. };
  5488. // Timing line
  5489. time = line.split(' --> ');
  5490. cue.startTime = this.parseCueTime(time[0]);
  5491. cue.endTime = this.parseCueTime(time[1]);
  5492. // Additional lines - Cue Text
  5493. text = [];
  5494. // Loop until a blank line or end of lines
  5495. // Assumeing trim('') returns false for blank lines
  5496. while (lines[++i] && (line = vjs.trim(lines[i]))) {
  5497. text.push(line);
  5498. }
  5499. cue.text = text.join('<br/>');
  5500. // Add this cue
  5501. this.cues_.push(cue);
  5502. }
  5503. }
  5504. this.readyState_ = 2;
  5505. this.trigger('loaded');
  5506. };
  5507. vjs.TextTrack.prototype.parseCueTime = function (timeText) {
  5508. var parts = timeText.split(':'),
  5509. time = 0,
  5510. hours, minutes, other, seconds, ms;
  5511. // Check if optional hours place is included
  5512. // 00:00:00.000 vs. 00:00.000
  5513. if (parts.length == 3) {
  5514. hours = parts[0];
  5515. minutes = parts[1];
  5516. other = parts[2];
  5517. } else {
  5518. hours = 0;
  5519. minutes = parts[0];
  5520. other = parts[1];
  5521. }
  5522. // Break other (seconds, milliseconds, and flags) by spaces
  5523. // TODO: Make additional cue layout settings work with flags
  5524. other = other.split(/\s+/);
  5525. // Remove seconds. Seconds is the first part before any spaces.
  5526. seconds = other.splice(0, 1)[0];
  5527. // Could use either . or , for decimal
  5528. seconds = seconds.split(/\.|,/);
  5529. // Get milliseconds
  5530. ms = parseFloat(seconds[1]);
  5531. seconds = seconds[0];
  5532. // hours => seconds
  5533. time += parseFloat(hours) * 3600;
  5534. // minutes => seconds
  5535. time += parseFloat(minutes) * 60;
  5536. // Add seconds
  5537. time += parseFloat(seconds);
  5538. // Add milliseconds
  5539. if (ms) { time += ms / 1000; }
  5540. return time;
  5541. };
  5542. // Update active cues whenever timeupdate events are triggered on the player.
  5543. vjs.TextTrack.prototype.update = function () {
  5544. if (this.cues_.length > 0) {
  5545. // Get curent player time
  5546. var time = this.player_.currentTime();
  5547. // Check if the new time is outside the time box created by the the last update.
  5548. if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {
  5549. var cues = this.cues_,
  5550. // Create a new time box for this state.
  5551. newNextChange = this.player_.duration(), // Start at beginning of the timeline
  5552. newPrevChange = 0, // Start at end
  5553. reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.
  5554. newCues = [], // Store new active cues.
  5555. // Store where in the loop the current active cues are, to provide a smart starting point for the next loop.
  5556. firstActiveIndex, lastActiveIndex,
  5557. cue, i; // Loop vars
  5558. // Check if time is going forwards or backwards (scrubbing/rewinding)
  5559. // If we know the direction we can optimize the starting position and direction of the loop through the cues array.
  5560. if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen
  5561. // Forwards, so start at the index of the first active cue and loop forward
  5562. i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;
  5563. } else {
  5564. // Backwards, so start at the index of the last active cue and loop backward
  5565. reverse = true;
  5566. i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;
  5567. }
  5568. while (true) { // Loop until broken
  5569. cue = cues[i];
  5570. // Cue ended at this point
  5571. if (cue.endTime <= time) {
  5572. newPrevChange = Math.max(newPrevChange, cue.endTime);
  5573. if (cue.active) {
  5574. cue.active = false;
  5575. }
  5576. // No earlier cues should have an active start time.
  5577. // Nevermind. Assume first cue could have a duration the same as the video.
  5578. // In that case we need to loop all the way back to the beginning.
  5579. // if (reverse && cue.startTime) { break; }
  5580. // Cue hasn't started
  5581. } else if (time < cue.startTime) {
  5582. newNextChange = Math.min(newNextChange, cue.startTime);
  5583. if (cue.active) {
  5584. cue.active = false;
  5585. }
  5586. // No later cues should have an active start time.
  5587. if (!reverse) { break; }
  5588. // Cue is current
  5589. } else {
  5590. if (reverse) {
  5591. // Add cue to front of array to keep in time order
  5592. newCues.splice(0, 0, cue);
  5593. // If in reverse, the first current cue is our lastActiveCue
  5594. if (lastActiveIndex === undefined) { lastActiveIndex = i; }
  5595. firstActiveIndex = i;
  5596. } else {
  5597. // Add cue to end of array
  5598. newCues.push(cue);
  5599. // If forward, the first current cue is our firstActiveIndex
  5600. if (firstActiveIndex === undefined) { firstActiveIndex = i; }
  5601. lastActiveIndex = i;
  5602. }
  5603. newNextChange = Math.min(newNextChange, cue.endTime);
  5604. newPrevChange = Math.max(newPrevChange, cue.startTime);
  5605. cue.active = true;
  5606. }
  5607. if (reverse) {
  5608. // Reverse down the array of cues, break if at first
  5609. if (i === 0) { break; } else { i--; }
  5610. } else {
  5611. // Walk up the array fo cues, break if at last
  5612. if (i === cues.length - 1) { break; } else { i++; }
  5613. }
  5614. }
  5615. this.activeCues_ = newCues;
  5616. this.nextChange = newNextChange;
  5617. this.prevChange = newPrevChange;
  5618. this.firstActiveIndex = firstActiveIndex;
  5619. this.lastActiveIndex = lastActiveIndex;
  5620. this.updateDisplay();
  5621. this.trigger('cuechange');
  5622. }
  5623. }
  5624. };
  5625. // Add cue HTML to display
  5626. vjs.TextTrack.prototype.updateDisplay = function () {
  5627. var cues = this.activeCues_,
  5628. html = '',
  5629. i = 0, j = cues.length;
  5630. for (; i < j; i++) {
  5631. html += '<span class="vjs-tt-cue">' + cues[i].text + '</span>';
  5632. }
  5633. this.el_.innerHTML = html;
  5634. };
  5635. // Set all loop helper values back
  5636. vjs.TextTrack.prototype.reset = function () {
  5637. this.nextChange = 0;
  5638. this.prevChange = this.player_.duration();
  5639. this.firstActiveIndex = 0;
  5640. this.lastActiveIndex = 0;
  5641. };
  5642. // Create specific track types
  5643. /**
  5644. * The track component for managing the hiding and showing of captions
  5645. *
  5646. * @constructor
  5647. */
  5648. vjs.CaptionsTrack = vjs.TextTrack.extend();
  5649. vjs.CaptionsTrack.prototype.kind_ = 'captions';
  5650. // Exporting here because Track creation requires the track kind
  5651. // to be available on global object. e.g. new window['videojs'][Kind + 'Track']
  5652. /**
  5653. * The track component for managing the hiding and showing of subtitles
  5654. *
  5655. * @constructor
  5656. */
  5657. vjs.SubtitlesTrack = vjs.TextTrack.extend();
  5658. vjs.SubtitlesTrack.prototype.kind_ = 'subtitles';
  5659. /**
  5660. * The track component for managing the hiding and showing of chapters
  5661. *
  5662. * @constructor
  5663. */
  5664. vjs.ChaptersTrack = vjs.TextTrack.extend();
  5665. vjs.ChaptersTrack.prototype.kind_ = 'chapters';
  5666. /* Text Track Display
  5667. ============================================================================= */
  5668. // Global container for both subtitle and captions text. Simple div container.
  5669. /**
  5670. * The component for displaying text track cues
  5671. *
  5672. * @constructor
  5673. */
  5674. vjs.TextTrackDisplay = vjs.Component.extend({
  5675. /** @constructor */
  5676. init: function (player, options, ready) {
  5677. vjs.Component.call(this, player, options, ready);
  5678. // This used to be called during player init, but was causing an error
  5679. // if a track should show by default and the display hadn't loaded yet.
  5680. // Should probably be moved to an external track loader when we support
  5681. // tracks that don't need a display.
  5682. if (player.options_['tracks'] && player.options_['tracks'].length > 0) {
  5683. this.player_.addTextTracks(player.options_['tracks']);
  5684. }
  5685. }
  5686. });
  5687. vjs.TextTrackDisplay.prototype.createEl = function () {
  5688. return vjs.Component.prototype.createEl.call(this, 'div', {
  5689. className: 'vjs-text-track-display'
  5690. });
  5691. };
  5692. /**
  5693. * The specific menu item type for selecting a language within a text track kind
  5694. *
  5695. * @constructor
  5696. */
  5697. vjs.TextTrackMenuItem = vjs.MenuItem.extend({
  5698. /** @constructor */
  5699. init: function (player, options) {
  5700. var track = this.track = options['track'];
  5701. // Modify options for parent MenuItem class's init.
  5702. options['label'] = track.label();
  5703. options['selected'] = track.dflt();
  5704. vjs.MenuItem.call(this, player, options);
  5705. this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));
  5706. }
  5707. });
  5708. vjs.TextTrackMenuItem.prototype.onClick = function () {
  5709. vjs.MenuItem.prototype.onClick.call(this);
  5710. this.player_.showTextTrack(this.track.id_, this.track.kind());
  5711. };
  5712. vjs.TextTrackMenuItem.prototype.update = function () {
  5713. this.selected(this.track.mode() == 2);
  5714. };
  5715. /**
  5716. * A special menu item for turning of a specific type of text track
  5717. *
  5718. * @constructor
  5719. */
  5720. vjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({
  5721. /** @constructor */
  5722. init: function (player, options) {
  5723. // Create pseudo track info
  5724. // Requires options['kind']
  5725. options['track'] = {
  5726. kind: function () { return options['kind']; },
  5727. player: player,
  5728. label: function () { return options['kind'] + ' off'; },
  5729. dflt: function () { return false; },
  5730. mode: function () { return false; }
  5731. };
  5732. vjs.TextTrackMenuItem.call(this, player, options);
  5733. this.selected(true);
  5734. }
  5735. });
  5736. vjs.OffTextTrackMenuItem.prototype.onClick = function () {
  5737. vjs.TextTrackMenuItem.prototype.onClick.call(this);
  5738. this.player_.showTextTrack(this.track.id_, this.track.kind());
  5739. };
  5740. vjs.OffTextTrackMenuItem.prototype.update = function () {
  5741. var tracks = this.player_.textTracks(),
  5742. i = 0, j = tracks.length, track,
  5743. off = true;
  5744. for (; i < j; i++) {
  5745. track = tracks[i];
  5746. if (track.kind() == this.track.kind() && track.mode() == 2) {
  5747. off = false;
  5748. }
  5749. }
  5750. this.selected(off);
  5751. };
  5752. /**
  5753. * The base class for buttons that toggle specific text track types (e.g. subtitles)
  5754. *
  5755. * @constructor
  5756. */
  5757. vjs.TextTrackButton = vjs.MenuButton.extend({
  5758. /** @constructor */
  5759. init: function (player, options) {
  5760. vjs.MenuButton.call(this, player, options);
  5761. if (this.items.length <= 1) {
  5762. this.hide();
  5763. }
  5764. }
  5765. });
  5766. // vjs.TextTrackButton.prototype.buttonPressed = false;
  5767. // vjs.TextTrackButton.prototype.createMenu = function(){
  5768. // var menu = new vjs.Menu(this.player_);
  5769. // // Add a title list item to the top
  5770. // // menu.el().appendChild(vjs.createEl('li', {
  5771. // // className: 'vjs-menu-title',
  5772. // // innerHTML: vjs.capitalize(this.kind_),
  5773. // // tabindex: -1
  5774. // // }));
  5775. // this.items = this.createItems();
  5776. // // Add menu items to the menu
  5777. // for (var i = 0; i < this.items.length; i++) {
  5778. // menu.addItem(this.items[i]);
  5779. // }
  5780. // // Add list to element
  5781. // this.addChild(menu);
  5782. // return menu;
  5783. // };
  5784. // Create a menu item for each text track
  5785. vjs.TextTrackButton.prototype.createItems = function () {
  5786. var items = [], track;
  5787. // Add an OFF menu item to turn all tracks off
  5788. items.push(new vjs.OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));
  5789. for (var i = 0; i < this.player_.textTracks().length; i++) {
  5790. track = this.player_.textTracks()[i];
  5791. if (track.kind() === this.kind_) {
  5792. items.push(new vjs.TextTrackMenuItem(this.player_, {
  5793. 'track': track
  5794. }));
  5795. }
  5796. }
  5797. return items;
  5798. };
  5799. /**
  5800. * The button component for toggling and selecting captions
  5801. *
  5802. * @constructor
  5803. */
  5804. vjs.CaptionsButton = vjs.TextTrackButton.extend({
  5805. /** @constructor */
  5806. init: function (player, options, ready) {
  5807. vjs.TextTrackButton.call(this, player, options, ready);
  5808. this.el_.setAttribute('aria-label', 'Captions Menu');
  5809. }
  5810. });
  5811. vjs.CaptionsButton.prototype.kind_ = 'captions';
  5812. vjs.CaptionsButton.prototype.buttonText = 'Captions';
  5813. vjs.CaptionsButton.prototype.className = 'vjs-captions-button';
  5814. /**
  5815. * The button component for toggling and selecting subtitles
  5816. *
  5817. * @constructor
  5818. */
  5819. vjs.SubtitlesButton = vjs.TextTrackButton.extend({
  5820. /** @constructor */
  5821. init: function (player, options, ready) {
  5822. vjs.TextTrackButton.call(this, player, options, ready);
  5823. this.el_.setAttribute('aria-label', 'Subtitles Menu');
  5824. }
  5825. });
  5826. vjs.SubtitlesButton.prototype.kind_ = 'subtitles';
  5827. vjs.SubtitlesButton.prototype.buttonText = 'Subtitles';
  5828. vjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';
  5829. // Chapters act much differently than other text tracks
  5830. // Cues are navigation vs. other tracks of alternative languages
  5831. /**
  5832. * The button component for toggling and selecting chapters
  5833. *
  5834. * @constructor
  5835. */
  5836. vjs.ChaptersButton = vjs.TextTrackButton.extend({
  5837. /** @constructor */
  5838. init: function (player, options, ready) {
  5839. vjs.TextTrackButton.call(this, player, options, ready);
  5840. this.el_.setAttribute('aria-label', 'Chapters Menu');
  5841. }
  5842. });
  5843. vjs.ChaptersButton.prototype.kind_ = 'chapters';
  5844. vjs.ChaptersButton.prototype.buttonText = 'Chapters';
  5845. vjs.ChaptersButton.prototype.className = 'vjs-chapters-button';
  5846. // Create a menu item for each text track
  5847. vjs.ChaptersButton.prototype.createItems = function () {
  5848. var items = [], track;
  5849. for (var i = 0; i < this.player_.textTracks().length; i++) {
  5850. track = this.player_.textTracks()[i];
  5851. if (track.kind() === this.kind_) {
  5852. items.push(new vjs.TextTrackMenuItem(this.player_, {
  5853. 'track': track
  5854. }));
  5855. }
  5856. }
  5857. return items;
  5858. };
  5859. vjs.ChaptersButton.prototype.createMenu = function () {
  5860. var tracks = this.player_.textTracks(),
  5861. i = 0,
  5862. j = tracks.length,
  5863. track, chaptersTrack,
  5864. items = this.items = [];
  5865. for (; i < j; i++) {
  5866. track = tracks[i];
  5867. if (track.kind() == this.kind_ && track.dflt()) {
  5868. if (track.readyState() < 2) {
  5869. this.chaptersTrack = track;
  5870. track.on('loaded', vjs.bind(this, this.createMenu));
  5871. return;
  5872. } else {
  5873. chaptersTrack = track;
  5874. break;
  5875. }
  5876. }
  5877. }
  5878. var menu = this.menu = new vjs.Menu(this.player_);
  5879. menu.el_.appendChild(vjs.createEl('li', {
  5880. className: 'vjs-menu-title',
  5881. innerHTML: vjs.capitalize(this.kind_),
  5882. tabindex: -1
  5883. }));
  5884. if (chaptersTrack) {
  5885. var cues = chaptersTrack.cues_, cue, mi;
  5886. i = 0;
  5887. j = cues.length;
  5888. for (; i < j; i++) {
  5889. cue = cues[i];
  5890. mi = new vjs.ChaptersTrackMenuItem(this.player_, {
  5891. 'track': chaptersTrack,
  5892. 'cue': cue
  5893. });
  5894. items.push(mi);
  5895. menu.addChild(mi);
  5896. }
  5897. }
  5898. if (this.items.length > 0) {
  5899. this.show();
  5900. }
  5901. return menu;
  5902. };
  5903. /**
  5904. * @constructor
  5905. */
  5906. vjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({
  5907. /** @constructor */
  5908. init: function (player, options) {
  5909. var track = this.track = options['track'],
  5910. cue = this.cue = options['cue'],
  5911. currentTime = player.currentTime();
  5912. // Modify options for parent MenuItem class's init.
  5913. options['label'] = cue.text;
  5914. options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);
  5915. vjs.MenuItem.call(this, player, options);
  5916. track.on('cuechange', vjs.bind(this, this.update));
  5917. }
  5918. });
  5919. vjs.ChaptersTrackMenuItem.prototype.onClick = function () {
  5920. vjs.MenuItem.prototype.onClick.call(this);
  5921. this.player_.currentTime(this.cue.startTime);
  5922. this.update(this.cue.startTime);
  5923. };
  5924. vjs.ChaptersTrackMenuItem.prototype.update = function () {
  5925. var cue = this.cue,
  5926. currentTime = this.player_.currentTime();
  5927. // vjs.log(currentTime, cue.startTime);
  5928. this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
  5929. };
  5930. // Add Buttons to controlBar
  5931. vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
  5932. 'subtitlesButton': {},
  5933. 'captionsButton': {},
  5934. 'chaptersButton': {}
  5935. });
  5936. // vjs.Cue = vjs.Component.extend({
  5937. // /** @constructor */
  5938. // init: function(player, options){
  5939. // vjs.Component.call(this, player, options);
  5940. // }
  5941. // });
  5942. /**
  5943. * @fileoverview Add JSON support
  5944. * @suppress {undefinedVars}
  5945. * (Compiler doesn't like JSON not being declared)
  5946. */
  5947. /**
  5948. * Javascript JSON implementation
  5949. * (Parse Method Only)
  5950. * https://github.com/douglascrockford/JSON-js/blob/master/json2.js
  5951. * Only using for parse method when parsing data-setup attribute JSON.
  5952. * @suppress {undefinedVars}
  5953. * @namespace
  5954. * @private
  5955. */
  5956. vjs.JSON;
  5957. if (typeof window.JSON !== 'undefined' && window.JSON.parse === 'function') {
  5958. vjs.JSON = window.JSON;
  5959. } else {
  5960. vjs.JSON = {};
  5961. var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
  5962. /**
  5963. * parse the json
  5964. *
  5965. * @memberof vjs.JSON
  5966. * @return {Object|Array} The parsed JSON
  5967. */
  5968. vjs.JSON.parse = function (text, reviver) {
  5969. var j;
  5970. function walk(holder, key) {
  5971. var k, v, value = holder[key];
  5972. if (value && typeof value === 'object') {
  5973. for (k in value) {
  5974. if (Object.prototype.hasOwnProperty.call(value, k)) {
  5975. v = walk(value, k);
  5976. if (v !== undefined) {
  5977. value[k] = v;
  5978. } else {
  5979. delete value[k];
  5980. }
  5981. }
  5982. }
  5983. }
  5984. return reviver.call(holder, key, value);
  5985. }
  5986. text = String(text);
  5987. cx.lastIndex = 0;
  5988. if (cx.test(text)) {
  5989. text = text.replace(cx, function (a) {
  5990. return '\\u' +
  5991. ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  5992. });
  5993. }
  5994. if (/^[\],:{}\s]*$/
  5995. .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
  5996. .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
  5997. .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
  5998. j = eval('(' + text + ')');
  5999. return typeof reviver === 'function' ?
  6000. walk({ '': j }, '') : j;
  6001. }
  6002. throw new SyntaxError('JSON.parse(): invalid or malformed JSON data');
  6003. };
  6004. }
  6005. /**
  6006. * @fileoverview Functions for automatically setting up a player
  6007. * based on the data-setup attribute of the video tag
  6008. */
  6009. // Automatically set up any tags that have a data-setup attribute
  6010. vjs.autoSetup = function () {
  6011. var options, vid, player,
  6012. vids = document.getElementsByTagName('video');
  6013. // Check if any media elements exist
  6014. if (vids && vids.length > 0) {
  6015. for (var i = 0, j = vids.length; i < j; i++) {
  6016. vid = vids[i];
  6017. // Check if element exists, has getAttribute func.
  6018. // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.
  6019. if (vid && vid.getAttribute) {
  6020. // Make sure this player hasn't already been set up.
  6021. if (vid['player'] === undefined) {
  6022. options = vid.getAttribute('data-setup');
  6023. // Check if data-setup attr exists.
  6024. // We only auto-setup if they've added the data-setup attr.
  6025. if (options !== null) {
  6026. // Parse options JSON
  6027. // If empty string, make it a parsable json object.
  6028. options = vjs.JSON.parse(options || '{}');
  6029. // Create new video.js instance.
  6030. player = videojs(vid, options);
  6031. }
  6032. }
  6033. // If getAttribute isn't defined, we need to wait for the DOM.
  6034. } else {
  6035. vjs.autoSetupTimeout(1);
  6036. break;
  6037. }
  6038. }
  6039. // No videos were found, so keep looping unless page is finisehd loading.
  6040. } else if (!vjs.windowLoaded) {
  6041. vjs.autoSetupTimeout(1);
  6042. }
  6043. };
  6044. // Pause to let the DOM keep processing
  6045. vjs.autoSetupTimeout = function (wait) {
  6046. setTimeout(vjs.autoSetup, wait);
  6047. };
  6048. if (document.readyState === 'complete') {
  6049. vjs.windowLoaded = true;
  6050. } else {
  6051. vjs.one(window, 'load', function () {
  6052. vjs.windowLoaded = true;
  6053. });
  6054. }
  6055. // Run Auto-load players
  6056. // You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
  6057. vjs.autoSetupTimeout(1);
  6058. /**
  6059. * the method for registering a video.js plugin
  6060. *
  6061. * @param {String} name The name of the plugin
  6062. * @param {Function} init The function that is run when the player inits
  6063. */
  6064. vjs.plugin = function (name, init) {
  6065. vjs.Player.prototype[name] = init;
  6066. };