PageRenderTime 70ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 2ms

/static/popcorn/popcorn-complete.js

https://github.com/CodeCosmos/codecosmos.github.io
JavaScript | 9167 lines | 5793 code | 1632 blank | 1742 comment | 948 complexity | 8233cb13ba5c8f9a4a1b7108c1a76223 MD5 | raw file
Possible License(s): BSD-3-Clause, BSD-2-Clause, Apache-2.0, MIT, GPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * popcorn.js version 1.3
  3. * http://popcornjs.org
  4. *
  5. * Copyright 2011, Mozilla Foundation
  6. * Licensed under the MIT license
  7. */
  8. (function(global, document) {
  9. // Popcorn.js does not support archaic browsers
  10. if ( !document.addEventListener ) {
  11. global.Popcorn = {
  12. isSupported: false
  13. };
  14. var methods = ( "byId forEach extend effects error guid sizeOf isArray nop position disable enable destroy" +
  15. "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " +
  16. "timeUpdate plugin removePlugin compose effect xhr getJSONP getScript" ).split(/\s+/);
  17. while ( methods.length ) {
  18. global.Popcorn[ methods.shift() ] = function() {};
  19. }
  20. return;
  21. }
  22. var
  23. AP = Array.prototype,
  24. OP = Object.prototype,
  25. forEach = AP.forEach,
  26. slice = AP.slice,
  27. hasOwn = OP.hasOwnProperty,
  28. toString = OP.toString,
  29. // Copy global Popcorn (may not exist)
  30. _Popcorn = global.Popcorn,
  31. // Ready fn cache
  32. readyStack = [],
  33. readyBound = false,
  34. readyFired = false,
  35. // Non-public internal data object
  36. internal = {
  37. events: {
  38. hash: {},
  39. apis: {}
  40. }
  41. },
  42. // Non-public `requestAnimFrame`
  43. // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  44. requestAnimFrame = (function(){
  45. return global.requestAnimationFrame ||
  46. global.webkitRequestAnimationFrame ||
  47. global.mozRequestAnimationFrame ||
  48. global.oRequestAnimationFrame ||
  49. global.msRequestAnimationFrame ||
  50. function( callback, element ) {
  51. global.setTimeout( callback, 16 );
  52. };
  53. }()),
  54. // Non-public `getKeys`, return an object's keys as an array
  55. getKeys = function( obj ) {
  56. return Object.keys ? Object.keys( obj ) : (function( obj ) {
  57. var item,
  58. list = [];
  59. for ( item in obj ) {
  60. if ( hasOwn.call( obj, item ) ) {
  61. list.push( item );
  62. }
  63. }
  64. return list;
  65. })( obj );
  66. },
  67. // Declare constructor
  68. // Returns an instance object.
  69. Popcorn = function( entity, options ) {
  70. // Return new Popcorn object
  71. return new Popcorn.p.init( entity, options || null );
  72. };
  73. // Popcorn API version, automatically inserted via build system.
  74. Popcorn.version = "1.3";
  75. // Boolean flag allowing a client to determine if Popcorn can be supported
  76. Popcorn.isSupported = true;
  77. // Instance caching
  78. Popcorn.instances = [];
  79. // Declare a shortcut (Popcorn.p) to and a definition of
  80. // the new prototype for our Popcorn constructor
  81. Popcorn.p = Popcorn.prototype = {
  82. init: function( entity, options ) {
  83. var matches, nodeName,
  84. self = this;
  85. // Supports Popcorn(function () { /../ })
  86. // Originally proposed by Daniel Brooks
  87. if ( typeof entity === "function" ) {
  88. // If document ready has already fired
  89. if ( document.readyState === "complete" ) {
  90. entity( document, Popcorn );
  91. return;
  92. }
  93. // Add `entity` fn to ready stack
  94. readyStack.push( entity );
  95. // This process should happen once per page load
  96. if ( !readyBound ) {
  97. // set readyBound flag
  98. readyBound = true;
  99. var DOMContentLoaded = function() {
  100. readyFired = true;
  101. // Remove global DOM ready listener
  102. document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
  103. // Execute all ready function in the stack
  104. for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) {
  105. readyStack[ i ].call( document, Popcorn );
  106. }
  107. // GC readyStack
  108. readyStack = null;
  109. };
  110. // Register global DOM ready listener
  111. document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
  112. }
  113. return;
  114. }
  115. if ( typeof entity === "string" ) {
  116. try {
  117. matches = document.querySelector( entity );
  118. } catch( e ) {
  119. throw new Error( "Popcorn.js Error: Invalid media element selector: " + entity );
  120. }
  121. }
  122. // Get media element by id or object reference
  123. this.media = matches || entity;
  124. // inner reference to this media element's nodeName string value
  125. nodeName = ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video";
  126. // Create an audio or video element property reference
  127. this[ nodeName ] = this.media;
  128. this.options = options || {};
  129. // Resolve custom ID or default prefixed ID
  130. this.id = this.options.id || Popcorn.guid( nodeName );
  131. // Throw if an attempt is made to use an ID that already exists
  132. if ( Popcorn.byId( this.id ) ) {
  133. throw new Error( "Popcorn.js Error: Cannot use duplicate ID (" + this.id + ")" );
  134. }
  135. this.isDestroyed = false;
  136. this.data = {
  137. // data structure of all
  138. running: {
  139. cue: []
  140. },
  141. // Executed by either timeupdate event or in rAF loop
  142. timeUpdate: Popcorn.nop,
  143. // Allows disabling a plugin per instance
  144. disabled: {},
  145. // Stores DOM event queues by type
  146. events: {},
  147. // Stores Special event hooks data
  148. hooks: {},
  149. // Store track event history data
  150. history: [],
  151. // Stores ad-hoc state related data]
  152. state: {
  153. volume: this.media.volume
  154. },
  155. // Store track event object references by trackId
  156. trackRefs: {},
  157. // Playback track event queues
  158. trackEvents: {
  159. byStart: [{
  160. start: -1,
  161. end: -1
  162. }],
  163. byEnd: [{
  164. start: -1,
  165. end: -1
  166. }],
  167. animating: [],
  168. startIndex: 0,
  169. endIndex: 0,
  170. previousUpdateTime: -1
  171. }
  172. };
  173. // Register new instance
  174. Popcorn.instances.push( this );
  175. // function to fire when video is ready
  176. var isReady = function() {
  177. // chrome bug: http://code.google.com/p/chromium/issues/detail?id=119598
  178. // it is possible the video's time is less than 0
  179. // this has the potential to call track events more than once, when they should not
  180. // start: 0, end: 1 will start, end, start again, when it should just start
  181. // just setting it to 0 if it is below 0 fixes this issue
  182. if ( self.media.currentTime < 0 ) {
  183. self.media.currentTime = 0;
  184. }
  185. self.media.removeEventListener( "loadeddata", isReady, false );
  186. var duration, videoDurationPlus,
  187. runningPlugins, runningPlugin, rpLength, rpNatives;
  188. // Adding padding to the front and end of the arrays
  189. // this is so we do not fall off either end
  190. duration = self.media.duration;
  191. // Check for no duration info (NaN)
  192. videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1;
  193. Popcorn.addTrackEvent( self, {
  194. start: videoDurationPlus,
  195. end: videoDurationPlus
  196. });
  197. if ( self.options.frameAnimation ) {
  198. // if Popcorn is created with frameAnimation option set to true,
  199. // requestAnimFrame is used instead of "timeupdate" media event.
  200. // This is for greater frame time accuracy, theoretically up to
  201. // 60 frames per second as opposed to ~4 ( ~every 15-250ms)
  202. self.data.timeUpdate = function () {
  203. Popcorn.timeUpdate( self, {} );
  204. // fire frame for each enabled active plugin of every type
  205. Popcorn.forEach( Popcorn.manifest, function( key, val ) {
  206. runningPlugins = self.data.running[ val ];
  207. // ensure there are running plugins on this type on this instance
  208. if ( runningPlugins ) {
  209. rpLength = runningPlugins.length;
  210. for ( var i = 0; i < rpLength; i++ ) {
  211. runningPlugin = runningPlugins[ i ];
  212. rpNatives = runningPlugin._natives;
  213. rpNatives && rpNatives.frame &&
  214. rpNatives.frame.call( self, {}, runningPlugin, self.currentTime() );
  215. }
  216. }
  217. });
  218. self.emit( "timeupdate" );
  219. !self.isDestroyed && requestAnimFrame( self.data.timeUpdate );
  220. };
  221. !self.isDestroyed && requestAnimFrame( self.data.timeUpdate );
  222. } else {
  223. self.data.timeUpdate = function( event ) {
  224. Popcorn.timeUpdate( self, event );
  225. };
  226. if ( !self.isDestroyed ) {
  227. self.media.addEventListener( "timeupdate", self.data.timeUpdate, false );
  228. }
  229. }
  230. };
  231. Object.defineProperty( this, "error", {
  232. get: function() {
  233. return self.media.error;
  234. }
  235. });
  236. if ( self.media.readyState >= 2 ) {
  237. isReady();
  238. } else {
  239. self.media.addEventListener( "loadeddata", isReady, false );
  240. }
  241. return this;
  242. }
  243. };
  244. // Extend constructor prototype to instance prototype
  245. // Allows chaining methods to instances
  246. Popcorn.p.init.prototype = Popcorn.p;
  247. Popcorn.byId = function( str ) {
  248. var instances = Popcorn.instances,
  249. length = instances.length,
  250. i = 0;
  251. for ( ; i < length; i++ ) {
  252. if ( instances[ i ].id === str ) {
  253. return instances[ i ];
  254. }
  255. }
  256. return null;
  257. };
  258. Popcorn.forEach = function( obj, fn, context ) {
  259. if ( !obj || !fn ) {
  260. return {};
  261. }
  262. context = context || this;
  263. var key, len;
  264. // Use native whenever possible
  265. if ( forEach && obj.forEach === forEach ) {
  266. return obj.forEach( fn, context );
  267. }
  268. if ( toString.call( obj ) === "[object NodeList]" ) {
  269. for ( key = 0, len = obj.length; key < len; key++ ) {
  270. fn.call( context, obj[ key ], key, obj );
  271. }
  272. return obj;
  273. }
  274. for ( key in obj ) {
  275. if ( hasOwn.call( obj, key ) ) {
  276. fn.call( context, obj[ key ], key, obj );
  277. }
  278. }
  279. return obj;
  280. };
  281. Popcorn.extend = function( obj ) {
  282. var dest = obj, src = slice.call( arguments, 1 );
  283. Popcorn.forEach( src, function( copy ) {
  284. for ( var prop in copy ) {
  285. dest[ prop ] = copy[ prop ];
  286. }
  287. });
  288. return dest;
  289. };
  290. // A Few reusable utils, memoized onto Popcorn
  291. Popcorn.extend( Popcorn, {
  292. noConflict: function( deep ) {
  293. if ( deep ) {
  294. global.Popcorn = _Popcorn;
  295. }
  296. return Popcorn;
  297. },
  298. error: function( msg ) {
  299. throw new Error( msg );
  300. },
  301. guid: function( prefix ) {
  302. Popcorn.guid.counter++;
  303. return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter );
  304. },
  305. sizeOf: function( obj ) {
  306. var size = 0;
  307. for ( var prop in obj ) {
  308. size++;
  309. }
  310. return size;
  311. },
  312. isArray: Array.isArray || function( array ) {
  313. return toString.call( array ) === "[object Array]";
  314. },
  315. nop: function() {},
  316. position: function( elem ) {
  317. var clientRect = elem.getBoundingClientRect(),
  318. bounds = {},
  319. doc = elem.ownerDocument,
  320. docElem = document.documentElement,
  321. body = document.body,
  322. clientTop, clientLeft, scrollTop, scrollLeft, top, left;
  323. // Determine correct clientTop/Left
  324. clientTop = docElem.clientTop || body.clientTop || 0;
  325. clientLeft = docElem.clientLeft || body.clientLeft || 0;
  326. // Determine correct scrollTop/Left
  327. scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop );
  328. scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft );
  329. // Temp top/left
  330. top = Math.ceil( clientRect.top + scrollTop - clientTop );
  331. left = Math.ceil( clientRect.left + scrollLeft - clientLeft );
  332. for ( var p in clientRect ) {
  333. bounds[ p ] = Math.round( clientRect[ p ] );
  334. }
  335. return Popcorn.extend({}, bounds, { top: top, left: left });
  336. },
  337. disable: function( instance, plugin ) {
  338. if ( !instance.data.disabled[ plugin ] ) {
  339. instance.data.disabled[ plugin ] = true;
  340. for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) {
  341. event = instance.data.running[ plugin ][ i ];
  342. event._natives.end.call( instance, null, event );
  343. }
  344. }
  345. return instance;
  346. },
  347. enable: function( instance, plugin ) {
  348. if ( instance.data.disabled[ plugin ] ) {
  349. instance.data.disabled[ plugin ] = false;
  350. for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) {
  351. event = instance.data.running[ plugin ][ i ];
  352. event._natives.start.call( instance, null, event );
  353. }
  354. }
  355. return instance;
  356. },
  357. destroy: function( instance ) {
  358. var events = instance.data.events,
  359. trackEvents = instance.data.trackEvents,
  360. singleEvent, item, fn, plugin;
  361. // Iterate through all events and remove them
  362. for ( item in events ) {
  363. singleEvent = events[ item ];
  364. for ( fn in singleEvent ) {
  365. delete singleEvent[ fn ];
  366. }
  367. events[ item ] = null;
  368. }
  369. // remove all plugins off the given instance
  370. for ( plugin in Popcorn.registryByName ) {
  371. Popcorn.removePlugin( instance, plugin );
  372. }
  373. // Remove all data.trackEvents #1178
  374. trackEvents.byStart.length = 0;
  375. trackEvents.byEnd.length = 0;
  376. if ( !instance.isDestroyed ) {
  377. instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false );
  378. instance.isDestroyed = true;
  379. }
  380. }
  381. });
  382. // Memoized GUID Counter
  383. Popcorn.guid.counter = 1;
  384. // Factory to implement getters, setters and controllers
  385. // as Popcorn instance methods. The IIFE will create and return
  386. // an object with defined methods
  387. Popcorn.extend(Popcorn.p, (function() {
  388. var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " +
  389. "autoplay loop controls muted buffered readyState seeking paused played seekable ended",
  390. ret = {};
  391. // Build methods, store in object that is returned and passed to extend
  392. Popcorn.forEach( methods.split( /\s+/g ), function( name ) {
  393. ret[ name ] = function( arg ) {
  394. var previous;
  395. if ( typeof this.media[ name ] === "function" ) {
  396. // Support for shorthanded play(n)/pause(n) jump to currentTime
  397. // If arg is not null or undefined and called by one of the
  398. // allowed shorthandable methods, then set the currentTime
  399. // Supports time as seconds or SMPTE
  400. if ( arg != null && /play|pause/.test( name ) ) {
  401. this.media.currentTime = Popcorn.util.toSeconds( arg );
  402. }
  403. this.media[ name ]();
  404. return this;
  405. }
  406. if ( arg != null ) {
  407. // Capture the current value of the attribute property
  408. previous = this.media[ name ];
  409. // Set the attribute property with the new value
  410. this.media[ name ] = arg;
  411. // If the new value is not the same as the old value
  412. // emit an "attrchanged event"
  413. if ( previous !== arg ) {
  414. this.emit( "attrchange", {
  415. attribute: name,
  416. previousValue: previous,
  417. currentValue: arg
  418. });
  419. }
  420. return this;
  421. }
  422. return this.media[ name ];
  423. };
  424. });
  425. return ret;
  426. })()
  427. );
  428. Popcorn.forEach( "enable disable".split(" "), function( method ) {
  429. Popcorn.p[ method ] = function( plugin ) {
  430. return Popcorn[ method ]( this, plugin );
  431. };
  432. });
  433. Popcorn.extend(Popcorn.p, {
  434. // Rounded currentTime
  435. roundTime: function() {
  436. return Math.round( this.media.currentTime );
  437. },
  438. // Attach an event to a single point in time
  439. exec: function( id, time, fn ) {
  440. var length = arguments.length,
  441. trackEvent, sec;
  442. // Check if first could possibly be a SMPTE string
  443. // p.cue( "smpte string", fn );
  444. // try/catch avoid awful throw in Popcorn.util.toSeconds
  445. // TODO: Get rid of that, replace with NaN return?
  446. try {
  447. sec = Popcorn.util.toSeconds( id );
  448. } catch ( e ) {}
  449. // If it can be converted into a number then
  450. // it's safe to assume that the string was SMPTE
  451. if ( typeof sec === "number" ) {
  452. id = sec;
  453. }
  454. // Shift arguments based on use case
  455. //
  456. // Back compat for:
  457. // p.cue( time, fn );
  458. if ( typeof id === "number" && length === 2 ) {
  459. fn = time;
  460. time = id;
  461. id = Popcorn.guid( "cue" );
  462. } else {
  463. // Support for new forms
  464. // p.cue( "empty-cue" );
  465. if ( length === 1 ) {
  466. // Set a time for an empty cue. It's not important what
  467. // the time actually is, because the cue is a no-op
  468. time = -1;
  469. } else {
  470. // Get the trackEvent that matches the given id.
  471. trackEvent = this.getTrackEvent( id );
  472. if ( trackEvent ) {
  473. // p.cue( "my-id", 12 );
  474. // p.cue( "my-id", function() { ... });
  475. if ( typeof id === "string" && length === 2 ) {
  476. // p.cue( "my-id", 12 );
  477. // The path will update the cue time.
  478. if ( typeof time === "number" ) {
  479. // Re-use existing trackEvent start callback
  480. fn = trackEvent._natives.start;
  481. }
  482. // p.cue( "my-id", function() { ... });
  483. // The path will update the cue function
  484. if ( typeof time === "function" ) {
  485. fn = time;
  486. // Re-use existing trackEvent start time
  487. time = trackEvent.start;
  488. }
  489. }
  490. } else {
  491. if ( length >= 2 ) {
  492. // p.cue( "a", "00:00:00");
  493. if ( typeof time === "string" ) {
  494. try {
  495. sec = Popcorn.util.toSeconds( time );
  496. } catch ( e ) {}
  497. time = sec;
  498. }
  499. // p.cue( "b", 11 );
  500. if ( typeof time === "number" ) {
  501. fn = Popcorn.nop();
  502. }
  503. // p.cue( "c", function() {});
  504. if ( typeof time === "function" ) {
  505. fn = time;
  506. time = -1;
  507. }
  508. }
  509. }
  510. }
  511. }
  512. // Creating a one second track event with an empty end
  513. // Or update an existing track event with new values
  514. Popcorn.addTrackEvent( this, {
  515. id: id,
  516. start: time,
  517. end: time + 1,
  518. _running: false,
  519. _natives: {
  520. start: fn || Popcorn.nop,
  521. end: Popcorn.nop,
  522. type: "cue"
  523. }
  524. });
  525. return this;
  526. },
  527. // Mute the calling media, optionally toggle
  528. mute: function( toggle ) {
  529. var event = toggle == null || toggle === true ? "muted" : "unmuted";
  530. // If `toggle` is explicitly `false`,
  531. // unmute the media and restore the volume level
  532. if ( event === "unmuted" ) {
  533. this.media.muted = false;
  534. this.media.volume = this.data.state.volume;
  535. }
  536. // If `toggle` is either null or undefined,
  537. // save the current volume and mute the media element
  538. if ( event === "muted" ) {
  539. this.data.state.volume = this.media.volume;
  540. this.media.muted = true;
  541. }
  542. // Trigger either muted|unmuted event
  543. this.emit( event );
  544. return this;
  545. },
  546. // Convenience method, unmute the calling media
  547. unmute: function( toggle ) {
  548. return this.mute( toggle == null ? false : !toggle );
  549. },
  550. // Get the client bounding box of an instance element
  551. position: function() {
  552. return Popcorn.position( this.media );
  553. },
  554. // Toggle a plugin's playback behaviour (on or off) per instance
  555. toggle: function( plugin ) {
  556. return Popcorn[ this.data.disabled[ plugin ] ? "enable" : "disable" ]( this, plugin );
  557. },
  558. // Set default values for plugin options objects per instance
  559. defaults: function( plugin, defaults ) {
  560. // If an array of default configurations is provided,
  561. // iterate and apply each to this instance
  562. if ( Popcorn.isArray( plugin ) ) {
  563. Popcorn.forEach( plugin, function( obj ) {
  564. for ( var name in obj ) {
  565. this.defaults( name, obj[ name ] );
  566. }
  567. }, this );
  568. return this;
  569. }
  570. if ( !this.options.defaults ) {
  571. this.options.defaults = {};
  572. }
  573. if ( !this.options.defaults[ plugin ] ) {
  574. this.options.defaults[ plugin ] = {};
  575. }
  576. Popcorn.extend( this.options.defaults[ plugin ], defaults );
  577. return this;
  578. }
  579. });
  580. Popcorn.Events = {
  581. UIEvents: "blur focus focusin focusout load resize scroll unload",
  582. MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",
  583. Events: "loadstart progress suspend emptied stalled play pause error " +
  584. "loadedmetadata loadeddata waiting playing canplay canplaythrough " +
  585. "seeking seeked timeupdate ended ratechange durationchange volumechange"
  586. };
  587. Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " +
  588. Popcorn.Events.MouseEvents + " " +
  589. Popcorn.Events.Events;
  590. internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ];
  591. // Privately compile events table at load time
  592. (function( events, data ) {
  593. var apis = internal.events.apiTypes,
  594. eventsList = events.Natives.split( /\s+/g ),
  595. idx = 0, len = eventsList.length, prop;
  596. for( ; idx < len; idx++ ) {
  597. data.hash[ eventsList[idx] ] = true;
  598. }
  599. apis.forEach(function( val, idx ) {
  600. data.apis[ val ] = {};
  601. var apiEvents = events[ val ].split( /\s+/g ),
  602. len = apiEvents.length,
  603. k = 0;
  604. for ( ; k < len; k++ ) {
  605. data.apis[ val ][ apiEvents[ k ] ] = true;
  606. }
  607. });
  608. })( Popcorn.Events, internal.events );
  609. Popcorn.events = {
  610. isNative: function( type ) {
  611. return !!internal.events.hash[ type ];
  612. },
  613. getInterface: function( type ) {
  614. if ( !Popcorn.events.isNative( type ) ) {
  615. return false;
  616. }
  617. var eventApi = internal.events,
  618. apis = eventApi.apiTypes,
  619. apihash = eventApi.apis,
  620. idx = 0, len = apis.length, api, tmp;
  621. for ( ; idx < len; idx++ ) {
  622. tmp = apis[ idx ];
  623. if ( apihash[ tmp ][ type ] ) {
  624. api = tmp;
  625. break;
  626. }
  627. }
  628. return api;
  629. },
  630. // Compile all native events to single array
  631. all: Popcorn.Events.Natives.split( /\s+/g ),
  632. // Defines all Event handling static functions
  633. fn: {
  634. trigger: function( type, data ) {
  635. var eventInterface, evt;
  636. // setup checks for custom event system
  637. if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) {
  638. eventInterface = Popcorn.events.getInterface( type );
  639. if ( eventInterface ) {
  640. evt = document.createEvent( eventInterface );
  641. evt.initEvent( type, true, true, global, 1 );
  642. this.media.dispatchEvent( evt );
  643. return this;
  644. }
  645. // Custom events
  646. Popcorn.forEach( this.data.events[ type ], function( obj, key ) {
  647. obj.call( this, data );
  648. }, this );
  649. }
  650. return this;
  651. },
  652. listen: function( type, fn ) {
  653. var self = this,
  654. hasEvents = true,
  655. eventHook = Popcorn.events.hooks[ type ],
  656. origType = type,
  657. tmp;
  658. if ( !this.data.events[ type ] ) {
  659. this.data.events[ type ] = {};
  660. hasEvents = false;
  661. }
  662. // Check and setup event hooks
  663. if ( eventHook ) {
  664. // Execute hook add method if defined
  665. if ( eventHook.add ) {
  666. eventHook.add.call( this, {}, fn );
  667. }
  668. // Reassign event type to our piggyback event type if defined
  669. if ( eventHook.bind ) {
  670. type = eventHook.bind;
  671. }
  672. // Reassign handler if defined
  673. if ( eventHook.handler ) {
  674. tmp = fn;
  675. fn = function wrapper( event ) {
  676. eventHook.handler.call( self, event, tmp );
  677. };
  678. }
  679. // assume the piggy back event is registered
  680. hasEvents = true;
  681. // Setup event registry entry
  682. if ( !this.data.events[ type ] ) {
  683. this.data.events[ type ] = {};
  684. // Toggle if the previous assumption was untrue
  685. hasEvents = false;
  686. }
  687. }
  688. // Register event and handler
  689. this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn;
  690. // only attach one event of any type
  691. if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) {
  692. this.media.addEventListener( type, function( event ) {
  693. Popcorn.forEach( self.data.events[ type ], function( obj, key ) {
  694. if ( typeof obj === "function" ) {
  695. obj.call( self, event );
  696. }
  697. });
  698. }, false);
  699. }
  700. return this;
  701. },
  702. unlisten: function( type, fn ) {
  703. if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) {
  704. delete this.data.events[ type ][ fn ];
  705. return this;
  706. }
  707. this.data.events[ type ] = null;
  708. return this;
  709. }
  710. },
  711. hooks: {
  712. canplayall: {
  713. bind: "canplaythrough",
  714. add: function( event, callback ) {
  715. var state = false;
  716. if ( this.media.readyState ) {
  717. callback.call( this, event );
  718. state = true;
  719. }
  720. this.data.hooks.canplayall = {
  721. fired: state
  722. };
  723. },
  724. // declare special handling instructions
  725. handler: function canplayall( event, callback ) {
  726. if ( !this.data.hooks.canplayall.fired ) {
  727. // trigger original user callback once
  728. callback.call( this, event );
  729. this.data.hooks.canplayall.fired = true;
  730. }
  731. }
  732. }
  733. }
  734. };
  735. // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances
  736. // Extend aliases (on, off, emit)
  737. Popcorn.forEach( [ [ "trigger", "emit" ], [ "listen", "on" ], [ "unlisten", "off" ] ], function( key ) {
  738. Popcorn.p[ key[ 0 ] ] = Popcorn.p[ key[ 1 ] ] = Popcorn.events.fn[ key[ 0 ] ];
  739. });
  740. // Internal Only - Adds track events to the instance object
  741. Popcorn.addTrackEvent = function( obj, track ) {
  742. var trackEvent, isUpdate, eventType;
  743. // Do a lookup for existing trackevents with this id
  744. if ( track.id ) {
  745. trackEvent = obj.getTrackEvent( track.id );
  746. }
  747. // If a track event by this id currently exists, modify it
  748. if ( trackEvent ) {
  749. isUpdate = true;
  750. // Create a new object with the existing trackEvent
  751. // Extend with new track properties
  752. track = Popcorn.extend( {}, trackEvent, track );
  753. // Remove the existing track from the instance
  754. obj.removeTrackEvent( track.id );
  755. }
  756. // Determine if this track has default options set for it
  757. // If so, apply them to the track object
  758. if ( track && track._natives && track._natives.type &&
  759. ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) {
  760. track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track );
  761. }
  762. if ( track._natives ) {
  763. // Supports user defined track event id
  764. track._id = track.id || track._id || Popcorn.guid( track._natives.type );
  765. // Push track event ids into the history
  766. obj.data.history.push( track._id );
  767. }
  768. track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate );
  769. track.end = Popcorn.util.toSeconds( track.end, obj.options.framerate );
  770. // Store this definition in an array sorted by times
  771. var byStart = obj.data.trackEvents.byStart,
  772. byEnd = obj.data.trackEvents.byEnd,
  773. startIndex, endIndex;
  774. for ( startIndex = byStart.length - 1; startIndex >= 0; startIndex-- ) {
  775. if ( track.start >= byStart[ startIndex ].start ) {
  776. byStart.splice( startIndex + 1, 0, track );
  777. break;
  778. }
  779. }
  780. for ( endIndex = byEnd.length - 1; endIndex >= 0; endIndex-- ) {
  781. if ( track.end > byEnd[ endIndex ].end ) {
  782. byEnd.splice( endIndex + 1, 0, track );
  783. break;
  784. }
  785. }
  786. // Display track event immediately if it's enabled and current
  787. if ( track.end > obj.media.currentTime &&
  788. track.start <= obj.media.currentTime ) {
  789. track._running = true;
  790. obj.data.running[ track._natives.type ].push( track );
  791. if ( !obj.data.disabled[ track._natives.type ] ) {
  792. track._natives.start.call( obj, null, track );
  793. }
  794. }
  795. // update startIndex and endIndex
  796. if ( startIndex <= obj.data.trackEvents.startIndex &&
  797. track.start <= obj.data.trackEvents.previousUpdateTime ) {
  798. obj.data.trackEvents.startIndex++;
  799. }
  800. if ( endIndex <= obj.data.trackEvents.endIndex &&
  801. track.end < obj.data.trackEvents.previousUpdateTime ) {
  802. obj.data.trackEvents.endIndex++;
  803. }
  804. this.timeUpdate( obj, null, true );
  805. // Store references to user added trackevents in ref table
  806. if ( track._id ) {
  807. Popcorn.addTrackEvent.ref( obj, track );
  808. }
  809. // If the call to addTrackEvent was an update/modify call, fire an event
  810. if ( isUpdate ) {
  811. // Determine appropriate event type to trigger
  812. // they are identical in function, but the naming
  813. // adds some level of intuition for the end developer
  814. // to rely on
  815. if ( track._natives.type === "cue" ) {
  816. eventType = "cuechange";
  817. } else {
  818. eventType = "trackchange";
  819. }
  820. // Fire an event with change information
  821. obj.emit( eventType, {
  822. id: track.id,
  823. previousValue: {
  824. time: trackEvent.start,
  825. fn: trackEvent._natives.start
  826. },
  827. currentValue: {
  828. time: track.start,
  829. fn: track._natives.start
  830. }
  831. });
  832. }
  833. };
  834. // Internal Only - Adds track event references to the instance object's trackRefs hash table
  835. Popcorn.addTrackEvent.ref = function( obj, track ) {
  836. obj.data.trackRefs[ track._id ] = track;
  837. return obj;
  838. };
  839. Popcorn.removeTrackEvent = function( obj, removeId ) {
  840. var start, end, animate,
  841. historyLen = obj.data.history.length,
  842. length = obj.data.trackEvents.byStart.length,
  843. index = 0,
  844. indexWasAt = 0,
  845. byStart = [],
  846. byEnd = [],
  847. animating = [],
  848. history = [];
  849. while ( --length > -1 ) {
  850. start = obj.data.trackEvents.byStart[ index ];
  851. end = obj.data.trackEvents.byEnd[ index ];
  852. // Padding events will not have _id properties.
  853. // These should be safely pushed onto the front and back of the
  854. // track event array
  855. if ( !start._id ) {
  856. byStart.push( start );
  857. byEnd.push( end );
  858. }
  859. // Filter for user track events (vs system track events)
  860. if ( start._id ) {
  861. // If not a matching start event for removal
  862. if ( start._id !== removeId ) {
  863. byStart.push( start );
  864. }
  865. // If not a matching end event for removal
  866. if ( end._id !== removeId ) {
  867. byEnd.push( end );
  868. }
  869. // If the _id is matched, capture the current index
  870. if ( start._id === removeId ) {
  871. indexWasAt = index;
  872. // If a _teardown function was defined,
  873. // enforce for track event removals
  874. if ( start._natives._teardown ) {
  875. start._natives._teardown.call( obj, start );
  876. }
  877. }
  878. }
  879. // Increment the track index
  880. index++;
  881. }
  882. // Reset length to be used by the condition below to determine
  883. // if animating track events should also be filtered for removal.
  884. // Reset index below to be used by the reverse while as an
  885. // incrementing counter
  886. length = obj.data.trackEvents.animating.length;
  887. index = 0;
  888. if ( length ) {
  889. while ( --length > -1 ) {
  890. animate = obj.data.trackEvents.animating[ index ];
  891. // Padding events will not have _id properties.
  892. // These should be safely pushed onto the front and back of the
  893. // track event array
  894. if ( !animate._id ) {
  895. animating.push( animate );
  896. }
  897. // If not a matching animate event for removal
  898. if ( animate._id && animate._id !== removeId ) {
  899. animating.push( animate );
  900. }
  901. // Increment the track index
  902. index++;
  903. }
  904. }
  905. // Update
  906. if ( indexWasAt <= obj.data.trackEvents.startIndex ) {
  907. obj.data.trackEvents.startIndex--;
  908. }
  909. if ( indexWasAt <= obj.data.trackEvents.endIndex ) {
  910. obj.data.trackEvents.endIndex--;
  911. }
  912. obj.data.trackEvents.byStart = byStart;
  913. obj.data.trackEvents.byEnd = byEnd;
  914. obj.data.trackEvents.animating = animating;
  915. for ( var i = 0; i < historyLen; i++ ) {
  916. if ( obj.data.history[ i ] !== removeId ) {
  917. history.push( obj.data.history[ i ] );
  918. }
  919. }
  920. // Update ordered history array
  921. obj.data.history = history;
  922. // Update track event references
  923. Popcorn.removeTrackEvent.ref( obj, removeId );
  924. };
  925. // Internal Only - Removes track event references from instance object's trackRefs hash table
  926. Popcorn.removeTrackEvent.ref = function( obj, removeId ) {
  927. delete obj.data.trackRefs[ removeId ];
  928. return obj;
  929. };
  930. // Return an array of track events bound to this instance object
  931. Popcorn.getTrackEvents = function( obj ) {
  932. var trackevents = [],
  933. refs = obj.data.trackEvents.byStart,
  934. length = refs.length,
  935. idx = 0,
  936. ref;
  937. for ( ; idx < length; idx++ ) {
  938. ref = refs[ idx ];
  939. // Return only user attributed track event references
  940. if ( ref._id ) {
  941. trackevents.push( ref );
  942. }
  943. }
  944. return trackevents;
  945. };
  946. // Internal Only - Returns an instance object's trackRefs hash table
  947. Popcorn.getTrackEvents.ref = function( obj ) {
  948. return obj.data.trackRefs;
  949. };
  950. // Return a single track event bound to this instance object
  951. Popcorn.getTrackEvent = function( obj, trackId ) {
  952. return obj.data.trackRefs[ trackId ];
  953. };
  954. // Internal Only - Returns an instance object's track reference by track id
  955. Popcorn.getTrackEvent.ref = function( obj, trackId ) {
  956. return obj.data.trackRefs[ trackId ];
  957. };
  958. Popcorn.getLastTrackEventId = function( obj ) {
  959. return obj.data.history[ obj.data.history.length - 1 ];
  960. };
  961. Popcorn.timeUpdate = function( obj, event ) {
  962. var currentTime = obj.media.currentTime,
  963. previousTime = obj.data.trackEvents.previousUpdateTime,
  964. tracks = obj.data.trackEvents,
  965. end = tracks.endIndex,
  966. start = tracks.startIndex,
  967. byStartLen = tracks.byStart.length,
  968. byEndLen = tracks.byEnd.length,
  969. registryByName = Popcorn.registryByName,
  970. trackstart = "trackstart",
  971. trackend = "trackend",
  972. byEnd, byStart, byAnimate, natives, type, runningPlugins;
  973. // Playbar advancing
  974. if ( previousTime <= currentTime ) {
  975. while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) {
  976. byEnd = tracks.byEnd[ end ];
  977. natives = byEnd._natives;
  978. type = natives && natives.type;
  979. // If plugin does not exist on this instance, remove it
  980. if ( !natives ||
  981. ( !!registryByName[ type ] ||
  982. !!obj[ type ] ) ) {
  983. if ( byEnd._running === true ) {
  984. byEnd._running = false;
  985. runningPlugins = obj.data.running[ type ];
  986. runningPlugins.splice( runningPlugins.indexOf( byEnd ), 1 );
  987. if ( !obj.data.disabled[ type ] ) {
  988. natives.end.call( obj, event, byEnd );
  989. obj.emit( trackend,
  990. Popcorn.extend({}, byEnd, {
  991. plugin: type,
  992. type: trackend
  993. })
  994. );
  995. }
  996. }
  997. end++;
  998. } else {
  999. // remove track event
  1000. Popcorn.removeTrackEvent( obj, byEnd._id );
  1001. return;
  1002. }
  1003. }
  1004. while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) {
  1005. byStart = tracks.byStart[ start ];
  1006. natives = byStart._natives;
  1007. type = natives && natives.type;
  1008. // If plugin does not exist on this instance, remove it
  1009. if ( !natives ||
  1010. ( !!registryByName[ type ] ||
  1011. !!obj[ type ] ) ) {
  1012. if ( byStart.end > currentTime &&
  1013. byStart._running === false ) {
  1014. byStart._running = true;
  1015. obj.data.running[ type ].push( byStart );
  1016. if ( !obj.data.disabled[ type ] ) {
  1017. natives.start.call( obj, event, byStart );
  1018. obj.emit( trackstart,
  1019. Popcorn.extend({}, byStart, {
  1020. plugin: type,
  1021. type: trackstart
  1022. })
  1023. );
  1024. }
  1025. }
  1026. start++;
  1027. } else {
  1028. // remove track event
  1029. Popcorn.removeTrackEvent( obj, byStart._id );
  1030. return;
  1031. }
  1032. }
  1033. // Playbar receding
  1034. } else if ( previousTime > currentTime ) {
  1035. while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) {
  1036. byStart = tracks.byStart[ start ];
  1037. natives = byStart._natives;
  1038. type = natives && natives.type;
  1039. // if plugin does not exist on this instance, remove it
  1040. if ( !natives ||
  1041. ( !!registryByName[ type ] ||
  1042. !!obj[ type ] ) ) {
  1043. if ( byStart._running === true ) {
  1044. byStart._running = false;
  1045. runningPlugins = obj.data.running[ type ];
  1046. runningPlugins.splice( runningPlugins.indexOf( byStart ), 1 );
  1047. if ( !obj.data.disabled[ type ] ) {
  1048. natives.end.call( obj, event, byStart );
  1049. obj.emit( trackend,
  1050. Popcorn.extend({}, byStart, {
  1051. plugin: type,
  1052. type: trackend
  1053. })
  1054. );
  1055. }
  1056. }
  1057. start--;
  1058. } else {
  1059. // remove track event
  1060. Popcorn.removeTrackEvent( obj, byStart._id );
  1061. return;
  1062. }
  1063. }
  1064. while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) {
  1065. byEnd = tracks.byEnd[ end ];
  1066. natives = byEnd._natives;
  1067. type = natives && natives.type;
  1068. // if plugin does not exist on this instance, remove it
  1069. if ( !natives ||
  1070. ( !!registryByName[ type ] ||
  1071. !!obj[ type ] ) ) {
  1072. if ( byEnd.start <= currentTime &&
  1073. byEnd._running === false ) {
  1074. byEnd._running = true;
  1075. obj.data.running[ type ].push( byEnd );
  1076. if ( !obj.data.disabled[ type ] ) {
  1077. natives.start.call( obj, event, byEnd );
  1078. obj.emit( trackstart,
  1079. Popcorn.extend({}, byEnd, {
  1080. plugin: type,
  1081. type: trackstart
  1082. })
  1083. );
  1084. }
  1085. }
  1086. end--;
  1087. } else {
  1088. // remove track event
  1089. Popcorn.removeTrackEvent( obj, byEnd._id );
  1090. return;
  1091. }
  1092. }
  1093. }
  1094. tracks.endIndex = end;
  1095. tracks.startIndex = start;
  1096. tracks.previousUpdateTime = currentTime;
  1097. //enforce index integrity if trackRemoved
  1098. tracks.byStart.length < byStartLen && tracks.startIndex--;
  1099. tracks.byEnd.length < byEndLen && tracks.endIndex--;
  1100. };
  1101. // Map and Extend TrackEvent functions to all Popcorn instances
  1102. Popcorn.extend( Popcorn.p, {
  1103. getTrackEvents: function() {
  1104. return Popcorn.getTrackEvents.call( null, this );
  1105. },
  1106. getTrackEvent: function( id ) {
  1107. return Popcorn.getTrackEvent.call( null, this, id );
  1108. },
  1109. getLastTrackEventId: function() {
  1110. return Popcorn.getLastTrackEventId.call( null, this );
  1111. },
  1112. removeTrackEvent: function( id ) {
  1113. Popcorn.removeTrackEvent.call( null, this, id );
  1114. return this;
  1115. },
  1116. removePlugin: function( name ) {
  1117. Popcorn.removePlugin.call( null, this, name );
  1118. return this;
  1119. },
  1120. timeUpdate: function( event ) {
  1121. Popcorn.timeUpdate.call( null, this, event );
  1122. return this;
  1123. },
  1124. destroy: function() {
  1125. Popcorn.destroy.call( null, this );
  1126. return this;
  1127. }
  1128. });
  1129. // Plugin manifests
  1130. Popcorn.manifest = {};
  1131. // Plugins are registered
  1132. Popcorn.registry = [];
  1133. Popcorn.registryByName = {};
  1134. // An interface for extending Popcorn
  1135. // with plugin functionality
  1136. Popcorn.plugin = function( name, definition, manifest ) {
  1137. if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
  1138. Popcorn.error( "'" + name + "' is a protected function name" );
  1139. return;
  1140. }
  1141. // Provides some sugar, but ultimately extends
  1142. // the definition into Popcorn.p
  1143. var reserved = [ "start", "end" ],
  1144. plugin = {},
  1145. setup,
  1146. isfn = typeof definition === "function",
  1147. methods = [ "_setup", "_teardown", "start", "end", "frame" ];
  1148. // combines calls of two function calls into one
  1149. var combineFn = function( first, second ) {
  1150. first = first || Popcorn.nop;
  1151. second = second || Popcorn.nop;
  1152. return function() {
  1153. first.apply( this, arguments );
  1154. second.apply( this, arguments );
  1155. };
  1156. };
  1157. // If `manifest` arg is undefined, check for manifest within the `definition` object
  1158. // If no `definition.manifest`, an empty object is a sufficient fallback
  1159. Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
  1160. // apply safe, and empty default functions
  1161. methods.forEach(function( method ) {
  1162. definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name );
  1163. });
  1164. var pluginFn = function( setup, options ) {
  1165. if ( !options ) {
  1166. return this;
  1167. }
  1168. // When the "ranges" property is set and its value is an array, short-circuit
  1169. // the pluginFn definition to recall itself with an options object generated from
  1170. // each range object in the ranges array. (eg. { start: 15, end: 16 } )
  1171. if ( options.ranges && Popcorn.isArray(options.ranges) ) {
  1172. Popcorn.forEach( options.ranges, function( range ) {
  1173. // Create a fresh object, extend with current options
  1174. // and start/end range object's properties
  1175. // Works with in/out as well.
  1176. var opts = Popcorn.extend( {}, options, range );
  1177. // Remove the ranges property to prevent infinitely
  1178. // entering this condition
  1179. delete opts.ranges;
  1180. // Call the plugin with the newly created opts object
  1181. this[ name ]( opts );
  1182. }, this);
  1183. // Return the Popcorn instance to avoid creating an empty track event
  1184. return this;
  1185. }
  1186. // Storing the plugin natives
  1187. var natives = options._natives = {},
  1188. compose = "",
  1189. originalOpts, manifestOpts;
  1190. Popcorn.extend( natives, setup );
  1191. options._natives.type = name;
  1192. options._running = false;
  1193. natives.start = natives.start || natives[ "in" ];
  1194. natives.end = natives.end || natives[ "out" ];
  1195. if ( options.once ) {
  1196. natives.end = combineFn( natives.end, function() {
  1197. this.removeTrackEvent( options._id );
  1198. });
  1199. }
  1200. // extend teardown to always call end if running
  1201. natives._teardown = combineFn(function() {
  1202. var args = slice.call( arguments ),
  1203. runningPlugins = this.data.running[ natives.type ];
  1204. // end function signature is not the same as teardown,
  1205. // put null on the front of arguments for the event parameter
  1206. args.unshift( null );
  1207. // only call end if event is running
  1208. args[ 1 ]._running &&
  1209. runningPlugins.splice( runningPlugins.indexOf( options ), 1 ) &&
  1210. natives.end.apply( this, args );
  1211. }, natives._teardown );
  1212. // default to an empty string if no effect exists
  1213. // split string into an array of effects
  1214. options.compose = options.compose && options.compose.split( " " ) || [];
  1215. options.effect = options.effect && options.effect.split( " " ) || [];
  1216. // join the two arrays together
  1217. options.compose = options.compose.concat( options.effect );
  1218. options.compose.forEach(function( composeOption ) {
  1219. // if the requested compose is garbage, throw it away
  1220. compose = Popcorn.compositions[ composeOption ] || {};
  1221. // extends previous functions with compose function
  1222. methods.forEach(function( method ) {
  1223. natives[ method ] = combineFn( natives[ method ], compose[ method ] );
  1224. });
  1225. });
  1226. // Ensure a manifest object, an empty object is a sufficient fallback
  1227. options._natives.manifest = manifest;
  1228. // Checks for expected properties
  1229. if ( !( "start" in options ) ) {
  1230. options.start = options[ "in" ] || 0;
  1231. }
  1232. if ( !options.end && options.end !== 0 ) {
  1233. options.end = options[ "out" ] || Number.MAX_VALUE;
  1234. }
  1235. // Use hasOwn to detect non-inherited toString, since all
  1236. // objects will receive a toString - its otherwise undetectable
  1237. if ( !hasOwn.call( options, "toString" ) ) {
  1238. options.toString = function() {
  1239. var props = [
  1240. "start: " + options.start,
  1241. "end: " + options.end,
  1242. "id: " + (options.id || options._id)
  1243. ];
  1244. // Matches null and undefined, allows: false, 0, "" and truthy
  1245. if ( options.target != null ) {
  1246. props.push( "target: " + options.target );
  1247. }
  1248. return name + " ( " + props.join(", ") + " )";
  1249. };
  1250. }
  1251. // Resolves 239, 241, 242
  1252. if ( !options.target ) {
  1253. // Sometimes the manifest may be missing entirely
  1254. // or it has an options object that doesn't have a `target` property
  1255. manifestOpts = "options" in manifest && manifest.options;
  1256. options.target = manifestOpts && "target" in manifestOpts && manifestOpts.target;
  1257. }
  1258. if ( options._natives ) {
  1259. // ensure an initial id is there before setup is called
  1260. options._id = Popcorn.guid( options._natives.type );
  1261. }
  1262. // Trigger _setup method if exists
  1263. options._natives._setup && options._natives._setup.call( this, options );
  1264. // Create new track event for this instance
  1265. Popcorn.addTrackEvent( this, options );
  1266. // Future support for plugin event definitions
  1267. // for all of the native events
  1268. Popcorn.forEach( setup, function( callback, type ) {
  1269. if ( type !== "type" ) {
  1270. if ( reserved.indexOf( type ) === -1 ) {
  1271. this.on( type, callback );
  1272. }
  1273. }
  1274. }, this );
  1275. return this;
  1276. };
  1277. // Extend Popcorn.p with new named definition
  1278. // Assign new named definition
  1279. Popcorn.p[ name ] = plugin[ name ] = function( id, options ) {
  1280. var length = arguments.length,
  1281. trackEvent, defaults, mergedSetupOpts;
  1282. // Shift arguments based on use case
  1283. //
  1284. // Back compat for:
  1285. // p.plugin( options );
  1286. if ( id && !options ) {
  1287. options = id;
  1288. id = null;
  1289. } else {
  1290. // Get the trackEvent that matches the given id.
  1291. trackEvent = this.getTrackEvent( id );
  1292. // If the track event does not exist, ensure that the options
  1293. // object has a proper id
  1294. if ( !trackEvent ) {
  1295. options.id = id;
  1296. // If the track event does exist, merge the updated properties
  1297. } else {
  1298. options = Popcorn.extend( {}, trackEvent, options );
  1299. Popcorn.addTrackEvent( this, options );
  1300. return this;
  1301. }
  1302. }
  1303. this.data.running[ name ] = this.data.running[ name ] || [];
  1304. // Merge with defaults if they exist, make sure per call is prioritized
  1305. defaults = ( this.options.defaults && this.options.defaults[ name ] ) || {};
  1306. mergedSetupOpts = Popcorn.extend( {}, defaults, options );
  1307. return pluginFn.call( this, isfn ? definition.call( this, mergedSetupOpts ) : definition,
  1308. mergedSetupOpts );
  1309. };
  1310. // if the manifest parameter exists we should extend it onto the definition object
  1311. // so that it shows up when calling Popcorn.registry and Popcorn.registryByName
  1312. if ( manifest ) {
  1313. Popcorn.extend( definition, {
  1314. manifest: manifest
  1315. });
  1316. }
  1317. // Push into the registry
  1318. var entry = {
  1319. fn: plugin[ name ],
  1320. definition: definition,
  1321. base: definition,
  1322. parents: [],
  1323. name: name
  1324. };
  1325. Popcorn.registry.push(
  1326. Popcorn.extend( plugin, entry, {
  1327. type: name
  1328. })
  1329. );
  1330. Popcorn.registryByName[ name ] = entry;
  1331. return plugin;
  1332. };
  1333. // Storage for plugin function errors
  1334. Popcorn.plugin.errors = [];
  1335. // Returns wrapped plugin function
  1336. function safeTry( fn, pluginName ) {
  1337. return function() {
  1338. // When Popcorn.plugin.debug is true, do not suppress errors
  1339. if ( Popcorn.plugin.debug ) {
  1340. return fn.apply( this, arguments );
  1341. }
  1342. try {
  1343. return fn.apply( this, arguments );
  1344. } catch

Large files files are truncated, but you can click here to view the full file