PageRenderTime 71ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/external/popcorn/popcorn-complete.js

https://github.com/lulubulb/butter
JavaScript | 9098 lines | 5417 code | 1526 blank | 2155 comment | 962 complexity | 05596acbe4aff6871a9872129a6b656b MD5 | raw file
Possible License(s): Apache-2.0

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

  1. /*
  2. * popcorn.js version 0.7
  3. * http://popcornjs.org
  4. *
  5. * Copyright 2011, Mozilla Foundation
  6. * Licensed under the MIT license
  7. */
  8. (function(global, document) {
  9. // Cache refs to speed up calls to native utils
  10. var
  11. AP = Array.prototype,
  12. OP = Object.prototype,
  13. forEach = AP.forEach,
  14. slice = AP.slice,
  15. hasOwn = OP.hasOwnProperty,
  16. toString = OP.toString,
  17. // ID string matching
  18. rIdExp = /^(#([\w\-\_\.]+))$/,
  19. // Ready fn cache
  20. readyStack = [],
  21. readyBound = false,
  22. readyFired = false,
  23. // Non-public internal data object
  24. internal = {
  25. events: {
  26. hash: {},
  27. apis: {}
  28. }
  29. },
  30. // Declare constructor
  31. // Returns an instance object.
  32. Popcorn = function( entity, options ) {
  33. // Return new Popcorn object
  34. return new Popcorn.p.init( entity, options || null );
  35. };
  36. // Instance caching
  37. Popcorn.instances = [];
  38. Popcorn.instanceIds = {};
  39. Popcorn.removeInstance = function( instance ) {
  40. // If called prior to any instances being created
  41. // Return early to avoid splicing on nothing
  42. if ( !Popcorn.instances.length ) {
  43. return;
  44. }
  45. // Remove instance from Popcorn.instances
  46. Popcorn.instances.splice( Popcorn.instanceIds[ instance.id ], 1 );
  47. // Delete the instance id key
  48. delete Popcorn.instanceIds[ instance.id ];
  49. // Return current modified instances
  50. return Popcorn.instances;
  51. };
  52. // Addes a Popcorn instance to the Popcorn instance array
  53. Popcorn.addInstance = function( instance ) {
  54. var instanceLen = Popcorn.instances.length,
  55. instanceId = instance.media.id && instance.media.id;
  56. // If the media element has its own `id` use it, otherwise provide one
  57. // Ensure that instances have unique ids and unique entries
  58. // Uses `in` operator to avoid false positives on 0
  59. instance.id = !( instanceId in Popcorn.instanceIds ) && instanceId ||
  60. "__popcorn" + instanceLen;
  61. // Create a reference entry for this instance
  62. Popcorn.instanceIds[ instance.id ] = instanceLen;
  63. // Add this instance to the cache
  64. Popcorn.instances.push( instance );
  65. // Return the current modified instances
  66. return Popcorn.instances;
  67. };
  68. // Request Popcorn object instance by id
  69. Popcorn.getInstanceById = function( id ) {
  70. return Popcorn.instances[ Popcorn.instanceIds[ id ] ];
  71. };
  72. // Remove Popcorn object instance by id
  73. Popcorn.removeInstanceById = function( id ) {
  74. return Popcorn.removeInstance( Popcorn.instances[ Popcorn.instanceIds[ id ] ] );
  75. };
  76. // Declare a shortcut (Popcorn.p) to and a definition of
  77. // the new prototype for our Popcorn constructor
  78. Popcorn.p = Popcorn.prototype = {
  79. init: function( entity, options ) {
  80. var matches;
  81. // Supports Popcorn(function () { /../ })
  82. // Originally proposed by Daniel Brooks
  83. if ( typeof entity === "function" ) {
  84. // If document ready has already fired
  85. if ( document.readyState === "interactive" || document.readyState === "complete" ) {
  86. entity( document, Popcorn );
  87. return;
  88. }
  89. // Add `entity` fn to ready stack
  90. readyStack.push( entity );
  91. // This process should happen once per page load
  92. if ( !readyBound ) {
  93. // set readyBound flag
  94. readyBound = true;
  95. var DOMContentLoaded = function() {
  96. readyFired = true;
  97. // Remove global DOM ready listener
  98. document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
  99. // Execute all ready function in the stack
  100. for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) {
  101. readyStack[ i ].call( document, Popcorn );
  102. }
  103. // GC readyStack
  104. readyStack = null;
  105. };
  106. // Register global DOM ready listener
  107. document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
  108. }
  109. return;
  110. }
  111. // Check if entity is a valid string id
  112. matches = rIdExp.exec( entity );
  113. // Get media element by id or object reference
  114. this.media = matches && matches.length && matches[ 2 ] ?
  115. document.getElementById( matches[ 2 ] ) :
  116. entity;
  117. // Create an audio or video element property reference
  118. this[ ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video" ] = this.media;
  119. // Register new instance
  120. Popcorn.addInstance( this );
  121. this.options = options || {};
  122. this.data = {
  123. // Allows disabling a plugin per instance
  124. disabled: [],
  125. // Stores DOM event queues by type
  126. events: {},
  127. // Stores Special event hooks data
  128. hooks: {},
  129. // Store track event history data
  130. history: [],
  131. // Store track event object references by trackId
  132. trackRefs: {},
  133. // Playback track event queues
  134. trackEvents: {
  135. byStart: [{
  136. start: -1,
  137. end: -1
  138. }],
  139. byEnd: [{
  140. start: -1,
  141. end: -1
  142. }],
  143. startIndex: 0,
  144. endIndex: 0,
  145. previousUpdateTime: 0
  146. }
  147. };
  148. // Wrap true ready check
  149. var isReady = function( that ) {
  150. if ( that.media.readyState >= 2 ) {
  151. // Adding padding to the front and end of the arrays
  152. // this is so we do not fall off either end
  153. var duration = that.media.duration,
  154. // Check for no duration info (NaN)
  155. videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1;
  156. Popcorn.addTrackEvent( that, {
  157. start: videoDurationPlus,
  158. end: videoDurationPlus
  159. });
  160. that.media.addEventListener( "timeupdate", function( event ) {
  161. Popcorn.timeUpdate( that, event );
  162. }, false );
  163. } else {
  164. global.setTimeout(function() {
  165. isReady( that );
  166. }, 1 );
  167. }
  168. };
  169. isReady( this );
  170. return this;
  171. }
  172. };
  173. // Extend constructor prototype to instance prototype
  174. // Allows chaining methods to instances
  175. Popcorn.p.init.prototype = Popcorn.p;
  176. Popcorn.forEach = function( obj, fn, context ) {
  177. if ( !obj || !fn ) {
  178. return {};
  179. }
  180. context = context || this;
  181. var key, len;
  182. // Use native whenever possible
  183. if ( forEach && obj.forEach === forEach ) {
  184. return obj.forEach( fn, context );
  185. }
  186. if ( toString.call( obj ) === "[object NodeList]" ) {
  187. for ( key = 0, len = obj.length; key < len; key++ ) {
  188. fn.call( context, obj[ key ], key, obj );
  189. }
  190. return obj;
  191. }
  192. for ( key in obj ) {
  193. if ( hasOwn.call( obj, key ) ) {
  194. fn.call( context, obj[ key ], key, obj );
  195. }
  196. }
  197. return obj;
  198. };
  199. Popcorn.extend = function( obj ) {
  200. var dest = obj, src = slice.call( arguments, 1 );
  201. Popcorn.forEach( src, function( copy ) {
  202. for ( var prop in copy ) {
  203. dest[ prop ] = copy[ prop ];
  204. }
  205. });
  206. return dest;
  207. };
  208. // A Few reusable utils, memoized onto Popcorn
  209. Popcorn.extend( Popcorn, {
  210. error: function( msg ) {
  211. throw new Error( msg );
  212. },
  213. guid: function( prefix ) {
  214. Popcorn.guid.counter++;
  215. return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter );
  216. },
  217. sizeOf: function( obj ) {
  218. var size = 0;
  219. for ( var prop in obj ) {
  220. size++;
  221. }
  222. return size;
  223. },
  224. isArray: Array.isArray || function( array ) {
  225. return toString.call( array ) === "[object Array]";
  226. },
  227. nop: function() {},
  228. position: function( elem ) {
  229. var clientRect = elem.getBoundingClientRect(),
  230. bounds = {},
  231. doc = elem.ownerDocument,
  232. docElem = document.documentElement,
  233. body = document.body,
  234. clientTop, clientLeft, scrollTop, scrollLeft, top, left;
  235. // Determine correct clientTop/Left
  236. clientTop = docElem.clientTop || body.clientTop || 0;
  237. clientLeft = docElem.clientLeft || body.clientLeft || 0;
  238. // Determine correct scrollTop/Left
  239. scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop );
  240. scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft );
  241. // Temp top/left
  242. top = Math.ceil( clientRect.top + scrollTop - clientTop );
  243. left = Math.ceil( clientRect.left + scrollLeft - clientLeft );
  244. for ( var p in clientRect ) {
  245. bounds[ p ] = Math.round( clientRect[ p ] );
  246. }
  247. return Popcorn.extend({}, bounds, { top: top, left: left });
  248. },
  249. disable: function( instance, plugin ) {
  250. var disabled = instance.data.disabled;
  251. if ( disabled.indexOf( plugin ) === -1 ) {
  252. disabled.push( plugin );
  253. }
  254. return instance;
  255. },
  256. enable: function( instance, plugin ) {
  257. var disabled = instance.data.disabled,
  258. index = disabled.indexOf( plugin );
  259. if ( index > -1 ) {
  260. disabled.splice( index, 1 );
  261. }
  262. return instance;
  263. }
  264. });
  265. // Memoized GUID Counter
  266. Popcorn.guid.counter = 1;
  267. // Factory to implement getters, setters and controllers
  268. // as Popcorn instance methods. The IIFE will create and return
  269. // an object with defined methods
  270. Popcorn.extend(Popcorn.p, (function() {
  271. var methods = "load play pause currentTime playbackRate mute volume duration",
  272. ret = {};
  273. // Build methods, store in object that is returned and passed to extend
  274. Popcorn.forEach( methods.split( /\s+/g ), function( name ) {
  275. ret[ name ] = function( arg ) {
  276. if ( typeof this.media[ name ] === "function" ) {
  277. this.media[ name ]();
  278. return this;
  279. }
  280. if ( arg !== false && arg !== null && typeof arg !== "undefined" ) {
  281. this.media[ name ] = arg;
  282. return this;
  283. }
  284. return this.media[ name ];
  285. };
  286. });
  287. return ret;
  288. })()
  289. );
  290. Popcorn.forEach( "enable disable".split(" "), function( method ) {
  291. Popcorn.p[ method ] = function( plugin ) {
  292. return Popcorn[ method ]( this, plugin );
  293. };
  294. });
  295. Popcorn.extend(Popcorn.p, {
  296. // Rounded currentTime
  297. roundTime: function() {
  298. return -~this.media.currentTime;
  299. },
  300. // Attach an event to a single point in time
  301. exec: function( time, fn ) {
  302. // Creating a one second track event with an empty end
  303. Popcorn.addTrackEvent( this, {
  304. start: time,
  305. end: time + 1,
  306. _running: false,
  307. _natives: {
  308. start: fn || Popcorn.nop,
  309. end: Popcorn.nop,
  310. type: "exec"
  311. }
  312. });
  313. return this;
  314. },
  315. // Get the client bounding box of an instance element
  316. position: function() {
  317. return Popcorn.position( this.media );
  318. },
  319. // Toggle a plugin's playback behaviour (on or off) per instance
  320. toggle: function( plugin ) {
  321. return Popcorn[ this.data.disabled.indexOf( plugin ) > -1 ? "enable" : "disable" ]( this, plugin );
  322. },
  323. // Set default values for plugin options objects per instance
  324. defaults: function( plugin, defaults ) {
  325. // If an array of default configurations is provided,
  326. // iterate and apply each to this instance
  327. if ( Popcorn.isArray( plugin ) ) {
  328. Popcorn.forEach( plugin, function( obj ) {
  329. for ( var name in obj ) {
  330. this.defaults( name, obj[ name ] );
  331. }
  332. }, this );
  333. return this;
  334. }
  335. if ( !this.options.defaults ) {
  336. this.options.defaults = {};
  337. }
  338. if ( !this.options.defaults[ plugin ] ) {
  339. this.options.defaults[ plugin ] = {};
  340. }
  341. Popcorn.extend( this.options.defaults[ plugin ], defaults );
  342. return this;
  343. }
  344. });
  345. Popcorn.Events = {
  346. UIEvents: "blur focus focusin focusout load resize scroll unload",
  347. MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",
  348. Events: "loadstart progress suspend emptied stalled play pause " +
  349. "loadedmetadata loadeddata waiting playing canplay canplaythrough " +
  350. "seeking seeked timeupdate ended ratechange durationchange volumechange"
  351. };
  352. Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " +
  353. Popcorn.Events.MouseEvents + " " +
  354. Popcorn.Events.Events;
  355. internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ];
  356. // Privately compile events table at load time
  357. (function( events, data ) {
  358. var apis = internal.events.apiTypes,
  359. eventsList = events.Natives.split( /\s+/g ),
  360. idx = 0, len = eventsList.length, prop;
  361. for( ; idx < len; idx++ ) {
  362. data.hash[ eventsList[idx] ] = true;
  363. }
  364. apis.forEach(function( val, idx ) {
  365. data.apis[ val ] = {};
  366. var apiEvents = events[ val ].split( /\s+/g ),
  367. len = apiEvents.length,
  368. k = 0;
  369. for ( ; k < len; k++ ) {
  370. data.apis[ val ][ apiEvents[ k ] ] = true;
  371. }
  372. });
  373. })( Popcorn.Events, internal.events );
  374. Popcorn.events = {
  375. isNative: function( type ) {
  376. return !!internal.events.hash[ type ];
  377. },
  378. getInterface: function( type ) {
  379. if ( !Popcorn.events.isNative( type ) ) {
  380. return false;
  381. }
  382. var eventApi = internal.events,
  383. apis = eventApi.apiTypes,
  384. apihash = eventApi.apis,
  385. idx = 0, len = apis.length, api, tmp;
  386. for ( ; idx < len; idx++ ) {
  387. tmp = apis[ idx ];
  388. if ( apihash[ tmp ][ type ] ) {
  389. api = tmp;
  390. break;
  391. }
  392. }
  393. return api;
  394. },
  395. // Compile all native events to single array
  396. all: Popcorn.Events.Natives.split( /\s+/g ),
  397. // Defines all Event handling static functions
  398. fn: {
  399. trigger: function( type, data ) {
  400. var eventInterface, evt;
  401. // setup checks for custom event system
  402. if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) {
  403. eventInterface = Popcorn.events.getInterface( type );
  404. if ( eventInterface ) {
  405. evt = document.createEvent( eventInterface );
  406. evt.initEvent( type, true, true, global, 1 );
  407. this.media.dispatchEvent( evt );
  408. return this;
  409. }
  410. // Custom events
  411. Popcorn.forEach( this.data.events[ type ], function( obj, key ) {
  412. obj.call( this, data );
  413. }, this );
  414. }
  415. return this;
  416. },
  417. listen: function( type, fn ) {
  418. var self = this,
  419. hasEvents = true,
  420. eventHook = Popcorn.events.hooks[ type ],
  421. origType = type,
  422. tmp;
  423. if ( !this.data.events[ type ] ) {
  424. this.data.events[ type ] = {};
  425. hasEvents = false;
  426. }
  427. // Check and setup event hooks
  428. if ( eventHook ) {
  429. // Execute hook add method if defined
  430. if ( eventHook.add ) {
  431. eventHook.add.call( this );
  432. }
  433. // Reassign event type to our piggyback event type if defined
  434. if ( eventHook.bind ) {
  435. type = eventHook.bind;
  436. }
  437. // Reassign handler if defined
  438. if ( eventHook.handler ) {
  439. tmp = fn;
  440. fn = function wrapper( event ) {
  441. eventHook.handler.call( self, event, tmp );
  442. };
  443. }
  444. // assume the piggy back event is registered
  445. hasEvents = true;
  446. // Setup event registry entry
  447. if ( !this.data.events[ type ] ) {
  448. this.data.events[ type ] = {};
  449. // Toggle if the previous assumption was untrue
  450. hasEvents = false;
  451. }
  452. }
  453. // Register event and handler
  454. this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn;
  455. // only attach one event of any type
  456. if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) {
  457. this.media.addEventListener( type, function( event ) {
  458. Popcorn.forEach( self.data.events[ type ], function( obj, key ) {
  459. if ( typeof obj === "function" ) {
  460. obj.call( self, event );
  461. }
  462. });
  463. }, false);
  464. }
  465. return this;
  466. },
  467. unlisten: function( type, fn ) {
  468. if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) {
  469. delete this.data.events[ type ][ fn ];
  470. return this;
  471. }
  472. this.data.events[ type ] = null;
  473. return this;
  474. }
  475. },
  476. hooks: {
  477. canplayall: {
  478. bind: "canplaythrough",
  479. add: function() {
  480. this.data.hooks.canplayall = {
  481. fired: false
  482. };
  483. },
  484. // declare special handling instructions
  485. handler: function canplayall( event, callback ) {
  486. if ( !this.data.hooks.canplayall.fired ) {
  487. // trigger original user callback once
  488. callback.call( this, event );
  489. this.data.hooks.canplayall.fired = true;
  490. }
  491. }
  492. }
  493. }
  494. };
  495. // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances
  496. Popcorn.forEach( [ "trigger", "listen", "unlisten" ], function( key ) {
  497. Popcorn.p[ key ] = Popcorn.events.fn[ key ];
  498. });
  499. // Protected API methods
  500. Popcorn.protect = {
  501. natives: "load play pause currentTime playbackRate mute volume duration removePlugin roundTime trigger listen unlisten exec".toLowerCase().split( /\s+/ )
  502. };
  503. // Internal Only - Adds track events to the instance object
  504. Popcorn.addTrackEvent = function( obj, track ) {
  505. // Determine if this track has default options set for it
  506. // If so, apply them to the track object
  507. if ( track && track._natives && track._natives.type &&
  508. ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) {
  509. track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track );
  510. }
  511. if ( track._natives ) {
  512. // Supports user defined track event id
  513. track._id = !track.id ? Popcorn.guid( track._natives.type ) : track.id;
  514. // Push track event ids into the history
  515. obj.data.history.push( track._id );
  516. }
  517. track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate );
  518. track.end = Popcorn.util.toSeconds( track.end, obj.options.framerate );
  519. // Store this definition in an array sorted by times
  520. var byStart = obj.data.trackEvents.byStart,
  521. byEnd = obj.data.trackEvents.byEnd,
  522. idx;
  523. for ( idx = byStart.length - 1; idx >= 0; idx-- ) {
  524. if ( track.start >= byStart[ idx ].start ) {
  525. byStart.splice( idx + 1, 0, track );
  526. break;
  527. }
  528. }
  529. for ( idx = byEnd.length - 1; idx >= 0; idx-- ) {
  530. if ( track.end > byEnd[ idx ].end ) {
  531. byEnd.splice( idx + 1, 0, track );
  532. break;
  533. }
  534. }
  535. if ( track.start <= obj.media.currentTime && track.end >= obj.media.currentTime ) {
  536. track.startIndex = idx + 1;
  537. }
  538. if ( obj.media.currentTime >= track.end ) {
  539. track.startIndex = idx + 1;
  540. }
  541. // Store references to user added trackevents in ref table
  542. if ( track._id ) {
  543. Popcorn.addTrackEvent.ref( obj, track );
  544. }
  545. Popcorn.timeUpdate( obj, null );
  546. };
  547. // Internal Only - Adds track event references to the instance object's trackRefs hash table
  548. Popcorn.addTrackEvent.ref = function( obj, track ) {
  549. obj.data.trackRefs[ track._id ] = track;
  550. return obj;
  551. };
  552. Popcorn.removeTrackEvent = function( obj, trackId ) {
  553. var historyLen = obj.data.history.length,
  554. indexWasAt = 0,
  555. byStart = [],
  556. byEnd = [],
  557. history = [];
  558. Popcorn.forEach( obj.data.trackEvents.byStart, function( o, i, context ) {
  559. // Preserve the original start/end trackEvents
  560. if ( !o._id ) {
  561. byStart.push( obj.data.trackEvents.byStart[i] );
  562. byEnd.push( obj.data.trackEvents.byEnd[i] );
  563. }
  564. // Filter for user track events (vs system track events)
  565. if ( o._id ) {
  566. // Filter for the trackevent to remove
  567. if ( o._id !== trackId ) {
  568. byStart.push( obj.data.trackEvents.byStart[i] );
  569. byEnd.push( obj.data.trackEvents.byEnd[i] );
  570. }
  571. // Capture the position of the track being removed.
  572. if ( o._id === trackId ) {
  573. indexWasAt = i;
  574. o._natives._teardown && o._natives._teardown.call( obj, o );
  575. }
  576. }
  577. });
  578. // Update
  579. if ( indexWasAt <= obj.data.trackEvents.startIndex ) {
  580. obj.data.trackEvents.startIndex--;
  581. }
  582. if ( indexWasAt <= obj.data.trackEvents.endIndex ) {
  583. obj.data.trackEvents.endIndex--;
  584. }
  585. obj.data.trackEvents.byStart = byStart;
  586. obj.data.trackEvents.byEnd = byEnd;
  587. for ( var i = 0; i < historyLen; i++ ) {
  588. if ( obj.data.history[ i ] !== trackId ) {
  589. history.push( obj.data.history[ i ] );
  590. }
  591. }
  592. // Update ordered history array
  593. obj.data.history = history;
  594. // Update track event references
  595. Popcorn.removeTrackEvent.ref( obj, trackId );
  596. };
  597. // Internal Only - Removes track event references from instance object's trackRefs hash table
  598. Popcorn.removeTrackEvent.ref = function( obj, trackId ) {
  599. delete obj.data.trackRefs[ trackId ];
  600. return obj;
  601. };
  602. // Return an array of track events bound to this instance object
  603. Popcorn.getTrackEvents = function( obj ) {
  604. var trackevents = [],
  605. refs = obj.data.trackEvents.byStart,
  606. length = refs.length,
  607. idx = 0,
  608. ref;
  609. for ( ; idx < length; idx++ ) {
  610. ref = refs[ idx ];
  611. // Return only user attributed track event references
  612. if ( ref._id ) {
  613. trackevents.push( ref );
  614. }
  615. }
  616. return trackevents;
  617. };
  618. // Internal Only - Returns an instance object's trackRefs hash table
  619. Popcorn.getTrackEvents.ref = function( obj ) {
  620. return obj.data.trackRefs;
  621. };
  622. // Return a single track event bound to this instance object
  623. Popcorn.getTrackEvent = function( obj, trackId ) {
  624. return obj.data.trackRefs[ trackId ];
  625. };
  626. // Internal Only - Returns an instance object's track reference by track id
  627. Popcorn.getTrackEvent.ref = function( obj, trackId ) {
  628. return obj.data.trackRefs[ trackId ];
  629. };
  630. Popcorn.getLastTrackEventId = function( obj ) {
  631. return obj.data.history[ obj.data.history.length - 1 ];
  632. };
  633. Popcorn.timeUpdate = function( that, event ) {
  634. var currentTime = that.media.currentTime,
  635. previousTime = that.data.trackEvents.previousUpdateTime,
  636. tracks = that.data.trackEvents,
  637. tracksByEnd = tracks.byEnd,
  638. tracksByStart = tracks.byStart,
  639. checkTrackEvents = function() {
  640. while ( tracksByEnd[ tracks.endIndex ] && tracksByEnd[ tracks.endIndex ].end <= currentTime ) {
  641. // If plugin does not exist on this instance, remove it
  642. if ( !tracksByEnd[ tracks.endIndex ]._natives || !!that[ tracksByEnd[ tracks.endIndex ]._natives.type ] ) {
  643. if ( tracksByEnd[ tracks.endIndex ]._running === true ) {
  644. tracksByEnd[ tracks.endIndex ]._running = false;
  645. tracksByEnd[ tracks.endIndex ]._natives.end.call( that, event, tracksByEnd[ tracks.endIndex ] );
  646. }
  647. tracks.endIndex++;
  648. } else {
  649. // remove track event
  650. Popcorn.removeTrackEvent( that, tracksByEnd[ tracks.endIndex ]._id );
  651. return;
  652. }
  653. }
  654. while ( tracksByStart[ tracks.startIndex ] && tracksByStart[ tracks.startIndex ].start <= currentTime ) {
  655. // If plugin does not exist on this instance, remove it
  656. if ( !tracksByStart[ tracks.startIndex ]._natives || !!that[ tracksByStart[ tracks.startIndex ]._natives.type ] ) {
  657. if ( tracksByStart[ tracks.startIndex ].end > currentTime &&
  658. tracksByStart[ tracks.startIndex ]._running === false &&
  659. that.data.disabled.indexOf( tracksByStart[ tracks.startIndex ]._natives.type ) === -1 ) {
  660. tracksByStart[ tracks.startIndex ]._running = true;
  661. tracksByStart[ tracks.startIndex ]._natives.start.call( that, event, tracksByStart[ tracks.startIndex ] );
  662. }
  663. tracks.startIndex++;
  664. } else {
  665. // remove track event
  666. Popcorn.removeTrackEvent( that, tracksByStart[ tracks.startIndex ]._id );
  667. return;
  668. }
  669. }
  670. }
  671. // Playbar advancing
  672. if ( previousTime < currentTime ) {
  673. checkTrackEvents();
  674. // Playbar receding
  675. } else if ( previousTime > currentTime ) {
  676. while ( tracksByStart[ tracks.startIndex ] && tracksByStart[ tracks.startIndex ].start > currentTime ) {
  677. // if plugin does not exist on this instance, remove it
  678. if ( !tracksByStart[ tracks.startIndex ]._natives || !!that[ tracksByStart[ tracks.startIndex ]._natives.type ] ) {
  679. if ( tracksByStart[ tracks.startIndex ]._running === true ) {
  680. tracksByStart[ tracks.startIndex ]._running = false;
  681. tracksByStart[ tracks.startIndex ]._natives.end.call( that, event, tracksByStart[ tracks.startIndex ] );
  682. }
  683. tracks.startIndex--;
  684. } else {
  685. // remove track event
  686. Popcorn.removeTrackEvent( that, tracksByStart[ tracks.startIndex ]._id );
  687. return;
  688. }
  689. }
  690. while ( tracksByEnd[ tracks.endIndex ] && tracksByEnd[ tracks.endIndex ].end > currentTime ) {
  691. // if plugin does not exist on this instance, remove it
  692. if ( !tracksByEnd[ tracks.endIndex ]._natives || !!that[ tracksByEnd[ tracks.endIndex ]._natives.type ] ) {
  693. if ( tracksByEnd[ tracks.endIndex ].start <= currentTime &&
  694. tracksByEnd[ tracks.endIndex ]._running === false &&
  695. that.data.disabled.indexOf( tracksByEnd[ tracks.endIndex ]._natives.type ) === -1 ) {
  696. tracksByEnd[ tracks.endIndex ]._running = true;
  697. tracksByEnd[ tracks.endIndex ]._natives.start.call( that, event, tracksByEnd[tracks.endIndex] );
  698. }
  699. tracks.endIndex--;
  700. } else {
  701. // remove track event
  702. Popcorn.removeTrackEvent( that, tracksByEnd[ tracks.endIndex ]._id );
  703. return;
  704. }
  705. }
  706. // time bar is not moving ( video is paused )
  707. } else if ( previousTime === currentTime ) {
  708. checkTrackEvents();
  709. }
  710. tracks.previousUpdateTime = currentTime;
  711. };
  712. // Map and Extend TrackEvent functions to all Popcorn instances
  713. Popcorn.extend( Popcorn.p, {
  714. getTrackEvents: function() {
  715. return Popcorn.getTrackEvents.call( null, this );
  716. },
  717. getTrackEvent: function( id ) {
  718. return Popcorn.getTrackEvent.call( null, this, id );
  719. },
  720. getLastTrackEventId: function() {
  721. return Popcorn.getLastTrackEventId.call( null, this );
  722. },
  723. removeTrackEvent: function( id ) {
  724. Popcorn.removeTrackEvent.call( null, this, id );
  725. return this;
  726. },
  727. removePlugin: function( name ) {
  728. Popcorn.removePlugin.call( null, this, name );
  729. return this;
  730. },
  731. timeUpdate: function( event ) {
  732. Popcorn.timeUpdate.call( null, this, event );
  733. return this;
  734. }
  735. });
  736. // Plugin manifests
  737. Popcorn.manifest = {};
  738. // Plugins are registered
  739. Popcorn.registry = [];
  740. Popcorn.registryByName = {};
  741. // An interface for extending Popcorn
  742. // with plugin functionality
  743. Popcorn.plugin = function( name, definition, manifest ) {
  744. if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
  745. Popcorn.error( "'" + name + "' is a protected function name" );
  746. return;
  747. }
  748. // Provides some sugar, but ultimately extends
  749. // the definition into Popcorn.p
  750. var reserved = [ "start", "end" ],
  751. plugin = {},
  752. setup,
  753. isfn = typeof definition === "function",
  754. methods = [ "_setup", "_teardown", "start", "end" ];
  755. // combines calls of two function calls into one
  756. var combineFn = function( first, second ) {
  757. first = first || Popcorn.nop;
  758. second = second || Popcorn.nop;
  759. return function() {
  760. first.apply( this, arguments );
  761. second.apply( this, arguments );
  762. };
  763. };
  764. // If `manifest` arg is undefined, check for manifest within the `definition` object
  765. // If no `definition.manifest`, an empty object is a sufficient fallback
  766. if ( !manifest ) {
  767. manifest = definition.manifest || {};
  768. }
  769. // apply safe, and empty default functions
  770. methods.forEach(function( method ) {
  771. definition[ method ] = definition[ method ] || Popcorn.nop;
  772. });
  773. var pluginFn = function( setup, options ) {
  774. if ( !options ) {
  775. return this;
  776. }
  777. // Storing the plugin natives
  778. var natives = options._natives = {},
  779. compose = "",
  780. defaults, originalOpts, manifestOpts, mergedSetupOpts;
  781. Popcorn.extend( natives, setup );
  782. options._natives.type = name;
  783. options._running = false;
  784. // Check for previously set default options
  785. defaults = this.options.defaults && this.options.defaults[ options._natives && options._natives.type ];
  786. // default to an empty string if no effect exists
  787. // split string into an array of effects
  788. options.compose = options.compose && options.compose.split( " " ) || [];
  789. options.effect = options.effect && options.effect.split( " " ) || [];
  790. // join the two arrays together
  791. options.compose = options.compose.concat( options.effect );
  792. options.compose.forEach(function( composeOption ) {
  793. // if the requested compose is garbage, throw it away
  794. compose = Popcorn.compositions[ composeOption ] || {};
  795. // extends previous functions with compose function
  796. methods.forEach(function( method ) {
  797. natives[ method ] = combineFn( natives[ method ], compose[ method ] );
  798. });
  799. });
  800. // Ensure a manifest object, an empty object is a sufficient fallback
  801. options._natives.manifest = manifest;
  802. // Checks for expected properties
  803. if ( !( "start" in options ) ) {
  804. options.start = 0;
  805. }
  806. if ( !( "end" in options ) ) {
  807. options.end = this.duration() || Number.MAX_VALUE;
  808. }
  809. // Merge with defaults if they exist, make sure per call is prioritized
  810. mergedSetupOpts = defaults ? Popcorn.extend( {}, defaults, options ) :
  811. options;
  812. // Resolves 239, 241, 242
  813. if ( !mergedSetupOpts.target ) {
  814. // Sometimes the manifest may be missing entirely
  815. // or it has an options object that doesn't have a `target` property
  816. manifestOpts = "options" in manifest && manifest.options;
  817. mergedSetupOpts.target = manifestOpts && "target" in manifestOpts && manifestOpts.target;
  818. }
  819. // Trigger _setup method if exists
  820. options._natives._setup && options._natives._setup.call( this, mergedSetupOpts );
  821. // Create new track event for this instance
  822. Popcorn.addTrackEvent( this, Popcorn.extend( mergedSetupOpts, options ) );
  823. // Future support for plugin event definitions
  824. // for all of the native events
  825. Popcorn.forEach( setup, function( callback, type ) {
  826. if ( type !== "type" ) {
  827. if ( reserved.indexOf( type ) === -1 ) {
  828. this.listen( type, callback );
  829. }
  830. }
  831. }, this );
  832. return this;
  833. };
  834. // Augment the manifest object
  835. if ( manifest || ( "manifest" in definition ) ) {
  836. Popcorn.manifest[ name ] = manifest || definition.manifest;
  837. }
  838. // Assign new named definition
  839. plugin[ name ] = function( options ) {
  840. return pluginFn.call( this, isfn ? definition.call( this, options ) : definition,
  841. options );
  842. };
  843. // Extend Popcorn.p with new named definition
  844. Popcorn.extend( Popcorn.p, plugin );
  845. // Push into the registry
  846. var entry = {
  847. fn: plugin[ name ],
  848. definition: definition,
  849. base: definition,
  850. parents: [],
  851. name: name
  852. };
  853. Popcorn.registry.push(
  854. Popcorn.extend( plugin, entry, {
  855. type: name
  856. })
  857. );
  858. Popcorn.registryByName[ name ] = entry;
  859. return plugin;
  860. };
  861. // removePlugin( type ) removes all tracks of that from all instances of popcorn
  862. // removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn
  863. Popcorn.removePlugin = function( obj, name ) {
  864. // Check if we are removing plugin from an instance or from all of Popcorn
  865. if ( !name ) {
  866. // Fix the order
  867. name = obj;
  868. obj = Popcorn.p;
  869. if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
  870. Popcorn.error( "'" + name + "' is a protected function name" );
  871. return;
  872. }
  873. var registryLen = Popcorn.registry.length,
  874. registryIdx;
  875. // remove plugin reference from registry
  876. for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) {
  877. if ( Popcorn.registry[ registryIdx ].name === name ) {
  878. Popcorn.registry.splice( registryIdx, 1 );
  879. delete Popcorn.registryByName[ name ];
  880. // delete the plugin
  881. delete obj[ name ];
  882. // plugin found and removed, stop checking, we are done
  883. return;
  884. }
  885. }
  886. }
  887. var byStart = obj.data.trackEvents.byStart,
  888. byEnd = obj.data.trackEvents.byEnd,
  889. idx, sl;
  890. // remove all trackEvents
  891. for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) {
  892. if ( ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) &&
  893. ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) ) {
  894. byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] );
  895. byStart.splice( idx, 1 );
  896. byEnd.splice( idx, 1 );
  897. // update for loop if something removed, but keep checking
  898. idx--; sl--;
  899. if ( obj.data.trackEvents.startIndex <= idx ) {
  900. obj.data.trackEvents.startIndex--;
  901. obj.data.trackEvents.endIndex--;
  902. }
  903. }
  904. }
  905. };
  906. Popcorn.compositions = {};
  907. // Plugin inheritance
  908. Popcorn.compose = function( name, definition, manifest ) {
  909. // If `manifest` arg is undefined, check for manifest within the `definition` object
  910. // If no `definition.manifest`, an empty object is a sufficient fallback
  911. Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
  912. // register the effect by name
  913. Popcorn.compositions[ name ] = definition;
  914. };
  915. Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose;
  916. // stores parsers keyed on filetype
  917. Popcorn.parsers = {};
  918. // An interface for extending Popcorn
  919. // with parser functionality
  920. Popcorn.parser = function( name, type, definition ) {
  921. if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
  922. Popcorn.error( "'" + name + "' is a protected function name" );
  923. return;
  924. }
  925. // fixes parameters for overloaded function call
  926. if ( typeof type === "function" && !definition ) {
  927. definition = type;
  928. type = "";
  929. }
  930. if ( typeof definition !== "function" || typeof type !== "string" ) {
  931. return;
  932. }
  933. // Provides some sugar, but ultimately extends
  934. // the definition into Popcorn.p
  935. var natives = Popcorn.events.all,
  936. parseFn,
  937. parser = {};
  938. parseFn = function( filename, callback ) {
  939. if ( !filename ) {
  940. return this;
  941. }
  942. var that = this;
  943. Popcorn.xhr({
  944. url: filename,
  945. dataType: type,
  946. success: function( data ) {
  947. var tracksObject = definition( data ),
  948. tracksData,
  949. tracksDataLen,
  950. tracksDef,
  951. idx = 0;
  952. tracksData = tracksObject.data || [];
  953. tracksDataLen = tracksData.length;
  954. tracksDef = null;
  955. // If no tracks to process, return immediately
  956. if ( !tracksDataLen ) {
  957. return;
  958. }
  959. // Create tracks out of parsed object
  960. for ( ; idx < tracksDataLen; idx++ ) {
  961. tracksDef = tracksData[ idx ];
  962. for ( var key in tracksDef ) {
  963. if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) {
  964. that[ key ]( tracksDef[ key ] );
  965. }
  966. }
  967. }
  968. if ( callback ) {
  969. callback();
  970. }
  971. }
  972. });
  973. return this;
  974. };
  975. // Assign new named definition
  976. parser[ name ] = parseFn;
  977. // Extend Popcorn.p with new named definition
  978. Popcorn.extend( Popcorn.p, parser );
  979. // keys the function name by filetype extension
  980. //Popcorn.parsers[ name ] = true;
  981. return parser;
  982. };
  983. // Cache references to reused RegExps
  984. var rparams = /\?/,
  985. // XHR Setup object
  986. setup = {
  987. url: "",
  988. data: "",
  989. dataType: "",
  990. success: Popcorn.nop,
  991. type: "GET",
  992. async: true,
  993. xhr: function() {
  994. return new global.XMLHttpRequest();
  995. }
  996. };
  997. Popcorn.xhr = function( options ) {
  998. options.dataType = options.dataType && options.dataType.toLowerCase() || null;
  999. if ( options.dataType &&
  1000. ( options.dataType === "jsonp" || options.dataType === "script" ) ) {
  1001. Popcorn.xhr.getJSONP(
  1002. options.url,
  1003. options.success,
  1004. options.dataType === "script"
  1005. );
  1006. return;
  1007. }
  1008. var settings = Popcorn.extend( {}, setup, options );
  1009. // Create new XMLHttpRequest object
  1010. settings.ajax = settings.xhr();
  1011. if ( settings.ajax ) {
  1012. if ( settings.type === "GET" && settings.data ) {
  1013. // append query string
  1014. settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data;
  1015. // Garbage collect and reset settings.data
  1016. settings.data = null;
  1017. }
  1018. settings.ajax.open( settings.type, settings.url, settings.async );
  1019. settings.ajax.send( settings.data || null );
  1020. return Popcorn.xhr.httpData( settings );
  1021. }
  1022. };
  1023. Popcorn.xhr.httpData = function( settings ) {
  1024. var data, json = null;
  1025. settings.ajax.onreadystatechange = function() {
  1026. if ( settings.ajax.readyState === 4 ) {
  1027. try {
  1028. json = JSON.parse( settings.ajax.responseText );
  1029. } catch( e ) {
  1030. //suppress
  1031. }
  1032. data = {
  1033. xml: settings.ajax.responseXML,
  1034. text: settings.ajax.responseText,
  1035. json: json
  1036. };
  1037. // If a dataType was specified, return that type of data
  1038. if ( settings.dataType ) {
  1039. data = data[ settings.dataType ];
  1040. }
  1041. settings.success.call( settings.ajax, data );
  1042. }
  1043. };
  1044. return data;
  1045. };
  1046. Popcorn.xhr.getJSONP = function( url, success, isScript ) {
  1047. // If this is a script request, ensure that we do not call something that has already been loaded
  1048. if ( isScript ) {
  1049. var scripts = document.querySelectorAll( "script[src=\"" + url + "\"]" );
  1050. // If there are scripts with this url loaded, early return
  1051. if ( scripts.length ) {
  1052. // Execute success callback and pass "exists" flag
  1053. success && success( true );
  1054. return;
  1055. }
  1056. }
  1057. var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement,
  1058. script = document.createElement( "script" ),
  1059. paramStr = url.split( "?" )[ 1 ],
  1060. isFired = false,
  1061. params = [],
  1062. callback, parts, callparam;
  1063. if ( paramStr && !isScript ) {
  1064. params = paramStr.split( "&" );
  1065. }
  1066. if ( params.length ) {
  1067. parts = params[ params.length - 1 ].split( "=" );
  1068. }
  1069. callback = params.length ? ( parts[ 1 ] ? parts[ 1 ] : parts[ 0 ] ) : "jsonp";
  1070. if ( !paramStr && !isScript ) {
  1071. url += "?callback=" + callback;
  1072. }
  1073. if ( callback && !isScript ) {
  1074. // If a callback name already exists
  1075. if ( !!window[ callback ] ) {
  1076. // Create a new unique callback name
  1077. callback = Popcorn.guid( callback );
  1078. }
  1079. // Define the JSONP success callback globally
  1080. window[ callback ] = function( data ) {
  1081. success && success( data );
  1082. isFired = true;
  1083. };
  1084. // Replace callback param and callback name
  1085. url = url.replace( parts.join( "=" ), parts[ 0 ] + "=" + callback );
  1086. }
  1087. script.onload = script.onreadystatechange = function() {
  1088. if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) {
  1089. // Handling remote script loading callbacks
  1090. if ( isScript ) {
  1091. // getScript
  1092. success && success();
  1093. }
  1094. // Executing for JSONP requests
  1095. if ( isFired ) {
  1096. // Garbage collect the callback
  1097. delete window[ callback ];
  1098. // Garbage collect the script resource
  1099. head.removeChild( script );
  1100. }
  1101. }
  1102. };
  1103. script.src = url;
  1104. head.insertBefore( script, head.firstChild );
  1105. return;
  1106. };
  1107. Popcorn.getJSONP = Popcorn.xhr.getJSONP;
  1108. Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) {
  1109. return Popcorn.xhr.getJSONP( url, success, true );
  1110. };
  1111. Popcorn.util = {
  1112. // Simple function to parse a timestamp into seconds
  1113. // Acceptable formats are:
  1114. // HH:MM:SS.MMM
  1115. // HH:MM:SS;FF
  1116. // Hours and minutes are optional. They default to 0
  1117. toSeconds: function( timeStr, framerate ) {
  1118. //Hours and minutes are optional
  1119. //Seconds must be specified
  1120. //Seconds can be followed by milliseconds OR by the frame information
  1121. var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/,
  1122. errorMessage = "Invalid time format";
  1123. if ( typeof timeStr === "number" ) {
  1124. return timeStr;
  1125. } else if ( typeof timeStr === "string" ) {
  1126. if ( ! validTimeFormat.test( timeStr ) ) {
  1127. Popcorn.error( errorMessage );
  1128. }
  1129. } else {
  1130. Popcorn.error( errorMessage );
  1131. }
  1132. var t = timeStr.split( ":" ),
  1133. lastIndex = t.length - 1,
  1134. lastElement = t[ lastIndex ];
  1135. //Fix last element:
  1136. if ( lastElement.indexOf( ";" ) > -1 ) {
  1137. var frameInfo = lastElement.split( ";" ),
  1138. frameTime = 0;
  1139. if ( framerate && ( typeof framerate === "number" ) ) {
  1140. frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate;
  1141. }
  1142. t[ lastIndex ] =
  1143. parseInt( frameInfo[ 0 ], 10 ) + frameTime;
  1144. }
  1145. if ( t.length === 1 ) {
  1146. return parseFloat( t[ 0 ], 10 );
  1147. } else if ( t.length === 2 ) {
  1148. return ( parseInt( t[ 0 ], 10 ) * 60 ) + parseFloat( t[ 1 ], 10 );
  1149. } else if ( t.length === 3 ) {
  1150. return ( parseInt( t[ 0 ], 10 ) * 3600 ) +
  1151. ( parseInt( t[ 1 ], 10 ) * 60 ) +
  1152. parseFloat( t[ 2 ], 10 );
  1153. }
  1154. }
  1155. };
  1156. // Exposes Popcorn to global context
  1157. global.Popcorn = Popcorn;
  1158. document.addEventListener( "DOMContentLoaded", function() {
  1159. // Supports non-specific elements
  1160. var dataAttr = "data-timeline-sources",
  1161. medias = document.querySelectorAll( "[" + dataAttr + "]" );
  1162. Popcorn.forEach( medias, function( idx, key ) {
  1163. var media = medias[ key ],
  1164. hasDataSources = false,
  1165. dataSources, data, popcornMedia;
  1166. // Ensure that the DOM has an id
  1167. if ( !media.id ) {
  1168. media.id = Popcorn.guid( "__popcorn" );
  1169. }
  1170. // Ensure we're looking at a dom node
  1171. if ( media.nodeType && media.nodeType === 1 ) {
  1172. popcornMedia = Popcorn( "#" + media.id );
  1173. dataSources = ( media.getAttribute( dataAttr ) || "" ).split( "," );
  1174. if ( dataSources[ 0 ] ) {
  1175. Popcorn.forEach( dataSources, function( source ) {
  1176. // split the parser and data as parser!file
  1177. data = source.split( "!" );
  1178. // if no parser is defined for the file, assume "parse" + file extension
  1179. if ( data.length === 1 ) {
  1180. data = source.split( "." );
  1181. data[ 0 ] = "parse" + data[ data.length - 1 ].toUpperCase();
  1182. data[ 1 ] = source;
  1183. }
  1184. // If the media has data sources and the correct parser is registered, continue to load
  1185. if ( dataSources[ 0 ] && popcornMedia[ data[ 0 ] ] ) {
  1186. // Set up the media and load in the datasources
  1187. popcornMedia[ data[ 0 ] ]( data[ 1 ] );
  1188. }
  1189. });
  1190. }
  1191. // Only play the media if it was specified to do so
  1192. if ( !!popcornMedia.autoplay ) {
  1193. popcornMedia.play();
  1194. }
  1195. }
  1196. });
  1197. }, false );
  1198. })(window, window.document);
  1199. // PLUGIN: Attribution
  1200. (function( Popcorn ) {
  1201. /**
  1202. * Attribution popcorn plug-in
  1203. * Adds text to an element on the page.
  1204. * Options parameter will need a mandatory start, end, target.
  1205. * Optional parameters include nameofwork, NameOfWorkUrl, CopyrightHolder, CopyrightHolderUrl, license & licenseUrl.
  1206. * Start is the time that you want this plug-in to execute
  1207. * End is the time that you want this plug-in to stop executing
  1208. * Target is the id of the document element that the text needs to be attached to, this target element must exist on the DOM
  1209. * nameofwork is the title of the attribution
  1210. * NameOfWorkUrl is a url that provides more details about the attribution
  1211. * CopyrightHolder is the name of the person/institution that holds the rights to the attribution
  1212. * CopyrightHolderUrl is the url that provides more details about the copyrightholder
  1213. * license is the type of license that the work is copyrighted under
  1214. * LicenseUrl is the url that provides more details about the ticense type
  1215. * @param {Object} options
  1216. *
  1217. * Example:
  1218. var p = Popcorn('#video')
  1219. .attribution({
  1220. start: 5, // seconds
  1221. end: 15, // seconds
  1222. target: 'attributiondiv'
  1223. } )
  1224. *
  1225. */
  1226. Popcorn.plugin( "attribution" , (function(){
  1227. var
  1228. common = "data:image/png;base64,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…

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