PageRenderTime 96ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/popcorn/popcorn-complete.js

https://github.com/lulubulb/popcorn-maker
JavaScript | 9170 lines | 5662 code | 1595 blank | 1913 comment | 967 complexity | e2f4ccbe30d87b2f643028790fb9b320 MD5 | raw file
Possible License(s): Apache-2.0, MIT, BSD-3-Clause
  1. /*
  2. * popcorn.js version bd50431
  3. * http://popcornjs.org
  4. *
  5. * Copyright 2011, Mozilla Foundation
  6. * Licensed under the MIT license
  7. */
  8. (function(global, document) {
  9. // Popcorn.js does not support archaic browsers
  10. if ( !document.addEventListener ) {
  11. global.Popcorn = {
  12. isSupported: false
  13. };
  14. var methods = ( "forEach extend effects error guid sizeOf isArray nop position disable enable destroy " +
  15. "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " +
  16. "timeUpdate plugin removePlugin compose effect parser xhr getJSONP getScript" ).split(/\s+/);
  17. while ( methods.length ) {
  18. global.Popcorn[ methods.shift() ] = function() {};
  19. }
  20. return;
  21. }
  22. var
  23. AP = Array.prototype,
  24. OP = Object.prototype,
  25. forEach = AP.forEach,
  26. slice = AP.slice,
  27. hasOwn = OP.hasOwnProperty,
  28. toString = OP.toString,
  29. // Copy global Popcorn (may not exist)
  30. _Popcorn = global.Popcorn,
  31. // ID string matching
  32. rIdExp = /^(#([\w\-\_\.]+))$/,
  33. // Ready fn cache
  34. readyStack = [],
  35. readyBound = false,
  36. readyFired = false,
  37. // Non-public internal data object
  38. internal = {
  39. events: {
  40. hash: {},
  41. apis: {}
  42. }
  43. },
  44. // Non-public `requestAnimFrame`
  45. // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  46. requestAnimFrame = (function(){
  47. return global.requestAnimationFrame ||
  48. global.webkitRequestAnimationFrame ||
  49. global.mozRequestAnimationFrame ||
  50. global.oRequestAnimationFrame ||
  51. global.msRequestAnimationFrame ||
  52. function( callback, element ) {
  53. global.setTimeout( callback, 16 );
  54. };
  55. }()),
  56. refresh = function( obj ) {
  57. var currentTime = obj.media.currentTime,
  58. animation = obj.options.frameAnimation,
  59. disabled = obj.data.disabled,
  60. tracks = obj.data.trackEvents,
  61. animating = tracks.animating,
  62. start = tracks.startIndex,
  63. registryByName = Popcorn.registryByName,
  64. animIndex = 0,
  65. byStart, natives, type;
  66. start = Math.min( start + 1, tracks.byStart.length - 2 );
  67. while ( start > 0 && tracks.byStart[ start ] ) {
  68. byStart = tracks.byStart[ start ];
  69. natives = byStart._natives;
  70. type = natives && natives.type;
  71. if ( !natives ||
  72. ( !!registryByName[ type ] || !!obj[ type ] ) ) {
  73. if ( ( byStart.start <= currentTime && byStart.end > currentTime ) &&
  74. disabled.indexOf( type ) === -1 ) {
  75. if ( !byStart._running ) {
  76. byStart._running = true;
  77. natives.start.call( obj, null, byStart );
  78. // if the 'frameAnimation' option is used,
  79. // push the current byStart object into the `animating` cue
  80. if ( animation &&
  81. ( byStart && byStart._running && byStart.natives.frame ) ) {
  82. natives.frame.call( obj, null, byStart, currentTime );
  83. }
  84. }
  85. } else if ( byStart._running === true ) {
  86. byStart._running = false;
  87. natives.end.call( obj, null, byStart );
  88. if ( animation && byStart._natives.frame ) {
  89. animIndex = animating.indexOf( byStart );
  90. if ( animIndex >= 0 ) {
  91. animating.splice( animIndex, 1 );
  92. }
  93. }
  94. }
  95. }
  96. start--;
  97. }
  98. },
  99. // Declare constructor
  100. // Returns an instance object.
  101. Popcorn = function( entity, options ) {
  102. // Return new Popcorn object
  103. return new Popcorn.p.init( entity, options || null );
  104. };
  105. // Popcorn API version, automatically inserted via build system.
  106. Popcorn.version = "bd50431";
  107. // Boolean flag allowing a client to determine if Popcorn can be supported
  108. Popcorn.isSupported = true;
  109. // Instance caching
  110. Popcorn.instances = [];
  111. // Declare a shortcut (Popcorn.p) to and a definition of
  112. // the new prototype for our Popcorn constructor
  113. Popcorn.p = Popcorn.prototype = {
  114. init: function( entity, options ) {
  115. var matches;
  116. // Supports Popcorn(function () { /../ })
  117. // Originally proposed by Daniel Brooks
  118. if ( typeof entity === "function" ) {
  119. // If document ready has already fired
  120. if ( document.readyState === "interactive" || document.readyState === "complete" ) {
  121. entity( document, Popcorn );
  122. return;
  123. }
  124. // Add `entity` fn to ready stack
  125. readyStack.push( entity );
  126. // This process should happen once per page load
  127. if ( !readyBound ) {
  128. // set readyBound flag
  129. readyBound = true;
  130. var DOMContentLoaded = function() {
  131. readyFired = true;
  132. // Remove global DOM ready listener
  133. document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
  134. // Execute all ready function in the stack
  135. for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) {
  136. readyStack[ i ].call( document, Popcorn );
  137. }
  138. // GC readyStack
  139. readyStack = null;
  140. };
  141. // Register global DOM ready listener
  142. document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
  143. }
  144. return;
  145. }
  146. // Check if entity is a valid string id
  147. matches = rIdExp.exec( entity );
  148. // Get media element by id or object reference
  149. this.media = matches && matches.length && matches[ 2 ] ?
  150. document.getElementById( matches[ 2 ] ) :
  151. entity;
  152. // Create an audio or video element property reference
  153. this[ ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video" ] = this.media;
  154. // Register new instance
  155. Popcorn.instances.push( this );
  156. this.options = options || {};
  157. this.isDestroyed = false;
  158. this.data = {
  159. // Executed by either timeupdate event or in rAF loop
  160. timeUpdate: Popcorn.nop,
  161. // Allows disabling a plugin per instance
  162. disabled: [],
  163. // Stores DOM event queues by type
  164. events: {},
  165. // Stores Special event hooks data
  166. hooks: {},
  167. // Store track event history data
  168. history: [],
  169. // Stores ad-hoc state related data]
  170. state: {
  171. volume: this.media.volume
  172. },
  173. // Store track event object references by trackId
  174. trackRefs: {},
  175. // Playback track event queues
  176. trackEvents: {
  177. byStart: [{
  178. start: -1,
  179. end: -1
  180. }],
  181. byEnd: [{
  182. start: -1,
  183. end: -1
  184. }],
  185. animating: [],
  186. startIndex: 0,
  187. endIndex: 0,
  188. previousUpdateTime: -1
  189. }
  190. };
  191. // Wrap true ready check
  192. var isReady = function( that ) {
  193. var duration, videoDurationPlus;
  194. if ( that.media.readyState >= 2 ) {
  195. // Adding padding to the front and end of the arrays
  196. // this is so we do not fall off either end
  197. duration = that.media.duration;
  198. // Check for no duration info (NaN)
  199. videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1;
  200. Popcorn.addTrackEvent( that, {
  201. start: videoDurationPlus,
  202. end: videoDurationPlus
  203. });
  204. if ( that.options.frameAnimation ) {
  205. // if Popcorn is created with frameAnimation option set to true,
  206. // requestAnimFrame is used instead of "timeupdate" media event.
  207. // This is for greater frame time accuracy, theoretically up to
  208. // 60 frames per second as opposed to ~4 ( ~every 15-250ms)
  209. that.data.timeUpdate = function () {
  210. Popcorn.timeUpdate( that, {} );
  211. that.trigger( "timeupdate" );
  212. !that.isDestroyed && requestAnimFrame( that.data.timeUpdate );
  213. };
  214. !that.isDestroyed && requestAnimFrame( that.data.timeUpdate );
  215. } else {
  216. that.data.timeUpdate = function( event ) {
  217. Popcorn.timeUpdate( that, event );
  218. };
  219. if ( !that.isDestroyed ) {
  220. that.media.addEventListener( "timeupdate", that.data.timeUpdate, false );
  221. }
  222. }
  223. } else {
  224. global.setTimeout(function() {
  225. isReady( that );
  226. }, 1 );
  227. }
  228. };
  229. isReady( this );
  230. return this;
  231. }
  232. };
  233. // Extend constructor prototype to instance prototype
  234. // Allows chaining methods to instances
  235. Popcorn.p.init.prototype = Popcorn.p;
  236. Popcorn.forEach = function( obj, fn, context ) {
  237. if ( !obj || !fn ) {
  238. return {};
  239. }
  240. context = context || this;
  241. var key, len;
  242. // Use native whenever possible
  243. if ( forEach && obj.forEach === forEach ) {
  244. return obj.forEach( fn, context );
  245. }
  246. if ( toString.call( obj ) === "[object NodeList]" ) {
  247. for ( key = 0, len = obj.length; key < len; key++ ) {
  248. fn.call( context, obj[ key ], key, obj );
  249. }
  250. return obj;
  251. }
  252. for ( key in obj ) {
  253. if ( hasOwn.call( obj, key ) ) {
  254. fn.call( context, obj[ key ], key, obj );
  255. }
  256. }
  257. return obj;
  258. };
  259. Popcorn.extend = function( obj ) {
  260. var dest = obj, src = slice.call( arguments, 1 );
  261. Popcorn.forEach( src, function( copy ) {
  262. for ( var prop in copy ) {
  263. dest[ prop ] = copy[ prop ];
  264. }
  265. });
  266. return dest;
  267. };
  268. // A Few reusable utils, memoized onto Popcorn
  269. Popcorn.extend( Popcorn, {
  270. noConflict: function( deep ) {
  271. if ( deep ) {
  272. global.Popcorn = _Popcorn;
  273. }
  274. return Popcorn;
  275. },
  276. error: function( msg ) {
  277. throw new Error( msg );
  278. },
  279. guid: function( prefix ) {
  280. Popcorn.guid.counter++;
  281. return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter );
  282. },
  283. sizeOf: function( obj ) {
  284. var size = 0;
  285. for ( var prop in obj ) {
  286. size++;
  287. }
  288. return size;
  289. },
  290. isArray: Array.isArray || function( array ) {
  291. return toString.call( array ) === "[object Array]";
  292. },
  293. nop: function() {},
  294. position: function( elem ) {
  295. var clientRect = elem.getBoundingClientRect(),
  296. bounds = {},
  297. doc = elem.ownerDocument,
  298. docElem = document.documentElement,
  299. body = document.body,
  300. clientTop, clientLeft, scrollTop, scrollLeft, top, left;
  301. // Determine correct clientTop/Left
  302. clientTop = docElem.clientTop || body.clientTop || 0;
  303. clientLeft = docElem.clientLeft || body.clientLeft || 0;
  304. // Determine correct scrollTop/Left
  305. scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop );
  306. scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft );
  307. // Temp top/left
  308. top = Math.ceil( clientRect.top + scrollTop - clientTop );
  309. left = Math.ceil( clientRect.left + scrollLeft - clientLeft );
  310. for ( var p in clientRect ) {
  311. bounds[ p ] = Math.round( clientRect[ p ] );
  312. }
  313. return Popcorn.extend({}, bounds, { top: top, left: left });
  314. },
  315. disable: function( instance, plugin ) {
  316. var disabled = instance.data.disabled;
  317. if ( disabled.indexOf( plugin ) === -1 ) {
  318. disabled.push( plugin );
  319. }
  320. refresh( instance );
  321. return instance;
  322. },
  323. enable: function( instance, plugin ) {
  324. var disabled = instance.data.disabled,
  325. index = disabled.indexOf( plugin );
  326. if ( index > -1 ) {
  327. disabled.splice( index, 1 );
  328. }
  329. refresh( instance );
  330. return instance;
  331. },
  332. destroy: function( instance ) {
  333. var events = instance.data.events,
  334. singleEvent, item, fn;
  335. // Iterate through all events and remove them
  336. for ( item in events ) {
  337. singleEvent = events[ item ];
  338. for ( fn in singleEvent ) {
  339. delete singleEvent[ fn ];
  340. }
  341. events[ item ] = null;
  342. }
  343. if ( !instance.isDestroyed ) {
  344. instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false );
  345. instance.isDestroyed = true;
  346. }
  347. }
  348. });
  349. // Memoized GUID Counter
  350. Popcorn.guid.counter = 1;
  351. // Factory to implement getters, setters and controllers
  352. // as Popcorn instance methods. The IIFE will create and return
  353. // an object with defined methods
  354. Popcorn.extend(Popcorn.p, (function() {
  355. var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " +
  356. "autoplay loop controls muted buffered readyState seeking paused played seekable ended",
  357. ret = {};
  358. // Build methods, store in object that is returned and passed to extend
  359. Popcorn.forEach( methods.split( /\s+/g ), function( name ) {
  360. ret[ name ] = function( arg ) {
  361. if ( typeof this.media[ name ] === "function" ) {
  362. // Support for shorthanded play(n)/pause(n) jump to currentTime
  363. // If arg is not null or undefined and called by one of the
  364. // allowed shorthandable methods, then set the currentTime
  365. // Supports time as seconds or SMPTE
  366. if ( arg != null && /play|pause/.test( name ) ) {
  367. this.media.currentTime = Popcorn.util.toSeconds( arg );
  368. }
  369. this.media[ name ]();
  370. return this;
  371. }
  372. if ( arg != null ) {
  373. this.media[ name ] = arg;
  374. return this;
  375. }
  376. return this.media[ name ];
  377. };
  378. });
  379. return ret;
  380. })()
  381. );
  382. Popcorn.forEach( "enable disable".split(" "), function( method ) {
  383. Popcorn.p[ method ] = function( plugin ) {
  384. return Popcorn[ method ]( this, plugin );
  385. };
  386. });
  387. Popcorn.extend(Popcorn.p, {
  388. // Rounded currentTime
  389. roundTime: function() {
  390. return -~this.media.currentTime;
  391. },
  392. // Attach an event to a single point in time
  393. exec: function( time, fn ) {
  394. // Creating a one second track event with an empty end
  395. Popcorn.addTrackEvent( this, {
  396. start: time,
  397. end: time + 1,
  398. _running: false,
  399. _natives: {
  400. start: fn || Popcorn.nop,
  401. end: Popcorn.nop,
  402. type: "exec"
  403. }
  404. });
  405. return this;
  406. },
  407. // Mute the calling media, optionally toggle
  408. mute: function( toggle ) {
  409. var event = toggle == null || toggle === true ? "muted" : "unmuted";
  410. // If `toggle` is explicitly `false`,
  411. // unmute the media and restore the volume level
  412. if ( event === "unmuted" ) {
  413. this.media.muted = false;
  414. this.media.volume = this.data.state.volume;
  415. }
  416. // If `toggle` is either null or undefined,
  417. // save the current volume and mute the media element
  418. if ( event === "muted" ) {
  419. this.data.state.volume = this.media.volume;
  420. this.media.muted = true;
  421. }
  422. // Trigger either muted|unmuted event
  423. this.trigger( event );
  424. return this;
  425. },
  426. // Convenience method, unmute the calling media
  427. unmute: function( toggle ) {
  428. return this.mute( toggle == null ? false : !toggle );
  429. },
  430. // Get the client bounding box of an instance element
  431. position: function() {
  432. return Popcorn.position( this.media );
  433. },
  434. // Toggle a plugin's playback behaviour (on or off) per instance
  435. toggle: function( plugin ) {
  436. return Popcorn[ this.data.disabled.indexOf( plugin ) > -1 ? "enable" : "disable" ]( this, plugin );
  437. },
  438. // Set default values for plugin options objects per instance
  439. defaults: function( plugin, defaults ) {
  440. // If an array of default configurations is provided,
  441. // iterate and apply each to this instance
  442. if ( Popcorn.isArray( plugin ) ) {
  443. Popcorn.forEach( plugin, function( obj ) {
  444. for ( var name in obj ) {
  445. this.defaults( name, obj[ name ] );
  446. }
  447. }, this );
  448. return this;
  449. }
  450. if ( !this.options.defaults ) {
  451. this.options.defaults = {};
  452. }
  453. if ( !this.options.defaults[ plugin ] ) {
  454. this.options.defaults[ plugin ] = {};
  455. }
  456. Popcorn.extend( this.options.defaults[ plugin ], defaults );
  457. return this;
  458. }
  459. });
  460. Popcorn.Events = {
  461. UIEvents: "blur focus focusin focusout load resize scroll unload",
  462. MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",
  463. Events: "loadstart progress suspend emptied stalled play pause " +
  464. "loadedmetadata loadeddata waiting playing canplay canplaythrough " +
  465. "seeking seeked timeupdate ended ratechange durationchange volumechange"
  466. };
  467. Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " +
  468. Popcorn.Events.MouseEvents + " " +
  469. Popcorn.Events.Events;
  470. internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ];
  471. // Privately compile events table at load time
  472. (function( events, data ) {
  473. var apis = internal.events.apiTypes,
  474. eventsList = events.Natives.split( /\s+/g ),
  475. idx = 0, len = eventsList.length, prop;
  476. for( ; idx < len; idx++ ) {
  477. data.hash[ eventsList[idx] ] = true;
  478. }
  479. apis.forEach(function( val, idx ) {
  480. data.apis[ val ] = {};
  481. var apiEvents = events[ val ].split( /\s+/g ),
  482. len = apiEvents.length,
  483. k = 0;
  484. for ( ; k < len; k++ ) {
  485. data.apis[ val ][ apiEvents[ k ] ] = true;
  486. }
  487. });
  488. })( Popcorn.Events, internal.events );
  489. Popcorn.events = {
  490. isNative: function( type ) {
  491. return !!internal.events.hash[ type ];
  492. },
  493. getInterface: function( type ) {
  494. if ( !Popcorn.events.isNative( type ) ) {
  495. return false;
  496. }
  497. var eventApi = internal.events,
  498. apis = eventApi.apiTypes,
  499. apihash = eventApi.apis,
  500. idx = 0, len = apis.length, api, tmp;
  501. for ( ; idx < len; idx++ ) {
  502. tmp = apis[ idx ];
  503. if ( apihash[ tmp ][ type ] ) {
  504. api = tmp;
  505. break;
  506. }
  507. }
  508. return api;
  509. },
  510. // Compile all native events to single array
  511. all: Popcorn.Events.Natives.split( /\s+/g ),
  512. // Defines all Event handling static functions
  513. fn: {
  514. trigger: function( type, data ) {
  515. var eventInterface, evt;
  516. // setup checks for custom event system
  517. if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) {
  518. eventInterface = Popcorn.events.getInterface( type );
  519. if ( eventInterface ) {
  520. evt = document.createEvent( eventInterface );
  521. evt.initEvent( type, true, true, global, 1 );
  522. this.media.dispatchEvent( evt );
  523. return this;
  524. }
  525. // Custom events
  526. Popcorn.forEach( this.data.events[ type ], function( obj, key ) {
  527. obj.call( this, data );
  528. }, this );
  529. }
  530. return this;
  531. },
  532. listen: function( type, fn ) {
  533. var self = this,
  534. hasEvents = true,
  535. eventHook = Popcorn.events.hooks[ type ],
  536. origType = type,
  537. tmp;
  538. if ( !this.data.events[ type ] ) {
  539. this.data.events[ type ] = {};
  540. hasEvents = false;
  541. }
  542. // Check and setup event hooks
  543. if ( eventHook ) {
  544. // Execute hook add method if defined
  545. if ( eventHook.add ) {
  546. eventHook.add.call( this, {}, fn );
  547. }
  548. // Reassign event type to our piggyback event type if defined
  549. if ( eventHook.bind ) {
  550. type = eventHook.bind;
  551. }
  552. // Reassign handler if defined
  553. if ( eventHook.handler ) {
  554. tmp = fn;
  555. fn = function wrapper( event ) {
  556. eventHook.handler.call( self, event, tmp );
  557. };
  558. }
  559. // assume the piggy back event is registered
  560. hasEvents = true;
  561. // Setup event registry entry
  562. if ( !this.data.events[ type ] ) {
  563. this.data.events[ type ] = {};
  564. // Toggle if the previous assumption was untrue
  565. hasEvents = false;
  566. }
  567. }
  568. // Register event and handler
  569. this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn;
  570. // only attach one event of any type
  571. if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) {
  572. this.media.addEventListener( type, function( event ) {
  573. Popcorn.forEach( self.data.events[ type ], function( obj, key ) {
  574. if ( typeof obj === "function" ) {
  575. obj.call( self, event );
  576. }
  577. });
  578. }, false);
  579. }
  580. return this;
  581. },
  582. unlisten: function( type, fn ) {
  583. if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) {
  584. delete this.data.events[ type ][ fn ];
  585. return this;
  586. }
  587. this.data.events[ type ] = null;
  588. return this;
  589. }
  590. },
  591. hooks: {
  592. canplayall: {
  593. bind: "canplaythrough",
  594. add: function( event, callback ) {
  595. var state = false;
  596. if ( this.media.readyState ) {
  597. callback.call( this, event );
  598. state = true;
  599. }
  600. this.data.hooks.canplayall = {
  601. fired: state
  602. };
  603. },
  604. // declare special handling instructions
  605. handler: function canplayall( event, callback ) {
  606. if ( !this.data.hooks.canplayall.fired ) {
  607. // trigger original user callback once
  608. callback.call( this, event );
  609. this.data.hooks.canplayall.fired = true;
  610. }
  611. }
  612. }
  613. }
  614. };
  615. // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances
  616. Popcorn.forEach( [ "trigger", "listen", "unlisten" ], function( key ) {
  617. Popcorn.p[ key ] = Popcorn.events.fn[ key ];
  618. });
  619. // Internal Only - Adds track events to the instance object
  620. Popcorn.addTrackEvent = function( obj, track ) {
  621. // Determine if this track has default options set for it
  622. // If so, apply them to the track object
  623. if ( track && track._natives && track._natives.type &&
  624. ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) {
  625. track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track );
  626. }
  627. if ( track._natives ) {
  628. // Supports user defined track event id
  629. track._id = !track.id ? Popcorn.guid( track._natives.type ) : track.id;
  630. // Push track event ids into the history
  631. obj.data.history.push( track._id );
  632. }
  633. track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate );
  634. track.end = Popcorn.util.toSeconds( track.end, obj.options.framerate );
  635. // Store this definition in an array sorted by times
  636. var byStart = obj.data.trackEvents.byStart,
  637. byEnd = obj.data.trackEvents.byEnd,
  638. startIndex, endIndex,
  639. currentTime;
  640. for ( startIndex = byStart.length - 1; startIndex >= 0; startIndex-- ) {
  641. if ( track.start >= byStart[ startIndex ].start ) {
  642. byStart.splice( startIndex + 1, 0, track );
  643. break;
  644. }
  645. }
  646. for ( endIndex = byEnd.length - 1; endIndex >= 0; endIndex-- ) {
  647. if ( track.end > byEnd[ endIndex ].end ) {
  648. byEnd.splice( endIndex + 1, 0, track );
  649. break;
  650. }
  651. }
  652. // Display track event immediately if it's enabled and current
  653. if ( track._natives &&
  654. ( !!Popcorn.registryByName[ track._natives.type ] || !!obj[ track._natives.type ] ) ) {
  655. currentTime = obj.media.currentTime;
  656. if ( track.end > currentTime &&
  657. track.start <= currentTime &&
  658. obj.data.disabled.indexOf( track._natives.type ) === -1 ) {
  659. track._running = true;
  660. track._natives.start.call( obj, null, track );
  661. if ( obj.options.frameAnimation &&
  662. track._natives.frame ) {
  663. obj.data.trackEvents.animating.push( track );
  664. track._natives.frame.call( obj, null, track, currentTime );
  665. }
  666. }
  667. }
  668. // update startIndex and endIndex
  669. if ( startIndex <= obj.data.trackEvents.startIndex &&
  670. track.start <= obj.data.trackEvents.previousUpdateTime ) {
  671. obj.data.trackEvents.startIndex++;
  672. }
  673. if ( endIndex <= obj.data.trackEvents.endIndex &&
  674. track.end < obj.data.trackEvents.previousUpdateTime ) {
  675. obj.data.trackEvents.endIndex++;
  676. }
  677. this.timeUpdate( obj, null, true );
  678. // Store references to user added trackevents in ref table
  679. if ( track._id ) {
  680. Popcorn.addTrackEvent.ref( obj, track );
  681. }
  682. };
  683. // Internal Only - Adds track event references to the instance object's trackRefs hash table
  684. Popcorn.addTrackEvent.ref = function( obj, track ) {
  685. obj.data.trackRefs[ track._id ] = track;
  686. return obj;
  687. };
  688. Popcorn.removeTrackEvent = function( obj, trackId ) {
  689. var historyLen = obj.data.history.length,
  690. indexWasAt = 0,
  691. byStart = [],
  692. byEnd = [],
  693. animating = [],
  694. history = [];
  695. Popcorn.forEach( obj.data.trackEvents.byStart, function( o, i, context ) {
  696. // Preserve the original start/end trackEvents
  697. if ( !o._id ) {
  698. byStart.push( obj.data.trackEvents.byStart[i] );
  699. byEnd.push( obj.data.trackEvents.byEnd[i] );
  700. }
  701. // Filter for user track events (vs system track events)
  702. if ( o._id ) {
  703. // Filter for the trackevent to remove
  704. if ( o._id !== trackId ) {
  705. byStart.push( obj.data.trackEvents.byStart[i] );
  706. byEnd.push( obj.data.trackEvents.byEnd[i] );
  707. }
  708. // Capture the position of the track being removed.
  709. if ( o._id === trackId ) {
  710. indexWasAt = i;
  711. o._natives._teardown && o._natives._teardown.call( obj, o );
  712. }
  713. }
  714. });
  715. if ( obj.data.trackEvents.animating.length ) {
  716. Popcorn.forEach( obj.data.trackEvents.animating, function( o, i, context ) {
  717. // Preserve the original start/end trackEvents
  718. if ( !o._id ) {
  719. animating.push( obj.data.trackEvents.animating[i] );
  720. }
  721. // Filter for user track events (vs system track events)
  722. if ( o._id ) {
  723. // Filter for the trackevent to remove
  724. if ( o._id !== trackId ) {
  725. animating.push( obj.data.trackEvents.animating[i] );
  726. }
  727. }
  728. });
  729. }
  730. // Update
  731. if ( indexWasAt <= obj.data.trackEvents.startIndex ) {
  732. obj.data.trackEvents.startIndex--;
  733. }
  734. if ( indexWasAt <= obj.data.trackEvents.endIndex ) {
  735. obj.data.trackEvents.endIndex--;
  736. }
  737. obj.data.trackEvents.byStart = byStart;
  738. obj.data.trackEvents.byEnd = byEnd;
  739. obj.data.trackEvents.animating = animating;
  740. for ( var i = 0; i < historyLen; i++ ) {
  741. if ( obj.data.history[ i ] !== trackId ) {
  742. history.push( obj.data.history[ i ] );
  743. }
  744. }
  745. // Update ordered history array
  746. obj.data.history = history;
  747. // Update track event references
  748. Popcorn.removeTrackEvent.ref( obj, trackId );
  749. };
  750. // Internal Only - Removes track event references from instance object's trackRefs hash table
  751. Popcorn.removeTrackEvent.ref = function( obj, trackId ) {
  752. delete obj.data.trackRefs[ trackId ];
  753. return obj;
  754. };
  755. // Return an array of track events bound to this instance object
  756. Popcorn.getTrackEvents = function( obj ) {
  757. var trackevents = [],
  758. refs = obj.data.trackEvents.byStart,
  759. length = refs.length,
  760. idx = 0,
  761. ref;
  762. for ( ; idx < length; idx++ ) {
  763. ref = refs[ idx ];
  764. // Return only user attributed track event references
  765. if ( ref._id ) {
  766. trackevents.push( ref );
  767. }
  768. }
  769. return trackevents;
  770. };
  771. // Internal Only - Returns an instance object's trackRefs hash table
  772. Popcorn.getTrackEvents.ref = function( obj ) {
  773. return obj.data.trackRefs;
  774. };
  775. // Return a single track event bound to this instance object
  776. Popcorn.getTrackEvent = function( obj, trackId ) {
  777. return obj.data.trackRefs[ trackId ];
  778. };
  779. // Internal Only - Returns an instance object's track reference by track id
  780. Popcorn.getTrackEvent.ref = function( obj, trackId ) {
  781. return obj.data.trackRefs[ trackId ];
  782. };
  783. Popcorn.getLastTrackEventId = function( obj ) {
  784. return obj.data.history[ obj.data.history.length - 1 ];
  785. };
  786. Popcorn.timeUpdate = function( obj, event ) {
  787. var currentTime = obj.media.currentTime,
  788. previousTime = obj.data.trackEvents.previousUpdateTime,
  789. tracks = obj.data.trackEvents,
  790. animating = tracks.animating,
  791. end = tracks.endIndex,
  792. start = tracks.startIndex,
  793. animIndex = 0,
  794. registryByName = Popcorn.registryByName,
  795. byEnd, byStart, byAnimate, natives, type;
  796. // Playbar advancing
  797. if ( previousTime <= currentTime ) {
  798. while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) {
  799. byEnd = tracks.byEnd[ end ];
  800. natives = byEnd._natives;
  801. type = natives && natives.type;
  802. // If plugin does not exist on this instance, remove it
  803. if ( !natives ||
  804. ( !!registryByName[ type ] ||
  805. !!obj[ type ] ) ) {
  806. if ( byEnd._running === true ) {
  807. byEnd._running = false;
  808. natives.end.call( obj, event, byEnd );
  809. }
  810. end++;
  811. } else {
  812. // remove track event
  813. Popcorn.removeTrackEvent( obj, byEnd._id );
  814. return;
  815. }
  816. }
  817. while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) {
  818. byStart = tracks.byStart[ start ];
  819. natives = byStart._natives;
  820. type = natives && natives.type;
  821. // If plugin does not exist on this instance, remove it
  822. if ( !natives ||
  823. ( !!registryByName[ type ] ||
  824. !!obj[ type ] ) ) {
  825. if ( byStart.end > currentTime &&
  826. byStart._running === false &&
  827. obj.data.disabled.indexOf( type ) === -1 ) {
  828. byStart._running = true;
  829. natives.start.call( obj, event, byStart );
  830. // If the `frameAnimation` option is used,
  831. // push the current byStart object into the `animating` cue
  832. if ( obj.options.frameAnimation &&
  833. ( byStart && byStart._running && byStart._natives.frame ) ) {
  834. animating.push( byStart );
  835. }
  836. }
  837. start++;
  838. } else {
  839. // remove track event
  840. Popcorn.removeTrackEvent( obj, byStart._id );
  841. return;
  842. }
  843. }
  844. // If the `frameAnimation` option is used, iterate the animating track
  845. // and execute the `frame` callback
  846. if ( obj.options.frameAnimation ) {
  847. while ( animIndex < animating.length ) {
  848. byAnimate = animating[ animIndex ];
  849. if ( !byAnimate._running ) {
  850. animating.splice( animIndex, 1 );
  851. } else {
  852. byAnimate._natives.frame.call( obj, event, byAnimate, currentTime );
  853. animIndex++;
  854. }
  855. }
  856. }
  857. // Playbar receding
  858. } else if ( previousTime > currentTime ) {
  859. while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) {
  860. byStart = tracks.byStart[ start ];
  861. natives = byStart._natives;
  862. type = natives && natives.type;
  863. // if plugin does not exist on this instance, remove it
  864. if ( !natives ||
  865. ( !!registryByName[ type ] ||
  866. !!obj[ type ] ) ) {
  867. if ( byStart._running === true ) {
  868. byStart._running = false;
  869. natives.end.call( obj, event, byStart );
  870. }
  871. start--;
  872. } else {
  873. // remove track event
  874. Popcorn.removeTrackEvent( obj, byStart._id );
  875. return;
  876. }
  877. }
  878. while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) {
  879. byEnd = tracks.byEnd[ end ];
  880. natives = byEnd._natives;
  881. type = natives && natives.type;
  882. // if plugin does not exist on this instance, remove it
  883. if ( !natives ||
  884. ( !!registryByName[ type ] ||
  885. !!obj[ type ] ) ) {
  886. if ( byEnd.start <= currentTime &&
  887. byEnd._running === false &&
  888. obj.data.disabled.indexOf( type ) === -1 ) {
  889. byEnd._running = true;
  890. natives.start.call( obj, event, byEnd );
  891. // If the `frameAnimation` option is used,
  892. // push the current byEnd object into the `animating` cue
  893. if ( obj.options.frameAnimation &&
  894. ( byEnd && byEnd._running && byEnd._natives.frame ) ) {
  895. animating.push( byEnd );
  896. }
  897. }
  898. end--;
  899. } else {
  900. // remove track event
  901. Popcorn.removeTrackEvent( obj, byEnd._id );
  902. return;
  903. }
  904. }
  905. // If the `frameAnimation` option is used, iterate the animating track
  906. // and execute the `frame` callback
  907. if ( obj.options.frameAnimation ) {
  908. while ( animIndex < animating.length ) {
  909. byAnimate = animating[ animIndex ];
  910. if ( !byAnimate._running ) {
  911. animating.splice( animIndex, 1 );
  912. } else {
  913. byAnimate._natives.frame.call( obj, event, byAnimate, currentTime );
  914. animIndex++;
  915. }
  916. }
  917. }
  918. // time bar is not moving ( video is paused )
  919. }
  920. tracks.endIndex = end;
  921. tracks.startIndex = start;
  922. tracks.previousUpdateTime = currentTime;
  923. };
  924. // Map and Extend TrackEvent functions to all Popcorn instances
  925. Popcorn.extend( Popcorn.p, {
  926. getTrackEvents: function() {
  927. return Popcorn.getTrackEvents.call( null, this );
  928. },
  929. getTrackEvent: function( id ) {
  930. return Popcorn.getTrackEvent.call( null, this, id );
  931. },
  932. getLastTrackEventId: function() {
  933. return Popcorn.getLastTrackEventId.call( null, this );
  934. },
  935. removeTrackEvent: function( id ) {
  936. Popcorn.removeTrackEvent.call( null, this, id );
  937. return this;
  938. },
  939. removePlugin: function( name ) {
  940. Popcorn.removePlugin.call( null, this, name );
  941. return this;
  942. },
  943. timeUpdate: function( event ) {
  944. Popcorn.timeUpdate.call( null, this, event );
  945. return this;
  946. },
  947. destroy: function() {
  948. Popcorn.destroy.call( null, this );
  949. return this;
  950. }
  951. });
  952. // Plugin manifests
  953. Popcorn.manifest = {};
  954. // Plugins are registered
  955. Popcorn.registry = [];
  956. Popcorn.registryByName = {};
  957. // An interface for extending Popcorn
  958. // with plugin functionality
  959. Popcorn.plugin = function( name, definition, manifest ) {
  960. if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
  961. Popcorn.error( "'" + name + "' is a protected function name" );
  962. return;
  963. }
  964. // Provides some sugar, but ultimately extends
  965. // the definition into Popcorn.p
  966. var reserved = [ "start", "end" ],
  967. plugin = {},
  968. setup,
  969. isfn = typeof definition === "function",
  970. methods = [ "_setup", "_teardown", "start", "end", "frame" ];
  971. // combines calls of two function calls into one
  972. var combineFn = function( first, second ) {
  973. first = first || Popcorn.nop;
  974. second = second || Popcorn.nop;
  975. return function() {
  976. first.apply( this, arguments );
  977. second.apply( this, arguments );
  978. };
  979. };
  980. // If `manifest` arg is undefined, check for manifest within the `definition` object
  981. // If no `definition.manifest`, an empty object is a sufficient fallback
  982. Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
  983. // apply safe, and empty default functions
  984. methods.forEach(function( method ) {
  985. definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name );
  986. });
  987. var pluginFn = function( setup, options ) {
  988. if ( !options ) {
  989. return this;
  990. }
  991. // Storing the plugin natives
  992. var natives = options._natives = {},
  993. compose = "",
  994. defaults, originalOpts, manifestOpts, mergedSetupOpts;
  995. Popcorn.extend( natives, setup );
  996. options._natives.type = name;
  997. options._running = false;
  998. natives.start = natives.start || natives[ "in" ];
  999. natives.end = natives.end || natives[ "out" ];
  1000. // extend teardown to always call end if running
  1001. natives._teardown = combineFn(function() {
  1002. var args = slice.call( arguments );
  1003. // end function signature is not the same as teardown,
  1004. // put null on the front of arguments for the event parameter
  1005. args.unshift( null );
  1006. // only call end if event is running
  1007. args[ 1 ]._running && natives.end.apply( this, args );
  1008. }, natives._teardown );
  1009. // Check for previously set default options
  1010. defaults = this.options.defaults && this.options.defaults[ options._natives && options._natives.type ];
  1011. // default to an empty string if no effect exists
  1012. // split string into an array of effects
  1013. options.compose = options.compose && options.compose.split( " " ) || [];
  1014. options.effect = options.effect && options.effect.split( " " ) || [];
  1015. // join the two arrays together
  1016. options.compose = options.compose.concat( options.effect );
  1017. options.compose.forEach(function( composeOption ) {
  1018. // if the requested compose is garbage, throw it away
  1019. compose = Popcorn.compositions[ composeOption ] || {};
  1020. // extends previous functions with compose function
  1021. methods.forEach(function( method ) {
  1022. natives[ method ] = combineFn( natives[ method ], compose[ method ] );
  1023. });
  1024. });
  1025. // Ensure a manifest object, an empty object is a sufficient fallback
  1026. options._natives.manifest = manifest;
  1027. // Checks for expected properties
  1028. if ( !( "start" in options ) ) {
  1029. options.start = options[ "in" ] || 0;
  1030. }
  1031. if ( !( "end" in options ) ) {
  1032. options.end = options[ "out" ] || this.duration() || Number.MAX_VALUE;
  1033. }
  1034. // Merge with defaults if they exist, make sure per call is prioritized
  1035. mergedSetupOpts = defaults ? Popcorn.extend( {}, defaults, options ) :
  1036. options;
  1037. // Resolves 239, 241, 242
  1038. if ( !mergedSetupOpts.target ) {
  1039. // Sometimes the manifest may be missing entirely
  1040. // or it has an options object that doesn't have a `target` property
  1041. manifestOpts = "options" in manifest && manifest.options;
  1042. mergedSetupOpts.target = manifestOpts && "target" in manifestOpts && manifestOpts.target;
  1043. }
  1044. // Trigger _setup method if exists
  1045. options._natives._setup && options._natives._setup.call( this, mergedSetupOpts );
  1046. // Create new track event for this instance
  1047. Popcorn.addTrackEvent( this, Popcorn.extend( mergedSetupOpts, options ) );
  1048. // Future support for plugin event definitions
  1049. // for all of the native events
  1050. Popcorn.forEach( setup, function( callback, type ) {
  1051. if ( type !== "type" ) {
  1052. if ( reserved.indexOf( type ) === -1 ) {
  1053. this.listen( type, callback );
  1054. }
  1055. }
  1056. }, this );
  1057. return this;
  1058. };
  1059. // Assign new named definition
  1060. plugin[ name ] = function( options ) {
  1061. return pluginFn.call( this, isfn ? definition.call( this, options ) : definition,
  1062. options );
  1063. };
  1064. // Extend Popcorn.p with new named definition
  1065. Popcorn.extend( Popcorn.p, plugin );
  1066. // Push into the registry
  1067. var entry = {
  1068. fn: plugin[ name ],
  1069. definition: definition,
  1070. base: definition,
  1071. parents: [],
  1072. name: name
  1073. };
  1074. Popcorn.registry.push(
  1075. Popcorn.extend( plugin, entry, {
  1076. type: name
  1077. })
  1078. );
  1079. Popcorn.registryByName[ name ] = entry;
  1080. return plugin;
  1081. };
  1082. // Storage for plugin function errors
  1083. Popcorn.plugin.errors = [];
  1084. // Returns wrapped plugin function
  1085. function safeTry( fn, pluginName ) {
  1086. return function() {
  1087. // When Popcorn.plugin.debug is true, do not suppress errors
  1088. if ( Popcorn.plugin.debug ) {
  1089. return fn.apply( this, arguments );
  1090. }
  1091. try {
  1092. return fn.apply( this, arguments );
  1093. } catch ( ex ) {
  1094. // Push plugin function errors into logging queue
  1095. Popcorn.plugin.errors.push({
  1096. plugin: pluginName,
  1097. thrown: ex,
  1098. source: fn.toString()
  1099. });
  1100. // Trigger an error that the instance can listen for
  1101. // and react to
  1102. this.trigger( "error", Popcorn.plugin.errors );
  1103. }
  1104. };
  1105. }
  1106. // Debug-mode flag for plugin development
  1107. Popcorn.plugin.debug = false;
  1108. // removePlugin( type ) removes all tracks of that from all instances of popcorn
  1109. // removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn
  1110. Popcorn.removePlugin = function( obj, name ) {
  1111. // Check if we are removing plugin from an instance or from all of Popcorn
  1112. if ( !name ) {
  1113. // Fix the order
  1114. name = obj;
  1115. obj = Popcorn.p;
  1116. if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
  1117. Popcorn.error( "'" + name + "' is a protected function name" );
  1118. return;
  1119. }
  1120. var registryLen = Popcorn.registry.length,
  1121. registryIdx;
  1122. // remove plugin reference from registry
  1123. for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) {
  1124. if ( Popcorn.registry[ registryIdx ].name === name ) {
  1125. Popcorn.registry.splice( registryIdx, 1 );
  1126. delete Popcorn.registryByName[ name ];
  1127. delete Popcorn.manifest[ name ];
  1128. // delete the plugin
  1129. delete obj[ name ];
  1130. // plugin found and removed, stop checking, we are done
  1131. return;
  1132. }
  1133. }
  1134. }
  1135. var byStart = obj.data.trackEvents.byStart,
  1136. byEnd = obj.data.trackEvents.byEnd,
  1137. animating = obj.data.trackEvents.animating,
  1138. idx, sl;
  1139. // remove all trackEvents
  1140. for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) {
  1141. if ( ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) &&
  1142. ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) ) {
  1143. byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] );
  1144. byStart.splice( idx, 1 );
  1145. byEnd.splice( idx, 1 );
  1146. // update for loop if something removed, but keep checking
  1147. idx--; sl--;
  1148. if ( obj.data.trackEvents.startIndex <= idx ) {
  1149. obj.data.trackEvents.startIndex--;
  1150. obj.data.trackEvents.endIndex--;
  1151. }
  1152. }
  1153. }
  1154. //remove all animating events
  1155. for ( idx = 0, sl = animating.length; idx < sl; idx++ ) {
  1156. if ( animating[ idx ] && animating[ idx ]._natives && animating[ idx ]._natives.type === name ) {
  1157. animating.splice( idx, 1 );
  1158. // update for loop if something removed, but keep checking
  1159. idx--; sl--;
  1160. }
  1161. }
  1162. };
  1163. Popcorn.compositions = {};
  1164. // Plugin inheritance
  1165. Popcorn.compose = function( name, definition, manifest ) {
  1166. // If `manifest` arg is undefined, check for manifest within the `definition` object
  1167. // If no `definition.manifest`, an empty object is a sufficient fallback
  1168. Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
  1169. // register the effect by name
  1170. Popcorn.compositions[ name ] = definition;
  1171. };
  1172. Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose;
  1173. // stores parsers keyed on filetype
  1174. Popcorn.parsers = {};
  1175. // An interface for extending Popcorn
  1176. // with parser functionality
  1177. Popcorn.parser = function( name, type, definition ) {
  1178. if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
  1179. Popcorn.error( "'" + name + "' is a protected function name" );
  1180. return;
  1181. }
  1182. // fixes parameters for overloaded function call
  1183. if ( typeof type === "function" && !definition ) {
  1184. definition = type;
  1185. type = "";
  1186. }
  1187. if ( typeof definition !== "function" || typeof type !== "string" ) {
  1188. return;
  1189. }
  1190. // Provides some sugar, but ultimately extends
  1191. // the definition into Popcorn.p
  1192. var natives = Popcorn.events.all,
  1193. parseFn,
  1194. parser = {};
  1195. parseFn = function( filename, callback ) {
  1196. if ( !filename ) {
  1197. return this;
  1198. }
  1199. var that = this;
  1200. Popcorn.xhr({
  1201. url: filename,
  1202. dataType: type,
  1203. success: function( data ) {
  1204. var tracksObject = definition( data ),
  1205. tracksData,
  1206. tracksDataLen,
  1207. tracksDef,
  1208. idx = 0;
  1209. tracksData = tracksObject.data || [];
  1210. tracksDataLen = tracksData.length;
  1211. tracksDef = null;
  1212. // If no tracks to process, return immediately
  1213. if ( !tracksDataLen ) {
  1214. return;
  1215. }
  1216. // Create tracks out of parsed object
  1217. for ( ; idx < tracksDataLen; idx++ ) {
  1218. tracksDef = tracksData[ idx ];
  1219. for ( var key in tracksDef ) {
  1220. if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) {
  1221. that[ key ]( tracksDef[ key ] );
  1222. }
  1223. }
  1224. }
  1225. if ( callback ) {
  1226. callback();
  1227. }
  1228. }
  1229. });
  1230. return this;
  1231. };
  1232. // Assign new named definition
  1233. parser[ name ] = parseFn;
  1234. // Extend Popcorn.p with new named definition
  1235. Popcorn.extend( Popcorn.p, parser );
  1236. // keys the function name by filetype extension
  1237. //Popcorn.parsers[ name ] = true;
  1238. return parser;
  1239. };
  1240. Popcorn.player = function( name, player ) {
  1241. player = player || {};
  1242. var playerFn = function( target, src, options ) {
  1243. options = options || {};
  1244. // List of events
  1245. var date = new Date() / 1000,
  1246. baselineTime = date,
  1247. currentTime = 0,
  1248. volume = 1,
  1249. muted = false,
  1250. events = {},
  1251. // The container div of the resource
  1252. container = document.getElementById( rIdExp.exec( target ) && rIdExp.exec( target )[ 2 ] ) ||
  1253. document.getElementById( target ) ||
  1254. target,
  1255. basePlayer = {},
  1256. timeout,
  1257. popcorn;
  1258. // copies a div into the media object
  1259. for( var val in container ) {
  1260. if ( typeof container[ val ] === "object" ) {
  1261. basePlayer[ val ] = container[ val ];
  1262. } else if ( typeof container[ val ] === "function" ) {
  1263. basePlayer[ val ] = (function( value ) {
  1264. // this is a stupid ugly kludgy hack in honour of Safari
  1265. // in Safari a NodeList is a function, not an object
  1266. if ( "length" in container[ value ] && !container[ value ].call ) {
  1267. return container[ value ];
  1268. } else {
  1269. return function() {
  1270. return container[ value ].apply( container, arguments );
  1271. };
  1272. }
  1273. }( val ));
  1274. } else {
  1275. Popcorn.player.defineProperty( basePlayer, val, {
  1276. get: (function( value ) {
  1277. return function() {
  1278. return container[ value ];
  1279. };
  1280. }( val )),
  1281. set: Popcorn.nop,
  1282. configurable: true
  1283. });
  1284. }
  1285. }
  1286. var timeupdate = function() {
  1287. date = new Date() / 1000;
  1288. if ( !basePlayer.paused ) {
  1289. basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime );
  1290. basePlayer.dispatchEvent( "timeupdate" );
  1291. timeout = setTimeout( timeupdate, 10 );
  1292. }
  1293. baselineTime = date;
  1294. };
  1295. basePlayer.play = function() {
  1296. this.paused = false;
  1297. if ( basePlayer.readyState >= 4 ) {
  1298. baselineTime = new Date() / 1000;
  1299. basePlayer.dispatchEvent( "play" );
  1300. timeupdate();
  1301. }
  1302. };
  1303. basePlayer.pause = function() {
  1304. this.paused = true;
  1305. basePlayer.dispatchEvent( "pause" );
  1306. };
  1307. Popcorn.player.defineProperty( basePlayer, "currentTime", {
  1308. get: function() {
  1309. return currentTime;
  1310. },
  1311. set: function( val ) {
  1312. // make sure val is a number
  1313. currentTime = +val;
  1314. basePlayer.dispatchEvent( "timeupdate" );
  1315. return currentTime;
  1316. },
  1317. configurable: true
  1318. });
  1319. Popcorn.player.defineProperty( basePlayer, "volume", {
  1320. get: function() {
  1321. return volume;
  1322. },
  1323. set: function( val ) {
  1324. // make sure val is a number
  1325. volume = +val;
  1326. basePlayer.dispatchEvent( "volumechange" );
  1327. return volume;
  1328. },
  1329. configurable: true
  1330. });
  1331. Popcorn.player.defineProperty( basePlayer, "muted", {
  1332. get: function() {
  1333. return muted;
  1334. },
  1335. set: function( val ) {
  1336. // make sure val is a number
  1337. muted = +val;
  1338. basePlayer.dispatchEvent( "volumechange" );
  1339. return muted;
  1340. },
  1341. configurable: true
  1342. });
  1343. // Adds an event listener to the object
  1344. basePlayer.addEventListener = function( evtName, fn ) {
  1345. if ( !events[ evtName ] ) {
  1346. events[ evtName ] = [];
  1347. }
  1348. events[ evtName ].push( fn );
  1349. return fn;
  1350. };
  1351. // Can take event object or simple string
  1352. basePlayer.dispatchEvent = function( oEvent ) {
  1353. var evt,
  1354. self = this,
  1355. eventInterface,
  1356. eventName = oEvent.type;
  1357. // A string was passed, create event object
  1358. if ( !eventName ) {
  1359. eventName = oEvent;
  1360. eventInterface = Popcorn.events.getInterface( eventName );
  1361. if ( eventInterface ) {
  1362. evt = document.createEvent( eventInterface );
  1363. evt.initEvent( eventName, true, true, window, 1 );
  1364. }
  1365. }
  1366. Popcorn.forEach( events[ eventName ], function( val ) {
  1367. val.call( self, evt, self );
  1368. });
  1369. };
  1370. // Attempt to get src from playerFn parameter
  1371. basePlayer.src = src || "";
  1372. basePlayer.readyState = 0;
  1373. basePlayer.duration = 0;
  1374. basePlayer.paused = true;
  1375. basePlayer.ended = 0;
  1376. if ( player._setup ) {
  1377. player._setup.call( basePlayer, options );
  1378. } else {
  1379. // there is no setup, which means there is nothing to load
  1380. basePlayer.readyState = 4;
  1381. basePlayer.dispatchEvent( "load" );
  1382. basePlayer.dispatchEvent( "loadeddata" );
  1383. }
  1384. // when a custom player is loaded, load basePlayer state into custom player
  1385. basePlayer.addEventListener( "load", function() {
  1386. // if a player is not ready before currentTime is called, this will set it after it is ready
  1387. basePlayer.currentTime = currentTime;
  1388. // same as above with volume and muted
  1389. basePlayer.volume = volume;
  1390. basePlayer.muted = muted;
  1391. });
  1392. basePlayer.addEventListener( "loadeddata", function() {
  1393. // if play was called before player ready, start playing video
  1394. !basePlayer.paused && basePlayer.play();
  1395. });
  1396. popcorn = new Popcorn.p.init( basePlayer, options );
  1397. return popcorn;
  1398. };
  1399. Popcorn[ name ] = Popcorn[ name ] || playerFn;
  1400. };
  1401. Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) {
  1402. object.__defineGetter__( description, options.get || Popcorn.nop );
  1403. object.__defineSetter__( description, options.set || Popcorn.nop );
  1404. };
  1405. // Cache references to reused RegExps
  1406. var rparams = /\?/,
  1407. // XHR Setup object
  1408. setup = {
  1409. url: "",
  1410. data: "",
  1411. dataType: "",
  1412. success: Popcorn.nop,
  1413. type: "GET",
  1414. async: true,
  1415. xhr: function() {
  1416. return new global.XMLHttpRequest();
  1417. }
  1418. };
  1419. Popcorn.xhr = function( options ) {
  1420. options.dataType = options.dataType && options.dataType.toLowerCase() || null;
  1421. if ( options.dataType &&
  1422. ( options.dataType === "jsonp" || options.dataType === "script" ) ) {
  1423. Popcorn.xhr.getJSONP(
  1424. options.url,
  1425. options.success,
  1426. options.dataType === "script"
  1427. );
  1428. return;
  1429. }
  1430. var settings = Popcorn.extend( {}, setup, options );
  1431. // Create new XMLHttpRequest object
  1432. settings.ajax = settings.xhr();
  1433. if ( settings.ajax ) {
  1434. if ( settings.type === "GET" && settings.data ) {
  1435. // append query string
  1436. settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data;
  1437. // Garbage collect and reset settings.data
  1438. settings.data = null;
  1439. }
  1440. settings.ajax.open( settings.type, settings.url, settings.async );
  1441. settings.ajax.send( settings.data || null );
  1442. return Popcorn.xhr.httpData( settings );
  1443. }
  1444. };
  1445. Popcorn.xhr.httpData = function( settings ) {
  1446. var data, json = null,
  1447. parser, xml = null;
  1448. settings.ajax.onreadystatechange = function() {
  1449. if ( settings.ajax.readyState === 4 ) {
  1450. try {
  1451. json = JSON.parse( settings.ajax.responseText );
  1452. } catch( e ) {
  1453. //suppress
  1454. }
  1455. data = {
  1456. xml: settings.ajax.responseXML,
  1457. text: settings.ajax.responseText,
  1458. json: json
  1459. };
  1460. // Normalize: data.xml is non-null in IE9 regardless of if response is valid xml
  1461. if ( !data.xml || !data.xml.documentElement ) {
  1462. data.xml = null;
  1463. try {
  1464. parser = new DOMParser();
  1465. xml = parser.parseFromString( settings.ajax.responseText, "text/xml" );
  1466. if ( !xml.getElementsByTagName( "parsererror" ).length ) {
  1467. data.xml = xml;
  1468. }
  1469. } catch ( e ) {
  1470. // data.xml remains null
  1471. }
  1472. }
  1473. // If a dataType was specified, return that type of data
  1474. if ( settings.dataType ) {
  1475. data = data[ settings.dataType ];
  1476. }
  1477. settings.success.call( settings.ajax, data );
  1478. }
  1479. };
  1480. return data;
  1481. };
  1482. Popcorn.xhr.getJSONP = function( url, success, isScript ) {
  1483. var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement,
  1484. script = document.createElement( "script" ),
  1485. paramStr = url.split( "?" )[ 1 ],
  1486. isFired = false,
  1487. params = [],
  1488. callback, parts, callparam;
  1489. if ( paramStr && !isScript ) {
  1490. params = paramStr.split( "&" );
  1491. }
  1492. if ( params.length ) {
  1493. parts = params[ params.length - 1 ].split( "=" );
  1494. }
  1495. callback = params.length ? ( parts[ 1 ] ? parts[ 1 ] : parts[ 0 ] ) : "jsonp";
  1496. if ( !paramStr && !isScript ) {
  1497. url += "?callback=" + callback;
  1498. }
  1499. if ( callback && !isScript ) {
  1500. // If a callback name already exists
  1501. if ( !!window[ callback ] ) {
  1502. // Create a new unique callback name
  1503. callback = Popcorn.guid( callback );
  1504. }
  1505. // Define the JSONP success callback globally
  1506. window[ callback ] = function( data ) {
  1507. // Fire success callbacks
  1508. success && success( data );
  1509. isFired = true;
  1510. };
  1511. // Replace callback param and callback name
  1512. url = url.replace( parts.join( "=" ), parts[ 0 ] + "=" + callback );
  1513. }
  1514. script.onload = function() {
  1515. // Handling remote script loading callbacks
  1516. if ( isScript ) {
  1517. // getScript
  1518. success && success();
  1519. }
  1520. // Executing for JSONP requests
  1521. if ( isFired ) {
  1522. // Garbage collect the callback
  1523. delete window[ callback ];
  1524. }
  1525. // Garbage collect the script resource
  1526. head.removeChild( script );
  1527. };
  1528. script.src = url;
  1529. head.insertBefore( script, head.firstChild );
  1530. return;
  1531. };
  1532. Popcorn.getJSONP = Popcorn.xhr.getJSONP;
  1533. Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) {
  1534. return Popcorn.xhr.getJSONP( url, success, true );
  1535. };
  1536. Popcorn.util = {
  1537. // Simple function to parse a timestamp into seconds
  1538. // Acceptable formats are:
  1539. // HH:MM:SS.MMM
  1540. // HH:MM:SS;FF
  1541. // Hours and minutes are optional. They default to 0
  1542. toSeconds: function( timeStr, framerate ) {
  1543. // Hours and minutes are optional
  1544. // Seconds must be specified
  1545. // Seconds can be followed by milliseconds OR by the frame information
  1546. var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/,
  1547. errorMessage = "Invalid time format",
  1548. digitPairs, lastIndex, lastPair, firstPair,
  1549. frameInfo, frameTime;
  1550. if ( typeof timeStr === "number" ) {
  1551. return timeStr;
  1552. }
  1553. if ( typeof timeStr === "string" &&
  1554. !validTimeFormat.test( timeStr ) ) {
  1555. Popcorn.error( errorMessage );
  1556. }
  1557. digitPairs = timeStr.split( ":" );
  1558. lastIndex = digitPairs.length - 1;
  1559. lastPair = digitPairs[ lastIndex ];
  1560. // Fix last element:
  1561. if ( lastPair.indexOf( ";" ) > -1 ) {
  1562. frameInfo = lastPair.split( ";" );
  1563. frameTime = 0;
  1564. if ( framerate && ( typeof framerate === "number" ) ) {
  1565. frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate;
  1566. }
  1567. digitPairs[ lastIndex ] = parseInt( frameInfo[ 0 ], 10 ) + frameTime;
  1568. }
  1569. firstPair = digitPairs[ 0 ];
  1570. return {
  1571. 1: parseFloat( firstPair, 10 ),
  1572. 2: ( parseInt( firstPair, 10 ) * 60 ) +
  1573. parseFloat( digitPairs[ 1 ], 10 ),
  1574. 3: ( parseInt( firstPair, 10 ) * 3600 ) +
  1575. ( parseInt( digitPairs[ 1 ], 10 ) * 60 ) +
  1576. parseFloat( digitPairs[ 2 ], 10 )
  1577. }[ digitPairs.length || 1 ];
  1578. }
  1579. };
  1580. // Initialize locale data
  1581. // Based on http://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes
  1582. function initLocale( arg ) {
  1583. var locale = typeof arg === "string" ? arg : [ arg.language, arg.region ].join( "-" ),
  1584. parts = locale.split( "-" );
  1585. // Setup locale data table
  1586. return {
  1587. iso6391: locale,
  1588. language: parts[ 0 ] || "",
  1589. region: parts[ 1 ] || ""
  1590. };
  1591. }
  1592. // Declare locale data table
  1593. var localeData = initLocale( global.navigator.userLanguage || global.navigator.language );
  1594. Popcorn.locale = {
  1595. // Popcorn.locale.get()
  1596. // returns reference to privately
  1597. // defined localeData
  1598. get: function() {
  1599. return localeData;
  1600. },
  1601. // Popcorn.locale.set( string|object );
  1602. set: function( arg ) {
  1603. localeData = initLocale( arg );
  1604. Popcorn.locale.broadcast();
  1605. return localeData;
  1606. },
  1607. // Popcorn.locale.broadcast( type )
  1608. // Sends events to all popcorn media instances that are
  1609. // listening for locale events
  1610. broadcast: function( type ) {
  1611. var instances = Popcorn.instances,
  1612. length = instances.length,
  1613. idx = 0,
  1614. instance;
  1615. type = type || "locale:changed";
  1616. // Iterate all current instances
  1617. for ( ; idx < length; idx++ ) {
  1618. instance = instances[ idx ];
  1619. // For those instances with locale event listeners,
  1620. // trigger a locale change event
  1621. if ( type in instance.data.events ) {
  1622. instance.trigger( type );
  1623. }
  1624. }
  1625. }
  1626. };
  1627. // alias for exec function
  1628. Popcorn.p.cue = Popcorn.p.exec;
  1629. function getItems() {
  1630. var item,
  1631. list = [];
  1632. if ( Object.keys ) {
  1633. list = Object.keys( Popcorn.p );
  1634. } else {
  1635. for ( item in Popcorn.p ) {
  1636. if ( hasOwn.call( Popcorn.p, item ) ) {
  1637. list.push( item );
  1638. }
  1639. }
  1640. }
  1641. return list.join( "," ).toLowerCase().split( ",");
  1642. }
  1643. // Protected API methods
  1644. Popcorn.protect = {
  1645. natives: getItems()
  1646. };
  1647. // Exposes Popcorn to global context
  1648. global.Popcorn = Popcorn;
  1649. })(window, window.document);
  1650. /*!
  1651. * Popcorn.sequence
  1652. *
  1653. * Copyright 2011, Rick Waldron
  1654. * Licensed under MIT license.
  1655. *
  1656. */
  1657. /* jslint forin: true, maxerr: 50, indent: 4, es5: true */
  1658. /* global Popcorn: true */
  1659. // Requires Popcorn.js
  1660. (function( global, Popcorn ) {
  1661. // TODO: as support increases, migrate to element.dataset
  1662. var doc = global.document,
  1663. location = global.location,
  1664. rprotocol = /:\/\//,
  1665. // TODO: better solution to this sucky stop-gap
  1666. lochref = location.href.replace( location.href.split("/").slice(-1)[0], "" ),
  1667. // privately held
  1668. range = function(start, stop, step) {
  1669. start = start || 0;
  1670. stop = ( stop || start || 0 ) + 1;
  1671. step = step || 1;
  1672. var len = Math.ceil((stop - start) / step) || 0,
  1673. idx = 0,
  1674. range = [];
  1675. range.length = len;
  1676. while (idx < len) {
  1677. range[idx++] = start;
  1678. start += step;
  1679. }
  1680. return range;
  1681. };
  1682. Popcorn.sequence = function( parent, list ) {
  1683. return new Popcorn.sequence.init( parent, list );
  1684. };
  1685. Popcorn.sequence.init = function( parent, list ) {
  1686. // Video element
  1687. this.parent = doc.getElementById( parent );
  1688. // Store ref to a special ID
  1689. this.seqId = Popcorn.guid( "__sequenced" );
  1690. // List of HTMLVideoElements
  1691. this.queue = [];
  1692. // List of Popcorn objects
  1693. this.playlist = [];
  1694. // Lists of in/out points
  1695. this.inOuts = {
  1696. // Stores the video in/out times for each video in sequence
  1697. ofVideos: [],
  1698. // Stores the clip in/out times for each clip in sequences
  1699. ofClips: []
  1700. };
  1701. // Store first video dimensions
  1702. this.dims = {
  1703. width: 0, //this.video.videoWidth,
  1704. height: 0 //this.video.videoHeight
  1705. };
  1706. this.active = 0;
  1707. this.cycling = false;
  1708. this.playing = false;
  1709. this.times = {
  1710. last: 0
  1711. };
  1712. // Store event pointers and queues
  1713. this.events = {
  1714. };
  1715. var self = this,
  1716. clipOffset = 0;
  1717. // Create `video` elements
  1718. Popcorn.forEach( list, function( media, idx ) {
  1719. var video = doc.createElement( "video" );
  1720. video.preload = "auto";
  1721. // Setup newly created video element
  1722. video.controls = true;
  1723. // If the first, show it, if the after, hide it
  1724. video.style.display = ( idx && "none" ) || "" ;
  1725. // Seta registered sequence id
  1726. video.id = self.seqId + "-" + idx ;
  1727. // Push this video into the sequence queue
  1728. self.queue.push( video );
  1729. var //satisfy lint
  1730. mIn = media["in"],
  1731. mOut = media["out"];
  1732. // Push the in/out points into sequence ioVideos
  1733. self.inOuts.ofVideos.push({
  1734. "in": ( mIn !== undefined && mIn ) || 1,
  1735. "out": ( mOut !== undefined && mOut ) || 0
  1736. });
  1737. self.inOuts.ofVideos[ idx ]["out"] = self.inOuts.ofVideos[ idx ]["out"] || self.inOuts.ofVideos[ idx ]["in"] + 2;
  1738. // Set the sources
  1739. video.src = !rprotocol.test( media.src ) ? lochref + media.src : media.src;
  1740. // Set some squence specific data vars
  1741. video.setAttribute("data-sequence-owner", parent );
  1742. video.setAttribute("data-sequence-guid", self.seqId );
  1743. video.setAttribute("data-sequence-id", idx );
  1744. video.setAttribute("data-sequence-clip", [ self.inOuts.ofVideos[ idx ]["in"], self.inOuts.ofVideos[ idx ]["out"] ].join(":") );
  1745. // Append the video to the parent element
  1746. self.parent.appendChild( video );
  1747. self.playlist.push( Popcorn("#" + video.id ) );
  1748. });
  1749. self.inOuts.ofVideos.forEach(function( obj ) {
  1750. var clipDuration = obj["out"] - obj["in"],
  1751. offs = {
  1752. "in": clipOffset,
  1753. "out": clipOffset + clipDuration
  1754. };
  1755. self.inOuts.ofClips.push( offs );
  1756. clipOffset = offs["out"] + 1;
  1757. });
  1758. Popcorn.forEach( this.queue, function( media, idx ) {
  1759. function canPlayThrough( event ) {
  1760. // If this is idx zero, use it as dimension for all
  1761. if ( !idx ) {
  1762. self.dims.width = media.videoWidth;
  1763. self.dims.height = media.videoHeight;
  1764. }
  1765. media.currentTime = self.inOuts.ofVideos[ idx ]["in"] - 0.5;
  1766. media.removeEventListener( "canplaythrough", canPlayThrough, false );
  1767. return true;
  1768. }
  1769. // Hook up event listeners for managing special playback
  1770. media.addEventListener( "canplaythrough", canPlayThrough, false );
  1771. // TODO: consolidate & DRY
  1772. media.addEventListener( "play", function( event ) {
  1773. self.playing = true;
  1774. }, false );
  1775. media.addEventListener( "pause", function( event ) {
  1776. self.playing = false;
  1777. }, false );
  1778. media.addEventListener( "timeupdate", function( event ) {
  1779. var target = event.srcElement || event.target,
  1780. seqIdx = +( (target.dataset && target.dataset.sequenceId) || target.getAttribute("data-sequence-id") ),
  1781. floor = Math.floor( media.currentTime );
  1782. if ( self.times.last !== floor &&
  1783. seqIdx === self.active ) {
  1784. self.times.last = floor;
  1785. if ( floor === self.inOuts.ofVideos[ seqIdx ]["out"] ) {
  1786. Popcorn.sequence.cycle.call( self, seqIdx );
  1787. }
  1788. }
  1789. }, false );
  1790. });
  1791. return this;
  1792. };
  1793. Popcorn.sequence.init.prototype = Popcorn.sequence.prototype;
  1794. //
  1795. Popcorn.sequence.cycle = function( idx ) {
  1796. if ( !this.queue ) {
  1797. Popcorn.error("Popcorn.sequence.cycle is not a public method");
  1798. }
  1799. var // Localize references
  1800. queue = this.queue,
  1801. ioVideos = this.inOuts.ofVideos,
  1802. current = queue[ idx ],
  1803. nextIdx = 0,
  1804. next, clip;
  1805. var // Popcorn instances
  1806. $popnext,
  1807. $popprev;
  1808. if ( queue[ idx + 1 ] ) {
  1809. nextIdx = idx + 1;
  1810. }
  1811. // Reset queue
  1812. if ( !queue[ idx + 1 ] ) {
  1813. nextIdx = 0;
  1814. this.playlist[ idx ].pause();
  1815. } else {
  1816. next = queue[ nextIdx ];
  1817. clip = ioVideos[ nextIdx ];
  1818. // Constrain dimentions
  1819. Popcorn.extend( next, {
  1820. width: this.dims.width,
  1821. height: this.dims.height
  1822. });
  1823. $popnext = this.playlist[ nextIdx ];
  1824. $popprev = this.playlist[ idx ];
  1825. // When not resetting to 0
  1826. current.pause();
  1827. this.active = nextIdx;
  1828. this.times.last = clip["in"] - 1;
  1829. // Play the next video in the sequence
  1830. $popnext.currentTime( clip["in"] );
  1831. $popnext[ nextIdx ? "play" : "pause" ]();
  1832. // Trigger custom cycling event hook
  1833. this.trigger( "cycle", {
  1834. position: {
  1835. previous: idx,
  1836. current: nextIdx
  1837. }
  1838. });
  1839. // Set the previous back to it's beginning time
  1840. // $popprev.currentTime( ioVideos[ idx ].in );
  1841. if ( nextIdx ) {
  1842. // Hide the currently ending video
  1843. current.style.display = "none";
  1844. // Show the next video in the sequence
  1845. next.style.display = "";
  1846. }
  1847. this.cycling = false;
  1848. }
  1849. return this;
  1850. };
  1851. var excludes = [ "timeupdate", "play", "pause" ];
  1852. // Sequence object prototype
  1853. Popcorn.extend( Popcorn.sequence.prototype, {
  1854. // Returns Popcorn object from sequence at index
  1855. eq: function( idx ) {
  1856. return this.playlist[ idx ];
  1857. },
  1858. // Remove a sequence from it's playback display container
  1859. remove: function() {
  1860. this.parent.innerHTML = null;
  1861. },
  1862. // Returns Clip object from sequence at index
  1863. clip: function( idx ) {
  1864. return this.inOuts.ofVideos[ idx ];
  1865. },
  1866. // Returns sum duration for all videos in sequence
  1867. duration: function() {
  1868. var ret = 0,
  1869. seq = this.inOuts.ofClips,
  1870. idx = 0;
  1871. for ( ; idx < seq.length; idx++ ) {
  1872. ret += seq[ idx ]["out"] - seq[ idx ]["in"] + 1;
  1873. }
  1874. return ret - 1;
  1875. },
  1876. play: function() {
  1877. this.playlist[ this.active ].play();
  1878. return this;
  1879. },
  1880. // Attach an event to a single point in time
  1881. exec: function ( time, fn ) {
  1882. var index = this.active;
  1883. this.inOuts.ofClips.forEach(function( off, idx ) {
  1884. if ( time >= off["in"] && time <= off["out"] ) {
  1885. index = idx;
  1886. }
  1887. });
  1888. //offsetBy = time - self.inOuts.ofVideos[ index ].in;
  1889. time += this.inOuts.ofVideos[ index ]["in"] - this.inOuts.ofClips[ index ]["in"];
  1890. // Creating a one second track event with an empty end
  1891. Popcorn.addTrackEvent( this.playlist[ index ], {
  1892. start: time - 1,
  1893. end: time,
  1894. _running: false,
  1895. _natives: {
  1896. start: fn || Popcorn.nop,
  1897. end: Popcorn.nop,
  1898. type: "exec"
  1899. }
  1900. });
  1901. return this;
  1902. },
  1903. // Binds event handlers that fire only when all
  1904. // videos in sequence have heard the event
  1905. listen: function( type, callback ) {
  1906. var self = this,
  1907. seq = this.playlist,
  1908. total = seq.length,
  1909. count = 0,
  1910. fnName;
  1911. if ( !callback ) {
  1912. callback = Popcorn.nop;
  1913. }
  1914. // Handling for DOM and Media events
  1915. if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) {
  1916. Popcorn.forEach( seq, function( video ) {
  1917. video.listen( type, function( event ) {
  1918. event.active = self;
  1919. if ( excludes.indexOf( type ) > -1 ) {
  1920. callback.call( video, event );
  1921. } else {
  1922. if ( ++count === total ) {
  1923. callback.call( video, event );
  1924. }
  1925. }
  1926. });
  1927. });
  1928. } else {
  1929. // If no events registered with this name, create a cache
  1930. if ( !this.events[ type ] ) {
  1931. this.events[ type ] = {};
  1932. }
  1933. // Normalize a callback name key
  1934. fnName = callback.name || Popcorn.guid( "__" + type );
  1935. // Store in event cache
  1936. this.events[ type ][ fnName ] = callback;
  1937. }
  1938. // Return the sequence object
  1939. return this;
  1940. },
  1941. unlisten: function( type, name ) {
  1942. // TODO: finish implementation
  1943. },
  1944. trigger: function( type, data ) {
  1945. var self = this;
  1946. // Handling for DOM and Media events
  1947. if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) {
  1948. // find the active video and trigger api events on that video.
  1949. return;
  1950. } else {
  1951. // Only proceed if there are events of this type
  1952. // currently registered on the sequence
  1953. if ( this.events[ type ] ) {
  1954. Popcorn.forEach( this.events[ type ], function( callback, name ) {
  1955. callback.call( self, { type: type }, data );
  1956. });
  1957. }
  1958. }
  1959. return this;
  1960. }
  1961. });
  1962. Popcorn.forEach( Popcorn.manifest, function( obj, plugin ) {
  1963. // Implement passthrough methods to plugins
  1964. Popcorn.sequence.prototype[ plugin ] = function( options ) {
  1965. // console.log( this, options );
  1966. var videos = {}, assignTo = [],
  1967. idx, off, inOuts, inIdx, outIdx, keys, clip, clipInOut, clipRange;
  1968. for ( idx = 0; idx < this.inOuts.ofClips.length; idx++ ) {
  1969. // store reference
  1970. off = this.inOuts.ofClips[ idx ];
  1971. // array to test against
  1972. inOuts = range( off["in"], off["out"] );
  1973. inIdx = inOuts.indexOf( options.start );
  1974. outIdx = inOuts.indexOf( options.end );
  1975. if ( inIdx > -1 ) {
  1976. videos[ idx ] = Popcorn.extend( {}, off, {
  1977. start: inOuts[ inIdx ],
  1978. clipIdx: inIdx
  1979. });
  1980. }
  1981. if ( outIdx > -1 ) {
  1982. videos[ idx ] = Popcorn.extend( {}, off, {
  1983. end: inOuts[ outIdx ],
  1984. clipIdx: outIdx
  1985. });
  1986. }
  1987. }
  1988. keys = Object.keys( videos ).map(function( val ) {
  1989. return +val;
  1990. });
  1991. assignTo = range( keys[ 0 ], keys[ 1 ] );
  1992. //console.log( "PLUGIN CALL MAPS: ", videos, keys, assignTo );
  1993. for ( idx = 0; idx < assignTo.length; idx++ ) {
  1994. var compile = {},
  1995. play = assignTo[ idx ],
  1996. vClip = videos[ play ];
  1997. if ( vClip ) {
  1998. // has instructions
  1999. clip = this.inOuts.ofVideos[ play ];
  2000. clipInOut = vClip.clipIdx;
  2001. clipRange = range( clip["in"], clip["out"] );
  2002. if ( vClip.start ) {
  2003. compile.start = clipRange[ clipInOut ];
  2004. compile.end = clipRange[ clipRange.length - 1 ];
  2005. }
  2006. if ( vClip.end ) {
  2007. compile.start = clipRange[ 0 ];
  2008. compile.end = clipRange[ clipInOut ];
  2009. }
  2010. //compile.start += 0.1;
  2011. //compile.end += 0.9;
  2012. } else {
  2013. compile.start = this.inOuts.ofVideos[ play ]["in"];
  2014. compile.end = this.inOuts.ofVideos[ play ]["out"];
  2015. //compile.start += 0.1;
  2016. //compile.end += 0.9;
  2017. }
  2018. // Handling full clip persistance
  2019. //if ( compile.start === compile.end ) {
  2020. //compile.start -= 0.1;
  2021. //compile.end += 0.9;
  2022. //}
  2023. // Call the plugin on the appropriate Popcorn object in the playlist
  2024. // Merge original options object & compiled (start/end) object into
  2025. // a new fresh object
  2026. this.playlist[ play ][ plugin ](
  2027. Popcorn.extend( {}, options, compile )
  2028. );
  2029. }
  2030. // Return the sequence object
  2031. return this;
  2032. };
  2033. });
  2034. })( this, Popcorn );
  2035. (function( Popcorn ) {
  2036. document.addEventListener( "DOMContentLoaded", function() {
  2037. // Supports non-specific elements
  2038. var dataAttr = "data-timeline-sources",
  2039. medias = document.querySelectorAll( "[" + dataAttr + "]" );
  2040. Popcorn.forEach( medias, function( idx, key ) {
  2041. var media = medias[ key ],
  2042. hasDataSources = false,
  2043. dataSources, data, popcornMedia;
  2044. // Ensure that the DOM has an id
  2045. if ( !media.id ) {
  2046. media.id = Popcorn.guid( "__popcorn" );
  2047. }
  2048. // Ensure we're looking at a dom node
  2049. if ( media.nodeType && media.nodeType === 1 ) {
  2050. popcornMedia = Popcorn( "#" + media.id );
  2051. dataSources = ( media.getAttribute( dataAttr ) || "" ).split( "," );
  2052. if ( dataSources[ 0 ] ) {
  2053. Popcorn.forEach( dataSources, function( source ) {
  2054. // split the parser and data as parser!file
  2055. data = source.split( "!" );
  2056. // if no parser is defined for the file, assume "parse" + file extension
  2057. if ( data.length === 1 ) {
  2058. // parse a relative URL for the filename, split to get extension
  2059. data = source.match( /(.*)[\/\\]([^\/\\]+\.\w+)$/ )[ 2 ].split( "." );
  2060. data[ 0 ] = "parse" + data[ 1 ].toUpperCase();
  2061. data[ 1 ] = source;
  2062. }
  2063. // If the media has data sources and the correct parser is registered, continue to load
  2064. if ( dataSources[ 0 ] && popcornMedia[ data[ 0 ] ] ) {
  2065. // Set up the media and load in the datasources
  2066. popcornMedia[ data[ 0 ] ]( data[ 1 ] );
  2067. }
  2068. });
  2069. }
  2070. // Only play the media if it was specified to do so
  2071. if ( !!popcornMedia.autoplay ) {
  2072. popcornMedia.play();
  2073. }
  2074. }
  2075. });
  2076. }, false );
  2077. })( Popcorn );// PLUGIN: Attribution
  2078. (function( Popcorn ) {
  2079. /**
  2080. * Attribution popcorn plug-in
  2081. * Adds text to an element on the page.
  2082. * Options parameter will need a mandatory start, end, target.
  2083. * Optional parameters include nameofwork, NameOfWorkUrl, CopyrightHolder, CopyrightHolderUrl, license & licenseUrl.
  2084. * Start is the time that you want this plug-in to execute
  2085. * End is the time that you want this plug-in to stop executing
  2086. * Target is the id of the document element that the text needs to be attached to, this target element must exist on the DOM
  2087. * nameofwork is the title of the attribution
  2088. * NameOfWorkUrl is a url that provides more details about the attribution
  2089. * CopyrightHolder is the name of the person/institution that holds the rights to the attribution
  2090. * CopyrightHolderUrl is the url that provides more details about the copyrightholder
  2091. * license is the type of license that the work is copyrighted under
  2092. * LicenseUrl is the url that provides more details about the ticense type
  2093. * @param {Object} options
  2094. *
  2095. * Example:
  2096. var p = Popcorn('#video')
  2097. .attribution({
  2098. start: 5, // seconds
  2099. end: 15, // seconds
  2100. target: 'attributiondiv'
  2101. } )
  2102. *
  2103. */
  2104. Popcorn.plugin( "attribution" , (function() {
  2105. var
  2106. common = "data:image/png;base64,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",
  2107. licenses = {
  2108. "cc-by": common + "eeSURBVHja7JpfbNvGHce/R9JBU9Qa89QN2gD5TepLmGTJYyyte9mypiSC7aXrIj8NqDFI6lavLezISpwuE5LJwpACw7aaWJ8L0/kD7B8iyi2wRXYiGikgvUkPNbY+ybXbh5l/bg8kT6RlO7Zjq2maM0488e4o8sPv/e53vzOhlEYIIZ/hadr3RCklBAAFgNt/vwWO48BxHHieB8fx4DkOHO8dOQ6EcOAIASEEIMS/CigoqEPhUAeO42bbtt2jY8O2HTiOzeoc6rD2lFL/Zlj5SUg/fvknAAACgPpweZ53M8d3yzzv1nG8B5mAEC7I14PjgXVcmLbt5WDZDkN2HIeBDYJ+kiALAMJweQFC6Ojmm3O3UKlUUKvVsLa6FrrQYGQQp06dQup7Kbx09kewHR4cZ7kvxOZAQLx3GRg+DnVHArwxRPYH7v2FOrQPNDQajdD5RCIB+ZyM4yeP9RUyAUD/duevEASBQRUEwc28gKo+j+KVIpaXl3d0wWg0irG3xjA8fBqWbcO2LViWl20LlmUzhW+m5L2q+L//+RTXy9fRbDQBAMlkEpIkAQAMw4Cu6wCAeCKO0cwovvmt5/uiYAKA/rP6Dwi80AUrDGBAEJCfmIQ2q7EOoihClmXEYjEMDw8DAKrVKtrtNjRNw8rKCmsrKzJ+NfZLHH72MCzLgmlZsCwTlmWFTYYP2PFs+R5s8eernyMzmsXq6ipkWUapVEIsFgu1abfbyOVy0DQNkUgEl4uXDxwyA3znwzsY8MEOCBgQBkJwRVFENptFJpOBKIpbXlBVVeRyOQY6nojjT+/9Ec8cPgzLMmGaJlPyppDp3gBPvHkBzUYT6XQaMzMz3eHpmaDg9VRVxcjICOKJOC5duXjggDkA4D0bLPA8BD6sXEmSUK/Xkc/nt4ULAOl0Gq1Wiw3NZqOJq8VrIVvOMY+EdLP3txHMTm1us9GELMsYe+ONh7ZPp9OQZRnNRhP3F+oHbiY4AOB8t4znUdXnQ3ArlUrPcNsuiaKISqXCIGuzGqrVefC8sDlkznf7EIK806R94N5rqVRC4oUXNvqhm46GUqkU6nvggF0FuyouXikyUDMzMw9V7XaQ/b7F3xQ9X9qDSzyfmvM8DIIuZLI7yI1GA8lkskcEIyMjbISMjIyE6mKxGJLJZI+ncXAK9h7+5twt5i1ks1mmwr0kURSZUpaXl3Hzxi22YHEhb20idps2u09VVTctb9fnwAD7aqpUKgxOJpNhjXRdh6IoSKVSSKVSKBQKW9ZNT0+H7J2v4sqdSkC9XdNAyKOZiMc9uQsNQsARglqt5rpYsszA6LqOVCoV6qTrOnRdRyaTgaIoPXVLS0tsNpdlGaqqolaruSvAAFigC7frle/+IQzD2HQy85WbTqd31OcAFew+qL9CO3r0KGuQy+WY3Wq1WmzSO3/+PFOyJElotVqYnZ0N+cgAWHltda1rDtjR57p3E5FIJKDrOtrtduh80F0Lln2fWNd1JBKJ/ih44+QStE/+m06n04jFYgy0P5H4KvXrZFnumVC67hf72LcHkM/JaEw1kMvlMDs7u6M+vmjkc3J/FPxVTsdPHkM8EYemaT3ewlZwNU1DPBHvS1yC84MtQX8xaJ98NauqipWVFRiGgaGhIRQKha6v6y2Tg3XB4dj1S9nHvj7Er98eQyQSgaqqUBSF/WbQD26321AUBdPT04hEIhjNjPZvkvNvZDAyiLXVNSwtLbEG+Xye3fSRI0dC4Pw6wzB66vzkX2swMghKA8thUPjv1Pu254d4LvIcyten8dt3itA0DZqmQZIkSJIEURSh6zoTTT+DPWzevnvvLg4dOoTChQK0WQ2iKKLT6YQ8g3K5zGIMyWQS+XyeqbdcLrO2wToAGBoaQrvdxovffxHXSlfxv/V1mOY6TMuEaVqw/biEY8OxHRaE32vo8nEKV7Jgz78X/4WBgUP4aP4jZH6RYcvJbDb7SD/gB1YAYOqdKfzwzA+wbq5j3TRhmSZMawPgRwj4PK4Bdw4A29JJpoYRjUYBAIVCocf12U1aWVlhs3U0GvUC8X5o0oHj2WLfXDypiQMAhzqwbXcf7dLliwyQoiihGO9u4KZSKdZ37M0xL8BudyEHQpRskqVP1pYRm9wB0PH8OF24X6PGgzp99Wev+lM9lSSJ1ut1utPUarWoJEmsv6zI1HhQpwv3a/Ti5Yvs/Ncod79kX8/QxfoCNT42qKzI7LwoinRycpJ2Op0twXY6HTo5OUlFUWT9Tp46SZc+NuiisUDH8+NfR7i0Z/U/kR/Hy4oMQRBwrXgN7//l/T1vGRUuTcKyLNy9W8NrP3/t4IdiwLwEdzOCq9SN3/tmIoJ5Ij/uKvlBnb6n/plGo9Edv7FoNErLvy9T40GdLhoL9N0/vNs3tVBKty0Hz31pCvZT9vUMXvnpK2wXQq9UcWPuxrbb9mfls0gmh9le29zcDUwVpvqnlE0U/GUq96EBwuMnjmEifwHf/k40sBsRDDci5Lf6/3iy/Mkn+N3VEuar8/0digGIj4Np2HEE9vTwaZx56QxOfPcEvhGJhGO4nmv12eoq7i3ew+2bt/sO9iur4KdpHwBTSp8lhHzxFMWBjCjy/wEATHqgDqiBjQoAAAAASUVORK5CYII=",
  2109. "cc-by-sa": common + "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",
  2110. "cc-by-nd": common + "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",
  2111. "cc-by-nc": common + "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",
  2112. "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",
  2113. "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=="
  2114. },
  2115. target;
  2116. return {
  2117. _setup: function( options ) {
  2118. var attrib = "",
  2119. license = options.license && licenses[ options.license.toLowerCase() ],
  2120. tar = "target=_blank";
  2121. // make a div to put the information into
  2122. options._container = document.createElement( "div" );
  2123. options._container.style.display = "none";
  2124. // Cache declared target
  2125. target = document.getElementById( options.target );
  2126. if ( options.nameofworkurl ) {
  2127. attrib += "<a href='" + options.nameofworkurl + "' " + tar + ">";
  2128. }
  2129. if ( options.nameofwork ) {
  2130. attrib += options.nameofwork;
  2131. }
  2132. if ( options.nameofworkurl ) {
  2133. attrib += "</a>";
  2134. }
  2135. if ( options.copyrightholderurl ) {
  2136. attrib += "<a href='" + options.copyrightholderurl + "' " + tar + ">";
  2137. }
  2138. if ( options.copyrightholder ) {
  2139. attrib += ", " + options.copyrightholder;
  2140. }
  2141. if ( options.copyrightholderurl ) {
  2142. attrib += "</a>";
  2143. }
  2144. //if the user did not specify any parameters just pull the text from the tag
  2145. if ( attrib === "" ) {
  2146. attrib = options.text;
  2147. }
  2148. if ( options.license ) {
  2149. if ( license ) {
  2150. if ( options.licenseurl ) {
  2151. attrib = "<a href='" + options.licenseurl + "' " + tar + "><img src='"+ license +"' border='0'/></a> " + attrib;
  2152. } else {
  2153. attrib = "<img src='"+ license +"' />" + attrib;
  2154. }
  2155. } else {
  2156. attrib += ", license: ";
  2157. if ( options.licenseurl ) {
  2158. attrib += "<a href='" + options.licenseurl + "' " + tar + ">" + options.license + "</a> ";
  2159. } else {
  2160. attrib += options.license;
  2161. }
  2162. }
  2163. } else if ( options.licenseurl ) {
  2164. attrib += ", <a href='" + options.licenseurl + "' " + tar + ">license</a> ";
  2165. }
  2166. options._container.innerHTML = attrib;
  2167. if ( !target && Popcorn.plugin.debug ) {
  2168. throw new Error( "target container doesn't exist" );
  2169. }
  2170. target && target.appendChild( options._container );
  2171. },
  2172. /**
  2173. * @member attribution
  2174. * The start function will be executed when the currentTime
  2175. * of the video reaches the start time provided by the
  2176. * options variable
  2177. */
  2178. start: function( event, options ) {
  2179. options._container.style.display = "inline";
  2180. },
  2181. /**
  2182. * @member attribution
  2183. * The end function will be executed when the currentTime
  2184. * of the video reaches the end time provided by the
  2185. * options variable
  2186. */
  2187. end: function( event, options ) {
  2188. options._container.style.display = "none";
  2189. },
  2190. _teardown: function( options ) {
  2191. // Cache declared target
  2192. target = document.getElementById( options.target );
  2193. target && target.removeChild( options._container );
  2194. }
  2195. };
  2196. })(),
  2197. {
  2198. about:{
  2199. name: "Popcorn Attribution Plugin",
  2200. version: "0.2",
  2201. author: "@rwaldron",
  2202. website: "github.com/rwldrn"
  2203. },
  2204. options:{
  2205. start: {
  2206. elem: "input",
  2207. type: "text",
  2208. label: "In"
  2209. },
  2210. end: {
  2211. elem: "input",
  2212. type: "text",
  2213. label: "Out"
  2214. },
  2215. nameofwork: {
  2216. elem: "input",
  2217. type: "text",
  2218. label: "Name of Work"
  2219. },
  2220. nameofworkurl: {
  2221. elem: "input",
  2222. type: "url",
  2223. label: "Url of Work"
  2224. },
  2225. copyrightholder: {
  2226. elem: "input",
  2227. type: "text",
  2228. label: "Copyright Holder"
  2229. },
  2230. copyrightholderurl: {
  2231. elem: "input",
  2232. type: "url",
  2233. label: "Copyright Holder Url"
  2234. },
  2235. license: {
  2236. elem: "input",
  2237. type: "text",
  2238. label: "License type"
  2239. },
  2240. licenseurl: {
  2241. elem: "input",
  2242. type: "url",
  2243. label: "License URL"
  2244. },
  2245. target: "attribution-container"
  2246. }
  2247. });
  2248. })( Popcorn );
  2249. // PLUGIN: Code
  2250. (function ( Popcorn ) {
  2251. /**
  2252. * Code Popcorn Plug-in
  2253. *
  2254. * Adds the ability to run arbitrary code (JavaScript functions) according to video timing.
  2255. *
  2256. * @param {Object} options
  2257. *
  2258. * Required parameters: start, end, template, data, and target.
  2259. * Optional parameter: static.
  2260. *
  2261. * start: the time in seconds when the mustache template should be rendered
  2262. * in the target div.
  2263. *
  2264. * end: the time in seconds when the rendered mustache template should be
  2265. * removed from the target div.
  2266. *
  2267. * onStart: the function to be run when the start time is reached.
  2268. *
  2269. * onFrame: [optional] a function to be run on each paint call
  2270. * (e.g., called ~60 times per second) between the start and end times.
  2271. *
  2272. * onEnd: [optional] a function to be run when the end time is reached.
  2273. *
  2274. * Example:
  2275. var p = Popcorn('#video')
  2276. // onStart function only
  2277. .code({
  2278. start: 1,
  2279. end: 4,
  2280. onStart: function( options ) {
  2281. // called on start
  2282. }
  2283. })
  2284. // onStart + onEnd only
  2285. .code({
  2286. start: 6,
  2287. end: 8,
  2288. onStart: function( options ) {
  2289. // called on start
  2290. },
  2291. onEnd: function ( options ) {
  2292. // called on end
  2293. }
  2294. })
  2295. // onStart, onEnd, onFrame
  2296. .code({
  2297. start: 10,
  2298. end: 14,
  2299. onStart: function( options ) {
  2300. // called on start
  2301. },
  2302. onFrame: function ( options ) {
  2303. // called on every paint frame between start and end.
  2304. // uses mozRequestAnimationFrame, webkitRequestAnimationFrame,
  2305. // or setTimeout with 16ms window.
  2306. },
  2307. onEnd: function ( options ) {
  2308. // called on end
  2309. }
  2310. });
  2311. *
  2312. */
  2313. Popcorn.plugin( "code" , function( options ) {
  2314. var running = false;
  2315. // Setup a proper frame interval function (60fps), favouring paint events.
  2316. var step = (function() {
  2317. var buildFrameRunner = function( runner ) {
  2318. return function( f, options ) {
  2319. var _f = function() {
  2320. running && f();
  2321. running && runner( _f );
  2322. };
  2323. _f();
  2324. };
  2325. };
  2326. // Figure out which level of browser support we have for this
  2327. if ( window.webkitRequestAnimationFrame ) {
  2328. return buildFrameRunner( window.webkitRequestAnimationFrame );
  2329. } else if ( window.mozRequestAnimationFrame ) {
  2330. return buildFrameRunner( window.mozRequestAnimationFrame );
  2331. } else {
  2332. return buildFrameRunner( function( f ) {
  2333. window.setTimeout( f, 16 );
  2334. });
  2335. }
  2336. })();
  2337. if ( !options.onStart || typeof options.onStart !== "function" ) {
  2338. if ( Popcorn.plugin.debug ) {
  2339. throw new Error( "Popcorn Code Plugin Error: onStart must be a function." );
  2340. }
  2341. options.onStart = Popcorn.nop;
  2342. }
  2343. if ( options.onEnd && typeof options.onEnd !== "function" ) {
  2344. if ( Popcorn.plugin.debug ) {
  2345. throw new Error( "Popcorn Code Plugin Error: onEnd must be a function." );
  2346. }
  2347. options.onEnd = undefined;
  2348. }
  2349. if ( options.onFrame && typeof options.onFrame !== "function" ) {
  2350. if ( Popcorn.plugin.debug ) {
  2351. throw new Error( "Popcorn Code Plugin Error: onFrame must be a function." );
  2352. }
  2353. options.onFrame = undefined;
  2354. }
  2355. return {
  2356. start: function( event, options ) {
  2357. options.onStart( options );
  2358. if ( options.onFrame ) {
  2359. running = true;
  2360. step( options.onFrame, options );
  2361. }
  2362. },
  2363. end: function( event, options ) {
  2364. if ( options.onFrame ) {
  2365. running = false;
  2366. }
  2367. if ( options.onEnd ) {
  2368. options.onEnd( options );
  2369. }
  2370. }
  2371. };
  2372. },
  2373. {
  2374. about: {
  2375. name: "Popcorn Code Plugin",
  2376. version: "0.1",
  2377. author: "David Humphrey (@humphd)",
  2378. website: "http://vocamus.net/dave"
  2379. },
  2380. options: {
  2381. start: {
  2382. elem: "input",
  2383. type: "text",
  2384. label: "In"
  2385. },
  2386. end: {
  2387. elem: "input",
  2388. type: "text",
  2389. label: "Out"
  2390. },
  2391. onStart: {
  2392. elem: "input",
  2393. type: "function",
  2394. label: "onStart"
  2395. },
  2396. onFrame: {
  2397. elem: "input",
  2398. type: "function",
  2399. label: "onFrame"
  2400. },
  2401. onEnd: {
  2402. elem: "input",
  2403. type: "function",
  2404. label: "onEnd"
  2405. }
  2406. }
  2407. });
  2408. })( Popcorn );
  2409. //PLUGIN: facebook
  2410. (function( Popcorn, global ) {
  2411. /**
  2412. * Facebook Popcorn plug-in
  2413. * Places Facebook's "social plugins" inside a div ( http://developers.facebook.com/docs/plugins/ )
  2414. * Sets options according to user input or default values
  2415. * Options parameter will need a target, type, start and end time
  2416. * Type is the name of the plugin in fbxml format. Options: LIKE (default), LIKE-BOX, ACTIVITY, FACEPILE
  2417. * Target is the id of the document element that the text needs to be attached to. This target element must exist on the DOM
  2418. * Start is the time that you want this plug-in to execute
  2419. * End is the time that you want this plug-in to stop executing
  2420. *
  2421. * Other than the mandatory four parameters, there are several optional parameters (Some options are only applicable to certain plugins)
  2422. * Action - like button will either "Like" or "Recommend". Options: recommend / like(default)
  2423. * Always_post_to_friends - live-stream posts will be always be posted on your facebook wall if true. Options: true / false(default)
  2424. * Border_color - border color of the activity feed. Names (i.e: "white") and html color codes are valid
  2425. * Colorscheme - changes the color of almost all plugins. Options: light(default) / dark
  2426. * Event_app_id - an app_id is required for the live-stream plugin
  2427. * Font - the font of the text contained in the plugin. Options: arial / segoe ui / tahoma / trebuchet ms / verdana / lucida grande
  2428. * Header - displays the title of like-box or activity feed. Options: true / false(default)
  2429. * Href - url to apply to the plugin. Default is current page
  2430. * Layout - changes the format of the 'like' count (written in english or a number in a callout).
  2431. * Options: box_count / button_count / standard(default)
  2432. * Max_rows - number of rows to disperse pictures in facepile. Default is 1
  2433. * Recommendations - shows recommendations, if any, in the bottom half of activity feed. Options: true / false(default)
  2434. * Show_faces - show pictures beside like button and like-box. Options: true / false(default)
  2435. * Site - href for activity feed. No idea why it must be "site". Default is current page
  2436. * Stream - displays a the latest posts from the specified page's wall. Options: true / false(default)
  2437. * Type - determines which plugin to create. Case insensitive
  2438. * Xid - unique identifier if more than one live-streams are on one page
  2439. *
  2440. * @param {Object} options
  2441. *
  2442. * Example:
  2443. var p = Popcorn('#video')
  2444. .facebook({
  2445. type : "LIKE-BOX",
  2446. target: "likeboxdiv",
  2447. start : 3,
  2448. end : 10,
  2449. href : "http://www.facebook.com/senecacollege",
  2450. show_faces: "true",
  2451. header: "false"
  2452. } )
  2453. * This will show how many people "like" Seneca College's Facebook page, and show their profile pictures
  2454. */
  2455. var ranOnce = false;
  2456. Popcorn.plugin( "facebook" , {
  2457. manifest: {
  2458. about: {
  2459. name: "Popcorn Facebook Plugin",
  2460. version: "0.1",
  2461. author: "Dan Ventura, Matthew Schranz: @mjschranz",
  2462. website: "dsventura.blogspot.com, mschranz.wordpress.com"
  2463. },
  2464. options: {
  2465. type: {
  2466. elem: "select",
  2467. options: [ "LIKE", "LIKE-BOX", "ACTIVITY", "FACEPILE", "LIVE-STREAM", "SEND", "COMMENTS" ],
  2468. label: "Type"
  2469. },
  2470. target: "facebook-container",
  2471. start: {
  2472. elem: "input",
  2473. type: "number",
  2474. label: "In"
  2475. },
  2476. end: {
  2477. elem: "input",
  2478. type: "number",
  2479. label: "Out"
  2480. },
  2481. // optional parameters:
  2482. font: {
  2483. elem: "input",
  2484. type: "text",
  2485. label: "font"
  2486. },
  2487. xid: {
  2488. elem: "input",
  2489. type: "text",
  2490. label: "Xid"
  2491. },
  2492. href: {
  2493. elem: "input",
  2494. type: "url",
  2495. label: "Href"
  2496. },
  2497. site: {
  2498. elem: "input",
  2499. type: "url",
  2500. label:"Site"
  2501. },
  2502. height: {
  2503. elem: "input",
  2504. type: "text",
  2505. label: "Height"
  2506. },
  2507. width: {
  2508. elem: "input",
  2509. type: "text",
  2510. label: "Width"
  2511. },
  2512. action: {
  2513. elem: "select",
  2514. options: [ "like", "recommend" ],
  2515. label: "Action"
  2516. },
  2517. stream: {
  2518. elem: "select",
  2519. options: [ "false", "true" ],
  2520. label: "Stream"
  2521. },
  2522. header: {
  2523. elem: "select",
  2524. options: [ "false", "true" ],
  2525. label: "Header"
  2526. },
  2527. layout: {
  2528. elem: "select",
  2529. options: [ "standard", "button_count", "box_count" ],
  2530. label: "Layout"
  2531. },
  2532. max_rows: {
  2533. elem: "input",
  2534. type: "text",
  2535. label: "Max_rows"
  2536. },
  2537. border_color: {
  2538. elem: "input",
  2539. type: "text",
  2540. label: "Border_color"
  2541. },
  2542. event_app_id: {
  2543. elem: "input",
  2544. type: "text",
  2545. label: "Event_app_id"
  2546. },
  2547. colorscheme: {
  2548. elem: "select",
  2549. options: [ "light", "dark" ],
  2550. label: "Colorscheme"
  2551. },
  2552. show_faces: {
  2553. elem: "select",
  2554. options: [ "false", "true" ],
  2555. label: "Showfaces"
  2556. },
  2557. recommendations: {
  2558. elem: "select",
  2559. options: [ "false", "true" ],
  2560. label: "Recommendations"
  2561. },
  2562. always_post_to_friends: {
  2563. elem: "input",
  2564. options: [ "false", "true" ],
  2565. label: "Always_post_to_friends"
  2566. },
  2567. num_posts: {
  2568. elem: "input",
  2569. type: "text",
  2570. label: "Number_of_Comments"
  2571. }
  2572. }
  2573. },
  2574. _setup: function( options ) {
  2575. var target = document.getElementById( options.target ),
  2576. _type = options.type;
  2577. // facebook script requires a div named fb-root
  2578. if ( !document.getElementById( "fb-root" ) ) {
  2579. var fbRoot = document.createElement( "div" );
  2580. fbRoot.setAttribute( "id", "fb-root" );
  2581. document.body.appendChild( fbRoot );
  2582. }
  2583. if ( !ranOnce || options.event_app_id ) {
  2584. ranOnce = true;
  2585. // initialize facebook JS SDK
  2586. Popcorn.getScript( "//connect.facebook.net/en_US/all.js" );
  2587. global.fbAsyncInit = function() {
  2588. FB.init({
  2589. appId: ( options.event_app_id || "" ),
  2590. status: true,
  2591. cookie: true,
  2592. xfbml: true
  2593. });
  2594. };
  2595. }
  2596. // Lowercase to make value consistent no matter what user inputs
  2597. _type = _type.toLowerCase();
  2598. var validType = function( type ) {
  2599. return ( [ "like", "like-box", "activity", "facepile", "live-stream", "send", "comments" ].indexOf( type ) > -1 );
  2600. };
  2601. // Checks if type is valid
  2602. if ( !validType( _type ) ) {
  2603. throw new Error( "Facebook plugin type was invalid." );
  2604. }
  2605. options._container = document.createElement( "div" );
  2606. options._container.id = "facebookdiv-" + Popcorn.guid();
  2607. options._facebookdiv = document.createElement( "fb:" + _type );
  2608. options._container.appendChild( options._facebookdiv );
  2609. // All the the "types" for facebook share largely identical attributes, for loop suffices.
  2610. // ** Credit to Rick Waldron, it's essentially all his code in this function.
  2611. // activity feed uses 'site' rather than 'href'
  2612. var attr = _type === "activity" ? "site" : "href";
  2613. options._facebookdiv.setAttribute( attr, ( options[ attr ] || document.URL ) );
  2614. // create an array of Facebook widget attributes
  2615. var fbAttrs = (
  2616. "width height layout show_faces stream header colorscheme" +
  2617. " maxrows border_color recommendations font always_post_to_friends xid" +
  2618. " num_posts"
  2619. ).split(" ");
  2620. // For Each that loops through all of our attributes adding them to the divs properties
  2621. Popcorn.forEach( fbAttrs, function( attr ) {
  2622. // Test for null/undef. Allows 0, false & ""
  2623. if ( options[ attr ] != null ) {
  2624. options._facebookdiv.setAttribute( attr, options[ attr ] );
  2625. }
  2626. });
  2627. // Checks if the plugins target container exists
  2628. if ( !target && Popcorn.plugin.debug ) {
  2629. throw new Error( "Facebook target container doesn't exist" );
  2630. }
  2631. target && target.appendChild( options._container );
  2632. },
  2633. /**
  2634. * @member facebook
  2635. * The start function will be executed when the currentTime
  2636. * of the video reaches the start time provided by the
  2637. * options variable
  2638. */
  2639. start: function( event, options ){
  2640. options._container.style.display = "";
  2641. },
  2642. /**
  2643. * @member facebook
  2644. * The end function will be executed when the currentTime
  2645. * of the video reaches the end time provided by the
  2646. * options variable
  2647. */
  2648. end: function( event, options ){
  2649. options._container.style.display = "none";
  2650. },
  2651. _teardown: function( options ){
  2652. var target = document.getElementById( options.target );
  2653. target && target.removeChild( options._container );
  2654. }
  2655. });
  2656. })( Popcorn, this );
  2657. // PLUGIN: Flickr
  2658. (function (Popcorn) {
  2659. /**
  2660. * Flickr popcorn plug-in
  2661. * Appends a users Flickr images to an element on the page.
  2662. * Options parameter will need a start, end, target and userid or username and api_key.
  2663. * Optional parameters are numberofimages, height, width, padding, and border
  2664. * Start is the time that you want this plug-in to execute (in seconds)
  2665. * End is the time that you want this plug-in to stop executing (in seconds)
  2666. * Userid is the id of who's Flickr images you wish to show
  2667. * Tags is a mutually exclusive list of image descriptor tags
  2668. * Username is the username of who's Flickr images you wish to show
  2669. * using both userid and username is redundant
  2670. * an api_key is required when using username
  2671. * Apikey is your own api key provided by Flickr
  2672. * Target is the id of the document element that the images are
  2673. * appended to, this target element must exist on the DOM
  2674. * Numberofimages specify the number of images to retreive from flickr, defaults to 4
  2675. * Height the height of the image, defaults to '50px'
  2676. * Width the width of the image, defaults to '50px'
  2677. * Padding number of pixels between images, defaults to '5px'
  2678. * Border border size in pixels around images, defaults to '0px'
  2679. *
  2680. * @param {Object} options
  2681. *
  2682. * Example:
  2683. var p = Popcorn('#video')
  2684. .flickr({
  2685. start: 5, // seconds, mandatory
  2686. end: 15, // seconds, mandatory
  2687. userid: '35034346917@N01', // optional
  2688. tags: 'dogs,cats', // optional
  2689. numberofimages: '8', // optional
  2690. height: '50px', // optional
  2691. width: '50px', // optional
  2692. padding: '5px', // optional
  2693. border: '0px', // optional
  2694. target: 'flickrdiv' // mandatory
  2695. } )
  2696. *
  2697. */
  2698. var idx = 0;
  2699. Popcorn.plugin( "flickr" , function( options ) {
  2700. var containerDiv,
  2701. target = document.getElementById( options.target ),
  2702. _userid,
  2703. _uri,
  2704. _link,
  2705. _image,
  2706. _count = options.numberofimages || 4,
  2707. _height = options.height || "50px",
  2708. _width = options.width || "50px",
  2709. _padding = options.padding || "5px",
  2710. _border = options.border || "0px";
  2711. // create a new div this way anything in the target div is left intact
  2712. // this is later populated with Flickr images
  2713. containerDiv = document.createElement( "div" );
  2714. containerDiv.id = "flickr" + idx;
  2715. containerDiv.style.width = "100%";
  2716. containerDiv.style.height = "100%";
  2717. containerDiv.style.display = "none";
  2718. idx++;
  2719. // ensure the target container the user chose exists
  2720. if ( !target && Popcorn.plugin.debug ) {
  2721. throw new Error( "flickr target container doesn't exist" );
  2722. }
  2723. target && target.appendChild( containerDiv );
  2724. // get the userid from Flickr API by using the username and apikey
  2725. var isUserIDReady = function() {
  2726. if ( !_userid ) {
  2727. _uri = "http://api.flickr.com/services/rest/?method=flickr.people.findByUsername&";
  2728. _uri += "username=" + options.username + "&api_key=" + options.apikey + "&format=json&jsoncallback=flickr";
  2729. Popcorn.getJSONP( _uri, function( data ) {
  2730. _userid = data.user.nsid;
  2731. getFlickrData();
  2732. });
  2733. } else {
  2734. setTimeout(function () {
  2735. isUserIDReady();
  2736. }, 5 );
  2737. }
  2738. };
  2739. // get the photos from Flickr API by using the user_id and/or tags
  2740. var getFlickrData = function() {
  2741. _uri = "http://api.flickr.com/services/feeds/photos_public.gne?";
  2742. if ( _userid ) {
  2743. _uri += "id=" + _userid + "&";
  2744. }
  2745. if ( options.tags ) {
  2746. _uri += "tags=" + options.tags + "&";
  2747. }
  2748. _uri += "lang=en-us&format=json&jsoncallback=flickr";
  2749. Popcorn.xhr.getJSONP( _uri, function( data ) {
  2750. var fragment = document.createElement( "p" );
  2751. fragment.innerHTML = "<p style='padding:" + _padding + ";'>" + data.title + "<p/>";
  2752. Popcorn.forEach( data.items, function ( item, i ) {
  2753. if ( i < _count ) {
  2754. _link = document.createElement( "a" );
  2755. _link.setAttribute( "href", item.link );
  2756. _link.setAttribute( "target", "_blank" );
  2757. _image = document.createElement( "img" );
  2758. _image.setAttribute( "src", item.media.m );
  2759. _image.setAttribute( "height",_height );
  2760. _image.setAttribute( "width", _width );
  2761. _image.setAttribute( "style", "border:" + _border + ";padding:" + _padding );
  2762. _link.appendChild( _image );
  2763. fragment.appendChild( _link );
  2764. } else {
  2765. return false;
  2766. }
  2767. });
  2768. containerDiv.appendChild( fragment );
  2769. });
  2770. };
  2771. if ( options.username && options.apikey ) {
  2772. isUserIDReady();
  2773. }
  2774. else {
  2775. _userid = options.userid;
  2776. getFlickrData();
  2777. }
  2778. return {
  2779. /**
  2780. * @member flickr
  2781. * The start function will be executed when the currentTime
  2782. * of the video reaches the start time provided by the
  2783. * options variable
  2784. */
  2785. start: function( event, options ) {
  2786. containerDiv.style.display = "inline";
  2787. },
  2788. /**
  2789. * @member flickr
  2790. * The end function will be executed when the currentTime
  2791. * of the video reaches the end time provided by the
  2792. * options variable
  2793. */
  2794. end: function( event, options ) {
  2795. containerDiv.style.display = "none";
  2796. },
  2797. _teardown: function( options ) {
  2798. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( containerDiv );
  2799. }
  2800. };
  2801. },
  2802. {
  2803. about: {
  2804. name: "Popcorn Flickr Plugin",
  2805. version: "0.2",
  2806. author: "Scott Downe, Steven Weerdenburg, Annasob",
  2807. website: "http://scottdowne.wordpress.com/"
  2808. },
  2809. options: {
  2810. start: {
  2811. elem: "input",
  2812. type: "number",
  2813. label: "In"
  2814. },
  2815. end: {
  2816. elem: "input",
  2817. type: "number",
  2818. label: "Out"
  2819. },
  2820. userid: {
  2821. elem: "input",
  2822. type: "text",
  2823. label: "UserID"
  2824. },
  2825. tags: {
  2826. elem: "input",
  2827. type: "text",
  2828. label: "Tags"
  2829. },
  2830. username: {
  2831. elem: "input",
  2832. type: "text",
  2833. label: "Username"
  2834. },
  2835. apikey: {
  2836. elem: "input",
  2837. type: "text",
  2838. label: "Api_key"
  2839. },
  2840. target: "flickr-container",
  2841. height: {
  2842. elem: "input",
  2843. type: "text",
  2844. label: "Height"
  2845. },
  2846. width: {
  2847. elem: "input",
  2848. type: "text",
  2849. label: "Width"
  2850. },
  2851. padding: {
  2852. elem: "input",
  2853. type: "text",
  2854. label: "Padding"
  2855. },
  2856. border: {
  2857. elem: "input",
  2858. type: "text",
  2859. label: "Border"
  2860. },
  2861. numberofimages: {
  2862. elem: "input",
  2863. type: "text",
  2864. label: "Number of Images"
  2865. }
  2866. }
  2867. });
  2868. })( Popcorn );
  2869. // PLUGIN: Footnote/Text
  2870. (function ( Popcorn ) {
  2871. /**
  2872. * Footnote popcorn plug-in
  2873. * Adds text to an element on the page.
  2874. * Options parameter will need a start, end, target and text.
  2875. * Start is the time that you want this plug-in to execute
  2876. * End is the time that you want this plug-in to stop executing
  2877. * Text is the text that you want to appear in the target
  2878. * Target is the id of the document element that the text needs to be
  2879. * attached to, this target element must exist on the DOM
  2880. *
  2881. * @param {Object} options
  2882. *
  2883. * Example:
  2884. var p = Popcorn('#video')
  2885. .footnote({
  2886. start: 5, // seconds
  2887. end: 15, // seconds
  2888. text: 'This video made exclusively for drumbeat.org',
  2889. target: 'footnotediv'
  2890. } )
  2891. *
  2892. */
  2893. Popcorn.forEach( [ "footnote", "text" ], function( name ) {
  2894. Popcorn.plugin( name , {
  2895. manifest: {
  2896. about: {
  2897. name: "Popcorn " + name + " Plugin",
  2898. version: "0.2",
  2899. author: "@annasob, @rwaldron",
  2900. website: "annasob.wordpress.com"
  2901. },
  2902. options: {
  2903. start: {
  2904. elem: "input",
  2905. type: "text",
  2906. label: "In"
  2907. },
  2908. end: {
  2909. elem: "input",
  2910. type: "text",
  2911. label: "Out"
  2912. },
  2913. text: {
  2914. elem: "input",
  2915. type: "text",
  2916. label: "Text"
  2917. },
  2918. target: name + "-container"
  2919. }
  2920. },
  2921. _setup: function( options ) {
  2922. var target = document.getElementById( options.target );
  2923. options._container = document.createElement( "div" );
  2924. options._container.style.display = "none";
  2925. options._container.innerHTML = options.text;
  2926. if ( !target && Popcorn.plugin.debug ) {
  2927. throw new Error( "target container doesn't exist" );
  2928. }
  2929. target && target.appendChild( options._container );
  2930. },
  2931. /**
  2932. * @member footnote
  2933. * The start function will be executed when the currentTime
  2934. * of the video reaches the start time provided by the
  2935. * options variable
  2936. */
  2937. start: function( event, options ){
  2938. options._container.style.display = "inline";
  2939. },
  2940. /**
  2941. * @member footnote
  2942. * The end function will be executed when the currentTime
  2943. * of the video reaches the end time provided by the
  2944. * options variable
  2945. */
  2946. end: function( event, options ){
  2947. options._container.style.display = "none";
  2948. },
  2949. _teardown: function( options ) {
  2950. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._container );
  2951. }
  2952. });
  2953. });
  2954. })( Popcorn );
  2955. // PLUGIN: GML
  2956. (function( Popcorn ) {
  2957. var gmlPlayer = function( $p ) {
  2958. var _stroke = 0,
  2959. onPt = 0,
  2960. onStroke = 0,
  2961. x = null,
  2962. y = null,
  2963. rotation = false,
  2964. strokes = 0,
  2965. play = function() {},
  2966. reset = function() {
  2967. $p.background( 0 );
  2968. onPt = onStroke = 0;
  2969. x = y = null;
  2970. },
  2971. drawLine = function( x, y, x2, y2 ) {
  2972. var _x, _y, _x2, _y2;
  2973. if ( rotation ) {
  2974. _x = y * $p.height;
  2975. _y = $p.width - ( x * $p.width );
  2976. _x2 = y2 * $p.height;
  2977. _y2 = $p.width - ( x2 * $p.width );
  2978. } else {
  2979. _x = x * $p.width;
  2980. _y = y * $p.height;
  2981. _x2 = x2 * $p.width;
  2982. _y2 = y2 * $p.height;
  2983. }
  2984. $p.stroke( 0 );
  2985. $p.strokeWeight( 13 );
  2986. $p.strokeCap( $p.SQUARE );
  2987. $p.line( _x, _y, _x2, _y2 );
  2988. $p.stroke( 255 );
  2989. $p.strokeWeight( 12 );
  2990. $p.strokeCap( $p.ROUND );
  2991. $p.line( _x, _y, _x2, _y2 );
  2992. },
  2993. seek = function( point ) {
  2994. ( point < onPt ) && reset();
  2995. while ( onPt <= point ) {
  2996. if ( !strokes ) {
  2997. return;
  2998. }
  2999. _stroke = strokes[ onStroke ] || strokes;
  3000. var pt = _stroke.pt[ onPt ],
  3001. p = onPt;
  3002. x != null && drawLine( x, y, pt.x, pt.y );
  3003. x = pt.x;
  3004. y = pt.y;
  3005. ( onPt === p ) && onPt++;
  3006. }
  3007. };
  3008. $p.draw = function() {
  3009. play();
  3010. };
  3011. $p.setup = function() {};
  3012. $p.construct = function( media, data, options ) {
  3013. var dataReady = function() {
  3014. if ( data ) {
  3015. strokes = data.gml.tag.drawing.stroke;
  3016. var drawingDur = ( options.end - options.start ) / ( strokes.pt || (function( strokes ) {
  3017. var rStrokes = [];
  3018. for ( var i = 0, sl = strokes.length; i < sl; i++ ) {
  3019. rStrokes = rStrokes.concat( strokes[ i ].pt );
  3020. }
  3021. return rStrokes;
  3022. })( strokes ) ).length;
  3023. var tag = data.gml.tag,
  3024. app_name = tag.header && tag.header.client && tag.header.client.name;
  3025. rotation = app_name === "Graffiti Analysis 2.0: DustTag" ||
  3026. app_name === "DustTag: Graffiti Analysis 2.0" ||
  3027. app_name === "Fat Tag - Katsu Edition";
  3028. play = function() {
  3029. if ( media.currentTime < options.endDrawing ) {
  3030. seek( ( media.currentTime - options.start ) / drawingDur );
  3031. }
  3032. };
  3033. return;
  3034. }
  3035. setTimeout( dataReady, 5 );
  3036. };
  3037. $p.size( 640, 640 );
  3038. $p.frameRate( 60 );
  3039. $p.smooth();
  3040. reset();
  3041. $p.noLoop();
  3042. dataReady();
  3043. };
  3044. };
  3045. /**
  3046. * Grafiti markup Language (GML) popcorn plug-in
  3047. * Renders a GML tag inside an HTML element
  3048. * Options parameter will need a mandatory start, end, target, gmltag.
  3049. * Optional parameters: none.
  3050. * Start is the time that you want this plug-in to execute
  3051. * End is the time that you want this plug-in to stop executing
  3052. * Target is the id of the document element that you wish to render the grafiti in
  3053. * gmltag is the numerical reference to a gml tag via 000000book.com
  3054. * @param {Object} options
  3055. *
  3056. * Example:
  3057. var p = Popcorn('#video')
  3058. .gml({
  3059. start: 0, // seconds
  3060. end: 5, // seconds
  3061. gmltag: '29582',
  3062. target: 'gmldiv'
  3063. });
  3064. *
  3065. */
  3066. Popcorn.plugin( "gml" , {
  3067. _setup: function( options ) {
  3068. var self = this,
  3069. target = document.getElementById( options.target );
  3070. options.endDrawing = options.endDrawing || options.end;
  3071. // create a canvas to put in the target div
  3072. options.container = document.createElement( "canvas" );
  3073. options.container.style.display = "none";
  3074. options.container.setAttribute( "id", "canvas" + options.gmltag );
  3075. if ( !target && Popcorn.plugin.debug ) {
  3076. throw new Error( "target container doesn't exist" );
  3077. }
  3078. target && target.appendChild( options.container );
  3079. var scriptReady = function() {
  3080. Popcorn.getJSONP( "//000000book.com/data/" + options.gmltag + ".json?callback=", function( data ) {
  3081. options.pjsInstance = new Processing( options.container, gmlPlayer );
  3082. options.pjsInstance.construct( self.media, data, options );
  3083. options._running && options.pjsInstance.loop();
  3084. }, false );
  3085. };
  3086. if ( !window.Processing ) {
  3087. Popcorn.getScript( "//processingjs.org/content/download/processing-js-1.3.0/processing-1.3.0.min.js", scriptReady );
  3088. } else {
  3089. scriptReady();
  3090. }
  3091. },
  3092. /**
  3093. * @member gml
  3094. * The start function will be executed when the currentTime
  3095. * of the video reaches the start time provided by the
  3096. * options variable
  3097. */
  3098. start: function( event, options ) {
  3099. options.pjsInstance && options.pjsInstance.loop();
  3100. options.container.style.display = "block";
  3101. },
  3102. /**
  3103. * @member gml
  3104. * The end function will be executed when the currentTime
  3105. * of the video reaches the end time provided by the
  3106. * options variable
  3107. */
  3108. end: function( event, options ) {
  3109. options.pjsInstance && options.pjsInstance.noLoop();
  3110. options.container.style.display = "none";
  3111. },
  3112. _teardown: function( options ) {
  3113. options.pjsInstance && options.pjsInstance.exit();
  3114. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.container );
  3115. }
  3116. });
  3117. })( Popcorn );
  3118. // PLUGIN: Google Feed
  3119. (function ( Popcorn ) {
  3120. var i = 1,
  3121. scriptLoaded = false,
  3122. dynamicFeedLoad = function() {
  3123. var dontLoad = false,
  3124. k = 0,
  3125. links = document.getElementsByTagName( "link" ),
  3126. len = links.length,
  3127. head = document.head || document.getElementsByTagName( "head" )[ 0 ],
  3128. css = document.createElement( "link" ),
  3129. resource = "//www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.";
  3130. if ( !window.GFdynamicFeedControl ) {
  3131. Popcorn.getScript( resource + "js", function() {
  3132. scriptLoaded = true;
  3133. });
  3134. } else {
  3135. scriptLoaded = true;
  3136. }
  3137. // Checking if the css file is already included
  3138. for ( ; k < len; k++ ){
  3139. if ( links[ k ].href === resource + "css" ) {
  3140. dontLoad = true;
  3141. }
  3142. }
  3143. if ( !dontLoad ) {
  3144. css.type = "text/css";
  3145. css.rel = "stylesheet";
  3146. css.href = resource + "css";
  3147. head.insertBefore( css, head.firstChild );
  3148. }
  3149. };
  3150. if ( !window.google ) {
  3151. Popcorn.getScript( "//www.google.com/jsapi", function() {
  3152. google.load( "feeds", "1", {
  3153. callback: function () {
  3154. dynamicFeedLoad();
  3155. }
  3156. });
  3157. });
  3158. } else {
  3159. dynamicFeedLoad();
  3160. }
  3161. /**
  3162. * googlefeed popcorn plug-in
  3163. * Adds a feed from the specified blog url at the target div
  3164. * Options parameter will need a start, end, target, url and title
  3165. * -Start is the time that you want this plug-in to execute
  3166. * -End is the time that you want this plug-in to stop executing
  3167. * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
  3168. * -Url is the url of the blog's feed you are trying to access
  3169. * -Title is the title of the blog you want displayed above the feed
  3170. * -Orientation is the orientation of the blog, accepts either Horizontal or Vertical, defaults to Vertical
  3171. * @param {Object} options
  3172. *
  3173. * Example:
  3174. var p = Popcorn("#video")
  3175. .googlefeed({
  3176. start: 5, // seconds
  3177. end: 15, // seconds
  3178. target: "map",
  3179. url: "http://zenit.senecac.on.ca/~chris.tyler/planet/rss20.xml",
  3180. title: "Planet Feed"
  3181. } )
  3182. *
  3183. */
  3184. Popcorn.plugin( "googlefeed", function( options ) {
  3185. // create a new div and append it to the parent div so nothing
  3186. // that already exists in the parent div gets overwritten
  3187. var newdiv = document.createElement( "div" ),
  3188. target = document.getElementById( options.target ),
  3189. initialize = function() {
  3190. //ensure that the script has been loaded
  3191. if ( !scriptLoaded ) {
  3192. setTimeout( function () {
  3193. initialize();
  3194. }, 5 );
  3195. } else {
  3196. // Create the feed control using the user entered url and title
  3197. options.feed = new GFdynamicFeedControl( options.url, newdiv, {
  3198. vertical: options.orientation.toLowerCase() === "vertical" ? true : false,
  3199. horizontal: options.orientation.toLowerCase() === "horizontal" ? true : false,
  3200. title: options.title = options.title || "Blog"
  3201. });
  3202. }
  3203. };
  3204. // Default to vertical orientation if empty or incorrect input
  3205. if( !options.orientation || ( options.orientation.toLowerCase() !== "vertical" &&
  3206. options.orientation.toLowerCase() !== "horizontal" ) ) {
  3207. options.orientation = "vertical";
  3208. }
  3209. newdiv.style.display = "none";
  3210. newdiv.id = "_feed" + i;
  3211. newdiv.style.width = "100%";
  3212. newdiv.style.height = "100%";
  3213. i++;
  3214. if ( !target && Popcorn.plugin.debug ) {
  3215. throw new Error( "target container doesn't exist" );
  3216. }
  3217. target && target.appendChild( newdiv );
  3218. initialize();
  3219. return {
  3220. /**
  3221. * @member webpage
  3222. * The start function will be executed when the currentTime
  3223. * of the video reaches the start time provided by the
  3224. * options variable
  3225. */
  3226. start: function( event, options ){
  3227. newdiv.setAttribute( "style", "display:inline" );
  3228. },
  3229. /**
  3230. * @member webpage
  3231. * The end function will be executed when the currentTime
  3232. * of the video reaches the end time provided by the
  3233. * options variable
  3234. */
  3235. end: function( event, options ){
  3236. newdiv.setAttribute( "style", "display:none" );
  3237. },
  3238. _teardown: function( options ) {
  3239. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( newdiv );
  3240. delete options.feed;
  3241. }
  3242. };
  3243. },
  3244. {
  3245. about: {
  3246. name: "Popcorn Google Feed Plugin",
  3247. version: "0.1",
  3248. author: "David Seifried",
  3249. website: "dseifried.wordpress.com"
  3250. },
  3251. options: {
  3252. start: {
  3253. elem: "input",
  3254. type: "text",
  3255. label: "In"
  3256. },
  3257. end: {
  3258. elem: "input",
  3259. type: "text",
  3260. label: "Out"
  3261. },
  3262. target: "feed-container",
  3263. url: {
  3264. elem: "input",
  3265. type: "url",
  3266. label: "url"
  3267. },
  3268. title: {
  3269. elem: "input",
  3270. type: "text",
  3271. label: "title"
  3272. },
  3273. orientation: {
  3274. elem: "select",
  3275. options: [ "Vertical", "Horizontal" ],
  3276. label: "orientation"
  3277. }
  3278. }
  3279. });
  3280. })( Popcorn );
  3281. // PLUGIN: Google Maps
  3282. var googleCallback;
  3283. (function ( Popcorn ) {
  3284. var i = 1,
  3285. _mapFired = false,
  3286. _mapLoaded = false,
  3287. geocoder, loadMaps;
  3288. //google api callback
  3289. googleCallback = function ( data ) {
  3290. // ensure all of the maps functions needed are loaded
  3291. // before setting _maploaded to true
  3292. if ( typeof google !== "undefined" && google.maps && google.maps.Geocoder && google.maps.LatLng ) {
  3293. geocoder = new google.maps.Geocoder();
  3294. _mapLoaded = true;
  3295. } else {
  3296. setTimeout(function () {
  3297. googleCallback( data );
  3298. }, 1);
  3299. }
  3300. };
  3301. // function that loads the google api
  3302. loadMaps = function () {
  3303. // for some reason the Google Map API adds content to the body
  3304. if ( document.body ) {
  3305. _mapFired = true;
  3306. Popcorn.getScript( "//maps.google.com/maps/api/js?sensor=false&callback=googleCallback" );
  3307. } else {
  3308. setTimeout(function () {
  3309. loadMaps();
  3310. }, 1);
  3311. }
  3312. };
  3313. /**
  3314. * googlemap popcorn plug-in
  3315. * Adds a map to the target div centered on the location specified by the user
  3316. * Options parameter will need a start, end, target, type, zoom, lat and lng, and location
  3317. * -Start is the time that you want this plug-in to execute
  3318. * -End is the time that you want this plug-in to stop executing
  3319. * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
  3320. * -Type [optional] either: HYBRID (default), ROADMAP, SATELLITE, TERRAIN, STREETVIEW
  3321. * -Zoom [optional] defaults to 0
  3322. * -Heading [optional] STREETVIEW orientation of camera in degrees relative to true north (0 north, 90 true east, ect)
  3323. * -Pitch [optional] STREETVIEW vertical orientation of the camera (between 1 and 3 is recommended)
  3324. * -Lat and Lng: the coordinates of the map must be present if location is not specified.
  3325. * -Location: the adress you want the map to display, must be present if lat and lng are not specified.
  3326. * Note: using location requires extra loading time, also not specifying both lat/lng and location will
  3327. * cause and error.
  3328. *
  3329. * Tweening works using the following specifications:
  3330. * -location is the start point when using an auto generated route
  3331. * -tween when used in this context is a string which specifies the end location for your route
  3332. * Note that both location and tween must be present when using an auto generated route, or the map will not tween
  3333. * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds )
  3334. * Heading, Zoom, and Pitch streetview values are also used in tweening with the autogenerated route
  3335. *
  3336. * -tween is an array of objects, each containing data for one frame of a tween
  3337. * -position is an object with has two paramaters, lat and lng, both which are mandatory for a tween to work
  3338. * -pov is an object which houses heading, pitch, and zoom paramters, which are all optional, if undefined, these values default to 0
  3339. * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds )
  3340. *
  3341. * @param {Object} options
  3342. *
  3343. * Example:
  3344. var p = Popcorn("#video")
  3345. .googlemap({
  3346. start: 5, // seconds
  3347. end: 15, // seconds
  3348. type: "ROADMAP",
  3349. target: "map",
  3350. lat: 43.665429,
  3351. lng: -79.403323
  3352. } )
  3353. *
  3354. */
  3355. Popcorn.plugin( "googlemap", function ( options ) {
  3356. var newdiv, map, location,
  3357. target = document.getElementById( options.target );
  3358. // if this is the firest time running the plugins
  3359. // call the function that gets the sctipt
  3360. if ( !_mapFired ) {
  3361. loadMaps();
  3362. }
  3363. // create a new div this way anything in the target div is left intact
  3364. // this is later passed on to the maps api
  3365. newdiv = document.createElement( "div" );
  3366. newdiv.id = "actualmap" + i;
  3367. newdiv.style.width = "100%";
  3368. newdiv.style.height = "100%";
  3369. i++;
  3370. // ensure the target container the user chose exists
  3371. if ( !target && Popcorn.plugin.debug ) {
  3372. throw new Error( "target container doesn't exist" );
  3373. }
  3374. target && target.appendChild( newdiv );
  3375. // ensure that google maps and its functions are loaded
  3376. // before setting up the map parameters
  3377. var isMapReady = function () {
  3378. if ( _mapLoaded ) {
  3379. if ( options.location ) {
  3380. // calls an anonymous google function called on separate thread
  3381. geocoder.geocode({
  3382. "address": options.location
  3383. }, function ( results, status ) {
  3384. if ( status === google.maps.GeocoderStatus.OK ) {
  3385. options.lat = results[ 0 ].geometry.location.lat();
  3386. options.lng = results[ 0 ].geometry.location.lng();
  3387. location = new google.maps.LatLng( options.lat, options.lng );
  3388. map = new google.maps.Map( newdiv, {
  3389. mapTypeId: google.maps.MapTypeId[ options.type ] || google.maps.MapTypeId.HYBRID
  3390. });
  3391. map.getDiv().style.display = "none";
  3392. }
  3393. });
  3394. } else {
  3395. location = new google.maps.LatLng( options.lat, options.lng );
  3396. map = new google.maps.Map( newdiv, {
  3397. mapTypeId: google.maps.MapTypeId[ options.type ] || google.maps.MapTypeId.HYBRID
  3398. });
  3399. map.getDiv().style.display = "none";
  3400. }
  3401. } else {
  3402. setTimeout(function () {
  3403. isMapReady();
  3404. }, 5);
  3405. }
  3406. };
  3407. isMapReady();
  3408. return {
  3409. /**
  3410. * @member webpage
  3411. * The start function will be executed when the currentTime
  3412. * of the video reaches the start time provided by the
  3413. * options variable
  3414. */
  3415. start: function( event, options ) {
  3416. var that = this,
  3417. sView;
  3418. // ensure the map has been initialized in the setup function above
  3419. var isMapSetup = function() {
  3420. if ( map ) {
  3421. map.getDiv().style.display = "block";
  3422. // reset the location and zoom just in case the user plaid with the map
  3423. google.maps.event.trigger( map, "resize" );
  3424. map.setCenter( location );
  3425. // make sure options.zoom is a number
  3426. if ( options.zoom && typeof options.zoom !== "number" ) {
  3427. options.zoom = +options.zoom;
  3428. }
  3429. options.zoom = options.zoom || 8; // default to 8
  3430. map.setZoom( options.zoom );
  3431. //Make sure heading is a number
  3432. if ( options.heading && typeof options.heading !== "number" ) {
  3433. options.heading = +options.heading;
  3434. }
  3435. //Make sure pitch is a number
  3436. if ( options.pitch && typeof options.pitch !== "number" ) {
  3437. options.pitch = +options.pitch;
  3438. }
  3439. if ( options.type === "STREETVIEW" ) {
  3440. // Switch this map into streeview mode
  3441. map.setStreetView(
  3442. // Pass a new StreetViewPanorama instance into our map
  3443. sView = new google.maps.StreetViewPanorama( newdiv, {
  3444. position: location,
  3445. pov: {
  3446. heading: options.heading = options.heading || 0,
  3447. pitch: options.pitch = options.pitch || 0,
  3448. zoom: options.zoom
  3449. }
  3450. })
  3451. );
  3452. // Function to handle tweening using a set timeout
  3453. var tween = function( rM, t ) {
  3454. var computeHeading = google.maps.geometry.spherical.computeHeading;
  3455. setTimeout(function() {
  3456. var current_time = that.media.currentTime;
  3457. // Checks whether this is a generated route or not
  3458. if ( typeof options.tween === "object" ) {
  3459. for ( var i = 0, m = rM.length; i < m; i++ ) {
  3460. var waypoint = rM[ i ];
  3461. // Checks if this position along the tween should be displayed or not
  3462. if ( current_time >= ( waypoint.interval * ( i + 1 ) ) / 1000 &&
  3463. ( current_time <= ( waypoint.interval * ( i + 2 ) ) / 1000 ||
  3464. current_time >= waypoint.interval * ( m ) / 1000 ) ) {
  3465. sView3.setPosition( new google.maps.LatLng( waypoint.position.lat, waypoint.position.lng ) );
  3466. sView3.setPov({
  3467. heading: waypoint.pov.heading || computeHeading( waypoint, rM[ i + 1 ] ) || 0,
  3468. zoom: waypoint.pov.zoom || 0,
  3469. pitch: waypoint.pov.pitch || 0
  3470. });
  3471. }
  3472. }
  3473. // Calls the tween function again at the interval set by the user
  3474. tween( rM, rM[ 0 ].interval );
  3475. } else {
  3476. for ( var k = 0, l = rM.length; k < l; k++ ) {
  3477. var interval = options.interval;
  3478. if( current_time >= (interval * ( k + 1 ) ) / 1000 &&
  3479. ( current_time <= (interval * ( k + 2 ) ) / 1000 ||
  3480. current_time >= interval * ( l ) / 1000 ) ) {
  3481. sView2.setPov({
  3482. heading: computeHeading( rM[ k ], rM[ k + 1 ] ) || 0,
  3483. zoom: options.zoom,
  3484. pitch: options.pitch || 0
  3485. });
  3486. sView2.setPosition( checkpoints[ k ] );
  3487. }
  3488. }
  3489. tween( checkpoints, options.interval );
  3490. }
  3491. }, t );
  3492. };
  3493. // Determines if we should use hardcoded values ( using options.tween ),
  3494. // or if we should use a start and end location and let google generate
  3495. // the route for us
  3496. if ( options.location && typeof options.tween === "string" ) {
  3497. // Creating another variable to hold the streetview map for tweening,
  3498. // Doing this because if there was more then one streetview map, the tweening would sometimes appear in other maps
  3499. var sView2 = sView;
  3500. // Create an array to store all the lat/lang values along our route
  3501. var checkpoints = [];
  3502. // Creates a new direction service, later used to create a route
  3503. var directionsService = new google.maps.DirectionsService();
  3504. // Creates a new direction renderer using the current map
  3505. // This enables us to access all of the route data that is returned to us
  3506. var directionsDisplay = new google.maps.DirectionsRenderer( sView2 );
  3507. var request = {
  3508. origin: options.location,
  3509. destination: options.tween,
  3510. travelMode: google.maps.TravelMode.DRIVING
  3511. };
  3512. // Create the route using the direction service and renderer
  3513. directionsService.route( request, function( response, status ) {
  3514. if ( status == google.maps.DirectionsStatus.OK ) {
  3515. directionsDisplay.setDirections( response );
  3516. showSteps( response, that );
  3517. }
  3518. });
  3519. var showSteps = function ( directionResult, that ) {
  3520. // Push new google map lat and lng values into an array from our list of lat and lng values
  3521. var routes = directionResult.routes[ 0 ].overview_path;
  3522. for ( var j = 0, k = routes.length; j < k; j++ ) {
  3523. checkpoints.push( new google.maps.LatLng( routes[ j ].lat(), routes[ j ].lng() ) );
  3524. }
  3525. // Check to make sure the interval exists, if not, set to a default of 1000
  3526. options.interval = options.interval || 1000;
  3527. tween( checkpoints, 10 );
  3528. };
  3529. } else if ( typeof options.tween === "object" ) {
  3530. // Same as the above to stop streetview maps from overflowing into one another
  3531. var sView3 = sView;
  3532. for ( var i = 0, l = options.tween.length; i < l; i++ ) {
  3533. // Make sure interval exists, if not, set to 1000
  3534. options.tween[ i ].interval = options.tween[ i ].interval || 1000;
  3535. tween( options.tween, 10 );
  3536. }
  3537. }
  3538. }
  3539. } else {
  3540. setTimeout(function () {
  3541. isMapSetup();
  3542. }, 13);
  3543. }
  3544. };
  3545. isMapSetup();
  3546. },
  3547. /**
  3548. * @member webpage
  3549. * The end function will be executed when the currentTime
  3550. * of the video reaches the end time provided by the
  3551. * options variable
  3552. */
  3553. end: function ( event, options ) {
  3554. // if the map exists hide it do not delete the map just in
  3555. // case the user seeks back to time b/w start and end
  3556. if ( map ) {
  3557. map.getDiv().style.display = "none";
  3558. }
  3559. },
  3560. _teardown: function ( options ) {
  3561. var target = document.getElementById( options.target );
  3562. // the map must be manually removed
  3563. target && target.removeChild( newdiv );
  3564. newdiv = map = location = null;
  3565. }
  3566. };
  3567. }, {
  3568. about: {
  3569. name: "Popcorn Google Map Plugin",
  3570. version: "0.1",
  3571. author: "@annasob",
  3572. website: "annasob.wordpress.com"
  3573. },
  3574. options: {
  3575. start: {
  3576. elem: "input",
  3577. type: "text",
  3578. label: "In"
  3579. },
  3580. end: {
  3581. elem: "input",
  3582. type: "text",
  3583. label: "Out"
  3584. },
  3585. target: "map-container",
  3586. type: {
  3587. elem: "select",
  3588. options: [ "ROADMAP", "SATELLITE", "STREETVIEW", "HYBRID", "TERRAIN" ],
  3589. label: "Type"
  3590. },
  3591. zoom: {
  3592. elem: "input",
  3593. type: "text",
  3594. label: "Zoom"
  3595. },
  3596. lat: {
  3597. elem: "input",
  3598. type: "text",
  3599. label: "Lat"
  3600. },
  3601. lng: {
  3602. elem: "input",
  3603. type: "text",
  3604. label: "Lng"
  3605. },
  3606. location: {
  3607. elem: "input",
  3608. type: "text",
  3609. label: "Location"
  3610. },
  3611. heading: {
  3612. elem: "input",
  3613. type: "text",
  3614. label: "Heading"
  3615. },
  3616. pitch: {
  3617. elem: "input",
  3618. type: "text",
  3619. label: "Pitch"
  3620. }
  3621. }
  3622. });
  3623. })( Popcorn );
  3624. // PLUGIN: IMAGE
  3625. (function ( Popcorn ) {
  3626. /**
  3627. * Images popcorn plug-in
  3628. * Shows an image element
  3629. * Options parameter will need a start, end, href, target and src.
  3630. * Start is the time that you want this plug-in to execute
  3631. * End is the time that you want this plug-in to stop executing
  3632. * href is the url of the destination of a link - optional
  3633. * Target is the id of the document element that the iframe needs to be attached to,
  3634. * this target element must exist on the DOM
  3635. * Src is the url of the image that you want to display
  3636. * text is the overlayed text on the image - optional
  3637. *
  3638. * @param {Object} options
  3639. *
  3640. * Example:
  3641. var p = Popcorn('#video')
  3642. .image({
  3643. start: 5, // seconds
  3644. end: 15, // seconds
  3645. href: 'http://www.drumbeat.org/',
  3646. src: 'http://www.drumbeat.org/sites/default/files/domain-2/drumbeat_logo.png',
  3647. text: 'DRUMBEAT',
  3648. target: 'imagediv'
  3649. } )
  3650. *
  3651. */
  3652. Popcorn.plugin( "image", {
  3653. manifest: {
  3654. about: {
  3655. name: "Popcorn image Plugin",
  3656. version: "0.1",
  3657. author: "Scott Downe",
  3658. website: "http://scottdowne.wordpress.com/"
  3659. },
  3660. options: {
  3661. start: {
  3662. elem: "input",
  3663. type: "number",
  3664. label: "In"
  3665. },
  3666. end: {
  3667. elem: "input",
  3668. type: "number",
  3669. label: "Out"
  3670. },
  3671. href: {
  3672. elem: "input",
  3673. type: "url",
  3674. label: "Link URL"
  3675. },
  3676. target: "image-container",
  3677. src: {
  3678. elem: "input",
  3679. type: "url",
  3680. label: "Source URL"
  3681. },
  3682. text: {
  3683. elem: "input",
  3684. type: "text",
  3685. label: "TEXT"
  3686. }
  3687. }
  3688. },
  3689. _setup: function( options ) {
  3690. var img = document.createElement( "img" ),
  3691. target = document.getElementById( options.target );
  3692. options.link = document.createElement( "a" );
  3693. options.link.style.position = "relative";
  3694. options.link.style.textDecoration = "none";
  3695. if ( !target && Popcorn.plugin.debug ) {
  3696. throw new Error( "target container doesn't exist" );
  3697. }
  3698. // add the widget's div to the target div
  3699. target && target.appendChild( options.link );
  3700. img.addEventListener( "load", function() {
  3701. // borders look really bad, if someone wants it they can put it on their div target
  3702. img.style.borderStyle = "none";
  3703. if ( options.href ) {
  3704. options.link.href = options.href;
  3705. }
  3706. options.link.target = "_blank";
  3707. var fontHeight = ( img.height / 12 ) + "px",
  3708. divText = document.createElement( "div" );
  3709. Popcorn.extend( divText.style, {
  3710. color: "black",
  3711. fontSize: fontHeight,
  3712. fontWeight: "bold",
  3713. position: "relative",
  3714. textAlign: "center",
  3715. width: img.width + "px",
  3716. zIndex: "10"
  3717. });
  3718. divText.innerHTML = options.text || "";
  3719. options.link.appendChild( divText );
  3720. options.link.appendChild( img );
  3721. divText.style.top = ( img.height / 2 ) - ( divText.offsetHeight / 2 ) + "px";
  3722. options.link.style.display = "none";
  3723. }, false );
  3724. img.src = options.src;
  3725. },
  3726. /**
  3727. * @member image
  3728. * The start function will be executed when the currentTime
  3729. * of the video reaches the start time provided by the
  3730. * options variable
  3731. */
  3732. start: function( event, options ) {
  3733. options.link.style.display = "block";
  3734. },
  3735. /**
  3736. * @member image
  3737. * The end function will be executed when the currentTime
  3738. * of the video reaches the end time provided by the
  3739. * options variable
  3740. */
  3741. end: function( event, options ) {
  3742. options.link.style.display = "none";
  3743. },
  3744. _teardown: function( options ) {
  3745. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.link );
  3746. }
  3747. });
  3748. })( Popcorn );
  3749. // PLUGIN: LASTFM
  3750. (function ( Popcorn ) {
  3751. var _artists = {},
  3752. lastFMcallback = function( data ) {
  3753. if ( data.artist ) {
  3754. var htmlString = "";
  3755. htmlString = "<h3>" + data.artist.name + "</h3>";
  3756. 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>";
  3757. htmlString += "<p>" + data.artist.bio.summary + "</p>";
  3758. htmlString += "<hr /><p><h4>Tags</h4><ul>";
  3759. Popcorn.forEach( data.artist.tags.tag, function( val, i) {
  3760. htmlString += "<li><a href='" + val.url + "'>" + val.name + "</a></li>";
  3761. });
  3762. htmlString += "</ul></p>";
  3763. htmlString += "<hr /><p><h4>Similar</h4><ul>";
  3764. Popcorn.forEach( data.artist.similar.artist, function( val, i) {
  3765. htmlString += "<li><a href='" + val.url + "'>" + val.name + "</a></li>";
  3766. });
  3767. htmlString += "</ul></p>";
  3768. _artists[ data.artist.name.toLowerCase() ].htmlString = htmlString;
  3769. }
  3770. };
  3771. /**
  3772. * LastFM popcorn plug-in
  3773. * Appends information about a LastFM artist to an element on the page.
  3774. * Options parameter will need a start, end, target, artist and apikey.
  3775. * Start is the time that you want this plug-in to execute
  3776. * End is the time that you want this plug-in to stop executing
  3777. * Artist is the name of who's LastFM information you wish to show
  3778. * Target is the id of the document element that the images are
  3779. * appended to, this target element must exist on the DOM
  3780. * ApiKey is the API key registered with LastFM for use with their API
  3781. *
  3782. * @param {Object} options
  3783. *
  3784. * Example:
  3785. var p = Popcorn('#video')
  3786. .lastfm({
  3787. start: 5, // seconds, mandatory
  3788. end: 15, // seconds, mandatory
  3789. artist: 'yacht', // mandatory
  3790. target: 'lastfmdiv', // mandatory
  3791. apikey: '1234567890abcdef1234567890abcdef' // mandatory
  3792. } )
  3793. *
  3794. */
  3795. Popcorn.plugin( "lastfm" , (function(){
  3796. return {
  3797. _setup: function( options ) {
  3798. options._container = document.createElement( "div" );
  3799. options._container.style.display = "none";
  3800. options._container.innerHTML = "";
  3801. options.artist = options.artist && options.artist.toLowerCase() || "";
  3802. var target = document.getElementById( options.target );
  3803. if ( !target && Popcorn.plugin.debug ) {
  3804. throw new Error( "target container doesn't exist" );
  3805. }
  3806. target && target.appendChild( options._container );
  3807. if ( !_artists[ options.artist ] ) {
  3808. _artists[ options.artist ] = {
  3809. count: 0,
  3810. htmlString: "Unknown Artist"
  3811. };
  3812. Popcorn.getJSONP( "//ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=" + options.artist + "&api_key=" + options.apikey + "&format=json&callback=lastFMcallback", lastFMcallback, false );
  3813. }
  3814. _artists[ options.artist ].count++;
  3815. },
  3816. /**
  3817. * @member LastFM
  3818. * The start function will be executed when the currentTime
  3819. * of the video reaches the start time provided by the
  3820. * options variable
  3821. */
  3822. start: function( event, options ) {
  3823. options._container.innerHTML = _artists[ options.artist ].htmlString;
  3824. options._container.style.display = "inline";
  3825. },
  3826. /**
  3827. * @member LastFM
  3828. * The end function will be executed when the currentTime
  3829. * of the video reaches the end time provided by the
  3830. * options variable
  3831. */
  3832. end: function( event, options ) {
  3833. options._container.style.display = "none";
  3834. options._container.innerHTML = "";
  3835. },
  3836. _teardown: function( options ) {
  3837. // cleaning possible reference to _artist array;
  3838. --_artists[ options.artist ].count || delete _artists[ options.artist ];
  3839. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._container );
  3840. }
  3841. };
  3842. })(),
  3843. {
  3844. about:{
  3845. name: "Popcorn LastFM Plugin",
  3846. version: "0.1",
  3847. author: "Steven Weerdenburg",
  3848. website: "http://sweerdenburg.wordpress.com/"
  3849. },
  3850. options: {
  3851. start: {
  3852. elem: "input",
  3853. type: "text",
  3854. label: "In"
  3855. },
  3856. end: {
  3857. elem: "input",
  3858. type: "text",
  3859. label: "Out"
  3860. },
  3861. target: "lastfm-container",
  3862. artist: {
  3863. elem: "input",
  3864. type: "text",
  3865. label: "Artist"
  3866. }
  3867. }
  3868. });
  3869. })( Popcorn );
  3870. //PLUGIN: linkedin
  3871. (function ( Popcorn ){
  3872. /**
  3873. * LinkedIn Popcorn plug-in
  3874. * Places a LinkedIn plugin inside a div ( http://developers.facebook.com/docs/plugins/ )
  3875. * Options parameter will need a start, end, target, type, and an api key
  3876. * Optional parameters are url, counter, format, companyid, and productid
  3877. * Start is the time that you want this plug-in to execute
  3878. * End is the time that you want this plug-in to stop executing
  3879. * Target is the id of the document element that the plugin needs to be attached to, this target element must exist on the DOM
  3880. * Type is the name of the plugin, options are share, memberprofile, companyinsider, companyprofile, or recommendproduct
  3881. * Apikey is your own api key from obtained from https://www.linkedin.com/secure/developer
  3882. * Url is the desired url to share via LinkedIn. Defaults to the current page if no url is specified
  3883. * Counter is the position where the counter will be positioned. This is used if the type is "share" or "recommendproduct"
  3884. * The options are right and top (don't include this option if you do not want a counter)
  3885. * Format is the data format of the member and company profile plugins. The options are inlined, hover, and click. Defaults to inline
  3886. * Companyid must be specified if the type is "companyprofile," "companyinsider," or "recommendproduct"
  3887. * Productid must be specified if the type is "recommendproduct"
  3888. *
  3889. * @param {Object} options
  3890. *
  3891. * Example:
  3892. * <script src="popcorn.linkedin.js"></script>
  3893. * ...
  3894. * var p = Popcorn("#video")
  3895. * .linkedin({
  3896. * type: "share",
  3897. * url: "http://www.google.ca",
  3898. * counter: "right",
  3899. * target: "sharediv"
  3900. * apikey: "ZOLRI2rzQS_oaXELpPF0aksxwFFEvoxAFZRLfHjaAhcGPfOX0Ds4snkJpWwKs8gk",
  3901. * start: 1,
  3902. * end: 3
  3903. * })
  3904. *
  3905. * 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,
  3906. * with the number of people (counter) displayed to the right of the share plugin.
  3907. */
  3908. Popcorn.plugin( "linkedin", {
  3909. manifest: {
  3910. about: {
  3911. name: "Popcorn LinkedIn Plugin",
  3912. version: "0.1",
  3913. author: "Dan Ventura",
  3914. website: "dsventura.blogspot.com"
  3915. },
  3916. options: {
  3917. type: {
  3918. elem: "input",
  3919. type: "text",
  3920. label: "Type"
  3921. },
  3922. url: {
  3923. elem: "input",
  3924. type: "text",
  3925. label: "URL"
  3926. },
  3927. apikey: {
  3928. elem: "input",
  3929. type: "text",
  3930. label: "API Key"
  3931. },
  3932. counter: {
  3933. elem: "input",
  3934. type: "text",
  3935. label: "Counter"
  3936. },
  3937. memberid: {
  3938. elem: "input",
  3939. type: "text",
  3940. label: "Member ID"
  3941. },
  3942. format: {
  3943. elem: "input",
  3944. type: "text",
  3945. label: "Format"
  3946. },
  3947. companyid: {
  3948. elem: "input",
  3949. type: "text",
  3950. label: "Company ID"
  3951. },
  3952. modules: {
  3953. elem: "input",
  3954. type: "text",
  3955. label: "Modules"
  3956. },
  3957. productid: {
  3958. elem: "input",
  3959. type: "text",
  3960. label: "productid"
  3961. },
  3962. related: {
  3963. elem: "input",
  3964. type: "text",
  3965. label: "related"
  3966. },
  3967. start: {
  3968. elem: "input",
  3969. type: "text",
  3970. label: "In"
  3971. },
  3972. end: {
  3973. elem: "input",
  3974. type: "text",
  3975. label: "Out"
  3976. },
  3977. target: "linkedin-container"
  3978. }
  3979. },
  3980. _setup: function( options ) {
  3981. var apikey = options.apikey,
  3982. target = document.getElementById( options.target ),
  3983. script = document.createElement( "script" );
  3984. Popcorn.getScript( "//platform.linkedin.com/in.js" );
  3985. options._container = document.createElement( "div" );
  3986. options._container.appendChild( script );
  3987. if ( apikey ) {
  3988. script.innerHTML = "api_key: " + apikey;
  3989. }
  3990. options.type = options.type && options.type.toLowerCase() || "";
  3991. // Replace the LinkedIn plugin's error message to something more helpful
  3992. var errorMsg = function() {
  3993. options._container = document.createElement( "p" );
  3994. options._container.innerHTML = "Plugin requires a valid <a href='https://www.linkedin.com/secure/developer'>apikey</a>";
  3995. if ( !target && Popcorn.plugin.debug ) {
  3996. throw ( "target container doesn't exist" );
  3997. }
  3998. target && target.appendChild( options._container );
  3999. };
  4000. var setOptions = (function ( options ) {
  4001. return {
  4002. share: function () {
  4003. script.setAttribute( "type", "IN/Share" );
  4004. if ( options.counter ) {
  4005. script.setAttribute( "data-counter", options.counter );
  4006. }
  4007. if ( options.url ) {
  4008. script.setAttribute( "data-url", options.url );
  4009. }
  4010. },
  4011. memberprofile: function () {
  4012. script.setAttribute( "type", "IN/MemberProfile" );
  4013. script.setAttribute( "data-id", ( options.memberid ) );
  4014. script.setAttribute( "data-format", ( options.format || "inline" ) );
  4015. if ( options.text && options.format.toLowerCase() !== "inline" ) {
  4016. script.setAttribute( "data-text", options.text );
  4017. }
  4018. },
  4019. companyinsider: function () {
  4020. script.setAttribute( "type", "IN/CompanyInsider" );
  4021. script.setAttribute( "data-id", options.companyid );
  4022. if( options.modules ) {
  4023. options._container.setAttribute( "data-modules", options.modules );
  4024. }
  4025. },
  4026. companyprofile: function () {
  4027. script.setAttribute( "type", "IN/CompanyProfile" );
  4028. script.setAttribute( "data-id", ( options.companyid ) );
  4029. script.setAttribute( "data-format", ( options.format || "inline" ) );
  4030. if ( options.text && options.format.toLowerCase() !== "inline" ) {
  4031. script.setAttribute( "data-text", options.text );
  4032. }
  4033. if ( options.related !== undefined ) {
  4034. script.setAttribute( "data-related", options.related );
  4035. }
  4036. },
  4037. recommendproduct: function () {
  4038. script.setAttribute( "type", "IN/RecommendProduct" );
  4039. script.setAttribute( "data-company", ( options.companyid || "LinkedIn" ) );
  4040. script.setAttribute( "data-product", ( options.productid || "201714" ) );
  4041. if ( options.counter ) {
  4042. script.setAttribute( "data-counter", options.counter );
  4043. }
  4044. }
  4045. };
  4046. })( options );
  4047. if ( !apikey ) {
  4048. errorMsg();
  4049. } else {
  4050. setOptions[ options.type ] && setOptions[ options.type ]();
  4051. }
  4052. if ( !target && Popcorn.plugin.debug ) {
  4053. throw new Error( "target container doesn't exist" );
  4054. }
  4055. target && target.appendChild( options._container );
  4056. options._container.style.display = "none";
  4057. },
  4058. /**
  4059. * @member linkedin
  4060. * The start function will be executed when the currentTime
  4061. * of the video reaches the start time provided by the
  4062. * options variable
  4063. */
  4064. start: function( event, options ) {
  4065. options._container.style.display = "block";
  4066. },
  4067. /**
  4068. * @member linkedin
  4069. * The end function will be executed when the currentTime
  4070. * of the video reaches the end time provided by the
  4071. * options variable
  4072. */
  4073. end: function( event, options ) {
  4074. options._container.style.display = "none";
  4075. },
  4076. _teardown: function( options ) {
  4077. var tar = document.getElementById( options.target );
  4078. tar && tar.removeChild( options._container );
  4079. }
  4080. });
  4081. })( Popcorn );
  4082. // PLUGIN: lowerthird
  4083. (function ( Popcorn ) {
  4084. /**
  4085. * Lower Third popcorn plug-in
  4086. * Displays information about a speaker over the video, or in the target div
  4087. * Options parameter will need a start, and end.
  4088. * Optional parameters are target, salutation, name and role.
  4089. * Start is the time that you want this plug-in to execute
  4090. * End is the time that you want this plug-in to stop executing
  4091. * Target is the id of the document element that the content is
  4092. * appended to, this target element must exist on the DOM
  4093. * salutation is the speaker's Mr. Ms. Dr. etc.
  4094. * name is the speaker's name.
  4095. * role is information about the speaker, example Engineer.
  4096. *
  4097. * @param {Object} options
  4098. *
  4099. * Example:
  4100. var p = Popcorn('#video')
  4101. .lowerthird({
  4102. start: 5, // seconds, mandatory
  4103. end: 15, // seconds, mandatory
  4104. salutation: 'Mr', // optional
  4105. name: 'Scott Downe', // optional
  4106. role: 'Programmer', // optional
  4107. target: 'subtitlediv' // optional
  4108. } )
  4109. *
  4110. */
  4111. Popcorn.plugin( "lowerthird", {
  4112. manifest: {
  4113. about:{
  4114. name: "Popcorn lowerthird Plugin",
  4115. version: "0.1",
  4116. author: "Scott Downe",
  4117. website: "http://scottdowne.wordpress.com/"
  4118. },
  4119. options:{
  4120. start: {
  4121. elem: "input",
  4122. type: "text",
  4123. label: "In"
  4124. },
  4125. end: {
  4126. elem: "input",
  4127. type: "text",
  4128. label: "Out"
  4129. },
  4130. target: "lowerthird-container",
  4131. salutation : {
  4132. elem: "input",
  4133. type: "text",
  4134. label: "Text"
  4135. },
  4136. name: {
  4137. elem: "input",
  4138. type: "text",
  4139. label: "Text"
  4140. },
  4141. role: {
  4142. elem: "input",
  4143. type: "text",
  4144. label: "Text"
  4145. }
  4146. }
  4147. },
  4148. _setup: function( options ) {
  4149. var target = document.getElementById( options.target );
  4150. // Creates a div for all Lower Thirds to use
  4151. if ( !this.container ) {
  4152. this.container = document.createElement( "div" );
  4153. this.container.style.position = "absolute";
  4154. this.container.style.color = "white";
  4155. this.container.style.textShadow = "black 2px 2px 6px";
  4156. this.container.style.fontSize = "24px";
  4157. this.container.style.fontWeight = "bold";
  4158. this.container.style.paddingLeft = "40px";
  4159. // the video element must have height and width defined
  4160. this.container.style.width = this.video.offsetWidth + "px";
  4161. this.container.style.left = this.position().left + "px";
  4162. this.video.parentNode.appendChild( this.container );
  4163. }
  4164. // if a target is specified, use that
  4165. if ( options.target && options.target !== "lowerthird-container" ) {
  4166. options.container = document.createElement( "div" );
  4167. if ( !target && Popcorn.plugin.debug ) {
  4168. throw new Error( "target container doesn't exist" );
  4169. }
  4170. target && target.appendChild( options.container );
  4171. // use shared default container
  4172. } else {
  4173. options.container = this.container;
  4174. }
  4175. },
  4176. /**
  4177. * @member lowerthird
  4178. * The start function will be executed when the currentTime
  4179. * of the video reaches the start time provided by the
  4180. * options variable
  4181. */
  4182. start: function(event, options){
  4183. options.container.innerHTML = ( options.salutation ? options.salutation + " " : "" ) + options.name + ( options.role ? "<br />" + options.role : "" );
  4184. this.container.style.top = this.position().top + this.video.offsetHeight - ( 40 + this.container.offsetHeight ) + "px";
  4185. },
  4186. /**
  4187. * @member lowerthird
  4188. * The end function will be executed when the currentTime
  4189. * of the video reaches the end time provided by the
  4190. * options variable
  4191. */
  4192. end: function( event, options ) {
  4193. options.container.innerHTML = "";
  4194. }
  4195. });
  4196. })( Popcorn );
  4197. // PLUGIN: Mustache
  4198. (function ( Popcorn ) {
  4199. /**
  4200. * Mustache Popcorn Plug-in
  4201. *
  4202. * Adds the ability to render JSON using templates via the Mustache templating library.
  4203. *
  4204. * @param {Object} options
  4205. *
  4206. * Required parameters: start, end, template, data, and target.
  4207. * Optional parameter: static.
  4208. *
  4209. * start: the time in seconds when the mustache template should be rendered
  4210. * in the target div.
  4211. *
  4212. * end: the time in seconds when the rendered mustache template should be
  4213. * removed from the target div.
  4214. *
  4215. * target: a String -- the target div's id.
  4216. *
  4217. * template: the mustache template for the plugin to use when rendering. This can be
  4218. * a String containing the template, or a Function that returns the template's
  4219. * String.
  4220. *
  4221. * data: the data to be rendered using the mustache template. This can be a JSON String,
  4222. * a JavaScript Object literal, or a Function returning a String or Literal.
  4223. *
  4224. * dynamic: an optional argument indicating that the template and json data are dynamic
  4225. * and need to be loaded dynamically on every use. Defaults to True.
  4226. *
  4227. * Example:
  4228. var p = Popcorn('#video')
  4229. // Example using template and JSON strings.
  4230. .mustache({
  4231. start: 5, // seconds
  4232. end: 15, // seconds
  4233. target: 'mustache',
  4234. template: '<h1>{{header}}</h1>' +
  4235. '{{#bug}}' +
  4236. '{{/bug}}' +
  4237. '' +
  4238. '{{#items}}' +
  4239. ' {{#first}}' +
  4240. ' <li><strong>{{name}}</strong></li>' +
  4241. ' {{/first}}' +
  4242. ' {{#link}}' +
  4243. ' <li><a href="{{url}}">{{name}}</a></li>' +
  4244. ' {{/link}}' +
  4245. '{{/items}}' +
  4246. '' +
  4247. '{{#empty}}' +
  4248. ' <p>The list is empty.</p>' +
  4249. '{{/empty}}' ,
  4250. data: '{' +
  4251. ' "header": "Colors", ' +
  4252. ' "items": [ ' +
  4253. ' {"name": "red", "first": true, "url": "#Red"}, ' +
  4254. ' {"name": "green", "link": true, "url": "#Green"}, ' +
  4255. ' {"name": "blue", "link": true, "url": "#Blue"} ' +
  4256. ' ],' +
  4257. ' 'empty': false' +
  4258. '}',
  4259. dynamic: false // The json is not going to change, load it early.
  4260. } )
  4261. // Example showing Functions instead of Strings.
  4262. .mustache({
  4263. start: 20, // seconds
  4264. end: 25, // seconds
  4265. target: 'mustache',
  4266. template: function(instance, options) {
  4267. var template = // load your template file here...
  4268. return template;
  4269. },
  4270. data: function(instance, options) {
  4271. var json = // load your json here...
  4272. return json;
  4273. }
  4274. } );
  4275. *
  4276. */
  4277. Popcorn.plugin( "mustache" , function( options ){
  4278. var getData, data, getTemplate, template;
  4279. Popcorn.getScript( "https://github.com/janl/mustache.js/raw/master/mustache.js" );
  4280. var shouldReload = !!options.dynamic,
  4281. typeOfTemplate = typeof options.template,
  4282. typeOfData = typeof options.data,
  4283. target = document.getElementById( options.target );
  4284. if ( !target && Popcorn.plugin.debug ) {
  4285. throw new Error( "target container doesn't exist" );
  4286. }
  4287. options.container = target || document.createElement( "div" );
  4288. if ( typeOfTemplate === "function" ) {
  4289. if ( !shouldReload ) {
  4290. template = options.template( options );
  4291. } else {
  4292. getTemplate = options.template;
  4293. }
  4294. } else if ( typeOfTemplate === "string" ) {
  4295. template = options.template;
  4296. } else if ( Popcorn.plugin.debug ) {
  4297. throw new Error( "Mustache Plugin Error: options.template must be a String or a Function." );
  4298. } else {
  4299. template = "";
  4300. }
  4301. if ( typeOfData === "function" ) {
  4302. if ( !shouldReload ) {
  4303. data = options.data( options );
  4304. } else {
  4305. getData = options.data;
  4306. }
  4307. } else if ( typeOfData === "string" ) {
  4308. data = JSON.parse( options.data );
  4309. } else if ( typeOfData === "object" ) {
  4310. data = options.data;
  4311. } else if ( Popcorn.plugin.debug ) {
  4312. throw new Error( "Mustache Plugin Error: options.data must be a String, Object, or Function." );
  4313. } else {
  4314. data = "";
  4315. }
  4316. return {
  4317. start: function( event, options ) {
  4318. var interval = function() {
  4319. if( !window.Mustache ) {
  4320. setTimeout( function() {
  4321. interval();
  4322. }, 10 );
  4323. } else {
  4324. // if dynamic, freshen json data on every call to start, just in case.
  4325. if ( getData ) {
  4326. data = getData( options );
  4327. }
  4328. if ( getTemplate ) {
  4329. template = getTemplate( options );
  4330. }
  4331. var html = Mustache.to_html( template,
  4332. data
  4333. ).replace( /^\s*/mg, "" );
  4334. options.container.innerHTML = html;
  4335. }
  4336. };
  4337. interval();
  4338. },
  4339. end: function( event, options ) {
  4340. options.container.innerHTML = "";
  4341. },
  4342. _teardown: function( options ) {
  4343. getData = data = getTemplate = template = null;
  4344. }
  4345. };
  4346. },
  4347. {
  4348. about: {
  4349. name: "Popcorn Mustache Plugin",
  4350. version: "0.1",
  4351. author: "David Humphrey (@humphd)",
  4352. website: "http://vocamus.net/dave"
  4353. },
  4354. options: {
  4355. start: {
  4356. elem: "input",
  4357. type: "text",
  4358. label: "In"
  4359. },
  4360. end: {
  4361. elem: "input",
  4362. type: "text",
  4363. label: "Out"
  4364. },
  4365. target: "mustache-container",
  4366. template: {
  4367. elem: "input",
  4368. type: "text",
  4369. label: "Template"
  4370. },
  4371. data: {
  4372. elem: "input",
  4373. type: "text",
  4374. label: "Data"
  4375. },
  4376. /* TODO: how to show a checkbox/boolean? */
  4377. dynamic: {
  4378. elem: "input",
  4379. type: "text",
  4380. label: "Dynamic"
  4381. }
  4382. }
  4383. });
  4384. })( Popcorn );
  4385. // PLUGIN: OPENMAP
  4386. ( function ( Popcorn ) {
  4387. /**
  4388. * openmap popcorn plug-in
  4389. * Adds an OpenLayers map and open map tiles (OpenStreetMap [default], NASA WorldWind, or USGS Topographic)
  4390. * Based on the googlemap popcorn plug-in. No StreetView support
  4391. * Options parameter will need a start, end, target, type, zoom, lat and lng
  4392. * -Start is the time that you want this plug-in to execute
  4393. * -End is the time that you want this plug-in to stop executing
  4394. * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
  4395. * -Type [optional] either: ROADMAP (OpenStreetMap), SATELLITE (NASA WorldWind / LandSat), or TERRAIN (USGS). ROADMAP/OpenStreetMap is the default.
  4396. * -Zoom [optional] defaults to 2
  4397. * -Lat and Lng are the coordinates of the map if location is not named
  4398. * -Location is a name of a place to center the map, geocoded to coordinates using TinyGeocoder.com
  4399. * -Markers [optional] is an array of map marker objects, with the following properties:
  4400. * --Icon is the URL of a map marker image
  4401. * --Size [optional] is the radius in pixels of the scaled marker image (default is 14)
  4402. * --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
  4403. * --Lat and Lng are coordinates of the map marker if location is not specified
  4404. * --Location is a name of a place for the map marker, geocoded to coordinates using TinyGeocoder.com
  4405. * Note: using location requires extra loading time, also not specifying both lat/lng and location will
  4406. * cause a JavaScript error.
  4407. * @param {Object} options
  4408. *
  4409. * Example:
  4410. var p = Popcorn( '#video' )
  4411. .openmap({
  4412. start: 5,
  4413. end: 15,
  4414. type: 'ROADMAP',
  4415. target: 'map',
  4416. lat: 43.665429,
  4417. lng: -79.403323
  4418. })
  4419. *
  4420. */
  4421. var newdiv,
  4422. i = 1;
  4423. function toggle( container, display ) {
  4424. if ( container.map ) {
  4425. container.map.div.style.display = display;
  4426. return;
  4427. }
  4428. setTimeout(function() {
  4429. toggle( container, display );
  4430. }, 10 );
  4431. }
  4432. Popcorn.plugin( "openmap", function( options ){
  4433. var newdiv,
  4434. centerlonlat,
  4435. projection,
  4436. displayProjection,
  4437. pointLayer,
  4438. selectControl,
  4439. popup,
  4440. isGeoReady,
  4441. target = document.getElementById( options.target );
  4442. // create a new div within the target div
  4443. // this is later passed on to the maps api
  4444. newdiv = document.createElement( "div" );
  4445. newdiv.id = "openmapdiv" + i;
  4446. newdiv.style.width = "100%";
  4447. newdiv.style.height = "100%";
  4448. i++;
  4449. if ( !target && Popcorn.plugin.debug ) {
  4450. throw new Error( "target container doesn't exist" );
  4451. }
  4452. target && target.appendChild( newdiv );
  4453. // callback function fires when the script is run
  4454. isGeoReady = function() {
  4455. if ( !window.OpenLayers ) {
  4456. setTimeout(function() {
  4457. isGeoReady();
  4458. }, 50);
  4459. } else {
  4460. if ( options.location ) {
  4461. // set a dummy center at start
  4462. location = new OpenLayers.LonLat( 0, 0 );
  4463. // query TinyGeocoder and re-center in callback
  4464. Popcorn.getJSONP(
  4465. "//tinygeocoder.com/create-api.php?q=" + options.location + "&callback=jsonp",
  4466. function( latlng ) {
  4467. centerlonlat = new OpenLayers.LonLat( latlng[ 1 ], latlng[ 0 ] );
  4468. options.map.setCenter( centerlonlat );
  4469. }
  4470. );
  4471. } else {
  4472. centerlonlat = new OpenLayers.LonLat( options.lng, options.lat );
  4473. }
  4474. options.type = options.type || "ROADMAP";
  4475. if ( options.type === "SATELLITE" ) {
  4476. // add NASA WorldWind / LANDSAT map
  4477. options.map = new OpenLayers.Map( { div: newdiv, "maxResolution": 0.28125, tileSize: new OpenLayers.Size( 512, 512 ) } );
  4478. var worldwind = new OpenLayers.Layer.WorldWind( "LANDSAT", "//worldwind25.arc.nasa.gov/tile/tile.aspx", 2.25, 4, { T: "105" } );
  4479. options.map.addLayer( worldwind );
  4480. displayProjection = new OpenLayers.Projection( "EPSG:4326" );
  4481. projection = new OpenLayers.Projection( "EPSG:4326" );
  4482. }
  4483. else if ( options.type === "TERRAIN" ) {
  4484. // add terrain map ( USGS )
  4485. displayProjection = new OpenLayers.Projection( "EPSG:4326" );
  4486. projection = new OpenLayers.Projection( "EPSG:4326" );
  4487. options.map = new OpenLayers.Map( {div: newdiv, projection: projection } );
  4488. var relief = new OpenLayers.Layer.WMS( "USGS Terraserver", "//terraserver-usa.org/ogcmap.ashx?", { layers: "DRG" } );
  4489. options.map.addLayer( relief );
  4490. } else {
  4491. // add OpenStreetMap layer
  4492. projection = new OpenLayers.Projection( 'EPSG:900913' );
  4493. displayProjection = new OpenLayers.Projection( 'EPSG:4326' );
  4494. centerlonlat = centerlonlat.transform( displayProjection, projection );
  4495. options.map = new OpenLayers.Map( { div: newdiv, projection: projection, "displayProjection": displayProjection } );
  4496. var osm = new OpenLayers.Layer.OSM();
  4497. options.map.addLayer( osm );
  4498. }
  4499. if ( options.map ) {
  4500. options.map.div.style.display = "none";
  4501. }
  4502. }
  4503. };
  4504. isGeoReady();
  4505. return {
  4506. /**
  4507. * @member openmap
  4508. * The setup function will be executed when the plug-in is instantiated
  4509. */
  4510. _setup: function( options ) {
  4511. // insert openlayers api script once
  4512. if ( !window.OpenLayers ) {
  4513. Popcorn.getScript( "//openlayers.org/api/OpenLayers.js" );
  4514. }
  4515. var isReady = function() {
  4516. // wait until OpenLayers has been loaded, and the start function is run, before adding map
  4517. if ( !options.map ) {
  4518. setTimeout(function() {
  4519. isReady();
  4520. }, 13 );
  4521. } else {
  4522. // default zoom is 2
  4523. options.zoom = options.zoom || 2;
  4524. // make sure options.zoom is a number
  4525. if ( options.zoom && typeof options.zoom !== "number" ) {
  4526. options.zoom = +options.zoom;
  4527. }
  4528. // reset the location and zoom just in case the user played with the map
  4529. options.map.setCenter( centerlonlat, options.zoom );
  4530. if ( options.markers ) {
  4531. var layerStyle = OpenLayers.Util.extend( {} , OpenLayers.Feature.Vector.style[ "default" ] ),
  4532. featureSelected = function( clickInfo ) {
  4533. clickedFeature = clickInfo.feature;
  4534. if ( !clickedFeature.attributes.text ) {
  4535. return;
  4536. }
  4537. popup = new OpenLayers.Popup.FramedCloud(
  4538. "featurePopup",
  4539. clickedFeature.geometry.getBounds().getCenterLonLat(),
  4540. new OpenLayers.Size( 120, 250 ),
  4541. clickedFeature.attributes.text,
  4542. null,
  4543. true,
  4544. function( closeInfo ) {
  4545. selectControl.unselect( this.feature );
  4546. }
  4547. );
  4548. clickedFeature.popup = popup;
  4549. popup.feature = clickedFeature;
  4550. options.map.addPopup( popup );
  4551. },
  4552. featureUnSelected = function( clickInfo ) {
  4553. feature = clickInfo.feature;
  4554. if ( feature.popup ) {
  4555. popup.feature = null;
  4556. options.map.removePopup( feature.popup );
  4557. feature.popup.destroy();
  4558. feature.popup = null;
  4559. }
  4560. },
  4561. gcThenPlotMarker = function( myMarker ) {
  4562. Popcorn.getJSONP(
  4563. "//tinygeocoder.com/create-api.php?q=" + myMarker.location + "&callback=jsonp",
  4564. function( latlng ) {
  4565. var myPoint = new OpenLayers.Geometry.Point( latlng[1], latlng[0] ).transform( displayProjection, projection ),
  4566. myPointStyle = OpenLayers.Util.extend( {}, layerStyle );
  4567. if ( !myMarker.size || isNaN( myMarker.size ) ) {
  4568. myMarker.size = 14;
  4569. }
  4570. myPointStyle.pointRadius = myMarker.size;
  4571. myPointStyle.graphicOpacity = 1;
  4572. myPointStyle.externalGraphic = myMarker.icon;
  4573. var myPointFeature = new OpenLayers.Feature.Vector( myPoint, null, myPointStyle );
  4574. if ( myMarker.text ) {
  4575. myPointFeature.attributes = {
  4576. text: myMarker.text
  4577. };
  4578. }
  4579. pointLayer.addFeatures( [ myPointFeature ] );
  4580. }
  4581. );
  4582. };
  4583. pointLayer = new OpenLayers.Layer.Vector( "Point Layer", { style: layerStyle } );
  4584. options.map.addLayer( pointLayer );
  4585. for ( var m = 0, l = options.markers.length; m < l ; m++ ) {
  4586. var myMarker = options.markers[ m ];
  4587. if( myMarker.text ){
  4588. if( !selectControl ){
  4589. selectControl = new OpenLayers.Control.SelectFeature( pointLayer );
  4590. options.map.addControl( selectControl );
  4591. selectControl.activate();
  4592. pointLayer.events.on({
  4593. "featureselected": featureSelected,
  4594. "featureunselected": featureUnSelected
  4595. });
  4596. }
  4597. }
  4598. if ( myMarker.location ) {
  4599. var geocodeThenPlotMarker = gcThenPlotMarker;
  4600. geocodeThenPlotMarker( myMarker );
  4601. } else {
  4602. var myPoint = new OpenLayers.Geometry.Point( myMarker.lng, myMarker.lat ).transform( displayProjection, projection ),
  4603. myPointStyle = OpenLayers.Util.extend( {}, layerStyle );
  4604. if ( !myMarker.size || isNaN( myMarker.size ) ) {
  4605. myMarker.size = 14;
  4606. }
  4607. myPointStyle.pointRadius = myMarker.size;
  4608. myPointStyle.graphicOpacity = 1;
  4609. myPointStyle.externalGraphic = myMarker.icon;
  4610. var myPointFeature = new OpenLayers.Feature.Vector( myPoint, null, myPointStyle );
  4611. if ( myMarker.text ) {
  4612. myPointFeature.attributes = {
  4613. text: myMarker.text
  4614. };
  4615. }
  4616. pointLayer.addFeatures( [ myPointFeature ] );
  4617. }
  4618. }
  4619. }
  4620. }
  4621. };
  4622. isReady();
  4623. },
  4624. /**
  4625. * @member openmap
  4626. * The start function will be executed when the currentTime
  4627. * of the video reaches the start time provided by the
  4628. * options variable
  4629. */
  4630. start: function( event, options ) {
  4631. toggle( options, "block" );
  4632. },
  4633. /**
  4634. * @member openmap
  4635. * The end function will be executed when the currentTime
  4636. * of the video reaches the end time provided by the
  4637. * options variable
  4638. */
  4639. end: function( event, options ) {
  4640. toggle( options, "none" );
  4641. },
  4642. _teardown: function( options ) {
  4643. target && target.removeChild( newdiv );
  4644. newdiv = map = centerlonlat = projection = displayProjection = pointLayer = selectControl = popup = null;
  4645. }
  4646. };
  4647. },
  4648. {
  4649. about:{
  4650. name: "Popcorn OpenMap Plugin",
  4651. version: "0.3",
  4652. author: "@mapmeld",
  4653. website: "mapadelsur.blogspot.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: "map-container",
  4667. type: {
  4668. elem: "select",
  4669. options: [ "ROADMAP", "SATELLITE", "TERRAIN" ],
  4670. label: "Type"
  4671. },
  4672. zoom: {
  4673. elem: "input",
  4674. type: "text",
  4675. label: "Zoom"
  4676. },
  4677. lat: {
  4678. elem: "input",
  4679. type: "text",
  4680. label: "Lat"
  4681. },
  4682. lng: {
  4683. elem: "input",
  4684. type: "text",
  4685. label: "Lng"
  4686. },
  4687. location: {
  4688. elem: "input",
  4689. type: "text",
  4690. label: "Location"
  4691. },
  4692. markers: {
  4693. elem: "input",
  4694. type: "text",
  4695. label: "List Markers"
  4696. }
  4697. }
  4698. });
  4699. }) ( Popcorn );
  4700. /**
  4701. * Pause Popcorn Plug-in
  4702. *
  4703. * When this plugin is used, links on the webpage, when clicked, will pause
  4704. * popcorn videos that especified 'pauseOnLinkClicked' as an option. Links may
  4705. * cause a new page to display on a new window, or may cause a new page to
  4706. * display in the current window, in which case the videos won't be available
  4707. * anymore. It only affects anchor tags. It does not affect objects with click
  4708. * events that act as anchors.
  4709. *
  4710. * Example:
  4711. var p = Popcorn('#video', { pauseOnLinkClicked : true } )
  4712. .play();
  4713. *
  4714. */
  4715. document.addEventListener( "click", function( event ) {
  4716. var targetElement = event.target;
  4717. //Some browsers use an element as the target, some use the text node inside
  4718. if ( targetElement.nodeName === "A" || targetElement.parentNode && targetElement.parentNode.nodeName === "A" ) {
  4719. Popcorn.instances.forEach( function( video ) {
  4720. if ( video.options.pauseOnLinkClicked ) {
  4721. video.pause();
  4722. }
  4723. });
  4724. }
  4725. }, false );
  4726. /**
  4727. * Processing Popcorn Plug-In
  4728. *
  4729. * This plugin adds a Processing.js sketch to be added to a target div or canvas.
  4730. *
  4731. * Options parameter needs to specify start, end, target and sketch attributes
  4732. * -Start is the time [in seconds] that you want the sketch to display and start looping.
  4733. * -End is the time [in seconds] you want the sketch to become hidden and stop looping.
  4734. * -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. )
  4735. * -Sketch specifies the filename of the Procesing code to be loaded into Processing.js
  4736. * -noLoop [optional] specifies whether a sketch should continue to loop when the video is paused or seeking.
  4737. *
  4738. * @param {Object} options
  4739. *
  4740. * Example:
  4741. var p = Popcorn( "#video" )
  4742. .processing({
  4743. start: 5,
  4744. end: 10,
  4745. target: "processing-div",
  4746. sketch: "processingSketch.pjs",
  4747. noLoop: true
  4748. });
  4749. *
  4750. */
  4751. (function( Popcorn ) {
  4752. Popcorn.plugin( "processing", function( options ) {
  4753. var init = function( context ) {
  4754. function scriptReady( options ) {
  4755. var addListeners = function() {
  4756. context.listen( "pause", function() {
  4757. if ( options.canvas.style.display === "inline" ) {
  4758. options.pjsInstance.noLoop();
  4759. }
  4760. });
  4761. context.listen( "play", function() {
  4762. if ( options.canvas.style.display === "inline" ) {
  4763. options.pjsInstance.loop();
  4764. }
  4765. });
  4766. };
  4767. if ( options.sketch ) {
  4768. Popcorn.xhr({
  4769. url: options.sketch,
  4770. dataType: "text",
  4771. success: function( responseCode ) {
  4772. options.codeReady = false;
  4773. var s = Processing.compile( responseCode );
  4774. options.pjsInstance = new Processing( options.canvas, s );
  4775. options.seeking = false;
  4776. ( options._running && !context.media.paused && options.pjsInstance.loop() ) || options.pjsInstance.noLoop();
  4777. context.listen( "seeking", function() {
  4778. options._running && options.canvas.style.display === "inline" && options.noPause && options.pjsInstance.loop();
  4779. });
  4780. options.noPause = options.noPause || false;
  4781. !options.noPause && addListeners();
  4782. options.codeReady = true;
  4783. }
  4784. });
  4785. } else if ( Popcorn.plugin.debug ) {
  4786. throw new Error( "Popcorn.Processing: options.sketch is undefined" );
  4787. }
  4788. }
  4789. if ( !window.Processing ) {
  4790. Popcorn.getScript( "//wac.1237.edgecastcdn.net/801237/cdn.processingjs.org/content/download/processing-js-1.3.6/processing-1.3.6.min.js", function() {
  4791. scriptReady( options );
  4792. });
  4793. } else {
  4794. scriptReady( options );
  4795. }
  4796. };
  4797. return {
  4798. _setup: function( options ) {
  4799. options.codeReady = false;
  4800. options.parentTarget = document.getElementById( options.target );
  4801. if ( !options.parentTarget && Popcorn.plugin.debug ) {
  4802. throw new Error( "target container doesn't exist" );
  4803. }
  4804. var canvas = document.createElement( "canvas" );
  4805. canvas.id = Popcorn.guid( options.target + "-sketch" );
  4806. canvas.style.display = "none";
  4807. options.canvas = canvas;
  4808. options.parentTarget && options.parentTarget.appendChild( options.canvas );
  4809. init( this );
  4810. },
  4811. start: function( event, options ) {
  4812. options.codeReady && !this.media.paused && options.pjsInstance.loop();
  4813. options.canvas.style.display = "inline";
  4814. },
  4815. end: function( event, options ) {
  4816. options.pjsInstance && options.pjsInstance.noLoop();
  4817. options.canvas.style.display = "none";
  4818. },
  4819. _teardown: function( options ) {
  4820. options.pjsInstance && options.pjsInstance.exit();
  4821. options.parentTarget && options.parentTarget.removeChild( options.canvas );
  4822. }
  4823. };
  4824. },
  4825. {
  4826. about: {
  4827. name: "Popcorn Processing Plugin",
  4828. version: "0.1",
  4829. author: "Christopher De Cairos, Benjamin Chalovich",
  4830. website: "cadecairos.blogspot.com, ben1amin.wordpress.org"
  4831. },
  4832. options: {
  4833. start: {
  4834. elem: "input",
  4835. type: "text",
  4836. label: "In"
  4837. },
  4838. end: {
  4839. elem: "input",
  4840. type: "text",
  4841. label: "Out"
  4842. },
  4843. target: {
  4844. elem: "input",
  4845. type: "text",
  4846. label: "Target"
  4847. },
  4848. sketch: {
  4849. elem: "input",
  4850. type: "url",
  4851. label: "Sketch"
  4852. },
  4853. noPause: {
  4854. elem: "select",
  4855. options: [ "TRUE", "FALSE" ],
  4856. label: "No Loop"
  4857. }
  4858. }
  4859. });
  4860. }( Popcorn ));
  4861. // PLUGIN: Subtitle
  4862. (function ( Popcorn ) {
  4863. var i = 0,
  4864. createDefaultContainer = function( context ) {
  4865. var ctxContainer = context.container = document.createElement( "div" ),
  4866. style = ctxContainer.style,
  4867. media = context.media;
  4868. var updatePosition = function() {
  4869. var position = context.position();
  4870. // the video element must have height and width defined
  4871. style.fontSize = "18px";
  4872. style.width = media.offsetWidth + "px";
  4873. style.top = position.top + media.offsetHeight - ctxContainer.offsetHeight - 40 + "px";
  4874. style.left = position.left + "px";
  4875. setTimeout( updatePosition, 10 );
  4876. };
  4877. ctxContainer.id = Popcorn.guid();
  4878. style.position = "absolute";
  4879. style.color = "white";
  4880. style.textShadow = "black 2px 2px 6px";
  4881. style.fontWeight = "bold";
  4882. style.textAlign = "center";
  4883. updatePosition();
  4884. context.media.parentNode.appendChild( ctxContainer );
  4885. };
  4886. /**
  4887. * Subtitle popcorn plug-in
  4888. * Displays a subtitle over the video, or in the target div
  4889. * Options parameter will need a start, and end.
  4890. * Optional parameters are target and text.
  4891. * Start is the time that you want this plug-in to execute
  4892. * End is the time that you want this plug-in to stop executing
  4893. * Target is the id of the document element that the content is
  4894. * appended to, this target element must exist on the DOM
  4895. * Text is the text of the subtitle you want to display.
  4896. *
  4897. * @param {Object} options
  4898. *
  4899. * Example:
  4900. var p = Popcorn('#video')
  4901. .subtitle({
  4902. start: 5, // seconds, mandatory
  4903. end: 15, // seconds, mandatory
  4904. text: 'Hellow world', // optional
  4905. target: 'subtitlediv', // optional
  4906. } )
  4907. *
  4908. */
  4909. Popcorn.plugin( "subtitle" , {
  4910. manifest: {
  4911. about: {
  4912. name: "Popcorn Subtitle Plugin",
  4913. version: "0.1",
  4914. author: "Scott Downe",
  4915. website: "http://scottdowne.wordpress.com/"
  4916. },
  4917. options: {
  4918. start: {
  4919. elem: "input",
  4920. type: "text",
  4921. label: "In"
  4922. },
  4923. end: {
  4924. elem: "input",
  4925. type: "text",
  4926. label: "Out"
  4927. },
  4928. target: "subtitle-container",
  4929. text: {
  4930. elem: "input",
  4931. type: "text",
  4932. label: "Text"
  4933. }
  4934. }
  4935. },
  4936. _setup: function( options ) {
  4937. var newdiv = document.createElement( "div" );
  4938. newdiv.id = "subtitle-" + i++;
  4939. newdiv.style.display = "none";
  4940. // Creates a div for all subtitles to use
  4941. ( !this.container && ( !options.target || options.target === "subtitle-container" ) ) &&
  4942. createDefaultContainer( this );
  4943. // if a target is specified, use that
  4944. if ( options.target && options.target !== "subtitle-container" ) {
  4945. options.container = document.getElementById( options.target );
  4946. } else {
  4947. // use shared default container
  4948. options.container = this.container;
  4949. }
  4950. document.getElementById( options.container.id ) && document.getElementById( options.container.id ).appendChild( newdiv );
  4951. options.innerContainer = newdiv;
  4952. options.showSubtitle = function() {
  4953. options.innerContainer.innerHTML = options.text || "";
  4954. };
  4955. },
  4956. /**
  4957. * @member subtitle
  4958. * The start function will be executed when the currentTime
  4959. * of the video reaches the start time provided by the
  4960. * options variable
  4961. */
  4962. start: function( event, options ){
  4963. options.innerContainer.style.display = "inline";
  4964. options.showSubtitle( options, options.text );
  4965. },
  4966. /**
  4967. * @member subtitle
  4968. * The end function will be executed when the currentTime
  4969. * of the video reaches the end time provided by the
  4970. * options variable
  4971. */
  4972. end: function( event, options ) {
  4973. options.innerContainer.style.display = "none";
  4974. options.innerContainer.innerHTML = "";
  4975. },
  4976. _teardown: function ( options ) {
  4977. options.container.removeChild( options.innerContainer );
  4978. }
  4979. });
  4980. })( Popcorn );
  4981. // PLUGIN: tagthisperson
  4982. (function ( Popcorn ) {
  4983. var peopleArray = [];
  4984. // one People object per options.target
  4985. var People = function() {
  4986. this.name = "";
  4987. this.contains = { };
  4988. this.toString = function() {
  4989. var r = [];
  4990. for ( var j in this.contains ) {
  4991. if ( this.contains.hasOwnProperty( j ) ) {
  4992. r.push( " " + this.contains[ j ] );
  4993. }
  4994. }
  4995. return r.toString();
  4996. };
  4997. };
  4998. /**
  4999. * tagthisperson popcorn plug-in
  5000. * Adds people's names to an element on the page.
  5001. * Options parameter will need a start, end, target, image and person.
  5002. * Start is the time that you want this plug-in to execute
  5003. * End is the time that you want this plug-in to stop executing
  5004. * Person is the name of the person who you want to tag
  5005. * Image is the url to the image of the person - optional
  5006. * href is the url to the webpage of the person - optional
  5007. * Target is the id of the document element that the text needs to be
  5008. * attached to, this target element must exist on the DOM
  5009. *
  5010. * @param {Object} options
  5011. *
  5012. * Example:
  5013. var p = Popcorn('#video')
  5014. .tagthisperson({
  5015. start: 5, // seconds
  5016. end: 15, // seconds
  5017. person: '@annasob',
  5018. image: 'http://newshour.s3.amazonaws.com/photos%2Fspeeches%2Fguests%2FRichardNSmith_thumbnail.jpg',
  5019. href: 'http://annasob.wordpress.com',
  5020. target: 'tagdiv'
  5021. } )
  5022. *
  5023. */
  5024. Popcorn.plugin( "tagthisperson" , ( function() {
  5025. return {
  5026. _setup: function( options ) {
  5027. var exists = false,
  5028. target = document.getElementById( options.target );
  5029. if ( !target && Popcorn.plugin.debug ) {
  5030. throw new Error( "target container doesn't exist" );
  5031. }
  5032. // loop through the existing objects to ensure no duplicates
  5033. // the idea here is to have one object per unique options.target
  5034. for ( var i = 0; i < peopleArray.length; i++ ) {
  5035. if ( peopleArray[ i ].name === options.target ) {
  5036. options._p = peopleArray[ i ];
  5037. exists = true;
  5038. break;
  5039. }
  5040. }
  5041. if ( !exists ) {
  5042. options._p = new People();
  5043. options._p.name = options.target;
  5044. peopleArray.push( options._p );
  5045. }
  5046. },
  5047. /**
  5048. * @member tagthisperson
  5049. * The start function will be executed when the currentTime
  5050. * of the video reaches the start time provided by the
  5051. * options variable
  5052. */
  5053. start: function( event, options ){
  5054. options._p.contains[ options.person ] = ( options.image ) ? "<img src='" + options.image + "'/> " : "" ;
  5055. options._p.contains[ options.person ] += ( options.href ) ? "<a href='" + options.href + "' target='_blank'> " + options.person + "</a>" : options.person ;
  5056. document.getElementById( options.target ).innerHTML = options._p.toString();
  5057. },
  5058. /**
  5059. * @member tagthisperson
  5060. * The end function will be executed when the currentTime
  5061. * of the video reaches the end time provided by the
  5062. * options variable
  5063. */
  5064. end: function( event, options ){
  5065. delete options._p.contains[ options.person ];
  5066. document.getElementById( options.target ).innerHTML = options._p.toString();
  5067. }
  5068. };
  5069. })(),
  5070. {
  5071. about:{
  5072. name: "Popcorn tagthisperson Plugin",
  5073. version: "0.1",
  5074. author: "@annasob",
  5075. website: "annasob.wordpress.com"
  5076. },
  5077. options:{
  5078. start: {
  5079. elem: "input",
  5080. type: "text",
  5081. label: "In"
  5082. },
  5083. end: {
  5084. elem: "input",
  5085. type: "text",
  5086. label: "Out"
  5087. },
  5088. target : "tagthisperson-container",
  5089. person: {
  5090. elem: "input",
  5091. type: "text",
  5092. label: "Name"
  5093. },
  5094. image: {
  5095. elem: "input",
  5096. type: "url",
  5097. label: "Image Src"
  5098. },
  5099. href: {
  5100. elem: "input",
  5101. type: "url",
  5102. label: "URL"
  5103. }
  5104. }
  5105. });
  5106. })( Popcorn );
  5107. // PLUGIN: Timeline
  5108. (function ( Popcorn ) {
  5109. /**
  5110. * timeline popcorn plug-in
  5111. * Adds data associated with a certain time in the video, which creates a scrolling view of each item as the video progresses
  5112. * Options parameter will need a start, target, title, and text
  5113. * -Start is the time that you want this plug-in to execute
  5114. * -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 )
  5115. * -Target is the id of the DOM element that you want the timeline to appear in. This element must be in the DOM
  5116. * -Title is the title of the current timeline box
  5117. * -Text is text is simply related text that will be displayed
  5118. * -innerHTML gives the user the option to add things such as links, buttons and so on
  5119. * -direction specifies whether the timeline will grow from the top or the bottom, receives input as "UP" or "DOWN"
  5120. * @param {Object} options
  5121. *
  5122. * Example:
  5123. var p = Popcorn("#video")
  5124. .timeline( {
  5125. start: 5, // seconds
  5126. target: "timeline",
  5127. title: "Seneca",
  5128. text: "Welcome to seneca",
  5129. innerHTML: "Click this link <a href='http://www.google.ca'>Google</a>"
  5130. } )
  5131. *
  5132. */
  5133. var i = 1,
  5134. head = document.getElementsByTagName( "head" )[ 0 ],
  5135. css = document.createElement( "link" );
  5136. css.type = "text/css";
  5137. css.rel = "stylesheet";
  5138. css.href = "//popcornjs.org/code/plugins/timeline/popcorn.timeline.css";
  5139. head.insertBefore( css, head.firstChild );
  5140. Popcorn.plugin( "timeline" , function( options ) {
  5141. var target = document.getElementById( options.target ),
  5142. contentDiv = document.createElement( "div" ),
  5143. container,
  5144. goingUp = true;
  5145. if ( target && !target.firstChild ) {
  5146. target.appendChild ( container = document.createElement( "div" ) );
  5147. container.style.width = "inherit";
  5148. container.style.height = "inherit";
  5149. container.style.overflow = "auto";
  5150. } else {
  5151. container = target.firstChild;
  5152. }
  5153. contentDiv.style.display = "none";
  5154. contentDiv.id = "timelineDiv" + i;
  5155. // Default to up if options.direction is non-existant or not up or down
  5156. options.direction = options.direction || "up";
  5157. if ( options.direction.toLowerCase() === "down" ) {
  5158. goingUp = false;
  5159. }
  5160. if ( target && container ) {
  5161. // if this isnt the first div added to the target div
  5162. if( goingUp ){
  5163. // insert the current div before the previous div inserted
  5164. container.insertBefore( contentDiv, container.firstChild );
  5165. }
  5166. else {
  5167. container.appendChild( contentDiv );
  5168. }
  5169. } else if ( Popcorn.plugin.debug ) {
  5170. throw new Error( "target container doesn't exist" );
  5171. }
  5172. i++;
  5173. // Default to empty if not used
  5174. //options.innerHTML = options.innerHTML || "";
  5175. contentDiv.innerHTML = "<p><span id='big'>" + options.title + "</span><br />" +
  5176. "<span id='mid'>" + options.text + "</span><br />" + options.innerHTML;
  5177. return {
  5178. start: function( event, options ) {
  5179. contentDiv.style.display = "block";
  5180. if( options.direction === "down" ) {
  5181. container.scrollTop = container.scrollHeight;
  5182. }
  5183. },
  5184. end: function( event, options ) {
  5185. contentDiv.style.display = "none";
  5186. },
  5187. _teardown: function( options ) {
  5188. ( container && contentDiv ) && container.removeChild( contentDiv ) && !container.firstChild && target.removeChild( container );
  5189. }
  5190. };
  5191. },
  5192. {
  5193. about: {
  5194. name: "Popcorn Timeline Plugin",
  5195. version: "0.1",
  5196. author: "David Seifried @dcseifried",
  5197. website: "dseifried.wordpress.com"
  5198. },
  5199. options: {
  5200. start: {
  5201. elem: "input",
  5202. type: "text",
  5203. label: "In"
  5204. },
  5205. end: {
  5206. elem: "input",
  5207. type: "text",
  5208. label: "Out"
  5209. },
  5210. target: "feed-container",
  5211. title: {
  5212. elem: "input",
  5213. type: "text",
  5214. label: "title"
  5215. },
  5216. text: {
  5217. elem: "input",
  5218. type: "text",
  5219. label: "text"
  5220. },
  5221. innerHTML: {
  5222. elem: "input",
  5223. type: "text",
  5224. label: "innerHTML"
  5225. },
  5226. direction: {
  5227. elem: "input",
  5228. type: "text",
  5229. label: "direction"
  5230. }
  5231. }
  5232. });
  5233. })( Popcorn );
  5234. // PLUGIN: TWITTER
  5235. (function ( Popcorn ) {
  5236. var scriptLoading = false;
  5237. /**
  5238. * Twitter popcorn plug-in
  5239. * Appends a Twitter widget to an element on the page.
  5240. * Options parameter will need a start, end, target and source.
  5241. * Optional parameters are height and width.
  5242. * Start is the time that you want this plug-in to execute
  5243. * End is the time that you want this plug-in to stop executing
  5244. * Src is the hash tag or twitter user to get tweets from
  5245. * Target is the id of the document element that the images are
  5246. * appended to, this target element must exist on the DOM
  5247. * Height is the height of the widget, defaults to 200
  5248. * Width is the width of the widget, defaults to 250
  5249. *
  5250. * @param {Object} options
  5251. *
  5252. * Example:
  5253. var p = Popcorn('#video')
  5254. .twitter({
  5255. start: 5, // seconds, mandatory
  5256. end: 15, // seconds, mandatory
  5257. src: '@stevesong', // mandatory, also accepts hash tags
  5258. height: 200, // optional
  5259. width: 250, // optional
  5260. target: 'twitterdiv' // mandatory
  5261. } )
  5262. *
  5263. */
  5264. Popcorn.plugin( "twitter" , {
  5265. manifest: {
  5266. about: {
  5267. name: "Popcorn Twitter Plugin",
  5268. version: "0.1",
  5269. author: "Scott Downe",
  5270. website: "http://scottdowne.wordpress.com/"
  5271. },
  5272. options:{
  5273. start: {
  5274. elem: "input",
  5275. type: "number",
  5276. label: "In"
  5277. },
  5278. end: {
  5279. elem: "input",
  5280. type: "number",
  5281. label: "Out"
  5282. },
  5283. src: {
  5284. elem: "input",
  5285. type: "text",
  5286. label: "Source"
  5287. },
  5288. target: "twitter-container",
  5289. height: {
  5290. elem: "input",
  5291. type: "number",
  5292. label: "Height"
  5293. },
  5294. width: {
  5295. elem: "input",
  5296. type: "number",
  5297. label: "Width"
  5298. }
  5299. }
  5300. },
  5301. _setup: function( options ) {
  5302. if ( !window.TWTR && !scriptLoading ) {
  5303. scriptLoading = true;
  5304. Popcorn.getScript( "//widgets.twimg.com/j/2/widget.js" );
  5305. }
  5306. var target = document.getElementById( options.target );
  5307. // create the div to store the widget
  5308. // setup widget div that is unique per track
  5309. options.container = document.createElement( "div" );
  5310. // use this id to connect it to the widget
  5311. options.container.setAttribute( "id", Popcorn.guid() );
  5312. // display none by default
  5313. options.container.style.display = "none";
  5314. if ( !target && Popcorn.plugin.debug ) {
  5315. throw new Error( "target container doesn't exist" );
  5316. }
  5317. // add the widget's div to the target div
  5318. target && target.appendChild( options.container );
  5319. // setup info for the widget
  5320. var src = options.src || "",
  5321. width = options.width || 250,
  5322. height = options.height || 200,
  5323. profile = /^@/.test( src ),
  5324. widgetOptions = {
  5325. version: 2,
  5326. // use this id to connect it to the div
  5327. id: options.container.getAttribute( "id" ),
  5328. rpp: 30,
  5329. width: width,
  5330. height: height,
  5331. interval: 6000,
  5332. theme: {
  5333. shell: {
  5334. background: "#ffffff",
  5335. color: "#000000"
  5336. },
  5337. tweets: {
  5338. background: "#ffffff",
  5339. color: "#444444",
  5340. links: "#1985b5"
  5341. }
  5342. },
  5343. features: {
  5344. loop: true,
  5345. timestamp: true,
  5346. avatars: true,
  5347. hashtags: true,
  5348. toptweets: true,
  5349. live: true,
  5350. scrollbar: false,
  5351. behavior: 'default'
  5352. }
  5353. };
  5354. // create widget
  5355. var isReady = function( that ) {
  5356. if ( window.TWTR ) {
  5357. if ( profile ) {
  5358. widgetOptions.type = "profile";
  5359. new TWTR.Widget( widgetOptions ).render().setUser( src ).start();
  5360. } else {
  5361. widgetOptions.type = "search";
  5362. widgetOptions.search = src;
  5363. widgetOptions.subject = src;
  5364. new TWTR.Widget( widgetOptions ).render().start();
  5365. }
  5366. } else {
  5367. setTimeout( function() {
  5368. isReady( that );
  5369. }, 1);
  5370. }
  5371. };
  5372. isReady( this );
  5373. },
  5374. /**
  5375. * @member Twitter
  5376. * The start function will be executed when the currentTime
  5377. * of the video reaches the start time provided by the
  5378. * options variable
  5379. */
  5380. start: function( event, options ) {
  5381. options.container.style.display = "inline";
  5382. },
  5383. /**
  5384. * @member Twitter
  5385. * The end function will be executed when the currentTime
  5386. * of the video reaches the end time provided by the
  5387. * options variable
  5388. */
  5389. end: function( event, options ) {
  5390. options.container.style.display = "none";
  5391. },
  5392. _teardown: function( options ) {
  5393. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.container );
  5394. }
  5395. });
  5396. })( Popcorn );
  5397. // PLUGIN: WEBPAGE
  5398. (function ( Popcorn ) {
  5399. /**
  5400. * Webpages popcorn plug-in
  5401. * Creates an iframe showing a website specified by the user
  5402. * Options parameter will need a start, end, id, target and src.
  5403. * Start is the time that you want this plug-in to execute
  5404. * End is the time that you want this plug-in to stop executing
  5405. * Id is the id that you want assigned to the iframe
  5406. * Target is the id of the document element that the iframe needs to be attached to,
  5407. * this target element must exist on the DOM
  5408. * Src is the url of the website that you want the iframe to display
  5409. *
  5410. * @param {Object} options
  5411. *
  5412. * Example:
  5413. var p = Popcorn('#video')
  5414. .webpage({
  5415. id: "webpages-a",
  5416. start: 5, // seconds
  5417. end: 15, // seconds
  5418. src: 'http://www.webmademovies.org',
  5419. target: 'webpagediv'
  5420. } )
  5421. *
  5422. */
  5423. Popcorn.plugin( "webpage" , {
  5424. manifest: {
  5425. about: {
  5426. name: "Popcorn Webpage Plugin",
  5427. version: "0.1",
  5428. author: "@annasob",
  5429. website: "annasob.wordpress.com"
  5430. },
  5431. options: {
  5432. id: {
  5433. elem: "input",
  5434. type: "text",
  5435. label: "Id"
  5436. },
  5437. start: {
  5438. elem: "input",
  5439. type: "text",
  5440. label: "In"
  5441. },
  5442. end: {
  5443. elem: "input",
  5444. type: "text",
  5445. label: "Out"
  5446. },
  5447. src: {
  5448. elem: "input",
  5449. type: "url",
  5450. label: "Src"
  5451. },
  5452. target: "iframe-container"
  5453. }
  5454. },
  5455. _setup: function( options ) {
  5456. var target = document.getElementById( options.target );
  5457. // make src an iframe acceptable string
  5458. options.src = options.src.replace( /^(https?:)?(\/\/)?/, "//" );
  5459. // make an iframe
  5460. options._iframe = document.createElement( "iframe" );
  5461. options._iframe.setAttribute( "width", "100%" );
  5462. options._iframe.setAttribute( "height", "100%" );
  5463. options._iframe.id = options.id;
  5464. options._iframe.src = options.src;
  5465. options._iframe.style.display = "none";
  5466. if ( !target && Popcorn.plugin.debug ) {
  5467. throw new Error( "target container doesn't exist" );
  5468. }
  5469. // add the hidden iframe to the DOM
  5470. target && target.appendChild( options._iframe );
  5471. },
  5472. /**
  5473. * @member webpage
  5474. * The start function will be executed when the currentTime
  5475. * of the video reaches the start time provided by the
  5476. * options variable
  5477. */
  5478. start: function( event, options ){
  5479. // make the iframe visible
  5480. options._iframe.src = options.src;
  5481. options._iframe.style.display = "inline";
  5482. },
  5483. /**
  5484. * @member webpage
  5485. * The end function will be executed when the currentTime
  5486. * of the video reaches the end time provided by the
  5487. * options variable
  5488. */
  5489. end: function( event, options ){
  5490. // make the iframe invisible
  5491. options._iframe.style.display = "none";
  5492. },
  5493. _teardown: function( options ) {
  5494. document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._iframe );
  5495. }
  5496. });
  5497. })( Popcorn );
  5498. // PLUGIN: WIKIPEDIA
  5499. var wikiCallback;
  5500. (function ( Popcorn ) {
  5501. /**
  5502. * Wikipedia popcorn plug-in
  5503. * Displays a wikipedia aricle in the target specified by the user by using
  5504. * new DOM element instead overwriting them
  5505. * Options parameter will need a start, end, target, lang, src, title and numberofwords.
  5506. * -Start is the time that you want this plug-in to execute
  5507. * -End is the time that you want this plug-in to stop executing
  5508. * -Target is the id of the document element that the text from the article needs to be
  5509. * attached to, this target element must exist on the DOM
  5510. * -Lang (optional, defaults to english) is the language in which the article is in.
  5511. * -Src is the url of the article
  5512. * -Title (optional) is the title of the article
  5513. * -numberofwords (optional, defaults to 200) is the number of words you want displaid.
  5514. *
  5515. * @param {Object} options
  5516. *
  5517. * Example:
  5518. var p = Popcorn("#video")
  5519. .wikipedia({
  5520. start: 5, // seconds
  5521. end: 15, // seconds
  5522. src: "http://en.wikipedia.org/wiki/Cape_Town",
  5523. target: "wikidiv"
  5524. } )
  5525. *
  5526. */
  5527. Popcorn.plugin( "wikipedia" , {
  5528. manifest: {
  5529. about:{
  5530. name: "Popcorn Wikipedia Plugin",
  5531. version: "0.1",
  5532. author: "@annasob",
  5533. website: "annasob.wordpress.com"
  5534. },
  5535. options:{
  5536. start: {
  5537. elem: "input",
  5538. type: "text",
  5539. label: "In"
  5540. },
  5541. end: {
  5542. elem: "input",
  5543. type: "text",
  5544. label: "Out"
  5545. },
  5546. lang: {
  5547. elem: "input",
  5548. type: "text",
  5549. label: "Language"
  5550. },
  5551. src: {
  5552. elem: "input",
  5553. type: "url",
  5554. label: "Src"
  5555. },
  5556. title: {
  5557. elem: "input",
  5558. type: "text",
  5559. label: "Title"
  5560. },
  5561. numberofwords: {
  5562. elem: "input",
  5563. type: "text",
  5564. label: "Num Of Words"
  5565. },
  5566. target: "wikipedia-container"
  5567. }
  5568. },
  5569. /**
  5570. * @member wikipedia
  5571. * The setup function will get all of the needed
  5572. * items in place before the start function is called.
  5573. * This includes getting data from wikipedia, if the data
  5574. * is not received and processed before start is called start
  5575. * will not do anything
  5576. */
  5577. _setup : function( options ) {
  5578. // declare needed variables
  5579. // get a guid to use for the global wikicallback function
  5580. var _text, _guid = Popcorn.guid();
  5581. // if the user didn't specify a language default to english
  5582. if ( !options.lang ) {
  5583. options.lang = "en";
  5584. }
  5585. // if the user didn't specify number of words to use default to 200
  5586. options.numberofwords = options.numberofwords || 200;
  5587. // wiki global callback function with a unique id
  5588. // function gets the needed information from wikipedia
  5589. // and stores it by appending values to the options object
  5590. window[ "wikiCallback" + _guid ] = function ( data ) {
  5591. options._link = document.createElement( "a" );
  5592. options._link.setAttribute( "href", options.src );
  5593. options._link.setAttribute( "target", "_blank" );
  5594. // add the title of the article to the link
  5595. options._link.innerHTML = options.title || data.parse.displaytitle;
  5596. // get the content of the wiki article
  5597. options._desc = document.createElement( "p" );
  5598. // get the article text and remove any special characters
  5599. _text = data.parse.text[ "*" ].substr( data.parse.text[ "*" ].indexOf( "<p>" ) );
  5600. _text = _text.replace( /((<(.|\n)+?>)|(\((.*?)\) )|(\[(.*?)\]))/g, "" );
  5601. _text = _text.split( " " );
  5602. options._desc.innerHTML = ( _text.slice( 0, ( _text.length >= options.numberofwords ? options.numberofwords : _text.length ) ).join (" ") + " ..." ) ;
  5603. options._fired = true;
  5604. };
  5605. if ( options.src ) {
  5606. Popcorn.getScript( "//" + options.lang + ".wikipedia.org/w/api.php?action=parse&props=text&page=" +
  5607. options.src.slice( options.src.lastIndexOf( "/" ) + 1 ) + "&format=json&callback=wikiCallback" + _guid );
  5608. } else if ( Popcorn.plugin.debug ) {
  5609. throw new Error( "Wikipedia plugin needs a 'src'" );
  5610. }
  5611. },
  5612. /**
  5613. * @member wikipedia
  5614. * The start function will be executed when the currentTime
  5615. * of the video reaches the start time provided by the
  5616. * options variable
  5617. */
  5618. start: function( event, options ){
  5619. // dont do anything if the information didn't come back from wiki
  5620. var isReady = function () {
  5621. if ( !options._fired ) {
  5622. setTimeout( function () {
  5623. isReady();
  5624. }, 13);
  5625. } else {
  5626. if ( options._link && options._desc ) {
  5627. if ( document.getElementById( options.target ) ) {
  5628. document.getElementById( options.target ).appendChild( options._link );
  5629. document.getElementById( options.target ).appendChild( options._desc );
  5630. options._added = true;
  5631. }
  5632. }
  5633. }
  5634. };
  5635. isReady();
  5636. },
  5637. /**
  5638. * @member wikipedia
  5639. * The end function will be executed when the currentTime
  5640. * of the video reaches the end time provided by the
  5641. * options variable
  5642. */
  5643. end: function( event, options ){
  5644. // ensure that the data was actually added to the
  5645. // DOM before removal
  5646. if ( options._added ) {
  5647. document.getElementById( options.target ).removeChild( options._link );
  5648. document.getElementById( options.target ).removeChild( options._desc );
  5649. }
  5650. },
  5651. _teardown: function( options ){
  5652. if ( options._added ) {
  5653. options._link.parentNode && document.getElementById( options.target ).removeChild( options._link );
  5654. options._desc.parentNode && document.getElementById( options.target ).removeChild( options._desc );
  5655. delete options.target;
  5656. }
  5657. }
  5658. });
  5659. })( Popcorn );
  5660. // PLUGIN: Wordriver
  5661. (function ( Popcorn ) {
  5662. var container = {},
  5663. spanLocation = 0,
  5664. setupContainer = function( target ) {
  5665. container[ target ] = document.createElement( "div" );
  5666. var t = document.getElementById( target );
  5667. t && t.appendChild( container[ target ] );
  5668. container[ target ].style.height = "100%";
  5669. container[ target ].style.position = "relative";
  5670. return container[ target ];
  5671. },
  5672. // creates an object of supported, cross platform css transitions
  5673. span = document.createElement( "span" ),
  5674. prefixes = [ "webkit", "Moz", "ms", "O", "" ],
  5675. specProp = [ "Transform", "TransitionDuration", "TransitionTimingFunction" ],
  5676. supports = {},
  5677. prop;
  5678. document.getElementsByTagName( "head" )[ 0 ].appendChild( span );
  5679. for ( var sIdx = 0, sLen = specProp.length; sIdx < sLen; sIdx++ ) {
  5680. for ( var pIdx = 0, pLen = prefixes.length; pIdx < pLen; pIdx++ ) {
  5681. prop = prefixes[ pIdx ] + specProp[ sIdx ];
  5682. if ( prop in span.style ) {
  5683. supports[ specProp[ sIdx ].toLowerCase() ] = prop;
  5684. break;
  5685. }
  5686. }
  5687. }
  5688. // Garbage collect support test span
  5689. document.getElementsByTagName( "head" )[ 0 ].appendChild( span );
  5690. /**
  5691. * Word River popcorn plug-in
  5692. * Displays a string of text, fading it in and out
  5693. * while transitioning across the height of the parent container
  5694. * for the duration of the instance (duration = end - start)
  5695. *
  5696. * @param {Object} options
  5697. *
  5698. * Example:
  5699. var p = Popcorn( '#video' )
  5700. .wordriver({
  5701. start: 5, // When to begin the Word River animation
  5702. end: 15, // When to finish the Word River animation
  5703. text: 'Hello World', // The text you want to be displayed by Word River
  5704. target: 'wordRiverDiv', // The target div to append the text to
  5705. color: "blue" // The color of the text. (can be Hex value i.e. #FFFFFF )
  5706. } )
  5707. *
  5708. */
  5709. Popcorn.plugin( "wordriver" , {
  5710. manifest: {
  5711. about:{
  5712. name: "Popcorn WordRiver Plugin"
  5713. },
  5714. options: {
  5715. start: {
  5716. elem: "input",
  5717. type: "text",
  5718. label: "In"
  5719. },
  5720. end: {
  5721. elem: "input",
  5722. type: "text",
  5723. label: "Out"
  5724. },
  5725. target: "wordriver-container",
  5726. text: {
  5727. elem: "input",
  5728. type: "text",
  5729. label: "Text"
  5730. },
  5731. color: {
  5732. elem: "input",
  5733. type: "text",
  5734. label: "Color"
  5735. }
  5736. }
  5737. },
  5738. _setup: function( options ) {
  5739. if ( !document.getElementById( options.target ) && Popcorn.plugin.debug ) {
  5740. throw new Error( "target container doesn't exist" );
  5741. }
  5742. options._duration = options.end - options.start;
  5743. options._container = container[ options.target ] || setupContainer( options.target );
  5744. options.word = document.createElement( "span" );
  5745. options.word.style.position = "absolute";
  5746. options.word.style.whiteSpace = "nowrap";
  5747. options.word.style.opacity = 0;
  5748. options.word.style.MozTransitionProperty = "opacity, -moz-transform";
  5749. options.word.style.webkitTransitionProperty = "opacity, -webkit-transform";
  5750. options.word.style.OTransitionProperty = "opacity, -o-transform";
  5751. options.word.style.transitionProperty = "opacity, transform";
  5752. options.word.style[ supports.transitionduration ] = 1 + "s, " + options._duration + "s";
  5753. options.word.style[ supports.transitiontimingfunction ] = "linear";
  5754. options.word.innerHTML = options.text;
  5755. options.word.style.color = options.color || "black";
  5756. },
  5757. start: function( event, options ){
  5758. options._container.appendChild( options.word );
  5759. // Resets the transform when changing to a new currentTime before the end event occurred.
  5760. options.word.style[ supports.transform ] = "";
  5761. options.word.style.fontSize = ~~( 30 + 20 * Math.random() ) + "px";
  5762. spanLocation = spanLocation % ( options._container.offsetWidth - options.word.offsetWidth );
  5763. options.word.style.left = spanLocation + "px";
  5764. spanLocation += options.word.offsetWidth + 10;
  5765. options.word.style[ supports.transform ] = "translateY(" +
  5766. ( options._container.offsetHeight - options.word.offsetHeight ) + "px)";
  5767. options.word.style.opacity = 1;
  5768. // automatically clears the word based on time
  5769. setTimeout( function() {
  5770. options.word.style.opacity = 0;
  5771. // ensures at least one second exists, because the fade animation is 1 second
  5772. }, ( ( (options.end - options.start) - 1 ) || 1 ) * 1000 );
  5773. },
  5774. end: function( event, options ){
  5775. // manually clears the word based on user interaction
  5776. options.word.style.opacity = 0;
  5777. },
  5778. _teardown: function( options ) {
  5779. var target = document.getElementById( options.target );
  5780. // removes word span from generated container
  5781. options.word.parentNode && options._container.removeChild( options.word );
  5782. // if no more word spans exist in container, remove container
  5783. container[ options.target ] &&
  5784. !container[ options.target ].childElementCount &&
  5785. target && target.removeChild( container[ options.target ] ) &&
  5786. delete container[ options.target ];
  5787. }
  5788. });
  5789. })( Popcorn );
  5790. // PARSER: 0.3 JSON
  5791. (function (Popcorn) {
  5792. Popcorn.parser( "parseJSON", "JSON", function( data ) {
  5793. // declare needed variables
  5794. var retObj = {
  5795. title: "",
  5796. remote: "",
  5797. data: []
  5798. },
  5799. manifestData = {},
  5800. dataObj = data;
  5801. /*
  5802. TODO: add support for filling in source children of the video element
  5803. remote: [
  5804. {
  5805. src: "whatever.mp4",
  5806. type: 'video/mp4; codecs="avc1, mp4a"'
  5807. },
  5808. {
  5809. src: "whatever.ogv",
  5810. type: 'video/ogg; codecs="theora, vorbis"'
  5811. }
  5812. ]
  5813. */
  5814. Popcorn.forEach( dataObj.data, function ( obj, key ) {
  5815. retObj.data.push( obj );
  5816. });
  5817. return retObj;
  5818. });
  5819. })( Popcorn );
  5820. // PARSER: 0.1 SBV
  5821. (function (Popcorn) {
  5822. /**
  5823. * SBV popcorn parser plug-in
  5824. * Parses subtitle files in the SBV format.
  5825. * Times are expected in H:MM:SS.MIL format, with hours optional
  5826. * Subtitles which don't match expected format are ignored
  5827. * Data parameter is given by Popcorn, will need a text.
  5828. * Text is the file contents to be parsed
  5829. *
  5830. * @param {Object} data
  5831. *
  5832. * Example:
  5833. 0:00:02.400,0:00:07.200
  5834. Senator, we're making our final approach into Coruscant.
  5835. */
  5836. Popcorn.parser( "parseSBV", function( data ) {
  5837. // declare needed variables
  5838. var retObj = {
  5839. title: "",
  5840. remote: "",
  5841. data: []
  5842. },
  5843. subs = [],
  5844. lines,
  5845. i = 0,
  5846. len = 0,
  5847. idx = 0;
  5848. // [H:]MM:SS.MIL string to SS.MIL
  5849. // Will thrown exception on bad time format
  5850. var toSeconds = function( t_in ) {
  5851. var t = t_in.split( ":" ),
  5852. l = t.length-1,
  5853. time;
  5854. try {
  5855. time = parseInt( t[l-1], 10 )*60 + parseFloat( t[l], 10 );
  5856. // Hours optionally given
  5857. if ( l === 2 ) {
  5858. time += parseInt( t[0], 10 )*3600;
  5859. }
  5860. } catch ( e ) {
  5861. throw "Bad cue";
  5862. }
  5863. return time;
  5864. };
  5865. var createTrack = function( name, attributes ) {
  5866. var track = {};
  5867. track[name] = attributes;
  5868. return track;
  5869. };
  5870. // Here is where the magic happens
  5871. // Split on line breaks
  5872. lines = data.text.split( /(?:\r\n|\r|\n)/gm );
  5873. len = lines.length;
  5874. while ( i < len ) {
  5875. var sub = {},
  5876. text = [],
  5877. time = lines[i++].split( "," );
  5878. try {
  5879. sub.start = toSeconds( time[0] );
  5880. sub.end = toSeconds( time[1] );
  5881. // Gather all lines of text
  5882. while ( i < len && lines[i] ) {
  5883. text.push( lines[i++] );
  5884. }
  5885. // Join line breaks in text
  5886. sub.text = text.join( "<br />" );
  5887. subs.push( createTrack( "subtitle", sub ) );
  5888. } catch ( e ) {
  5889. // Bad cue, advance to end of cue
  5890. while ( i < len && lines[i] ) {
  5891. i++;
  5892. }
  5893. }
  5894. // Consume empty whitespace
  5895. while ( i < len && !lines[i] ) {
  5896. i++;
  5897. }
  5898. }
  5899. retObj.data = subs;
  5900. return retObj;
  5901. });
  5902. })( Popcorn );
  5903. // PARSER: 0.3 SRT
  5904. (function (Popcorn) {
  5905. /**
  5906. * SRT popcorn parser plug-in
  5907. * Parses subtitle files in the SRT format.
  5908. * Times are expected in HH:MM:SS,MIL format, though HH:MM:SS.MIL also supported
  5909. * Ignore styling, which may occur after the end time or in-text
  5910. * While not part of the "official" spec, majority of players support HTML and SSA styling tags
  5911. * SSA-style tags are stripped, HTML style tags are left for the browser to handle:
  5912. * HTML: <font>, <b>, <i>, <u>, <s>
  5913. * SSA: \N or \n, {\cmdArg1}, {\cmd(arg1, arg2, ...)}
  5914. * Data parameter is given by Popcorn, will need a text.
  5915. * Text is the file contents to be parsed
  5916. *
  5917. * @param {Object} data
  5918. *
  5919. * Example:
  5920. 1
  5921. 00:00:25,712 --> 00:00:30.399
  5922. This text is <font color="red">RED</font> and has not been {\pos(142,120)} positioned.
  5923. This takes \Nup three \nentire lines.
  5924. This contains nested <b>bold, <i>italic, <u>underline</u> and <s>strike-through</s></u></i></b> HTML tags
  5925. Unclosed but <b>supported tags are left in
  5926. <ggg>Unsupported</ggg> HTML tags are left in, even if <hhh>not closed.
  5927. SSA tags with {\i1} would open and close italicize {\i0}, but are stripped
  5928. Multiple {\pos(142,120)\b1}SSA tags are stripped
  5929. */
  5930. Popcorn.parser( "parseSRT", function( data ) {
  5931. // declare needed variables
  5932. var retObj = {
  5933. title: "",
  5934. remote: "",
  5935. data: []
  5936. },
  5937. subs = [],
  5938. i = 0,
  5939. len = 0,
  5940. idx = 0,
  5941. lines,
  5942. time,
  5943. text,
  5944. sub;
  5945. // Simple function to convert HH:MM:SS,MMM or HH:MM:SS.MMM to SS.MMM
  5946. // Assume valid, returns 0 on error
  5947. var toSeconds = function( t_in ) {
  5948. var t = t_in.split( ':' );
  5949. try {
  5950. var s = t[2].split( ',' );
  5951. // Just in case a . is decimal seperator
  5952. if ( s.length === 1 ) {
  5953. s = t[2].split( '.' );
  5954. }
  5955. return parseFloat( t[0], 10 )*3600 + parseFloat( t[1], 10 )*60 + parseFloat( s[0], 10 ) + parseFloat( s[1], 10 )/1000;
  5956. } catch ( e ) {
  5957. return 0;
  5958. }
  5959. };
  5960. var createTrack = function( name, attributes ) {
  5961. var track = {};
  5962. track[name] = attributes;
  5963. return track;
  5964. };
  5965. // Here is where the magic happens
  5966. // Split on line breaks
  5967. lines = data.text.split( /(?:\r\n|\r|\n)/gm );
  5968. len = lines.length;
  5969. for( i=0; i < len; i++ ) {
  5970. sub = {};
  5971. text = [];
  5972. sub.id = parseInt( lines[i++], 10 );
  5973. // Split on '-->' delimiter, trimming spaces as well
  5974. time = lines[i++].split( /[\t ]*-->[\t ]*/ );
  5975. sub.start = toSeconds( time[0] );
  5976. // So as to trim positioning information from end
  5977. idx = time[1].indexOf( " " );
  5978. if ( idx !== -1) {
  5979. time[1] = time[1].substr( 0, idx );
  5980. }
  5981. sub.end = toSeconds( time[1] );
  5982. // Build single line of text from multi-line subtitle in file
  5983. while ( i < len && lines[i] ) {
  5984. text.push( lines[i++] );
  5985. }
  5986. // Join into 1 line, SSA-style linebreaks
  5987. // Strip out other SSA-style tags
  5988. sub.text = text.join( "\\N" ).replace( /\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi, "" );
  5989. // Escape HTML entities
  5990. sub.text = sub.text.replace( /</g, "&lt;" ).replace( />/g, "&gt;" );
  5991. // Unescape great than and less than when it makes a valid html tag of a supported style (font, b, u, s, i)
  5992. // Modified version of regex from Phil Haack's blog: http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx
  5993. // Later modified by kev: http://kevin.deldycke.com/2007/03/ultimate-regular-expression-for-html-tag-parsing-with-php/
  5994. 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>" );
  5995. sub.text = sub.text.replace( /\\N/gi, "<br />" );
  5996. subs.push( createTrack( "subtitle", sub ) );
  5997. }
  5998. retObj.data = subs;
  5999. return retObj;
  6000. });
  6001. })( Popcorn );
  6002. // PARSER: 0.3 SSA/ASS
  6003. (function ( Popcorn ) {
  6004. /**
  6005. * SSA/ASS popcorn parser plug-in
  6006. * Parses subtitle files in the identical SSA and ASS formats.
  6007. * Style information is ignored, and may be found in these
  6008. * formats: (\N \n {\pos(400,570)} {\kf89})
  6009. * Out of the [Script Info], [V4 Styles], [Events], [Pictures],
  6010. * and [Fonts] sections, only [Events] is processed.
  6011. * Data parameter is given by Popcorn, will need a text.
  6012. * Text is the file contents to be parsed
  6013. *
  6014. * @param {Object} data
  6015. *
  6016. * Example:
  6017. [Script Info]
  6018. Title: Testing subtitles for the SSA Format
  6019. [V4 Styles]
  6020. Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
  6021. Style: Default,Arial,20,65535,65535,65535,-2147483640,-1,0,1,3,0,2,30,30,30,0,0
  6022. [Events]
  6023. Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
  6024. Dialogue: 0,0:00:02.40,0:00:07.20,Default,,0000,0000,0000,,Senator, {\kf89}we're \Nmaking our final \napproach into Coruscant.
  6025. Dialogue: 0,0:00:09.71,0:00:13.39,Default,,0000,0000,0000,,{\pos(400,570)}Very good, Lieutenant.
  6026. Dialogue: 0,0:00:15.04,0:00:18.04,Default,,0000,0000,0000,,It's \Na \ntrap!
  6027. *
  6028. */
  6029. // Register for SSA extensions
  6030. Popcorn.parser( "parseSSA", function( data ) {
  6031. // declare needed variables
  6032. var retObj = {
  6033. title: "",
  6034. remote: "",
  6035. data: [ ]
  6036. },
  6037. rNewLineFile = /(?:\r\n|\r|\n)/gm,
  6038. subs = [ ],
  6039. lines,
  6040. headers,
  6041. i = 0,
  6042. len;
  6043. // Here is where the magic happens
  6044. // Split on line breaks
  6045. lines = data.text.split( rNewLineFile );
  6046. len = lines.length;
  6047. // Ignore non-textual info
  6048. while ( i < len && lines[ i ] !== "[Events]" ) {
  6049. i++;
  6050. }
  6051. headers = parseFieldHeaders( lines[ ++i ] );
  6052. while ( ++i < len && lines[ i ] && lines[ i ][ 0 ] !== "[" ) {
  6053. try {
  6054. subs.push( createTrack( "subtitle", parseSub( lines[ i ], headers ) ) );
  6055. } catch ( e ) {}
  6056. }
  6057. retObj.data = subs;
  6058. return retObj;
  6059. });
  6060. function parseSub( line, headers ) {
  6061. // Trim beginning 'Dialogue: ' and split on delim
  6062. var fields = line.substr( 10 ).split( "," ),
  6063. rAdvancedStyles = /\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi,
  6064. rNewLineSSA = /\\N/gi,
  6065. sub;
  6066. sub = {
  6067. start: toSeconds( fields[ headers.start ] ),
  6068. end: toSeconds( fields[ headers.end ] )
  6069. };
  6070. // Invalid time, skip
  6071. if ( sub.start === -1 || sub.end === -1 ) {
  6072. throw "Invalid time";
  6073. }
  6074. // Eliminate advanced styles and convert forced line breaks
  6075. sub.text = getTextFromFields( fields, headers.text ).replace( rAdvancedStyles, "" ).replace( rNewLineSSA, "<br />" );
  6076. return sub;
  6077. }
  6078. // h:mm:ss.cc (centisec) string to SS.mmm
  6079. // Returns -1 if invalid
  6080. function toSeconds( t_in ) {
  6081. var t = t_in.split( ":" );
  6082. // Not all there
  6083. if ( t_in.length !== 10 || t.length < 3 ) {
  6084. return -1;
  6085. }
  6086. return parseInt( t[ 0 ], 10 ) * 3600 + parseInt( t[ 1 ], 10 ) * 60 + parseFloat( t[ 2 ], 10 );
  6087. }
  6088. function getTextFromFields( fields, startIdx ) {
  6089. var fieldLen = fields.length,
  6090. text = [ ],
  6091. i = startIdx;
  6092. // There may be commas in the text which were split, append back together into one line
  6093. for( ; i < fieldLen; i++ ) {
  6094. text.push( fields[ i ] );
  6095. }
  6096. return text.join( "," );
  6097. }
  6098. function createTrack( name, attributes ) {
  6099. var track = {};
  6100. track[ name ] = attributes;
  6101. return track;
  6102. }
  6103. function parseFieldHeaders( line ) {
  6104. // Trim 'Format: ' off front, split on delim
  6105. var fields = line.substr( 8 ).split( ", " ),
  6106. result = {},
  6107. len,
  6108. i;
  6109. //Find where in Dialogue string the start, end and text info is
  6110. for ( i = 0, len = fields.length; i < len; i++ ) {
  6111. if ( fields[ i ] === "Start" ) {
  6112. result.start = i;
  6113. } else if ( fields[ i ] === "End" ) {
  6114. result.end = i;
  6115. } else if ( fields[ i ] === "Text" ) {
  6116. result.text = i;
  6117. }
  6118. }
  6119. return result;
  6120. }
  6121. })( Popcorn );
  6122. // PARSER: 0.3 TTML
  6123. (function (Popcorn) {
  6124. /**
  6125. * TTML popcorn parser plug-in
  6126. * Parses subtitle files in the TTML format.
  6127. * Times may be absolute to the timeline or relative
  6128. * Absolute times are ISO 8601 format (hh:mm:ss[.mmm])
  6129. * Relative times are a fraction followed by a unit metric (d.ddu)
  6130. * Relative times are relative to the time given on the parent node
  6131. * Styling information is ignored
  6132. * Data parameter is given by Popcorn, will need an xml.
  6133. * Xml is the file contents to be processed
  6134. *
  6135. * @param {Object} data
  6136. *
  6137. * Example:
  6138. <tt xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling" xmlns="http://www.w3.org/2006/04/ttaf1">
  6139. <body region="subtitleArea">
  6140. <div>
  6141. <p xml:id="subtitle1" begin="0.76s" end="3.45s">
  6142. It seems a paradox, does it not,
  6143. </p>
  6144. </div>
  6145. </body>
  6146. </tt>
  6147. */
  6148. Popcorn.parser( "parseTTML", function( data ) {
  6149. // declare needed variables
  6150. var returnData = {
  6151. title: "",
  6152. remote: "",
  6153. data: []
  6154. },
  6155. node,
  6156. numTracks = 0,
  6157. region;
  6158. // Convert time expression to SS.mmm
  6159. // Expression may be absolute to timeline (hh:mm:ss.ms)
  6160. // or relative ( fraction followedd by metric ) ex: 3.4s, 5.7m
  6161. // Returns -1 if invalid
  6162. var toSeconds = function ( t_in, offset ) {
  6163. if ( !t_in ) {
  6164. return -1;
  6165. }
  6166. var t = t_in.split( ":" ),
  6167. l = t.length - 1,
  6168. metric,
  6169. multiplier,
  6170. i;
  6171. // Try clock time
  6172. if ( l >= 2 ) {
  6173. return parseInt( t[0], 10 )*3600 + parseInt( t[l-1], 10 )*60 + parseFloat( t[l], 10 );
  6174. }
  6175. // Was not clock time, assume relative time
  6176. // Take metric from end of string (may not be single character)
  6177. // First find metric
  6178. for( i = t_in.length - 1; i >= 0; i-- ) {
  6179. if ( t_in[i] <= "9" && t_in[i] >= "0" ) {
  6180. break;
  6181. }
  6182. }
  6183. // Point i at metric and normalize offsete time
  6184. i++;
  6185. metric = t_in.substr( i );
  6186. offset = offset || 0;
  6187. // Determine multiplier for metric relative to seconds
  6188. if ( metric === "h" ) {
  6189. multiplier = 3600;
  6190. } else if ( metric === "m" ) {
  6191. multiplier = 60;
  6192. } else if ( metric === "s" ) {
  6193. multiplier = 1;
  6194. } else if ( metric === "ms" ) {
  6195. multiplier = 0.001;
  6196. } else {
  6197. return -1;
  6198. }
  6199. // Valid multiplier
  6200. return parseFloat( t_in.substr( 0, i ) ) * multiplier + offset;
  6201. };
  6202. // creates an object of all atrributes keyd by name
  6203. var createTrack = function( name, attributes ) {
  6204. var track = {};
  6205. track[name] = attributes;
  6206. return track;
  6207. };
  6208. // Parse a node for text content
  6209. var parseNode = function( node, timeOffset ) {
  6210. var sub = {};
  6211. // Trim left and right whitespace from text and change non-explicit line breaks to spaces
  6212. sub.text = node.textContent.replace(/^[\s]+|[\s]+$/gm, "").replace(/(?:\r\n|\r|\n)/gm, "<br />");
  6213. sub.id = node.getAttribute( "xml:id" ) || node.getAttribute( "id" );
  6214. sub.start = toSeconds ( node.getAttribute( "begin" ), timeOffset );
  6215. sub.end = toSeconds( node.getAttribute( "end" ), timeOffset );
  6216. sub.target = region;
  6217. if ( sub.end < 0 ) {
  6218. // No end given, infer duration if possible
  6219. // Otherwise, give end as MAX_VALUE
  6220. sub.end = toSeconds( node.getAttribute( "duration" ), 0 );
  6221. if ( sub.end >= 0 ) {
  6222. sub.end += sub.start;
  6223. } else {
  6224. sub.end = Number.MAX_VALUE;
  6225. }
  6226. }
  6227. return sub;
  6228. };
  6229. // Parse the children of the given node
  6230. var parseChildren = function( node, timeOffset ) {
  6231. var currNode = node.firstChild,
  6232. sub,
  6233. newOffset;
  6234. while ( currNode ) {
  6235. if ( currNode.nodeType === 1 ) {
  6236. if ( currNode.nodeName === "p" ) {
  6237. // p is a teextual node, process contents as subtitle
  6238. sub = parseNode( currNode, timeOffset );
  6239. returnData.data.push( createTrack( "subtitle", sub ) );
  6240. numTracks++;
  6241. } else if ( currNode.nodeName === "div" ) {
  6242. // div is container for subtitles, recurse
  6243. newOffset = toSeconds( currNode.getAttribute("begin") );
  6244. if (newOffset < 0 ) {
  6245. newOffset = timeOffset;
  6246. }
  6247. parseChildren( currNode, newOffset );
  6248. }
  6249. }
  6250. currNode = currNode.nextSibling;
  6251. }
  6252. };
  6253. // Null checks
  6254. if ( !data.xml || !data.xml.documentElement || !( node = data.xml.documentElement.firstChild ) ) {
  6255. return returnData;
  6256. }
  6257. // Find body tag
  6258. while ( node.nodeName !== "body" ) {
  6259. node = node.nextSibling;
  6260. }
  6261. region = "";
  6262. parseChildren( node, 0 );
  6263. return returnData;
  6264. });
  6265. })( Popcorn );
  6266. // PARSER: 0.1 TTXT
  6267. (function (Popcorn) {
  6268. /**
  6269. * TTXT popcorn parser plug-in
  6270. * Parses subtitle files in the TTXT format.
  6271. * Style information is ignored.
  6272. * Data parameter is given by Popcorn, will need an xml.
  6273. * Xml is the file contents to be parsed as a DOM tree
  6274. *
  6275. * @param {Object} data
  6276. *
  6277. * Example:
  6278. <TextSample sampleTime="00:00:00.000" text=""></TextSample>
  6279. */
  6280. Popcorn.parser( "parseTTXT", function( data ) {
  6281. // declare needed variables
  6282. var returnData = {
  6283. title: "",
  6284. remote: "",
  6285. data: []
  6286. };
  6287. // Simple function to convert HH:MM:SS.MMM to SS.MMM
  6288. // Assume valid, returns 0 on error
  6289. var toSeconds = function(t_in) {
  6290. var t = t_in.split(":");
  6291. var time = 0;
  6292. try {
  6293. return parseFloat(t[0], 10)*60*60 + parseFloat(t[1], 10)*60 + parseFloat(t[2], 10);
  6294. } catch (e) { time = 0; }
  6295. return time;
  6296. };
  6297. // creates an object of all atrributes keyed by name
  6298. var createTrack = function( name, attributes ) {
  6299. var track = {};
  6300. track[name] = attributes;
  6301. return track;
  6302. };
  6303. // this is where things actually start
  6304. var node = data.xml.lastChild.lastChild; // Last Child of TextStreamHeader
  6305. var lastStart = Number.MAX_VALUE;
  6306. var cmds = [];
  6307. // Work backwards through DOM, processing TextSample nodes
  6308. while (node) {
  6309. if ( node.nodeType === 1 && node.nodeName === "TextSample") {
  6310. var sub = {};
  6311. sub.start = toSeconds(node.getAttribute('sampleTime'));
  6312. sub.text = node.getAttribute('text');
  6313. if (sub.text) { // Only process if text to display
  6314. // Infer end time from prior element, ms accuracy
  6315. sub.end = lastStart - 0.001;
  6316. cmds.push( createTrack("subtitle", sub) );
  6317. }
  6318. lastStart = sub.start;
  6319. }
  6320. node = node.previousSibling;
  6321. }
  6322. returnData.data = cmds.reverse();
  6323. return returnData;
  6324. });
  6325. })( Popcorn );
  6326. // PARSER: 0.3 WebSRT/VTT
  6327. (function ( Popcorn ) {
  6328. /**
  6329. * WebVTT popcorn parser plug-in
  6330. * Parses subtitle files in the WebVTT format.
  6331. * Specification here: http://www.whatwg.org/specs/web-apps/current-work/webvtt.html
  6332. * Styles which appear after timing information are presently ignored.
  6333. * Inline styling tags follow HTML conventions and are left in for the browser to handle (or ignore if VTT-specific)
  6334. * Data parameter is given by Popcorn, text property holds file contents.
  6335. * Text is the file contents to be parsed
  6336. *
  6337. * @param {Object} data
  6338. *
  6339. * Example:
  6340. 00:32.500 --> 00:00:33.500 A:start S:50% D:vertical L:98%
  6341. <v Neil DeGrass Tyson><i>Laughs</i>
  6342. */
  6343. Popcorn.parser( "parseVTT", function( data ) {
  6344. // declare needed variables
  6345. var retObj = {
  6346. title: "",
  6347. remote: "",
  6348. data: []
  6349. },
  6350. subs = [],
  6351. i = 0,
  6352. len = 0,
  6353. lines,
  6354. text,
  6355. sub,
  6356. rNewLine = /(?:\r\n|\r|\n)/gm;
  6357. // Here is where the magic happens
  6358. // Split on line breaks
  6359. lines = data.text.split( rNewLine );
  6360. len = lines.length;
  6361. // Check for BOF token
  6362. if ( len === 0 || lines[ 0 ] !== "WEBVTT" ) {
  6363. return retObj;
  6364. }
  6365. i++;
  6366. while ( i < len ) {
  6367. text = [];
  6368. try {
  6369. i = skipWhitespace( lines, len, i );
  6370. sub = parseCueHeader( lines[ i++ ] );
  6371. // Build single line of text from multi-line subtitle in file
  6372. while ( i < len && lines[ i ] ) {
  6373. text.push( lines[ i++ ] );
  6374. }
  6375. // Join lines together to one and build subtitle text
  6376. sub.text = text.join( "<br />" );
  6377. subs.push( createTrack( "subtitle", sub ) );
  6378. } catch ( e ) {
  6379. i = skipNonWhitespace( lines, len, i );
  6380. }
  6381. }
  6382. retObj.data = subs;
  6383. return retObj;
  6384. });
  6385. // [HH:]MM:SS.mmm string to SS.mmm float
  6386. // Throws exception if invalid
  6387. function toSeconds ( t_in ) {
  6388. var t = t_in.split( ":" ),
  6389. l = t_in.length,
  6390. time;
  6391. // Invalid time string provided
  6392. if ( l !== 12 && l !== 9 ) {
  6393. throw "Bad cue";
  6394. }
  6395. l = t.length - 1;
  6396. try {
  6397. time = parseInt( t[ l-1 ], 10 ) * 60 + parseFloat( t[ l ], 10 );
  6398. // Hours were given
  6399. if ( l === 2 ) {
  6400. time += parseInt( t[ 0 ], 10 ) * 3600;
  6401. }
  6402. } catch ( e ) {
  6403. throw "Bad cue";
  6404. }
  6405. return time;
  6406. }
  6407. function createTrack( name, attributes ) {
  6408. var track = {};
  6409. track[ name ] = attributes;
  6410. return track;
  6411. }
  6412. function parseCueHeader ( line ) {
  6413. var lineSegments,
  6414. args,
  6415. sub = {},
  6416. rToken = /-->/,
  6417. rWhitespace = /[\t ]+/;
  6418. if ( !line || line.indexOf( "-->" ) === -1 ) {
  6419. throw "Bad cue";
  6420. }
  6421. lineSegments = line.replace( rToken, " --> " ).split( rWhitespace );
  6422. if ( lineSegments.length < 2 ) {
  6423. throw "Bad cue";
  6424. }
  6425. sub.id = line;
  6426. sub.start = toSeconds( lineSegments[ 0 ] );
  6427. sub.end = toSeconds( lineSegments[ 2 ] );
  6428. return sub;
  6429. }
  6430. function skipWhitespace ( lines, len, i ) {
  6431. while ( i < len && !lines[ i ] ) {
  6432. i++;
  6433. }
  6434. return i;
  6435. }
  6436. function skipNonWhitespace ( lines, len, i ) {
  6437. while ( i < len && lines[ i ] ) {
  6438. i++;
  6439. }
  6440. return i;
  6441. }
  6442. })( Popcorn );
  6443. // PARSER: 0.1 XML
  6444. (function (Popcorn) {
  6445. /**
  6446. *
  6447. *
  6448. */
  6449. Popcorn.parser( "parseXML", "XML", function( data ) {
  6450. // declare needed variables
  6451. var returnData = {
  6452. title: "",
  6453. remote: "",
  6454. data: []
  6455. },
  6456. manifestData = {};
  6457. // Simple function to convert 0:05 to 0.5 in seconds
  6458. // acceptable formats are HH:MM:SS:MM, MM:SS:MM, SS:MM, SS
  6459. var toSeconds = function(time) {
  6460. var t = time.split(":");
  6461. if (t.length === 1) {
  6462. return parseFloat(t[0], 10);
  6463. } else if (t.length === 2) {
  6464. return parseFloat(t[0], 10) + parseFloat(t[1] / 12, 10);
  6465. } else if (t.length === 3) {
  6466. return parseInt(t[0] * 60, 10) + parseFloat(t[1], 10) + parseFloat(t[2] / 12, 10);
  6467. } else if (t.length === 4) {
  6468. return parseInt(t[0] * 3600, 10) + parseInt(t[1] * 60, 10) + parseFloat(t[2], 10) + parseFloat(t[3] / 12, 10);
  6469. }
  6470. };
  6471. // turns a node tree element into a straight up javascript object
  6472. // also converts in and out to start and end
  6473. // also links manifest data with ids
  6474. var objectifyAttributes = function ( nodeAttributes ) {
  6475. var returnObject = {};
  6476. for ( var i = 0, nal = nodeAttributes.length; i < nal; i++ ) {
  6477. var key = nodeAttributes.item(i).nodeName,
  6478. data = nodeAttributes.item(i).nodeValue;
  6479. // converts in into start
  6480. if (key === "in") {
  6481. returnObject.start = toSeconds( data );
  6482. // converts out into end
  6483. } else if ( key === "out" ){
  6484. returnObject.end = toSeconds( data );
  6485. // this is where ids in the manifest are linked
  6486. } else if ( key === "resourceid" ) {
  6487. Popcorn.extend( returnObject, manifestData[data] );
  6488. // everything else
  6489. } else {
  6490. returnObject[key] = data;
  6491. }
  6492. }
  6493. return returnObject;
  6494. };
  6495. // creates an object of all atrributes keyd by name
  6496. var createTrack = function( name, attributes ) {
  6497. var track = {};
  6498. track[name] = attributes;
  6499. return track;
  6500. };
  6501. // recursive function to process a node, or process the next child node
  6502. var parseNode = function ( node, allAttributes, manifest ) {
  6503. var attributes = {};
  6504. Popcorn.extend( attributes, allAttributes, objectifyAttributes( node.attributes ), { text: node.textContent } );
  6505. var childNodes = node.childNodes;
  6506. // processes the node
  6507. if ( childNodes.length < 1 || ( childNodes.length === 1 && childNodes[0].nodeType === 3 ) ) {
  6508. if ( !manifest ) {
  6509. returnData.data.push( createTrack( node.nodeName, attributes ) );
  6510. } else {
  6511. manifestData[attributes.id] = attributes;
  6512. }
  6513. // process the next child node
  6514. } else {
  6515. for ( var i = 0; i < childNodes.length; i++ ) {
  6516. if ( childNodes[i].nodeType === 1 ) {
  6517. parseNode( childNodes[i], attributes, manifest );
  6518. }
  6519. }
  6520. }
  6521. };
  6522. // this is where things actually start
  6523. var x = data.documentElement.childNodes;
  6524. for ( var i = 0, xl = x.length; i < xl; i++ ) {
  6525. if ( x[i].nodeType === 1 ) {
  6526. // start the process of each main node type, manifest or timeline
  6527. if ( x[i].nodeName === "manifest" ) {
  6528. parseNode( x[i], {}, true );
  6529. } else { // timeline
  6530. parseNode( x[i], {}, false );
  6531. }
  6532. }
  6533. }
  6534. return returnData;
  6535. });
  6536. })( Popcorn );
  6537. // Popcorn Soundcloud Player Wrapper
  6538. ( function( Popcorn, global ) {
  6539. /**
  6540. * Soundcloud wrapper for Popcorn.
  6541. * This player adds enables Popcorn.js to handle Soundcloud audio. It does so by masking an embedded Soundcloud Flash object
  6542. * as a video and implementing the HTML5 Media Element interface.
  6543. *
  6544. * You can configure the video source and dimensions in two ways:
  6545. * 1. Use the embed code path supplied by Soundcloud the id of the desired location into a new Popcorn.soundcloud object.
  6546. * Width and height can be configured throughh CSS.
  6547. *
  6548. * <div id="player_1" style="width: 500px; height: 81px"></div>
  6549. * <script type="text/javascript">
  6550. * document.addEventListener("DOMContentLoaded", function() {
  6551. * var popcorn = Popcorn( Popcorn.soundcloud( "player_1", "http://soundcloud.com/forss/flickermood" ));
  6552. * }, false);
  6553. * </script>
  6554. *
  6555. * 2. Width and height may also be configured directly with the player; this will override any CSS. This is useful for
  6556. * when different sizes are desired. for multiple players within the same parent container.
  6557. *
  6558. * <div id="player_1"></div>
  6559. * <script type="text/javascript">
  6560. * document.addEventListener("DOMContentLoaded", function() {
  6561. * var popcorn = Popcorn( Popcorn.soundcloud( "player_1", "http://soundcloud.com/forss/flickermood", {
  6562. * width: "500", // Optional, will default to CSS values
  6563. * height: "81" // Optional, will default to CSS values
  6564. * }));
  6565. * }, false);
  6566. * </script>
  6567. *
  6568. * The player can be further configured to integrate with the SoundCloud API:
  6569. *
  6570. * var popcorn = Popcorn( Popcorn.soundcloud( "player_1", "http://soundcloud.com/forss/flickermood", {
  6571. * width: "100%", // Optional, the width for the player. May also be as '##px'
  6572. * // Defaults to the maximum possible width
  6573. * height: "81px", // Optional, the height for the player. May also be as '###%'
  6574. * // Defaults to 81px
  6575. * api: { // Optional, information for Soundcloud API interaction
  6576. * key: "abcdefsdfsdf", // Required for API interaction. The Soundcloud API key
  6577. * commentdiv: "divId_for_output", // Required for comment retrieval, the Div Id for outputting comments.
  6578. * commentformat: function( comment ) {} // Optional, a function to format a comment. Returns HTML string
  6579. * }
  6580. * }));
  6581. *
  6582. * Comments are retrieved from Soundcloud when the player is registered with Popcorn by calling the registerWithPopcorn()
  6583. * function. For this to work, the api_key and commentdiv attributes must be set. Comments are output by default similar to
  6584. * how Soundcloud formats them in-player, but a custom formatting function may be supplied. It receives a comment object and
  6585. * the current date. A comment object has:
  6586. *
  6587. * var comment = {
  6588. * start: 0, // Required. Start time in ms.
  6589. * date: new Date(), // Required. Date comment wasa posted.
  6590. * text: "", // Required. Comment text
  6591. * user: { // Required. Describes the user who posted the comment
  6592. * name: "", // Required. User name
  6593. * profile: "", // Required. User profile link
  6594. * avatar: "" // Required. User avatar link
  6595. * }
  6596. * }
  6597. *
  6598. * These events are completely custom-implemented and may be subscribed to at any time:
  6599. * canplaythrough
  6600. * durationchange
  6601. * load
  6602. * loadedmetadata
  6603. * loadstart
  6604. * play
  6605. * readystatechange
  6606. * volumechange
  6607. *
  6608. * These events are related to player functionality and must be subscribed to during or after the load event:
  6609. * canplay
  6610. * ended
  6611. * error
  6612. * pause
  6613. * playing
  6614. * progress
  6615. * seeked
  6616. * timeupdate
  6617. *
  6618. * These events are not supported:
  6619. * abort
  6620. * emptied
  6621. * loadeddata
  6622. * ratechange
  6623. * seeking
  6624. * stalled
  6625. * suspend
  6626. * waiting
  6627. *
  6628. * Supported media attributes:
  6629. * autoplay ( via Popcorn )
  6630. * currentTime
  6631. * defaultPlaybackRate ( get only )
  6632. * duration ( get only )
  6633. * ended ( get only )
  6634. * initialTime ( get only, always 0 )
  6635. * loop ( get only, set by calling setLoop() )
  6636. * muted ( get only )
  6637. * paused ( get only )
  6638. * playbackRate ( get only )
  6639. * played ( get only, 0/1 only )
  6640. * readyState ( get only )
  6641. * src ( get only )
  6642. * volume
  6643. *
  6644. * load() function
  6645. * mute() function ( toggles on/off )
  6646. * play() function
  6647. * pause() function
  6648. *
  6649. * Unsupported media attributes:
  6650. * buffered
  6651. * networkState
  6652. * preload
  6653. * seekable
  6654. * seeking
  6655. * startOffsetTime
  6656. *
  6657. * canPlayType() function
  6658. */
  6659. // Trackers
  6660. var timeupdateInterval = 33,
  6661. timeCheckInterval = 0.25,
  6662. abs = Math.abs,
  6663. floor = Math.floor,
  6664. round = Math.round,
  6665. registry = {};
  6666. function hasAllDependencies() {
  6667. return global.swfobject && global.soundcloud;
  6668. }
  6669. // Borrowed from: http://www.quirksmode.org/dom/getstyles.html
  6670. // Gets the style for the given element
  6671. function getStyle( elem, styleProp ) {
  6672. if ( elem.currentStyle ) {
  6673. // IE way
  6674. return elem.currentStyle[styleProp];
  6675. } else if ( global.getComputedStyle ) {
  6676. // Firefox, Chrome, et. al
  6677. return document.defaultView.getComputedStyle( elem, null ).getPropertyValue( styleProp );
  6678. }
  6679. }
  6680. function formatComment( comment ) {
  6681. // Calclate the difference between d and now, express as "n units ago"
  6682. function ago( d ) {
  6683. var diff = ( ( new Date() ).getTime() - d.getTime() )/1000;
  6684. function pluralize( value, unit ) {
  6685. return value + " " + unit + ( value > 1 ? "s" : "") + " ago";
  6686. }
  6687. if ( diff < 60 ) {
  6688. return pluralize( round( diff ), "second" );
  6689. }
  6690. diff /= 60;
  6691. if ( diff < 60 ) {
  6692. return pluralize( round( diff ), "minute" );
  6693. }
  6694. diff /= 60;
  6695. if ( diff < 24 ) {
  6696. return pluralize( round( diff ), "hour" );
  6697. }
  6698. diff /= 24;
  6699. // Rough approximation of months
  6700. if ( diff < 30 ) {
  6701. return pluralize( round( diff ), "day" );
  6702. }
  6703. if ( diff < 365 ) {
  6704. return pluralize( round( diff/30 ), "month" );
  6705. }
  6706. return pluralize( round( diff/365 ), "year" );
  6707. }
  6708. // Converts sec to min.sec
  6709. function timeToFraction ( totalSec ) {
  6710. var min = floor( totalSec / 60 ),
  6711. sec = round( totalSec % 60 );
  6712. return min + "." + ( sec < 10 ? "0" : "" ) + sec;
  6713. }
  6714. return '<div><a href="' + comment.user.profile + '">' +
  6715. '<img width="16px height="16px" src="' + comment.user.avatar + '"></img>' +
  6716. comment.user.name + '</a> at ' + timeToFraction( comment.start ) + ' ' +
  6717. ago( comment.date ) +
  6718. '<br />' + comment.text + '</span>';
  6719. }
  6720. function isReady( self ) {
  6721. if ( !hasAllDependencies() ) {
  6722. setTimeout( function() {
  6723. isReady( self );
  6724. }, 15 );
  6725. return;
  6726. }
  6727. var flashvars = {
  6728. enable_api: true,
  6729. object_id: self._playerId,
  6730. url: self.src,
  6731. // Hide comments in player if showing them elsewhere
  6732. show_comments: !self._options.api.key && !self._options.api.commentdiv
  6733. },
  6734. params = {
  6735. allowscriptaccess: "always",
  6736. // This is so we can overlay html ontop of Flash
  6737. wmode: 'transparent'
  6738. },
  6739. attributes = {
  6740. id: self._playerId,
  6741. name: self._playerId
  6742. },
  6743. actualTarget = document.createElement( 'div' );
  6744. actualTarget.setAttribute( "id", self._playerId );
  6745. self._container.appendChild( actualTarget );
  6746. swfobject.embedSWF( "http://player.soundcloud.com/player.swf", self._playerId, self.offsetWidth, self.height, "9.0.0", "expressInstall.swf", flashvars, params, attributes );
  6747. }
  6748. Popcorn.getScript( "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js" );
  6749. // Source file originally from 'https://github.com/soundcloud/Widget-JS-API/raw/master/soundcloud.player.api.js'
  6750. Popcorn.getScript( "http://popcornjs.org/code/players/soundcloud/lib/soundcloud.player.api.js", function() {
  6751. // Play event is fired twice when player is first started. Ignore second one
  6752. var ignorePlayEvt = 1;
  6753. // Register the wrapper's load event with the player
  6754. soundcloud.addEventListener( 'onPlayerReady', function( object, data ) {
  6755. var wrapper = registry[object.api_getFlashId()];
  6756. wrapper.swfObj = object;
  6757. wrapper.duration = object.api_getTrackDuration();
  6758. wrapper.currentTime = object.api_getTrackPosition();
  6759. // This eliminates volumechangee event from firing on load
  6760. wrapper.volume = wrapper.previousVolume = object.api_getVolume()/100;
  6761. // The numeric id of the track for use with Soundcloud API
  6762. wrapper._mediaId = data.mediaId;
  6763. wrapper.dispatchEvent( 'load' );
  6764. wrapper.dispatchEvent( 'canplay' );
  6765. wrapper.dispatchEvent( 'durationchange' );
  6766. wrapper.timeupdate();
  6767. });
  6768. // Register events for when the flash player plays a track for the first time
  6769. soundcloud.addEventListener( 'onMediaStart', function( object, data ) {
  6770. var wrapper = registry[object.api_getFlashId()];
  6771. wrapper.played = 1;
  6772. wrapper.dispatchEvent( 'playing' );
  6773. });
  6774. // Register events for when the flash player plays a track
  6775. soundcloud.addEventListener( 'onMediaPlay', function( object, data ) {
  6776. if ( ignorePlayEvt ) {
  6777. ignorePlayEvt = 0;
  6778. return;
  6779. }
  6780. var wrapper = registry[object.api_getFlashId()];
  6781. wrapper.dispatchEvent( 'play' );
  6782. });
  6783. // Register events for when the flash player pauses a track
  6784. soundcloud.addEventListener( 'onMediaPause', function( object, data ) {
  6785. var wrapper = registry[object.api_getFlashId()];
  6786. wrapper.dispatchEvent( 'pause' );
  6787. });
  6788. // Register events for when the flash player is buffering
  6789. soundcloud.addEventListener( 'onMediaBuffering', function( object, data ) {
  6790. var wrapper = registry[object.api_getFlashId()];
  6791. wrapper.dispatchEvent( 'progress' );
  6792. if ( wrapper.readyState === 0 ) {
  6793. wrapper.readyState = 3;
  6794. wrapper.dispatchEvent( "readystatechange" );
  6795. }
  6796. });
  6797. // Register events for when the flash player is done buffering
  6798. soundcloud.addEventListener( 'onMediaDoneBuffering', function( object, data ) {
  6799. var wrapper = registry[object.api_getFlashId()];
  6800. wrapper.dispatchEvent( 'canplaythrough' );
  6801. });
  6802. // Register events for when the flash player has finished playing
  6803. soundcloud.addEventListener( 'onMediaEnd', function( object, data ) {
  6804. var wrapper = registry[object.api_getFlashId()];
  6805. wrapper.paused = 1;
  6806. //wrapper.pause();
  6807. wrapper.dispatchEvent( 'ended' );
  6808. });
  6809. // Register events for when the flash player has seeked
  6810. soundcloud.addEventListener( 'onMediaSeek', function( object, data ) {
  6811. var wrapper = registry[object.api_getFlashId()];
  6812. wrapper.setCurrentTime( object.api_getTrackPosition() );
  6813. if ( wrapper.paused ) {
  6814. wrapper.dispatchEvent( "timeupdate" );
  6815. }
  6816. });
  6817. // Register events for when the flash player has errored
  6818. soundcloud.addEventListener( 'onPlayerError', function( object, data ) {
  6819. var wrapper = registry[object.api_getFlashId()];
  6820. wrapper.dispatchEvent( 'error' );
  6821. });
  6822. });
  6823. Popcorn.soundcloud = function( containerId, src, options ) {
  6824. return new Popcorn.soundcloud.init( containerId, src, options );
  6825. };
  6826. // A constructor, but we need to wrap it to allow for "static" functions
  6827. Popcorn.soundcloud.init = (function() {
  6828. function pullFromContainer( that ) {
  6829. var options = that._options,
  6830. container = that._container,
  6831. bounds = container.getBoundingClientRect(),
  6832. tmp,
  6833. undef;
  6834. that.width = options.width || getStyle( container, "width" ) || "100%";
  6835. that.height = options.height || getStyle( container, "height" ) || "81px";
  6836. that.src = options.src;
  6837. that.autoplay = options.autoplay;
  6838. if ( parseFloat( that.height, 10 ) !== 81 ) {
  6839. that.height = "81px";
  6840. }
  6841. that.offsetLeft = bounds.left;
  6842. that.offsetTop = bounds.top;
  6843. that.offsetHeight = parseFloat( that.height, 10 );
  6844. that.offsetWidth = parseFloat( that.width, 10 );
  6845. // Width and height may've been specified as a %, find the value now in case a plugin needs it (like subtitle)
  6846. if ( /[\d]+%/.test( that.width ) ) {
  6847. tmp = getStyle( container, "width" );
  6848. that._container.style.width = that.width;
  6849. that.offsetWidth = that._container.offsetWidth;
  6850. that._container.style.width = tmp;
  6851. }
  6852. if ( /[\d]+%/.test( that.height ) ) {
  6853. tmp = getStyle( container, "height" );
  6854. that._container.style.height = that.height;
  6855. that.offsetHeight = that._container.offsetHeight;
  6856. that._container.style.height = tmp;
  6857. }
  6858. }
  6859. // If container id is not supplied, assumed to be same as player id
  6860. var ctor = function ( containerId, src, options ) {
  6861. if ( !containerId ) {
  6862. throw "Must supply an id!";
  6863. } else if ( !src ) {
  6864. throw "Must supply a source!";
  6865. } else if ( /file/.test( location.protocol ) ) {
  6866. throw "Must run from a web server!";
  6867. }
  6868. var container = this._container = document.getElementById( containerId );
  6869. if ( !container ) {
  6870. throw "Could not find that container in the DOM!";
  6871. }
  6872. options = options || {};
  6873. options.api = options.api || {};
  6874. options.target = containerId;
  6875. options.src = src;
  6876. options.api.commentformat = options.api.commentformat || formatComment;
  6877. this._mediaId = 0;
  6878. this._listeners = {};
  6879. this._playerId = Popcorn.guid( options.target );
  6880. this._containerId = options.target;
  6881. this._options = options;
  6882. this._comments = [];
  6883. this._popcorn = null;
  6884. pullFromContainer( this );
  6885. this.duration = 0;
  6886. this.volume = 1;
  6887. this.currentTime = 0;
  6888. this.ended = 0;
  6889. this.paused = 1;
  6890. this.readyState = 0;
  6891. this.playbackRate = 1;
  6892. this.top = 0;
  6893. this.left = 0;
  6894. this.autoplay = null;
  6895. this.played = 0;
  6896. this.addEventListener( "load", function() {
  6897. var boundRect = this.getBoundingClientRect();
  6898. this.top = boundRect.top;
  6899. this.left = boundRect.left;
  6900. this.offsetWidth = this.swfObj.offsetWidth;
  6901. this.offsetHeight = this.swfObj.offsetHeight;
  6902. this.offsetLeft = this.swfObj.offsetLeft;
  6903. this.offsetTop = this.swfObj.offsetTop;
  6904. });
  6905. registry[ this._playerId ] = this;
  6906. isReady( this );
  6907. };
  6908. return ctor;
  6909. })();
  6910. Popcorn.soundcloud.init.prototype = Popcorn.soundcloud.prototype;
  6911. // Sequence object prototype
  6912. Popcorn.extend( Popcorn.soundcloud.prototype, {
  6913. // Set the volume as a value between 0 and 1
  6914. setVolume: function( val ) {
  6915. if ( !val && val !== 0 ) {
  6916. return;
  6917. }
  6918. // Normalize in case outside range of expected values of 0 .. 1
  6919. if ( val < 0 ) {
  6920. val = -val;
  6921. }
  6922. if ( val > 1 ) {
  6923. val %= 1;
  6924. }
  6925. // HTML video expects to be 0.0 -> 1.0, Flash object expects 0-100
  6926. this.volume = this.previousVolume = val;
  6927. this.swfObj.api_setVolume( val*100 );
  6928. this.dispatchEvent( "volumechange" );
  6929. },
  6930. // Seeks the video
  6931. setCurrentTime: function ( time ) {
  6932. if ( !time && time !== 0 ) {
  6933. return;
  6934. }
  6935. this.currentTime = this.previousCurrentTime = time;
  6936. this.ended = time >= this.duration;
  6937. // Fire events for seeking and time change
  6938. this.dispatchEvent( "seeked" );
  6939. },
  6940. // Play the video
  6941. play: function() {
  6942. // In case someone is cheeky enough to try this before loaded
  6943. if ( !this.swfObj ) {
  6944. this.addEventListener( "load", this.play );
  6945. return;
  6946. } else if ( !this.paused ) {
  6947. // No need to process if already playing
  6948. return;
  6949. }
  6950. this.paused = 0;
  6951. this.swfObj.api_play();
  6952. },
  6953. // Pause the video
  6954. pause: function() {
  6955. // In case someone is cheeky enough to try this before loaded
  6956. if ( !this.swfObj ) {
  6957. this.addEventListener( "load", this.pause );
  6958. return;
  6959. } else if ( this.paused ) {
  6960. // No need to process if already playing
  6961. return;
  6962. }
  6963. this.paused = 1;
  6964. this.swfObj.api_pause();
  6965. },
  6966. // Toggle video muting
  6967. // Unmuting will leave it at the old value
  6968. mute: function() {
  6969. // In case someone is cheeky enough to try this before loaded
  6970. if ( !this.swfObj ) {
  6971. this.addEventListener( "load", this.mute );
  6972. return;
  6973. }
  6974. if ( !this.muted() ) {
  6975. this.oldVol = this.volume;
  6976. if ( this.paused ) {
  6977. this.setVolume( 0 );
  6978. } else {
  6979. this.volume = 0;
  6980. }
  6981. } else {
  6982. if ( this.paused ) {
  6983. this.setVolume( this.oldVol );
  6984. } else {
  6985. this.volume = this.oldVol;
  6986. }
  6987. }
  6988. },
  6989. muted: function() {
  6990. return this.volume === 0;
  6991. },
  6992. // Force loading by playing the player. Pause afterwards
  6993. load: function() {
  6994. // In case someone is cheeky enough to try this before loaded
  6995. if ( !this.swfObj ) {
  6996. this.addEventListener( "load", this.load );
  6997. return;
  6998. }
  6999. this.play();
  7000. this.pause();
  7001. },
  7002. // Hook an event listener for the player event into internal event system
  7003. // Stick to HTML conventions of add event listener and keep lowercase, without prepending "on"
  7004. addEventListener: function( evt, fn ) {
  7005. if ( !this._listeners[evt] ) {
  7006. this._listeners[evt] = [];
  7007. }
  7008. this._listeners[evt].push( fn );
  7009. return fn;
  7010. },
  7011. dispatchEvent: function( evt ) {
  7012. var self = this,
  7013. evtName = evt.type || evt;
  7014. // Manually triggered a UI event, have it invoke rather than just the event handlers
  7015. if ( evtName === "play" && this.paused || evtName === "pause" && !this.paused ) {
  7016. this[evtName]();
  7017. return;
  7018. }
  7019. Popcorn.forEach( this._listeners[evtName], function( fn ) {
  7020. fn.call( self );
  7021. });
  7022. },
  7023. timeupdate: function() {
  7024. var self = this,
  7025. checkedVolume = this.swfObj.api_getVolume()/100,
  7026. seeked = 0;
  7027. // If has been changed through setting currentTime attribute
  7028. if ( abs( this.currentTime - this.previousCurrentTime ) > timeCheckInterval ) {
  7029. // Has programatically set the currentTime
  7030. this.swfObj.api_seekTo( this.currentTime );
  7031. seeked = 1;
  7032. } else {
  7033. this.previousCurrentTime = this.currentTime = this.swfObj.api_getTrackPosition();
  7034. }
  7035. // If has been changed throughh volume attribute
  7036. if ( checkedVolume !== this.previousVolume ) {
  7037. this.setVolume( checkedVolume );
  7038. } else if ( this.volume !== this.previousVolume ) {
  7039. this.setVolume( this.volume );
  7040. }
  7041. if ( !this.paused ) {
  7042. this.dispatchEvent( 'timeupdate' );
  7043. }
  7044. if( !self.ended ) {
  7045. setTimeout( function() {
  7046. self.timeupdate.call( self );
  7047. }, timeupdateInterval);
  7048. }
  7049. },
  7050. getBoundingClientRect: function() {
  7051. var b,
  7052. self = this;
  7053. if ( this.swfObj ) {
  7054. b = this.swfObj.getBoundingClientRect();
  7055. return {
  7056. bottom: b.bottom,
  7057. left: b.left,
  7058. right: b.right,
  7059. top: b.top,
  7060. // These not guaranteed to be in there
  7061. width: b.width || ( b.right - b.left ),
  7062. height: b.height || ( b.bottom - b.top )
  7063. };
  7064. } else {
  7065. //container = document.getElementById( this.playerId );
  7066. tmp = this._container.getBoundingClientRect();
  7067. // Update bottom, right for expected values once the container loads
  7068. return {
  7069. left: tmp.left,
  7070. top: tmp.top,
  7071. width: self.offsetWidth,
  7072. height: self.offsetHeight,
  7073. bottom: tmp.top + this.width,
  7074. right: tmp.top + this.height
  7075. };
  7076. }
  7077. },
  7078. registerPopcornWithPlayer: function( popcorn ) {
  7079. if ( !this.swfObj ) {
  7080. this.addEventListener( "load", function() {
  7081. this.registerPopcornWithPlayer( popcorn );
  7082. });
  7083. return;
  7084. }
  7085. this._popcorn = popcorn;
  7086. var api = this._options.api;
  7087. if ( api.key && api.commentdiv ) {
  7088. var self = this;
  7089. Popcorn.xhr({
  7090. url: "http://api.soundcloud.com/tracks/" + self._mediaId + "/comments.js?consumer_key=" + api.key,
  7091. success: function( data ) {
  7092. Popcorn.forEach( data.json, function ( obj ) {
  7093. self.addComment({
  7094. start: obj.timestamp/1000,
  7095. date: new Date( obj.created_at ),
  7096. text: obj.body,
  7097. user: {
  7098. name: obj.user.username,
  7099. profile: obj.user.permalink_url,
  7100. avatar: obj.user.avatar_url
  7101. }
  7102. });
  7103. });
  7104. }
  7105. });
  7106. }
  7107. }
  7108. });
  7109. Popcorn.extend( Popcorn.soundcloud.prototype, {
  7110. addComment: function( obj, displayFn ) {
  7111. var self = this,
  7112. comment = {
  7113. start: obj.start || 0,
  7114. date: obj.date || new Date(),
  7115. text: obj.text || "",
  7116. user: {
  7117. name: obj.user.name || "",
  7118. profile: obj.user.profile || "",
  7119. avatar: obj.user.avatar || ""
  7120. },
  7121. display: function() {
  7122. return ( displayFn || self._options.api.commentformat )( comment );
  7123. }
  7124. };
  7125. this._comments.push( comment );
  7126. if ( !this._popcorn ) {
  7127. return;
  7128. }
  7129. this._popcorn.subtitle({
  7130. start: comment.start,
  7131. target: this._options.api.commentdiv,
  7132. display: 'inline',
  7133. language: 'en',
  7134. text: comment.display()
  7135. });
  7136. }
  7137. });
  7138. })( Popcorn, window );(function() {
  7139. // global callback for vimeo.. yuck.
  7140. vimeo_player_loaded = function( playerId ) {
  7141. vimeo_player_loaded[ playerId ] && vimeo_player_loaded[ playerId ]();
  7142. };
  7143. vimeo_player_loaded.seek = {};
  7144. vimeo_player_loaded.loadProgress = {};
  7145. vimeo_player_loaded.play = {};
  7146. vimeo_player_loaded.pause = {};
  7147. Popcorn.player( "vimeo", {
  7148. _setup: function( options ) {
  7149. var media = this,
  7150. vimeoObject,
  7151. vimeoContainer = document.createElement( "div" ),
  7152. currentTime = 0,
  7153. seekTime = 0,
  7154. seeking = false,
  7155. volumeChanged = false,
  7156. lastMuted = false,
  7157. lastVolume = 0,
  7158. height,
  7159. width;
  7160. vimeoContainer.id = media.id + Popcorn.guid();
  7161. media.appendChild( vimeoContainer );
  7162. // setting vimeo player's height and width, default to 560 x 315
  7163. width = media.style.width ? ""+media.offsetWidth : "560";
  7164. height = media.style.height ? ""+media.offsetHeight : "315";
  7165. var vimeoInit = function() {
  7166. var flashvars,
  7167. params,
  7168. attributes = {},
  7169. src = media.src,
  7170. toggleMuteVolume = 0,
  7171. loadStarted = false;
  7172. vimeo_player_loaded[ vimeoContainer.id ] = function() {
  7173. vimeoObject = document.getElementById( vimeoContainer.id );
  7174. vimeo_player_loaded.seek[ vimeoContainer.id ] = function( time ) {
  7175. if( time !== currentTime ) {
  7176. currentTime = time;
  7177. media.dispatchEvent( "seeked" );
  7178. media.dispatchEvent( "timeupdate" );
  7179. }
  7180. };
  7181. vimeo_player_loaded.play[ vimeoContainer.id ] = function() {
  7182. if ( media.paused ) {
  7183. media.paused = false;
  7184. media.dispatchEvent( "play" );
  7185. media.dispatchEvent( "playing" );
  7186. timeUpdate();
  7187. }
  7188. };
  7189. vimeo_player_loaded.pause[ vimeoContainer.id ] = function() {
  7190. if ( !media.paused ) {
  7191. media.paused = true;
  7192. media.dispatchEvent( "pause" );
  7193. }
  7194. };
  7195. vimeo_player_loaded.loadProgress[ vimeoContainer.id ] = function( progress ) {
  7196. if ( !loadStarted ) {
  7197. loadStarted = true;
  7198. media.dispatchEvent( "loadstart" );
  7199. }
  7200. if ( progress.percent === 100 ) {
  7201. media.dispatchEvent( "canplaythrough" );
  7202. }
  7203. };
  7204. vimeoObject.api_addEventListener( "seek", "vimeo_player_loaded.seek." + vimeoContainer.id );
  7205. vimeoObject.api_addEventListener( "loadProgress", "vimeo_player_loaded.loadProgress." + vimeoContainer.id );
  7206. vimeoObject.api_addEventListener( "play", "vimeo_player_loaded.play." + vimeoContainer.id );
  7207. vimeoObject.api_addEventListener( "pause", "vimeo_player_loaded.pause." + vimeoContainer.id );
  7208. var timeUpdate = function() {
  7209. if ( !media.paused ) {
  7210. currentTime = vimeoObject.api_getCurrentTime();
  7211. media.dispatchEvent( "timeupdate" );
  7212. setTimeout( timeUpdate, 10 );
  7213. }
  7214. },
  7215. isMuted = function() {
  7216. return vimeoObject.api_getVolume() === 0;
  7217. };
  7218. var volumeUpdate = function() {
  7219. var muted = isMuted(),
  7220. vol = vimeoObject.api_getVolume();
  7221. if ( lastMuted !== muted ) {
  7222. lastMuted = muted;
  7223. media.dispatchEvent( "volumechange" );
  7224. }
  7225. if ( lastVolume !== vol ) {
  7226. lastVolume = vol;
  7227. media.dispatchEvent( "volumechange" );
  7228. }
  7229. setTimeout( volumeUpdate, 250 );
  7230. };
  7231. media.play = function() {
  7232. media.paused = false;
  7233. media.dispatchEvent( "play" );
  7234. media.dispatchEvent( "playing" );
  7235. timeUpdate();
  7236. vimeoObject.api_play();
  7237. };
  7238. media.pause = function() {
  7239. if ( !media.paused ) {
  7240. media.paused = true;
  7241. media.dispatchEvent( "pause" );
  7242. vimeoObject.api_pause();
  7243. }
  7244. };
  7245. Popcorn.player.defineProperty( media, "currentTime", {
  7246. set: function( val ) {
  7247. if ( !val ) {
  7248. return currentTime;
  7249. }
  7250. currentTime = seekTime = +val;
  7251. seeking = true;
  7252. media.dispatchEvent( "seeked" );
  7253. media.dispatchEvent( "timeupdate" );
  7254. vimeoObject.api_seekTo( currentTime );
  7255. return currentTime;
  7256. },
  7257. get: function() {
  7258. return currentTime;
  7259. }
  7260. });
  7261. Popcorn.player.defineProperty( media, "muted", {
  7262. set: function( val ) {
  7263. if ( isMuted() !== val ) {
  7264. if ( val ) {
  7265. toggleMuteVolume = vimeoObject.api_getVolume();
  7266. vimeoObject.api_setVolume( 0 );
  7267. } else {
  7268. vimeoObject.api_setVolume( toggleMuteVolume );
  7269. }
  7270. }
  7271. },
  7272. get: function() {
  7273. return isMuted();
  7274. }
  7275. });
  7276. Popcorn.player.defineProperty( media, "volume", {
  7277. set: function( val ) {
  7278. if ( !val || typeof val !== "number" || ( val < 0 || val > 1 ) ) {
  7279. return vimeoObject.api_getVolume() / 100;
  7280. }
  7281. if ( vimeoObject.api_getVolume() !== val ) {
  7282. vimeoObject.api_setVolume( val * 100 );
  7283. lastVolume = vimeoObject.api_getVolume();
  7284. media.dispatchEvent( "volumechange" );
  7285. }
  7286. return vimeoObject.api_getVolume() / 100;
  7287. },
  7288. get: function() {
  7289. return vimeoObject.api_getVolume() / 100;
  7290. }
  7291. });
  7292. media.readyState = 4;
  7293. media.dispatchEvent( "canplaythrough" );
  7294. media.dispatchEvent( "load" );
  7295. media.duration = vimeoObject.api_getDuration();
  7296. media.dispatchEvent( "durationchange" );
  7297. volumeUpdate();
  7298. media.dispatchEvent( "loadeddata" );
  7299. };
  7300. function extractId( videoUrl ) {
  7301. if ( !videoUrl ) {
  7302. return;
  7303. }
  7304. var rPlayerUri = /^http:\/\/player\.vimeo\.com\/video\/[\d]+/i,
  7305. rWebUrl = /vimeo\.com\/[\d]+/;
  7306. var matches = videoUrl.match( rPlayerUri ) ? videoUrl.match( rPlayerUri )[ 0 ].substr( 30 ) : "";
  7307. return matches ? matches : videoUrl.match( rWebUrl ) ? videoUrl.match( rWebUrl )[ 0 ].substr( 10 ) : "";
  7308. }
  7309. if ( !( src = extractId( src ) ) ) {
  7310. throw "Invalid Video Id";
  7311. }
  7312. flashvars = {
  7313. clip_id: src,
  7314. js_api: 1,
  7315. js_swf_id: vimeoContainer.id
  7316. };
  7317. // extend options from user to flashvars. NOTE: Videos owned by Plus Vimeo users may override these options
  7318. Popcorn.extend( flashvars, options );
  7319. params = {
  7320. allowscriptaccess: "always",
  7321. allowfullscreen: "true",
  7322. wmode: "transparent"
  7323. };
  7324. swfobject.embedSWF( "//vimeo.com/moogaloop.swf", vimeoContainer.id,
  7325. width, height, "9.0.0", "expressInstall.swf",
  7326. flashvars, params, attributes );
  7327. };
  7328. if ( !window.swfobject ) {
  7329. Popcorn.getScript( "//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", vimeoInit );
  7330. } else {
  7331. vimeoInit();
  7332. }
  7333. }
  7334. });
  7335. })();// A global callback for youtube... that makes me angry
  7336. var onYouTubePlayerReady = function( containerId ) {
  7337. onYouTubePlayerReady[ containerId ] && onYouTubePlayerReady[ containerId ]();
  7338. };
  7339. onYouTubePlayerReady.stateChangeEventHandler = {};
  7340. Popcorn.player( "youtube", {
  7341. _setup: function( options ) {
  7342. var media = this,
  7343. youtubeObject,
  7344. container = document.createElement( "div" ),
  7345. currentTime = 0,
  7346. seekTime = 0,
  7347. seeking = false,
  7348. // state code for volume changed polling
  7349. volumeChanged = false,
  7350. lastMuted = false,
  7351. lastVolume = 100;
  7352. container.id = media.id + Popcorn.guid();
  7353. media.appendChild( container );
  7354. var youtubeInit = function() {
  7355. var flashvars,
  7356. params,
  7357. attributes,
  7358. src,
  7359. width,
  7360. height,
  7361. query;
  7362. // expose a callback to this scope, that is called from the global callback youtube calls
  7363. onYouTubePlayerReady[ container.id ] = function() {
  7364. youtubeObject = document.getElementById( container.id );
  7365. // more youtube callback nonsense
  7366. onYouTubePlayerReady.stateChangeEventHandler[ container.id ] = function( state ) {
  7367. // playing is state 1
  7368. // paused is state 2
  7369. if ( state === 1 ) {
  7370. media.paused && media.play();
  7371. // youtube fires paused events while seeking
  7372. // this is the only way to get seeking events
  7373. } else if ( state === 2 ) {
  7374. // silly logic forced on me by the youtube API
  7375. // calling youtube.seekTo triggers multiple events
  7376. // with the second events getCurrentTime being the old time
  7377. if ( seeking && seekTime === currentTime && seekTime !== youtubeObject.getCurrentTime() ) {
  7378. seeking = false;
  7379. youtubeObject.seekTo( currentTime );
  7380. return;
  7381. }
  7382. currentTime = youtubeObject.getCurrentTime();
  7383. media.dispatchEvent( "timeupdate" );
  7384. !media.paused && media.pause();
  7385. }
  7386. };
  7387. // youtube requires callbacks to be a string to a function path from the global scope
  7388. youtubeObject.addEventListener( "onStateChange", "onYouTubePlayerReady.stateChangeEventHandler." + container.id );
  7389. var timeupdate = function() {
  7390. if ( !media.paused ) {
  7391. currentTime = youtubeObject.getCurrentTime();
  7392. media.dispatchEvent( "timeupdate" );
  7393. setTimeout( timeupdate, 10 );
  7394. }
  7395. };
  7396. var volumeupdate = function() {
  7397. if ( lastMuted !== youtubeObject.isMuted() ) {
  7398. lastMuted = youtubeObject.isMuted();
  7399. media.dispatchEvent( "volumechange" );
  7400. }
  7401. if ( lastVolume !== youtubeObject.getVolume() ) {
  7402. lastVolume = youtubeObject.getVolume();
  7403. media.dispatchEvent( "volumechange" );
  7404. }
  7405. setTimeout( volumeupdate, 250 );
  7406. };
  7407. media.play = function() {
  7408. media.paused = false;
  7409. media.dispatchEvent( "play" );
  7410. media.dispatchEvent( "playing" );
  7411. timeupdate();
  7412. youtubeObject.playVideo();
  7413. };
  7414. media.pause = function() {
  7415. if ( !media.paused ) {
  7416. media.paused = true;
  7417. media.dispatchEvent( "pause" );
  7418. youtubeObject.pauseVideo();
  7419. }
  7420. };
  7421. Popcorn.player.defineProperty( media, "currentTime", {
  7422. set: function( val ) {
  7423. // make sure val is a number
  7424. currentTime = seekTime = +val;
  7425. seeking = true;
  7426. media.dispatchEvent( "seeked" );
  7427. media.dispatchEvent( "timeupdate" );
  7428. youtubeObject.seekTo( currentTime );
  7429. return currentTime;
  7430. },
  7431. get: function() {
  7432. return currentTime;
  7433. }
  7434. });
  7435. Popcorn.player.defineProperty( media, "muted", {
  7436. set: function( val ) {
  7437. if ( youtubeObject.isMuted() !== val ) {
  7438. if ( val ) {
  7439. youtubeObject.mute();
  7440. } else {
  7441. youtubeObject.unMute();
  7442. }
  7443. lastMuted = youtubeObject.isMuted();
  7444. media.dispatchEvent( "volumechange" );
  7445. }
  7446. return youtubeObject.isMuted();
  7447. },
  7448. get: function() {
  7449. return youtubeObject.isMuted();
  7450. }
  7451. });
  7452. Popcorn.player.defineProperty( media, "volume", {
  7453. set: function( val ) {
  7454. if ( youtubeObject.getVolume() / 100 !== val ) {
  7455. youtubeObject.setVolume( val * 100 );
  7456. lastVolume = youtubeObject.getVolume();
  7457. media.dispatchEvent( "volumechange" );
  7458. }
  7459. return youtubeObject.getVolume() / 100;
  7460. },
  7461. get: function() {
  7462. return youtubeObject.getVolume() / 100;
  7463. }
  7464. });
  7465. media.readyState = 4;
  7466. media.dispatchEvent( "canplaythrough" );
  7467. media.dispatchEvent( "load" );
  7468. media.duration = youtubeObject.getDuration();
  7469. media.dispatchEvent( "durationchange" );
  7470. volumeupdate();
  7471. media.dispatchEvent( "loadeddata" );
  7472. };
  7473. options.controls = +options.controls === 0 || +options.controls === 1 ? options.controls : 1;
  7474. options.annotations = +options.annotations === 1 || +options.annotations === 3 ? options.annotations : 1;
  7475. flashvars = {
  7476. playerapiid: container.id
  7477. };
  7478. params = {
  7479. wmode: "transparent",
  7480. allowScriptAccess: "always"
  7481. };
  7482. attributes = {
  7483. id: container.id
  7484. };
  7485. src = /^.*(?:\/|v=)(.{11})/.exec( media.src )[ 1 ];
  7486. query = ( media.src.split( "?" )[ 1 ] || "" ).replace( /v=.{11}/, "" );
  7487. // setting youtube player's height and width, default to 560 x 315
  7488. width = media.style.width ? ""+media.offsetWidth : "560";
  7489. height = media.style.height ? ""+media.offsetHeight : "315";
  7490. swfobject.embedSWF( "//www.youtube.com/e/" + src + "?" + query + "&enablejsapi=1&playerapiid=" + container.id + "&version=3",
  7491. container.id, width, height, "8", null, flashvars, params, attributes );
  7492. };
  7493. if ( !window.swfobject ) {
  7494. Popcorn.getScript( "//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", youtubeInit );
  7495. } else {
  7496. youtubeInit();
  7497. }
  7498. }
  7499. });
  7500. // EFFECT: applyclass
  7501. (function (Popcorn) {
  7502. /**
  7503. * apply css class to jquery selector
  7504. * selector is relative to plugin target's id
  7505. * so .overlay is actually jQuery( "#target .overlay")
  7506. *
  7507. * @param {Object} options
  7508. *
  7509. * Example:
  7510. var p = Popcorn('#video')
  7511. .footnote({
  7512. start: 5, // seconds
  7513. end: 15, // seconds
  7514. text: 'This video made exclusively for drumbeat.org',
  7515. target: 'footnotediv',
  7516. effect: 'applyclass',
  7517. applyclass: 'selector: class'
  7518. })
  7519. *
  7520. */
  7521. var toggleClass = function( event, options ) {
  7522. var idx = 0, len = 0, elements;
  7523. Popcorn.forEach( options.classes, function( key, val ) {
  7524. elements = [];
  7525. if ( key === "parent" ) {
  7526. elements[ 0 ] = document.querySelectorAll("#" + options.target )[ 0 ].parentNode;
  7527. } else {
  7528. elements = document.querySelectorAll("#" + options.target + " " + key );
  7529. }
  7530. for ( idx = 0, len = elements.length; idx < len; idx++ ) {
  7531. elements[ idx ].classList.toggle( val );
  7532. }
  7533. });
  7534. };
  7535. Popcorn.compose( "applyclass", {
  7536. manifest: {
  7537. about: {
  7538. name: "Popcorn applyclass Effect",
  7539. version: "0.1",
  7540. author: "@scottdowne",
  7541. website: "scottdowne.wordpress.com"
  7542. },
  7543. options: {}
  7544. },
  7545. _setup: function( options ) {
  7546. options.classes = {};
  7547. options.applyclass = options.applyclass || "";
  7548. var classes = options.applyclass.replace( /\s/g, "" ).split( "," ),
  7549. item = [],
  7550. idx = 0, len = classes.length;
  7551. for ( ; idx < len; idx++ ) {
  7552. item = classes[ idx ].split( ":" );
  7553. if ( item[ 0 ] ) {
  7554. options.classes[ item[ 0 ] ] = item[ 1 ] || "";
  7555. }
  7556. }
  7557. },
  7558. start: toggleClass,
  7559. end: toggleClass
  7560. });
  7561. })( Popcorn );