PageRenderTime 160ms CodeModel.GetById 44ms RepoModel.GetById 1ms app.codeStats 2ms

/external/layouts/bookreport/js/popcorn-complete.js

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