PageRenderTime 114ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 2ms

/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
  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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",
  1229. licenses = {
  1230. "cc-by": common + "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",
  1231. "cc-by-sa": common + "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",
  1232. "cc-by-nd": common + "grSURBVHja7FpNbBvHFf72R0YdROz6lBZsAQrogczFtB37aFF1AqR1bC1h2Jc0NXUqEKEgmTZqWkimaMupS9ilicJJA7fRojkHWvkH6B/MpRqgNSWLKzgAeSjAPURoe5IipYeKuzs97O5wl1xSFCWljeNnjHa5M/Ptzjdv3nvzxgwhJMAwzKd4KnsuhBCGAUAA4P4f74FlWbAsC47jwLIcOJYFy9lXlgXDsGAZBgzDAAzjoICAgJgEJjFhmlYxDMO6mgYMw4RpGrTOJCZtTwhxPobePwlyfvQCAIABQBxyOY6zCss17znOqmM5m2QGDMO6+bXJsYk1LTINwy7ue8NLsmmalFg30U8SyTwAL7kcD95ztcrd+XsoFosol8vY3Nj0AA0GBnHixAmMfHsEZ86+AsPkwLK6NSEGCwaMPZeu5WMSayXAXkNMd3KXFyuQP5RRrVY9zyORCMRzIo4eP7IrMvYLnwFA/vDg9+B5npLK87xVOB4lZQG5azmsrq72BBgMBjHx0wkMD5+EbhgwDB26bhdDh64bVMP9NLlVi//5j3/hVuEWatUaACAWiyEajQIAVFWFoigAgHAkjPHkOL729ed2RMB+4p8fvWAR/OfSn8BzfJNYfgADPI/M1DTkOZl2EAQBoigiFApheHgYAFAqlaBpGmRZxvr6Om0rxkX8eOJHOPjMQei6joauQ9cb0HXdazIcgk3blruI/mzjMyTHU9jY2IAoisjn8wiFQp5BaJqGdDoNWZYRCARwNXe1ZxL2G58S/OAvDzDgEDvAY4Af8JArCAJSqRSSySQEQegIKEkS0uk0JTocCeM379/GVw4ehK430Gg0qCb7ktxij6feuoRatYZEIoHZ2dnmsrNNi1vTJUnC2NgYwpEwrly73BMBnfA7jW2n+OdHL4AFAM62wTzHgee8mhuNRlGpVJDJZLqSCwCJRAL1ep0usVq1huu5Gx5bztKIhGkW+5+bwOXFCmrVGkRRxMSbb247mEQiAVEUUavWsLxY6cnm7ie+IywAsE5YxnEoKQsecovFYtuy6SaCIKBYLFKS5TkZpdICOI73J5l1wj54SJY/tL4hn88j8vzzrfGlr0PM5/Oevt2kG34n2Qm+h2BLgy0tzl3LUaJmZ2e31dpuJDt9cz/P2bG0TS5jx9SsHWEwaJJsL/9qtYpYLNY2uWNjY1Tzx8bGPHWhUAixWKwtEvATP/xvhYZ8Sz/4Xg22B393/h6NFlKpFNXCfkQQBDrjq6uruHvnHt2wWCR3NhGO+L1fkiTf+259Oklr25deftm39IsPwIqDHW0qFouUnGQySRspioJCoUCdVywWQyaT8a0bHR1FKpWidstxesUHRbxy5rStvbZpMJskOyaC4H+30Xj31+/uOaa10WAYsAyDcrlshViiSJe3oigYGRnxdFIUBYqiIJlMIh6Pt9WtrKxQryyKIiRJQrlctnaArItUNMltRuVNLFVVfZ2No7mJRKKt3q9PJ2lt6zYHbvm7Vu8Ln5oIZ8DODu3w4cO0QTqdpvanXq9Tp3fx4kVks1m6bOr1Oubm5jwxMgB6v7mx2TQH9Orw2m4iIpEIFEWBpmme5+5wqjW00jQNiqIgEolsO3A//FMvvehb+sH3aLDbubTaGWfGEokEQqEQJdpxOI6WOnWiKLY5nmb4Rf9s+2HiORHVmSrS6TTm5uZ6GoyjDOI5sS/8927f3jN8jwb/P8rR40cQjoQhy3JbtNBp8LIsIxwJ95Q32G98L8HEuyty2xlHmyVJwvr6OlRVxdDQELLZbDPWtbfJ7jr3smrGr/RPTx/3k59NIBAIQJIkxONxiuWOgzVNQzwex82bNxEIBDCeHO958J3wW81Ov/jURDgfPBgYxObGJlZWVmiDTCZDX37o0CHPi506VVXb6hxxsAYDgyDEtR0GgTOn9q+2j3s28CwKt27iF2/nIMsyZFlGNBpFNBqFIAhQFIUqQz/JmP3Gp3774aOHOHDgALKXspDnZAiCgLW1tZ7CNFmWUSgUaFt3HQAMDQ1B0zScevEUbuSv4z9bW2g0ttDQG2g0dBhOXsI0YBomTcK37tS+iOlKmuz529JfMTBwAB8tfITkD5N0W+jEs/2KkyABgJm3Z/Dd09/BVmMLW40G9EYDDb2FYJ+Ezxc94c4CoEc6sZFhBINBAEA2m/W1Sb3K+vo69brBYNBOxDupSROmbYsdc/GkCgsAJjFhGNY52pWrlylB8Xjck+PdCbkjIyO078RbE3aC3WiS7EpRUidLnqwjI+rcAZDJzCRZXC4T9XGFvPb91xxXT6LRKKlUKqRXqdfrJBqN0v5iXCTq4wpZXC6Ty1cv0+dfotL8kXojSZYqi0T9WCViXKTPBUEg09PTZG1trSOxa2trZHp6mgiCQPsdP3GcrHyskiV1kUxmJr+M5BKmNSidykxiNC6C53ncyN3AB7/7oO8jo+yVaei6jocPy3j9B6/3v8RcZsN9muHefbb+3im+H5bfe/s2Ee4ylZm0NPlxhbwv/ZYEg8GeZywYDJLCrwpEfVwhS+oieee9d3atBYSQrvfuZ/3ib4fb7zuYTtuq1BtJvPq9V+kphFIs4c78na7H9mfFs4jFhulZ2/z8HcxkZ3bvJLpo0m40109j/a67eQ/Tbd969NgRTGUu4RvfDLpOI9zpRnjiVuc/nqx+8gl+eT2PhdLC3njhLgPdS4Ldk/m5EOzIyeGTOH3mNI69cAxfDQS8OVw7tPp0YwOPlh7h/t37e0bs563B+2GDeyL4qfQvDCHkGYZh/v2Uin3YYBDC/HcArOiX8zGX6zMAAAAASUVORK5CYII=",
  1233. "cc-by-nc": common + "k0SURBVHja7FpdbNvWFf5IysFS1BrztA1yMBt7sQqskZMmy4Ytlta9LJ4TCnaCFkkWuQ812mCTlB+3S+3Iyk8TK/Zkb0iBYVstrCjahwZm/oDNGSLaKzBbTiIZaSM9rJCK2FiHDbArpwVmkbx7EHlF2pIty3axpjnGFX/uvR/J75577jnnmiGEWBmG+RSPZc2FEMIwAAgA3Bi+DpZlwbIsOI4Dy3LgWBYspx1ZFgzDgmUYMAwDMIyOAgICohKoRIWq5ouiKPmjqkBRVKiqQutUotL2hBD9Zej5oyD79u4HADAAiE4ux3H5wnKFc47L17GcRjIDhmGN/GrkaMSqeTIVRSvGc8VMsqqqlFgj0Y8SyRYAZnI5CyymY75cu3Id0WgUsVgMc9k5E1C1tRo7duyA68cuNO/5GRSVA8vK+QFRWDBgtLE0TB+V5GcCtDnELE3u3Yk4xMsiksmk6b7dbofQImDr9oZVkbFe+AwA8pdbf4bFYqGkWiyWfOEsGJFGEboQwvT0dFmANpsNHb/qQGPjLsiKAkWRIctaUWTIskI1vJgmL9TiT/75L1wauIRUMgUAcDqdcDgcAIBEIgFJkgAA9fZ6HPEewTe/9Y0VEbCe+Pv27s8T/NeRm7BwlgKxlipUWSwIdHVDHBJpB57nIQgCamtr0djYCAAYGRlBJpOBKIqYnZ2lbQW3gOMdx7DxiY2QZRk5WYYs5yDLstlk6ASrmi03EP0w+xDeIz5ks1kIgoBwOIza2lrTR2QyGfj9foiiCKvVinOhc2WTsN74lOBbf7uFKp3YKguqLFUmcnmeh8/ng9frBc/zJQEjkQj8fj8lut5ejz+8+Xt8beNGyHIOuVyOanJRkhfY465XTyGVTMHj8WBwcLAw7TTTYtT0SCSCtrY21NvrcebC6bIIKIX/m/5+jI+N4+1331kV/r69+8ECAKfZYAvHwcKZNdfhcCAejyMQCCxJLgB4PB6k02k6xVLJFHpDfSZbzlKPhCkU7c9I4N2JOFLJFARBQMeJE8t+jMfjgSAISCVTuDsRL8vmppIpbG1owA92ft9E7oVQCNdu3MArx09gamqqInxdWABgdbeM4zAijZrIjUaji6bNUsLzPKLRKCVZHBIxMjIKjrMUJ5nV3T6YSBYv598hHA7D/tRTC/3LogtiOBw29V1K9DafP/wMPefPw/nDH+GlF9vh9fvR3t6OkydPItTXi/GxsYrwTQTnNTivxaELIUrU4ODgslq7FMl639D5kOZLa+Qymk/Nah4GgwLJ2vRPJpNwOp2LBretrY1qfltbm6mutrYWTqdzkSdQTHT85uZm7Nu/H1NTU7g5PIzvfLsWn889xMFDB3H/ww/R0tpaEb5Zg7WPv3blOvUWfD4f1cJKhOd5OuLT09O4dvU6DVjyJJc2EboUe34kEil6vlSfUuJwOBDq68X5UA/efvcdtLS24qOPMwj19WLz5s2IvDmI5P37FeNTgnVtikajlByv10sbSZIEt9sNl8sFl8uFYDBYsq6/v99kF3Utjt6KGrS3YBoYpriJ+KLlezt3oqf3Ih48eICOY8fR8N2ncfm999C8uwkHnnseN4eHK8LNBxoMA5ZhEIvF8i6WIFBiJEmCy+UydZIkCZIkwev1wu12L6qbnJykq7IgCIhEIojFYvkI0EAsUCC34JUXsBKJRNHFTNdcj8ezqL5Yn1KysG02m8XN4WH09F6E534bmnc3AQDGx8YwPjaGmpoaMFWWSjQ4/6F6hLZlyxbawO/3U/uTTqfponf48GGqyQ6HA+l0GkNDQyYfGQA9n8vOFcwBPeq8LjYRdrsdkiQhk8mY7hvdKeO57rNKkgS73b7shxfDf+nFdpw7fQZbn96CA889j48+zqCltRU9vRdx4ODBFeGbCDYuLgvtjD7KHo+HGvl0Og2Px0Pr9OBDEARaZ1wYCu4X/Vn2xYQWwTTA5YjeVu+7Uvye3otoe+EFfPKff+Mf6TQGwmG8dqoLLa2tCJ49g4btz5SNbyb4/1C2bm9Avb0eoigu8hZKkSuKIurt9WXlDYrh19TU4LVTXTjmP4rmpib80ueD1WqtCN9MMDFHRUbbpGtzJBLB7OwsEokE6urqEAwGC76uFiYb64zTtuC/0p+yXu6Vkx2wWq2IRCJwu90Uy+gHZzIZuN1u9Pf3w2q14oj3SNkfXwr/2InjNIpbDT5d5PQXrrZWYy47h8nJSdogEAjQh2/atMlEnF6XSCQW1emiY1Vbq0GIIRwGgT6m2tWil3vS+iQGLvWj5/UQRFGEKIpwOBxwOBzgeR6SJFFlqCQZs974dN0evzOODRs2IHgqCHFIBM/zmJmZMXkGAwMDNMfgdDoRCASo9g4MDNC2xjoAqKurQyaTwbM/eRZ94V78d34eudw8cnIOuZwMRc9LqApURaVJ+IWR2pcxXUmTPWO3/46qqg14f/R9eH/hpWGhz+db1UvrCRIAOPv6Wexu+inmc/OYz+Ug53LIyQsILpLw+bIn3FkAdEvH6WqEzWYDAASDwUUu0kpkdnaWrtA2m01LxOupSRWqZot1c/GoCgsAKlGhKPl9tDPnTlOC3G63Kce7EnJdLhft2/Fqh5ZgVwokG1KUdJElj9aWEV3cAZDOQCeZuBsjiXtxcujnh/SlnjgcDhKPx0m5kk6nicPhoP0Ft0AS9+Jk4m6MnD53mt7/CpXChe+ol9yOT5DEBwkiuAV6n+d50t3dTWZmZkoSOzMzQ7q7uwnP87Tf9h3byeQHCXI7MUE6A51fRXIJs9Ap7Qp0Yq9bgMViQV+oD2/96a2Kt4yCZ7ohyzLGx2N4uf3lyqeYwWwYdzOM0efC65Xil8LSn10pNoqx3hXozGvyvTh5M/JHYrPZyh4xm81GBn47QBL34uR2YoK88bs3Vq0FhJAlz433KsVfDrfSZzClwirfUS8OHDxAdyGk6AiuXrm65Lb9HmEPnM5Gutd25cpVnA2eXf0iUUSD10JzF2KUOq5GmKXi1q3bGtAVOIWazTbDboQx3QiT36r/48n01BR+3RvG6Mjo2qzCC6bsWpmG5UzCUs9dE4J12dW4C03NTdj2zDZ83Wo153A11+rTbBZ3bt/BjWs31ozYL1qD18MGl0XwY1mFiSCEPMEwzGePqViHAIMQ5n8DAFb/49reYmyHAAAAAElFTkSuQmCC",
  1234. "cc-by-nc-sa": common + "pvSURBVHja7FptbFPXGX7utYlGJzz/2yYHYYQ2xZFWHAq0dLSx161TS9NcLylfocNmWtuVdUlKCNvIl4FAY0Id91Ob1sRrV7VaqTBfaxc6fEPQ4sRJbEaL82OVjZKoVJvm4KCpxB/vflzfE9/EThxo1Y72lY7v8T3nPPfc57znPe95z+WISMNx3FV8JZ+6EBHHASAAON19CjzPg+d5qFQq8LwKKp4Hr0pfeR4cx4PnOHAcB3CcjAICgVKEFKWQSkkpmUxK11QSyWQKqVSSlaUoxeoTkdwZlr8V5JHyjQAADgDJ5KpUKinxqum8SiWV8ao0yRw4js/kN01OmtiURGYymU6Z+aSS5FQqxYjNJPpWIlkNQEmuSg214iqlk8dPwev1YmBgAJOxSQXQEs0SrF27FuYfmFH28ENIplTg+YQ0IEkeHLj0WGZMnxRJMwHpOcRJ5A77A/C87UEoFFLUNxgMECoErFpTktfLfVFwOAD017PvQq1WM1LVarWUVGr0iOfgeMaB8fHxvDqk0+lQ/5t6lJbei0QyiWQygUQinZIJJBJJpuGZmvzR+Ed4vuMFjIRGAAAmkwlGoxEAEAwGIYoiAKDIUISd1TvxrW9/M+vzr3z0MV50vfiFwHmkfKNE8Hs9Z6BWqaeJVS/CIrUazY0t8BzzsAZarRaCIECv16O0tBQA0NPTg0gkAo/Hg4mJCVZXsAioq9+FxbctRiKRQDyRQCIRRyKRUJoMSuFq9Cp++cRTiMViEAQBTqcTer1e0dlIJILa2lp4PB5oNBq0OlpnvdS12DVU76z5wuDIdpjO9p6l3r5z1Ofvo8Ggny68HyTBIlB68pJWq6WWlhaKRqM0l3R1dZFWq2XtigxFdL6vlwaDg+Qb7KPevnPk7T1LZ8Ruevdv79Dp7lN04p3jZDAYCABZrVYFnowz8xky9lvH/6xIRYairDgup5O2btp8Uzijo6Pk6+sjX18fjY6O5oUDgHgAUKVtsFqlglql1Fyj0YhAIIDm5mZotdo5zYPVakU4HGZTaSQ0gnbHEYUt55lHInkjfp8foVAIgiCgfvfueU2Q1WqFIAgYCY1g2B9Q2MqR0AhWlZTg7rvWsfvPdXTgGYcDJ0+fxp663RgbG8sLJ7M/f3r1VZjW34OqzVtQtXkLTOvvwZnu7jlxFOtNr6+XfIM+Gr4wRK7nXUxzjEbjvFqbTaLRKBmNRobjesFFw/8Ypv4hH5339ZL3vKTF77z3FzIUS9obDofzxg+HwwSADAYD0xZ5FhR957u0YpmeSr+/np74+WMEgFpaWujQwUMEgI6+9VZeOHJ/fH19Et6d6+hn221Uv6uOVizT04plenI5nTlxsmiwpMWOZxzM3nZ1dc2rtdlEq9XC6/Wyto5DjrQvndZgLu1T8zxCl0IwmUyzbJzNZmNabrPZFGV6vR4mk0mxsodCEk5ZWRke2bgRY2NjONPdjRXL9Pjv5DVse3QbLn3wASoqK/PC0ev1iMViCAUuAgDKhZ/gD+5OtLUfxt6mRgCAu7MrJ44svOym8bzkisneQk1NDZvqNyJarRZOpxMAMD4+jpMnTrENi0Qyx9y0bM9xu91Z87Jka2M0GuE40o5Djja8/uYbqKisxIeXI3AcacfSpUvh7uxC6NKlvHBkaX1WUrjf//EVdu9H998PAIjFYvj4ypWcOIxgWZu8Xi8jp7q6mlUSRREWiwVmsxlmsxl2uz1nWUdHh8JeylrsPevN0F4OHD9N8Gchd951F9raD2N0dBT1u+pQ8r3b8fbRoyh7cAOqNm9hNnQu0Wg0cLlcuE2zBC+//HLWOp98cn1ODGmjwXHgOQ4DAwOSiyUIjBhRFGE2mxWNRFGEKIqorq6GxWKZVXbhwgV0dXUxLLfbjYGBAWkHmCZWIpdjfmW2xUzWXKvVOqs8W5uZ92KxGM50d6Ot/TCsl2woe3ADAKDf50O/z4fCwkJwi9Rz4ixSq1FfV4fbFi9m9/p9PpZfpl+Wsz8ZGiy9sLxDW7lyJatQW1vL7Ew4HIbX64Ver8f27duZJhuNRoTDYRw7dkzhIwNg+cnYpPQccBlXoLi4GKIoIhKJKDomD9DMvOyDiqIIg8Gg2FnNxPnFY4+jdd9+rLp9Jao2b8GHlyOoqKxEW/thVG3blhfO2NgYWpqasXXTZrTu24/WffvR1NAAANi9Z0/O/igIBgfFdM20J/LIWK1WZszD4TCsVisrkzcfgiCwssyFhG0bOfYz7YxvqlQMZD4i1xUqhOmNTTqfidPWfhi2HTtw5d//wj/DYbicTuxtakRFZSXsB/ajZM3qeXFsO3bAtmOHNNCdnejq7MT1T65jQ9lD2FK1NWd/FCbi85R169fBUGyAx+OBzWabpa3ZyPV4PCgyFCniAKvWlKDIUKTAKSwsxN6mRnxt8WIMDw3hVzU1N4Szt6kRP37gAVy+LGl1cXExDMXFc+IoNZiUUaxMeyJrs9vtxsTEBILBIJYvXw673c7K5G1yZlnmdJ6Oj7IfRScaWxqh0WjgdrthsVhYm8woWyQSgcViQUdHBzQaDXZW75z1Mnt+W58VZ9fuOrz+5hs3hbN6zWpUVFaiorIShuLivHBYsMc/PICCggKsv/seTMYmYbVamSZ5PJ5ZC5lsMsrLy3OWye1ra2vR0dGBJZolOP/3XkxNTWEqPoV4Io54PCEFg5IJRP8zgYP2g8yXNBqNMBqN0Gq1EEWRDfp8QZprsWtoO+hgQZrPE4cFe/qH+lFQUAB7kx2eYx5otVpEo1GFZ+ByuVgwx2Qyobm5mQ2Ay+VidTPLAGD58uWIRCK474f34YizHdenphCfQbAcN04lU/D3+3Hs6K0RrmQE+wb7sGhRAc6fO4/qpyT/1+l0oibDZt2IuN1utgs7cPAAHtzwAKbiU5iKx5GIxxFPzCA4SwD+/z3gzgNgRzomcyl0Oh0AwG63z3KdFiITExNsddXpdOlAfPoUI5VCKm2LKX3kdKsKDwApSiGZlM7R9rfuYwRZLBZFjHch5JrNZta2/tf16QB7cprkjCMjtsjSrXVkxBZ3ANTQ3ED+4QEKXgzQoz99VBFRCwQCC4p0ZUbSBItAwYsB8g8P0L7Wfez+lyhN/6l5upoGA34K3kDAPRqNUktLiyLgvmbtGrrwfpAGg35qaG74MpJL3EyntLG5AeUWAWq1GkccR/Daq6/d8JGRfX8LEokE+vsH8OTjT+bzHUHGro9j9zJ3mTP/58LJ1UZ+Rr6Bplx9WhDGzNTY3CBp8sUAdbpfIZ1Ol/eI6XQ6cj3vouDFAA0G/fTS717Ku+3MY6KZ+cx78+HM1z4frGx1FooxS4NlqXm6GlXbqthRj+jtwYnjJ+Y8tn9YeBgmUyk70Dx+/AQO2A8s5EuYWdqyEM2dWTfXdYFf52TV3lz9zLqTy1W46o4SNDY3oXCpLuM0IjPcCIXfKn94Mj42hmfbnTjXc27BL3MzpmE+kzAX/kIHLV+MOQmW5d7Se7GhbAPuWH0HvqHRpD+dmjYwRISrsRiGBodw+uTpBRP7WWnwzdrg+daET43gr+QmNhpE9PWvaPiMNhhE3P8GAG3CFDKJWtqSAAAAAElFTkSuQmCC",
  1235. "cc-by-nc-nd": common + "m8SURBVHja7FpdcBvVFf52pXgGplH11mbkDPbQdqy8oIQmMZRiufwMxRivJiHtFChyZwqUlMoiiWlaO5JCfkBNKqvhp30oUsswMCVMlL9CHRqt4xTLkmKtE7D8UMZisIf2pZLltDO1Vnv6sNprrS1bsgNDGjgz17vW3fvt3W/PPfe75y5HRCaO46bxhX3iRkQcB4AA4HT/KfA8D57nYTAYwPMGGHgevKF05HlwHA+e48BxHMBxGgoIBFIICilQFLUUi0X1qBRRLCpQlCKrU0hh1xOR1hl2fi3YAx3bAAAcANLINRgMauENc+cGg1rHG0okc+A4vpzfEjklYhWVzGKxVMrPi3qSFUVhxJYTfS2RbASgJ9dghFF3VMvJ46cQjUYRj8cxk5/RAa02rcamTZvQ+p1WtN9/H4qKATwvqy+kyIMDV3qXZcNHIXUkoDSGOJXckUQKkTcjSKfTuuutViuELQI2bFxf08NdLTgcAPrL2bdhNBoZqUajUS0GIwbEc/A/68fU1FRNHbJYLOje3Y2WltshF4soFmXIcqkUZchykXl4uSd/PPUxjvQ9j/H0OADAbrfDZrMBACRJgiiKAIAmaxO2u7bjq2u+UvH+//j4n3gh+MJVgfNAxzaV4HcGzsBoMM4Ra1yFVUYjPL1eRI5FWAOz2QxBENDQ0ICWlhYAwMDAADKZDCKRCHK5HLtWcAjY2b0D111/HWRZRkGWIcsFyLKsDxmkYDo7jZ8+/iTy+TwEQUAgEEBDQ4Ous5lMBm63G5FIBCaTCfv9+xc81OX8Zbi2d101OFocprODZ2lw6BwNJYYoKSVo9D2JBIdApcFLZrOZvF4vZbNZWspCoRCZzWbWrsnaROeHBikpJSmWHKLBoXMUHTxLZ8R+evuvb9Hp/lN04q3jZLVaCQA5nU4dnoYz/x4a9hvH/6QrTdamijjBQIB+8L3vXzHOYs+8GA4A4gHAUIrBRoMBRoPec202G1KpFDweD8xm85Lhwel0YmJigg2l8fQ4DvkP62I5zxSJqkYSsQTS6TQEQUD3rl1VQ5DT6YQgCBhPj2MkkdLFyvH0ODasX49bm29hv/+mrw/P+v04efo0nt65C5OTkzXhXGl/dPPNYGyQYskYjYxeoOCRIPMcm81W1WsrWTabJZvNxnCCzwdp5OIIDV+I0fnYIEXPq1781jt/Jus61XsnJiZqxp+YmCAAZLVambdoo6Dp69+gG29ooJZv3UaP//hRAkBer5cOHjhIAOjoG2/UhHOl/angwaoX+5/1s3gbCoWqem0lM5vNiEajrK3/oL+kpUsezJU0Nc8jPZaG3W5fEOM6OzuZl3d2durqGhoaYLfbdTN7Oq3itLe344Ft2zA5OYkz/f248YYG/GfmMh56+CGMvf8+tmzdWhNOeX++1tBYsSyFoxmvyTSeV6WYpha6urrYUF+Jmc1mBAIBAMDU1BROnjjFFiwqyRyTaZXuEw6HK55rVqmNzWaD//AhHPQ/h1dffw1btm7FBx9m4D98CGvXrkX45RDSY2M14ZTbXffcU7FUwwGg6mDNm6LRKCPH5XKxi0RRRDAYZCrBbrfD4/FUrOvo6EBXVxeLT263G7lcDtGzUdzX3lbyXg4cz4FTuE9N5G9ubsbm5mY82eXCkb4gzvT3482jR/Hm0aPY3NwM5486cdfdd9eE9dJvX1pxP9SFBseB5zjE43FVYgkCG96iKKK1tVXXSBRFiKIIl8sFh8OxoG50dBShUIhhhcNhxONxdQXIc2zoa4sPSZIqTh6a5zqdzgX1ldrM/y2fz+NMfz+eO/QrOMc60X5vGwBgOBbDcCyG+vp6cKuMVXHKw0G5/T0zsWR/yjxYfWBthXbTTTexC9xuN4sz0WgUmUwGnZ2deOSRR+Dz+djwOHbsGCRJgtvtZhoZAFpaWhAOhzGTn1HvA67sCKxbtw6iKCKTyejiXigUYgRrL6tcg4qiCKvVqltZzcf5yaOPYTgWw5G+IADggw8z6N6xE5uaN+OiNIo/hMP4cGqyKs4dd925pJdW6o9ORSSlBF0au8hm/Wg0ukCLer3eBbPnUnWaRaNRdt2lsYuUlJL0bvxdGvibSO8MnCGPbw8BIEEQFsWfb4KgavTdPbvZjL27Z/cCnI8++oj2+fbSmjVraPWXVlMwEKDp6ell41SzSjg6FfFZ2i233QLrOisikcgCtVDJtNVTk7VJlwfYsHE9mqxNOpz6+nr8ck8vdrifQntbG37W1QWTybRsnJX0R6ciQPosVnk80WbHcDiMXC4HSZLQ2NgIn8/H6rRlcnld+fCZy4+yP7pO9Hp7YTKZEA6H4XA4WJvyLFsmk4HD4UBfXx9MJhO2u7YveJinf9FdEWfHrp149fXXrhhnfliohsOSPYmROOrq6nDbrd/GTH4GTqeTxb1IJLJgItMmno6OjkXrtPZutxt9fX1YbVqN8+8OYnZ2FrOFWRTkAgoFWU0GFWVk/5XDAd8BpiVtNhtsNhvMZjNEUWQvvVqS5nL+Mp474GdJms8ShyV7hi8Mo66uDr49PkSORWA2m5HNZmuSaZFIBMFgkF1bXgcAjY2NyGQyuOPOO3A4cAj/nZ1FYR7BWt5YKSpIDCdw7Oi1ka5kBMeSQ1i1qg7nz52H60lV/wYCAaZnV2rhcJjFsX0H9uHetu9itjCL2UIBcqGAgjyP4AoJ+P/3hDsPgG3p2FtbYLFYAAA+n69i7KnVcrkck3gWi6WUiC/tYigKlFIsptKW07VqPAAopKBYVPfRntm/lxHkcDh0Od7lkNva2sradv+8u5RgL86RXLZlxCZZura2jNjkDoB6PD2UGImTdClFD//wYV1GLZVKLSuzVJ5JExwCSZdSlBiJ0979e9nvn6My90/XUy5KphIkrSDhns1myev16hLuGzdtpNH3JEpKCerx9HweySVuvijt9fSgwyHAaDTisP8wXvnjKyveMvI944UsyxgejuOJx56o5TuCOf1YyrQRlW2OVvh/MZzF2mj3qIaxFE6lflYNEeWl19OjevKlFL0c/j1ZLJaa35jFYqHgkSBJl1KUlBL04u9erLnt/OXx/PPy36rhVGtfC9YngbPAgzXresqFBx96kG31iNEBnDh+Yslt+/uF+2G3t7ANzePHT2Cfb99yvoRZ1DNq8dxKnlbpuJz+VMOphrkowQCw4eb16PXsQf1aS9luRHm6ETrdqn14MjU5iV8fCuDcwLnlfmp0RaGhWkhYDjGfFM6SBGt2e8vtaGtvw83fvBlfNplKn07NBRgiwnQ+jwvJCzh98vSyif20PPhqiME1EfyFrdw4Irqe47h/f0HFp7DAIOL+NwDFrtvhh4x87AAAAABJRU5ErkJggg=="
  1236. },
  1237. target;
  1238. return {
  1239. manifest: {
  1240. about:{
  1241. name: "Popcorn Attribution Plugin",
  1242. version: "0.2",
  1243. author: "@rwaldron",
  1244. website: "github.com/rwldrn"
  1245. },
  1246. options:{
  1247. start: { elem:"input", type:"text", label:"In" },
  1248. end: { elem:"input", type:"text", label:"Out" },
  1249. nameofwork: { elem:"input", type:"text", label:"Name of Work" },
  1250. nameofworkurl: { elem:"input", type:"text", label:"Url of Work" },
  1251. copyrightholder: { elem:"input", type:"text", label:"Copyright Holder" },
  1252. copyrightholderurl: { elem:"input", type:"text", label:"Copyright Holder Url" },
  1253. license: { elem:"input", type:"text", label:"License type" },
  1254. licenseurl: { elem:"input", type:"text", label:"License URL" },
  1255. target: "attribution-container"
  1256. }
  1257. },
  1258. _setup: function( options ) {
  1259. var attrib = "",
  1260. license = options.license && licenses[ options.license.toLowerCase() ],
  1261. tar = "target=_blank";
  1262. // make a div to put the information into
  1263. options._container = document.createElement( "div" );
  1264. options._container.style.display = "none";
  1265. // Cache declared target
  1266. target = document.getElementById( options.target );
  1267. if ( options.nameofworkurl ) {
  1268. attrib += "<a href='" + options.nameofworkurl + "' " + tar + ">";
  1269. }
  1270. if ( options.nameofwork ) {
  1271. attrib += options.nameofwork;
  1272. }
  1273. if ( options.nameofworkurl ) {
  1274. attrib += "</a>";
  1275. }
  1276. if ( options.copyrightholderurl ) {
  1277. attrib += "<a href='" + options.copyrightholderurl + "' " + tar + ">";
  1278. }
  1279. if ( options.copyrightholder ) {
  1280. attrib += ", " + options.copyrightholder;
  1281. }
  1282. if ( options.copyrightholderurl ) {
  1283. attrib += "</a>";
  1284. }
  1285. //if the user did not specify any parameters just pull the text from the tag
  1286. if ( attrib === "" ) {
  1287. attrib = options.text;
  1288. }
  1289. if ( options.license ) {
  1290. if ( license ) {
  1291. if ( options.licenseurl ) {
  1292. attrib = "<a href='" + options.licenseurl + "' " + tar + "><img src='"+ license +"' border='0'/></a> " + attrib;
  1293. } else {
  1294. attrib = "<img src='"+ license +"' />" + attrib;
  1295. }
  1296. } else {
  1297. attrib += ", license: ";
  1298. if ( options.licenseurl ) {
  1299. attrib += "<a href='" + options.licenseurl + "' " + tar + ">" + options.license + "</a> ";
  1300. } else {
  1301. attrib += options.license;
  1302. }
  1303. }
  1304. } else if ( options.licenseurl ) {
  1305. attrib += ", <a href='" + options.licenseurl + "' " + tar + ">license</a> ";
  1306. }
  1307. options._container.innerHTML = attrib;
  1308. target && target.appendChild( options._container );
  1309. },
  1310. /**
  1311. * @member attribution
  1312. * The start function will be executed when the currentTime
  1313. * of the video reaches the start time provided by the
  1314. * options variable
  1315. */
  1316. start: function( event, options ) {
  1317. options._container.style.display = "inline";
  1318. },
  1319. /**
  1320. * @member attribution
  1321. * The end function will be executed when the currentTime
  1322. * of the video reaches the end time provided by the
  1323. * options variable
  1324. */
  1325. end: function( event, options ) {
  1326. options._container.style.display = "none";
  1327. },
  1328. _teardown: function( options ) {
  1329. // Cache declared target
  1330. target = document.getElementById( options.target );
  1331. target && target.removeChild( options._container );
  1332. }
  1333. };
  1334. })());
  1335. })( Popcorn );
  1336. // PLUGIN: Code
  1337. (function (Popcorn) {
  1338. /**
  1339. * Code Popcorn Plug-in
  1340. *
  1341. * Adds the ability to run arbitrary code (JavaScript functions) according to video timing.
  1342. *
  1343. * @param {Object} options
  1344. *
  1345. * Required parameters: start, end, template, data, and target.
  1346. * Optional parameter: static.
  1347. *
  1348. * start: the time in seconds when the mustache template should be rendered
  1349. * in the target div.
  1350. *
  1351. * end: the time in seconds when the rendered mustache template should be
  1352. * removed from the target div.
  1353. *
  1354. * onStart: the function to be run when the start time is reached.
  1355. *
  1356. * onFrame: [optional] a function to be run on each paint call
  1357. * (e.g., called ~60 times per second) between the start and end times.
  1358. *
  1359. * onEnd: [optional] a function to be run when the end time is reached.
  1360. *
  1361. * Example:
  1362. var p = Popcorn('#video')
  1363. // onStart function only
  1364. .code({
  1365. start: 1,
  1366. end: 4,
  1367. onStart: function( options ) {
  1368. // called on start
  1369. }
  1370. })
  1371. // onStart + onEnd only
  1372. .code({
  1373. start: 6,
  1374. end: 8,
  1375. onStart: function( options ) {
  1376. // called on start
  1377. },
  1378. onEnd: function ( options ) {
  1379. // called on end
  1380. }
  1381. })
  1382. // onStart, onEnd, onFrame
  1383. .code({
  1384. start: 10,
  1385. end: 14,
  1386. onStart: function( options ) {
  1387. // called on start
  1388. },
  1389. onFrame: function ( options ) {
  1390. // called on every paint frame between start and end.
  1391. // uses mozRequestAnimationFrame, webkitRequestAnimationFrame,
  1392. // or setTimeout with 16ms window.
  1393. },
  1394. onEnd: function ( options ) {
  1395. // called on end
  1396. }
  1397. });
  1398. *
  1399. */
  1400. Popcorn.plugin( 'code' , function(options) {
  1401. var running = false;
  1402. // Setup a proper frame interval function (60fps), favouring paint events.
  1403. var step = ( function() {
  1404. var buildFrameRunner = function( runner ) {
  1405. return function( f, options ) {
  1406. var _f = function() {
  1407. f();
  1408. if ( running ) {
  1409. runner( _f );
  1410. }
  1411. };
  1412. _f();
  1413. };
  1414. };
  1415. // Figure out which level of browser support we have for this
  1416. if ( window.webkitRequestAnimationFrame ) {
  1417. return buildFrameRunner( window.webkitRequestAnimationFrame );
  1418. } else if ( window.mozRequestAnimationFrame ) {
  1419. return buildFrameRunner( window.mozRequestAnimationFrame );
  1420. } else {
  1421. return buildFrameRunner( function( f ) {
  1422. window.setTimeout( f, 16 );
  1423. } );
  1424. }
  1425. } )();
  1426. if ( !options.onStart || typeof options.onStart !== 'function' ) {
  1427. throw 'Popcorn Code Plugin Error: onStart must be a function.';
  1428. }
  1429. if ( options.onEnd && typeof options.onEnd !== 'function' ) {
  1430. throw 'Popcorn Code Plugin Error: onEnd must be a function.';
  1431. }
  1432. if ( options.onFrame && typeof options.onFrame !== 'function' ) {
  1433. throw 'Popcorn Code Plugin Error: onFrame must be a function.';
  1434. }
  1435. return {
  1436. start: function( event, options ) {
  1437. options.onStart( options );
  1438. if ( options.onFrame ) {
  1439. running = true;
  1440. step( options.onFrame, options );
  1441. }
  1442. },
  1443. end: function( event, options ) {
  1444. if ( options.onFrame ) {
  1445. running = false;
  1446. }
  1447. if ( options.onEnd ) {
  1448. options.onEnd( options );
  1449. }
  1450. }
  1451. };
  1452. },
  1453. {
  1454. about: {
  1455. name: 'Popcorn Code Plugin',
  1456. version: '0.1',
  1457. author: 'David Humphrey (@humphd)',
  1458. website: 'http://vocamus.net/dave'
  1459. },
  1460. options: {
  1461. start: {elem:'input', type:'text', label:'In'},
  1462. end: {elem:'input', type:'text', label:'Out'},
  1463. onStart: {elem:'input', type:'function', label:'onStart'},
  1464. onFrame: {elem:'input', type:'function', label:'onFrame'},
  1465. onEnd: {elem:'input', type:'function', label:'onEnd'}
  1466. }
  1467. });
  1468. })( Popcorn );
  1469. // PLUGIN: Flickr
  1470. (function (Popcorn) {
  1471. /**
  1472. * Flickr popcorn plug-in
  1473. * Appends a users Flickr images to an element on the page.
  1474. * Options parameter will need a start, end, target and userid or username and api_key.
  1475. * Optional parameters are numberofimages, height, width, padding, and border
  1476. * Start is the time that you want this plug-in to execute (in seconds)
  1477. * End is the time that you want this plug-in to stop executing (in seconds)
  1478. * Userid is the id of who's Flickr images you wish to show
  1479. * Tags is a mutually exclusive list of image descriptor tags
  1480. * Username is the username of who's Flickr images you wish to show
  1481. * using both userid and username is redundant
  1482. * an api_key is required when using username
  1483. * Apikey is your own api key provided by Flickr
  1484. * Target is the id of the document element that the images are
  1485. * appended to, this target element must exist on the DOM
  1486. * Numberofimages specify the number of images to retreive from flickr, defaults to 4
  1487. * Height the height of the image, defaults to '50px'
  1488. * Width the width of the image, defaults to '50px'
  1489. * Padding number of pixels between images, defaults to '5px'
  1490. * Border border size in pixels around images, defaults to '0px'
  1491. *
  1492. * @param {Object} options
  1493. *
  1494. * Example:
  1495. var p = Popcorn('#video')
  1496. .flickr({
  1497. start: 5, // seconds, mandatory
  1498. end: 15, // seconds, mandatory
  1499. userid: '35034346917@N01', // optional
  1500. tags: 'dogs,cats', // optional
  1501. numberofimages: '8', // optional
  1502. height: '50px', // optional
  1503. width: '50px', // optional
  1504. padding: '5px', // optional
  1505. border: '0px', // optional
  1506. target: 'flickrdiv' // mandatory
  1507. } )
  1508. *
  1509. */
  1510. var idx = 0;
  1511. Popcorn.plugin( "flickr" , function( options ) {
  1512. var containerDiv,
  1513. _userid,
  1514. _uri,
  1515. _link,
  1516. _image,
  1517. _count = options.numberofimages || 4 ,
  1518. _height = options.height || "50px",
  1519. _width = options.width || "50px",
  1520. _padding = options.padding || "5px",
  1521. _border = options.border || "0px";
  1522. // create a new div this way anything in the target div is left intact
  1523. // this is later populated with Flickr images
  1524. containerDiv = document.createElement( "div" );
  1525. containerDiv.id = "flickr" + idx;
  1526. containerDiv.style.width = "100%";
  1527. containerDiv.style.height = "100%";
  1528. containerDiv.style.display = "none";
  1529. idx++;
  1530. // ensure the target container the user chose exists
  1531. if ( document.getElementById( options.target ) ) {
  1532. document.getElementById( options.target ).appendChild( containerDiv );
  1533. } else {
  1534. throw ( "flickr target container doesn't exist" );
  1535. }
  1536. // get the userid from Flickr API by using the username and apikey
  1537. var isUserIDReady = function() {
  1538. if ( !_userid ) {
  1539. _uri = "http://api.flickr.com/services/rest/?method=flickr.people.findByUsername&";
  1540. _uri += "username=" + options.username + "&api_key=" + options.apikey + "&format=json&jsoncallback=flickr";
  1541. Popcorn.getJSONP( _uri, function( data ) {
  1542. _userid = data.user.nsid;
  1543. getFlickrData();
  1544. });
  1545. } else {
  1546. setTimeout(function () {
  1547. isUserIDReady();
  1548. }, 5 );
  1549. }
  1550. };
  1551. // get the photos from Flickr API by using the user_id and/or tags
  1552. var getFlickrData = function() {
  1553. _uri = "http://api.flickr.com/services/feeds/photos_public.gne?";
  1554. if ( _userid ) {
  1555. _uri += "id=" + _userid + "&";
  1556. }
  1557. if ( options.tags ) {
  1558. _uri += "tags=" + options.tags + "&";
  1559. }
  1560. _uri += "lang=en-us&format=json&jsoncallback=flickr";
  1561. Popcorn.xhr.getJSONP( _uri, function( data ) {
  1562. containerDiv.innerHTML = "<p style='padding:" + _padding + ";'>" + data.title + "<p/>";
  1563. Popcorn.forEach( data.items, function ( item, i ) {
  1564. if ( i < _count ) {
  1565. _link = document.createElement( 'a' );
  1566. _link.setAttribute( 'href', item.link );
  1567. _link.setAttribute( "target", "_blank" );
  1568. _image = document.createElement( 'img' );
  1569. _image.setAttribute( 'src', item.media.m );
  1570. _image.setAttribute( 'height',_height );
  1571. _image.setAttribute( 'width', _width );
  1572. _image.setAttribute( 'style', 'border:' + _border + ';padding:' + _padding );
  1573. _link.appendChild( _image );
  1574. containerDiv.appendChild( _link );
  1575. } else {
  1576. return false;
  1577. }
  1578. });
  1579. });
  1580. };
  1581. if ( options.username && options.apikey ) {
  1582. isUserIDReady();
  1583. }
  1584. else {
  1585. _userid = options.userid;
  1586. getFlickrData();
  1587. }
  1588. return {
  1589. /**
  1590. * @member flickr
  1591. * The start function will be executed when the currentTime
  1592. * of the video reaches the start time provided by the
  1593. * options variable
  1594. */
  1595. start: function( event, options ) {
  1596. containerDiv.style.display = "inline";
  1597. },
  1598. /**
  1599. * @member flickr
  1600. * The end function will be executed when the currentTime
  1601. * of the video reaches the end time provided by the
  1602. * options variable
  1603. */
  1604. end: function( event, options ) {
  1605. containerDiv.style.display = "none";
  1606. },
  1607. _teardown: function( options ) {
  1608. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( containerDiv );
  1609. }
  1610. };
  1611. },
  1612. {
  1613. about: {
  1614. name: "Popcorn Flickr Plugin",
  1615. version: "0.2",
  1616. author: "Scott Downe, Steven Weerdenburg, Annasob",
  1617. website: "http://scottdowne.wordpress.com/"
  1618. },
  1619. options: {
  1620. start: {
  1621. elem: "input",
  1622. type: "number",
  1623. label: "In"
  1624. },
  1625. end: {
  1626. elem: "input",
  1627. type: "number",
  1628. label: "Out"
  1629. },
  1630. userid: {
  1631. elem: "input",
  1632. type: "text",
  1633. label: "UserID"
  1634. },
  1635. tags: {
  1636. elem: "input",
  1637. type: "text",
  1638. label: "Tags"
  1639. },
  1640. username: {
  1641. elem: "input",
  1642. type: "text",
  1643. label: "Username"
  1644. },
  1645. apikey: {
  1646. elem: "input",
  1647. type: "text",
  1648. label: "Api_key"
  1649. },
  1650. target: "flickr-container",
  1651. height: {
  1652. elem: "input",
  1653. type: "text",
  1654. label: "Height"
  1655. },
  1656. width: {
  1657. elem: "input",
  1658. type: "text",
  1659. label: "Width"
  1660. },
  1661. padding: {
  1662. elem: "input",
  1663. type: "text",
  1664. label: "Padding"
  1665. },
  1666. border: {
  1667. elem: "input",
  1668. type: "text",
  1669. label: "Border"
  1670. },
  1671. numberofimages: {
  1672. elem: "input",
  1673. type: "text",
  1674. label: "Number of Images"
  1675. }
  1676. }
  1677. });
  1678. })( Popcorn );
  1679. // PLUGIN: Footnote
  1680. (function (Popcorn) {
  1681. /**
  1682. * Footnote popcorn plug-in
  1683. * Adds text to an element on the page.
  1684. * Options parameter will need a start, end, target and text.
  1685. * Start is the time that you want this plug-in to execute
  1686. * End is the time that you want this plug-in to stop executing
  1687. * Text is the text that you want to appear in the target
  1688. * Target is the id of the document element that the text needs to be
  1689. * attached to, this target element must exist on the DOM
  1690. *
  1691. * @param {Object} options
  1692. *
  1693. * Example:
  1694. var p = Popcorn('#video')
  1695. .footnote({
  1696. start: 5, // seconds
  1697. end: 15, // seconds
  1698. text: 'This video made exclusively for drumbeat.org',
  1699. target: 'footnotediv'
  1700. } )
  1701. *
  1702. */
  1703. Popcorn.plugin( "footnote" , {
  1704. manifest: {
  1705. about:{
  1706. name: "Popcorn Footnote Plugin",
  1707. version: "0.1",
  1708. author: "@annasob",
  1709. website: "annasob.wordpress.com"
  1710. },
  1711. options:{
  1712. start : {elem:'input', type:'text', label:'In'},
  1713. end : {elem:'input', type:'text', label:'Out'},
  1714. target : 'footnote-container',
  1715. text : {elem:'input', type:'text', label:'Text'}
  1716. }
  1717. },
  1718. _setup: function(options) {
  1719. options._container = document.createElement( 'div' );
  1720. options._container.style.display = "none";
  1721. options._container.innerHTML = options.text;
  1722. document.getElementById( options.target ) && document.getElementById( options.target ).appendChild( options._container );
  1723. },
  1724. /**
  1725. * @member footnote
  1726. * The start function will be executed when the currentTime
  1727. * of the video reaches the start time provided by the
  1728. * options variable
  1729. */
  1730. start: function(event, options){
  1731. options._container.style.display = "inline";
  1732. },
  1733. /**
  1734. * @member footnote
  1735. * The end function will be executed when the currentTime
  1736. * of the video reaches the end time provided by the
  1737. * options variable
  1738. */
  1739. end: function(event, options){
  1740. options._container.style.display = "none";
  1741. },
  1742. _teardown: function( options ) {
  1743. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._container );
  1744. }
  1745. });
  1746. })( Popcorn );
  1747. //PLUGIN: facebook
  1748. (function(Popcorn, global ) {
  1749. /**
  1750. * Facebook Popcorn plug-in
  1751. * Places Facebook's "social plugins" inside a div ( http://developers.facebook.com/docs/plugins/ )
  1752. * Sets options according to user input or default values
  1753. * Options parameter will need a target, type, start and end time
  1754. * Type is the name of the plugin in fbxml format. Options: LIKE (default), LIKE-BOX, ACTIVITY, FACEPILE
  1755. * Target is the id of the document element that the text needs to be attached to. This target element must exist on the DOM
  1756. * Start is the time that you want this plug-in to execute
  1757. * End is the time that you want this plug-in to stop executing
  1758. *
  1759. * Other than the mandatory four parameters, there are several optional parameters (Some options are only applicable to certain plugins)
  1760. * Action - like button will either "Like" or "Recommend". Options: recommend / like(default)
  1761. * Always_post_to_friends - live-stream posts will be always be posted on your facebook wall if true. Options: true / false(default)
  1762. * Border_color - border color of the activity feed. Names (i.e: "white") and html color codes are valid
  1763. * Colorscheme - changes the color of almost all plugins. Options: light(default) / dark
  1764. * Event_app_id - an app_id is required for the live-stream plugin
  1765. * Font - the font of the text contained in the plugin. Options: arial / segoe ui / tahoma / trebuchet ms / verdana / lucida grande
  1766. * Header - displays the title of like-box or activity feed. Options: true / false(default)
  1767. * Href - url to apply to the plugin. Default is current page
  1768. * Layout - changes the format of the 'like' count (written in english or a number in a callout).
  1769. * Options: box_count / button_count / standard(default)
  1770. * Max_rows - number of rows to disperse pictures in facepile. Default is 1
  1771. * Recommendations - shows recommendations, if any, in the bottom half of activity feed. Options: true / false(default)
  1772. * Show_faces - show pictures beside like button and like-box. Options: true / false(default)
  1773. * Site - href for activity feed. No idea why it must be "site". Default is current page
  1774. * Stream - displays a the latest posts from the specified page's wall. Options: true / false(default)
  1775. * Type - determines which plugin to create. Case insensitive
  1776. * Xid - unique identifier if more than one live-streams are on one page
  1777. *
  1778. * @param {Object} options
  1779. *
  1780. * Example:
  1781. var p = Popcorn('#video')
  1782. .facebook({
  1783. type : "LIKE-BOX",
  1784. target: "likeboxdiv",
  1785. start : 3,
  1786. end : 10,
  1787. href : "http://www.facebook.com/senecacollege",
  1788. show_faces: "true",
  1789. header: "false"
  1790. } )
  1791. * This will show how many people "like" Seneca College's Facebook page, and show their profile pictures
  1792. */
  1793. var ranOnce = false;
  1794. function toggle( container, display ) {
  1795. if ( container ) {
  1796. container.style.display = display;
  1797. return;
  1798. }
  1799. setTimeout(function() {
  1800. toggle( container, display );
  1801. }, 10 );
  1802. }
  1803. Popcorn.plugin( "facebook" , {
  1804. manifest:{
  1805. about:{
  1806. name : "Popcorn Facebook Plugin",
  1807. version: "0.1",
  1808. author : "Dan Ventura",
  1809. website: "dsventura.blogspot.com"
  1810. },
  1811. options:{
  1812. type : {elem:"select", options:["LIKE", "LIKE-BOX", "ACTIVITY", "FACEPILE", "LIVE-STREAM", "SEND"], label:"Type"},
  1813. target : "facebook-container",
  1814. start : {elem:'input', type:'number', label:'In'},
  1815. end : {elem:'input', type:'number', label:'Out'},
  1816. // optional parameters:
  1817. font : {elem:"input", type:"text", label:"font"},
  1818. xid : {elem:"input", type:"text", label:"Xid"},
  1819. href : {elem:"input", type:"text", label:"Href"},
  1820. site : {elem:"input", type:"text", label:"Site"},
  1821. height : {elem:"input", type:"text", label:"Height"},
  1822. width : {elem:"input", type:"text", label:"Width"},
  1823. action : {elem:"select", options:["like", "recommend"], label:"Action"},
  1824. stream : {elem:"select", options:["false", "true"], label:"Stream"},
  1825. header : {elem:"select", options:["false", "true"], label:"Header"},
  1826. layout : {elem:"select", options:["standard", "button_count", "box_count"], label:"Layout"},
  1827. max_rows : {elem:"input", type:"text", label:"Max_rows"},
  1828. border_color : {elem:"input", type:"text", label:"Border_color"},
  1829. event_app_id : {elem:"input", type:"text", label:"Event_app_id"},
  1830. colorscheme : {elem:"select", options:["light", "dark"], label:"Colorscheme"},
  1831. show_faces : {elem:"select", options:["false", "true"], label:"Showfaces"},
  1832. recommendations : {elem:"select", options:["false", "true"], label:"Recommendations"},
  1833. always_post_to_friends : {elem:"input", options:["false", "true"], label:"Always_post_to_friends"}
  1834. }
  1835. },
  1836. _setup: function( options ) {
  1837. // facebook script requires a div named fb-root
  1838. if ( !document.getElementById( "fb-root" ) ) {
  1839. var fbRoot = document.createElement( "div" );
  1840. fbRoot.setAttribute( "id", "fb-root" );
  1841. document.body.appendChild( fbRoot );
  1842. }
  1843. if ( !ranOnce || options.event_app_id ) {
  1844. ranOnce = true;
  1845. // initialize facebook JS SDK
  1846. Popcorn.getScript("http://connect.facebook.net/en_US/all.js");
  1847. global.fbAsyncInit = function() {
  1848. FB.init({
  1849. appId : ( options.event_app_id || "" ),
  1850. status : true,
  1851. cookie : true,
  1852. xfbml : true
  1853. });
  1854. };
  1855. }
  1856. var validType = function( type ) {
  1857. return ( [ "like", "like-box", "activity", "facepile", "comments", "live-stream", "send" ].indexOf( type ) > -1 );
  1858. };
  1859. // default plugin is like button
  1860. options.type = ( options.type || "like" ).toLowerCase();
  1861. // default plugin is like button
  1862. if ( !validType( options.type ) ) {
  1863. options.type = "like";
  1864. }
  1865. options._container = document.createElement( "fb:" + options.type );
  1866. var setOptions = (function( options ) {
  1867. options._container.style.display = "none";
  1868. // activity feed uses 'site' rather than 'href'
  1869. var attr = options.type === "activity" ? "site" : "href";
  1870. options._container.setAttribute( attr, ( options[ attr ] || document.URL ) );
  1871. return {
  1872. "like": function () {
  1873. options._container.setAttribute( "send", ( options.send || false ) );
  1874. options._container.setAttribute( "width", options.width );
  1875. options._container.setAttribute( "show_faces", options.show_faces );
  1876. options._container.setAttribute( "layout", options.layout );
  1877. options._container.setAttribute( "font", options.font );
  1878. options._container.setAttribute( "colorscheme", options.colorscheme );
  1879. },
  1880. "like-box": function () {
  1881. options._container.setAttribute( "height", ( options.height || 250 ) );
  1882. options._container.setAttribute( "width", options.width );
  1883. options._container.setAttribute( "show_faces", options.show_faces );
  1884. options._container.setAttribute( "stream", options.stream );
  1885. options._container.setAttribute( "header", options.header );
  1886. options._container.setAttribute( "colorscheme", options.colorscheme );
  1887. },
  1888. "facepile": function () {
  1889. options._container.setAttribute( "height", options.height );
  1890. options._container.setAttribute( "width", options.width );
  1891. options._container.setAttribute( "max_rows", ( options.max_rows || 1 ) );
  1892. },
  1893. "activity": function () {
  1894. options._container.setAttribute( "width", options.width );
  1895. options._container.setAttribute( "height", options.height );
  1896. options._container.setAttribute( "header", options.header );
  1897. options._container.setAttribute( "border_color", options.border_color );
  1898. options._container.setAttribute( "recommendations", options.recommendations );
  1899. options._container.setAttribute( "font", options.font );
  1900. options._container.setAttribute( "colorscheme", options.colorscheme );
  1901. },
  1902. "live-stream": function() {
  1903. options._container.setAttribute( "width", ( options.width || 400 ) );
  1904. options._container.setAttribute( "height", ( options.height || 500 ) );
  1905. options._container.setAttribute( "always_post_to_friends", ( options.always_post_to_friends || false ) );
  1906. options._container.setAttribute( "event_app_id", options.event_app_id );
  1907. options._container.setAttribute( "xid", options.xid );
  1908. },
  1909. "send": function() {
  1910. options._container.setAttribute( "font", options.font );
  1911. options._container.setAttribute( "colorscheme", options.colorscheme );
  1912. }
  1913. };
  1914. })( options );
  1915. setOptions[ options.type ]();
  1916. if ( document.getElementById( options.target ) ) {
  1917. document.getElementById( options.target ).appendChild( options._container );
  1918. }
  1919. },
  1920. /**
  1921. * @member facebook
  1922. * The start function will be executed when the currentTime
  1923. * of the video reaches the start time provided by the
  1924. * options variable
  1925. */
  1926. start: function( event, options ){
  1927. toggle( options._container, "inline" );
  1928. },
  1929. /**
  1930. * @member facebook
  1931. * The end function will be executed when the currentTime
  1932. * of the video reaches the end time provided by the
  1933. * options variable
  1934. */
  1935. end: function( event, options ){
  1936. toggle ( options._container, "none" );
  1937. }
  1938. });
  1939. })( Popcorn, this );
  1940. // PLUGIN: Google Maps
  1941. var googleCallback;
  1942. (function (Popcorn) {
  1943. var i = 1,
  1944. _mapFired = false,
  1945. _mapLoaded = false,
  1946. geocoder, loadMaps;
  1947. //google api callback
  1948. googleCallback = function (data) {
  1949. // ensure all of the maps functions needed are loaded
  1950. // before setting _maploaded to true
  1951. if (typeof google !== "undefined" && google.maps && google.maps.Geocoder && google.maps.LatLng) {
  1952. geocoder = new google.maps.Geocoder();
  1953. _mapLoaded = true;
  1954. } else {
  1955. setTimeout(function () {
  1956. googleCallback(data);
  1957. }, 1);
  1958. }
  1959. };
  1960. // function that loads the google api
  1961. loadMaps = function () {
  1962. // for some reason the Google Map API adds content to the body
  1963. if (document.body) {
  1964. _mapFired = true;
  1965. Popcorn.getScript("http://maps.google.com/maps/api/js?sensor=false&callback=googleCallback");
  1966. } else {
  1967. setTimeout(function () {
  1968. loadMaps();
  1969. }, 1);
  1970. }
  1971. };
  1972. /**
  1973. * googlemap popcorn plug-in
  1974. * Adds a map to the target div centered on the location specified by the user
  1975. * Options parameter will need a start, end, target, type, zoom, lat and lng, and location
  1976. * -Start is the time that you want this plug-in to execute
  1977. * -End is the time that you want this plug-in to stop executing
  1978. * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
  1979. * -Type [optional] either: HYBRID (default), ROADMAP, SATELLITE, TERRAIN, STREETVIEW
  1980. * -Zoom [optional] defaults to 0
  1981. * -Heading [optional] STREETVIEW orientation of camera in degrees relative to true north (0 north, 90 true east, ect)
  1982. * -Pitch [optional] STREETVIEW vertical orientation of the camera (between 1 and 3 is recommended)
  1983. * -Lat and Lng: the coordinates of the map must be present if location is not specified.
  1984. * -Location: the adress you want the map to display, must be present if lat and lng are not specified.
  1985. * Note: using location requires extra loading time, also not specifying both lat/lng and location will
  1986. * cause and error.
  1987. *
  1988. * Tweening works using the following specifications:
  1989. * -location is the start point when using an auto generated route
  1990. * -tween when used in this context is a string which specifies the end location for your route
  1991. * Note that both location and tween must be present when using an auto generated route, or the map will not tween
  1992. * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds )
  1993. * Heading, Zoom, and Pitch streetview values are also used in tweening with the autogenerated route
  1994. *
  1995. * -tween is an array of objects, each containing data for one frame of a tween
  1996. * -position is an object with has two paramaters, lat and lng, both which are mandatory for a tween to work
  1997. * -pov is an object which houses heading, pitch, and zoom paramters, which are all optional, if undefined, these values default to 0
  1998. * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds )
  1999. *
  2000. * @param {Object} options
  2001. *
  2002. * Example:
  2003. var p = Popcorn("#video")
  2004. .googlemap({
  2005. start: 5, // seconds
  2006. end: 15, // seconds
  2007. type: "ROADMAP",
  2008. target: "map",
  2009. lat: 43.665429,
  2010. lng: -79.403323
  2011. } )
  2012. *
  2013. */
  2014. Popcorn.plugin("googlemap", function (options) {
  2015. var newdiv, map, location;
  2016. // if this is the firest time running the plugins
  2017. // call the function that gets the sctipt
  2018. if (!_mapFired) {
  2019. loadMaps();
  2020. }
  2021. // create a new div this way anything in the target div is left intact
  2022. // this is later passed on to the maps api
  2023. newdiv = document.createElement("div");
  2024. newdiv.id = "actualmap" + i;
  2025. newdiv.style.width = "100%";
  2026. newdiv.style.height = "100%";
  2027. i++;
  2028. // ensure the target container the user chose exists
  2029. if (document.getElementById(options.target)) {
  2030. document.getElementById(options.target).appendChild(newdiv);
  2031. } else {
  2032. throw ("map target container doesn't exist");
  2033. }
  2034. // ensure that google maps and its functions are loaded
  2035. // before setting up the map parameters
  2036. var isMapReady = function () {
  2037. if (_mapLoaded) {
  2038. if (options.location) {
  2039. // calls an anonymous google function called on separate thread
  2040. geocoder.geocode({
  2041. "address": options.location
  2042. }, function (results, status) {
  2043. if (status === google.maps.GeocoderStatus.OK) {
  2044. options.lat = results[0].geometry.location.lat();
  2045. options.lng = results[0].geometry.location.lng();
  2046. location = new google.maps.LatLng(options.lat, options.lng);
  2047. map = new google.maps.Map(newdiv, {
  2048. mapTypeId: google.maps.MapTypeId[options.type] || google.maps.MapTypeId.HYBRID
  2049. });
  2050. map.getDiv().style.display = "none";
  2051. }
  2052. });
  2053. } else {
  2054. location = new google.maps.LatLng(options.lat, options.lng);
  2055. map = new google.maps.Map(newdiv, {
  2056. mapTypeId: google.maps.MapTypeId[options.type] || google.maps.MapTypeId.HYBRID
  2057. });
  2058. map.getDiv().style.display = "none";
  2059. }
  2060. } else {
  2061. setTimeout(function () {
  2062. isMapReady();
  2063. }, 5);
  2064. }
  2065. };
  2066. isMapReady();
  2067. return {
  2068. /**
  2069. * @member webpage
  2070. * The start function will be executed when the currentTime
  2071. * of the video reaches the start time provided by the
  2072. * options variable
  2073. */
  2074. start: function( event, options ) {
  2075. var that = this,
  2076. sView;
  2077. // ensure the map has been initialized in the setup function above
  2078. var isMapSetup = function() {
  2079. if ( map ) {
  2080. map.getDiv().style.display = "block";
  2081. // reset the location and zoom just in case the user plaid with the map
  2082. google.maps.event.trigger(map, 'resize');
  2083. map.setCenter( location );
  2084. // make sure options.zoom is a number
  2085. if ( options.zoom && typeof options.zoom !== "number" ) {
  2086. options.zoom = +options.zoom;
  2087. }
  2088. options.zoom = options.zoom || 8; // default to 8
  2089. map.setZoom( options.zoom );
  2090. //Make sure heading is a number
  2091. if ( options.heading && typeof options.heading !== "number" ) {
  2092. options.heading = +options.heading;
  2093. }
  2094. //Make sure pitch is a number
  2095. if ( options.pitch && typeof options.pitch !== "number" ) {
  2096. options.pitch = +options.pitch;
  2097. }
  2098. if ( options.type === "STREETVIEW" ) {
  2099. // Switch this map into streeview mode
  2100. map.setStreetView(
  2101. // Pass a new StreetViewPanorama instance into our map
  2102. sView = new google.maps.StreetViewPanorama( newdiv, {
  2103. position: location,
  2104. pov: {
  2105. heading: options.heading = options.heading || 0,
  2106. pitch: options.pitch = options.pitch || 0,
  2107. zoom: options.zoom
  2108. }
  2109. })
  2110. );
  2111. // Function to handle tweening using a set timeout
  2112. var tween = function( rM, t ) {
  2113. setTimeout(function() {
  2114. // Checks whether this is a generated route or not
  2115. if( typeof options.tween === "object" ) {
  2116. for ( var i = 0; i < rM.length; i++ ) {
  2117. // Checks if this position along the tween should be displayed or not
  2118. if ( that.media.currentTime >= ( rM[ i ].interval * ( i + 1 ) ) / 1000 &&
  2119. ( that.media.currentTime <= ( rM[ i ].interval * ( i + 2 ) ) / 1000 ||
  2120. that.media.currentTime >= rM[ i ].interval * ( rM.length ) / 1000 ) ) {
  2121. sView3.setPosition( new google.maps.LatLng( rM[ i ].position.lat, rM[ i ].position.lng ) );
  2122. sView3.setPov({
  2123. heading: rM[ i ].pov.heading || 0,
  2124. zoom: rM[ i ].pov.zoom || 0,
  2125. pitch: rM[ i ].pov.pitch || 0
  2126. });
  2127. }
  2128. }
  2129. // Calls the tween function again at the interval set by the user
  2130. tween( rM, rM[ 0 ].interval );
  2131. } else {
  2132. for ( var k = 0; k < rM.length; k++ ) {
  2133. if( that.media.currentTime >= (options.interval * ( k + 1 ) ) / 1000 &&
  2134. ( that.media.currentTime <= (options.interval * ( k + 2 ) ) / 1000 ||
  2135. that.media.currentTime >= options.interval * ( rM.length ) / 1000 ) ) {
  2136. sView2.setPosition( checkpoints[ k ] );
  2137. sView2.setPov({
  2138. heading: options.heading || 0,
  2139. zoom: options.zoom,
  2140. pitch: options.pitch || 0
  2141. });
  2142. }
  2143. }
  2144. tween( checkpoints, options.interval );
  2145. }
  2146. }, t );
  2147. };
  2148. // Determines if we should use hardcoded values ( using options.tween ),
  2149. // or if we should use a start and end location and let google generate
  2150. // the route for us
  2151. if ( options.location && typeof options.tween === "string" ) {
  2152. // Creating another variable to hold the streetview map for tweening,
  2153. // Doing this because if there was more then one streetview map, the tweening would sometimes appear in other maps
  2154. var sView2 = sView;
  2155. // Create an array to store all the lat/lang values along our route
  2156. var checkpoints = [];
  2157. // Creates a new direction service, later used to create a route
  2158. var directionsService = new google.maps.DirectionsService();
  2159. // Creates a new direction renderer using the current map
  2160. // This enables us to access all of the route data that is returned to us
  2161. var directionsDisplay = new google.maps.DirectionsRenderer( sView2 );
  2162. var request = {
  2163. origin: options.location,
  2164. destination: options.tween,
  2165. travelMode: google.maps.TravelMode.DRIVING
  2166. };
  2167. // Create the route using the direction service and renderer
  2168. directionsService.route( request, function( response, status ) {
  2169. if ( status == google.maps.DirectionsStatus.OK ) {
  2170. directionsDisplay.setDirections( response );
  2171. showSteps( response, that );
  2172. }
  2173. });
  2174. var showSteps = function ( directionResult, that ) {
  2175. // Push new google map lat and lng values into an array from our list of lat and lng values
  2176. for ( var j = 0; j < directionResult.routes[ 0 ].overview_path.length; j++ ) {
  2177. checkpoints.push( new google.maps.LatLng( directionResult.routes[ 0 ].overview_path[ j ].lat(), directionResult.routes[ 0 ].overview_path[ j ].lng() ) );
  2178. }
  2179. // Check to make sure the interval exists, if not, set to a default of 1000
  2180. options.interval = options.interval || 1000;
  2181. tween( checkpoints, 10 );
  2182. };
  2183. } else if ( typeof options.tween === "object" ) {
  2184. // Same as the above to stop streetview maps from overflowing into one another
  2185. var sView3 = sView;
  2186. for ( var i = 0; i < options.tween.length; i++ ) {
  2187. // Make sure interval exists, if not, set to 1000
  2188. options.tween[ i ].interval = options.tween[ i ].interval || 1000;
  2189. tween( options.tween, 10 );
  2190. }
  2191. }
  2192. }
  2193. } else {
  2194. setTimeout(function () {
  2195. isMapSetup();
  2196. }, 13);
  2197. }
  2198. };
  2199. isMapSetup();
  2200. },
  2201. /**
  2202. * @member webpage
  2203. * The end function will be executed when the currentTime
  2204. * of the video reaches the end time provided by the
  2205. * options variable
  2206. */
  2207. end: function (event, options) {
  2208. // if the map exists hide it do not delete the map just in
  2209. // case the user seeks back to time b/w start and end
  2210. if (map) {
  2211. map.getDiv().style.display = "none";
  2212. }
  2213. },
  2214. _teardown: function (options) {
  2215. // the map must be manually removed
  2216. document.getElementById(options.target).removeChild(newdiv);
  2217. newdiv = map = location = null;
  2218. }
  2219. };
  2220. }, {
  2221. about: {
  2222. name: "Popcorn Google Map Plugin",
  2223. version: "0.1",
  2224. author: "@annasob",
  2225. website: "annasob.wordpress.com"
  2226. },
  2227. options: {
  2228. start: {
  2229. elem: "input",
  2230. type: "text",
  2231. label: "In"
  2232. },
  2233. end: {
  2234. elem: "input",
  2235. type: "text",
  2236. label: "Out"
  2237. },
  2238. target: "map-container",
  2239. type: {
  2240. elem: "select",
  2241. options: ["ROADMAP", "SATELLITE", "STREETVIEW", "HYBRID", "TERRAIN"],
  2242. label: "Type"
  2243. },
  2244. zoom: {
  2245. elem: "input",
  2246. type: "text",
  2247. label: "Zoom"
  2248. },
  2249. lat: {
  2250. elem: "input",
  2251. type: "text",
  2252. label: "Lat"
  2253. },
  2254. lng: {
  2255. elem: "input",
  2256. type: "text",
  2257. label: "Lng"
  2258. },
  2259. location: {
  2260. elem: "input",
  2261. type: "text",
  2262. label: "Location"
  2263. },
  2264. heading: {
  2265. elem: "input",
  2266. type: "text",
  2267. label: "Heading"
  2268. },
  2269. pitch: {
  2270. elem: "input",
  2271. type: "text",
  2272. label: "Pitch"
  2273. }
  2274. }
  2275. });
  2276. })(Popcorn);
  2277. // PLUGIN: Google News
  2278. (function (Popcorn) {
  2279. var scriptLoaded = false,
  2280. scriptLoading = false,
  2281. callBack = function( data ) {
  2282. if ( typeof google !== 'undefined' && google.load ) {
  2283. google.load("elements", "1", {packages : ["newsshow"], callback: function() {scriptLoaded = true;}});
  2284. } else {
  2285. setTimeout( function() {
  2286. callBack( data );
  2287. }, 1);
  2288. }
  2289. };
  2290. /**
  2291. * Google News popcorn plug-in
  2292. * Displays Google News information on a topic in a targeted div.
  2293. * Options parameter will need a start, end and target.
  2294. * Optional parameters are topic. topic defaults to "top stories"
  2295. * Start is the time that you want this plug-in to execute
  2296. * End is the time that you want this plug-in to stop executing
  2297. * Target is the id of the document element that the content is
  2298. * appended to, this target element must exist on the DOM
  2299. * Topic is the topic of news articles you want to display.
  2300. *
  2301. * @param {Object} options
  2302. *
  2303. * Example:
  2304. var p = Popcorn('#video')
  2305. .googlenews({
  2306. start: 5, // seconds, mandatory
  2307. end: 15, // seconds, mandatory
  2308. topic: 'oil spill', // optional
  2309. target: 'newsdiv' // mandatory
  2310. } )
  2311. *
  2312. */
  2313. Popcorn.plugin( "googlenews" , {
  2314. manifest: {
  2315. about:{
  2316. name: "Popcorn Google News Plugin",
  2317. version: "0.1",
  2318. author: "Scott Downe",
  2319. website: "http://scottdowne.wordpress.com/"
  2320. },
  2321. options:{
  2322. start : {elem:'input', type:'text', label:'In'},
  2323. end : {elem:'input', type:'text', label:'Out'},
  2324. target : 'news-container',
  2325. topic : {elem:'select', type:'text', label:'Type'}
  2326. }
  2327. },
  2328. _setup : function( options ) {
  2329. if ( !scriptLoading ) {
  2330. scriptLoading = true;
  2331. Popcorn.getScript( "http://www.google.com/jsapi", callBack );
  2332. }
  2333. options.container = document.createElement( 'div' );
  2334. var container = document.createElement( 'div' );
  2335. if ( document.getElementById( options.target ) ) {
  2336. document.getElementById( options.target ).appendChild( options.container );
  2337. options.container.appendChild( container );
  2338. }
  2339. var readyCheck = setInterval(function() {
  2340. if ( !scriptLoaded ) {
  2341. return;
  2342. }
  2343. clearInterval( readyCheck );
  2344. options.newsShow = new google.elements.NewsShow( container, {
  2345. format : "300x250",
  2346. queryList : [
  2347. { q: options.topic || "Top Stories" }
  2348. ]
  2349. } );
  2350. }, 5);
  2351. options.container.style.display = "none";
  2352. },
  2353. /**
  2354. * @member googlenews
  2355. * The start function will be executed when the currentTime
  2356. * of the video reaches the start time provided by the
  2357. * options variable
  2358. */
  2359. start: function( event, options ){
  2360. options.container.setAttribute( 'style', 'display:inline' );
  2361. },
  2362. /**
  2363. * @member googlenews
  2364. * The end function will be executed when the currentTime
  2365. * of the video reaches the end time provided by the
  2366. * options variable
  2367. */
  2368. end: function( event, options ){
  2369. options.container.setAttribute( 'style', 'display:none' );
  2370. },
  2371. _teardown: function( options ) {
  2372. // google news does not like this, throws an error "a is null"
  2373. // doesn't hurt popcorn, and only happens once
  2374. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.container );
  2375. options.newsShow = null;
  2376. }
  2377. });
  2378. })( Popcorn );
  2379. // PLUGIN: IMAGE
  2380. (function (Popcorn) {
  2381. /**
  2382. * Images popcorn plug-in
  2383. * Shows an image element
  2384. * Options parameter will need a start, end, href, target and src.
  2385. * Start is the time that you want this plug-in to execute
  2386. * End is the time that you want this plug-in to stop executing
  2387. * href is the url of the destination of a link - optional
  2388. * Target is the id of the document element that the iframe needs to be attached to,
  2389. * this target element must exist on the DOM
  2390. * Src is the url of the image that you want to display
  2391. * text is the overlayed text on the image - optional
  2392. *
  2393. * @param {Object} options
  2394. *
  2395. * Example:
  2396. var p = Popcorn('#video')
  2397. .image({
  2398. start: 5, // seconds
  2399. end: 15, // seconds
  2400. href: 'http://www.drumbeat.org/',
  2401. src: 'http://www.drumbeat.org/sites/default/files/domain-2/drumbeat_logo.png',
  2402. text: 'DRUMBEAT',
  2403. target: 'imagediv'
  2404. } )
  2405. *
  2406. */
  2407. Popcorn.plugin( "image", {
  2408. manifest: {
  2409. about:{
  2410. name: "Popcorn image Plugin",
  2411. version: "0.1",
  2412. author: "Scott Downe",
  2413. website: "http://scottdowne.wordpress.com/"
  2414. },
  2415. options: {
  2416. start: {
  2417. elem: "input",
  2418. type: "number",
  2419. label: "In"
  2420. },
  2421. end: {
  2422. elem: "input",
  2423. type: "number",
  2424. label: "Out"
  2425. },
  2426. href: {
  2427. elem: "input",
  2428. type: "text",
  2429. label: "Link URL"
  2430. },
  2431. target: "image-container",
  2432. src: {
  2433. elem: "input",
  2434. type: "text",
  2435. label: "Source URL"
  2436. },
  2437. text: {
  2438. elem: "input",
  2439. type: "text",
  2440. label: "TEXT"
  2441. }
  2442. }
  2443. },
  2444. _setup: function( options ) {
  2445. var img = document.createElement( "img" );
  2446. options.link = document.createElement( "a" );
  2447. options.link.style.position = "relative";
  2448. options.link.style.textDecoration = "none";
  2449. if ( document.getElementById( options.target ) ) {
  2450. // add the widget's div to the target div
  2451. document.getElementById( options.target ).appendChild( options.link );
  2452. }
  2453. img.addEventListener( "load", function() {
  2454. // borders look really bad, if someone wants it they can put it on their div target
  2455. img.style.borderStyle = "none";
  2456. if ( options.href ) {
  2457. options.link.href = options.href;
  2458. }
  2459. options.link.target = "_blank";
  2460. var fontHeight = ( img.height / 12 ) + "px",
  2461. divText = document.createElement( "div" );
  2462. Popcorn.extend( divText.style, {
  2463. color: "black",
  2464. fontSize: fontHeight,
  2465. fontWeight: "bold",
  2466. position: "relative",
  2467. textAlign: "center",
  2468. width: img.width + "px",
  2469. zIndex: "10"
  2470. });
  2471. divText.innerHTML = options.text || "";
  2472. options.link.appendChild( divText );
  2473. options.link.appendChild( img );
  2474. divText.style.top = ( img.height / 2 ) - ( divText.offsetHeight / 2 ) + "px";
  2475. options.link.style.display = "none";
  2476. }, false );
  2477. img.src = options.src;
  2478. },
  2479. /**
  2480. * @member image
  2481. * The start function will be executed when the currentTime
  2482. * of the video reaches the start time provided by the
  2483. * options variable
  2484. */
  2485. start: function( event, options ) {
  2486. options.link.style.display = "block";
  2487. },
  2488. /**
  2489. * @member image
  2490. * The end function will be executed when the currentTime
  2491. * of the video reaches the end time provided by the
  2492. * options variable
  2493. */
  2494. end: function( event, options ) {
  2495. options.link.style.display = "none";
  2496. },
  2497. _teardown: function( options ) {
  2498. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.link );
  2499. }
  2500. });
  2501. })( Popcorn );
  2502. // PLUGIN: GML
  2503. (function (Popcorn) {
  2504. var gmlPlayer = function( $p ) {
  2505. var _stroke = 0,
  2506. onPt = 0,
  2507. onStroke = 0,
  2508. x = null,
  2509. y = null,
  2510. rotation = false,
  2511. strokes = 0,
  2512. play = function() {},
  2513. reset = function() {
  2514. $p.background( 0 );
  2515. onPt = onStroke = 0;
  2516. x = y = null;
  2517. },
  2518. drawLine = function( x, y, x2, y2 ) {
  2519. var _x, _y, _x2, _y2;
  2520. if ( rotation ) {
  2521. _x = y * $p.height;
  2522. _y = $p.width - ( x * $p.width );
  2523. _x2 = y2 * $p.height;
  2524. _y2 = $p.width - ( x2 * $p.width );
  2525. } else {
  2526. _x = x * $p.width;
  2527. _y = y * $p.height;
  2528. _x2 = x2 * $p.width;
  2529. _y2 = y2 * $p.height;
  2530. }
  2531. $p.stroke( 0 );
  2532. $p.strokeWeight( 13 );
  2533. $p.strokeCap( $p.SQUARE );
  2534. $p.line( _x, _y, _x2, _y2 );
  2535. $p.stroke( 255 );
  2536. $p.strokeWeight( 12 );
  2537. $p.strokeCap( $p.ROUND );
  2538. $p.line( _x, _y, _x2, _y2 );
  2539. },
  2540. seek = function( point ) {
  2541. ( point < onPt ) && reset();
  2542. while ( onPt <= point ) {
  2543. if ( !strokes ) {
  2544. return;
  2545. }
  2546. _stroke = strokes[ onStroke ] || strokes;
  2547. var pt = _stroke.pt[ onPt ],
  2548. p = onPt;
  2549. x != null && drawLine( x, y, pt.x, pt.y );
  2550. x = pt.x;
  2551. y = pt.y;
  2552. ( onPt === p ) && onPt++;
  2553. }
  2554. };
  2555. $p.draw = function() {
  2556. play();
  2557. };
  2558. $p.setup = function() {};
  2559. $p.construct = function( media, data, options ) {
  2560. var dataReady = function() {
  2561. if ( data ) {
  2562. strokes = data.gml.tag.drawing.stroke;
  2563. var drawingDur = ( options.end - options.start ) / ( strokes.pt || (function( strokes ) {
  2564. var rStrokes = [];
  2565. for ( var i = 0, sl = strokes.length; i < sl; i++ ) {
  2566. rStrokes = rStrokes.concat( strokes[ i ].pt );
  2567. }
  2568. return rStrokes;
  2569. })( strokes ) ).length;
  2570. var tag = data.gml.tag,
  2571. app_name = tag.header && tag.header.client && tag.header.client.name;
  2572. rotation = app_name === 'Graffiti Analysis 2.0: DustTag' ||
  2573. app_name === 'DustTag: Graffiti Analysis 2.0' ||
  2574. app_name === 'Fat Tag - Katsu Edition';
  2575. play = function() {
  2576. if ( media.currentTime < options.endDrawing ) {
  2577. seek( ( media.currentTime - options.start ) / drawingDur );
  2578. }
  2579. };
  2580. return;
  2581. }
  2582. setTimeout( dataReady, 5 );
  2583. };
  2584. $p.size( 640, 640 );
  2585. $p.frameRate( 60 );
  2586. $p.smooth();
  2587. reset();
  2588. $p.noLoop();
  2589. dataReady();
  2590. };
  2591. };
  2592. /**
  2593. * Grafiti markup Language (GML) popcorn plug-in
  2594. * Renders a GML tag inside an HTML element
  2595. * Options parameter will need a mandatory start, end, target, gmltag.
  2596. * Optional parameters: none.
  2597. * Start is the time that you want this plug-in to execute
  2598. * End is the time that you want this plug-in to stop executing
  2599. * Target is the id of the document element that you wish to render the grafiti in
  2600. * gmltag is the numerical reference to a gml tag via 000000book.com
  2601. * @param {Object} options
  2602. *
  2603. * Example:
  2604. var p = Popcorn('#video')
  2605. .gml({
  2606. start: 0, // seconds
  2607. end: 5, // seconds
  2608. gmltag: '29582',
  2609. target: 'gmldiv'
  2610. });
  2611. *
  2612. */
  2613. Popcorn.plugin( "gml" , {
  2614. _setup : function( options ) {
  2615. var self = this;
  2616. options.endDrawing = options.endDrawing || options.end;
  2617. // create a canvas to put in the target div
  2618. options.container = document.createElement( "canvas" );
  2619. options.container.style.display = "none";
  2620. options.container.setAttribute( "id", "canvas" + options.gmltag );
  2621. document.getElementById( options.target ) && document.getElementById( options.target ).appendChild( options.container );
  2622. if ( !window.Processing ) {
  2623. Popcorn.getScript( "http://processingjs.org/content/download/processing-js-1.2.1/processing-1.2.1.min.js" );
  2624. }
  2625. // makes sure both processing.js and the gml data are loaded
  2626. var readyCheck = function() {
  2627. if ( window.Processing ) {
  2628. Popcorn.getJSONP( "http://000000book.com/data/" + options.gmltag + ".json?callback=", function( data ) {
  2629. options.pjsInstance = new Processing( options.container, gmlPlayer );
  2630. options.pjsInstance.construct( self.media, data, options );
  2631. options._running && options.pjsInstance.loop();
  2632. }, false );
  2633. return;
  2634. }
  2635. setTimeout( readyCheck, 5 );
  2636. };
  2637. readyCheck();
  2638. },
  2639. /**
  2640. * @member gml
  2641. * The start function will be executed when the currentTime
  2642. * of the video reaches the start time provided by the
  2643. * options variable
  2644. */
  2645. start: function( event, options ) {
  2646. options.pjsInstance && options.pjsInstance.loop();
  2647. options.container.style.display = "block";
  2648. },
  2649. /**
  2650. * @member gml
  2651. * The end function will be executed when the currentTime
  2652. * of the video reaches the end time provided by the
  2653. * options variable
  2654. */
  2655. end: function( event, options ) {
  2656. options.pjsInstance && options.pjsInstance.noLoop();
  2657. options.container.style.display = "none";
  2658. },
  2659. _teardown: function( options ) {
  2660. options.pjsInstance && options.pjsInstance.exit();
  2661. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.container );
  2662. }
  2663. });
  2664. })( Popcorn );
  2665. // PLUGIN: LASTFM
  2666. (function (Popcorn) {
  2667. var _artists = {},
  2668. lastFMcallback = function(data){
  2669. if (data.artist) {
  2670. var htmlString = "";
  2671. htmlString = '<h3>'+data.artist.name+'</h3>';
  2672. htmlString += '<a href="'+data.artist.url+'" target="_blank" style="float:left;margin:0 10px 0 0;"><img src="'+ data.artist.image[2]['#text'] +'" alt=""></a>';
  2673. htmlString += '<p>'+ data.artist.bio.summary +'</p>';
  2674. htmlString += '<hr /><p><h4>Tags</h4><ul>';
  2675. Popcorn.forEach( data.artist.tags.tag, function( val, i) {
  2676. htmlString += '<li><a href="'+ val.url +'">'+ val.name +'</a></li>';
  2677. });
  2678. htmlString += '</ul></p>';
  2679. htmlString += '<hr /><p><h4>Similar</h4><ul>';
  2680. Popcorn.forEach( data.artist.similar.artist, function( val, i) {
  2681. htmlString += '<li><a href="'+ val.url +'">'+ val.name +'</a></li>';
  2682. });
  2683. htmlString += '</ul></p>';
  2684. _artists[data.artist.name.toLowerCase()].htmlString = htmlString;
  2685. }
  2686. };
  2687. /**
  2688. * LastFM popcorn plug-in
  2689. * Appends information about a LastFM artist to an element on the page.
  2690. * Options parameter will need a start, end, target, artist and apikey.
  2691. * Start is the time that you want this plug-in to execute
  2692. * End is the time that you want this plug-in to stop executing
  2693. * Artist is the name of who's LastFM information you wish to show
  2694. * Target is the id of the document element that the images are
  2695. * appended to, this target element must exist on the DOM
  2696. * ApiKey is the API key registered with LastFM for use with their API
  2697. *
  2698. * @param {Object} options
  2699. *
  2700. * Example:
  2701. var p = Popcorn('#video')
  2702. .lastfm({
  2703. start: 5, // seconds, mandatory
  2704. end: 15, // seconds, mandatory
  2705. artist: 'yacht', // mandatory
  2706. target: 'lastfmdiv', // mandatory
  2707. apikey: '1234567890abcdef1234567890abcdef' // mandatory
  2708. } )
  2709. *
  2710. */
  2711. Popcorn.plugin( "lastfm" , (function(){
  2712. return {
  2713. manifest: {
  2714. about:{
  2715. name: "Popcorn LastFM Plugin",
  2716. version: "0.1",
  2717. author: "Steven Weerdenburg",
  2718. website: "http://sweerdenburg.wordpress.com/"
  2719. },
  2720. options:{
  2721. start : {elem:'input', type:'text', label:'In'},
  2722. end : {elem:'input', type:'text', label:'Out'},
  2723. target : 'lastfm-container',
  2724. artist : {elem:'input', type:'text', label:'Artist'}
  2725. }
  2726. },
  2727. _setup: function( options ) {
  2728. options._container = document.createElement( 'div' );
  2729. options._container.style.display = "none";
  2730. options._container.innerHTML = "";
  2731. options.artist = options.artist.toLowerCase();
  2732. document.getElementById( options.target ) && document.getElementById( options.target ).appendChild( options._container );
  2733. if(!_artists[options.artist]) {
  2734. _artists[options.artist] = {
  2735. count: 0,
  2736. htmlString: "Unknown Artist"
  2737. };
  2738. Popcorn.getJSONP("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+ options.artist +"&api_key="+options.apikey+"&format=json&callback=lastFMcallback", lastFMcallback, false );
  2739. }
  2740. _artists[options.artist].count++;
  2741. },
  2742. /**
  2743. * @member LastFM
  2744. * The start function will be executed when the currentTime
  2745. * of the video reaches the start time provided by the
  2746. * options variable
  2747. */
  2748. start: function( event, options ) {
  2749. options._container.innerHTML = _artists[options.artist].htmlString;
  2750. options._container.style.display = "inline";
  2751. },
  2752. /**
  2753. * @member LastFM
  2754. * The end function will be executed when the currentTime
  2755. * of the video reaches the end time provided by the
  2756. * options variable
  2757. */
  2758. end: function( event, options ) {
  2759. options._container.style.display = "none";
  2760. options._container.innerHTML = "";
  2761. },
  2762. _teardown: function( options ) {
  2763. // cleaning possible reference to _artist array;
  2764. --_artists[ options.artist ].count || delete _artists[ options.artist ];
  2765. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._container );
  2766. }
  2767. };
  2768. })());
  2769. })( Popcorn );
  2770. // PLUGIN: lowerthird
  2771. (function (Popcorn) {
  2772. /**
  2773. * Lower Third popcorn plug-in
  2774. * Displays information about a speaker over the video, or in the target div
  2775. * Options parameter will need a start, and end.
  2776. * Optional parameters are target, salutation, name and role.
  2777. * Start is the time that you want this plug-in to execute
  2778. * End is the time that you want this plug-in to stop executing
  2779. * Target is the id of the document element that the content is
  2780. * appended to, this target element must exist on the DOM
  2781. * salutation is the speaker's Mr. Ms. Dr. etc.
  2782. * name is the speaker's name.
  2783. * role is information about the speaker, example Engineer.
  2784. *
  2785. * @param {Object} options
  2786. *
  2787. * Example:
  2788. var p = Popcorn('#video')
  2789. .lowerthird({
  2790. start: 5, // seconds, mandatory
  2791. end: 15, // seconds, mandatory
  2792. salutation: 'Mr', // optional
  2793. name: 'Scott Downe', // optional
  2794. role: 'Programmer', // optional
  2795. target: 'subtitlediv' // optional
  2796. } )
  2797. *
  2798. */
  2799. Popcorn.plugin( "lowerthird" , {
  2800. manifest: {
  2801. about:{
  2802. name: "Popcorn lowerthird Plugin",
  2803. version: "0.1",
  2804. author: "Scott Downe",
  2805. website: "http://scottdowne.wordpress.com/"
  2806. },
  2807. options:{
  2808. start : {elem:'input', type:'text', label:'In'},
  2809. end : {elem:'input', type:'text', label:'Out'},
  2810. target : 'lowerthird-container',
  2811. salutation : {elem:'input', type:'text', label:'Text'},
  2812. name : {elem:'input', type:'text', label:'Text'},
  2813. role : {elem:'input', type:'text', label:'Text'}
  2814. }
  2815. },
  2816. _setup: function( options ) {
  2817. // Creates a div for all Lower Thirds to use
  2818. if ( !this.container ) {
  2819. this.container = document.createElement('div');
  2820. this.container.style.position = "absolute";
  2821. this.container.style.color = "white";
  2822. this.container.style.textShadow = "black 2px 2px 6px";
  2823. this.container.style.fontSize = "24px";
  2824. this.container.style.fontWeight = "bold";
  2825. this.container.style.paddingLeft = "40px";
  2826. // the video element must have height and width defined
  2827. this.container.style.width = this.video.offsetWidth + "px";
  2828. this.container.style.left = this.position().left + "px";
  2829. this.video.parentNode.appendChild( this.container );
  2830. }
  2831. // if a target is specified, use that
  2832. if ( options.target && options.target !== 'lowerthird-container' ) {
  2833. options.container = document.getElementById( options.target );
  2834. } else { // use shared default container
  2835. options.container = this.container;
  2836. }
  2837. },
  2838. /**
  2839. * @member lowerthird
  2840. * The start function will be executed when the currentTime
  2841. * of the video reaches the start time provided by the
  2842. * options variable
  2843. */
  2844. start: function(event, options){
  2845. options.container.innerHTML = ( options.salutation ? options.salutation + " " : "" ) + options.name + ( options.role ? "<br />" + options.role : "" );
  2846. this.container.style.top = this.position().top + this.video.offsetHeight - ( 40 + this.container.offsetHeight ) + "px";
  2847. },
  2848. /**
  2849. * @member lowerthird
  2850. * The end function will be executed when the currentTime
  2851. * of the video reaches the end time provided by the
  2852. * options variable
  2853. */
  2854. end: function(event, options){
  2855. options.container.innerHTML = "";
  2856. }
  2857. } );
  2858. })( Popcorn );
  2859. // PLUGIN: Google Feed
  2860. (function ( Popcorn ) {
  2861. var i = 1,
  2862. scriptLoaded = false,
  2863. dynamicFeedLoad = function() {
  2864. var dontLoad = false,
  2865. k = 0,
  2866. links = document.getElementsByTagName( "link" ),
  2867. len = links.length,
  2868. head = document.head || document.getElementsByTagName( "head" )[ 0 ],
  2869. css = document.createElement( "link" ),
  2870. resource = "http://www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.";
  2871. if ( !window.GFdynamicFeedControl ) {
  2872. Popcorn.getScript( resource + "js", function() {
  2873. scriptLoaded = true;
  2874. });
  2875. } else {
  2876. scriptLoaded = true;
  2877. }
  2878. // Checking if the css file is already included
  2879. for ( ; k < len; k++ ){
  2880. if ( links[ k ].href === resource + "css" ) {
  2881. dontLoad = true;
  2882. }
  2883. }
  2884. if ( !dontLoad ) {
  2885. css.type = "text/css";
  2886. css.rel = "stylesheet";
  2887. css.href = resource + "css";
  2888. head.insertBefore( css, head.firstChild );
  2889. }
  2890. };
  2891. if ( !window.google ) {
  2892. Popcorn.getScript( "http://www.google.com/jsapi", function() {
  2893. google.load( "feeds", "1", {
  2894. callback: function () {
  2895. dynamicFeedLoad();
  2896. }
  2897. });
  2898. });
  2899. } else {
  2900. dynamicFeedLoad();
  2901. }
  2902. /**
  2903. * googlefeed popcorn plug-in
  2904. * Adds a feed from the specified blog url at the target div
  2905. * Options parameter will need a start, end, target, url and title
  2906. * -Start is the time that you want this plug-in to execute
  2907. * -End is the time that you want this plug-in to stop executing
  2908. * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
  2909. * -Url is the url of the blog's feed you are trying to access
  2910. * -Title is the title of the blog you want displayed above the feed
  2911. * -Orientation is the orientation of the blog, accepts either Horizontal or Vertical, defaults to Vertical
  2912. * @param {Object} options
  2913. *
  2914. * Example:
  2915. var p = Popcorn("#video")
  2916. .googlefeed({
  2917. start: 5, // seconds
  2918. end: 15, // seconds
  2919. target: "map",
  2920. url: "http://zenit.senecac.on.ca/~chris.tyler/planet/rss20.xml",
  2921. title: "Planet Feed"
  2922. } )
  2923. *
  2924. */
  2925. Popcorn.plugin( "googlefeed" , function( options ) {
  2926. // create a new div and append it to the parent div so nothing
  2927. // that already exists in the parent div gets overwritten
  2928. var newdiv = document.createElement( "div" ),
  2929. initialize = function() {
  2930. //ensure that the script has been loaded
  2931. if ( !scriptLoaded ) {
  2932. setTimeout( function () {
  2933. initialize();
  2934. }, 5 );
  2935. } else {
  2936. // Create the feed control using the user entered url and title
  2937. options.feed = new GFdynamicFeedControl( options.url, newdiv, {
  2938. vertical: options.orientation.toLowerCase() === "vertical" ? true : false,
  2939. horizontal: options.orientation.toLowerCase() === "horizontal" ? true : false,
  2940. title: options.title = options.title || "Blog"
  2941. });
  2942. }
  2943. };
  2944. // Default to vertical orientation if empty or incorrect input
  2945. if( !options.orientation || ( options.orientation.toLowerCase() !== "vertical" &&
  2946. options.orientation.toLowerCase() !== "horizontal" ) ) {
  2947. options.orientation = "vertical";
  2948. }
  2949. newdiv.style.display = "none";
  2950. newdiv.id = "_feed" + i;
  2951. newdiv.style.width = "100%";
  2952. newdiv.style.height = "100%";
  2953. i++;
  2954. document.getElementById( options.target ).appendChild( newdiv );
  2955. initialize();
  2956. return {
  2957. /**
  2958. * @member webpage
  2959. * The start function will be executed when the currentTime
  2960. * of the video reaches the start time provided by the
  2961. * options variable
  2962. */
  2963. start: function( event, options ){
  2964. newdiv.setAttribute( "style", "display:inline" );
  2965. },
  2966. /**
  2967. * @member webpage
  2968. * The end function will be executed when the currentTime
  2969. * of the video reaches the end time provided by the
  2970. * options variable
  2971. */
  2972. end: function( event, options ){
  2973. newdiv.setAttribute( "style", "display:none" );
  2974. },
  2975. _teardown: function( options ) {
  2976. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( newdiv );
  2977. delete options.feed;
  2978. }
  2979. };
  2980. },
  2981. {
  2982. about: {
  2983. name: "Popcorn Google Feed Plugin",
  2984. version: "0.1",
  2985. author: "David Seifried",
  2986. website: "dseifried.wordpress.com"
  2987. },
  2988. options: {
  2989. start: {
  2990. elem: "input",
  2991. type: "text",
  2992. label: "In"
  2993. },
  2994. end: {
  2995. elem: "input",
  2996. type: "text",
  2997. label: "Out"
  2998. },
  2999. target: "feed-container",
  3000. url: {
  3001. elem: "input",
  3002. type: "text",
  3003. label: "url"
  3004. },
  3005. title: {
  3006. elem: "input",
  3007. type: "text",
  3008. label: "title"
  3009. },
  3010. orientation: {
  3011. elem: "select",
  3012. options: [ "Vertical","Horizontal" ],
  3013. label: "orientation"
  3014. }
  3015. }
  3016. });
  3017. })( Popcorn );
  3018. // PLUGIN: Subtitle
  3019. (function ( Popcorn ) {
  3020. var scriptLoaded = false,
  3021. i = 0,
  3022. callBack = function( data ) {
  3023. if ( window.google && google.load ) {
  3024. google.load( "language", "1", {
  3025. callback: function() {
  3026. scriptLoaded = true;
  3027. }
  3028. });
  3029. } else {
  3030. setTimeout( function() {
  3031. callBack( data );
  3032. }, 1);
  3033. }
  3034. },
  3035. createDefaultContainer = function( context ) {
  3036. // clear this function from future calls; we are done
  3037. createDefaultContainer = Popcorn.nop;
  3038. var updatePosition = function() {
  3039. var position = context.position();
  3040. // the video element must have height and width defined
  3041. style.fontSize = "18px";
  3042. style.width = media.offsetWidth + "px";
  3043. style.top = position.top + media.offsetHeight - ctxContainer.offsetHeight - 40 + "px";
  3044. style.left = position.left + "px";
  3045. setTimeout( updatePosition, 10 );
  3046. };
  3047. var ctxContainer = context.container = document.createElement( "div" ),
  3048. style = ctxContainer.style,
  3049. media = context.media;
  3050. ctxContainer.id = "subtitlediv";
  3051. style.position = "absolute";
  3052. style.color = "white";
  3053. style.textShadow = "black 2px 2px 6px";
  3054. style.fontWeight = "bold";
  3055. style.textAlign = "center";
  3056. updatePosition();
  3057. document.body.appendChild( ctxContainer );
  3058. };
  3059. Popcorn.getScript( "http://www.google.com/jsapi", callBack );
  3060. /**
  3061. * Subtitle popcorn plug-in
  3062. * Displays a subtitle over the video, or in the target div
  3063. * Options parameter will need a start, and end.
  3064. * Optional parameters are target and text.
  3065. * Start is the time that you want this plug-in to execute
  3066. * End is the time that you want this plug-in to stop executing
  3067. * Target is the id of the document element that the content is
  3068. * appended to, this target element must exist on the DOM
  3069. * Text is the text of the subtitle you want to display.
  3070. *
  3071. * Language is the expected language the subtitle text is in
  3072. * Languagesrc is the target id of the element that contains
  3073. * the language value ("en", "fr", etc.) to translate the text into
  3074. * example:
  3075. * <select id="language">
  3076. * <option value="zh" selected="selected">Chinese</option>
  3077. * <option value="en">English</option>
  3078. * <option value="fr">French</option>
  3079. * <option value="de">German</option>
  3080. * <option value="it">Italian</option>
  3081. * <option value="ja">Japanese</option>
  3082. * <option value="ko">Korean</option>
  3083. * <option value="fa">Persian</option>
  3084. * <option value="pl">Polish</option>
  3085. * <option value="pt">Portuguese</option>
  3086. * <option value="es">Spanish</option>
  3087. * </select>
  3088. * Accessibilitysrc is the target id of a checkbox element
  3089. * checked means show all subtitles regardless of language and languagesrc
  3090. * not checked means only translate if language and languagesrc are different
  3091. * if no accessibilitysrc exists, default is to display all subtitles regardless
  3092. *
  3093. * @param {Object} options
  3094. *
  3095. * Example:
  3096. var p = Popcorn('#video')
  3097. .subtitle({
  3098. start: 5, // seconds, mandatory
  3099. end: 15, // seconds, mandatory
  3100. text: 'Hellow world', // optional
  3101. target: 'subtitlediv', // optional
  3102. language: 'en', // optional
  3103. languagesrc: 'language', // optional
  3104. accessibilitysrc: 'accessibility' // optional
  3105. } )
  3106. *
  3107. */
  3108. // translates whatever is in options.container into selected language
  3109. var translate = function( options, text ) {
  3110. options.selectedLanguage = options.languageSrc.options[ options.languageSrc.selectedIndex ].value;
  3111. google.language.translate( text, "", options.selectedLanguage, function( result ) {
  3112. for( var k = 0, children = options.container.children, len = children.length; k < len; k++ ) {
  3113. if ( children[ k ].style.display === "inline" ) {
  3114. children[ k ].innerHTML = result.translation;
  3115. }
  3116. }
  3117. });
  3118. };
  3119. Popcorn.plugin( "subtitle" , {
  3120. manifest: {
  3121. about: {
  3122. name: "Popcorn Subtitle Plugin",
  3123. version: "0.1",
  3124. author: "Scott Downe",
  3125. website: "http://scottdowne.wordpress.com/"
  3126. },
  3127. options: {
  3128. start: {
  3129. elem: "input",
  3130. type: "text",
  3131. label: "In"
  3132. },
  3133. end: {
  3134. elem: "input",
  3135. type: "text",
  3136. label: "Out"
  3137. },
  3138. target: "subtitle-container",
  3139. text: {
  3140. elem: "input",
  3141. type: "text",
  3142. label: "Text"
  3143. }
  3144. }
  3145. },
  3146. _setup: function( options ) {
  3147. var newdiv = document.createElement( "div" ),
  3148. accessibility = document.getElementById( options.accessibilitysrc );
  3149. newdiv.id = "subtitle-" + i;
  3150. newdiv.style.display = "none";
  3151. i++;
  3152. // Creates a div for all subtitles to use
  3153. ( !this.container && !options.target || options.target === "subtitle-container" ) &&
  3154. createDefaultContainer( this );
  3155. // if a target is specified, use that
  3156. if ( options.target && options.target !== "subtitle-container" ) {
  3157. options.container = document.getElementById( options.target );
  3158. } else {
  3159. // use shared default container
  3160. options.container = this.container;
  3161. }
  3162. document.getElementById( options.container.id ).appendChild( newdiv );
  3163. options.innerContainer = newdiv;
  3164. options.showSubtitle = function() {
  3165. options.innerContainer.innerHTML = options.text;
  3166. };
  3167. options.toggleSubtitles = function() {};
  3168. var readyCheck = setInterval(function() {
  3169. if ( !scriptLoaded ) {
  3170. return;
  3171. }
  3172. clearInterval( readyCheck );
  3173. if ( options.languagesrc ) {
  3174. options.showSubtitle = translate;
  3175. options.languageSrc = document.getElementById( options.languagesrc );
  3176. options.selectedLanguage = options.languageSrc.options[ options.languageSrc.selectedIndex ].value;
  3177. if ( !this.languageSources ) {
  3178. this.languageSources = {};
  3179. }
  3180. if ( !this.languageSources[ options.languagesrc ] ) {
  3181. this.languageSources[ options.languagesrc ] = {};
  3182. }
  3183. if ( !this.languageSources[ options.languagesrc ][ options.target ] ) {
  3184. this.languageSources[ options.languagesrc ][ options.target ] = true;
  3185. options.languageSrc.addEventListener( "change", function() {
  3186. options.toggleSubtitles();
  3187. for( var k = 0, children = options.container.children, len = children.length; k < len; k++ ) {
  3188. if ( children[ k ].style.display === "inline" ) {
  3189. options.showSubtitle( options, children[ k ].innerHTML );
  3190. }
  3191. }
  3192. }, false );
  3193. }
  3194. }
  3195. if ( accessibility ) {
  3196. options.accessibility = accessibility;
  3197. options.toggleSubtitles = function() {
  3198. options.selectedLanguage = options.languageSrc.options[ options.languageSrc.selectedIndex ].value;
  3199. if ( options.accessibility.checked || options.selectedLanguage !== ( options.language || "") ) {
  3200. options.display = "inline";
  3201. options.container.style.display = options.display;
  3202. } else if ( options.selectedLanguage === ( options.language || "") ) {
  3203. options.display = "none";
  3204. options.container.style.display = options.display;
  3205. }
  3206. };
  3207. options.accessibility.addEventListener( "change", options.toggleSubtitles, false );
  3208. // initiate it once to set starting state
  3209. options.toggleSubtitles();
  3210. }
  3211. }, 5);
  3212. },
  3213. /**
  3214. * @member subtitle
  3215. * The start function will be executed when the currentTime
  3216. * of the video reaches the start time provided by the
  3217. * options variable
  3218. */
  3219. start: function( event, options ){
  3220. options.innerContainer.style.display = "inline";
  3221. options.showSubtitle( options, options.text );
  3222. },
  3223. /**
  3224. * @member subtitle
  3225. * The end function will be executed when the currentTime
  3226. * of the video reaches the end time provided by the
  3227. * options variable
  3228. */
  3229. end: function( event, options ) {
  3230. options.innerContainer.style.display = "none";
  3231. options.innerContainer.innerHTML = "";
  3232. },
  3233. _teardown: function ( options ) {
  3234. options.container.removeChild( options.innerContainer );
  3235. }
  3236. });
  3237. })( Popcorn );
  3238. // PLUGIN: tagthisperson
  3239. (function (Popcorn) {
  3240. var peopleArray = [];
  3241. // one People object per options.target
  3242. var People = function() {
  3243. this.name = "";
  3244. this.contains = { };
  3245. this.toString = function() {
  3246. var r = [];
  3247. for ( var j in this.contains ) {
  3248. if ( this.contains.hasOwnProperty( j ) ) {
  3249. r.push( " " + this.contains[ j ] );
  3250. }
  3251. }
  3252. return r.toString();
  3253. };
  3254. };
  3255. /**
  3256. * tagthisperson popcorn plug-in
  3257. * Adds people's names to an element on the page.
  3258. * Options parameter will need a start, end, target, image and person.
  3259. * Start is the time that you want this plug-in to execute
  3260. * End is the time that you want this plug-in to stop executing
  3261. * Person is the name of the person who you want to tag
  3262. * Image is the url to the image of the person - optional
  3263. * href is the url to the webpage of the person - optional
  3264. * Target is the id of the document element that the text needs to be
  3265. * attached to, this target element must exist on the DOM
  3266. *
  3267. * @param {Object} options
  3268. *
  3269. * Example:
  3270. var p = Popcorn('#video')
  3271. .tagthisperson({
  3272. start: 5, // seconds
  3273. end: 15, // seconds
  3274. person: '@annasob',
  3275. image: 'http://newshour.s3.amazonaws.com/photos%2Fspeeches%2Fguests%2FRichardNSmith_thumbnail.jpg',
  3276. href: 'http://annasob.wordpress.com',
  3277. target: 'tagdiv'
  3278. } )
  3279. *
  3280. */
  3281. Popcorn.plugin( "tagthisperson" , ( function() {
  3282. return {
  3283. manifest: {
  3284. about:{
  3285. name: "Popcorn tagthisperson Plugin",
  3286. version: "0.1",
  3287. author: "@annasob",
  3288. website: "annasob.wordpress.com"
  3289. },
  3290. options:{
  3291. start : {elem:'input', type:'text', label:'In'},
  3292. end : {elem:'input', type:'text', label:'Out'},
  3293. target : 'tagthisperson-container',
  3294. person : {elem:'input', type:'text', label:'Name'},
  3295. image : {elem:'input', type:'text', label:'Image Src'},
  3296. href : {elem:'input', type:'text', label:'URL'}
  3297. }
  3298. },
  3299. _setup: function( options ) {
  3300. var exists = false;
  3301. // loop through the existing objects to ensure no duplicates
  3302. // the idea here is to have one object per unique options.target
  3303. for ( var i = 0; i < peopleArray.length; i++ ) {
  3304. if ( peopleArray[ i ].name === options.target ) {
  3305. options._p = peopleArray[ i ];
  3306. exists = true;
  3307. break;
  3308. }
  3309. }
  3310. if ( !exists ) {
  3311. options._p = new People();
  3312. options._p.name = options.target;
  3313. peopleArray.push( options._p );
  3314. }
  3315. },
  3316. /**
  3317. * @member tagthisperson
  3318. * The start function will be executed when the currentTime
  3319. * of the video reaches the start time provided by the
  3320. * options variable
  3321. */
  3322. start: function( event, options ){
  3323. options._p.contains[ options.person ] = ( options.image ) ? "<img src='" + options.image + "'/> " : "" ;
  3324. options._p.contains[ options.person ] += ( options.href ) ? "<a href='" + options.href + "' target='_blank'> " + options.person + "</a>" : options.person ;
  3325. document.getElementById( options.target ).innerHTML = options._p.toString();
  3326. },
  3327. /**
  3328. * @member tagthisperson
  3329. * The end function will be executed when the currentTime
  3330. * of the video reaches the end time provided by the
  3331. * options variable
  3332. */
  3333. end: function( event, options ){
  3334. delete options._p.contains[ options.person ];
  3335. document.getElementById( options.target ).innerHTML = options._p.toString();
  3336. }
  3337. };
  3338. })());
  3339. })( Popcorn );
  3340. // PLUGIN: TWITTER
  3341. (function (Popcorn) {
  3342. var scriptLoading = false;
  3343. /**
  3344. * Twitter popcorn plug-in
  3345. * Appends a Twitter widget to an element on the page.
  3346. * Options parameter will need a start, end, target and source.
  3347. * Optional parameters are height and width.
  3348. * Start is the time that you want this plug-in to execute
  3349. * End is the time that you want this plug-in to stop executing
  3350. * Src is the hash tag or twitter user to get tweets from
  3351. * Target is the id of the document element that the images are
  3352. * appended to, this target element must exist on the DOM
  3353. * Height is the height of the widget, defaults to 200
  3354. * Width is the width of the widget, defaults to 250
  3355. *
  3356. * @param {Object} options
  3357. *
  3358. * Example:
  3359. var p = Popcorn('#video')
  3360. .twitter({
  3361. start: 5, // seconds, mandatory
  3362. end: 15, // seconds, mandatory
  3363. src: '@stevesong', // mandatory, also accepts hash tags
  3364. height: 200, // optional
  3365. width: 250, // optional
  3366. target: 'twitterdiv' // mandatory
  3367. } )
  3368. *
  3369. */
  3370. Popcorn.plugin( "twitter" , {
  3371. manifest: {
  3372. about:{
  3373. name: "Popcorn Twitter Plugin",
  3374. version: "0.1",
  3375. author: "Scott Downe",
  3376. website: "http://scottdowne.wordpress.com/"
  3377. },
  3378. options:{
  3379. start : {elem:'input', type:'number', label:'In'},
  3380. end : {elem:'input', type:'number', label:'Out'},
  3381. src : {elem:'input', type:'text', label:'Source'},
  3382. target : 'twitter-container',
  3383. height : {elem:'input', type:'number', label:'Height'},
  3384. width : {elem:'input', type:'number', label:'Width'}
  3385. }
  3386. },
  3387. _setup: function( options ) {
  3388. if ( !window.TWTR && !scriptLoading ) {
  3389. scriptLoading = true;
  3390. Popcorn.getScript("http://widgets.twimg.com/j/2/widget.js");
  3391. }
  3392. // setup widget div that is unique per track
  3393. options.container = document.createElement( 'div' ); // create the div to store the widget
  3394. options.container.setAttribute('id', Popcorn.guid()); // use this id to connect it to the widget
  3395. options.container.style.display = "none"; // display none by default
  3396. // add the widget's div to the target div
  3397. document.getElementById( options.target ) && document.getElementById( options.target ).appendChild( options.container );
  3398. // setup info for the widget
  3399. var src = options.src || "",
  3400. width = options.width || 250,
  3401. height = options.height || 200,
  3402. profile = /^@/.test( src ),
  3403. hash = /^#/.test( src ),
  3404. widgetOptions = {
  3405. version: 2,
  3406. id: options.container.getAttribute( 'id' ), // use this id to connect it to the div
  3407. rpp: 30,
  3408. width: width,
  3409. height: height,
  3410. interval: 6000,
  3411. theme: {
  3412. shell: {
  3413. background: '#ffffff',
  3414. color: '#000000'
  3415. },
  3416. tweets: {
  3417. background: '#ffffff',
  3418. color: '#444444',
  3419. links: '#1985b5'
  3420. }
  3421. },
  3422. features: {
  3423. loop: true,
  3424. timestamp: true,
  3425. avatars: true,
  3426. hashtags: true,
  3427. toptweets: true,
  3428. live: true,
  3429. scrollbar: false,
  3430. behavior: 'default'
  3431. }
  3432. };
  3433. // create widget
  3434. var isReady = function( that ) {
  3435. if ( window.TWTR ) {
  3436. if ( profile ) {
  3437. widgetOptions.type = "profile";
  3438. new TWTR.Widget( widgetOptions ).render().setUser( src ).start();
  3439. } else if ( hash ) {
  3440. widgetOptions.type = "search";
  3441. widgetOptions.search = src;
  3442. widgetOptions.subject = src;
  3443. new TWTR.Widget( widgetOptions ).render().start();
  3444. }
  3445. } else {
  3446. setTimeout( function() {
  3447. isReady( that );
  3448. }, 1);
  3449. }
  3450. };
  3451. isReady( this );
  3452. },
  3453. /**
  3454. * @member Twitter
  3455. * The start function will be executed when the currentTime
  3456. * of the video reaches the start time provided by the
  3457. * options variable
  3458. */
  3459. start: function( event, options ) {
  3460. options.container.style.display = "inline";
  3461. },
  3462. /**
  3463. * @member Twitter
  3464. * The end function will be executed when the currentTime
  3465. * of the video reaches the end time provided by the
  3466. * options variable
  3467. */
  3468. end: function( event, options ) {
  3469. options.container.style.display = "none";
  3470. },
  3471. _teardown: function( options ) {
  3472. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.container );
  3473. }
  3474. });
  3475. })( Popcorn );
  3476. // PLUGIN: WEBPAGE
  3477. (function (Popcorn) {
  3478. /**
  3479. * Webpages popcorn plug-in
  3480. * Creates an iframe showing a website specified by the user
  3481. * Options parameter will need a start, end, id, target and src.
  3482. * Start is the time that you want this plug-in to execute
  3483. * End is the time that you want this plug-in to stop executing
  3484. * Id is the id that you want assigned to the iframe
  3485. * Target is the id of the document element that the iframe needs to be attached to,
  3486. * this target element must exist on the DOM
  3487. * Src is the url of the website that you want the iframe to display
  3488. *
  3489. * @param {Object} options
  3490. *
  3491. * Example:
  3492. var p = Popcorn('#video')
  3493. .webpage({
  3494. id: "webpages-a",
  3495. start: 5, // seconds
  3496. end: 15, // seconds
  3497. src: 'http://www.webmademovies.org',
  3498. target: 'webpagediv'
  3499. } )
  3500. *
  3501. */
  3502. Popcorn.plugin( "webpage" , {
  3503. manifest: {
  3504. about:{
  3505. name: "Popcorn Webpage Plugin",
  3506. version: "0.1",
  3507. author: "@annasob",
  3508. website: "annasob.wordpress.com"
  3509. },
  3510. options:{
  3511. id : {elem:'input', type:'text', label:'Id'},
  3512. start : {elem:'input', type:'text', label:'In'},
  3513. end : {elem:'input', type:'text', label:'Out'},
  3514. src : {elem:'input', type:'text', label:'Src'},
  3515. target : 'iframe-container'
  3516. }
  3517. },
  3518. _setup : function( options ) {
  3519. // make an iframe
  3520. options._iframe = document.createElement( 'iframe' );
  3521. options._iframe.setAttribute('width', "100%");
  3522. options._iframe.setAttribute('height', "100%");
  3523. options._iframe.id = options.id;
  3524. options._iframe.src = options.src;
  3525. options._iframe.style.display = 'none';
  3526. // add the hidden iframe to the DOM
  3527. document.getElementById( options.target ) && document.getElementById( options.target ).appendChild(options._iframe);
  3528. },
  3529. /**
  3530. * @member webpage
  3531. * The start function will be executed when the currentTime
  3532. * of the video reaches the start time provided by the
  3533. * options variable
  3534. */
  3535. start: function(event, options){
  3536. // make the iframe visible
  3537. options._iframe.src = options.src;
  3538. options._iframe.style.display = 'inline';
  3539. },
  3540. /**
  3541. * @member webpage
  3542. * The end function will be executed when the currentTime
  3543. * of the video reaches the end time provided by the
  3544. * options variable
  3545. */
  3546. end: function(event, options){
  3547. // make the iframe invisible
  3548. options._iframe.style.display = 'none';
  3549. },
  3550. _teardown: function( options ) {
  3551. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._iframe );
  3552. }
  3553. });
  3554. })( Popcorn );
  3555. // PLUGIN: WIKIPEDIA
  3556. var wikiCallback;
  3557. (function ( Popcorn ) {
  3558. /**
  3559. * Wikipedia popcorn plug-in
  3560. * Displays a wikipedia aricle in the target specified by the user by using
  3561. * new DOM element instead overwriting them
  3562. * Options parameter will need a start, end, target, lang, src, title and numberofwords.
  3563. * -Start is the time that you want this plug-in to execute
  3564. * -End is the time that you want this plug-in to stop executing
  3565. * -Target is the id of the document element that the text from the article needs to be
  3566. * attached to, this target element must exist on the DOM
  3567. * -Lang (optional, defaults to english) is the language in which the article is in.
  3568. * -Src is the url of the article
  3569. * -Title (optional) is the title of the article
  3570. * -numberofwords (optional, defaults to 200) is the number of words you want displaid.
  3571. *
  3572. * @param {Object} options
  3573. *
  3574. * Example:
  3575. var p = Popcorn("#video")
  3576. .wikipedia({
  3577. start: 5, // seconds
  3578. end: 15, // seconds
  3579. src: "http://en.wikipedia.org/wiki/Cape_Town",
  3580. target: "wikidiv"
  3581. } )
  3582. *
  3583. */
  3584. Popcorn.plugin( "wikipedia" , {
  3585. manifest: {
  3586. about:{
  3587. name: "Popcorn Wikipedia Plugin",
  3588. version: "0.1",
  3589. author: "@annasob",
  3590. website: "annasob.wordpress.com"
  3591. },
  3592. options:{
  3593. start: {
  3594. elem: "input",
  3595. type: "text",
  3596. label: "In"
  3597. },
  3598. end: {
  3599. elem: "input",
  3600. type: "text",
  3601. label: "Out"
  3602. },
  3603. lang: {
  3604. elem: "input",
  3605. type: "text",
  3606. label: "Language"
  3607. },
  3608. src: {
  3609. elem: "input",
  3610. type: "text",
  3611. label: "Src"
  3612. },
  3613. title: {
  3614. elem: "input",
  3615. type: "text",
  3616. label: "Title"
  3617. },
  3618. numberofwords: {
  3619. elem: "input",
  3620. type: "text",
  3621. label: "Num Of Words"
  3622. },
  3623. target: "wikipedia-container"
  3624. }
  3625. },
  3626. /**
  3627. * @member wikipedia
  3628. * The setup function will get all of the needed
  3629. * items in place before the start function is called.
  3630. * This includes getting data from wikipedia, if the data
  3631. * is not received and processed before start is called start
  3632. * will not do anything
  3633. */
  3634. _setup : function( options ) {
  3635. // declare needed variables
  3636. // get a guid to use for the global wikicallback function
  3637. var _text, _guid = Popcorn.guid();
  3638. // if the user didn't specify a language default to english
  3639. if ( !options.lang ) {
  3640. options.lang = "en";
  3641. }
  3642. // if the user didn't specify number of words to use default to 200
  3643. options.numberofwords = options.numberofwords || 200;
  3644. // wiki global callback function with a unique id
  3645. // function gets the needed information from wikipedia
  3646. // and stores it by appending values to the options object
  3647. window[ "wikiCallback" + _guid ] = function ( data ) {
  3648. options._link = document.createElement( "a" );
  3649. options._link.setAttribute( "href", options.src );
  3650. options._link.setAttribute( "target", "_blank" );
  3651. // add the title of the article to the link
  3652. options._link.innerHTML = options.title || data.parse.displaytitle;
  3653. // get the content of the wiki article
  3654. options._desc = document.createElement( "p" );
  3655. // get the article text and remove any special characters
  3656. _text = data.parse.text[ "*" ].substr( data.parse.text[ "*" ].indexOf( "<p>" ) );
  3657. _text = _text.replace( /((<(.|\n)+?>)|(\((.*?)\) )|(\[(.*?)\]))/g, "" );
  3658. options._desc.innerHTML = _text.substr( 0, options.numberofwords ) + " ...";
  3659. options._fired = true;
  3660. };
  3661. if ( options.src ) {
  3662. Popcorn.getScript( "http://" + options.lang + ".wikipedia.org/w/api.php?action=parse&props=text&page=" +
  3663. options.src.slice( options.src.lastIndexOf("/")+1) + "&format=json&callback=wikiCallback" + _guid);
  3664. } else {
  3665. throw ( "Wikipedia plugin needs a 'src'" );
  3666. }
  3667. },
  3668. /**
  3669. * @member wikipedia
  3670. * The start function will be executed when the currentTime
  3671. * of the video reaches the start time provided by the
  3672. * options variable
  3673. */
  3674. start: function( event, options ){
  3675. // dont do anything if the information didn't come back from wiki
  3676. var isReady = function () {
  3677. if ( !options._fired ) {
  3678. setTimeout( function () {
  3679. isReady();
  3680. }, 13);
  3681. } else {
  3682. if ( options._link && options._desc ) {
  3683. if ( document.getElementById( options.target ) ) {
  3684. document.getElementById( options.target ).appendChild( options._link );
  3685. document.getElementById( options.target ).appendChild( options._desc );
  3686. options._added = true;
  3687. }
  3688. }
  3689. }
  3690. };
  3691. isReady();
  3692. },
  3693. /**
  3694. * @member wikipedia
  3695. * The end function will be executed when the currentTime
  3696. * of the video reaches the end time provided by the
  3697. * options variable
  3698. */
  3699. end: function( event, options ){
  3700. // ensure that the data was actually added to the
  3701. // DOM before removal
  3702. if ( options._added ) {
  3703. document.getElementById( options.target ).removeChild( options._link );
  3704. document.getElementById( options.target ).removeChild( options._desc );
  3705. }
  3706. },
  3707. _teardown: function( options ){
  3708. if ( options._added ) {
  3709. options._link.parentNode && document.getElementById( options.target ).removeChild( options._link );
  3710. options._desc.parentNode && document.getElementById( options.target ).removeChild( options._desc );
  3711. delete options.target;
  3712. }
  3713. }
  3714. });
  3715. })( Popcorn );
  3716. //PLUGIN: linkedin
  3717. (function (Popcorn){
  3718. /**
  3719. * LinkedIn Popcorn plug-in
  3720. * Places a LinkedIn plugin inside a div ( http://developers.facebook.com/docs/plugins/ )
  3721. * Options parameter will need a start, end, target, type, and an api key
  3722. * Optional parameters are url, counter, format, companyid, and productid
  3723. * Start is the time that you want this plug-in to execute
  3724. * End is the time that you want this plug-in to stop executing
  3725. * Target is the id of the document element that the plugin needs to be attached to, this target element must exist on the DOM
  3726. * Type is the name of the plugin, options are share, memberprofile, companyinsider, companyprofile, or recommendproduct
  3727. * Apikey is your own api key from obtained from https://www.linkedin.com/secure/developer
  3728. * Url is the desired url to share via LinkedIn. Defaults to the current page if no url is specified
  3729. * Counter is the position where the counter will be positioned. This is used if the type is "share" or "recommendproduct"
  3730. * The options are right and top (don't include this option if you do not want a counter)
  3731. * Format is the data format of the member and company profile plugins. The options are inlined, hover, and click. Defaults to inline
  3732. * Companyid must be specified if the type is "companyprofile," "companyinsider," or "recommendproduct"
  3733. * Productid must be specified if the type is "recommendproduct"
  3734. *
  3735. * @param {Object} options
  3736. *
  3737. * Example:
  3738. * <script src="popcorn.linkedin.js"></script>
  3739. * ...
  3740. * var p = Popcorn("#video")
  3741. * .linkedin({
  3742. * type: "share",
  3743. * url: "http://www.google.ca",
  3744. * counter: "right",
  3745. * target: "sharediv"
  3746. * apikey: "ZOLRI2rzQS_oaXELpPF0aksxwFFEvoxAFZRLfHjaAhcGPfOX0Ds4snkJpWwKs8gk",
  3747. * start: 1,
  3748. * end: 3
  3749. * })
  3750. *
  3751. * This plugin will be displayed between 1 and 3 seconds, inclusive, in the video. This will show how many people have "shared" Google via LinkedIn,
  3752. * with the number of people (counter) displayed to the right of the share plugin.
  3753. */
  3754. Popcorn.plugin( "linkedin", {
  3755. manifest: {
  3756. about: {
  3757. name: "Popcorn LinkedIn Plugin",
  3758. version: "0.1",
  3759. author: "Dan Ventura",
  3760. website: "dsventura.blogspot.com"
  3761. },
  3762. options: {
  3763. type: {
  3764. elem: "input",
  3765. type: "text",
  3766. label: "Type"
  3767. },
  3768. target: "linkedin-container"
  3769. }
  3770. },
  3771. _setup: function( options ) {
  3772. var apikey = options.apikey,
  3773. target = document.getElementById( options.target ),
  3774. script = document.createElement( "script" );
  3775. Popcorn.getScript("http://platform.linkedin.com/in.js");
  3776. options._container = document.createElement( "div" );
  3777. options._container.appendChild( script );
  3778. if ( apikey ) {
  3779. script.innerHTML = "api_key: " + apikey;
  3780. }
  3781. options.type = options.type.toLowerCase();
  3782. // Replace the LinkedIn plugin's error message to something more helpful
  3783. var errorMsg = function() {
  3784. options._container = document.createElement( "p" );
  3785. options._container.innerHTML = "Plugin requires a valid <a href='https://www.linkedin.com/secure/developer'>apikey</a>";
  3786. document.getElementById( options.target ).appendChild( options._container );
  3787. };
  3788. var setOptions = (function ( options ) {
  3789. return {
  3790. share: function () {
  3791. script.setAttribute( "type", "IN/Share" );
  3792. if ( options.counter ) {
  3793. script.setAttribute( "data-counter", options.counter );
  3794. }
  3795. if ( options.url ) {
  3796. script.setAttribute( "data-url", options.url );
  3797. }
  3798. },
  3799. memberprofile: function () {
  3800. script.setAttribute( "type", "IN/MemberProfile" );
  3801. script.setAttribute( "data-id", ( options.memberid ) );
  3802. script.setAttribute( "data-format", ( options.format || "inline" ) );
  3803. if ( options.text && options.format.toLowerCase() !== "inline" ) {
  3804. script.setAttribute( "data-text", options.text );
  3805. }
  3806. },
  3807. companyinsider: function () {
  3808. script.setAttribute( "type", "IN/CompanyInsider" );
  3809. script.setAttribute( "data-id", options.companyid );
  3810. if( options.modules ) {
  3811. options._container.setAttribute( "data-modules", options.modules );
  3812. }
  3813. },
  3814. companyprofile: function () {
  3815. script.setAttribute( "type", "IN/CompanyProfile" );
  3816. script.setAttribute( "data-id", ( options.companyid ) );
  3817. script.setAttribute( "data-format", ( options.format || "inline" ) );
  3818. if ( options.text && options.format.toLowerCase() !== "inline" ) {
  3819. script.setAttribute( "data-text", options.text );
  3820. }
  3821. if ( options.related !== undefined ) {
  3822. script.setAttribute( "data-related", options.related );
  3823. }
  3824. },
  3825. recommendproduct: function () {
  3826. script.setAttribute( "type", "IN/RecommendProduct" );
  3827. script.setAttribute( "data-company", ( options.companyid || "LinkedIn" ) );
  3828. script.setAttribute( "data-product", ( options.productid || "201714" ) );
  3829. if ( options.counter ) {
  3830. script.setAttribute( "data-counter", options.counter );
  3831. }
  3832. }
  3833. };
  3834. })( options );
  3835. if ( !apikey ) {
  3836. errorMsg();
  3837. } else {
  3838. setOptions[ options.type ] && setOptions[ options.type ]();
  3839. }
  3840. if ( document.getElementById( options.target ) ) {
  3841. document.getElementById( options.target ).appendChild( options._container );
  3842. }
  3843. target.style.display = "none";
  3844. },
  3845. /**
  3846. * @member linkedin
  3847. * The start function will be executed when the currentTime
  3848. * of the video reaches the start time provided by the
  3849. * options variable
  3850. */
  3851. start: function( event, options ) {
  3852. options._container.parentNode.style.display = "block";
  3853. },
  3854. /**
  3855. * @member linkedin
  3856. * The end function will be executed when the currentTime
  3857. * of the video reaches the end time provided by the
  3858. * options variable
  3859. */
  3860. end: function( event, options ) {
  3861. options._container.parentNode.style.display = "none";
  3862. },
  3863. _teardown: function( options ) {
  3864. var tar = document.getElementById( options.target );
  3865. tar && tar.removeChild( options._container );
  3866. }
  3867. });
  3868. })( Popcorn );
  3869. // PLUGIN: Mustache
  3870. (function (Popcorn) {
  3871. /**
  3872. * Mustache Popcorn Plug-in
  3873. *
  3874. * Adds the ability to render JSON using templates via the Mustache templating library.
  3875. *
  3876. * @param {Object} options
  3877. *
  3878. * Required parameters: start, end, template, data, and target.
  3879. * Optional parameter: static.
  3880. *
  3881. * start: the time in seconds when the mustache template should be rendered
  3882. * in the target div.
  3883. *
  3884. * end: the time in seconds when the rendered mustache template should be
  3885. * removed from the target div.
  3886. *
  3887. * target: a String -- the target div's id.
  3888. *
  3889. * template: the mustache template for the plugin to use when rendering. This can be
  3890. * a String containing the template, or a Function that returns the template's
  3891. * String.
  3892. *
  3893. * data: the data to be rendered using the mustache template. This can be a JSON String,
  3894. * a JavaScript Object literal, or a Function returning a String or Literal.
  3895. *
  3896. * dynamic: an optional argument indicating that the template and json data are dynamic
  3897. * and need to be loaded dynamically on every use. Defaults to True.
  3898. *
  3899. * Example:
  3900. var p = Popcorn('#video')
  3901. // Example using template and JSON strings.
  3902. .mustache({
  3903. start: 5, // seconds
  3904. end: 15, // seconds
  3905. target: 'mustache',
  3906. template: '<h1>{{header}}</h1>' +
  3907. '{{#bug}}' +
  3908. '{{/bug}}' +
  3909. '' +
  3910. '{{#items}}' +
  3911. ' {{#first}}' +
  3912. ' <li><strong>{{name}}</strong></li>' +
  3913. ' {{/first}}' +
  3914. ' {{#link}}' +
  3915. ' <li><a href="{{url}}">{{name}}</a></li>' +
  3916. ' {{/link}}' +
  3917. '{{/items}}' +
  3918. '' +
  3919. '{{#empty}}' +
  3920. ' <p>The list is empty.</p>' +
  3921. '{{/empty}}' ,
  3922. data: '{' +
  3923. ' "header": "Colors", ' +
  3924. ' "items": [ ' +
  3925. ' {"name": "red", "first": true, "url": "#Red"}, ' +
  3926. ' {"name": "green", "link": true, "url": "#Green"}, ' +
  3927. ' {"name": "blue", "link": true, "url": "#Blue"} ' +
  3928. ' ],' +
  3929. ' 'empty': false' +
  3930. '}',
  3931. dynamic: false // The json is not going to change, load it early.
  3932. } )
  3933. // Example showing Functions instead of Strings.
  3934. .mustache({
  3935. start: 20, // seconds
  3936. end: 25, // seconds
  3937. target: 'mustache',
  3938. template: function(instance, options) {
  3939. var template = // load your template file here...
  3940. return template;
  3941. },
  3942. data: function(instance, options) {
  3943. var json = // load your json here...
  3944. return json;
  3945. }
  3946. } );
  3947. *
  3948. */
  3949. Popcorn.plugin( 'mustache' , function( options ) {
  3950. var getData, data, getTemplate, template, loaded = false;
  3951. Popcorn.getScript('https://github.com/janl/mustache.js/raw/master/mustache.js', function() {
  3952. loaded = true;
  3953. });
  3954. var shouldReload = !!options.dynamic,
  3955. typeOfTemplate = typeof options.template,
  3956. typeOfData = typeof options.data;
  3957. if ( typeOfTemplate === 'function' ) {
  3958. if ( !shouldReload ) {
  3959. template = options.template( options );
  3960. } else {
  3961. getTemplate = options.template;
  3962. }
  3963. } else if ( typeOfTemplate === 'string' ) {
  3964. template = options.template;
  3965. } else {
  3966. Popcorn.error( 'Mustache Plugin Error: options.template must be a String or a Function.' );
  3967. }
  3968. if ( typeOfData === 'function' ) {
  3969. if ( !shouldReload ) {
  3970. data = options.data(options);
  3971. } else {
  3972. getData = options.data;
  3973. }
  3974. } else if ( typeOfData === 'string' ) {
  3975. data = JSON.parse( options.data );
  3976. } else if ( typeOfData === 'object' ) {
  3977. data = options.data;
  3978. } else {
  3979. Popcorn.error( 'Mustache Plugin Error: options.data must be a String, Object, or Function.' );
  3980. }
  3981. return {
  3982. start: function( event, options ) {
  3983. var interval = function() {
  3984. if( !loaded ) {
  3985. setTimeout( function() {
  3986. interval();
  3987. }, 10 );
  3988. } else {
  3989. // if dynamic, freshen json data on every call to start, just in case.
  3990. if ( getData ) {
  3991. data = getData( options );
  3992. }
  3993. if ( getTemplate ) {
  3994. template = getTemplate( options );
  3995. }
  3996. var html = Mustache.to_html( template,
  3997. data
  3998. ).replace( /^\s*/mg, '' );
  3999. document.getElementById( options.target ).innerHTML = html;
  4000. }
  4001. };
  4002. interval();
  4003. },
  4004. end: function( event, options ) {
  4005. document.getElementById( options.target ).innerHTML = '';
  4006. },
  4007. _teardown: function( options ) {
  4008. getData = data = getTemplate = template = null;
  4009. }
  4010. };
  4011. },
  4012. {
  4013. about: {
  4014. name: 'Popcorn Mustache Plugin',
  4015. version: '0.1',
  4016. author: 'David Humphrey (@humphd)',
  4017. website: 'http://vocamus.net/dave'
  4018. },
  4019. options: {
  4020. start: {elem:'input', type:'text', label:'In'},
  4021. end: {elem:'input', type:'text', label:'Out'},
  4022. target: 'mustache-container',
  4023. template: {elem:'input', type:'text', label:'Template'},
  4024. data: {elem:'input', type:'text', label:'Data'},
  4025. /* TODO: how to show a checkbox/boolean? */
  4026. dynamic: {elem:'input', type:'text', label:'Dynamic'}
  4027. }
  4028. });
  4029. })( Popcorn );
  4030. // PLUGIN: OPENMAP
  4031. ( function ( Popcorn ) {
  4032. /**
  4033. * openmap popcorn plug-in
  4034. * Adds an OpenLayers map and open map tiles (OpenStreetMap [default], NASA WorldWind, or USGS Topographic)
  4035. * Based on the googlemap popcorn plug-in. No StreetView support
  4036. * Options parameter will need a start, end, target, type, zoom, lat and lng
  4037. * -Start is the time that you want this plug-in to execute
  4038. * -End is the time that you want this plug-in to stop executing
  4039. * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
  4040. * -Type [optional] either: ROADMAP (OpenStreetMap), SATELLITE (NASA WorldWind / LandSat), or TERRAIN (USGS). ROADMAP/OpenStreetMap is the default.
  4041. * -Zoom [optional] defaults to 2
  4042. * -Lat and Lng are the coordinates of the map if location is not named
  4043. * -Location is a name of a place to center the map, geocoded to coordinates using TinyGeocoder.com
  4044. * -Markers [optional] is an array of map marker objects, with the following properties:
  4045. * --Icon is the URL of a map marker image
  4046. * --Size [optional] is the radius in pixels of the scaled marker image (default is 14)
  4047. * --Text [optional] is the HTML content of the map marker -- if your popcorn instance is named 'popped', use <script>popped.currentTime(10);</script> to control the video
  4048. * --Lat and Lng are coordinates of the map marker if location is not specified
  4049. * --Location is a name of a place for the map marker, geocoded to coordinates using TinyGeocoder.com
  4050. * Note: using location requires extra loading time, also not specifying both lat/lng and location will
  4051. * cause a JavaScript error.
  4052. * @param {Object} options
  4053. *
  4054. * Example:
  4055. var p = Popcorn( '#video' )
  4056. .openmap({
  4057. start: 5,
  4058. end: 15,
  4059. type: 'ROADMAP',
  4060. target: 'map',
  4061. lat: 43.665429,
  4062. lng: -79.403323
  4063. } )
  4064. *
  4065. */
  4066. var newdiv,
  4067. i = 1,
  4068. _mapFired = false,
  4069. _mapLoaded = false;
  4070. // insert openlayers api script once
  4071. if ( !_mapFired ) {
  4072. _mapFired = true;
  4073. Popcorn.getScript('http://openlayers.org/api/OpenLayers.js',
  4074. function() {
  4075. _mapLoaded = true;
  4076. } );
  4077. }
  4078. Popcorn.plugin( "openmap" , function( options ){
  4079. var newdiv,
  4080. map,
  4081. centerlonlat,
  4082. projection,
  4083. displayProjection,
  4084. pointLayer,
  4085. selectControl,
  4086. popup;
  4087. // create a new div within the target div
  4088. // this is later passed on to the maps api
  4089. newdiv = document.createElement( 'div' );
  4090. newdiv.id = "openmapdiv" + i;
  4091. newdiv.style.width = "100%";
  4092. newdiv.style.height = "100%";
  4093. i++;
  4094. document.getElementById( options.target ) && document.getElementById( options.target ).appendChild( newdiv );
  4095. // callback function fires when the script is run
  4096. var isGeoReady = function() {
  4097. if ( !_mapLoaded ) {
  4098. setTimeout( function () {
  4099. isGeoReady();
  4100. }, 50);
  4101. } else {
  4102. if( options.location ){
  4103. // set a dummy center at start
  4104. location = new OpenLayers.LonLat( 0, 0 );
  4105. // query TinyGeocoder and re-center in callback
  4106. Popcorn.getJSONP(
  4107. "http://tinygeocoder.com/create-api.php?q=" + options.location + "&callback=jsonp",
  4108. function( latlng ) {
  4109. centerlonlat = new OpenLayers.LonLat( latlng[1], latlng[0] );
  4110. map.setCenter( centerlonlat );
  4111. }
  4112. );
  4113. } else {
  4114. centerlonlat = new OpenLayers.LonLat( options.lng, options.lat );
  4115. }
  4116. options.type = options.type || "ROADMAP";
  4117. if( options.type == "SATELLITE" ) {
  4118. // add NASA WorldWind / LANDSAT map
  4119. map = new OpenLayers.Map( { div: newdiv, "maxResolution": 0.28125, tileSize: new OpenLayers.Size( 512, 512 ) } );
  4120. var worldwind = new OpenLayers.Layer.WorldWind( "LANDSAT", "http://worldwind25.arc.nasa.gov/tile/tile.aspx", 2.25, 4, { T: "105" } );
  4121. map.addLayer( worldwind );
  4122. displayProjection = new OpenLayers.Projection( "EPSG:4326" );
  4123. projection = new OpenLayers.Projection( "EPSG:4326" );
  4124. }
  4125. else if( options.type == "TERRAIN" ) {
  4126. // add terrain map ( USGS )
  4127. displayProjection = new OpenLayers.Projection( "EPSG:4326" );
  4128. projection = new OpenLayers.Projection( "EPSG:4326" );
  4129. map = new OpenLayers.Map( {div: newdiv, projection: projection } );
  4130. var relief = new OpenLayers.Layer.WMS( "USGS Terraserver", "http://terraserver-usa.org/ogcmap.ashx?", { layers: 'DRG' } );
  4131. map.addLayer( relief );
  4132. }
  4133. else {
  4134. // add OpenStreetMap layer
  4135. projection = new OpenLayers.Projection( 'EPSG:900913' );
  4136. displayProjection = new OpenLayers.Projection( 'EPSG:4326' );
  4137. centerlonlat = centerlonlat.transform( displayProjection, projection );
  4138. map = new OpenLayers.Map( { div: newdiv, projection: projection, "displayProjection": displayProjection } );
  4139. var osm = new OpenLayers.Layer.OSM();
  4140. map.addLayer( osm );
  4141. }
  4142. if( map ) {
  4143. map.div.style.display = "none";
  4144. }
  4145. }
  4146. };
  4147. isGeoReady();
  4148. return {
  4149. /**
  4150. * @member openmap
  4151. * The start function will be executed when the currentTime
  4152. * of the video reaches the start time provided by the
  4153. * options variable
  4154. */
  4155. start: function( event, options ) {
  4156. var isReady = function () {
  4157. // wait until OpenLayers has been loaded, and the start function is run, before adding map
  4158. if ( !map ) {
  4159. setTimeout( function () {
  4160. isReady();
  4161. }, 13 );
  4162. } else {
  4163. map.div.style.display = "block";
  4164. // make sure options.zoom is a number
  4165. if ( options.zoom && typeof options.zoom !== "number" ) {
  4166. options.zoom = +options.zoom;
  4167. }
  4168. // default zoom is 2
  4169. options.zoom = options.zoom || 2;
  4170. // reset the location and zoom just in case the user played with the map
  4171. map.setCenter( centerlonlat, options.zoom );
  4172. if( options.markers ){
  4173. var layerStyle = OpenLayers.Util.extend( {} , OpenLayers.Feature.Vector.style[ 'default' ] ),
  4174. featureSelected = function( clickInfo ) {
  4175. clickedFeature = clickInfo.feature;
  4176. if( !clickedFeature.attributes.text ){
  4177. return;
  4178. }
  4179. popup = new OpenLayers.Popup.FramedCloud(
  4180. "featurePopup",
  4181. clickedFeature.geometry.getBounds().getCenterLonLat(),
  4182. new OpenLayers.Size( 120, 250 ),
  4183. clickedFeature.attributes.text,
  4184. null,
  4185. true,
  4186. function( closeInfo ) {
  4187. selectControl.unselect( this.feature );
  4188. }
  4189. );
  4190. clickedFeature.popup = popup;
  4191. popup.feature = clickedFeature;
  4192. map.addPopup( popup );
  4193. },
  4194. featureUnSelected = function( clickInfo ) {
  4195. feature = clickInfo.feature;
  4196. if ( feature.popup ) {
  4197. popup.feature = null;
  4198. map.removePopup( feature.popup );
  4199. feature.popup.destroy();
  4200. feature.popup = null;
  4201. }
  4202. },
  4203. gcThenPlotMarker = function( myMarker ){
  4204. Popcorn.getJSONP(
  4205. "http://tinygeocoder.com/create-api.php?q=" + myMarker.location + "&callback=jsonp",
  4206. function( latlng ){
  4207. var myPoint = new OpenLayers.Geometry.Point( latlng[1], latlng[0] ).transform( displayProjection, projection ),
  4208. myPointStyle = OpenLayers.Util.extend( {}, layerStyle );
  4209. if( !myMarker.size || isNaN( myMarker.size ) ) {
  4210. myMarker.size = 14;
  4211. }
  4212. myPointStyle.pointRadius = myMarker.size;
  4213. myPointStyle.graphicOpacity = 1;
  4214. myPointStyle.externalGraphic = myMarker.icon;
  4215. var myPointFeature = new OpenLayers.Feature.Vector( myPoint, null, myPointStyle );
  4216. if( myMarker.text ) {
  4217. myPointFeature.attributes = {
  4218. text: myMarker.text
  4219. };
  4220. }
  4221. pointLayer.addFeatures( [ myPointFeature ] );
  4222. }
  4223. );
  4224. };
  4225. pointLayer = new OpenLayers.Layer.Vector( "Point Layer", { style: layerStyle } );
  4226. map.addLayer( pointLayer );
  4227. for( var m = 0; m < options.markers.length; m++ ) {
  4228. var myMarker = options.markers[ m ];
  4229. if( myMarker.text ){
  4230. if( !selectControl ){
  4231. selectControl = new OpenLayers.Control.SelectFeature( pointLayer );
  4232. map.addControl( selectControl );
  4233. selectControl.activate();
  4234. pointLayer.events.on( {
  4235. "featureselected": featureSelected,
  4236. "featureunselected": featureUnSelected
  4237. } );
  4238. }
  4239. }
  4240. if( myMarker.location ){
  4241. var geocodeThenPlotMarker = gcThenPlotMarker;
  4242. geocodeThenPlotMarker(myMarker);
  4243. } else {
  4244. var myPoint = new OpenLayers.Geometry.Point( myMarker.lng, myMarker.lat ).transform( displayProjection, projection ),
  4245. myPointStyle = OpenLayers.Util.extend( {}, layerStyle );
  4246. if( !myMarker.size || isNaN( myMarker.size ) ) {
  4247. myMarker.size = 14;
  4248. }
  4249. myPointStyle.pointRadius = myMarker.size;
  4250. myPointStyle.graphicOpacity = 1;
  4251. myPointStyle.externalGraphic = myMarker.icon;
  4252. var myPointFeature = new OpenLayers.Feature.Vector( myPoint, null, myPointStyle );
  4253. if( myMarker.text ) {
  4254. myPointFeature.attributes = {
  4255. text: myMarker.text
  4256. };
  4257. }
  4258. pointLayer.addFeatures( [ myPointFeature ] );
  4259. }
  4260. }
  4261. }
  4262. }
  4263. };
  4264. isReady();
  4265. },
  4266. /**
  4267. * @member openmap
  4268. * The end function will be executed when the currentTime
  4269. * of the video reaches the end time provided by the
  4270. * options variable
  4271. */
  4272. end: function( event, options ) {
  4273. // if the map exists hide it do not delete the map just in
  4274. // case the user seeks back to time b/w start and end
  4275. if ( map ) {
  4276. map.div.style.display = 'none';
  4277. }
  4278. },
  4279. _teardown: function( options ) {
  4280. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( newdiv );
  4281. newdiv = map = centerlonlat = projection = displayProjection = pointLayer = selectControl = popup = null;
  4282. }
  4283. };
  4284. },
  4285. {
  4286. about:{
  4287. name: "Popcorn OpenMap Plugin",
  4288. version: "0.3",
  4289. author: "@mapmeld",
  4290. website: "mapadelsur.blogspot.com"
  4291. },
  4292. options:{
  4293. start : { elem: 'input', type: 'text', label: 'In'},
  4294. end : { elem: 'input', type: 'text', label: 'Out'},
  4295. target : 'map-container',
  4296. type : { elem: 'select', options:[ 'ROADMAP', 'SATELLITE', 'TERRAIN' ], label: 'Type' },
  4297. zoom : { elem: 'input', type: 'text', label: 'Zoom'},
  4298. lat : { elem: 'input', type: 'text', label: 'Lat'},
  4299. lng : { elem: 'input', type: 'text', label: 'Lng'},
  4300. location : { elem: 'input', type: 'text', label: 'Location'},
  4301. markers : { elem: 'input', type: 'text', label: 'List Markers'}
  4302. }
  4303. } );
  4304. } ) ( Popcorn );
  4305. /**
  4306. * Pause Popcorn Plug-in
  4307. *
  4308. * When this plugin is used, links on the webpage, when clicked, will pause
  4309. * popcorn videos that especified 'pauseOnLinkClicked' as an option. Links may
  4310. * cause a new page to display on a new window, or may cause a new page to
  4311. * display in the current window, in which case the videos won't be available
  4312. * anymore. It only affects anchor tags. It does not affect objects with click
  4313. * events that act as anchors.
  4314. *
  4315. * Example:
  4316. var p = Popcorn('#video', { pauseOnLinkClicked : true } )
  4317. .play();
  4318. *
  4319. */
  4320. document.addEventListener( "click", function( event ) {
  4321. var targetElement = event.target;
  4322. //Some browsers use an element as the target, some use the text node inside
  4323. if ( targetElement.nodeName === "A" || targetElement.parentNode && targetElement.parentNode.nodeName === "A" ) {
  4324. Popcorn.instances.forEach( function( video ) {
  4325. if ( video.options.pauseOnLinkClicked ) {
  4326. video.pause();
  4327. }
  4328. });
  4329. }
  4330. }, false );
  4331. // PLUGIN: Wordriver
  4332. (function (Popcorn) {
  4333. var container = {},
  4334. spanLocation = 0,
  4335. setupContainer = function( target ) {
  4336. container[ target ] = document.createElement( "div" );
  4337. var t = document.getElementById( target );
  4338. t && t.appendChild( container[ target ] );
  4339. container[ target ].style.height = "100%";
  4340. container[ target ].style.position = "relative";
  4341. return container[ target ];
  4342. },
  4343. // creates an object of supported, cross platform css transitions
  4344. span = document.createElement( "span" ),
  4345. prefixes = [ "webkit", "Moz", "ms", "O", "" ],
  4346. specProp = [ "Transform", "TransitionDuration", "TransitionTimingFunction" ],
  4347. supports = {},
  4348. prop;
  4349. document.getElementsByTagName( "head" )[ 0 ].appendChild( span );
  4350. for ( var sIdx = 0, sLen = specProp.length; sIdx < sLen; sIdx++ ) {
  4351. for ( var pIdx = 0, pLen = prefixes.length; pIdx < pLen; pIdx++ ) {
  4352. prop = prefixes[ pIdx ] + specProp[ sIdx ];
  4353. if ( prop in span.style ) {
  4354. supports[ specProp[ sIdx ].toLowerCase() ] = prop;
  4355. break;
  4356. }
  4357. }
  4358. }
  4359. // Garbage collect support test span
  4360. document.getElementsByTagName( "head" )[ 0 ].appendChild( span );
  4361. /**
  4362. * Word River popcorn plug-in
  4363. * Displays a string of text, fading it in and out
  4364. * while transitioning across the height of the parent container
  4365. * for the duration of the instance (duration = end - start)
  4366. *
  4367. * @param {Object} options
  4368. *
  4369. * Example:
  4370. var p = Popcorn( '#video' )
  4371. .wordriver({
  4372. start: 5, // When to begin the Word River animation
  4373. end: 15, // When to finish the Word River animation
  4374. text: 'Hello World', // The text you want to be displayed by Word River
  4375. target: 'wordRiverDiv', // The target div to append the text to
  4376. color: "blue" // The color of the text. (can be Hex value i.e. #FFFFFF )
  4377. } )
  4378. *
  4379. */
  4380. Popcorn.plugin( "wordriver" , {
  4381. manifest: {
  4382. about:{
  4383. name: "Popcorn WordRiver Plugin"
  4384. },
  4385. options:{
  4386. start : {elem:'input', type:'text', label:'In'},
  4387. end : {elem:'input', type:'text', label:'Out'},
  4388. target : 'wordriver-container',
  4389. text : {elem:'input', type:'text', label:'Text'},
  4390. color : {elem:'input', type:'text', label:'Color'}
  4391. }
  4392. },
  4393. _setup: function( options ) {
  4394. options._duration = options.end - options.start;
  4395. options._container = container[ options.target ] || setupContainer( options.target );
  4396. options.word = document.createElement( "span" );
  4397. options.word.style.position = "absolute";
  4398. options.word.style.whiteSpace = "nowrap";
  4399. options.word.style.opacity = 0;
  4400. options.word.style.MozTransitionProperty = "opacity, -moz-transform";
  4401. options.word.style.webkitTransitionProperty = "opacity, -webkit-transform";
  4402. options.word.style.OTransitionProperty = "opacity, -o-transform";
  4403. options.word.style.transitionProperty = "opacity, transform";
  4404. options.word.style[ supports.transitionduration ] = 1 + "s, " + options._duration + "s";
  4405. options.word.style[ supports.transitiontimingfunction ] = "linear";
  4406. options.word.innerHTML = options.text;
  4407. options.word.style.color = options.color || "black";
  4408. },
  4409. start: function( event, options ){
  4410. options._container.appendChild( options.word );
  4411. // Resets the transform when changing to a new currentTime before the end event occurred.
  4412. options.word.style[ supports.transform ] = "";
  4413. options.word.style.fontSize = ~~( 30 + 20 * Math.random() ) + "px";
  4414. spanLocation = spanLocation % ( options._container.offsetWidth - options.word.offsetWidth );
  4415. options.word.style.left = spanLocation + "px";
  4416. spanLocation += options.word.offsetWidth + 10;
  4417. options.word.style[ supports.transform ] = "translateY(" +
  4418. ( document.getElementById( options.target ).offsetHeight - options.word.offsetHeight ) + "px)";
  4419. options.word.style.opacity = 1;
  4420. // automatically clears the word based on time
  4421. setTimeout( function() {
  4422. options.word.style.opacity = 0;
  4423. // ensures at least one second exists, because the fade animation is 1 second
  4424. }, ( ( (options.end - options.start) - 1 ) || 1 ) * 1000 );
  4425. },
  4426. end: function( event, options ){
  4427. // manually clears the word based on user interaction
  4428. options.word.style.opacity = 0;
  4429. },
  4430. _teardown: function( options ) {
  4431. // removes word span from generated container
  4432. options.word.parentNode && options._container.removeChild( options.word );
  4433. // if no more word spans exist in container, remove container
  4434. container[ options.target ] &&
  4435. !container[ options.target ].childElementCount &&
  4436. document.getElementById( options.target ).removeChild( container[ options.target ] ) &&
  4437. delete container[ options.target ];
  4438. }
  4439. });
  4440. })( Popcorn );
  4441. /**
  4442. * Processing Popcorn Plug-In
  4443. *
  4444. * This plugin adds a Processing.js sketch to be added to a target div or canvas.
  4445. *
  4446. * Options parameter needs to specify start, end, target and sketch attributes
  4447. * -Start is the time [in seconds] that you want the sketch to display and start looping.
  4448. * -End is the time [in seconds] you want the sketch to become hidden and stop looping.
  4449. * -Target is the id of the div or canvas you want the target sketch to be displayed in. ( a target that is a div will have a canvas created and placed inside of it. )
  4450. * -Sketch specifies the filename of the Procesing code to be loaded into Processing.js
  4451. * -noLoop [optional] specifies whether a sketch should continue to loop when the video is paused or seeking.
  4452. *
  4453. * @param {Object} options
  4454. *
  4455. * Example:
  4456. var p = Popcorn( "#video" )
  4457. .processing({
  4458. start: 5,
  4459. end: 10,
  4460. target: "processing-div",
  4461. sketch: "processingSketch.pjs",
  4462. noLoop: true
  4463. });
  4464. *
  4465. */
  4466. (function ( Popcorn ) {
  4467. var toggle = function( on, options ) {
  4468. var instance = options.pjsInstance,
  4469. canvas = options.canvas;
  4470. if ( canvas && options.isReady ) {
  4471. if ( on ) {
  4472. canvas.style.display = "inline";
  4473. !this.media.paused && instance.loop();
  4474. } else {
  4475. canvas.style.display = "none";
  4476. instance.noLoop();
  4477. }
  4478. } else {
  4479. setTimeout (function() {
  4480. toggle.call( this, on, options );
  4481. }, 10 );
  4482. }
  4483. };
  4484. Popcorn.plugin( "processing" , function ( options ) {
  4485. var init = function( context ) {
  4486. var initProcessing,
  4487. canvas;
  4488. if ( !window.Processing ) {
  4489. Popcorn.getScript( "http://processingjs.org/content/download/processing-js-1.2.1/processing-1.2.1.min.js" );
  4490. }
  4491. options.parentTarget = document.getElementById( options.target );
  4492. if ( !options.parentTarget ) {
  4493. throw ( "target container doesn't exist" );
  4494. }
  4495. initProcessing = function() {
  4496. var addListeners = function() {
  4497. context.listen( "pause", function () {
  4498. if ( options.canvas.style.display === "inline" ) {
  4499. options.pjsInstance.noLoop();
  4500. }
  4501. });
  4502. context.listen( "play", function() {
  4503. if ( options.canvas.style.display === "inline" ) {
  4504. options.pjsInstance.loop();
  4505. }
  4506. });
  4507. };
  4508. if ( options.codeReady && window.Processing ) {
  4509. options.pjsInstance = new Processing( options.canvas, options.processingCode );
  4510. options.pjsInstance.noLoop();
  4511. context.listen( "seeking", function() {
  4512. if ( options.canvas.style.display === "inline" && options.noPause ) {
  4513. options.pjsInstance.loop();
  4514. }
  4515. });
  4516. options.noPause = options.noPause || false;
  4517. !options.noPause && addListeners();
  4518. options.isReady = true;
  4519. } else {
  4520. setTimeout ( initProcessing, 10 );
  4521. }
  4522. };
  4523. canvas = document.createElement( "canvas" );
  4524. canvas.id = Popcorn.guid( options.target + "-sketch-" );
  4525. canvas[ "data-processing-sources" ] = options.sketch;
  4526. canvas.style.display = "none";
  4527. options.canvas = canvas;
  4528. options.parentTarget.appendChild( options.canvas );
  4529. Popcorn.xhr({
  4530. url: options.sketch,
  4531. dataType: "text",
  4532. success: function( responseCode ) {
  4533. options.codeReady = true;
  4534. options.processingCode = responseCode;
  4535. initProcessing();
  4536. }
  4537. });
  4538. };
  4539. return {
  4540. manifest: {
  4541. about: {
  4542. name: "Popcorn Processing Plugin",
  4543. version: "0.1",
  4544. author: "Christopher De Cairos, Benjamin Chalovich",
  4545. website: "cadecairos.blogspot.com, ben1amin.wordpress.org"
  4546. },
  4547. options: {
  4548. start : { elem: "input", type: "text", label: "In" },
  4549. end : { elem: "input", type: "text", label: "Out" },
  4550. target : { elem: "input", type: "text", label: "Target" },
  4551. sketch : { elem: "input", type: "text", label: "Sketch" },
  4552. noPause : { elem: "select", options: [ "TRUE", "FALSE" ], label: "No Loop" }
  4553. }
  4554. },
  4555. _setup: function( options ) {
  4556. options.codeReady = false;
  4557. init( this );
  4558. },
  4559. start: function( event, options ) {
  4560. toggle.call( this, true, options );
  4561. },
  4562. end: function( event, options ) {
  4563. toggle.call( this, false, options );
  4564. },
  4565. _teardown: function( options ) {
  4566. options.pjsInstance && options.pjsInstance.exit();
  4567. options.parentTarget && options.parentTarget.removeChild( options.canvas );
  4568. }
  4569. };
  4570. });
  4571. }( Popcorn ));
  4572. // PLUGIN: Timeline
  4573. (function ( Popcorn ) {
  4574. /**
  4575. * timeline popcorn plug-in
  4576. * Adds data associated with a certain time in the video, which creates a scrolling view of each item as the video progresses
  4577. * Options parameter will need a start, target, title, and text
  4578. * -Start is the time that you want this plug-in to execute
  4579. * -End is the time that you want this plug-in to stop executing, tho for this plugin an end time may not be needed ( optional )
  4580. * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
  4581. * -Title is the title of the current timeline box
  4582. * -Text is text is simply related text that will be displayed
  4583. * -innerHTML gives the user the option to add things such as links, buttons and so on
  4584. * -direction specifies whether the timeline will grow from the top or the bottom, receives input as "UP" or "DOWN"
  4585. * @param {Object} options
  4586. *
  4587. * Example:
  4588. var p = Popcorn("#video")
  4589. .timeline( {
  4590. start: 5, // seconds
  4591. target: "timeline",
  4592. title: "Seneca",
  4593. text: "Welcome to seneca",
  4594. innerHTML: "Click this link <a href='http://www.google.ca'>Google</a>"
  4595. } )
  4596. *
  4597. */
  4598. var i = 1,
  4599. head = document.getElementsByTagName( "head" )[ 0 ],
  4600. css = document.createElement( "link" );
  4601. css.type = "text/css";
  4602. css.rel = "stylesheet";
  4603. css.href = "//popcornjs.org/code/plugins/timeline/popcorn.timeline.css";
  4604. head.insertBefore( css, head.firstChild );
  4605. Popcorn.plugin( "timeline" , function( options ) {
  4606. var target = document.getElementById( options.target ),
  4607. newdiv = document.createElement( "div" );
  4608. target.style.width = "400px";
  4609. target.style.height = "200px";
  4610. target.style.overflow = "auto";
  4611. newdiv.style.display = "none";
  4612. newdiv.id = "timelineDiv" + i;
  4613. // Default to up if options.direction is non-existant or not up or down
  4614. options.direction = options.direction || "up";
  4615. if ( options.direction.toLowerCase() !== "up" || options.direction.toLowerCase() !== "down" ) {
  4616. options.direction = "up";
  4617. }
  4618. if ( target ) {
  4619. target.appendChild( newdiv );
  4620. // if this isnt the first div added to the target div
  4621. if( options.direction.length === 2 ){
  4622. // insert the current div before the previous div inserted
  4623. target.insertBefore( newdiv, document.getElementById( "timelineDiv" + ( i - 1 ) ) );
  4624. }
  4625. }
  4626. i++;
  4627. // Default to empty if not used
  4628. //options.innerHTML = options.innerHTML || "";
  4629. newdiv.innerHTML = "<p><span id='big'>" + options.title + "</span><br />" +
  4630. "<span id='mid'>" + options.text + "</span><br />" + options.innerHTML;
  4631. return {
  4632. start: function( event, options ) {
  4633. newdiv.style.display = "block";
  4634. if( options.direction === "down" ) {
  4635. target.scrollTop = target.scrollHeight;
  4636. }
  4637. },
  4638. end: function( event, options ) {
  4639. newdiv.style.display = "none";
  4640. },
  4641. _teardown: function( options ) {
  4642. while ( target.firstChild ) {
  4643. target.removeChild( target.firstChild );
  4644. }
  4645. }
  4646. };
  4647. },
  4648. {
  4649. about: {
  4650. name: "Popcorn Timeline Plugin",
  4651. version: "0.1",
  4652. author: "David Seifried @dcseifried",
  4653. website: "dseifried.wordpress.com"
  4654. },
  4655. options: {
  4656. start: {
  4657. elem: "input",
  4658. type: "text",
  4659. label: "In"
  4660. },
  4661. end: {
  4662. elem: "input",
  4663. type: "text",
  4664. label: "Out"
  4665. },
  4666. target: "feed-container",
  4667. title: {
  4668. elem: "input",
  4669. type: "text",
  4670. label: "title"
  4671. },
  4672. text: {
  4673. elem: "input",
  4674. type: "text",
  4675. label: "text"
  4676. },
  4677. innerHTML: {
  4678. elem: "input",
  4679. type: "text",
  4680. label: "innerHTML"
  4681. },
  4682. direction: {
  4683. elem: "input",
  4684. type: "text",
  4685. label: "direction"
  4686. }
  4687. }
  4688. });
  4689. })( Popcorn );
  4690. // PARSER: 0.3 JSON
  4691. (function (Popcorn) {
  4692. Popcorn.parser( "parseJSON", "JSON", function( data ) {
  4693. // declare needed variables
  4694. var retObj = {
  4695. title: "",
  4696. remote: "",
  4697. data: []
  4698. },
  4699. manifestData = {},
  4700. dataObj = data;
  4701. /*
  4702. TODO: add support for filling in source children of the video element
  4703. remote: [
  4704. {
  4705. src: "whatever.mp4",
  4706. type: 'video/mp4; codecs="avc1, mp4a"'
  4707. },
  4708. {
  4709. src: "whatever.ogv",
  4710. type: 'video/ogg; codecs="theora, vorbis"'
  4711. }
  4712. ]
  4713. */
  4714. Popcorn.forEach( dataObj.data, function ( obj, key ) {
  4715. retObj.data.push( obj );
  4716. });
  4717. return retObj;
  4718. });
  4719. })( Popcorn );
  4720. // PARSER: 0.1 SBV
  4721. (function (Popcorn) {
  4722. /**
  4723. * SBV popcorn parser plug-in
  4724. * Parses subtitle files in the SBV format.
  4725. * Times are expected in H:MM:SS.MIL format, with hours optional
  4726. * Subtitles which don't match expected format are ignored
  4727. * Data parameter is given by Popcorn, will need a text.
  4728. * Text is the file contents to be parsed
  4729. *
  4730. * @param {Object} data
  4731. *
  4732. * Example:
  4733. 0:00:02.400,0:00:07.200
  4734. Senator, we're making our final approach into Coruscant.
  4735. */
  4736. Popcorn.parser( "parseSBV", function( data ) {
  4737. // declare needed variables
  4738. var retObj = {
  4739. title: "",
  4740. remote: "",
  4741. data: []
  4742. },
  4743. subs = [],
  4744. lines,
  4745. i = 0,
  4746. len = 0,
  4747. idx = 0;
  4748. // [H:]MM:SS.MIL string to SS.MIL
  4749. // Will thrown exception on bad time format
  4750. var toSeconds = function( t_in ) {
  4751. var t = t_in.split( ":" ),
  4752. l = t.length-1,
  4753. time;
  4754. try {
  4755. time = parseInt( t[l-1], 10 )*60 + parseFloat( t[l], 10 );
  4756. // Hours optionally given
  4757. if ( l === 2 ) {
  4758. time += parseInt( t[0], 10 )*3600;
  4759. }
  4760. } catch ( e ) {
  4761. throw "Bad cue";
  4762. }
  4763. return time;
  4764. };
  4765. var createTrack = function( name, attributes ) {
  4766. var track = {};
  4767. track[name] = attributes;
  4768. return track;
  4769. };
  4770. // Here is where the magic happens
  4771. // Split on line breaks
  4772. lines = data.text.split( /(?:\r\n|\r|\n)/gm );
  4773. len = lines.length;
  4774. while ( i < len ) {
  4775. var sub = {},
  4776. text = [],
  4777. time = lines[i++].split( "," );
  4778. try {
  4779. sub.start = toSeconds( time[0] );
  4780. sub.end = toSeconds( time[1] );
  4781. // Gather all lines of text
  4782. while ( i < len && lines[i] ) {
  4783. text.push( lines[i++] );
  4784. }
  4785. // Join line breaks in text
  4786. sub.text = text.join( "<br />" );
  4787. subs.push( createTrack( "subtitle", sub ) );
  4788. } catch ( e ) {
  4789. // Bad cue, advance to end of cue
  4790. while ( i < len && lines[i] ) {
  4791. i++;
  4792. }
  4793. }
  4794. // Consume empty whitespace
  4795. while ( i < len && !lines[i] ) {
  4796. i++;
  4797. }
  4798. }
  4799. retObj.data = subs;
  4800. return retObj;
  4801. });
  4802. })( Popcorn );
  4803. // PARSER: 0.3 SRT
  4804. (function (Popcorn) {
  4805. /**
  4806. * SRT popcorn parser plug-in
  4807. * Parses subtitle files in the SRT format.
  4808. * Times are expected in HH:MM:SS,MIL format, though HH:MM:SS.MIL also supported
  4809. * Ignore styling, which may occur after the end time or in-text
  4810. * While not part of the "official" spec, majority of players support HTML and SSA styling tags
  4811. * SSA-style tags are stripped, HTML style tags are left for the browser to handle:
  4812. * HTML: <font>, <b>, <i>, <u>, <s>
  4813. * SSA: \N or \n, {\cmdArg1}, {\cmd(arg1, arg2, ...)}
  4814. * Data parameter is given by Popcorn, will need a text.
  4815. * Text is the file contents to be parsed
  4816. *
  4817. * @param {Object} data
  4818. *
  4819. * Example:
  4820. 1
  4821. 00:00:25,712 --> 00:00:30.399
  4822. This text is <font color="red">RED</font> and has not been {\pos(142,120)} positioned.
  4823. This takes \Nup three \nentire lines.
  4824. This contains nested <b>bold, <i>italic, <u>underline</u> and <s>strike-through</s></u></i></b> HTML tags
  4825. Unclosed but <b>supported tags are left in
  4826. <ggg>Unsupported</ggg> HTML tags are left in, even if <hhh>not closed.
  4827. SSA tags with {\i1} would open and close italicize {\i0}, but are stripped
  4828. Multiple {\pos(142,120)\b1}SSA tags are stripped
  4829. */
  4830. Popcorn.parser( "parseSRT", function( data ) {
  4831. // declare needed variables
  4832. var retObj = {
  4833. title: "",
  4834. remote: "",
  4835. data: []
  4836. },
  4837. subs = [],
  4838. i = 0,
  4839. len = 0,
  4840. idx = 0,
  4841. lines,
  4842. time,
  4843. text,
  4844. sub;
  4845. // Simple function to convert HH:MM:SS,MMM or HH:MM:SS.MMM to SS.MMM
  4846. // Assume valid, returns 0 on error
  4847. var toSeconds = function( t_in ) {
  4848. var t = t_in.split( ':' );
  4849. try {
  4850. var s = t[2].split( ',' );
  4851. // Just in case a . is decimal seperator
  4852. if ( s.length === 1 ) {
  4853. s = t[2].split( '.' );
  4854. }
  4855. return parseFloat( t[0], 10 )*3600 + parseFloat( t[1], 10 )*60 + parseFloat( s[0], 10 ) + parseFloat( s[1], 10 )/1000;
  4856. } catch ( e ) {
  4857. return 0;
  4858. }
  4859. };
  4860. var createTrack = function( name, attributes ) {
  4861. var track = {};
  4862. track[name] = attributes;
  4863. return track;
  4864. };
  4865. // Here is where the magic happens
  4866. // Split on line breaks
  4867. lines = data.text.split( /(?:\r\n|\r|\n)/gm );
  4868. len = lines.length;
  4869. for( i=0; i < len; i++ ) {
  4870. sub = {};
  4871. text = [];
  4872. sub.id = parseInt( lines[i++], 10 );
  4873. // Split on '-->' delimiter, trimming spaces as well
  4874. time = lines[i++].split( /[\t ]*-->[\t ]*/ );
  4875. sub.start = toSeconds( time[0] );
  4876. // So as to trim positioning information from end
  4877. idx = time[1].indexOf( " " );
  4878. if ( idx !== -1) {
  4879. time[1] = time[1].substr( 0, idx );
  4880. }
  4881. sub.end = toSeconds( time[1] );
  4882. // Build single line of text from multi-line subtitle in file
  4883. while ( i < len && lines[i] ) {
  4884. text.push( lines[i++] );
  4885. }
  4886. // Join into 1 line, SSA-style linebreaks
  4887. // Strip out other SSA-style tags
  4888. sub.text = text.join( "\\N" ).replace( /\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi, "" );
  4889. // Escape HTML entities
  4890. sub.text = sub.text.replace( /</g, "&lt;" ).replace( />/g, "&gt;" );
  4891. // Unescape great than and less than when it makes a valid html tag of a supported style (font, b, u, s, i)
  4892. // Modified version of regex from Phil Haack's blog: http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx
  4893. // Later modified by kev: http://kevin.deldycke.com/2007/03/ultimate-regular-expression-for-html-tag-parsing-with-php/
  4894. sub.text = sub.text.replace( /&lt;(\/?(font|b|u|i|s))((\s+(\w|\w[\w\-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)(\/?)&gt;/gi, "<$1$3$7>" );
  4895. sub.text = sub.text.replace( /\\N/gi, "<br />" );
  4896. subs.push( createTrack( "subtitle", sub ) );
  4897. }
  4898. retObj.data = subs;
  4899. return retObj;
  4900. });
  4901. })( Popcorn );
  4902. // PARSER: 0.3 SSA/ASS
  4903. (function (Popcorn) {
  4904. /**
  4905. * SSA/ASS popcorn parser plug-in
  4906. * Parses subtitle files in the identical SSA and ASS formats.
  4907. * Style information is ignored, and may be found in these
  4908. * formats: (\N \n {\pos(400,570)} {\kf89})
  4909. * Out of the [Script Info], [V4 Styles], [Events], [Pictures],
  4910. * and [Fonts] sections, only [Events] is processed.
  4911. * Data parameter is given by Popcorn, will need a text.
  4912. * Text is the file contents to be parsed
  4913. *
  4914. * @param {Object} data
  4915. *
  4916. * Example:
  4917. [Script Info]
  4918. Title: Testing subtitles for the SSA Format
  4919. [V4 Styles]
  4920. Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
  4921. Style: Default,Arial,20,65535,65535,65535,-2147483640,-1,0,1,3,0,2,30,30,30,0,0
  4922. [Events]
  4923. Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
  4924. Dialogue: 0,0:00:02.40,0:00:07.20,Default,,0000,0000,0000,,Senator, {\kf89}we're \Nmaking our final \napproach into Coruscant.
  4925. Dialogue: 0,0:00:09.71,0:00:13.39,Default,,0000,0000,0000,,{\pos(400,570)}Very good, Lieutenant.
  4926. Dialogue: 0,0:00:15.04,0:00:18.04,Default,,0000,0000,0000,,It's \Na \ntrap!
  4927. *
  4928. */
  4929. // Register for SSA extensions
  4930. Popcorn.parser( "parseSSA", function( data ) {
  4931. // declare needed variables
  4932. var retObj = {
  4933. title: "",
  4934. remote: "",
  4935. data: []
  4936. },
  4937. subs = [],
  4938. startIdx,
  4939. endIdx,
  4940. textIdx,
  4941. lines,
  4942. fields,
  4943. numFields,
  4944. sub,
  4945. text,
  4946. i = 0,
  4947. j = 0,
  4948. len = 0,
  4949. fieldLen = 0;
  4950. // h:mm:ss.cc (centisec) string to SS.mmm
  4951. // Returns -1 if invalid
  4952. var toSeconds = function( t_in ) {
  4953. var t = t_in.split( ":" ),
  4954. l = t.length - 1;
  4955. // Not all there
  4956. if ( t_in.length !== 10 ) {
  4957. return -1;
  4958. }
  4959. return parseInt( t[0], 10 )*3600 + parseInt( t[l-1], 10 )*60 + parseFloat( t[l], 10 );
  4960. };
  4961. var createTrack = function( name, attributes ) {
  4962. var track = {};
  4963. track[name] = attributes;
  4964. return track;
  4965. };
  4966. // Here is where the magic happens
  4967. // Split on line breaks
  4968. lines = data.text.split( /(?:\r\n|\r|\n)/gm );
  4969. len = lines.length;
  4970. // Ignore non-textual info
  4971. while ( i < len && lines[i] !== "[Events]" ) {
  4972. i++;
  4973. }
  4974. fields = lines[++i].substr( 8 ).split( ", " ); // Trim 'Format: ' off front, split on delim
  4975. numFields = fields.length;
  4976. //Find where in Dialogue string the start, end and text info is
  4977. for ( ; j < numFields; j++ ) {
  4978. if ( fields[j] === "Start" ) {
  4979. startIdx = j;
  4980. } else if ( fields[j] === "End" ) {
  4981. endIdx = j;
  4982. } else if ( fields[j] === "Text" ) {
  4983. textIdx = j;
  4984. }
  4985. }
  4986. while ( ++i < len && lines[i] && lines[i][0] !== "[" ) {
  4987. sub = {};
  4988. // Trim beginning 'Dialogue: ' and split on delim
  4989. fields = lines[i].substr( 10 ).split( "," );
  4990. sub.start = toSeconds( fields[startIdx] );
  4991. sub.end = toSeconds( fields[endIdx] );
  4992. // Invalid time, skip
  4993. if ( sub.start === -1 || sub.end === -1 ) {
  4994. continue;
  4995. }
  4996. if ( ( fieldLen = fields.length ) === numFields ) {
  4997. sub.text = fields[textIdx];
  4998. } else {
  4999. // There were commas in the text which were split, append back together into one line
  5000. text = [];
  5001. for( j = textIdx; j < fieldLen; j++ ) {
  5002. text.push( fields[j] );
  5003. }
  5004. sub.text = text.join( "," );
  5005. }
  5006. // Eliminate advanced styles and convert forced line breaks
  5007. sub.text = sub.text.replace( /\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi, "" ).replace( /\\N/gi, "<br />" );
  5008. subs.push( createTrack( "subtitle", sub ) );
  5009. }
  5010. retObj.data = subs;
  5011. return retObj;
  5012. });
  5013. })( Popcorn );
  5014. // PARSER: 0.3 TTML
  5015. (function (Popcorn) {
  5016. /**
  5017. * TTML popcorn parser plug-in
  5018. * Parses subtitle files in the TTML format.
  5019. * Times may be absolute to the timeline or relative
  5020. * Absolute times are ISO 8601 format (hh:mm:ss[.mmm])
  5021. * Relative times are a fraction followed by a unit metric (d.ddu)
  5022. * Relative times are relative to the time given on the parent node
  5023. * Styling information is ignored
  5024. * Data parameter is given by Popcorn, will need an xml.
  5025. * Xml is the file contents to be processed
  5026. *
  5027. * @param {Object} data
  5028. *
  5029. * Example:
  5030. <tt xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling" xmlns="http://www.w3.org/2006/04/ttaf1">
  5031. <body region="subtitleArea">
  5032. <div>
  5033. <p xml:id="subtitle1" begin="0.76s" end="3.45s">
  5034. It seems a paradox, does it not,
  5035. </p>
  5036. </div>
  5037. </body>
  5038. </tt>
  5039. */
  5040. Popcorn.parser( "parseTTML", function( data ) {
  5041. // declare needed variables
  5042. var returnData = {
  5043. title: "",
  5044. remote: "",
  5045. data: []
  5046. },
  5047. node,
  5048. numTracks = 0,
  5049. region;
  5050. // Convert time expression to SS.mmm
  5051. // Expression may be absolute to timeline (hh:mm:ss.ms)
  5052. // or relative ( fraction followedd by metric ) ex: 3.4s, 5.7m
  5053. // Returns -1 if invalid
  5054. var toSeconds = function ( t_in, offset ) {
  5055. if ( !t_in ) {
  5056. return -1;
  5057. }
  5058. var t = t_in.split( ":" ),
  5059. l = t.length - 1,
  5060. metric,
  5061. multiplier,
  5062. i;
  5063. // Try clock time
  5064. if ( l >= 2 ) {
  5065. return parseInt( t[0], 10 )*3600 + parseInt( t[l-1], 10 )*60 + parseFloat( t[l], 10 );
  5066. }
  5067. // Was not clock time, assume relative time
  5068. // Take metric from end of string (may not be single character)
  5069. // First find metric
  5070. for( i = t_in.length - 1; i >= 0; i-- ) {
  5071. if ( t_in[i] <= "9" && t_in[i] >= "0" ) {
  5072. break;
  5073. }
  5074. }
  5075. // Point i at metric and normalize offsete time
  5076. i++;
  5077. metric = t_in.substr( i );
  5078. offset = offset || 0;
  5079. // Determine multiplier for metric relative to seconds
  5080. if ( metric === "h" ) {
  5081. multiplier = 3600;
  5082. } else if ( metric === "m" ) {
  5083. multiplier = 60;
  5084. } else if ( metric === "s" ) {
  5085. multiplier = 1;
  5086. } else if ( metric === "ms" ) {
  5087. multiplier = 0.001;
  5088. } else {
  5089. return -1;
  5090. }
  5091. // Valid multiplier
  5092. return parseFloat( t_in.substr( 0, i ) ) * multiplier + offset;
  5093. };
  5094. // creates an object of all atrributes keyd by name
  5095. var createTrack = function( name, attributes ) {
  5096. var track = {};
  5097. track[name] = attributes;
  5098. return track;
  5099. };
  5100. // Parse a node for text content
  5101. var parseNode = function( node, timeOffset ) {
  5102. var sub = {};
  5103. // Trim left and right whitespace from text and change non-explicit line breaks to spaces
  5104. sub.text = node.textContent.replace(/^[\s]+|[\s]+$/gm, "").replace(/(?:\r\n|\r|\n)/gm, "<br />");
  5105. sub.id = node.getAttribute( "xml:id" );
  5106. sub.start = toSeconds ( node.getAttribute( "begin" ), timeOffset );
  5107. sub.end = toSeconds( node.getAttribute( "end" ), timeOffset );
  5108. sub.target = region;
  5109. if ( sub.end < 0 ) {
  5110. // No end given, infer duration if possible
  5111. // Otherwise, give end as MAX_VALUE
  5112. sub.end = toSeconds( node.getAttribute( "duration" ), 0 );
  5113. if ( sub.end >= 0 ) {
  5114. sub.end += sub.start;
  5115. } else {
  5116. sub.end = Number.MAX_VALUE;
  5117. }
  5118. }
  5119. return sub;
  5120. };
  5121. // Parse the children of the given node
  5122. var parseChildren = function( node, timeOffset ) {
  5123. var currNode = node.firstChild,
  5124. sub,
  5125. newOffset;
  5126. while ( currNode ) {
  5127. if ( currNode.nodeType === 1 ) {
  5128. if ( currNode.nodeName === "p" ) {
  5129. // p is a teextual node, process contents as subtitle
  5130. sub = parseNode( currNode, timeOffset );
  5131. returnData.data.push( createTrack( "subtitle", sub ) );
  5132. numTracks++;
  5133. } else if ( currNode.nodeName === "div" ) {
  5134. // div is container for subtitles, recurse
  5135. newOffset = toSeconds( currNode.getAttribute("begin") );
  5136. if (newOffset < 0 ) {
  5137. newOffset = timeOffset;
  5138. }
  5139. parseChildren( currNode, newOffset );
  5140. }
  5141. }
  5142. currNode = currNode.nextSibling;
  5143. }
  5144. };
  5145. // Null checks
  5146. if ( !data.xml || !data.xml.documentElement || !( node = data.xml.documentElement.firstChild ) ) {
  5147. return returnData;
  5148. }
  5149. // Find body tag
  5150. while ( node.nodeName !== "body" ) {
  5151. node = node.nextSibling;
  5152. }
  5153. region = "";
  5154. parseChildren( node, 0 );
  5155. return returnData;
  5156. });
  5157. })( Popcorn );
  5158. // PARSER: 0.1 TTXT
  5159. (function (Popcorn) {
  5160. /**
  5161. * TTXT popcorn parser plug-in
  5162. * Parses subtitle files in the TTXT format.
  5163. * Style information is ignored.
  5164. * Data parameter is given by Popcorn, will need an xml.
  5165. * Xml is the file contents to be parsed as a DOM tree
  5166. *
  5167. * @param {Object} data
  5168. *
  5169. * Example:
  5170. <TextSample sampleTime="00:00:00.000" text=""></TextSample>
  5171. */
  5172. Popcorn.parser( "parseTTXT", function( data ) {
  5173. // declare needed variables
  5174. var returnData = {
  5175. title: "",
  5176. remote: "",
  5177. data: []
  5178. };
  5179. // Simple function to convert HH:MM:SS.MMM to SS.MMM
  5180. // Assume valid, returns 0 on error
  5181. var toSeconds = function(t_in) {
  5182. var t = t_in.split(":");
  5183. var time = 0;
  5184. try {
  5185. return parseFloat(t[0], 10)*60*60 + parseFloat(t[1], 10)*60 + parseFloat(t[2], 10);
  5186. } catch (e) { time = 0; }
  5187. return time;
  5188. };
  5189. // creates an object of all atrributes keyed by name
  5190. var createTrack = function( name, attributes ) {
  5191. var track = {};
  5192. track[name] = attributes;
  5193. return track;
  5194. };
  5195. // this is where things actually start
  5196. var node = data.xml.lastChild.lastChild; // Last Child of TextStreamHeader
  5197. var lastStart = Number.MAX_VALUE;
  5198. var cmds = [];
  5199. // Work backwards through DOM, processing TextSample nodes
  5200. while (node) {
  5201. if ( node.nodeType === 1 && node.nodeName === "TextSample") {
  5202. var sub = {};
  5203. sub.start = toSeconds(node.getAttribute('sampleTime'));
  5204. sub.text = node.getAttribute('text');
  5205. if (sub.text) { // Only process if text to display
  5206. // Infer end time from prior element, ms accuracy
  5207. sub.end = lastStart - 0.001;
  5208. cmds.push( createTrack("subtitle", sub) );
  5209. }
  5210. lastStart = sub.start;
  5211. }
  5212. node = node.previousSibling;
  5213. }
  5214. returnData.data = cmds.reverse();
  5215. return returnData;
  5216. });
  5217. })( Popcorn );
  5218. // PARSER: 0.3 WebSRT/VTT
  5219. (function ( Popcorn ) {
  5220. /**
  5221. * WebSRT/VTT popcorn parser plug-in
  5222. * Parses subtitle files in the WebSRT/VTT format.
  5223. * Styles which appear after timing information are ignored.
  5224. * Inline styling tags follow HTML conventions and are left in for the browser to handle
  5225. * TrackEvents (cues) which are malformed are ignored.
  5226. * Data parameter is given by Popcorn, will need a text.
  5227. * Text is the file contents to be parsed
  5228. *
  5229. * @param {Object} data
  5230. *
  5231. * Example:
  5232. Track-3
  5233. 00:00:15.542 --> 00:00:18.542 A:start D:vertical L:98%
  5234. It's a <i>trap!</i>
  5235. */
  5236. Popcorn.parser( "parseVTT", function( data ) {
  5237. // declare needed variables
  5238. var retObj = {
  5239. title: "",
  5240. remote: "",
  5241. data: []
  5242. },
  5243. subs = [],
  5244. i = 0,
  5245. len = 0,
  5246. idx = 0,
  5247. lines,
  5248. time,
  5249. text,
  5250. sub;
  5251. // [HH:]MM:SS.mmm string to SS.mmm float
  5252. // Throws exception if invalid
  5253. var toSeconds = function( t_in ) {
  5254. var t = t_in.split( ":" ),
  5255. l = t_in.length,
  5256. time;
  5257. // Invalid time string provided
  5258. if ( l !== 12 && l !== 9 ) {
  5259. throw "Bad cue";
  5260. }
  5261. l = t.length - 1;
  5262. try {
  5263. time = parseInt( t[l-1], 10 )*60 + parseFloat( t[l], 10 );
  5264. // Hours were given
  5265. if ( l === 2 ) {
  5266. time += parseInt( t[0], 10 )*3600;
  5267. }
  5268. } catch ( e ) {
  5269. throw "Bad cue";
  5270. }
  5271. return time;
  5272. };
  5273. var createTrack = function( name, attributes ) {
  5274. var track = {};
  5275. track[name] = attributes;
  5276. return track;
  5277. };
  5278. // Here is where the magic happens
  5279. // Split on line breaks
  5280. lines = data.text.split( /(?:\r\n|\r|\n)/gm );
  5281. len = lines.length;
  5282. while ( i < len ) {
  5283. sub = {};
  5284. text = [];
  5285. try {
  5286. sub.id = lines[i++];
  5287. // Ignore if id contains "-->"
  5288. if ( !sub.id || sub.id.indexOf( "-->" ) !== -1 ) {
  5289. throw "Bad cue";
  5290. }
  5291. time = lines[i++].split( /[\t ]*-->[\t ]*/ );
  5292. sub.start = toSeconds(time[0]);
  5293. // Filter out any trailing styling info
  5294. idx = time[1].indexOf( " " );
  5295. if ( idx !== -1 ) {
  5296. time[1] = time[1].substr( 0, idx );
  5297. }
  5298. sub.end = toSeconds( time[1] );
  5299. // Build single line of text from multi-line subtitle in file
  5300. while ( i < len && lines[i] ) {
  5301. text.push( lines[i++] );
  5302. }
  5303. // Join lines together to one and build subtitle
  5304. sub.text = text.join( "<br />" );
  5305. subs.push( createTrack( "subtitle", sub ) );
  5306. } catch ( e ) {
  5307. // Bad cue, advance to end of cue
  5308. while ( i < len && lines[i] ) {
  5309. i++;
  5310. }
  5311. }
  5312. // Consume empty whitespace after a cue
  5313. while ( i < len && !lines[i] ) {
  5314. i++;
  5315. }
  5316. }
  5317. retObj.data = subs;
  5318. return retObj;
  5319. });
  5320. })( Popcorn );
  5321. // PARSER: 0.1 XML
  5322. (function (Popcorn) {
  5323. /**
  5324. *
  5325. *
  5326. */
  5327. Popcorn.parser( "parseXML", "XML", function( data ) {
  5328. // declare needed variables
  5329. var returnData = {
  5330. title: "",
  5331. remote: "",
  5332. data: []
  5333. },
  5334. manifestData = {};
  5335. // Simple function to convert 0:05 to 0.5 in seconds
  5336. // acceptable formats are HH:MM:SS:MM, MM:SS:MM, SS:MM, SS
  5337. var toSeconds = function(time) {
  5338. var t = time.split(":");
  5339. if (t.length === 1) {
  5340. return parseFloat(t[0], 10);
  5341. } else if (t.length === 2) {
  5342. return parseFloat(t[0], 10) + parseFloat(t[1] / 12, 10);
  5343. } else if (t.length === 3) {
  5344. return parseInt(t[0] * 60, 10) + parseFloat(t[1], 10) + parseFloat(t[2] / 12, 10);
  5345. } else if (t.length === 4) {
  5346. return parseInt(t[0] * 3600, 10) + parseInt(t[1] * 60, 10) + parseFloat(t[2], 10) + parseFloat(t[3] / 12, 10);
  5347. }
  5348. };
  5349. // turns a node tree element into a straight up javascript object
  5350. // also converts in and out to start and end
  5351. // also links manifest data with ids
  5352. var objectifyAttributes = function ( nodeAttributes ) {
  5353. var returnObject = {};
  5354. for ( var i = 0, nal = nodeAttributes.length; i < nal; i++ ) {
  5355. var key = nodeAttributes.item(i).nodeName,
  5356. data = nodeAttributes.item(i).nodeValue;
  5357. // converts in into start
  5358. if (key === "in") {
  5359. returnObject.start = toSeconds( data );
  5360. // converts out into end
  5361. } else if ( key === "out" ){
  5362. returnObject.end = toSeconds( data );
  5363. // this is where ids in the manifest are linked
  5364. } else if ( key === "resourceid" ) {
  5365. Popcorn.extend( returnObject, manifestData[data] );
  5366. // everything else
  5367. } else {
  5368. returnObject[key] = data;
  5369. }
  5370. }
  5371. return returnObject;
  5372. };
  5373. // creates an object of all atrributes keyd by name
  5374. var createTrack = function( name, attributes ) {
  5375. var track = {};
  5376. track[name] = attributes;
  5377. return track;
  5378. };
  5379. // recursive function to process a node, or process the next child node
  5380. var parseNode = function ( node, allAttributes, manifest ) {
  5381. var attributes = {};
  5382. Popcorn.extend( attributes, allAttributes, objectifyAttributes( node.attributes ), { text: node.textContent } );
  5383. var childNodes = node.childNodes;
  5384. // processes the node
  5385. if ( childNodes.length < 1 || ( childNodes.length === 1 && childNodes[0].nodeType === 3 ) ) {
  5386. if ( !manifest ) {
  5387. returnData.data.push( createTrack( node.nodeName, attributes ) );
  5388. } else {
  5389. manifestData[attributes.id] = attributes;
  5390. }
  5391. // process the next child node
  5392. } else {
  5393. for ( var i = 0; i < childNodes.length; i++ ) {
  5394. if ( childNodes[i].nodeType === 1 ) {
  5395. parseNode( childNodes[i], attributes, manifest );
  5396. }
  5397. }
  5398. }
  5399. };
  5400. // this is where things actually start
  5401. var x = data.documentElement.childNodes;
  5402. for ( var i = 0, xl = x.length; i < xl; i++ ) {
  5403. if ( x[i].nodeType === 1 ) {
  5404. // start the process of each main node type, manifest or timeline
  5405. if ( x[i].nodeName === "manifest" ) {
  5406. parseNode( x[i], {}, true );
  5407. } else { // timeline
  5408. parseNode( x[i], {}, false );
  5409. }
  5410. }
  5411. }
  5412. return returnData;
  5413. });
  5414. })( Popcorn );
  5415. (function( global, doc ) {
  5416. Popcorn.baseplayer = function() {
  5417. return new Popcorn.baseplayer.init();
  5418. };
  5419. Popcorn.baseplayer.init = function() {
  5420. this.readyState = 0;
  5421. this.currentTime = 0;
  5422. this.baselineTime = new Date();
  5423. this.duration = 0;
  5424. this.paused = 1;
  5425. this.ended = 0;
  5426. this.volume = 1;
  5427. this.muted = 0;
  5428. this.playbackRate = 1;
  5429. // These are considered to be "on" by being defined. Initialize to undefined
  5430. this.autoplay;
  5431. this.loop;
  5432. // List of events
  5433. this._events = {};
  5434. // The underlying player resource. May be <canvas>, <iframe>, <object>, array, etc
  5435. this._resource;
  5436. // The container div of the resource
  5437. this._container;
  5438. this.offsetWidth = this.width = 0;
  5439. this.offsetHeight = this.height = 0;
  5440. this.offsetLeft = 0;
  5441. this.offsetTop = 0;
  5442. this.offsetParent;
  5443. };
  5444. Popcorn.baseplayer.init.prototype = {
  5445. load: function() {},
  5446. play: function() {
  5447. this.paused = 0;
  5448. this.timeupdate();
  5449. },
  5450. pause: function() {
  5451. this.paused = 1;
  5452. },
  5453. timeupdate: function() {
  5454. // So we can refer to the instance when setTimeout is run
  5455. var self = this;
  5456. if( !this.paused ) {
  5457. this.currentTime += ( new Date() - this.baselineTime ) / 1000;
  5458. this.dispatchEvent( "timeupdate" );
  5459. }
  5460. this.baselineTime = new Date();
  5461. setTimeout(function() {
  5462. self.timeupdate.call( self );
  5463. }, 50 );
  5464. },
  5465. // By default, assumes this.resource is a DOM Element
  5466. // Changing the type of this.resource requires this method to be overridden
  5467. getBoundingClientRect: function() {
  5468. return Popcorn.position( this._resource || this._container );
  5469. },
  5470. // Add an event listener to the object
  5471. addEventListener: function( evtName, fn ) {
  5472. if ( !this._events[ evtName ] ) {
  5473. this._events[ evtName ] = [];
  5474. }
  5475. this._events[ evtName ].push( fn );
  5476. return fn;
  5477. },
  5478. // Can take event object or simple string
  5479. dispatchEvent: function( oEvent ) {
  5480. var evt,
  5481. self = this,
  5482. eventInterface,
  5483. eventName = oEvent.type;
  5484. // A string was passed, create event object
  5485. if ( !eventName ) {
  5486. eventName = oEvent;
  5487. eventInterface = Popcorn.events.getInterface( eventName );
  5488. if ( eventInterface ) {
  5489. evt = document.createEvent( eventInterface );
  5490. evt.initEvent( eventName, true, true, window, 1 );
  5491. }
  5492. }
  5493. Popcorn.forEach( this._events[ eventName ], function( val ) {
  5494. val.call( self, evt, self );
  5495. });
  5496. },
  5497. // Extracts values from container onto this object
  5498. extractContainerValues: function( id ) {
  5499. this._container = document.getElementById( id );
  5500. if ( !this._container ) {
  5501. return;
  5502. }
  5503. var bounds = this._container.getBoundingClientRect();
  5504. this.offsetWidth = this.width = this.getStyle( "width" ) || 0;
  5505. this.offsetHeight = this.height = this.getStyle( "height" ) || 0;
  5506. this.offsetLeft = bounds.left;
  5507. this.offsetTop = bounds.top;
  5508. this.offsetParent = this._container.offsetParent;
  5509. return this._container;
  5510. },
  5511. // By default, assumes this.resource is a DOM Element
  5512. // Changing the type of this.resource requires this method to be overridden
  5513. // Returns the computed value for CSS style 'prop' as computed by the browser
  5514. getStyle: function( prop ) {
  5515. var elem = this._resource || this._container;
  5516. if ( elem.currentStyle ) {
  5517. // IE syntax
  5518. return elem.currentStyle[ prop ];
  5519. } else if ( global.getComputedStyle ) {
  5520. // Firefox, Chrome et. al
  5521. return doc.defaultView.getComputedStyle( elem, null ).getPropertyValue( prop );
  5522. } else {
  5523. // Fallback, just in case
  5524. return elem.style[ prop ];
  5525. }
  5526. }
  5527. };
  5528. })( window, document );
  5529. // Popcorn Soundcloud Player Wrapper
  5530. ( function( Popcorn, global ) {
  5531. /**
  5532. * Soundcloud wrapper for Popcorn.
  5533. * This player adds enables Popcorn.js to handle Soundcloud audio. It does so by masking an embedded Soundcloud Flash object
  5534. * as a video and implementing the HTML5 Media Element interface.
  5535. *
  5536. * You can configure the video source and dimensions in two ways:
  5537. * 1. Use the embed code path supplied by Soundcloud the id of the desired location into a new Popcorn.soundcloud object.
  5538. * Width and height can be configured throughh CSS.
  5539. *
  5540. * <div id="player_1" style="width: 500px; height: 81px"></div>
  5541. * <script type="text/javascript">
  5542. * document.addEventListener("DOMContentLoaded", function() {
  5543. * var popcorn = Popcorn( Popcorn.soundcloud( "player_1", "http://soundcloud.com/forss/flickermood" ));
  5544. * }, false);
  5545. * </script>
  5546. *
  5547. * 2. Width and height may also be configured directly with the player; this will override any CSS. This is useful for
  5548. * when different sizes are desired. for multiple players within the same parent container.
  5549. *
  5550. * <div id="player_1"></div>
  5551. * <script type="text/javascript">
  5552. * document.addEventListener("DOMContentLoaded", function() {
  5553. * var popcorn = Popcorn( Popcorn.soundcloud( "player_1", "http://soundcloud.com/forss/flickermood", {
  5554. * width: "500", // Optional, will default to CSS values
  5555. * height: "81" // Optional, will default to CSS values
  5556. * }));
  5557. * }, false);
  5558. * </script>
  5559. *
  5560. * The player can be further configured to integrate with the SoundCloud API:
  5561. *
  5562. * var popcorn = Popcorn( Popcorn.soundcloud( "player_1", "http://soundcloud.com/forss/flickermood", {
  5563. * width: "100%", // Optional, the width for the player. May also be as '##px'
  5564. * // Defaults to the maximum possible width
  5565. * height: "81px", // Optional, the height for the player. May also be as '###%'
  5566. * // Defaults to 81px
  5567. * api: { // Optional, information for Soundcloud API interaction
  5568. * key: "abcdefsdfsdf", // Required for API interaction. The Soundcloud API key
  5569. * commentdiv: "divId_for_output", // Required for comment retrieval, the Div Id for outputting comments.
  5570. * commentformat: function( comment ) {} // Optional, a function to format a comment. Returns HTML string
  5571. * }
  5572. * }));
  5573. *
  5574. * Comments are retrieved from Soundcloud when the player is registered with Popcorn by calling the registerWithPopcorn()
  5575. * function. For this to work, the api_key and commentdiv attributes must be set. Comments are output by default similar to
  5576. * how Soundcloud formats them in-player, but a custom formatting function may be supplied. It receives a comment object and
  5577. * the current date. A comment object has:
  5578. *
  5579. * var comment = {
  5580. * start: 0, // Required. Start time in ms.
  5581. * date: new Date(), // Required. Date comment wasa posted.
  5582. * text: "", // Required. Comment text
  5583. * user: { // Required. Describes the user who posted the comment
  5584. * name: "", // Required. User name
  5585. * profile: "", // Required. User profile link
  5586. * avatar: "" // Required. User avatar link
  5587. * }
  5588. * }
  5589. *
  5590. * These events are completely custom-implemented and may be subscribed to at any time:
  5591. * canplaythrough
  5592. * durationchange
  5593. * load
  5594. * loadedmetadata
  5595. * loadstart
  5596. * play
  5597. * readystatechange
  5598. * volumechange
  5599. *
  5600. * These events are related to player functionality and must be subscribed to during or after the load event:
  5601. * canplay
  5602. * ended
  5603. * error
  5604. * pause
  5605. * playing
  5606. * progress
  5607. * seeked
  5608. * timeupdate
  5609. *
  5610. * These events are not supported:
  5611. * abort
  5612. * emptied
  5613. * loadeddata
  5614. * ratechange
  5615. * seeking
  5616. * stalled
  5617. * suspend
  5618. * waiting
  5619. *
  5620. * Supported media attributes:
  5621. * autoplay ( via Popcorn )
  5622. * currentTime
  5623. * defaultPlaybackRate ( get only )
  5624. * duration ( get only )
  5625. * ended ( get only )
  5626. * initialTime ( get only, always 0 )
  5627. * loop ( get only, set by calling setLoop() )
  5628. * muted ( get only )
  5629. * paused ( get only )
  5630. * playbackRate ( get only )
  5631. * played ( get only, 0/1 only )
  5632. * readyState ( get only )
  5633. * src ( get only )
  5634. * volume
  5635. *
  5636. * load() function
  5637. * mute() function ( toggles on/off )
  5638. * play() function
  5639. * pause() function
  5640. *
  5641. * Unsupported media attributes:
  5642. * buffered
  5643. * networkState
  5644. * preload
  5645. * seekable
  5646. * seeking
  5647. * startOffsetTime
  5648. *
  5649. * canPlayType() function
  5650. */
  5651. // Trackers
  5652. var timeupdateInterval = 33,
  5653. timeCheckInterval = 0.25,
  5654. abs = Math.abs,
  5655. floor = Math.floor,
  5656. round = Math.round,
  5657. registry = {};
  5658. function hasAllDependencies() {
  5659. return global.swfobject && global.soundcloud;
  5660. }
  5661. // Borrowed from: http://www.quirksmode.org/dom/getstyles.html
  5662. // Gets the style for the given element
  5663. function getStyle( elem, styleProp ) {
  5664. if ( elem.currentStyle ) {
  5665. // IE way
  5666. return elem.currentStyle[styleProp];
  5667. } else if ( global.getComputedStyle ) {
  5668. // Firefox, Chrome, et. al
  5669. return document.defaultView.getComputedStyle( elem, null ).getPropertyValue( styleProp );
  5670. }
  5671. }
  5672. function formatComment( comment ) {
  5673. // Calclate the difference between d and now, express as "n units ago"
  5674. function ago( d ) {
  5675. var diff = ( ( new Date() ).getTime() - d.getTime() )/1000;
  5676. function pluralize( value, unit ) {
  5677. return value + " " + unit + ( value > 1 ? "s" : "") + " ago";
  5678. }
  5679. if ( diff < 60 ) {
  5680. return pluralize( round( diff ), "second" );
  5681. }
  5682. diff /= 60;
  5683. if ( diff < 60 ) {
  5684. return pluralize( round( diff ), "minute" );
  5685. }
  5686. diff /= 60;
  5687. if ( diff < 24 ) {
  5688. return pluralize( round( diff ), "hour" );
  5689. }
  5690. diff /= 24;
  5691. // Rough approximation of months
  5692. if ( diff < 30 ) {
  5693. return pluralize( round( diff ), "day" );
  5694. }
  5695. if ( diff < 365 ) {
  5696. return pluralize( round( diff/30 ), "month" );
  5697. }
  5698. return pluralize( round( diff/365 ), "year" );
  5699. }
  5700. // Converts sec to min.sec
  5701. function timeToFraction ( totalSec ) {
  5702. var min = floor( totalSec / 60 ),
  5703. sec = round( totalSec % 60 );
  5704. return min + "." + ( sec < 10 ? "0" : "" ) + sec;
  5705. }
  5706. return '<div><a href="' + comment.user.profile + '">' +
  5707. '<img width="16px height="16px" src="' + comment.user.avatar + '"></img>' +
  5708. comment.user.name + '</a> at ' + timeToFraction( comment.start ) + ' ' +
  5709. ago( comment.date ) +
  5710. '<br />' + comment.text + '</span>';
  5711. }
  5712. function isReady( self ) {
  5713. if ( !hasAllDependencies() ) {
  5714. setTimeout( function() {
  5715. isReady( self );
  5716. }, 15 );
  5717. return;
  5718. }
  5719. var flashvars = {
  5720. enable_api: true,
  5721. object_id: self._playerId,
  5722. url: self.src,
  5723. // Hide comments in player if showing them elsewhere
  5724. show_comments: !self._options.api.key && !self._options.api.commentdiv
  5725. },
  5726. params = {
  5727. allowscriptaccess: "always",
  5728. // This is so we can overlay html ontop of Flash
  5729. wmode: 'transparent'
  5730. },
  5731. attributes = {
  5732. id: self._playerId,
  5733. name: self._playerId
  5734. },
  5735. actualTarget = document.createElement( 'div' );
  5736. actualTarget.setAttribute( "id", self._playerId );
  5737. self._container.appendChild( actualTarget );
  5738. swfobject.embedSWF( "http://player.soundcloud.com/player.swf", self._playerId, self.offsetWidth, self.height, "9.0.0", "expressInstall.swf", flashvars, params, attributes );
  5739. }
  5740. Popcorn.getScript( "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js" );
  5741. // Source file originally from 'https://github.com/soundcloud/Widget-JS-API/raw/master/soundcloud.player.api.js'
  5742. Popcorn.getScript( "http://popcornjs.org/code/players/soundcloud/lib/soundcloud.player.api.js", function() {
  5743. // Play event is fired twice when player is first started. Ignore second one
  5744. var ignorePlayEvt = 1;
  5745. // Register the wrapper's load event with the player
  5746. soundcloud.addEventListener( 'onPlayerReady', function( object, data ) {
  5747. var wrapper = registry[object.api_getFlashId()];
  5748. wrapper.swfObj = object;
  5749. wrapper.duration = object.api_getTrackDuration();
  5750. wrapper.currentTime = object.api_getTrackPosition();
  5751. // This eliminates volumechangee event from firing on load
  5752. wrapper.volume = wrapper.previousVolume = object.api_getVolume()/100;
  5753. // The numeric id of the track for use with Soundcloud API
  5754. wrapper._mediaId = data.mediaId;
  5755. wrapper.dispatchEvent( 'load' );
  5756. wrapper.dispatchEvent( 'canplay' );
  5757. wrapper.dispatchEvent( 'durationchange' );
  5758. wrapper.timeupdate();
  5759. });
  5760. // Register events for when the flash player plays a track for the first time
  5761. soundcloud.addEventListener( 'onMediaStart', function( object, data ) {
  5762. var wrapper = registry[object.api_getFlashId()];
  5763. wrapper.played = 1;
  5764. wrapper.dispatchEvent( 'playing' );
  5765. });
  5766. // Register events for when the flash player plays a track
  5767. soundcloud.addEventListener( 'onMediaPlay', function( object, data ) {
  5768. if ( ignorePlayEvt ) {
  5769. ignorePlayEvt = 0;
  5770. return;
  5771. }
  5772. var wrapper = registry[object.api_getFlashId()];
  5773. wrapper.dispatchEvent( 'play' );
  5774. });
  5775. // Register events for when the flash player pauses a track
  5776. soundcloud.addEventListener( 'onMediaPause', function( object, data ) {
  5777. var wrapper = registry[object.api_getFlashId()];
  5778. wrapper.dispatchEvent( 'pause' );
  5779. });
  5780. // Register events for when the flash player is buffering
  5781. soundcloud.addEventListener( 'onMediaBuffering', function( object, data ) {
  5782. var wrapper = registry[object.api_getFlashId()];
  5783. wrapper.dispatchEvent( 'progress' );
  5784. if ( wrapper.readyState === 0 ) {
  5785. wrapper.readyState = 3;
  5786. wrapper.dispatchEvent( "readystatechange" );
  5787. }
  5788. });
  5789. // Register events for when the flash player is done buffering
  5790. soundcloud.addEventListener( 'onMediaDoneBuffering', function( object, data ) {
  5791. var wrapper = registry[object.api_getFlashId()];
  5792. wrapper.dispatchEvent( 'canplaythrough' );
  5793. });
  5794. // Register events for when the flash player has finished playing
  5795. soundcloud.addEventListener( 'onMediaEnd', function( object, data ) {
  5796. var wrapper = registry[object.api_getFlashId()];
  5797. wrapper.paused = 1;
  5798. //wrapper.pause();
  5799. wrapper.dispatchEvent( 'ended' );
  5800. });
  5801. // Register events for when the flash player has seeked
  5802. soundcloud.addEventListener( 'onMediaSeek', function( object, data ) {
  5803. var wrapper = registry[object.api_getFlashId()];
  5804. wrapper.setCurrentTime( object.api_getTrackPosition() );
  5805. if ( wrapper.paused ) {
  5806. wrapper.dispatchEvent( "timeupdate" );
  5807. }
  5808. });
  5809. // Register events for when the flash player has errored
  5810. soundcloud.addEventListener( 'onPlayerError', function( object, data ) {
  5811. var wrapper = registry[object.api_getFlashId()];
  5812. wrapper.dispatchEvent( 'error' );
  5813. });
  5814. });
  5815. Popcorn.soundcloud = function( containerId, src, options ) {
  5816. return new Popcorn.soundcloud.init( containerId, src, options );
  5817. };
  5818. // A constructor, but we need to wrap it to allow for "static" functions
  5819. Popcorn.soundcloud.init = (function() {
  5820. function pullFromContainer( that ) {
  5821. var options = that._options,
  5822. container = that._container,
  5823. bounds = container.getBoundingClientRect(),
  5824. tmp,
  5825. undef;
  5826. that.width = options.width || getStyle( container, "width" ) || "100%";
  5827. that.height = options.height || getStyle( container, "height" ) || "81px";
  5828. that.src = options.src;
  5829. that.autoplay = options.autoplay;
  5830. if ( parseFloat( that.height, 10 ) !== 81 ) {
  5831. that.height = "81px";
  5832. }
  5833. that.offsetLeft = bounds.left;
  5834. that.offsetTop = bounds.top;
  5835. that.offsetHeight = parseFloat( that.height, 10 );
  5836. that.offsetWidth = parseFloat( that.width, 10 );
  5837. // Width and height may've been specified as a %, find the value now in case a plugin needs it (like subtitle)
  5838. if ( /[\d]+%/.test( that.width ) ) {
  5839. tmp = getStyle( container, "width" );
  5840. that._container.style.width = that.width;
  5841. that.offsetWidth = that._container.offsetWidth;
  5842. that._container.style.width = tmp;
  5843. }
  5844. if ( /[\d]+%/.test( that.height ) ) {
  5845. tmp = getStyle( container, "height" );
  5846. that._container.style.height = that.height;
  5847. that.offsetHeight = that._container.offsetHeight;
  5848. that._container.style.height = tmp;
  5849. }
  5850. }
  5851. // If container id is not supplied, assumed to be same as player id
  5852. var ctor = function ( containerId, src, options ) {
  5853. if ( !containerId ) {
  5854. throw "Must supply an id!";
  5855. } else if ( !src ) {
  5856. throw "Must supply a source!";
  5857. } else if ( /file/.test( location.protocol ) ) {
  5858. throw "Must run from a web server!";
  5859. }
  5860. var container = this._container = document.getElementById( containerId );
  5861. if ( !container ) {
  5862. throw "Could not find that container in the DOM!";
  5863. }
  5864. options = options || {};
  5865. options.api = options.api || {};
  5866. options.target = containerId;
  5867. options.src = src;
  5868. options.api.commentformat = options.api.commentformat || formatComment;
  5869. this._mediaId = 0;
  5870. this._listeners = {};
  5871. this._playerId = Popcorn.guid( options.target );
  5872. this._containerId = options.target;
  5873. this._options = options;
  5874. this._comments = [];
  5875. this._popcorn;
  5876. pullFromContainer( this );
  5877. this.duration = 0;
  5878. this.volume = 1;
  5879. this.currentTime = 0;
  5880. this.ended = 0;
  5881. this.paused = 1;
  5882. this.readyState = 0;
  5883. this.playbackRate = 1;
  5884. this.top = 0;
  5885. this.left = 0;
  5886. this.autoplay;
  5887. this.played = 0;
  5888. this.addEventListener( "load", function() {
  5889. var boundRect = this.getBoundingClientRect();
  5890. this.top = boundRect.top;
  5891. this.left = boundRect.left;
  5892. this.offsetWidth = this.swfObj.offsetWidth;
  5893. this.offsetHeight = this.swfObj.offsetHeight;
  5894. this.offsetLeft = this.swfObj.offsetLeft;
  5895. this.offsetTop = this.swfObj.offsetTop;
  5896. });
  5897. registry[ this._playerId ] = this;
  5898. isReady( this );
  5899. };
  5900. return ctor;
  5901. })();
  5902. Popcorn.soundcloud.init.prototype = Popcorn.soundcloud.prototype;
  5903. // Sequence object prototype
  5904. Popcorn.extend( Popcorn.soundcloud.prototype, {
  5905. // Set the volume as a value between 0 and 1
  5906. setVolume: function( val ) {
  5907. if ( !val && val !== 0 ) {
  5908. return;
  5909. }
  5910. // Normalize in case outside range of expected values of 0 .. 1
  5911. if ( val < 0 ) {
  5912. val = -val;
  5913. }
  5914. if ( val > 1 ) {
  5915. val %= 1;
  5916. }
  5917. // HTML video expects to be 0.0 -> 1.0, Flash object expects 0-100
  5918. this.volume = this.previousVolume = val;
  5919. this.swfObj.api_setVolume( val*100 );
  5920. this.dispatchEvent( "volumechange" );
  5921. },
  5922. // Seeks the video
  5923. setCurrentTime: function ( time ) {
  5924. if ( !time && time !== 0 ) {
  5925. return;
  5926. }
  5927. this.currentTime = this.previousCurrentTime = time;
  5928. this.ended = time >= this.duration;
  5929. // Fire events for seeking and time change
  5930. this.dispatchEvent( "seeked" );
  5931. },
  5932. // Play the video
  5933. play: function() {
  5934. // In case someone is cheeky enough to try this before loaded
  5935. if ( !this.swfObj ) {
  5936. this.addEventListener( "load", this.play );
  5937. return;
  5938. } else if ( !this.paused ) {
  5939. // No need to process if already playing
  5940. return;
  5941. }
  5942. this.paused = 0;
  5943. this.swfObj.api_play();
  5944. },
  5945. // Pause the video
  5946. pause: function() {
  5947. // In case someone is cheeky enough to try this before loaded
  5948. if ( !this.swfObj ) {
  5949. this.addEventListener( "load", this.pause );
  5950. return;
  5951. } else if ( this.paused ) {
  5952. // No need to process if already playing
  5953. return;
  5954. }
  5955. this.paused = 1;
  5956. this.swfObj.api_pause();
  5957. },
  5958. // Toggle video muting
  5959. // Unmuting will leave it at the old value
  5960. mute: function() {
  5961. // In case someone is cheeky enough to try this before loaded
  5962. if ( !this.swfObj ) {
  5963. this.addEventListener( "load", this.mute );
  5964. return;
  5965. }
  5966. if ( !this.muted() ) {
  5967. this.oldVol = this.volume;
  5968. if ( this.paused ) {
  5969. this.setVolume( 0 );
  5970. } else {
  5971. this.volume = 0;
  5972. }
  5973. } else {
  5974. if ( this.paused ) {
  5975. this.setVolume( this.oldVol );
  5976. } else {
  5977. this.volume = this.oldVol;
  5978. }
  5979. }
  5980. },
  5981. muted: function() {
  5982. return this.volume === 0;
  5983. },
  5984. // Force loading by playing the player. Pause afterwards
  5985. load: function() {
  5986. // In case someone is cheeky enough to try this before loaded
  5987. if ( !this.swfObj ) {
  5988. this.addEventListener( "load", this.load );
  5989. return;
  5990. }
  5991. this.play();
  5992. this.pause();
  5993. },
  5994. // Hook an event listener for the player event into internal event system
  5995. // Stick to HTML conventions of add event listener and keep lowercase, without prepending "on"
  5996. addEventListener: function( evt, fn ) {
  5997. if ( !this._listeners[evt] ) {
  5998. this._listeners[evt] = [];
  5999. }
  6000. this._listeners[evt].push( fn );
  6001. return fn;
  6002. },
  6003. dispatchEvent: function( evt ) {
  6004. var self = this,
  6005. evtName = evt.type || evt;
  6006. // Manually triggered a UI event, have it invoke rather than just the event handlers
  6007. if ( evtName === "play" && this.paused || evtName === "pause" && !this.paused ) {
  6008. this[evtName]();
  6009. return;
  6010. }
  6011. Popcorn.forEach( this._listeners[evtName], function( fn ) {
  6012. fn.call( self );
  6013. });
  6014. },
  6015. timeupdate: function() {
  6016. var self = this,
  6017. checkedVolume = this.swfObj.api_getVolume()/100,
  6018. seeked = 0;
  6019. // If has been changed through setting currentTime attribute
  6020. if ( abs( this.currentTime - this.previousCurrentTime ) > timeCheckInterval ) {
  6021. // Has programatically set the currentTime
  6022. this.swfObj.api_seekTo( this.currentTime );
  6023. seeked = 1;
  6024. } else {
  6025. this.previousCurrentTime = this.currentTime = this.swfObj.api_getTrackPosition();
  6026. }
  6027. // If has been changed throughh volume attribute
  6028. if ( checkedVolume !== this.previousVolume ) {
  6029. this.setVolume( checkedVolume );
  6030. } else if ( this.volume !== this.previousVolume ) {
  6031. this.setVolume( this.volume );
  6032. }
  6033. if ( !this.paused ) {
  6034. this.dispatchEvent( 'timeupdate' );
  6035. }
  6036. if( !self.ended ) {
  6037. setTimeout( function() {
  6038. self.timeupdate.call( self );
  6039. }, timeupdateInterval);
  6040. }
  6041. },
  6042. getBoundingClientRect: function() {
  6043. var b,
  6044. self = this;
  6045. if ( this.swfObj ) {
  6046. b = this.swfObj.getBoundingClientRect();
  6047. return {
  6048. bottom: b.bottom,
  6049. left: b.left,
  6050. right: b.right,
  6051. top: b.top,
  6052. // These not guaranteed to be in there
  6053. width: b.width || ( b.right - b.left ),
  6054. height: b.height || ( b.bottom - b.top )
  6055. };
  6056. } else {
  6057. //container = document.getElementById( this.playerId );
  6058. tmp = this._container.getBoundingClientRect();
  6059. // Update bottom, right for expected values once the container loads
  6060. return {
  6061. left: tmp.left,
  6062. top: tmp.top,
  6063. width: self.offsetWidth,
  6064. height: self.offsetHeight,
  6065. bottom: tmp.top + this.width,
  6066. right: tmp.top + this.height
  6067. };
  6068. }
  6069. },
  6070. registerPopcornWithPlayer: function( popcorn ) {
  6071. if ( !this.swfObj ) {
  6072. this.addEventListener( "load", function() {
  6073. this.registerPopcornWithPlayer( popcorn );
  6074. });
  6075. return;
  6076. }
  6077. this._popcorn = popcorn;
  6078. var api = this._options.api;
  6079. if ( api.key && api.commentdiv ) {
  6080. var self = this;
  6081. Popcorn.xhr({
  6082. url: "http://api.soundcloud.com/tracks/" + self._mediaId + "/comments.js?consumer_key=" + api.key,
  6083. success: function( data ) {
  6084. Popcorn.forEach( data.json, function ( obj ) {
  6085. self.addComment({
  6086. start: obj.timestamp/1000,
  6087. date: new Date( obj.created_at ),
  6088. text: obj.body,
  6089. user: {
  6090. name: obj.user.username,
  6091. profile: obj.user.permalink_url,
  6092. avatar: obj.user.avatar_url
  6093. }
  6094. });
  6095. });
  6096. }
  6097. });
  6098. }
  6099. }
  6100. });
  6101. Popcorn.extend( Popcorn.soundcloud.prototype, {
  6102. addComment: function( obj, displayFn ) {
  6103. var self = this,
  6104. comment = {
  6105. start: obj.start || 0,
  6106. date: obj.date || new Date(),
  6107. text: obj.text || "",
  6108. user: {
  6109. name: obj.user.name || "",
  6110. profile: obj.user.profile || "",
  6111. avatar: obj.user.avatar || ""
  6112. },
  6113. display: function() {
  6114. return ( displayFn || self._options.api.commentformat )( comment );
  6115. }
  6116. };
  6117. this._comments.push( comment );
  6118. if ( !this._popcorn ) {
  6119. return;
  6120. }
  6121. this._popcorn.subtitle({
  6122. start: comment.start,
  6123. target: this._options.api.commentdiv,
  6124. display: 'inline',
  6125. language: 'en',
  6126. text: comment.display()
  6127. });
  6128. }
  6129. });
  6130. })( Popcorn, window );// Popcorn Vimeo Player Wrapper
  6131. ( function( Popcorn, global ) {
  6132. /**
  6133. * Vimeo wrapper for Popcorn.
  6134. * This player adds enables Popcorn.js to handle Vimeo videos. It does so by masking an embedded Vimeo video Flash object
  6135. * as a video and implementing the HTML5 Media Element interface.
  6136. *
  6137. * You can specify the video in four ways:
  6138. * 1. Use the embed code path supplied by Vimeo as a div's src, and pass the div id into a new Popcorn.vimeo object
  6139. *
  6140. * <div id="player_1" width="500" height="281" src="http://player.vimeo.com/video/11127501" ></div>
  6141. * <script type="text/javascript">
  6142. * document.addEventListener("DOMContentLoaded", function() {
  6143. * var popcorn = Popcorn( Popcorn.vimeo( "player_1" ) );
  6144. * }, false);
  6145. * </script>
  6146. &
  6147. * 2. Pass the div id and the embed code path supplied by Vimeo into a new Popcorn.vimeo object
  6148. *
  6149. * <div id="player_1" width="500" height="281" ></div>
  6150. * <script type="text/javascript">
  6151. * document.addEventListener("DOMContentLoaded", function() {
  6152. * var popcorn = Popcorn( Popcorn.vimeo( "player_1", "http://player.vimeo.com/video/11127501" ) );
  6153. * }, false);
  6154. * </script>
  6155. *
  6156. * 3. Use a web url as a div's src, and pass the div id into a new Popcorn.vimeo object
  6157. *
  6158. * <div id="player_1" width="500" height="281" src="http://vimeo.com/11127501" ></div>
  6159. * <script type="text/javascript">
  6160. * document.addEventListener("DOMContentLoaded", function() {
  6161. * var popcorn = Popcorn( Popcorn.vimeo( "player_1" ) );
  6162. * }, false);
  6163. * </script>
  6164. *
  6165. * 4. Pass the div id and the web url into a new Popcorn.vimeo object
  6166. *
  6167. * <div id="player_1" width="500" height="281" ></div>
  6168. * <script type="text/javascript">
  6169. * document.addEventListener("DOMContentLoaded", function() {
  6170. * var popcorn = Popcorn( Popcorn.vimeo( "player_1", "http://vimeo.com/11127501" ) );
  6171. * }, false);
  6172. * </script>
  6173. *
  6174. * Due to Vimeo's API, certain events must be subscribed to at different times, and some not at all.
  6175. * These events are completely custom-implemented and may be subscribed to at any time:
  6176. * canplaythrough
  6177. * durationchange
  6178. * load
  6179. * loadedmetadata
  6180. * loadstart
  6181. * play
  6182. * readystatechange
  6183. * volumechange
  6184. *
  6185. * These events are related to player functionality and must be subscribed to during or after the load event:
  6186. * abort
  6187. * emptied
  6188. * ended
  6189. * pause
  6190. * playing
  6191. * progress
  6192. * seeked
  6193. * timeupdate
  6194. *
  6195. * These events are not supported:
  6196. * canplay
  6197. * error
  6198. * loadeddata
  6199. * ratechange
  6200. * seeking
  6201. * stalled
  6202. * suspend
  6203. * waiting
  6204. *
  6205. * Due to Vimeo's API, some attributes are be supported while others are not.
  6206. * Supported media attributes:
  6207. * autoplay ( via Popcorn )
  6208. * currentTime
  6209. * duration ( get only )
  6210. * ended ( get only )
  6211. * initialTime ( get only, always 0 )
  6212. * loop ( get only, set by calling setLoop() )
  6213. * muted ( get only )
  6214. * paused ( get only )
  6215. * readyState ( get only )
  6216. * volume
  6217. *
  6218. * load() function
  6219. * mute() function ( toggles on/off )
  6220. *
  6221. * Unsupported media attributes:
  6222. * buffered
  6223. * defaultPlaybackRate
  6224. * networkState
  6225. * playbackRate
  6226. * played
  6227. * preload
  6228. * seekable
  6229. * seeking
  6230. * src
  6231. * startOffsetTime
  6232. */
  6233. // Trackers
  6234. var timeupdateInterval = 33,
  6235. timeCheckInterval = 0.75,
  6236. abs = Math.abs,
  6237. registry = {};
  6238. // base object for DOM-related behaviour like events
  6239. var EventManager = function ( owner ) {
  6240. var evts = {};
  6241. function makeHandler( evtName ) {
  6242. if ( !evts[evtName] ) {
  6243. evts[evtName] = [];
  6244. // Create a wrapper function to all registered listeners
  6245. this["on"+evtName] = function( args ) {
  6246. Popcorn.forEach( evts[evtName], function( fn ) {
  6247. if ( fn ) {
  6248. fn.call( owner, args );
  6249. }
  6250. });
  6251. };
  6252. }
  6253. }
  6254. return {
  6255. addEventListener: function( evtName, fn, doFire ) {
  6256. evtName = evtName.toLowerCase();
  6257. makeHandler.call( this, evtName );
  6258. evts[evtName].push( fn );
  6259. if ( doFire ) {
  6260. dispatchEvent( evtName );
  6261. }
  6262. return fn;
  6263. },
  6264. // Add many listeners for a single event
  6265. // Takes an event name and array of functions
  6266. addEventListeners: function( evtName, events ) {
  6267. evtName = evtName.toLowerCase();
  6268. makeHandler.call( this, evtName );
  6269. evts[evtName] = evts[evtName].concat( events );
  6270. },
  6271. removeEventListener: function( evtName, fn ) {
  6272. var evtArray = this.getEventListeners( evtName ),
  6273. i,
  6274. l;
  6275. // Find and remove from events array
  6276. for ( i = 0, l = evtArray.length; i < l; i++) {
  6277. if ( evtArray[i] === fn ) {
  6278. var removed = evtArray[i];
  6279. evtArray[i] = 0;
  6280. return removed;
  6281. }
  6282. }
  6283. },
  6284. getEventListeners: function( evtName ) {
  6285. if( evtName ) {
  6286. return evts[ evtName.toLowerCase() ] || [];
  6287. } else {
  6288. return evts;
  6289. }
  6290. },
  6291. dispatchEvent: function( evt, args ) {
  6292. // If event object was passed in, toString will yield event type as string (timeupdate)
  6293. // If a string, toString() will return the string itself (timeupdate)
  6294. evt = "on"+evt.toString().toLowerCase();
  6295. this[evt] && this[evt]( args );
  6296. }
  6297. };
  6298. };
  6299. Popcorn.vimeo = function( mediaId, list, options ) {
  6300. return new Popcorn.vimeo.init( mediaId, list, options );
  6301. };
  6302. Popcorn.vimeo.onLoad = function( playerId ) {
  6303. var player = registry[ playerId ];
  6304. player.swfObj = document.getElementById( playerId );
  6305. // For calculating position relative to video (like subtitles)
  6306. player.offsetWidth = player.swfObj.offsetWidth;
  6307. player.offsetHeight = player.swfObj.offsetHeight;
  6308. player.offsetParent = player.swfObj.offsetParent;
  6309. player.offsetLeft = player.swfObj.offsetLeft;
  6310. player.offsetTop = player.swfObj.offsetTop;
  6311. player.dispatchEvent( "load" );
  6312. };
  6313. Popcorn.getScript( "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js" );
  6314. // A constructor, but we need to wrap it to allow for "static" functions
  6315. Popcorn.vimeo.init = (function() {
  6316. var rPlayerUri = /^http:\/\/player\.vimeo\.com\/video\/[\d]+/i,
  6317. rWebUrl = /vimeo\.com\/[\d]+/,
  6318. hasAPILoaded = false;
  6319. // Extract the numeric video id from container uri: 'http://player.vimeo.com/video/11127501' or 'http://player.vimeo.com/video/4282282'
  6320. // Expect id to be a valid 32/64-bit unsigned integer
  6321. // Returns string, empty string if could not match
  6322. function extractIdFromUri( uri ) {
  6323. if ( !uri ) {
  6324. return;
  6325. }
  6326. var matches = uri.match( rPlayerUri );
  6327. return matches ? matches[0].substr(30) : "";
  6328. }
  6329. // Extract the numeric video id from url: 'http://vimeo.com/11127501' or simply 'vimeo.com/4282282'
  6330. // Ignores protocol and subdomain, but one would expecct it to be http://www.vimeo.com/#######
  6331. // Expect id to be a valid 32/64-bit unsigned integer
  6332. // Returns string, empty string if could not match
  6333. function extractIdFromUrl( url ) {
  6334. if ( !url ) {
  6335. return;
  6336. }
  6337. var matches = url.match( rWebUrl );
  6338. return matches ? matches[0].substr(10) : "";
  6339. }
  6340. function makeSwf( self, vidId, containerId ) {
  6341. if ( !window.swfobject ) {
  6342. setTimeout( function() {
  6343. makeSwf( self, vidId, containerId );
  6344. }, 1);
  6345. return;
  6346. }
  6347. var params,
  6348. flashvars,
  6349. attributes = {};
  6350. flashvars = {
  6351. clip_id: vidId,
  6352. show_portrait: 1,
  6353. show_byline: 1,
  6354. show_title: 1,
  6355. // required in order to use the Javascript API
  6356. js_api: 1,
  6357. // moogaloop will call this JS function when it's done loading (optional)
  6358. js_onLoad: 'Popcorn.vimeo.onLoad',
  6359. // this will be passed into all event methods so you can keep track of multiple moogaloops (optional)
  6360. js_swf_id: containerId
  6361. };
  6362. params = {
  6363. allowscriptaccess: 'always',
  6364. allowfullscreen: 'true',
  6365. // This is so we can overlay html ontop o fFlash
  6366. wmode: 'transparent'
  6367. };
  6368. swfobject.embedSWF( "http://vimeo.com/moogaloop.swf", containerId, self.offsetWidth, self.offsetHeight, "9.0.0", "expressInstall.swf", flashvars, params, attributes );
  6369. }
  6370. // If container id is not supplied, assumed to be same as player id
  6371. var ctor = function ( containerId, videoUrl, options ) {
  6372. if ( !containerId ) {
  6373. throw "Must supply an id!";
  6374. } else if ( /file/.test( location.protocol ) ) {
  6375. throw "Must run from a web server!";
  6376. }
  6377. var vidId,
  6378. that = this,
  6379. tmp;
  6380. this._container = document.createElement( "div" );
  6381. this._container.id = containerId + "object";
  6382. this._target = document.getElementById( containerId );
  6383. this._target.appendChild( this._container );
  6384. options = options || {};
  6385. options.css && Popcorn.extend( this._target.style, options.css );
  6386. this.addEventFn;
  6387. this.evtHolder;
  6388. this.paused = true;
  6389. this.duration = Number.MAX_VALUE;
  6390. this.ended = 0;
  6391. this.currentTime = 0;
  6392. this.volume = 1;
  6393. this.loop = 0;
  6394. this.initialTime = 0;
  6395. this.played = 0;
  6396. this.readyState = 0;
  6397. this.previousCurrentTime = this.currentTime;
  6398. this.previousVolume = this.volume;
  6399. this.evtHolder = new EventManager( this );
  6400. // For calculating position relative to video (like subtitles)
  6401. this.width = this._target.style.width || "504px";
  6402. this.height = this._target.style.height || "340px";
  6403. if ( !/[\d]%/.test( this.width ) ) {
  6404. this.offsetWidth = parseInt( this.width, 10 );
  6405. this._target.style.width = this.width + "px";
  6406. } else {
  6407. // convert from pct to abs pixels
  6408. tmp = this._target.style.width;
  6409. this._target.style.width = this.width;
  6410. this.offsetWidth = this._target.offsetWidth;
  6411. this._target.style.width = tmp;
  6412. }
  6413. if ( !/[\d]%/.test( this.height ) ) {
  6414. this.offsetHeight = parseInt( this.height, 10 );
  6415. this._target.style.height = this.height + "px";
  6416. } else {
  6417. // convert from pct to abs pixels
  6418. tmp = this._target.style.height;
  6419. this._target.style.height = this.height;
  6420. this.offsetHeight = this._target.offsetHeight;
  6421. this._target.style.height = tmp;
  6422. }
  6423. this.offsetLeft = 0;
  6424. this.offsetTop = 0;
  6425. // Try and get a video id from a vimeo site url
  6426. // Try either from ctor param or from iframe itself
  6427. vidId = extractIdFromUrl( videoUrl ) || extractIdFromUri( videoUrl );
  6428. if ( !vidId ) {
  6429. throw "No video id";
  6430. }
  6431. registry[ this._container.id ] = this;
  6432. makeSwf( this, vidId, this._container.id );
  6433. // Set up listeners to internally track state as needed
  6434. this.addEventListener( "load", function() {
  6435. var hasLoaded = false;
  6436. that.duration = that.swfObj.api_getDuration();
  6437. that.evtHolder.dispatchEvent( "durationchange" );
  6438. that.evtHolder.dispatchEvent( "loadedmetadata" );
  6439. // Chain events and calls together so that this.currentTime reflects the current time of the video
  6440. // Done by Getting the Current Time while the video plays
  6441. that.addEventListener( "timeupdate", function() {
  6442. that.currentTime = that.swfObj.api_getCurrentTime();
  6443. });
  6444. // Add pause listener to keep track of playing state
  6445. that.addEventListener( "pause", function() {
  6446. that.paused = true;
  6447. });
  6448. // Add play listener to keep track of playing state
  6449. that.addEventListener( "playing", function() {
  6450. that.paused = false;
  6451. that.ended = 0;
  6452. });
  6453. // Add ended listener to keep track of playing state
  6454. that.addEventListener( "ended", function() {
  6455. if ( that.loop !== "loop" ) {
  6456. that.paused = true;
  6457. that.ended = 1;
  6458. }
  6459. });
  6460. // Add progress listener to keep track of ready state
  6461. that.addEventListener( "progress", function( data ) {
  6462. if ( !hasLoaded ) {
  6463. hasLoaded = 1;
  6464. that.readyState = 3;
  6465. that.evtHolder.dispatchEvent( "readystatechange" );
  6466. }
  6467. // Check if fully loaded
  6468. if ( data.percent === 100 ) {
  6469. that.readyState = 4;
  6470. that.evtHolder.dispatchEvent( "readystatechange" );
  6471. that.evtHolder.dispatchEvent( "canplaythrough" );
  6472. }
  6473. });
  6474. });
  6475. };
  6476. return ctor;
  6477. })();
  6478. Popcorn.vimeo.init.prototype = Popcorn.vimeo.prototype;
  6479. // Sequence object prototype
  6480. Popcorn.extend( Popcorn.vimeo.prototype, {
  6481. // Do everything as functions instead of get/set
  6482. setLoop: function( val ) {
  6483. if ( !val ) {
  6484. return;
  6485. }
  6486. this.loop = val;
  6487. var isLoop = val === "loop" ? 1 : 0;
  6488. // HTML convention says to loop if value is 'loop'
  6489. this.swfObj.api_setLoop( isLoop );
  6490. },
  6491. // Set the volume as a value between 0 and 1
  6492. setVolume: function( val ) {
  6493. if ( !val && val !== 0 ) {
  6494. return;
  6495. }
  6496. // Normalize in case outside range of expected values
  6497. if ( val < 0 ) {
  6498. val = -val;
  6499. }
  6500. if ( val > 1 ) {
  6501. val %= 1;
  6502. }
  6503. // HTML video expects to be 0.0 -> 1.0, Vimeo expects 0-100
  6504. this.volume = this.previousVolume = val;
  6505. this.swfObj.api_setVolume( val*100 );
  6506. this.evtHolder.dispatchEvent( "volumechange" );
  6507. },
  6508. // Seeks the video
  6509. setCurrentTime: function ( time ) {
  6510. if ( !time && time !== 0 ) {
  6511. return;
  6512. }
  6513. this.currentTime = this.previousCurrentTime = time;
  6514. this.ended = time >= this.duration;
  6515. this.swfObj.api_seekTo( time );
  6516. // Fire events for seeking and time change
  6517. this.evtHolder.dispatchEvent( "seeked" );
  6518. this.evtHolder.dispatchEvent( "timeupdate" );
  6519. },
  6520. // Play the video
  6521. play: function() {
  6522. // In case someone is cheeky enough to try this before loaded
  6523. if ( !this.swfObj ) {
  6524. this.addEventListener( "load", this.play );
  6525. return;
  6526. }
  6527. if ( !this.played ) {
  6528. this.played = 1;
  6529. this.startTimeUpdater();
  6530. this.evtHolder.dispatchEvent( "loadstart" );
  6531. }
  6532. this.evtHolder.dispatchEvent( "play" );
  6533. this.swfObj.api_play();
  6534. },
  6535. // Pause the video
  6536. pause: function() {
  6537. // In case someone is cheeky enough to try this before loaded
  6538. if ( !this.swfObj ) {
  6539. this.addEventListener( "load", this.pause );
  6540. return;
  6541. }
  6542. this.swfObj.api_pause();
  6543. },
  6544. // Toggle video muting
  6545. // Unmuting will leave it at the old value
  6546. mute: function() {
  6547. // In case someone is cheeky enough to try this before loaded
  6548. if ( !this.swfObj ) {
  6549. this.addEventListener( "load", this.mute );
  6550. return;
  6551. }
  6552. if ( !this.muted() ) {
  6553. this.oldVol = this.volume;
  6554. if ( this.paused ) {
  6555. this.setVolume( 0 );
  6556. } else {
  6557. this.volume = 0;
  6558. }
  6559. } else {
  6560. if ( this.paused ) {
  6561. this.setVolume( this.oldVol );
  6562. } else {
  6563. this.volume = this.oldVol;
  6564. }
  6565. }
  6566. },
  6567. muted: function() {
  6568. return this.volume === 0;
  6569. },
  6570. // Force loading by playing the player. Pause afterwards
  6571. load: function() {
  6572. // In case someone is cheeky enough to try this before loaded
  6573. if ( !this.swfObj ) {
  6574. this.addEventListener( "load", this.load );
  6575. return;
  6576. }
  6577. this.play();
  6578. this.pause();
  6579. },
  6580. unload: function() {
  6581. // In case someone is cheeky enough to try this before loaded
  6582. if ( !this.swfObj ) {
  6583. this.addEventListener( "load", this.unload );
  6584. return;
  6585. }
  6586. this.pause();
  6587. this.swfObj.api_unload();
  6588. this.evtHolder.dispatchEvent( "abort" );
  6589. this.evtHolder.dispatchEvent( "emptied" );
  6590. },
  6591. // Hook an event listener for the player event into internal event system
  6592. // Stick to HTML conventions of add event listener and keep lowercase, without prependinng "on"
  6593. addEventListener: function( evt, fn ) {
  6594. var playerEvt,
  6595. that = this;
  6596. // In case event object is passed in
  6597. evt = evt.type || evt.toLowerCase();
  6598. // If it's an HTML media event supported by player, map
  6599. if ( evt === "seeked" ) {
  6600. playerEvt = "onSeek";
  6601. } else if ( evt === "timeupdate" ) {
  6602. playerEvt = "onProgress";
  6603. } else if ( evt === "progress" ) {
  6604. playerEvt = "onLoading";
  6605. } else if ( evt === "ended" ) {
  6606. playerEvt = "onFinish";
  6607. } else if ( evt === "playing" ) {
  6608. playerEvt = "onPlay";
  6609. } else if ( evt === "pause" ) {
  6610. // Direct mapping, CamelCase the event name as vimeo API expects
  6611. playerEvt = "on"+evt[0].toUpperCase() + evt.substr(1);
  6612. }
  6613. // Vimeo only stores 1 callback per event
  6614. // Have vimeo call internal collection of callbacks
  6615. this.evtHolder.addEventListener( evt, fn, false );
  6616. // Link manual event structure with Vimeo's if not already
  6617. if( playerEvt && this.evtHolder.getEventListeners( evt ).length === 1 ) {
  6618. // Setup global functions on Popcorn.vimeo to sync player events to an internal collection
  6619. // Some events expect 2 args, some only one (the player id)
  6620. if ( playerEvt === "onSeek" || playerEvt === "onProgress" || playerEvt === "onLoading" ) {
  6621. Popcorn.vimeo[playerEvt] = function( arg1, arg2 ) {
  6622. var player = registry[arg2];
  6623. player.evtHolder.dispatchEvent( evt, arg1 );
  6624. };
  6625. } else {
  6626. Popcorn.vimeo[playerEvt] = function( arg1 ) {
  6627. var player = registry[arg1];
  6628. player.evtHolder.dispatchEvent( evt );
  6629. };
  6630. }
  6631. this.swfObj.api_addEventListener( playerEvt, "Popcorn.vimeo."+playerEvt );
  6632. }
  6633. },
  6634. removeEventListener: function( evtName, fn ) {
  6635. return this.evtHolder.removeEventListener( evtName, fn );
  6636. },
  6637. dispatchEvent: function( evtName ) {
  6638. return this.evtHolder.dispatchEvent( evtName );
  6639. },
  6640. getBoundingClientRect: function() {
  6641. return this._target.getBoundingClientRect();
  6642. },
  6643. startTimeUpdater: function() {
  6644. var self = this,
  6645. seeked = 0;
  6646. if ( abs( this.currentTime - this.previousCurrentTime ) > timeCheckInterval ) {
  6647. // Has programatically set the currentTime
  6648. this.setCurrentTime( this.currentTime );
  6649. seeked = 1;
  6650. } else {
  6651. this.previousCurrentTime = this.currentTime;
  6652. }
  6653. if ( this.volume !== this.previousVolume ) {
  6654. this.setVolume( this.volume );
  6655. }
  6656. if ( !self.paused || seeked ) {
  6657. this.dispatchEvent( 'timeupdate' );
  6658. }
  6659. if( !self.ended ) {
  6660. setTimeout( function() {
  6661. self.startTimeUpdater.call(self);
  6662. }, timeupdateInterval);
  6663. }
  6664. }
  6665. });
  6666. })( Popcorn, window );
  6667. // Popcorn Youtube Player Wrapper
  6668. var onYouTubePlayerReady;
  6669. ( function( Popcorn ) {
  6670. /**
  6671. * Youtube wrapper for popcorn.
  6672. * This plug-in adds capability for Popcorn.js to deal with Youtube
  6673. * videos. This plug-in also doesn't use Popcorn's plugin() API and
  6674. * instead hacks directly into Popcorn's core.
  6675. *
  6676. * To use this plug-in, onYouTubePlayerReady() event handler needs to be
  6677. * called by the Youtube video player, before videos can be registered.
  6678. * Once videos are registered, calls to them can be made the same way as
  6679. * regular Popcorn objects. Also note that enablejsapi=1 needs to be added
  6680. * to the embed code, in order for Youtube's JavaScript API to work.
  6681. *
  6682. * Note that there are a few methods, properties and events that are not
  6683. * supported. See the bottom of this plug-in for a complete list.
  6684. */
  6685. // Intended
  6686. var undef;
  6687. // Config parameters
  6688. // 33 ms per update is suitable for 30 fps
  6689. // 0.05 sec tolerance between old and new times to determine if currentTime has been set programatically
  6690. // 250 ms progress interval as specified by WHATWG
  6691. var timeupdateInterval = 33,
  6692. timeCheckInterval = 0.5,
  6693. progressInterval = 250;
  6694. // Ready State Constants
  6695. var READY_STATE_HAVE_NOTHING = 0,
  6696. READY_STATE_HAVE_METADATA = 1,
  6697. READY_STATE_HAVE_CURRENT_DATA = 2,
  6698. READY_STATE_HAVE_FUTURE_DATA = 3,
  6699. READY_STATE_HAVE_ENOUGH_DATA = 4;
  6700. // Youtube State Constants
  6701. var YOUTUBE_STATE_UNSTARTED = -1,
  6702. YOUTUBE_STATE_ENDED = 0,
  6703. YOUTUBE_STATE_PLAYING = 1,
  6704. YOUTUBE_STATE_PAUSED = 2,
  6705. YOUTUBE_STATE_BUFFERING = 3,
  6706. YOUTUBE_STATE_CUED = 5;
  6707. var urlRegex = /^.*[\/=](.{11})/;
  6708. // Collection of all Youtube players
  6709. var registry = {},
  6710. loadedPlayers = {};
  6711. var abs = Math.abs;
  6712. Popcorn.getScript( "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js" );
  6713. // Extract the id from a web url
  6714. function extractIdFromUrl( url ) {
  6715. if ( !url ) {
  6716. return;
  6717. }
  6718. var matches = urlRegex.exec( url );
  6719. // Return id, which comes after first equals sign
  6720. return matches ? matches[1] : "";
  6721. }
  6722. // Extract the id from a player url
  6723. function extractIdFromUri( url ) {
  6724. if ( !url ) {
  6725. return;
  6726. }
  6727. var matches = urlRegex.exec( url );
  6728. // Return id, which comes after first equals sign
  6729. return matches ? matches[1] : "";
  6730. }
  6731. function getPlayerAddress( vidId, playerId ) {
  6732. if( !vidId ) {
  6733. return;
  6734. }
  6735. return "http://www.youtube.com/e/" + id;
  6736. }
  6737. function makeSWF( url, container ) {
  6738. var bounds,
  6739. params,
  6740. flashvars,
  6741. attributes,
  6742. self = this;
  6743. if ( !window.swfobject ) {
  6744. setTimeout( function() {
  6745. makeSWF.call( self, url, container );
  6746. }, 1 );
  6747. return;
  6748. }
  6749. bounds = container.getBoundingClientRect();
  6750. // Determine width/height/etc based on container
  6751. this.width = container.style.width || 460;
  6752. this.height = container.style.height || 350;
  6753. // Just in case we got the attributes as strings. We'll need to do math with these later
  6754. this.width = parseFloat(this.width);
  6755. this.height = parseFloat(this.height);
  6756. // For calculating position relative to video (like subtitles)
  6757. this.offsetWidth = this.width;
  6758. this.offsetHeight = this.height;
  6759. this.offsetLeft = bounds.left;
  6760. this.offsetTop = bounds.top;
  6761. this.offsetParent = container.offsetParent;
  6762. flashvars = {
  6763. playerapiid: this.playerId,
  6764. controls: this.controls,
  6765. iv_load_policy: this.iv_load_policy
  6766. };
  6767. params = {
  6768. allowscriptaccess: 'always',
  6769. allowfullscreen: 'true',
  6770. // This is so we can overlay html on top of Flash
  6771. wmode: 'transparent'
  6772. };
  6773. attributes = {
  6774. id: this.playerId
  6775. };
  6776. swfobject.embedSWF( "http://www.youtube.com/e/" + this.vidId +"?enablejsapi=1&playerapiid=" + this.playerId + "&version=3",
  6777. this.playerId, this.width, this.height, "8", null, flashvars, params, attributes );
  6778. }
  6779. // Called when a player is loaded
  6780. // Playerid must match the element id
  6781. onYouTubePlayerReady = function ( playerId ) {
  6782. var vid = registry[playerId];
  6783. loadedPlayers[playerId] = 1;
  6784. // Video hadn't loaded yet when ctor was called
  6785. vid.video = document.getElementById( playerId );
  6786. vid.duration = vid.video.getDuration();
  6787. // Issue load event
  6788. vid.dispatchEvent( 'load' );
  6789. vid.dispatchEvent( "durationchange" );
  6790. };
  6791. Popcorn.youtube = function( elementId, url, options ) {
  6792. return new Popcorn.youtube.init( elementId, url, options );
  6793. };
  6794. Popcorn.youtube.init = function( elementId, url, options ) {
  6795. if ( !elementId ) {
  6796. throw "Element id is invalid.";
  6797. } else if ( /file/.test( location.protocol ) ) {
  6798. throw "This must be run from a web server.";
  6799. }
  6800. options = options || {};
  6801. var self = this;
  6802. this.playerId = elementId + Popcorn.guid();
  6803. this.readyState = READY_STATE_HAVE_NOTHING;
  6804. this._eventListeners = {};
  6805. this.loadStarted = false;
  6806. this.loadedData = false;
  6807. this.fullyLoaded = false;
  6808. this.paused = false;
  6809. // If supplied as number, append 'px' on end
  6810. // If suppliied as '###' or '###px', convert to number and append 'px' back on end
  6811. options.width = options.width && (+options.width)+"px";
  6812. options.height = options.height && (+options.height)+"px";
  6813. // show controls on video. Integer value - 1 is for show, 0 is for hide
  6814. this.controls = +options.controls === 0 || +options.controls === 1 ? options.controls : 1;
  6815. // show video annotations, 1 is show, 3 is hide
  6816. this.iv_load_policy = +options.annotations === 1 || +options.annotations === 3 ? options.annotations : 1;
  6817. this._target = document.getElementById( elementId );
  6818. this._container = document.createElement( "div" );
  6819. this._container.id = this.playerId;
  6820. this._container.style.height = this._target.style.height = options.height || this._target.style.height || "350px";
  6821. this._container.style.width = this._target.style.width = options.width || this._target.style.width || "460px";
  6822. this._target.appendChild( this._container );
  6823. this.offsetHeight = +this._target.offsetHeight;
  6824. this.offsetWidth = +this._target.offsetWidth;
  6825. this.currentTime = this.previousCurrentTime = 0;
  6826. this.volume = this.previousVolume = this.preMuteVol = 1;
  6827. this.duration = 0;
  6828. this.vidId = extractIdFromUrl( url ) || extractIdFromUri( url );
  6829. if ( !this.vidId ) {
  6830. throw "Could not find video id";
  6831. }
  6832. this.addEventListener( "load", function() {
  6833. // For calculating position relative to video (like subtitles)
  6834. this.offsetWidth = this.video.offsetWidth;
  6835. this.offsetHeight = this.video.offsetHeight;
  6836. this.offsetParent = this.video.offsetParent;
  6837. // Set up stuff that requires the API to be loaded
  6838. this.registerYoutubeEventHandlers();
  6839. this.registerInternalEventHandlers();
  6840. });
  6841. (function() {
  6842. var hasBeenCalled = 0;
  6843. self.addEventListener( "playing", function() {
  6844. if (hasBeenCalled) {
  6845. return;
  6846. }
  6847. hasBeenCalled = 1;
  6848. self.duration = self.video.getDuration();
  6849. self.dispatchEvent( "durationchange" );
  6850. });
  6851. })();
  6852. if ( loadedPlayers[this.playerId] ) {
  6853. this.video = registry[this.playerId].video;
  6854. this.vidId = this.vidId || extractIdFromUrl( this._container.getAttribute( "src" ) ) || extractIdFromUri( this._container.getAttribute( "src" ) );
  6855. if (this.vidId !== registry[this.playerId].vidId ) {
  6856. this.video.cueVideoById( this.vidId );
  6857. } else {
  6858. // Same video, new ctor. Force a seek to the beginning
  6859. this.previousCurrentTime = 1;
  6860. }
  6861. this.dispatchEvent( 'load' );
  6862. } else if ( this._container ) {
  6863. makeSWF.call( this, url, this._container );
  6864. } else {
  6865. // Container not yet loaded, get it on DOMDontentLoad
  6866. document.addEventListener( "DOMContentLoaded", function() {
  6867. self._container = document.getElementById( elementId );
  6868. if ( !self._container ) {
  6869. throw "Could not find container!";
  6870. }
  6871. makeSWF.call( self, url, self._container );
  6872. }, false);
  6873. }
  6874. registry[this.playerId] = this;
  6875. };
  6876. // end Popcorn.youtube.init
  6877. Popcorn.extend( Popcorn.youtube.init.prototype, {
  6878. // For internal use only.
  6879. // Register handlers to YouTube events.
  6880. registerYoutubeEventHandlers: function() {
  6881. var youcorn = this,
  6882. stateChangeHandler = 'Popcorn.youtube.stateChangeEventHandler',
  6883. errorHandler = 'Popcorn.youtube.errorEventHandler';
  6884. this.video.addEventListener( 'onStateChange', stateChangeHandler );
  6885. this.video.addEventListener( 'onError', errorHandler );
  6886. /**
  6887. * Since Flash can only call named functions, they are declared
  6888. * separately here.
  6889. */
  6890. Popcorn.youtube.stateChangeEventHandler = function( state ) {
  6891. // In case ctor has been called many times for many ctors
  6892. // Only use latest ctor call for each player id
  6893. var self = registry[youcorn.playerId];
  6894. if ( state === YOUTUBE_STATE_UNSTARTED ) {
  6895. self.readyState = READY_STATE_HAVE_METADATA;
  6896. self.dispatchEvent( 'loadedmetadata' );
  6897. } else if ( state === YOUTUBE_STATE_ENDED ) {
  6898. self.dispatchEvent( 'ended' );
  6899. } else if ( state === YOUTUBE_STATE_PLAYING ) {
  6900. // Being able to play means current data is loaded.
  6901. if ( !this.loadedData ) {
  6902. this.loadedData = true;
  6903. self.dispatchEvent( 'loadeddata' );
  6904. }
  6905. self.readyState = READY_STATE_HAVE_CURRENT_DATA;
  6906. self.dispatchEvent( 'playing' );
  6907. } else if ( state === YOUTUBE_STATE_PAUSED ) {
  6908. self.dispatchEvent( 'pause' );
  6909. } else if ( state === YOUTUBE_STATE_BUFFERING ) {
  6910. self.dispatchEvent( 'waiting' );
  6911. } else if ( state === YOUTUBE_STATE_CUED ) {
  6912. // not handled
  6913. }
  6914. };
  6915. Popcorn.youtube.errorEventHandler = function( state ) {
  6916. youcorn.dispatchEvent( 'error' );
  6917. };
  6918. },
  6919. // For internal use only.
  6920. // Start current time and loading progress syncing intervals.
  6921. registerInternalEventHandlers: function() {
  6922. this.addEventListener( 'playing', function() {
  6923. this.startTimeUpdater();
  6924. });
  6925. this.addEventListener( 'loadedmetadata', function() {
  6926. this.startProgressUpdater();
  6927. });
  6928. },
  6929. play: function() {
  6930. // In case called before video is loaded, defer acting
  6931. if ( !loadedPlayers[this.playerId] ) {
  6932. this.addEventListener( "load", function() {
  6933. this.play();
  6934. });
  6935. return;
  6936. }
  6937. this.dispatchEvent( 'play' );
  6938. this.video.playVideo();
  6939. },
  6940. pause: function() {
  6941. // In case called before video is loaded, defer acting
  6942. if ( !loadedPlayers[this.playerId] ) {
  6943. this.addEventListener( "load", this.pause );
  6944. return;
  6945. }
  6946. this.video.pauseVideo();
  6947. // pause event is raised by Youtube.
  6948. },
  6949. load: function() {
  6950. // In case called before video is loaded, defer acting
  6951. if ( !loadedPlayers[this.playerId] ) {
  6952. this.addEventListener( "load", function() {
  6953. this.load();
  6954. });
  6955. return;
  6956. }
  6957. this.video.playVideo();
  6958. this.video.pauseVideo();
  6959. },
  6960. seekTo: function( time ) {
  6961. var playing = this.video.getPlayerState() == YOUTUBE_STATE_PLAYING;
  6962. this.video.seekTo( time, true );
  6963. // Prevent Youtube's behaviour to start playing video after seeking.
  6964. if ( !playing ) {
  6965. this.video.paused = true;
  6966. this.video.pauseVideo();
  6967. } else {
  6968. this.video.paused = false;
  6969. }
  6970. // Data need to be loaded again.
  6971. if ( !this.fullyLoaded ) {
  6972. this.loadedData = false;
  6973. }
  6974. // Raise event.
  6975. this.dispatchEvent( 'seeked' );
  6976. },
  6977. // Mute is toggleable
  6978. mute: function() {
  6979. // In case called before video is loaded, defer acting
  6980. if ( !loadedPlayers[this.playerId] ) {
  6981. this.addEventListener( "load", this.mute );
  6982. return;
  6983. }
  6984. if ( this.volume !== 0 ) {
  6985. this.preMuteVol = this.volume;
  6986. this.setVolume( 0 );
  6987. } else {
  6988. this.setVolume( this.preMuteVol );
  6989. }
  6990. },
  6991. // Expects beteween 0 and 1
  6992. setVolume: function( vol ) {
  6993. this.volume = this.previousVolume = vol;
  6994. this.video.setVolume( vol * 100 );
  6995. this.dispatchEvent( 'volumechange' );
  6996. },
  6997. addEventListener: function( evt, func ) {
  6998. var evtName = evt.type || evt;
  6999. if ( !this._eventListeners[evtName] ) {
  7000. this._eventListeners[evtName] = [];
  7001. }
  7002. this._eventListeners[evtName].push( func );
  7003. },
  7004. /**
  7005. * Notify event listeners about an event.
  7006. */
  7007. dispatchEvent: function( name ) {
  7008. var evtName = name.type || name;
  7009. if ( !this._eventListeners[evtName] ) {
  7010. return;
  7011. }
  7012. var self = this;
  7013. Popcorn.forEach( this._eventListeners[evtName], function( evt ) {
  7014. evt.call( self, null );
  7015. });
  7016. },
  7017. /* Unsupported methods. */
  7018. defaultPlaybackRate: function( arg ) {
  7019. },
  7020. playbackRate: function( arg ) {
  7021. },
  7022. getBoundingClientRect: function() {
  7023. var b,
  7024. self = this;
  7025. if ( this.video ) {
  7026. b = this.video.getBoundingClientRect();
  7027. return {
  7028. bottom: b.bottom,
  7029. left: b.left,
  7030. right: b.right,
  7031. top: b.top,
  7032. // These not guaranteed to be in there
  7033. width: b.width || ( b.right - b.left ),
  7034. height: b.height || ( b.bottom - b.top )
  7035. };
  7036. } else {
  7037. b = self._container.getBoundingClientRect();
  7038. // Update bottom, right for expected values once the container loads
  7039. return {
  7040. left: b.left,
  7041. top: b.top,
  7042. width: self._target.offsetWidth,
  7043. height: self._target.offsetHeight,
  7044. bottom: b.top + self._target.offsetWidth,
  7045. right: b.left + self._target.offsetHeight
  7046. };
  7047. }
  7048. },
  7049. startTimeUpdater: function() {
  7050. var state = typeof this.video.getPlayerState != "function" ? this.readyState : this.video.getPlayerState(),
  7051. self = this,
  7052. seeked = 0;
  7053. if ( abs( this.currentTime - this.previousCurrentTime ) > timeCheckInterval ) {
  7054. // Has programatically set the currentTime
  7055. this.previousCurrentTime = this.currentTime - timeCheckInterval;
  7056. this.seekTo( this.currentTime );
  7057. seeked = 1;
  7058. } else {
  7059. this.previousCurrentTime = this.currentTime;
  7060. this.currentTime = typeof this.video.getCurrentTime != "function" ? this.currentTime : this.video.getCurrentTime();
  7061. }
  7062. if ( this.volume !== this.previousVolume ) {
  7063. this.setVolume( this.volume );
  7064. }
  7065. if ( state !== YOUTUBE_STATE_ENDED && state !== YOUTUBE_STATE_PAUSED || seeked ) {
  7066. this.dispatchEvent( 'timeupdate' );
  7067. }
  7068. if( state !== YOUTUBE_STATE_ENDED ) {
  7069. setTimeout( function() {
  7070. self.startTimeUpdater.call(self);
  7071. }, timeupdateInterval);
  7072. }
  7073. },
  7074. startProgressUpdater: function() {
  7075. var bytesLoaded = this.video.getVideoBytesLoaded(),
  7076. bytesToLoad = this.video.getVideoBytesTotal(),
  7077. self = this;
  7078. // do nothing if size is not yet determined
  7079. if ( bytesToLoad === 0 ) {
  7080. return;
  7081. }
  7082. // raise an event if load has just started
  7083. if ( !this.loadStarted ) {
  7084. this.loadStarted = true;
  7085. this.dispatchEvent( 'loadstart' );
  7086. }
  7087. // fully loaded
  7088. if ( bytesLoaded >= bytesToLoad ) {
  7089. this.fullyLoaded = true;
  7090. this.readyState = READY_STATE_HAVE_ENOUGH_DATA;
  7091. this.dispatchEvent( 'canplaythrough' );
  7092. return;
  7093. }
  7094. this.dispatchEvent( 'progress' );
  7095. setTimeout( function() {
  7096. self.startProgressUpdater.call( self );
  7097. }, progressInterval);
  7098. }
  7099. }); // end Popcorn.extend
  7100. /* Unsupported properties and events. */
  7101. /**
  7102. * Unsupported events are:
  7103. * * suspend
  7104. * * abort
  7105. * * emptied
  7106. * * stalled
  7107. * * canplay
  7108. * * seeking
  7109. * * ratechange
  7110. */
  7111. })( Popcorn );
  7112. /*!
  7113. * Popcorn.sequence
  7114. *
  7115. * Copyright 2011, Rick Waldron
  7116. * Licensed under MIT license.
  7117. *
  7118. */
  7119. /* jslint forin: true, maxerr: 50, indent: 4, es5: true */
  7120. /* global Popcorn: true */
  7121. // Requires Popcorn.js
  7122. (function( global, Popcorn ) {
  7123. // TODO: as support increases, migrate to element.dataset
  7124. var doc = global.document,
  7125. location = global.location,
  7126. rprotocol = /:\/\//,
  7127. // TODO: better solution to this sucky stop-gap
  7128. lochref = location.href.replace( location.href.split("/").slice(-1)[0], "" ),
  7129. // privately held
  7130. range = function(start, stop, step) {
  7131. start = start || 0;
  7132. stop = ( stop || start || 0 ) + 1;
  7133. step = step || 1;
  7134. var len = Math.ceil((stop - start) / step) || 0,
  7135. idx = 0,
  7136. range = [];
  7137. range.length = len;
  7138. while (idx < len) {
  7139. range[idx++] = start;
  7140. start += step;
  7141. }
  7142. return range;
  7143. };
  7144. Popcorn.sequence = function( parent, list ) {
  7145. return new Popcorn.sequence.init( parent, list );
  7146. };
  7147. Popcorn.sequence.init = function( parent, list ) {
  7148. // Video element
  7149. this.parent = doc.getElementById( parent );
  7150. // Store ref to a special ID
  7151. this.seqId = Popcorn.guid( "__sequenced" );
  7152. // List of HTMLVideoElements
  7153. this.queue = [];
  7154. // List of Popcorn objects
  7155. this.playlist = [];
  7156. // Lists of in/out points
  7157. this.inOuts = {
  7158. // Stores the video in/out times for each video in sequence
  7159. ofVideos: [],
  7160. // Stores the clip in/out times for each clip in sequences
  7161. ofClips: []
  7162. };
  7163. // Store first video dimensions
  7164. this.dims = {
  7165. width: 0, //this.video.videoWidth,
  7166. height: 0 //this.video.videoHeight
  7167. };
  7168. this.active = 0;
  7169. this.cycling = false;
  7170. this.playing = false;
  7171. this.times = {
  7172. last: 0
  7173. };
  7174. // Store event pointers and queues
  7175. this.events = {
  7176. };
  7177. var self = this,
  7178. clipOffset = 0;
  7179. // Create `video` elements
  7180. Popcorn.forEach( list, function( media, idx ) {
  7181. var video = doc.createElement( "video" );
  7182. video.preload = "auto";
  7183. // Setup newly created video element
  7184. video.controls = true;
  7185. // If the first, show it, if the after, hide it
  7186. video.style.display = ( idx && "none" ) || "" ;
  7187. // Seta registered sequence id
  7188. video.id = self.seqId + "-" + idx ;
  7189. // Push this video into the sequence queue
  7190. self.queue.push( video );
  7191. var //satisfy lint
  7192. mIn = media["in"],
  7193. mOut = media["out"];
  7194. // Push the in/out points into sequence ioVideos
  7195. self.inOuts.ofVideos.push({
  7196. "in": ( mIn !== undefined && mIn ) || 1,
  7197. "out": ( mOut !== undefined && mOut ) || 0
  7198. });
  7199. self.inOuts.ofVideos[ idx ]["out"] = self.inOuts.ofVideos[ idx ]["out"] || self.inOuts.ofVideos[ idx ]["in"] + 2;
  7200. // Set the sources
  7201. video.src = !rprotocol.test( media.src ) ? lochref + media.src : media.src;
  7202. // Set some squence specific data vars
  7203. video.setAttribute("data-sequence-owner", parent );
  7204. video.setAttribute("data-sequence-guid", self.seqId );
  7205. video.setAttribute("data-sequence-id", idx );
  7206. video.setAttribute("data-sequence-clip", [ self.inOuts.ofVideos[ idx ]["in"], self.inOuts.ofVideos[ idx ]["out"] ].join(":") );
  7207. // Append the video to the parent element
  7208. self.parent.appendChild( video );
  7209. self.playlist.push( Popcorn("#" + video.id ) );
  7210. });
  7211. self.inOuts.ofVideos.forEach(function( obj ) {
  7212. var clipDuration = obj["out"] - obj["in"],
  7213. offs = {
  7214. "in": clipOffset,
  7215. "out": clipOffset + clipDuration
  7216. };
  7217. self.inOuts.ofClips.push( offs );
  7218. clipOffset = offs["out"] + 1;
  7219. });
  7220. Popcorn.forEach( this.queue, function( media, idx ) {
  7221. function canPlayThrough( event ) {
  7222. // If this is idx zero, use it as dimension for all
  7223. if ( !idx ) {
  7224. self.dims.width = media.videoWidth;
  7225. self.dims.height = media.videoHeight;
  7226. }
  7227. media.currentTime = self.inOuts.ofVideos[ idx ]["in"] - 0.5;
  7228. media.removeEventListener( "canplaythrough", canPlayThrough, false );
  7229. return true;
  7230. }
  7231. // Hook up event listeners for managing special playback
  7232. media.addEventListener( "canplaythrough", canPlayThrough, false );
  7233. // TODO: consolidate & DRY
  7234. media.addEventListener( "play", function( event ) {
  7235. self.playing = true;
  7236. }, false );
  7237. media.addEventListener( "pause", function( event ) {
  7238. self.playing = false;
  7239. }, false );
  7240. media.addEventListener( "timeupdate", function( event ) {
  7241. var target = event.srcElement || event.target,
  7242. seqIdx = +( (target.dataset && target.dataset.sequenceId) || target.getAttribute("data-sequence-id") ),
  7243. floor = Math.floor( media.currentTime );
  7244. if ( self.times.last !== floor &&
  7245. seqIdx === self.active ) {
  7246. self.times.last = floor;
  7247. if ( floor === self.inOuts.ofVideos[ seqIdx ]["out"] ) {
  7248. Popcorn.sequence.cycle.call( self, seqIdx );
  7249. }
  7250. }
  7251. }, false );
  7252. });
  7253. return this;
  7254. };
  7255. Popcorn.sequence.init.prototype = Popcorn.sequence.prototype;
  7256. //
  7257. Popcorn.sequence.cycle = function( idx ) {
  7258. if ( !this.queue ) {
  7259. Popcorn.error("Popcorn.sequence.cycle is not a public method");
  7260. }
  7261. var // Localize references
  7262. queue = this.queue,
  7263. ioVideos = this.inOuts.ofVideos,
  7264. current = queue[ idx ],
  7265. nextIdx = 0,
  7266. next, clip;
  7267. var // Popcorn instances
  7268. $popnext,
  7269. $popprev;
  7270. if ( queue[ idx + 1 ] ) {
  7271. nextIdx = idx + 1;
  7272. }
  7273. // Reset queue
  7274. if ( !queue[ idx + 1 ] ) {
  7275. nextIdx = 0;
  7276. this.playlist[ idx ].pause();
  7277. } else {
  7278. next = queue[ nextIdx ];
  7279. clip = ioVideos[ nextIdx ];
  7280. // Constrain dimentions
  7281. Popcorn.extend( next, {
  7282. width: this.dims.width,
  7283. height: this.dims.height
  7284. });
  7285. $popnext = this.playlist[ nextIdx ];
  7286. $popprev = this.playlist[ idx ];
  7287. // When not resetting to 0
  7288. current.pause();
  7289. this.active = nextIdx;
  7290. this.times.last = clip["in"] - 1;
  7291. // Play the next video in the sequence
  7292. $popnext.currentTime( clip["in"] );
  7293. $popnext[ nextIdx ? "play" : "pause" ]();
  7294. // Trigger custom cycling event hook
  7295. this.trigger( "cycle", {
  7296. position: {
  7297. previous: idx,
  7298. current: nextIdx
  7299. }
  7300. });
  7301. // Set the previous back to it's beginning time
  7302. // $popprev.currentTime( ioVideos[ idx ].in );
  7303. if ( nextIdx ) {
  7304. // Hide the currently ending video
  7305. current.style.display = "none";
  7306. // Show the next video in the sequence
  7307. next.style.display = "";
  7308. }
  7309. this.cycling = false;
  7310. }
  7311. return this;
  7312. };
  7313. var excludes = [ "timeupdate", "play", "pause" ];
  7314. // Sequence object prototype
  7315. Popcorn.extend( Popcorn.sequence.prototype, {
  7316. // Returns Popcorn object from sequence at index
  7317. eq: function( idx ) {
  7318. return this.playlist[ idx ];
  7319. },
  7320. // Remove a sequence from it's playback display container
  7321. remove: function() {
  7322. this.parent.innerHTML = null;
  7323. },
  7324. // Returns Clip object from sequence at index
  7325. clip: function( idx ) {
  7326. return this.inOuts.ofVideos[ idx ];
  7327. },
  7328. // Returns sum duration for all videos in sequence
  7329. duration: function() {
  7330. var ret = 0,
  7331. seq = this.inOuts.ofClips,
  7332. idx = 0;
  7333. for ( ; idx < seq.length; idx++ ) {
  7334. ret += seq[ idx ]["out"] - seq[ idx ]["in"] + 1;
  7335. }
  7336. return ret - 1;
  7337. },
  7338. play: function() {
  7339. this.playlist[ this.active ].play();
  7340. return this;
  7341. },
  7342. // Attach an event to a single point in time
  7343. exec: function ( time, fn ) {
  7344. var index = this.active;
  7345. this.inOuts.ofClips.forEach(function( off, idx ) {
  7346. if ( time >= off["in"] && time <= off["out"] ) {
  7347. index = idx;
  7348. }
  7349. });
  7350. //offsetBy = time - self.inOuts.ofVideos[ index ].in;
  7351. time += this.inOuts.ofVideos[ index ]["in"] - this.inOuts.ofClips[ index ]["in"];
  7352. // Creating a one second track event with an empty end
  7353. Popcorn.addTrackEvent( this.playlist[ index ], {
  7354. start: time - 1,
  7355. end: time,
  7356. _running: false,
  7357. _natives: {
  7358. start: fn || Popcorn.nop,
  7359. end: Popcorn.nop,
  7360. type: "exec"
  7361. }
  7362. });
  7363. return this;
  7364. },
  7365. // Binds event handlers that fire only when all
  7366. // videos in sequence have heard the event
  7367. listen: function( type, callback ) {
  7368. var self = this,
  7369. seq = this.playlist,
  7370. total = seq.length,
  7371. count = 0,
  7372. fnName;
  7373. if ( !callback ) {
  7374. callback = Popcorn.nop;
  7375. }
  7376. // Handling for DOM and Media events
  7377. if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) {
  7378. Popcorn.forEach( seq, function( video ) {
  7379. video.listen( type, function( event ) {
  7380. event.active = self;
  7381. if ( excludes.indexOf( type ) > -1 ) {
  7382. callback.call( video, event );
  7383. } else {
  7384. if ( ++count === total ) {
  7385. callback.call( video, event );
  7386. }
  7387. }
  7388. });
  7389. });
  7390. } else {
  7391. // If no events registered with this name, create a cache
  7392. if ( !this.events[ type ] ) {
  7393. this.events[ type ] = {};
  7394. }
  7395. // Normalize a callback name key
  7396. fnName = callback.name || Popcorn.guid( "__" + type );
  7397. // Store in event cache
  7398. this.events[ type ][ fnName ] = callback;
  7399. }
  7400. // Return the sequence object
  7401. return this;
  7402. },
  7403. unlisten: function( type, name ) {
  7404. // TODO: finish implementation
  7405. },
  7406. trigger: function( type, data ) {
  7407. var self = this;
  7408. // Handling for DOM and Media events
  7409. if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) {
  7410. // find the active video and trigger api events on that video.
  7411. return;
  7412. } else {
  7413. // Only proceed if there are events of this type
  7414. // currently registered on the sequence
  7415. if ( this.events[ type ] ) {
  7416. Popcorn.forEach( this.events[ type ], function( callback, name ) {
  7417. callback.call( self, { type: type }, data );
  7418. });
  7419. }
  7420. }
  7421. return this;
  7422. }
  7423. });
  7424. Popcorn.forEach( Popcorn.manifest, function( obj, plugin ) {
  7425. // Implement passthrough methods to plugins
  7426. Popcorn.sequence.prototype[ plugin ] = function( options ) {
  7427. // console.log( this, options );
  7428. var videos = {}, assignTo = [],
  7429. idx, off, inOuts, inIdx, outIdx, keys, clip, clipInOut, clipRange;
  7430. for ( idx = 0; idx < this.inOuts.ofClips.length; idx++ ) {
  7431. // store reference
  7432. off = this.inOuts.ofClips[ idx ];
  7433. // array to test against
  7434. inOuts = range( off["in"], off["out"] );
  7435. inIdx = inOuts.indexOf( options.start );
  7436. outIdx = inOuts.indexOf( options.end );
  7437. if ( inIdx > -1 ) {
  7438. videos[ idx ] = Popcorn.extend( {}, off, {
  7439. start: inOuts[ inIdx ],
  7440. clipIdx: inIdx
  7441. });
  7442. }
  7443. if ( outIdx > -1 ) {
  7444. videos[ idx ] = Popcorn.extend( {}, off, {
  7445. end: inOuts[ outIdx ],
  7446. clipIdx: outIdx
  7447. });
  7448. }
  7449. }
  7450. keys = Object.keys( videos ).map(function( val ) {
  7451. return +val;
  7452. });
  7453. assignTo = range( keys[ 0 ], keys[ 1 ] );
  7454. //console.log( "PLUGIN CALL MAPS: ", videos, keys, assignTo );
  7455. for ( idx = 0; idx < assignTo.length; idx++ ) {
  7456. var compile = {},
  7457. play = assignTo[ idx ],
  7458. vClip = videos[ play ];
  7459. if ( vClip ) {
  7460. // has instructions
  7461. clip = this.inOuts.ofVideos[ play ];
  7462. clipInOut = vClip.clipIdx;
  7463. clipRange = range( clip["in"], clip["out"] );
  7464. if ( vClip.start ) {
  7465. compile.start = clipRange[ clipInOut ];
  7466. compile.end = clipRange[ clipRange.length - 1 ];
  7467. }
  7468. if ( vClip.end ) {
  7469. compile.start = clipRange[ 0 ];
  7470. compile.end = clipRange[ clipInOut ];
  7471. }
  7472. //compile.start += 0.1;
  7473. //compile.end += 0.9;
  7474. } else {
  7475. compile.start = this.inOuts.ofVideos[ play ]["in"];
  7476. compile.end = this.inOuts.ofVideos[ play ]["out"];
  7477. //compile.start += 0.1;
  7478. //compile.end += 0.9;
  7479. }
  7480. // Handling full clip persistance
  7481. //if ( compile.start === compile.end ) {
  7482. //compile.start -= 0.1;
  7483. //compile.end += 0.9;
  7484. //}
  7485. // Call the plugin on the appropriate Popcorn object in the playlist
  7486. // Merge original options object & compiled (start/end) object into
  7487. // a new fresh object
  7488. this.playlist[ play ][ plugin ](
  7489. Popcorn.extend( {}, options, compile )
  7490. );
  7491. }
  7492. // Return the sequence object
  7493. return this;
  7494. };
  7495. });
  7496. })( this, Popcorn );
  7497. // EFFECT: applyclass
  7498. (function (Popcorn) {
  7499. /**
  7500. * apply css class to jquery selector
  7501. * selector is relative to plugin target's id
  7502. * so .overlay is actually jQuery( "#target .overlay")
  7503. *
  7504. * @param {Object} options
  7505. *
  7506. * Example:
  7507. var p = Popcorn('#video')
  7508. .footnote({
  7509. start: 5, // seconds
  7510. end: 15, // seconds
  7511. text: 'This video made exclusively for drumbeat.org',
  7512. target: 'footnotediv',
  7513. effect: 'applyclass',
  7514. applyclass: 'selector: class'
  7515. })
  7516. *
  7517. */
  7518. var toggleClass = function( event, options ) {
  7519. var idx = 0, len = 0, elements;
  7520. Popcorn.forEach( options.classes, function( key, val ) {
  7521. elements = [];
  7522. if ( key === "parent" ) {
  7523. elements[ 0 ] = document.querySelectorAll("#" + options.target )[ 0 ].parentNode;
  7524. } else {
  7525. elements = document.querySelectorAll("#" + options.target + " " + key );
  7526. }
  7527. for ( idx = 0, len = elements.length; idx < len; idx++ ) {
  7528. elements[ idx ].classList.toggle( val );
  7529. }
  7530. });
  7531. };
  7532. Popcorn.compose( "applyclass", {
  7533. manifest: {
  7534. about: {
  7535. name: "Popcorn applyclass Effect",
  7536. version: "0.1",
  7537. author: "@scottdowne",
  7538. website: "scottdowne.wordpress.com"
  7539. },
  7540. options: {}
  7541. },
  7542. _setup: function( options ) {
  7543. options.classes = {};
  7544. options.applyclass = options.applyclass || "";
  7545. var classes = options.applyclass.replace( /\s/g, "" ).split( "," ),
  7546. item = [],
  7547. idx = 0, len = classes.length;
  7548. for ( ; idx < len; idx++ ) {
  7549. item = classes[ idx ].split( ":" );
  7550. if ( item[ 0 ] ) {
  7551. options.classes[ item[ 0 ] ] = item[ 1 ] || "";
  7552. }
  7553. }
  7554. },
  7555. start: toggleClass,
  7556. end: toggleClass
  7557. });
  7558. })( Popcorn );