|
1 /* |
|
2 * popcorn.js version 1.3 |
|
3 * http://popcornjs.org |
|
4 * |
|
5 * Copyright 2011, Mozilla Foundation |
|
6 * Licensed under the MIT license |
|
7 */ |
|
8 |
|
9 (function(global, document) { |
|
10 |
|
11 // Popcorn.js does not support archaic browsers |
|
12 if ( !document.addEventListener ) { |
|
13 global.Popcorn = { |
|
14 isSupported: false |
|
15 }; |
|
16 |
|
17 var methods = ( "byId forEach extend effects error guid sizeOf isArray nop position disable enable destroy" + |
|
18 "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " + |
|
19 "timeUpdate plugin removePlugin compose effect xhr getJSONP getScript" ).split(/\s+/); |
|
20 |
|
21 while ( methods.length ) { |
|
22 global.Popcorn[ methods.shift() ] = function() {}; |
|
23 } |
|
24 return; |
|
25 } |
|
26 |
|
27 var |
|
28 |
|
29 AP = Array.prototype, |
|
30 OP = Object.prototype, |
|
31 |
|
32 forEach = AP.forEach, |
|
33 slice = AP.slice, |
|
34 hasOwn = OP.hasOwnProperty, |
|
35 toString = OP.toString, |
|
36 |
|
37 // Copy global Popcorn (may not exist) |
|
38 _Popcorn = global.Popcorn, |
|
39 |
|
40 // Ready fn cache |
|
41 readyStack = [], |
|
42 readyBound = false, |
|
43 readyFired = false, |
|
44 |
|
45 // Non-public internal data object |
|
46 internal = { |
|
47 events: { |
|
48 hash: {}, |
|
49 apis: {} |
|
50 } |
|
51 }, |
|
52 |
|
53 // Non-public `requestAnimFrame` |
|
54 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ |
|
55 requestAnimFrame = (function(){ |
|
56 return global.requestAnimationFrame || |
|
57 global.webkitRequestAnimationFrame || |
|
58 global.mozRequestAnimationFrame || |
|
59 global.oRequestAnimationFrame || |
|
60 global.msRequestAnimationFrame || |
|
61 function( callback, element ) { |
|
62 global.setTimeout( callback, 16 ); |
|
63 }; |
|
64 }()), |
|
65 |
|
66 // Non-public `getKeys`, return an object's keys as an array |
|
67 getKeys = function( obj ) { |
|
68 return Object.keys ? Object.keys( obj ) : (function( obj ) { |
|
69 var item, |
|
70 list = []; |
|
71 |
|
72 for ( item in obj ) { |
|
73 if ( hasOwn.call( obj, item ) ) { |
|
74 list.push( item ); |
|
75 } |
|
76 } |
|
77 return list; |
|
78 })( obj ); |
|
79 }, |
|
80 |
|
81 // Declare constructor |
|
82 // Returns an instance object. |
|
83 Popcorn = function( entity, options ) { |
|
84 // Return new Popcorn object |
|
85 return new Popcorn.p.init( entity, options || null ); |
|
86 }; |
|
87 |
|
88 // Popcorn API version, automatically inserted via build system. |
|
89 Popcorn.version = "1.3"; |
|
90 |
|
91 // Boolean flag allowing a client to determine if Popcorn can be supported |
|
92 Popcorn.isSupported = true; |
|
93 |
|
94 // Instance caching |
|
95 Popcorn.instances = []; |
|
96 |
|
97 // Declare a shortcut (Popcorn.p) to and a definition of |
|
98 // the new prototype for our Popcorn constructor |
|
99 Popcorn.p = Popcorn.prototype = { |
|
100 |
|
101 init: function( entity, options ) { |
|
102 |
|
103 var matches, nodeName, |
|
104 self = this; |
|
105 |
|
106 // Supports Popcorn(function () { /../ }) |
|
107 // Originally proposed by Daniel Brooks |
|
108 |
|
109 if ( typeof entity === "function" ) { |
|
110 |
|
111 // If document ready has already fired |
|
112 if ( document.readyState === "complete" ) { |
|
113 |
|
114 entity( document, Popcorn ); |
|
115 |
|
116 return; |
|
117 } |
|
118 // Add `entity` fn to ready stack |
|
119 readyStack.push( entity ); |
|
120 |
|
121 // This process should happen once per page load |
|
122 if ( !readyBound ) { |
|
123 |
|
124 // set readyBound flag |
|
125 readyBound = true; |
|
126 |
|
127 var DOMContentLoaded = function() { |
|
128 |
|
129 readyFired = true; |
|
130 |
|
131 // Remove global DOM ready listener |
|
132 document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); |
|
133 |
|
134 // Execute all ready function in the stack |
|
135 for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) { |
|
136 |
|
137 readyStack[ i ].call( document, Popcorn ); |
|
138 |
|
139 } |
|
140 // GC readyStack |
|
141 readyStack = null; |
|
142 }; |
|
143 |
|
144 // Register global DOM ready listener |
|
145 document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); |
|
146 } |
|
147 |
|
148 return; |
|
149 } |
|
150 |
|
151 if ( typeof entity === "string" ) { |
|
152 try { |
|
153 matches = document.querySelector( entity ); |
|
154 } catch( e ) { |
|
155 throw new Error( "Popcorn.js Error: Invalid media element selector: " + entity ); |
|
156 } |
|
157 } |
|
158 |
|
159 // Get media element by id or object reference |
|
160 this.media = matches || entity; |
|
161 |
|
162 // inner reference to this media element's nodeName string value |
|
163 nodeName = ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video"; |
|
164 |
|
165 // Create an audio or video element property reference |
|
166 this[ nodeName ] = this.media; |
|
167 |
|
168 this.options = options || {}; |
|
169 |
|
170 // Resolve custom ID or default prefixed ID |
|
171 this.id = this.options.id || Popcorn.guid( nodeName ); |
|
172 |
|
173 // Throw if an attempt is made to use an ID that already exists |
|
174 if ( Popcorn.byId( this.id ) ) { |
|
175 throw new Error( "Popcorn.js Error: Cannot use duplicate ID (" + this.id + ")" ); |
|
176 } |
|
177 |
|
178 this.isDestroyed = false; |
|
179 |
|
180 this.data = { |
|
181 |
|
182 // data structure of all |
|
183 running: { |
|
184 cue: [] |
|
185 }, |
|
186 |
|
187 // Executed by either timeupdate event or in rAF loop |
|
188 timeUpdate: Popcorn.nop, |
|
189 |
|
190 // Allows disabling a plugin per instance |
|
191 disabled: {}, |
|
192 |
|
193 // Stores DOM event queues by type |
|
194 events: {}, |
|
195 |
|
196 // Stores Special event hooks data |
|
197 hooks: {}, |
|
198 |
|
199 // Store track event history data |
|
200 history: [], |
|
201 |
|
202 // Stores ad-hoc state related data] |
|
203 state: { |
|
204 volume: this.media.volume |
|
205 }, |
|
206 |
|
207 // Store track event object references by trackId |
|
208 trackRefs: {}, |
|
209 |
|
210 // Playback track event queues |
|
211 trackEvents: { |
|
212 byStart: [{ |
|
213 |
|
214 start: -1, |
|
215 end: -1 |
|
216 }], |
|
217 byEnd: [{ |
|
218 start: -1, |
|
219 end: -1 |
|
220 }], |
|
221 animating: [], |
|
222 startIndex: 0, |
|
223 endIndex: 0, |
|
224 previousUpdateTime: -1 |
|
225 } |
|
226 }; |
|
227 |
|
228 // Register new instance |
|
229 Popcorn.instances.push( this ); |
|
230 |
|
231 // function to fire when video is ready |
|
232 var isReady = function() { |
|
233 |
|
234 // chrome bug: http://code.google.com/p/chromium/issues/detail?id=119598 |
|
235 // it is possible the video's time is less than 0 |
|
236 // this has the potential to call track events more than once, when they should not |
|
237 // start: 0, end: 1 will start, end, start again, when it should just start |
|
238 // just setting it to 0 if it is below 0 fixes this issue |
|
239 if ( self.media.currentTime < 0 ) { |
|
240 |
|
241 self.media.currentTime = 0; |
|
242 } |
|
243 |
|
244 self.media.removeEventListener( "loadeddata", isReady, false ); |
|
245 |
|
246 var duration, videoDurationPlus, |
|
247 runningPlugins, runningPlugin, rpLength, rpNatives; |
|
248 |
|
249 // Adding padding to the front and end of the arrays |
|
250 // this is so we do not fall off either end |
|
251 duration = self.media.duration; |
|
252 |
|
253 // Check for no duration info (NaN) |
|
254 videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1; |
|
255 |
|
256 Popcorn.addTrackEvent( self, { |
|
257 start: videoDurationPlus, |
|
258 end: videoDurationPlus |
|
259 }); |
|
260 |
|
261 if ( self.options.frameAnimation ) { |
|
262 |
|
263 // if Popcorn is created with frameAnimation option set to true, |
|
264 // requestAnimFrame is used instead of "timeupdate" media event. |
|
265 // This is for greater frame time accuracy, theoretically up to |
|
266 // 60 frames per second as opposed to ~4 ( ~every 15-250ms) |
|
267 self.data.timeUpdate = function () { |
|
268 |
|
269 Popcorn.timeUpdate( self, {} ); |
|
270 |
|
271 // fire frame for each enabled active plugin of every type |
|
272 Popcorn.forEach( Popcorn.manifest, function( key, val ) { |
|
273 |
|
274 runningPlugins = self.data.running[ val ]; |
|
275 |
|
276 // ensure there are running plugins on this type on this instance |
|
277 if ( runningPlugins ) { |
|
278 |
|
279 rpLength = runningPlugins.length; |
|
280 for ( var i = 0; i < rpLength; i++ ) { |
|
281 |
|
282 runningPlugin = runningPlugins[ i ]; |
|
283 rpNatives = runningPlugin._natives; |
|
284 rpNatives && rpNatives.frame && |
|
285 rpNatives.frame.call( self, {}, runningPlugin, self.currentTime() ); |
|
286 } |
|
287 } |
|
288 }); |
|
289 |
|
290 self.emit( "timeupdate" ); |
|
291 |
|
292 !self.isDestroyed && requestAnimFrame( self.data.timeUpdate ); |
|
293 }; |
|
294 |
|
295 !self.isDestroyed && requestAnimFrame( self.data.timeUpdate ); |
|
296 |
|
297 } else { |
|
298 |
|
299 self.data.timeUpdate = function( event ) { |
|
300 Popcorn.timeUpdate( self, event ); |
|
301 }; |
|
302 |
|
303 if ( !self.isDestroyed ) { |
|
304 self.media.addEventListener( "timeupdate", self.data.timeUpdate, false ); |
|
305 } |
|
306 } |
|
307 }; |
|
308 |
|
309 Object.defineProperty( this, "error", { |
|
310 get: function() { |
|
311 |
|
312 return self.media.error; |
|
313 } |
|
314 }); |
|
315 |
|
316 if ( self.media.readyState >= 2 ) { |
|
317 |
|
318 isReady(); |
|
319 } else { |
|
320 |
|
321 self.media.addEventListener( "loadeddata", isReady, false ); |
|
322 } |
|
323 |
|
324 return this; |
|
325 } |
|
326 }; |
|
327 |
|
328 // Extend constructor prototype to instance prototype |
|
329 // Allows chaining methods to instances |
|
330 Popcorn.p.init.prototype = Popcorn.p; |
|
331 |
|
332 Popcorn.byId = function( str ) { |
|
333 var instances = Popcorn.instances, |
|
334 length = instances.length, |
|
335 i = 0; |
|
336 |
|
337 for ( ; i < length; i++ ) { |
|
338 if ( instances[ i ].id === str ) { |
|
339 return instances[ i ]; |
|
340 } |
|
341 } |
|
342 |
|
343 return null; |
|
344 }; |
|
345 |
|
346 Popcorn.forEach = function( obj, fn, context ) { |
|
347 |
|
348 if ( !obj || !fn ) { |
|
349 return {}; |
|
350 } |
|
351 |
|
352 context = context || this; |
|
353 |
|
354 var key, len; |
|
355 |
|
356 // Use native whenever possible |
|
357 if ( forEach && obj.forEach === forEach ) { |
|
358 return obj.forEach( fn, context ); |
|
359 } |
|
360 |
|
361 if ( toString.call( obj ) === "[object NodeList]" ) { |
|
362 for ( key = 0, len = obj.length; key < len; key++ ) { |
|
363 fn.call( context, obj[ key ], key, obj ); |
|
364 } |
|
365 return obj; |
|
366 } |
|
367 |
|
368 for ( key in obj ) { |
|
369 if ( hasOwn.call( obj, key ) ) { |
|
370 fn.call( context, obj[ key ], key, obj ); |
|
371 } |
|
372 } |
|
373 return obj; |
|
374 }; |
|
375 |
|
376 Popcorn.extend = function( obj ) { |
|
377 var dest = obj, src = slice.call( arguments, 1 ); |
|
378 |
|
379 Popcorn.forEach( src, function( copy ) { |
|
380 for ( var prop in copy ) { |
|
381 dest[ prop ] = copy[ prop ]; |
|
382 } |
|
383 }); |
|
384 |
|
385 return dest; |
|
386 }; |
|
387 |
|
388 |
|
389 // A Few reusable utils, memoized onto Popcorn |
|
390 Popcorn.extend( Popcorn, { |
|
391 noConflict: function( deep ) { |
|
392 |
|
393 if ( deep ) { |
|
394 global.Popcorn = _Popcorn; |
|
395 } |
|
396 |
|
397 return Popcorn; |
|
398 }, |
|
399 error: function( msg ) { |
|
400 throw new Error( msg ); |
|
401 }, |
|
402 guid: function( prefix ) { |
|
403 Popcorn.guid.counter++; |
|
404 return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter ); |
|
405 }, |
|
406 sizeOf: function( obj ) { |
|
407 var size = 0; |
|
408 |
|
409 for ( var prop in obj ) { |
|
410 size++; |
|
411 } |
|
412 |
|
413 return size; |
|
414 }, |
|
415 isArray: Array.isArray || function( array ) { |
|
416 return toString.call( array ) === "[object Array]"; |
|
417 }, |
|
418 |
|
419 nop: function() {}, |
|
420 |
|
421 position: function( elem ) { |
|
422 |
|
423 var clientRect = elem.getBoundingClientRect(), |
|
424 bounds = {}, |
|
425 doc = elem.ownerDocument, |
|
426 docElem = document.documentElement, |
|
427 body = document.body, |
|
428 clientTop, clientLeft, scrollTop, scrollLeft, top, left; |
|
429 |
|
430 // Determine correct clientTop/Left |
|
431 clientTop = docElem.clientTop || body.clientTop || 0; |
|
432 clientLeft = docElem.clientLeft || body.clientLeft || 0; |
|
433 |
|
434 // Determine correct scrollTop/Left |
|
435 scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop ); |
|
436 scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft ); |
|
437 |
|
438 // Temp top/left |
|
439 top = Math.ceil( clientRect.top + scrollTop - clientTop ); |
|
440 left = Math.ceil( clientRect.left + scrollLeft - clientLeft ); |
|
441 |
|
442 for ( var p in clientRect ) { |
|
443 bounds[ p ] = Math.round( clientRect[ p ] ); |
|
444 } |
|
445 |
|
446 return Popcorn.extend({}, bounds, { top: top, left: left }); |
|
447 }, |
|
448 |
|
449 disable: function( instance, plugin ) { |
|
450 |
|
451 if ( !instance.data.disabled[ plugin ] ) { |
|
452 |
|
453 instance.data.disabled[ plugin ] = true; |
|
454 |
|
455 for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) { |
|
456 |
|
457 event = instance.data.running[ plugin ][ i ]; |
|
458 event._natives.end.call( instance, null, event ); |
|
459 } |
|
460 } |
|
461 |
|
462 return instance; |
|
463 }, |
|
464 enable: function( instance, plugin ) { |
|
465 |
|
466 if ( instance.data.disabled[ plugin ] ) { |
|
467 |
|
468 instance.data.disabled[ plugin ] = false; |
|
469 |
|
470 for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) { |
|
471 |
|
472 event = instance.data.running[ plugin ][ i ]; |
|
473 event._natives.start.call( instance, null, event ); |
|
474 } |
|
475 } |
|
476 |
|
477 return instance; |
|
478 }, |
|
479 destroy: function( instance ) { |
|
480 var events = instance.data.events, |
|
481 trackEvents = instance.data.trackEvents, |
|
482 singleEvent, item, fn, plugin; |
|
483 |
|
484 // Iterate through all events and remove them |
|
485 for ( item in events ) { |
|
486 singleEvent = events[ item ]; |
|
487 for ( fn in singleEvent ) { |
|
488 delete singleEvent[ fn ]; |
|
489 } |
|
490 events[ item ] = null; |
|
491 } |
|
492 |
|
493 // remove all plugins off the given instance |
|
494 for ( plugin in Popcorn.registryByName ) { |
|
495 Popcorn.removePlugin( instance, plugin ); |
|
496 } |
|
497 |
|
498 // Remove all data.trackEvents #1178 |
|
499 trackEvents.byStart.length = 0; |
|
500 trackEvents.byEnd.length = 0; |
|
501 |
|
502 if ( !instance.isDestroyed ) { |
|
503 instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false ); |
|
504 instance.isDestroyed = true; |
|
505 } |
|
506 } |
|
507 }); |
|
508 |
|
509 // Memoized GUID Counter |
|
510 Popcorn.guid.counter = 1; |
|
511 |
|
512 // Factory to implement getters, setters and controllers |
|
513 // as Popcorn instance methods. The IIFE will create and return |
|
514 // an object with defined methods |
|
515 Popcorn.extend(Popcorn.p, (function() { |
|
516 |
|
517 var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " + |
|
518 "autoplay loop controls muted buffered readyState seeking paused played seekable ended", |
|
519 ret = {}; |
|
520 |
|
521 |
|
522 // Build methods, store in object that is returned and passed to extend |
|
523 Popcorn.forEach( methods.split( /\s+/g ), function( name ) { |
|
524 |
|
525 ret[ name ] = function( arg ) { |
|
526 var previous; |
|
527 |
|
528 if ( typeof this.media[ name ] === "function" ) { |
|
529 |
|
530 // Support for shorthanded play(n)/pause(n) jump to currentTime |
|
531 // If arg is not null or undefined and called by one of the |
|
532 // allowed shorthandable methods, then set the currentTime |
|
533 // Supports time as seconds or SMPTE |
|
534 if ( arg != null && /play|pause/.test( name ) ) { |
|
535 this.media.currentTime = Popcorn.util.toSeconds( arg ); |
|
536 } |
|
537 |
|
538 this.media[ name ](); |
|
539 |
|
540 return this; |
|
541 } |
|
542 |
|
543 if ( arg != null ) { |
|
544 // Capture the current value of the attribute property |
|
545 previous = this.media[ name ]; |
|
546 |
|
547 // Set the attribute property with the new value |
|
548 this.media[ name ] = arg; |
|
549 |
|
550 // If the new value is not the same as the old value |
|
551 // emit an "attrchanged event" |
|
552 if ( previous !== arg ) { |
|
553 this.emit( "attrchange", { |
|
554 attribute: name, |
|
555 previousValue: previous, |
|
556 currentValue: arg |
|
557 }); |
|
558 } |
|
559 return this; |
|
560 } |
|
561 |
|
562 return this.media[ name ]; |
|
563 }; |
|
564 }); |
|
565 |
|
566 return ret; |
|
567 |
|
568 })() |
|
569 ); |
|
570 |
|
571 Popcorn.forEach( "enable disable".split(" "), function( method ) { |
|
572 Popcorn.p[ method ] = function( plugin ) { |
|
573 return Popcorn[ method ]( this, plugin ); |
|
574 }; |
|
575 }); |
|
576 |
|
577 Popcorn.extend(Popcorn.p, { |
|
578 |
|
579 // Rounded currentTime |
|
580 roundTime: function() { |
|
581 return Math.round( this.media.currentTime ); |
|
582 }, |
|
583 |
|
584 // Attach an event to a single point in time |
|
585 exec: function( id, time, fn ) { |
|
586 var length = arguments.length, |
|
587 trackEvent, sec; |
|
588 |
|
589 // Check if first could possibly be a SMPTE string |
|
590 // p.cue( "smpte string", fn ); |
|
591 // try/catch avoid awful throw in Popcorn.util.toSeconds |
|
592 // TODO: Get rid of that, replace with NaN return? |
|
593 try { |
|
594 sec = Popcorn.util.toSeconds( id ); |
|
595 } catch ( e ) {} |
|
596 |
|
597 // If it can be converted into a number then |
|
598 // it's safe to assume that the string was SMPTE |
|
599 if ( typeof sec === "number" ) { |
|
600 id = sec; |
|
601 } |
|
602 |
|
603 // Shift arguments based on use case |
|
604 // |
|
605 // Back compat for: |
|
606 // p.cue( time, fn ); |
|
607 if ( typeof id === "number" && length === 2 ) { |
|
608 fn = time; |
|
609 time = id; |
|
610 id = Popcorn.guid( "cue" ); |
|
611 } else { |
|
612 // Support for new forms |
|
613 |
|
614 // p.cue( "empty-cue" ); |
|
615 if ( length === 1 ) { |
|
616 // Set a time for an empty cue. It's not important what |
|
617 // the time actually is, because the cue is a no-op |
|
618 time = -1; |
|
619 |
|
620 } else { |
|
621 |
|
622 // Get the trackEvent that matches the given id. |
|
623 trackEvent = this.getTrackEvent( id ); |
|
624 |
|
625 if ( trackEvent ) { |
|
626 |
|
627 // p.cue( "my-id", 12 ); |
|
628 // p.cue( "my-id", function() { ... }); |
|
629 if ( typeof id === "string" && length === 2 ) { |
|
630 |
|
631 // p.cue( "my-id", 12 ); |
|
632 // The path will update the cue time. |
|
633 if ( typeof time === "number" ) { |
|
634 // Re-use existing trackEvent start callback |
|
635 fn = trackEvent._natives.start; |
|
636 } |
|
637 |
|
638 // p.cue( "my-id", function() { ... }); |
|
639 // The path will update the cue function |
|
640 if ( typeof time === "function" ) { |
|
641 fn = time; |
|
642 // Re-use existing trackEvent start time |
|
643 time = trackEvent.start; |
|
644 } |
|
645 } |
|
646 } else { |
|
647 |
|
648 if ( length >= 2 ) { |
|
649 |
|
650 // p.cue( "a", "00:00:00"); |
|
651 if ( typeof time === "string" ) { |
|
652 try { |
|
653 sec = Popcorn.util.toSeconds( time ); |
|
654 } catch ( e ) {} |
|
655 |
|
656 time = sec; |
|
657 } |
|
658 |
|
659 // p.cue( "b", 11 ); |
|
660 if ( typeof time === "number" ) { |
|
661 fn = Popcorn.nop(); |
|
662 } |
|
663 |
|
664 // p.cue( "c", function() {}); |
|
665 if ( typeof time === "function" ) { |
|
666 fn = time; |
|
667 time = -1; |
|
668 } |
|
669 } |
|
670 } |
|
671 } |
|
672 } |
|
673 |
|
674 // Creating a one second track event with an empty end |
|
675 // Or update an existing track event with new values |
|
676 Popcorn.addTrackEvent( this, { |
|
677 id: id, |
|
678 start: time, |
|
679 end: time + 1, |
|
680 _running: false, |
|
681 _natives: { |
|
682 start: fn || Popcorn.nop, |
|
683 end: Popcorn.nop, |
|
684 type: "cue" |
|
685 } |
|
686 }); |
|
687 |
|
688 return this; |
|
689 }, |
|
690 |
|
691 // Mute the calling media, optionally toggle |
|
692 mute: function( toggle ) { |
|
693 |
|
694 var event = toggle == null || toggle === true ? "muted" : "unmuted"; |
|
695 |
|
696 // If `toggle` is explicitly `false`, |
|
697 // unmute the media and restore the volume level |
|
698 if ( event === "unmuted" ) { |
|
699 this.media.muted = false; |
|
700 this.media.volume = this.data.state.volume; |
|
701 } |
|
702 |
|
703 // If `toggle` is either null or undefined, |
|
704 // save the current volume and mute the media element |
|
705 if ( event === "muted" ) { |
|
706 this.data.state.volume = this.media.volume; |
|
707 this.media.muted = true; |
|
708 } |
|
709 |
|
710 // Trigger either muted|unmuted event |
|
711 this.emit( event ); |
|
712 |
|
713 return this; |
|
714 }, |
|
715 |
|
716 // Convenience method, unmute the calling media |
|
717 unmute: function( toggle ) { |
|
718 |
|
719 return this.mute( toggle == null ? false : !toggle ); |
|
720 }, |
|
721 |
|
722 // Get the client bounding box of an instance element |
|
723 position: function() { |
|
724 return Popcorn.position( this.media ); |
|
725 }, |
|
726 |
|
727 // Toggle a plugin's playback behaviour (on or off) per instance |
|
728 toggle: function( plugin ) { |
|
729 return Popcorn[ this.data.disabled[ plugin ] ? "enable" : "disable" ]( this, plugin ); |
|
730 }, |
|
731 |
|
732 // Set default values for plugin options objects per instance |
|
733 defaults: function( plugin, defaults ) { |
|
734 |
|
735 // If an array of default configurations is provided, |
|
736 // iterate and apply each to this instance |
|
737 if ( Popcorn.isArray( plugin ) ) { |
|
738 |
|
739 Popcorn.forEach( plugin, function( obj ) { |
|
740 for ( var name in obj ) { |
|
741 this.defaults( name, obj[ name ] ); |
|
742 } |
|
743 }, this ); |
|
744 |
|
745 return this; |
|
746 } |
|
747 |
|
748 if ( !this.options.defaults ) { |
|
749 this.options.defaults = {}; |
|
750 } |
|
751 |
|
752 if ( !this.options.defaults[ plugin ] ) { |
|
753 this.options.defaults[ plugin ] = {}; |
|
754 } |
|
755 |
|
756 Popcorn.extend( this.options.defaults[ plugin ], defaults ); |
|
757 |
|
758 return this; |
|
759 } |
|
760 }); |
|
761 |
|
762 Popcorn.Events = { |
|
763 UIEvents: "blur focus focusin focusout load resize scroll unload", |
|
764 MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick", |
|
765 Events: "loadstart progress suspend emptied stalled play pause error " + |
|
766 "loadedmetadata loadeddata waiting playing canplay canplaythrough " + |
|
767 "seeking seeked timeupdate ended ratechange durationchange volumechange" |
|
768 }; |
|
769 |
|
770 Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " + |
|
771 Popcorn.Events.MouseEvents + " " + |
|
772 Popcorn.Events.Events; |
|
773 |
|
774 internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ]; |
|
775 |
|
776 // Privately compile events table at load time |
|
777 (function( events, data ) { |
|
778 |
|
779 var apis = internal.events.apiTypes, |
|
780 eventsList = events.Natives.split( /\s+/g ), |
|
781 idx = 0, len = eventsList.length, prop; |
|
782 |
|
783 for( ; idx < len; idx++ ) { |
|
784 data.hash[ eventsList[idx] ] = true; |
|
785 } |
|
786 |
|
787 apis.forEach(function( val, idx ) { |
|
788 |
|
789 data.apis[ val ] = {}; |
|
790 |
|
791 var apiEvents = events[ val ].split( /\s+/g ), |
|
792 len = apiEvents.length, |
|
793 k = 0; |
|
794 |
|
795 for ( ; k < len; k++ ) { |
|
796 data.apis[ val ][ apiEvents[ k ] ] = true; |
|
797 } |
|
798 }); |
|
799 })( Popcorn.Events, internal.events ); |
|
800 |
|
801 Popcorn.events = { |
|
802 |
|
803 isNative: function( type ) { |
|
804 return !!internal.events.hash[ type ]; |
|
805 }, |
|
806 getInterface: function( type ) { |
|
807 |
|
808 if ( !Popcorn.events.isNative( type ) ) { |
|
809 return false; |
|
810 } |
|
811 |
|
812 var eventApi = internal.events, |
|
813 apis = eventApi.apiTypes, |
|
814 apihash = eventApi.apis, |
|
815 idx = 0, len = apis.length, api, tmp; |
|
816 |
|
817 for ( ; idx < len; idx++ ) { |
|
818 tmp = apis[ idx ]; |
|
819 |
|
820 if ( apihash[ tmp ][ type ] ) { |
|
821 api = tmp; |
|
822 break; |
|
823 } |
|
824 } |
|
825 return api; |
|
826 }, |
|
827 // Compile all native events to single array |
|
828 all: Popcorn.Events.Natives.split( /\s+/g ), |
|
829 // Defines all Event handling static functions |
|
830 fn: { |
|
831 trigger: function( type, data ) { |
|
832 |
|
833 var eventInterface, evt; |
|
834 // setup checks for custom event system |
|
835 if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) { |
|
836 |
|
837 eventInterface = Popcorn.events.getInterface( type ); |
|
838 |
|
839 if ( eventInterface ) { |
|
840 |
|
841 evt = document.createEvent( eventInterface ); |
|
842 evt.initEvent( type, true, true, global, 1 ); |
|
843 |
|
844 this.media.dispatchEvent( evt ); |
|
845 |
|
846 return this; |
|
847 } |
|
848 |
|
849 // Custom events |
|
850 Popcorn.forEach( this.data.events[ type ], function( obj, key ) { |
|
851 |
|
852 obj.call( this, data ); |
|
853 |
|
854 }, this ); |
|
855 |
|
856 } |
|
857 |
|
858 return this; |
|
859 }, |
|
860 listen: function( type, fn ) { |
|
861 |
|
862 var self = this, |
|
863 hasEvents = true, |
|
864 eventHook = Popcorn.events.hooks[ type ], |
|
865 origType = type, |
|
866 tmp; |
|
867 |
|
868 if ( !this.data.events[ type ] ) { |
|
869 this.data.events[ type ] = {}; |
|
870 hasEvents = false; |
|
871 } |
|
872 |
|
873 // Check and setup event hooks |
|
874 if ( eventHook ) { |
|
875 |
|
876 // Execute hook add method if defined |
|
877 if ( eventHook.add ) { |
|
878 eventHook.add.call( this, {}, fn ); |
|
879 } |
|
880 |
|
881 // Reassign event type to our piggyback event type if defined |
|
882 if ( eventHook.bind ) { |
|
883 type = eventHook.bind; |
|
884 } |
|
885 |
|
886 // Reassign handler if defined |
|
887 if ( eventHook.handler ) { |
|
888 tmp = fn; |
|
889 |
|
890 fn = function wrapper( event ) { |
|
891 eventHook.handler.call( self, event, tmp ); |
|
892 }; |
|
893 } |
|
894 |
|
895 // assume the piggy back event is registered |
|
896 hasEvents = true; |
|
897 |
|
898 // Setup event registry entry |
|
899 if ( !this.data.events[ type ] ) { |
|
900 this.data.events[ type ] = {}; |
|
901 // Toggle if the previous assumption was untrue |
|
902 hasEvents = false; |
|
903 } |
|
904 } |
|
905 |
|
906 // Register event and handler |
|
907 this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn; |
|
908 |
|
909 // only attach one event of any type |
|
910 if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) { |
|
911 |
|
912 this.media.addEventListener( type, function( event ) { |
|
913 |
|
914 Popcorn.forEach( self.data.events[ type ], function( obj, key ) { |
|
915 if ( typeof obj === "function" ) { |
|
916 obj.call( self, event ); |
|
917 } |
|
918 }); |
|
919 |
|
920 }, false); |
|
921 } |
|
922 return this; |
|
923 }, |
|
924 unlisten: function( type, fn ) { |
|
925 |
|
926 if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) { |
|
927 |
|
928 delete this.data.events[ type ][ fn ]; |
|
929 |
|
930 return this; |
|
931 } |
|
932 |
|
933 this.data.events[ type ] = null; |
|
934 |
|
935 return this; |
|
936 } |
|
937 }, |
|
938 hooks: { |
|
939 canplayall: { |
|
940 bind: "canplaythrough", |
|
941 add: function( event, callback ) { |
|
942 |
|
943 var state = false; |
|
944 |
|
945 if ( this.media.readyState ) { |
|
946 |
|
947 callback.call( this, event ); |
|
948 |
|
949 state = true; |
|
950 } |
|
951 |
|
952 this.data.hooks.canplayall = { |
|
953 fired: state |
|
954 }; |
|
955 }, |
|
956 // declare special handling instructions |
|
957 handler: function canplayall( event, callback ) { |
|
958 |
|
959 if ( !this.data.hooks.canplayall.fired ) { |
|
960 // trigger original user callback once |
|
961 callback.call( this, event ); |
|
962 |
|
963 this.data.hooks.canplayall.fired = true; |
|
964 } |
|
965 } |
|
966 } |
|
967 } |
|
968 }; |
|
969 |
|
970 // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances |
|
971 // Extend aliases (on, off, emit) |
|
972 Popcorn.forEach( [ [ "trigger", "emit" ], [ "listen", "on" ], [ "unlisten", "off" ] ], function( key ) { |
|
973 Popcorn.p[ key[ 0 ] ] = Popcorn.p[ key[ 1 ] ] = Popcorn.events.fn[ key[ 0 ] ]; |
|
974 }); |
|
975 |
|
976 // Internal Only - Adds track events to the instance object |
|
977 Popcorn.addTrackEvent = function( obj, track ) { |
|
978 var trackEvent, isUpdate, eventType; |
|
979 |
|
980 // Do a lookup for existing trackevents with this id |
|
981 if ( track.id ) { |
|
982 trackEvent = obj.getTrackEvent( track.id ); |
|
983 } |
|
984 |
|
985 // If a track event by this id currently exists, modify it |
|
986 if ( trackEvent ) { |
|
987 isUpdate = true; |
|
988 // Create a new object with the existing trackEvent |
|
989 // Extend with new track properties |
|
990 track = Popcorn.extend( {}, trackEvent, track ); |
|
991 |
|
992 // Remove the existing track from the instance |
|
993 obj.removeTrackEvent( track.id ); |
|
994 } |
|
995 |
|
996 // Determine if this track has default options set for it |
|
997 // If so, apply them to the track object |
|
998 if ( track && track._natives && track._natives.type && |
|
999 ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) { |
|
1000 |
|
1001 track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track ); |
|
1002 } |
|
1003 |
|
1004 if ( track._natives ) { |
|
1005 // Supports user defined track event id |
|
1006 track._id = track.id || track._id || Popcorn.guid( track._natives.type ); |
|
1007 |
|
1008 // Push track event ids into the history |
|
1009 obj.data.history.push( track._id ); |
|
1010 } |
|
1011 |
|
1012 track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate ); |
|
1013 track.end = Popcorn.util.toSeconds( track.end, obj.options.framerate ); |
|
1014 |
|
1015 // Store this definition in an array sorted by times |
|
1016 var byStart = obj.data.trackEvents.byStart, |
|
1017 byEnd = obj.data.trackEvents.byEnd, |
|
1018 startIndex, endIndex; |
|
1019 |
|
1020 for ( startIndex = byStart.length - 1; startIndex >= 0; startIndex-- ) { |
|
1021 |
|
1022 if ( track.start >= byStart[ startIndex ].start ) { |
|
1023 byStart.splice( startIndex + 1, 0, track ); |
|
1024 break; |
|
1025 } |
|
1026 } |
|
1027 |
|
1028 for ( endIndex = byEnd.length - 1; endIndex >= 0; endIndex-- ) { |
|
1029 |
|
1030 if ( track.end > byEnd[ endIndex ].end ) { |
|
1031 byEnd.splice( endIndex + 1, 0, track ); |
|
1032 break; |
|
1033 } |
|
1034 } |
|
1035 |
|
1036 // Display track event immediately if it's enabled and current |
|
1037 if ( track.end > obj.media.currentTime && |
|
1038 track.start <= obj.media.currentTime ) { |
|
1039 |
|
1040 track._running = true; |
|
1041 obj.data.running[ track._natives.type ].push( track ); |
|
1042 |
|
1043 if ( !obj.data.disabled[ track._natives.type ] ) { |
|
1044 |
|
1045 track._natives.start.call( obj, null, track ); |
|
1046 } |
|
1047 } |
|
1048 |
|
1049 // update startIndex and endIndex |
|
1050 if ( startIndex <= obj.data.trackEvents.startIndex && |
|
1051 track.start <= obj.data.trackEvents.previousUpdateTime ) { |
|
1052 |
|
1053 obj.data.trackEvents.startIndex++; |
|
1054 } |
|
1055 |
|
1056 if ( endIndex <= obj.data.trackEvents.endIndex && |
|
1057 track.end < obj.data.trackEvents.previousUpdateTime ) { |
|
1058 |
|
1059 obj.data.trackEvents.endIndex++; |
|
1060 } |
|
1061 |
|
1062 this.timeUpdate( obj, null, true ); |
|
1063 |
|
1064 // Store references to user added trackevents in ref table |
|
1065 if ( track._id ) { |
|
1066 Popcorn.addTrackEvent.ref( obj, track ); |
|
1067 } |
|
1068 |
|
1069 // If the call to addTrackEvent was an update/modify call, fire an event |
|
1070 if ( isUpdate ) { |
|
1071 |
|
1072 // Determine appropriate event type to trigger |
|
1073 // they are identical in function, but the naming |
|
1074 // adds some level of intuition for the end developer |
|
1075 // to rely on |
|
1076 if ( track._natives.type === "cue" ) { |
|
1077 eventType = "cuechange"; |
|
1078 } else { |
|
1079 eventType = "trackchange"; |
|
1080 } |
|
1081 |
|
1082 // Fire an event with change information |
|
1083 obj.emit( eventType, { |
|
1084 id: track.id, |
|
1085 previousValue: { |
|
1086 time: trackEvent.start, |
|
1087 fn: trackEvent._natives.start |
|
1088 }, |
|
1089 currentValue: { |
|
1090 time: track.start, |
|
1091 fn: track._natives.start |
|
1092 } |
|
1093 }); |
|
1094 } |
|
1095 }; |
|
1096 |
|
1097 // Internal Only - Adds track event references to the instance object's trackRefs hash table |
|
1098 Popcorn.addTrackEvent.ref = function( obj, track ) { |
|
1099 obj.data.trackRefs[ track._id ] = track; |
|
1100 |
|
1101 return obj; |
|
1102 }; |
|
1103 |
|
1104 Popcorn.removeTrackEvent = function( obj, removeId ) { |
|
1105 |
|
1106 var start, end, animate, |
|
1107 historyLen = obj.data.history.length, |
|
1108 length = obj.data.trackEvents.byStart.length, |
|
1109 index = 0, |
|
1110 indexWasAt = 0, |
|
1111 byStart = [], |
|
1112 byEnd = [], |
|
1113 animating = [], |
|
1114 history = []; |
|
1115 |
|
1116 while ( --length > -1 ) { |
|
1117 start = obj.data.trackEvents.byStart[ index ]; |
|
1118 end = obj.data.trackEvents.byEnd[ index ]; |
|
1119 |
|
1120 // Padding events will not have _id properties. |
|
1121 // These should be safely pushed onto the front and back of the |
|
1122 // track event array |
|
1123 if ( !start._id ) { |
|
1124 byStart.push( start ); |
|
1125 byEnd.push( end ); |
|
1126 } |
|
1127 |
|
1128 // Filter for user track events (vs system track events) |
|
1129 if ( start._id ) { |
|
1130 |
|
1131 // If not a matching start event for removal |
|
1132 if ( start._id !== removeId ) { |
|
1133 byStart.push( start ); |
|
1134 } |
|
1135 |
|
1136 // If not a matching end event for removal |
|
1137 if ( end._id !== removeId ) { |
|
1138 byEnd.push( end ); |
|
1139 } |
|
1140 |
|
1141 // If the _id is matched, capture the current index |
|
1142 if ( start._id === removeId ) { |
|
1143 indexWasAt = index; |
|
1144 |
|
1145 // If a _teardown function was defined, |
|
1146 // enforce for track event removals |
|
1147 if ( start._natives._teardown ) { |
|
1148 start._natives._teardown.call( obj, start ); |
|
1149 } |
|
1150 } |
|
1151 } |
|
1152 // Increment the track index |
|
1153 index++; |
|
1154 } |
|
1155 |
|
1156 // Reset length to be used by the condition below to determine |
|
1157 // if animating track events should also be filtered for removal. |
|
1158 // Reset index below to be used by the reverse while as an |
|
1159 // incrementing counter |
|
1160 length = obj.data.trackEvents.animating.length; |
|
1161 index = 0; |
|
1162 |
|
1163 if ( length ) { |
|
1164 while ( --length > -1 ) { |
|
1165 animate = obj.data.trackEvents.animating[ index ]; |
|
1166 |
|
1167 // Padding events will not have _id properties. |
|
1168 // These should be safely pushed onto the front and back of the |
|
1169 // track event array |
|
1170 if ( !animate._id ) { |
|
1171 animating.push( animate ); |
|
1172 } |
|
1173 |
|
1174 // If not a matching animate event for removal |
|
1175 if ( animate._id && animate._id !== removeId ) { |
|
1176 animating.push( animate ); |
|
1177 } |
|
1178 // Increment the track index |
|
1179 index++; |
|
1180 } |
|
1181 } |
|
1182 |
|
1183 // Update |
|
1184 if ( indexWasAt <= obj.data.trackEvents.startIndex ) { |
|
1185 obj.data.trackEvents.startIndex--; |
|
1186 } |
|
1187 |
|
1188 if ( indexWasAt <= obj.data.trackEvents.endIndex ) { |
|
1189 obj.data.trackEvents.endIndex--; |
|
1190 } |
|
1191 |
|
1192 obj.data.trackEvents.byStart = byStart; |
|
1193 obj.data.trackEvents.byEnd = byEnd; |
|
1194 obj.data.trackEvents.animating = animating; |
|
1195 |
|
1196 for ( var i = 0; i < historyLen; i++ ) { |
|
1197 if ( obj.data.history[ i ] !== removeId ) { |
|
1198 history.push( obj.data.history[ i ] ); |
|
1199 } |
|
1200 } |
|
1201 |
|
1202 // Update ordered history array |
|
1203 obj.data.history = history; |
|
1204 |
|
1205 // Update track event references |
|
1206 Popcorn.removeTrackEvent.ref( obj, removeId ); |
|
1207 }; |
|
1208 |
|
1209 // Internal Only - Removes track event references from instance object's trackRefs hash table |
|
1210 Popcorn.removeTrackEvent.ref = function( obj, removeId ) { |
|
1211 delete obj.data.trackRefs[ removeId ]; |
|
1212 |
|
1213 return obj; |
|
1214 }; |
|
1215 |
|
1216 // Return an array of track events bound to this instance object |
|
1217 Popcorn.getTrackEvents = function( obj ) { |
|
1218 |
|
1219 var trackevents = [], |
|
1220 refs = obj.data.trackEvents.byStart, |
|
1221 length = refs.length, |
|
1222 idx = 0, |
|
1223 ref; |
|
1224 |
|
1225 for ( ; idx < length; idx++ ) { |
|
1226 ref = refs[ idx ]; |
|
1227 // Return only user attributed track event references |
|
1228 if ( ref._id ) { |
|
1229 trackevents.push( ref ); |
|
1230 } |
|
1231 } |
|
1232 |
|
1233 return trackevents; |
|
1234 }; |
|
1235 |
|
1236 // Internal Only - Returns an instance object's trackRefs hash table |
|
1237 Popcorn.getTrackEvents.ref = function( obj ) { |
|
1238 return obj.data.trackRefs; |
|
1239 }; |
|
1240 |
|
1241 // Return a single track event bound to this instance object |
|
1242 Popcorn.getTrackEvent = function( obj, trackId ) { |
|
1243 return obj.data.trackRefs[ trackId ]; |
|
1244 }; |
|
1245 |
|
1246 // Internal Only - Returns an instance object's track reference by track id |
|
1247 Popcorn.getTrackEvent.ref = function( obj, trackId ) { |
|
1248 return obj.data.trackRefs[ trackId ]; |
|
1249 }; |
|
1250 |
|
1251 Popcorn.getLastTrackEventId = function( obj ) { |
|
1252 return obj.data.history[ obj.data.history.length - 1 ]; |
|
1253 }; |
|
1254 |
|
1255 Popcorn.timeUpdate = function( obj, event ) { |
|
1256 |
|
1257 var currentTime = obj.media.currentTime, |
|
1258 previousTime = obj.data.trackEvents.previousUpdateTime, |
|
1259 tracks = obj.data.trackEvents, |
|
1260 end = tracks.endIndex, |
|
1261 start = tracks.startIndex, |
|
1262 byStartLen = tracks.byStart.length, |
|
1263 byEndLen = tracks.byEnd.length, |
|
1264 registryByName = Popcorn.registryByName, |
|
1265 trackstart = "trackstart", |
|
1266 trackend = "trackend", |
|
1267 |
|
1268 byEnd, byStart, byAnimate, natives, type, runningPlugins; |
|
1269 |
|
1270 // Playbar advancing |
|
1271 if ( previousTime <= currentTime ) { |
|
1272 |
|
1273 while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) { |
|
1274 |
|
1275 byEnd = tracks.byEnd[ end ]; |
|
1276 natives = byEnd._natives; |
|
1277 type = natives && natives.type; |
|
1278 |
|
1279 // If plugin does not exist on this instance, remove it |
|
1280 if ( !natives || |
|
1281 ( !!registryByName[ type ] || |
|
1282 !!obj[ type ] ) ) { |
|
1283 |
|
1284 if ( byEnd._running === true ) { |
|
1285 |
|
1286 byEnd._running = false; |
|
1287 runningPlugins = obj.data.running[ type ]; |
|
1288 runningPlugins.splice( runningPlugins.indexOf( byEnd ), 1 ); |
|
1289 |
|
1290 if ( !obj.data.disabled[ type ] ) { |
|
1291 |
|
1292 natives.end.call( obj, event, byEnd ); |
|
1293 |
|
1294 obj.emit( trackend, |
|
1295 Popcorn.extend({}, byEnd, { |
|
1296 plugin: type, |
|
1297 type: trackend |
|
1298 }) |
|
1299 ); |
|
1300 } |
|
1301 } |
|
1302 |
|
1303 end++; |
|
1304 } else { |
|
1305 // remove track event |
|
1306 Popcorn.removeTrackEvent( obj, byEnd._id ); |
|
1307 return; |
|
1308 } |
|
1309 } |
|
1310 |
|
1311 while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) { |
|
1312 |
|
1313 byStart = tracks.byStart[ start ]; |
|
1314 natives = byStart._natives; |
|
1315 type = natives && natives.type; |
|
1316 |
|
1317 // If plugin does not exist on this instance, remove it |
|
1318 if ( !natives || |
|
1319 ( !!registryByName[ type ] || |
|
1320 !!obj[ type ] ) ) { |
|
1321 |
|
1322 if ( byStart.end > currentTime && |
|
1323 byStart._running === false ) { |
|
1324 |
|
1325 byStart._running = true; |
|
1326 obj.data.running[ type ].push( byStart ); |
|
1327 |
|
1328 if ( !obj.data.disabled[ type ] ) { |
|
1329 |
|
1330 natives.start.call( obj, event, byStart ); |
|
1331 |
|
1332 obj.emit( trackstart, |
|
1333 Popcorn.extend({}, byStart, { |
|
1334 plugin: type, |
|
1335 type: trackstart |
|
1336 }) |
|
1337 ); |
|
1338 } |
|
1339 } |
|
1340 start++; |
|
1341 } else { |
|
1342 // remove track event |
|
1343 Popcorn.removeTrackEvent( obj, byStart._id ); |
|
1344 return; |
|
1345 } |
|
1346 } |
|
1347 |
|
1348 // Playbar receding |
|
1349 } else if ( previousTime > currentTime ) { |
|
1350 |
|
1351 while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) { |
|
1352 |
|
1353 byStart = tracks.byStart[ start ]; |
|
1354 natives = byStart._natives; |
|
1355 type = natives && natives.type; |
|
1356 |
|
1357 // if plugin does not exist on this instance, remove it |
|
1358 if ( !natives || |
|
1359 ( !!registryByName[ type ] || |
|
1360 !!obj[ type ] ) ) { |
|
1361 |
|
1362 if ( byStart._running === true ) { |
|
1363 |
|
1364 byStart._running = false; |
|
1365 runningPlugins = obj.data.running[ type ]; |
|
1366 runningPlugins.splice( runningPlugins.indexOf( byStart ), 1 ); |
|
1367 |
|
1368 if ( !obj.data.disabled[ type ] ) { |
|
1369 |
|
1370 natives.end.call( obj, event, byStart ); |
|
1371 |
|
1372 obj.emit( trackend, |
|
1373 Popcorn.extend({}, byStart, { |
|
1374 plugin: type, |
|
1375 type: trackend |
|
1376 }) |
|
1377 ); |
|
1378 } |
|
1379 } |
|
1380 start--; |
|
1381 } else { |
|
1382 // remove track event |
|
1383 Popcorn.removeTrackEvent( obj, byStart._id ); |
|
1384 return; |
|
1385 } |
|
1386 } |
|
1387 |
|
1388 while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) { |
|
1389 |
|
1390 byEnd = tracks.byEnd[ end ]; |
|
1391 natives = byEnd._natives; |
|
1392 type = natives && natives.type; |
|
1393 |
|
1394 // if plugin does not exist on this instance, remove it |
|
1395 if ( !natives || |
|
1396 ( !!registryByName[ type ] || |
|
1397 !!obj[ type ] ) ) { |
|
1398 |
|
1399 if ( byEnd.start <= currentTime && |
|
1400 byEnd._running === false ) { |
|
1401 |
|
1402 byEnd._running = true; |
|
1403 obj.data.running[ type ].push( byEnd ); |
|
1404 |
|
1405 if ( !obj.data.disabled[ type ] ) { |
|
1406 |
|
1407 natives.start.call( obj, event, byEnd ); |
|
1408 |
|
1409 obj.emit( trackstart, |
|
1410 Popcorn.extend({}, byEnd, { |
|
1411 plugin: type, |
|
1412 type: trackstart |
|
1413 }) |
|
1414 ); |
|
1415 } |
|
1416 } |
|
1417 end--; |
|
1418 } else { |
|
1419 // remove track event |
|
1420 Popcorn.removeTrackEvent( obj, byEnd._id ); |
|
1421 return; |
|
1422 } |
|
1423 } |
|
1424 } |
|
1425 |
|
1426 tracks.endIndex = end; |
|
1427 tracks.startIndex = start; |
|
1428 tracks.previousUpdateTime = currentTime; |
|
1429 |
|
1430 //enforce index integrity if trackRemoved |
|
1431 tracks.byStart.length < byStartLen && tracks.startIndex--; |
|
1432 tracks.byEnd.length < byEndLen && tracks.endIndex--; |
|
1433 |
|
1434 }; |
|
1435 |
|
1436 // Map and Extend TrackEvent functions to all Popcorn instances |
|
1437 Popcorn.extend( Popcorn.p, { |
|
1438 |
|
1439 getTrackEvents: function() { |
|
1440 return Popcorn.getTrackEvents.call( null, this ); |
|
1441 }, |
|
1442 |
|
1443 getTrackEvent: function( id ) { |
|
1444 return Popcorn.getTrackEvent.call( null, this, id ); |
|
1445 }, |
|
1446 |
|
1447 getLastTrackEventId: function() { |
|
1448 return Popcorn.getLastTrackEventId.call( null, this ); |
|
1449 }, |
|
1450 |
|
1451 removeTrackEvent: function( id ) { |
|
1452 |
|
1453 Popcorn.removeTrackEvent.call( null, this, id ); |
|
1454 return this; |
|
1455 }, |
|
1456 |
|
1457 removePlugin: function( name ) { |
|
1458 Popcorn.removePlugin.call( null, this, name ); |
|
1459 return this; |
|
1460 }, |
|
1461 |
|
1462 timeUpdate: function( event ) { |
|
1463 Popcorn.timeUpdate.call( null, this, event ); |
|
1464 return this; |
|
1465 }, |
|
1466 |
|
1467 destroy: function() { |
|
1468 Popcorn.destroy.call( null, this ); |
|
1469 return this; |
|
1470 } |
|
1471 }); |
|
1472 |
|
1473 // Plugin manifests |
|
1474 Popcorn.manifest = {}; |
|
1475 // Plugins are registered |
|
1476 Popcorn.registry = []; |
|
1477 Popcorn.registryByName = {}; |
|
1478 // An interface for extending Popcorn |
|
1479 // with plugin functionality |
|
1480 Popcorn.plugin = function( name, definition, manifest ) { |
|
1481 |
|
1482 if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { |
|
1483 Popcorn.error( "'" + name + "' is a protected function name" ); |
|
1484 return; |
|
1485 } |
|
1486 |
|
1487 // Provides some sugar, but ultimately extends |
|
1488 // the definition into Popcorn.p |
|
1489 var reserved = [ "start", "end" ], |
|
1490 plugin = {}, |
|
1491 setup, |
|
1492 isfn = typeof definition === "function", |
|
1493 methods = [ "_setup", "_teardown", "start", "end", "frame" ]; |
|
1494 |
|
1495 // combines calls of two function calls into one |
|
1496 var combineFn = function( first, second ) { |
|
1497 |
|
1498 first = first || Popcorn.nop; |
|
1499 second = second || Popcorn.nop; |
|
1500 |
|
1501 return function() { |
|
1502 first.apply( this, arguments ); |
|
1503 second.apply( this, arguments ); |
|
1504 }; |
|
1505 }; |
|
1506 |
|
1507 // If `manifest` arg is undefined, check for manifest within the `definition` object |
|
1508 // If no `definition.manifest`, an empty object is a sufficient fallback |
|
1509 Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {}; |
|
1510 |
|
1511 // apply safe, and empty default functions |
|
1512 methods.forEach(function( method ) { |
|
1513 definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name ); |
|
1514 }); |
|
1515 |
|
1516 var pluginFn = function( setup, options ) { |
|
1517 |
|
1518 if ( !options ) { |
|
1519 return this; |
|
1520 } |
|
1521 |
|
1522 // When the "ranges" property is set and its value is an array, short-circuit |
|
1523 // the pluginFn definition to recall itself with an options object generated from |
|
1524 // each range object in the ranges array. (eg. { start: 15, end: 16 } ) |
|
1525 if ( options.ranges && Popcorn.isArray(options.ranges) ) { |
|
1526 Popcorn.forEach( options.ranges, function( range ) { |
|
1527 // Create a fresh object, extend with current options |
|
1528 // and start/end range object's properties |
|
1529 // Works with in/out as well. |
|
1530 var opts = Popcorn.extend( {}, options, range ); |
|
1531 |
|
1532 // Remove the ranges property to prevent infinitely |
|
1533 // entering this condition |
|
1534 delete opts.ranges; |
|
1535 |
|
1536 // Call the plugin with the newly created opts object |
|
1537 this[ name ]( opts ); |
|
1538 }, this); |
|
1539 |
|
1540 // Return the Popcorn instance to avoid creating an empty track event |
|
1541 return this; |
|
1542 } |
|
1543 |
|
1544 // Storing the plugin natives |
|
1545 var natives = options._natives = {}, |
|
1546 compose = "", |
|
1547 originalOpts, manifestOpts; |
|
1548 |
|
1549 Popcorn.extend( natives, setup ); |
|
1550 |
|
1551 options._natives.type = name; |
|
1552 options._running = false; |
|
1553 |
|
1554 natives.start = natives.start || natives[ "in" ]; |
|
1555 natives.end = natives.end || natives[ "out" ]; |
|
1556 |
|
1557 if ( options.once ) { |
|
1558 natives.end = combineFn( natives.end, function() { |
|
1559 this.removeTrackEvent( options._id ); |
|
1560 }); |
|
1561 } |
|
1562 |
|
1563 // extend teardown to always call end if running |
|
1564 natives._teardown = combineFn(function() { |
|
1565 |
|
1566 var args = slice.call( arguments ), |
|
1567 runningPlugins = this.data.running[ natives.type ]; |
|
1568 |
|
1569 // end function signature is not the same as teardown, |
|
1570 // put null on the front of arguments for the event parameter |
|
1571 args.unshift( null ); |
|
1572 |
|
1573 // only call end if event is running |
|
1574 args[ 1 ]._running && |
|
1575 runningPlugins.splice( runningPlugins.indexOf( options ), 1 ) && |
|
1576 natives.end.apply( this, args ); |
|
1577 }, natives._teardown ); |
|
1578 |
|
1579 // default to an empty string if no effect exists |
|
1580 // split string into an array of effects |
|
1581 options.compose = options.compose && options.compose.split( " " ) || []; |
|
1582 options.effect = options.effect && options.effect.split( " " ) || []; |
|
1583 |
|
1584 // join the two arrays together |
|
1585 options.compose = options.compose.concat( options.effect ); |
|
1586 |
|
1587 options.compose.forEach(function( composeOption ) { |
|
1588 |
|
1589 // if the requested compose is garbage, throw it away |
|
1590 compose = Popcorn.compositions[ composeOption ] || {}; |
|
1591 |
|
1592 // extends previous functions with compose function |
|
1593 methods.forEach(function( method ) { |
|
1594 natives[ method ] = combineFn( natives[ method ], compose[ method ] ); |
|
1595 }); |
|
1596 }); |
|
1597 |
|
1598 // Ensure a manifest object, an empty object is a sufficient fallback |
|
1599 options._natives.manifest = manifest; |
|
1600 |
|
1601 // Checks for expected properties |
|
1602 if ( !( "start" in options ) ) { |
|
1603 options.start = options[ "in" ] || 0; |
|
1604 } |
|
1605 |
|
1606 if ( !options.end && options.end !== 0 ) { |
|
1607 options.end = options[ "out" ] || Number.MAX_VALUE; |
|
1608 } |
|
1609 |
|
1610 // Use hasOwn to detect non-inherited toString, since all |
|
1611 // objects will receive a toString - its otherwise undetectable |
|
1612 if ( !hasOwn.call( options, "toString" ) ) { |
|
1613 options.toString = function() { |
|
1614 var props = [ |
|
1615 "start: " + options.start, |
|
1616 "end: " + options.end, |
|
1617 "id: " + (options.id || options._id) |
|
1618 ]; |
|
1619 |
|
1620 // Matches null and undefined, allows: false, 0, "" and truthy |
|
1621 if ( options.target != null ) { |
|
1622 props.push( "target: " + options.target ); |
|
1623 } |
|
1624 |
|
1625 return name + " ( " + props.join(", ") + " )"; |
|
1626 }; |
|
1627 } |
|
1628 |
|
1629 // Resolves 239, 241, 242 |
|
1630 if ( !options.target ) { |
|
1631 |
|
1632 // Sometimes the manifest may be missing entirely |
|
1633 // or it has an options object that doesn't have a `target` property |
|
1634 manifestOpts = "options" in manifest && manifest.options; |
|
1635 |
|
1636 options.target = manifestOpts && "target" in manifestOpts && manifestOpts.target; |
|
1637 } |
|
1638 |
|
1639 if ( options._natives ) { |
|
1640 // ensure an initial id is there before setup is called |
|
1641 options._id = Popcorn.guid( options._natives.type ); |
|
1642 } |
|
1643 |
|
1644 // Trigger _setup method if exists |
|
1645 options._natives._setup && options._natives._setup.call( this, options ); |
|
1646 |
|
1647 // Create new track event for this instance |
|
1648 Popcorn.addTrackEvent( this, options ); |
|
1649 |
|
1650 // Future support for plugin event definitions |
|
1651 // for all of the native events |
|
1652 Popcorn.forEach( setup, function( callback, type ) { |
|
1653 |
|
1654 if ( type !== "type" ) { |
|
1655 |
|
1656 if ( reserved.indexOf( type ) === -1 ) { |
|
1657 |
|
1658 this.on( type, callback ); |
|
1659 } |
|
1660 } |
|
1661 |
|
1662 }, this ); |
|
1663 |
|
1664 return this; |
|
1665 }; |
|
1666 |
|
1667 // Extend Popcorn.p with new named definition |
|
1668 // Assign new named definition |
|
1669 Popcorn.p[ name ] = plugin[ name ] = function( id, options ) { |
|
1670 var length = arguments.length, |
|
1671 trackEvent, defaults, mergedSetupOpts; |
|
1672 |
|
1673 // Shift arguments based on use case |
|
1674 // |
|
1675 // Back compat for: |
|
1676 // p.plugin( options ); |
|
1677 if ( id && !options ) { |
|
1678 options = id; |
|
1679 id = null; |
|
1680 } else { |
|
1681 |
|
1682 // Get the trackEvent that matches the given id. |
|
1683 trackEvent = this.getTrackEvent( id ); |
|
1684 |
|
1685 // If the track event does not exist, ensure that the options |
|
1686 // object has a proper id |
|
1687 if ( !trackEvent ) { |
|
1688 options.id = id; |
|
1689 |
|
1690 // If the track event does exist, merge the updated properties |
|
1691 } else { |
|
1692 |
|
1693 options = Popcorn.extend( {}, trackEvent, options ); |
|
1694 |
|
1695 Popcorn.addTrackEvent( this, options ); |
|
1696 |
|
1697 return this; |
|
1698 } |
|
1699 } |
|
1700 |
|
1701 this.data.running[ name ] = this.data.running[ name ] || []; |
|
1702 |
|
1703 // Merge with defaults if they exist, make sure per call is prioritized |
|
1704 defaults = ( this.options.defaults && this.options.defaults[ name ] ) || {}; |
|
1705 mergedSetupOpts = Popcorn.extend( {}, defaults, options ); |
|
1706 |
|
1707 return pluginFn.call( this, isfn ? definition.call( this, mergedSetupOpts ) : definition, |
|
1708 mergedSetupOpts ); |
|
1709 }; |
|
1710 |
|
1711 // if the manifest parameter exists we should extend it onto the definition object |
|
1712 // so that it shows up when calling Popcorn.registry and Popcorn.registryByName |
|
1713 if ( manifest ) { |
|
1714 Popcorn.extend( definition, { |
|
1715 manifest: manifest |
|
1716 }); |
|
1717 } |
|
1718 |
|
1719 // Push into the registry |
|
1720 var entry = { |
|
1721 fn: plugin[ name ], |
|
1722 definition: definition, |
|
1723 base: definition, |
|
1724 parents: [], |
|
1725 name: name |
|
1726 }; |
|
1727 Popcorn.registry.push( |
|
1728 Popcorn.extend( plugin, entry, { |
|
1729 type: name |
|
1730 }) |
|
1731 ); |
|
1732 Popcorn.registryByName[ name ] = entry; |
|
1733 |
|
1734 return plugin; |
|
1735 }; |
|
1736 |
|
1737 // Storage for plugin function errors |
|
1738 Popcorn.plugin.errors = []; |
|
1739 |
|
1740 // Returns wrapped plugin function |
|
1741 function safeTry( fn, pluginName ) { |
|
1742 return function() { |
|
1743 |
|
1744 // When Popcorn.plugin.debug is true, do not suppress errors |
|
1745 if ( Popcorn.plugin.debug ) { |
|
1746 return fn.apply( this, arguments ); |
|
1747 } |
|
1748 |
|
1749 try { |
|
1750 return fn.apply( this, arguments ); |
|
1751 } catch ( ex ) { |
|
1752 |
|
1753 // Push plugin function errors into logging queue |
|
1754 Popcorn.plugin.errors.push({ |
|
1755 plugin: pluginName, |
|
1756 thrown: ex, |
|
1757 source: fn.toString() |
|
1758 }); |
|
1759 |
|
1760 // Trigger an error that the instance can listen for |
|
1761 // and react to |
|
1762 this.emit( "pluginerror", Popcorn.plugin.errors ); |
|
1763 } |
|
1764 }; |
|
1765 } |
|
1766 |
|
1767 // Debug-mode flag for plugin development |
|
1768 // True for Popcorn development versions, false for stable/tagged versions |
|
1769 Popcorn.plugin.debug = ( Popcorn.version === "@" + "VERSION" ); |
|
1770 |
|
1771 // removePlugin( type ) removes all tracks of that from all instances of popcorn |
|
1772 // removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn |
|
1773 Popcorn.removePlugin = function( obj, name ) { |
|
1774 |
|
1775 // Check if we are removing plugin from an instance or from all of Popcorn |
|
1776 if ( !name ) { |
|
1777 |
|
1778 // Fix the order |
|
1779 name = obj; |
|
1780 obj = Popcorn.p; |
|
1781 |
|
1782 if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { |
|
1783 Popcorn.error( "'" + name + "' is a protected function name" ); |
|
1784 return; |
|
1785 } |
|
1786 |
|
1787 var registryLen = Popcorn.registry.length, |
|
1788 registryIdx; |
|
1789 |
|
1790 // remove plugin reference from registry |
|
1791 for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) { |
|
1792 if ( Popcorn.registry[ registryIdx ].name === name ) { |
|
1793 Popcorn.registry.splice( registryIdx, 1 ); |
|
1794 delete Popcorn.registryByName[ name ]; |
|
1795 delete Popcorn.manifest[ name ]; |
|
1796 |
|
1797 // delete the plugin |
|
1798 delete obj[ name ]; |
|
1799 |
|
1800 // plugin found and removed, stop checking, we are done |
|
1801 return; |
|
1802 } |
|
1803 } |
|
1804 |
|
1805 } |
|
1806 |
|
1807 var byStart = obj.data.trackEvents.byStart, |
|
1808 byEnd = obj.data.trackEvents.byEnd, |
|
1809 animating = obj.data.trackEvents.animating, |
|
1810 idx, sl; |
|
1811 |
|
1812 // remove all trackEvents |
|
1813 for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) { |
|
1814 |
|
1815 if ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) { |
|
1816 |
|
1817 byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] ); |
|
1818 |
|
1819 byStart.splice( idx, 1 ); |
|
1820 |
|
1821 // update for loop if something removed, but keep checking |
|
1822 idx--; sl--; |
|
1823 if ( obj.data.trackEvents.startIndex <= idx ) { |
|
1824 obj.data.trackEvents.startIndex--; |
|
1825 obj.data.trackEvents.endIndex--; |
|
1826 } |
|
1827 } |
|
1828 |
|
1829 // clean any remaining references in the end index |
|
1830 // we do this seperate from the above check because they might not be in the same order |
|
1831 if ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) { |
|
1832 |
|
1833 byEnd.splice( idx, 1 ); |
|
1834 } |
|
1835 } |
|
1836 |
|
1837 //remove all animating events |
|
1838 for ( idx = 0, sl = animating.length; idx < sl; idx++ ) { |
|
1839 |
|
1840 if ( animating[ idx ] && animating[ idx ]._natives && animating[ idx ]._natives.type === name ) { |
|
1841 |
|
1842 animating.splice( idx, 1 ); |
|
1843 |
|
1844 // update for loop if something removed, but keep checking |
|
1845 idx--; sl--; |
|
1846 } |
|
1847 } |
|
1848 |
|
1849 }; |
|
1850 |
|
1851 Popcorn.compositions = {}; |
|
1852 |
|
1853 // Plugin inheritance |
|
1854 Popcorn.compose = function( name, definition, manifest ) { |
|
1855 |
|
1856 // If `manifest` arg is undefined, check for manifest within the `definition` object |
|
1857 // If no `definition.manifest`, an empty object is a sufficient fallback |
|
1858 Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {}; |
|
1859 |
|
1860 // register the effect by name |
|
1861 Popcorn.compositions[ name ] = definition; |
|
1862 }; |
|
1863 |
|
1864 Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose; |
|
1865 |
|
1866 var rnaiveExpr = /^(?:\.|#|\[)/; |
|
1867 |
|
1868 // Basic DOM utilities and helpers API. See #1037 |
|
1869 Popcorn.dom = { |
|
1870 debug: false, |
|
1871 // Popcorn.dom.find( selector, context ) |
|
1872 // |
|
1873 // Returns the first element that matches the specified selector |
|
1874 // Optionally provide a context element, defaults to `document` |
|
1875 // |
|
1876 // eg. |
|
1877 // Popcorn.dom.find("video") returns the first video element |
|
1878 // Popcorn.dom.find("#foo") returns the first element with `id="foo"` |
|
1879 // Popcorn.dom.find("foo") returns the first element with `id="foo"` |
|
1880 // Note: Popcorn.dom.find("foo") is the only allowed deviation |
|
1881 // from valid querySelector selector syntax |
|
1882 // |
|
1883 // Popcorn.dom.find(".baz") returns the first element with `class="baz"` |
|
1884 // Popcorn.dom.find("[preload]") returns the first element with `preload="..."` |
|
1885 // ... |
|
1886 // See https://developer.mozilla.org/En/DOM/Document.querySelector |
|
1887 // |
|
1888 // |
|
1889 find: function( selector, context ) { |
|
1890 var node = null; |
|
1891 |
|
1892 // Trim leading/trailing whitespace to avoid false negatives |
|
1893 selector = selector.trim(); |
|
1894 |
|
1895 // Default context is the `document` |
|
1896 context = context || document; |
|
1897 |
|
1898 if ( selector ) { |
|
1899 // If the selector does not begin with "#", "." or "[", |
|
1900 // it could be either a nodeName or ID w/o "#" |
|
1901 if ( !rnaiveExpr.test( selector ) ) { |
|
1902 |
|
1903 // Try finding an element that matches by ID first |
|
1904 node = document.getElementById( selector ); |
|
1905 |
|
1906 // If a match was found by ID, return the element |
|
1907 if ( node !== null ) { |
|
1908 return node; |
|
1909 } |
|
1910 } |
|
1911 // Assume no elements have been found yet |
|
1912 // Catch any invalid selector syntax errors and bury them. |
|
1913 try { |
|
1914 node = context.querySelector( selector ); |
|
1915 } catch ( e ) { |
|
1916 if ( Popcorn.dom.debug ) { |
|
1917 throw new Error(e); |
|
1918 } |
|
1919 } |
|
1920 } |
|
1921 return node; |
|
1922 } |
|
1923 }; |
|
1924 |
|
1925 // Cache references to reused RegExps |
|
1926 var rparams = /\?/, |
|
1927 // XHR Setup object |
|
1928 setup = { |
|
1929 url: "", |
|
1930 data: "", |
|
1931 dataType: "", |
|
1932 success: Popcorn.nop, |
|
1933 type: "GET", |
|
1934 async: true, |
|
1935 xhr: function() { |
|
1936 return new global.XMLHttpRequest(); |
|
1937 } |
|
1938 }; |
|
1939 |
|
1940 Popcorn.xhr = function( options ) { |
|
1941 |
|
1942 options.dataType = options.dataType && options.dataType.toLowerCase() || null; |
|
1943 |
|
1944 if ( options.dataType && |
|
1945 ( options.dataType === "jsonp" || options.dataType === "script" ) ) { |
|
1946 |
|
1947 Popcorn.xhr.getJSONP( |
|
1948 options.url, |
|
1949 options.success, |
|
1950 options.dataType === "script" |
|
1951 ); |
|
1952 return; |
|
1953 } |
|
1954 |
|
1955 var settings = Popcorn.extend( {}, setup, options ); |
|
1956 |
|
1957 // Create new XMLHttpRequest object |
|
1958 settings.ajax = settings.xhr(); |
|
1959 |
|
1960 if ( settings.ajax ) { |
|
1961 |
|
1962 if ( settings.type === "GET" && settings.data ) { |
|
1963 |
|
1964 // append query string |
|
1965 settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data; |
|
1966 |
|
1967 // Garbage collect and reset settings.data |
|
1968 settings.data = null; |
|
1969 } |
|
1970 |
|
1971 |
|
1972 settings.ajax.open( settings.type, settings.url, settings.async ); |
|
1973 settings.ajax.send( settings.data || null ); |
|
1974 |
|
1975 return Popcorn.xhr.httpData( settings ); |
|
1976 } |
|
1977 }; |
|
1978 |
|
1979 |
|
1980 Popcorn.xhr.httpData = function( settings ) { |
|
1981 |
|
1982 var data, json = null, |
|
1983 parser, xml = null; |
|
1984 |
|
1985 settings.ajax.onreadystatechange = function() { |
|
1986 |
|
1987 if ( settings.ajax.readyState === 4 ) { |
|
1988 |
|
1989 try { |
|
1990 json = JSON.parse( settings.ajax.responseText ); |
|
1991 } catch( e ) { |
|
1992 //suppress |
|
1993 } |
|
1994 |
|
1995 data = { |
|
1996 xml: settings.ajax.responseXML, |
|
1997 text: settings.ajax.responseText, |
|
1998 json: json |
|
1999 }; |
|
2000 |
|
2001 // Normalize: data.xml is non-null in IE9 regardless of if response is valid xml |
|
2002 if ( !data.xml || !data.xml.documentElement ) { |
|
2003 data.xml = null; |
|
2004 |
|
2005 try { |
|
2006 parser = new DOMParser(); |
|
2007 xml = parser.parseFromString( settings.ajax.responseText, "text/xml" ); |
|
2008 |
|
2009 if ( !xml.getElementsByTagName( "parsererror" ).length ) { |
|
2010 data.xml = xml; |
|
2011 } |
|
2012 } catch ( e ) { |
|
2013 // data.xml remains null |
|
2014 } |
|
2015 } |
|
2016 |
|
2017 // If a dataType was specified, return that type of data |
|
2018 if ( settings.dataType ) { |
|
2019 data = data[ settings.dataType ]; |
|
2020 } |
|
2021 |
|
2022 |
|
2023 settings.success.call( settings.ajax, data ); |
|
2024 |
|
2025 } |
|
2026 }; |
|
2027 return data; |
|
2028 }; |
|
2029 |
|
2030 Popcorn.xhr.getJSONP = function( url, success, isScript ) { |
|
2031 |
|
2032 var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement, |
|
2033 script = document.createElement( "script" ), |
|
2034 isFired = false, |
|
2035 params = [], |
|
2036 rjsonp = /(=)\?(?=&|$)|\?\?/, |
|
2037 replaceInUrl, prefix, paramStr, callback, callparam; |
|
2038 |
|
2039 if ( !isScript ) { |
|
2040 |
|
2041 // is there a calback already in the url |
|
2042 callparam = url.match( /(callback=[^&]*)/ ); |
|
2043 |
|
2044 if ( callparam !== null && callparam.length ) { |
|
2045 |
|
2046 prefix = callparam[ 1 ].split( "=" )[ 1 ]; |
|
2047 |
|
2048 // Since we need to support developer specified callbacks |
|
2049 // and placeholders in harmony, make sure matches to "callback=" |
|
2050 // aren't just placeholders. |
|
2051 // We coded ourselves into a corner here. |
|
2052 // JSONP callbacks should never have been |
|
2053 // allowed to have developer specified callbacks |
|
2054 if ( prefix === "?" ) { |
|
2055 prefix = "jsonp"; |
|
2056 } |
|
2057 |
|
2058 // get the callback name |
|
2059 callback = Popcorn.guid( prefix ); |
|
2060 |
|
2061 // replace existing callback name with unique callback name |
|
2062 url = url.replace( /(callback=[^&]*)/, "callback=" + callback ); |
|
2063 } else { |
|
2064 |
|
2065 callback = Popcorn.guid( "jsonp" ); |
|
2066 |
|
2067 if ( rjsonp.test( url ) ) { |
|
2068 url = url.replace( rjsonp, "$1" + callback ); |
|
2069 } |
|
2070 |
|
2071 // split on first question mark, |
|
2072 // this is to capture the query string |
|
2073 params = url.split( /\?(.+)?/ ); |
|
2074 |
|
2075 // rebuild url with callback |
|
2076 url = params[ 0 ] + "?"; |
|
2077 if ( params[ 1 ] ) { |
|
2078 url += params[ 1 ] + "&"; |
|
2079 } |
|
2080 url += "callback=" + callback; |
|
2081 } |
|
2082 |
|
2083 // Define the JSONP success callback globally |
|
2084 window[ callback ] = function( data ) { |
|
2085 // Fire success callbacks |
|
2086 success && success( data ); |
|
2087 isFired = true; |
|
2088 }; |
|
2089 } |
|
2090 |
|
2091 script.addEventListener( "load", function() { |
|
2092 |
|
2093 // Handling remote script loading callbacks |
|
2094 if ( isScript ) { |
|
2095 // getScript |
|
2096 success && success(); |
|
2097 } |
|
2098 |
|
2099 // Executing for JSONP requests |
|
2100 if ( isFired ) { |
|
2101 // Garbage collect the callback |
|
2102 delete window[ callback ]; |
|
2103 } |
|
2104 // Garbage collect the script resource |
|
2105 head.removeChild( script ); |
|
2106 }, false ); |
|
2107 |
|
2108 script.src = url; |
|
2109 |
|
2110 head.insertBefore( script, head.firstChild ); |
|
2111 |
|
2112 return; |
|
2113 }; |
|
2114 |
|
2115 Popcorn.getJSONP = Popcorn.xhr.getJSONP; |
|
2116 |
|
2117 Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) { |
|
2118 |
|
2119 return Popcorn.xhr.getJSONP( url, success, true ); |
|
2120 }; |
|
2121 |
|
2122 Popcorn.util = { |
|
2123 // Simple function to parse a timestamp into seconds |
|
2124 // Acceptable formats are: |
|
2125 // HH:MM:SS.MMM |
|
2126 // HH:MM:SS;FF |
|
2127 // Hours and minutes are optional. They default to 0 |
|
2128 toSeconds: function( timeStr, framerate ) { |
|
2129 // Hours and minutes are optional |
|
2130 // Seconds must be specified |
|
2131 // Seconds can be followed by milliseconds OR by the frame information |
|
2132 var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/, |
|
2133 errorMessage = "Invalid time format", |
|
2134 digitPairs, lastIndex, lastPair, firstPair, |
|
2135 frameInfo, frameTime; |
|
2136 |
|
2137 if ( typeof timeStr === "number" ) { |
|
2138 return timeStr; |
|
2139 } |
|
2140 |
|
2141 if ( typeof timeStr === "string" && |
|
2142 !validTimeFormat.test( timeStr ) ) { |
|
2143 Popcorn.error( errorMessage ); |
|
2144 } |
|
2145 |
|
2146 digitPairs = timeStr.split( ":" ); |
|
2147 lastIndex = digitPairs.length - 1; |
|
2148 lastPair = digitPairs[ lastIndex ]; |
|
2149 |
|
2150 // Fix last element: |
|
2151 if ( lastPair.indexOf( ";" ) > -1 ) { |
|
2152 |
|
2153 frameInfo = lastPair.split( ";" ); |
|
2154 frameTime = 0; |
|
2155 |
|
2156 if ( framerate && ( typeof framerate === "number" ) ) { |
|
2157 frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate; |
|
2158 } |
|
2159 |
|
2160 digitPairs[ lastIndex ] = parseInt( frameInfo[ 0 ], 10 ) + frameTime; |
|
2161 } |
|
2162 |
|
2163 firstPair = digitPairs[ 0 ]; |
|
2164 |
|
2165 return { |
|
2166 |
|
2167 1: parseFloat( firstPair, 10 ), |
|
2168 |
|
2169 2: ( parseInt( firstPair, 10 ) * 60 ) + |
|
2170 parseFloat( digitPairs[ 1 ], 10 ), |
|
2171 |
|
2172 3: ( parseInt( firstPair, 10 ) * 3600 ) + |
|
2173 ( parseInt( digitPairs[ 1 ], 10 ) * 60 ) + |
|
2174 parseFloat( digitPairs[ 2 ], 10 ) |
|
2175 |
|
2176 }[ digitPairs.length || 1 ]; |
|
2177 } |
|
2178 }; |
|
2179 |
|
2180 // alias for exec function |
|
2181 Popcorn.p.cue = Popcorn.p.exec; |
|
2182 |
|
2183 // Protected API methods |
|
2184 Popcorn.protect = { |
|
2185 natives: getKeys( Popcorn.p ).map(function( val ) { |
|
2186 return val.toLowerCase(); |
|
2187 }) |
|
2188 }; |
|
2189 |
|
2190 // Setup logging for deprecated methods |
|
2191 Popcorn.forEach({ |
|
2192 // Deprecated: Recommended |
|
2193 "listen": "on", |
|
2194 "unlisten": "off", |
|
2195 "trigger": "emit", |
|
2196 "exec": "cue" |
|
2197 |
|
2198 }, function( recommend, api ) { |
|
2199 var original = Popcorn.p[ api ]; |
|
2200 // Override the deprecated api method with a method of the same name |
|
2201 // that logs a warning and defers to the new recommended method |
|
2202 Popcorn.p[ api ] = function() { |
|
2203 if ( typeof console !== "undefined" && console.warn ) { |
|
2204 console.warn( |
|
2205 "Deprecated method '" + api + "', " + |
|
2206 (recommend == null ? "do not use." : "use '" + recommend + "' instead." ) |
|
2207 ); |
|
2208 |
|
2209 // Restore api after first warning |
|
2210 Popcorn.p[ api ] = original; |
|
2211 } |
|
2212 return Popcorn.p[ recommend ].apply( this, [].slice.call( arguments ) ); |
|
2213 }; |
|
2214 }); |
|
2215 |
|
2216 |
|
2217 // Exposes Popcorn to global context |
|
2218 global.Popcorn = Popcorn; |
|
2219 |
|
2220 })(window, window.document); |
|
2221 /*! |
|
2222 * Popcorn.sequence |
|
2223 * |
|
2224 * Copyright 2011, Rick Waldron |
|
2225 * Licensed under MIT license. |
|
2226 * |
|
2227 */ |
|
2228 |
|
2229 /* jslint forin: true, maxerr: 50, indent: 4, es5: true */ |
|
2230 /* global Popcorn: true */ |
|
2231 |
|
2232 // Requires Popcorn.js |
|
2233 (function( global, Popcorn ) { |
|
2234 |
|
2235 // TODO: as support increases, migrate to element.dataset |
|
2236 var doc = global.document, |
|
2237 location = global.location, |
|
2238 rprotocol = /:\/\//, |
|
2239 // TODO: better solution to this sucky stop-gap |
|
2240 lochref = location.href.replace( location.href.split("/").slice(-1)[0], "" ), |
|
2241 // privately held |
|
2242 range = function(start, stop, step) { |
|
2243 |
|
2244 start = start || 0; |
|
2245 stop = ( stop || start || 0 ) + 1; |
|
2246 step = step || 1; |
|
2247 |
|
2248 var len = Math.ceil((stop - start) / step) || 0, |
|
2249 idx = 0, |
|
2250 range = []; |
|
2251 |
|
2252 range.length = len; |
|
2253 |
|
2254 while (idx < len) { |
|
2255 range[idx++] = start; |
|
2256 start += step; |
|
2257 } |
|
2258 return range; |
|
2259 }; |
|
2260 |
|
2261 Popcorn.sequence = function( parent, list ) { |
|
2262 return new Popcorn.sequence.init( parent, list ); |
|
2263 }; |
|
2264 |
|
2265 Popcorn.sequence.init = function( parent, list ) { |
|
2266 |
|
2267 // Video element |
|
2268 this.parent = doc.getElementById( parent ); |
|
2269 |
|
2270 // Store ref to a special ID |
|
2271 this.seqId = Popcorn.guid( "__sequenced" ); |
|
2272 |
|
2273 // List of HTMLVideoElements |
|
2274 this.queue = []; |
|
2275 |
|
2276 // List of Popcorn objects |
|
2277 this.playlist = []; |
|
2278 |
|
2279 // Lists of in/out points |
|
2280 this.inOuts = { |
|
2281 |
|
2282 // Stores the video in/out times for each video in sequence |
|
2283 ofVideos: [], |
|
2284 |
|
2285 // Stores the clip in/out times for each clip in sequences |
|
2286 ofClips: [] |
|
2287 |
|
2288 }; |
|
2289 |
|
2290 // Store first video dimensions |
|
2291 this.dims = { |
|
2292 width: 0, //this.video.videoWidth, |
|
2293 height: 0 //this.video.videoHeight |
|
2294 }; |
|
2295 |
|
2296 this.active = 0; |
|
2297 this.cycling = false; |
|
2298 this.playing = false; |
|
2299 |
|
2300 this.times = { |
|
2301 last: 0 |
|
2302 }; |
|
2303 |
|
2304 // Store event pointers and queues |
|
2305 this.events = { |
|
2306 |
|
2307 }; |
|
2308 |
|
2309 var self = this, |
|
2310 clipOffset = 0; |
|
2311 |
|
2312 // Create `video` elements |
|
2313 Popcorn.forEach( list, function( media, idx ) { |
|
2314 |
|
2315 var video = doc.createElement( "video" ); |
|
2316 |
|
2317 video.preload = "auto"; |
|
2318 |
|
2319 // Setup newly created video element |
|
2320 video.controls = true; |
|
2321 |
|
2322 // If the first, show it, if the after, hide it |
|
2323 video.style.display = ( idx && "none" ) || "" ; |
|
2324 |
|
2325 // Seta registered sequence id |
|
2326 video.id = self.seqId + "-" + idx ; |
|
2327 |
|
2328 // Push this video into the sequence queue |
|
2329 self.queue.push( video ); |
|
2330 |
|
2331 var //satisfy lint |
|
2332 mIn = media["in"], |
|
2333 mOut = media["out"]; |
|
2334 |
|
2335 // Push the in/out points into sequence ioVideos |
|
2336 self.inOuts.ofVideos.push({ |
|
2337 "in": ( mIn !== undefined && mIn ) || 1, |
|
2338 "out": ( mOut !== undefined && mOut ) || 0 |
|
2339 }); |
|
2340 |
|
2341 self.inOuts.ofVideos[ idx ]["out"] = self.inOuts.ofVideos[ idx ]["out"] || self.inOuts.ofVideos[ idx ]["in"] + 2; |
|
2342 |
|
2343 // Set the sources |
|
2344 video.src = !rprotocol.test( media.src ) ? lochref + media.src : media.src; |
|
2345 |
|
2346 // Set some squence specific data vars |
|
2347 video.setAttribute("data-sequence-owner", parent ); |
|
2348 video.setAttribute("data-sequence-guid", self.seqId ); |
|
2349 video.setAttribute("data-sequence-id", idx ); |
|
2350 video.setAttribute("data-sequence-clip", [ self.inOuts.ofVideos[ idx ]["in"], self.inOuts.ofVideos[ idx ]["out"] ].join(":") ); |
|
2351 |
|
2352 // Append the video to the parent element |
|
2353 self.parent.appendChild( video ); |
|
2354 |
|
2355 |
|
2356 self.playlist.push( Popcorn("#" + video.id ) ); |
|
2357 |
|
2358 }); |
|
2359 |
|
2360 self.inOuts.ofVideos.forEach(function( obj ) { |
|
2361 |
|
2362 var clipDuration = obj["out"] - obj["in"], |
|
2363 offs = { |
|
2364 "in": clipOffset, |
|
2365 "out": clipOffset + clipDuration |
|
2366 }; |
|
2367 |
|
2368 self.inOuts.ofClips.push( offs ); |
|
2369 |
|
2370 clipOffset = offs["out"] + 1; |
|
2371 }); |
|
2372 |
|
2373 Popcorn.forEach( this.queue, function( media, idx ) { |
|
2374 |
|
2375 function canPlayThrough( event ) { |
|
2376 |
|
2377 // If this is idx zero, use it as dimension for all |
|
2378 if ( !idx ) { |
|
2379 self.dims.width = media.videoWidth; |
|
2380 self.dims.height = media.videoHeight; |
|
2381 } |
|
2382 |
|
2383 media.currentTime = self.inOuts.ofVideos[ idx ]["in"] - 0.5; |
|
2384 |
|
2385 media.removeEventListener( "canplaythrough", canPlayThrough, false ); |
|
2386 |
|
2387 return true; |
|
2388 } |
|
2389 |
|
2390 // Hook up event listeners for managing special playback |
|
2391 media.addEventListener( "canplaythrough", canPlayThrough, false ); |
|
2392 |
|
2393 // TODO: consolidate & DRY |
|
2394 media.addEventListener( "play", function( event ) { |
|
2395 |
|
2396 self.playing = true; |
|
2397 |
|
2398 }, false ); |
|
2399 |
|
2400 media.addEventListener( "pause", function( event ) { |
|
2401 |
|
2402 self.playing = false; |
|
2403 |
|
2404 }, false ); |
|
2405 |
|
2406 media.addEventListener( "timeupdate", function( event ) { |
|
2407 |
|
2408 var target = event.srcElement || event.target, |
|
2409 seqIdx = +( (target.dataset && target.dataset.sequenceId) || target.getAttribute("data-sequence-id") ), |
|
2410 floor = Math.floor( media.currentTime ); |
|
2411 |
|
2412 if ( self.times.last !== floor && |
|
2413 seqIdx === self.active ) { |
|
2414 |
|
2415 self.times.last = floor; |
|
2416 |
|
2417 if ( floor === self.inOuts.ofVideos[ seqIdx ]["out"] ) { |
|
2418 |
|
2419 Popcorn.sequence.cycle.call( self, seqIdx ); |
|
2420 } |
|
2421 } |
|
2422 }, false ); |
|
2423 }); |
|
2424 |
|
2425 return this; |
|
2426 }; |
|
2427 |
|
2428 Popcorn.sequence.init.prototype = Popcorn.sequence.prototype; |
|
2429 |
|
2430 // |
|
2431 Popcorn.sequence.cycle = function( idx ) { |
|
2432 |
|
2433 if ( !this.queue ) { |
|
2434 Popcorn.error("Popcorn.sequence.cycle is not a public method"); |
|
2435 } |
|
2436 |
|
2437 var // Localize references |
|
2438 queue = this.queue, |
|
2439 ioVideos = this.inOuts.ofVideos, |
|
2440 current = queue[ idx ], |
|
2441 nextIdx = 0, |
|
2442 next, clip; |
|
2443 |
|
2444 |
|
2445 var // Popcorn instances |
|
2446 $popnext, |
|
2447 $popprev; |
|
2448 |
|
2449 |
|
2450 if ( queue[ idx + 1 ] ) { |
|
2451 nextIdx = idx + 1; |
|
2452 } |
|
2453 |
|
2454 // Reset queue |
|
2455 if ( !queue[ idx + 1 ] ) { |
|
2456 |
|
2457 nextIdx = 0; |
|
2458 this.playlist[ idx ].pause(); |
|
2459 |
|
2460 } else { |
|
2461 |
|
2462 next = queue[ nextIdx ]; |
|
2463 clip = ioVideos[ nextIdx ]; |
|
2464 |
|
2465 // Constrain dimentions |
|
2466 Popcorn.extend( next, { |
|
2467 width: this.dims.width, |
|
2468 height: this.dims.height |
|
2469 }); |
|
2470 |
|
2471 $popnext = this.playlist[ nextIdx ]; |
|
2472 $popprev = this.playlist[ idx ]; |
|
2473 |
|
2474 // When not resetting to 0 |
|
2475 current.pause(); |
|
2476 |
|
2477 this.active = nextIdx; |
|
2478 this.times.last = clip["in"] - 1; |
|
2479 |
|
2480 // Play the next video in the sequence |
|
2481 $popnext.currentTime( clip["in"] ); |
|
2482 |
|
2483 $popnext[ nextIdx ? "play" : "pause" ](); |
|
2484 |
|
2485 // Trigger custom cycling event hook |
|
2486 this.trigger( "cycle", { |
|
2487 |
|
2488 position: { |
|
2489 previous: idx, |
|
2490 current: nextIdx |
|
2491 } |
|
2492 |
|
2493 }); |
|
2494 |
|
2495 // Set the previous back to it's beginning time |
|
2496 // $popprev.currentTime( ioVideos[ idx ].in ); |
|
2497 |
|
2498 if ( nextIdx ) { |
|
2499 // Hide the currently ending video |
|
2500 current.style.display = "none"; |
|
2501 // Show the next video in the sequence |
|
2502 next.style.display = ""; |
|
2503 } |
|
2504 |
|
2505 this.cycling = false; |
|
2506 } |
|
2507 |
|
2508 return this; |
|
2509 }; |
|
2510 |
|
2511 var excludes = [ "timeupdate", "play", "pause" ]; |
|
2512 |
|
2513 // Sequence object prototype |
|
2514 Popcorn.extend( Popcorn.sequence.prototype, { |
|
2515 |
|
2516 // Returns Popcorn object from sequence at index |
|
2517 eq: function( idx ) { |
|
2518 return this.playlist[ idx ]; |
|
2519 }, |
|
2520 // Remove a sequence from it's playback display container |
|
2521 remove: function() { |
|
2522 this.parent.innerHTML = null; |
|
2523 }, |
|
2524 // Returns Clip object from sequence at index |
|
2525 clip: function( idx ) { |
|
2526 return this.inOuts.ofVideos[ idx ]; |
|
2527 }, |
|
2528 // Returns sum duration for all videos in sequence |
|
2529 duration: function() { |
|
2530 |
|
2531 var ret = 0, |
|
2532 seq = this.inOuts.ofClips, |
|
2533 idx = 0; |
|
2534 |
|
2535 for ( ; idx < seq.length; idx++ ) { |
|
2536 ret += seq[ idx ]["out"] - seq[ idx ]["in"] + 1; |
|
2537 } |
|
2538 |
|
2539 return ret - 1; |
|
2540 }, |
|
2541 |
|
2542 play: function() { |
|
2543 |
|
2544 this.playlist[ this.active ].play(); |
|
2545 |
|
2546 return this; |
|
2547 }, |
|
2548 // Attach an event to a single point in time |
|
2549 exec: function ( time, fn ) { |
|
2550 |
|
2551 var index = this.active; |
|
2552 |
|
2553 this.inOuts.ofClips.forEach(function( off, idx ) { |
|
2554 if ( time >= off["in"] && time <= off["out"] ) { |
|
2555 index = idx; |
|
2556 } |
|
2557 }); |
|
2558 |
|
2559 //offsetBy = time - self.inOuts.ofVideos[ index ].in; |
|
2560 |
|
2561 time += this.inOuts.ofVideos[ index ]["in"] - this.inOuts.ofClips[ index ]["in"]; |
|
2562 |
|
2563 // Creating a one second track event with an empty end |
|
2564 Popcorn.addTrackEvent( this.playlist[ index ], { |
|
2565 start: time - 1, |
|
2566 end: time, |
|
2567 _running: false, |
|
2568 _natives: { |
|
2569 start: fn || Popcorn.nop, |
|
2570 end: Popcorn.nop, |
|
2571 type: "exec" |
|
2572 } |
|
2573 }); |
|
2574 |
|
2575 return this; |
|
2576 }, |
|
2577 // Binds event handlers that fire only when all |
|
2578 // videos in sequence have heard the event |
|
2579 listen: function( type, callback ) { |
|
2580 |
|
2581 var self = this, |
|
2582 seq = this.playlist, |
|
2583 total = seq.length, |
|
2584 count = 0, |
|
2585 fnName; |
|
2586 |
|
2587 if ( !callback ) { |
|
2588 callback = Popcorn.nop; |
|
2589 } |
|
2590 |
|
2591 // Handling for DOM and Media events |
|
2592 if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) { |
|
2593 Popcorn.forEach( seq, function( video ) { |
|
2594 |
|
2595 video.listen( type, function( event ) { |
|
2596 |
|
2597 event.active = self; |
|
2598 |
|
2599 if ( excludes.indexOf( type ) > -1 ) { |
|
2600 |
|
2601 callback.call( video, event ); |
|
2602 |
|
2603 } else { |
|
2604 if ( ++count === total ) { |
|
2605 callback.call( video, event ); |
|
2606 } |
|
2607 } |
|
2608 }); |
|
2609 }); |
|
2610 |
|
2611 } else { |
|
2612 |
|
2613 // If no events registered with this name, create a cache |
|
2614 if ( !this.events[ type ] ) { |
|
2615 this.events[ type ] = {}; |
|
2616 } |
|
2617 |
|
2618 // Normalize a callback name key |
|
2619 fnName = callback.name || Popcorn.guid( "__" + type ); |
|
2620 |
|
2621 // Store in event cache |
|
2622 this.events[ type ][ fnName ] = callback; |
|
2623 } |
|
2624 |
|
2625 // Return the sequence object |
|
2626 return this; |
|
2627 }, |
|
2628 unlisten: function( type, name ) { |
|
2629 // TODO: finish implementation |
|
2630 }, |
|
2631 trigger: function( type, data ) { |
|
2632 var self = this; |
|
2633 |
|
2634 // Handling for DOM and Media events |
|
2635 if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) { |
|
2636 |
|
2637 // find the active video and trigger api events on that video. |
|
2638 return; |
|
2639 |
|
2640 } else { |
|
2641 |
|
2642 // Only proceed if there are events of this type |
|
2643 // currently registered on the sequence |
|
2644 if ( this.events[ type ] ) { |
|
2645 |
|
2646 Popcorn.forEach( this.events[ type ], function( callback, name ) { |
|
2647 callback.call( self, { type: type }, data ); |
|
2648 }); |
|
2649 |
|
2650 } |
|
2651 } |
|
2652 |
|
2653 return this; |
|
2654 } |
|
2655 }); |
|
2656 |
|
2657 |
|
2658 Popcorn.forEach( Popcorn.manifest, function( obj, plugin ) { |
|
2659 |
|
2660 // Implement passthrough methods to plugins |
|
2661 Popcorn.sequence.prototype[ plugin ] = function( options ) { |
|
2662 |
|
2663 // console.log( this, options ); |
|
2664 var videos = {}, assignTo = [], |
|
2665 idx, off, inOuts, inIdx, outIdx, keys, clip, clipInOut, clipRange; |
|
2666 |
|
2667 for ( idx = 0; idx < this.inOuts.ofClips.length; idx++ ) { |
|
2668 // store reference |
|
2669 off = this.inOuts.ofClips[ idx ]; |
|
2670 // array to test against |
|
2671 inOuts = range( off["in"], off["out"] ); |
|
2672 |
|
2673 inIdx = inOuts.indexOf( options.start ); |
|
2674 outIdx = inOuts.indexOf( options.end ); |
|
2675 |
|
2676 if ( inIdx > -1 ) { |
|
2677 videos[ idx ] = Popcorn.extend( {}, off, { |
|
2678 start: inOuts[ inIdx ], |
|
2679 clipIdx: inIdx |
|
2680 }); |
|
2681 } |
|
2682 |
|
2683 if ( outIdx > -1 ) { |
|
2684 videos[ idx ] = Popcorn.extend( {}, off, { |
|
2685 end: inOuts[ outIdx ], |
|
2686 clipIdx: outIdx |
|
2687 }); |
|
2688 } |
|
2689 } |
|
2690 |
|
2691 keys = Object.keys( videos ).map(function( val ) { |
|
2692 return +val; |
|
2693 }); |
|
2694 |
|
2695 assignTo = range( keys[ 0 ], keys[ 1 ] ); |
|
2696 |
|
2697 //console.log( "PLUGIN CALL MAPS: ", videos, keys, assignTo ); |
|
2698 for ( idx = 0; idx < assignTo.length; idx++ ) { |
|
2699 |
|
2700 var compile = {}, |
|
2701 play = assignTo[ idx ], |
|
2702 vClip = videos[ play ]; |
|
2703 |
|
2704 if ( vClip ) { |
|
2705 |
|
2706 // has instructions |
|
2707 clip = this.inOuts.ofVideos[ play ]; |
|
2708 clipInOut = vClip.clipIdx; |
|
2709 clipRange = range( clip["in"], clip["out"] ); |
|
2710 |
|
2711 if ( vClip.start ) { |
|
2712 compile.start = clipRange[ clipInOut ]; |
|
2713 compile.end = clipRange[ clipRange.length - 1 ]; |
|
2714 } |
|
2715 |
|
2716 if ( vClip.end ) { |
|
2717 compile.start = clipRange[ 0 ]; |
|
2718 compile.end = clipRange[ clipInOut ]; |
|
2719 } |
|
2720 |
|
2721 //compile.start += 0.1; |
|
2722 //compile.end += 0.9; |
|
2723 |
|
2724 } else { |
|
2725 |
|
2726 compile.start = this.inOuts.ofVideos[ play ]["in"]; |
|
2727 compile.end = this.inOuts.ofVideos[ play ]["out"]; |
|
2728 |
|
2729 //compile.start += 0.1; |
|
2730 //compile.end += 0.9; |
|
2731 |
|
2732 } |
|
2733 |
|
2734 // Handling full clip persistance |
|
2735 //if ( compile.start === compile.end ) { |
|
2736 //compile.start -= 0.1; |
|
2737 //compile.end += 0.9; |
|
2738 //} |
|
2739 |
|
2740 // Call the plugin on the appropriate Popcorn object in the playlist |
|
2741 // Merge original options object & compiled (start/end) object into |
|
2742 // a new fresh object |
|
2743 this.playlist[ play ][ plugin ]( |
|
2744 |
|
2745 Popcorn.extend( {}, options, compile ) |
|
2746 |
|
2747 ); |
|
2748 |
|
2749 } |
|
2750 |
|
2751 // Return the sequence object |
|
2752 return this; |
|
2753 }; |
|
2754 |
|
2755 }); |
|
2756 })( this, Popcorn ); |
|
2757 (function( Popcorn ) { |
|
2758 document.addEventListener( "DOMContentLoaded", function() { |
|
2759 |
|
2760 // Supports non-specific elements |
|
2761 var dataAttr = "data-timeline-sources", |
|
2762 medias = document.querySelectorAll( "[" + dataAttr + "]" ); |
|
2763 |
|
2764 Popcorn.forEach( medias, function( idx, key ) { |
|
2765 |
|
2766 var media = medias[ key ], |
|
2767 hasDataSources = false, |
|
2768 dataSources, data, popcornMedia; |
|
2769 |
|
2770 // Ensure that the DOM has an id |
|
2771 if ( !media.id ) { |
|
2772 |
|
2773 media.id = Popcorn.guid( "__popcorn" ); |
|
2774 } |
|
2775 |
|
2776 // Ensure we're looking at a dom node |
|
2777 if ( media.nodeType && media.nodeType === 1 ) { |
|
2778 |
|
2779 popcornMedia = Popcorn( "#" + media.id ); |
|
2780 |
|
2781 dataSources = ( media.getAttribute( dataAttr ) || "" ).split( "," ); |
|
2782 |
|
2783 if ( dataSources[ 0 ] ) { |
|
2784 |
|
2785 Popcorn.forEach( dataSources, function( source ) { |
|
2786 |
|
2787 // split the parser and data as parser!file |
|
2788 data = source.split( "!" ); |
|
2789 |
|
2790 // if no parser is defined for the file, assume "parse" + file extension |
|
2791 if ( data.length === 1 ) { |
|
2792 |
|
2793 // parse a relative URL for the filename, split to get extension |
|
2794 data = source.match( /(.*)[\/\\]([^\/\\]+\.\w+)$/ )[ 2 ].split( "." ); |
|
2795 |
|
2796 data[ 0 ] = "parse" + data[ 1 ].toUpperCase(); |
|
2797 data[ 1 ] = source; |
|
2798 } |
|
2799 |
|
2800 // If the media has data sources and the correct parser is registered, continue to load |
|
2801 if ( dataSources[ 0 ] && popcornMedia[ data[ 0 ] ] ) { |
|
2802 |
|
2803 // Set up the media and load in the datasources |
|
2804 popcornMedia[ data[ 0 ] ]( data[ 1 ] ); |
|
2805 |
|
2806 } |
|
2807 }); |
|
2808 |
|
2809 } |
|
2810 |
|
2811 // Only play the media if it was specified to do so |
|
2812 if ( !!popcornMedia.autoplay() ) { |
|
2813 popcornMedia.play(); |
|
2814 } |
|
2815 |
|
2816 } |
|
2817 }); |
|
2818 }, false ); |
|
2819 |
|
2820 })( Popcorn );(function( global, Popcorn ) { |
|
2821 |
|
2822 var navigator = global.navigator; |
|
2823 |
|
2824 // Initialize locale data |
|
2825 // Based on http://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes |
|
2826 function initLocale( arg ) { |
|
2827 |
|
2828 var locale = typeof arg === "string" ? arg : [ arg.language, arg.region ].join( "-" ), |
|
2829 parts = locale.split( "-" ); |
|
2830 |
|
2831 // Setup locale data table |
|
2832 return { |
|
2833 iso6391: locale, |
|
2834 language: parts[ 0 ] || "", |
|
2835 region: parts[ 1 ] || "" |
|
2836 }; |
|
2837 } |
|
2838 |
|
2839 // Declare locale data table |
|
2840 var localeData = initLocale( navigator.userLanguage || navigator.language ); |
|
2841 |
|
2842 Popcorn.locale = { |
|
2843 |
|
2844 // Popcorn.locale.get() |
|
2845 // returns reference to privately |
|
2846 // defined localeData |
|
2847 get: function() { |
|
2848 return localeData; |
|
2849 }, |
|
2850 |
|
2851 // Popcorn.locale.set( string|object ); |
|
2852 set: function( arg ) { |
|
2853 |
|
2854 localeData = initLocale( arg ); |
|
2855 |
|
2856 Popcorn.locale.broadcast(); |
|
2857 |
|
2858 return localeData; |
|
2859 }, |
|
2860 |
|
2861 // Popcorn.locale.broadcast( type ) |
|
2862 // Sends events to all popcorn media instances that are |
|
2863 // listening for locale events |
|
2864 broadcast: function( type ) { |
|
2865 |
|
2866 var instances = Popcorn.instances, |
|
2867 length = instances.length, |
|
2868 idx = 0, |
|
2869 instance; |
|
2870 |
|
2871 type = type || "locale:changed"; |
|
2872 |
|
2873 // Iterate all current instances |
|
2874 for ( ; idx < length; idx++ ) { |
|
2875 instance = instances[ idx ]; |
|
2876 |
|
2877 // For those instances with locale event listeners, |
|
2878 // trigger a locale change event |
|
2879 if ( type in instance.data.events ) { |
|
2880 instance.trigger( type ); |
|
2881 } |
|
2882 } |
|
2883 } |
|
2884 }; |
|
2885 })( this, this.Popcorn );(function( Popcorn ) { |
|
2886 |
|
2887 var |
|
2888 |
|
2889 AP = Array.prototype, |
|
2890 OP = Object.prototype, |
|
2891 |
|
2892 forEach = AP.forEach, |
|
2893 slice = AP.slice, |
|
2894 hasOwn = OP.hasOwnProperty, |
|
2895 toString = OP.toString; |
|
2896 |
|
2897 // stores parsers keyed on filetype |
|
2898 Popcorn.parsers = {}; |
|
2899 |
|
2900 // An interface for extending Popcorn |
|
2901 // with parser functionality |
|
2902 Popcorn.parser = function( name, type, definition ) { |
|
2903 |
|
2904 if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { |
|
2905 Popcorn.error( "'" + name + "' is a protected function name" ); |
|
2906 return; |
|
2907 } |
|
2908 |
|
2909 // fixes parameters for overloaded function call |
|
2910 if ( typeof type === "function" && !definition ) { |
|
2911 definition = type; |
|
2912 type = ""; |
|
2913 } |
|
2914 |
|
2915 if ( typeof definition !== "function" || typeof type !== "string" ) { |
|
2916 return; |
|
2917 } |
|
2918 |
|
2919 // Provides some sugar, but ultimately extends |
|
2920 // the definition into Popcorn.p |
|
2921 |
|
2922 var natives = Popcorn.events.all, |
|
2923 parseFn, |
|
2924 parser = {}; |
|
2925 |
|
2926 parseFn = function( filename, callback ) { |
|
2927 |
|
2928 if ( !filename ) { |
|
2929 return this; |
|
2930 } |
|
2931 |
|
2932 var that = this; |
|
2933 |
|
2934 Popcorn.xhr({ |
|
2935 url: filename, |
|
2936 dataType: type, |
|
2937 success: function( data ) { |
|
2938 |
|
2939 var tracksObject = definition( data ), |
|
2940 tracksData, |
|
2941 tracksDataLen, |
|
2942 tracksDef, |
|
2943 idx = 0; |
|
2944 |
|
2945 tracksData = tracksObject.data || []; |
|
2946 tracksDataLen = tracksData.length; |
|
2947 tracksDef = null; |
|
2948 |
|
2949 // If no tracks to process, return immediately |
|
2950 if ( !tracksDataLen ) { |
|
2951 return; |
|
2952 } |
|
2953 |
|
2954 // Create tracks out of parsed object |
|
2955 for ( ; idx < tracksDataLen; idx++ ) { |
|
2956 |
|
2957 tracksDef = tracksData[ idx ]; |
|
2958 |
|
2959 for ( var key in tracksDef ) { |
|
2960 |
|
2961 if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) { |
|
2962 |
|
2963 that[ key ]( tracksDef[ key ] ); |
|
2964 } |
|
2965 } |
|
2966 } |
|
2967 if ( callback ) { |
|
2968 callback(); |
|
2969 } |
|
2970 } |
|
2971 }); |
|
2972 |
|
2973 return this; |
|
2974 }; |
|
2975 |
|
2976 // Assign new named definition |
|
2977 parser[ name ] = parseFn; |
|
2978 |
|
2979 // Extend Popcorn.p with new named definition |
|
2980 Popcorn.extend( Popcorn.p, parser ); |
|
2981 |
|
2982 // keys the function name by filetype extension |
|
2983 //Popcorn.parsers[ name ] = true; |
|
2984 |
|
2985 return parser; |
|
2986 }; |
|
2987 })( Popcorn );(function( Popcorn ) { |
|
2988 |
|
2989 // combines calls of two function calls into one |
|
2990 var combineFn = function( first, second ) { |
|
2991 |
|
2992 first = first || Popcorn.nop; |
|
2993 second = second || Popcorn.nop; |
|
2994 |
|
2995 return function() { |
|
2996 |
|
2997 first.apply( this, arguments ); |
|
2998 second.apply( this, arguments ); |
|
2999 }; |
|
3000 }; |
|
3001 |
|
3002 // ID string matching |
|
3003 var rIdExp = /^(#([\w\-\_\.]+))$/; |
|
3004 |
|
3005 var audioExtensions = "ogg|oga|aac|mp3|wav", |
|
3006 videoExtensions = "ogg|ogv|mp4|webm", |
|
3007 mediaExtensions = audioExtensions + "|" + videoExtensions; |
|
3008 |
|
3009 var audioExtensionRegexp = new RegExp( "^.*\\.(" + audioExtensions + ")($|\\?)" ), |
|
3010 mediaExtensionRegexp = new RegExp( "^.*\\.(" + mediaExtensions + ")($|\\?)" ); |
|
3011 |
|
3012 Popcorn.player = function( name, player ) { |
|
3013 |
|
3014 // return early if a player already exists under this name |
|
3015 if ( Popcorn[ name ] ) { |
|
3016 |
|
3017 return; |
|
3018 } |
|
3019 |
|
3020 player = player || {}; |
|
3021 |
|
3022 var playerFn = function( target, src, options ) { |
|
3023 |
|
3024 options = options || {}; |
|
3025 |
|
3026 // List of events |
|
3027 var date = new Date() / 1000, |
|
3028 baselineTime = date, |
|
3029 currentTime = 0, |
|
3030 readyState = 0, |
|
3031 volume = 1, |
|
3032 muted = false, |
|
3033 events = {}, |
|
3034 |
|
3035 // The container div of the resource |
|
3036 container = typeof target === "string" ? Popcorn.dom.find( target ) : target, |
|
3037 basePlayer = {}, |
|
3038 timeout, |
|
3039 popcorn; |
|
3040 |
|
3041 if ( !Object.prototype.__defineGetter__ ) { |
|
3042 |
|
3043 basePlayer = container || document.createElement( "div" ); |
|
3044 } |
|
3045 |
|
3046 // copies a div into the media object |
|
3047 for( var val in container ) { |
|
3048 |
|
3049 // don't copy properties if using container as baseplayer |
|
3050 if ( val in basePlayer ) { |
|
3051 |
|
3052 continue; |
|
3053 } |
|
3054 |
|
3055 if ( typeof container[ val ] === "object" ) { |
|
3056 |
|
3057 basePlayer[ val ] = container[ val ]; |
|
3058 } else if ( typeof container[ val ] === "function" ) { |
|
3059 |
|
3060 basePlayer[ val ] = (function( value ) { |
|
3061 |
|
3062 // this is a stupid ugly kludgy hack in honour of Safari |
|
3063 // in Safari a NodeList is a function, not an object |
|
3064 if ( "length" in container[ value ] && !container[ value ].call ) { |
|
3065 |
|
3066 return container[ value ]; |
|
3067 } else { |
|
3068 |
|
3069 return function() { |
|
3070 |
|
3071 return container[ value ].apply( container, arguments ); |
|
3072 }; |
|
3073 } |
|
3074 }( val )); |
|
3075 } else { |
|
3076 |
|
3077 Popcorn.player.defineProperty( basePlayer, val, { |
|
3078 get: (function( value ) { |
|
3079 |
|
3080 return function() { |
|
3081 |
|
3082 return container[ value ]; |
|
3083 }; |
|
3084 }( val )), |
|
3085 set: Popcorn.nop, |
|
3086 configurable: true |
|
3087 }); |
|
3088 } |
|
3089 } |
|
3090 |
|
3091 var timeupdate = function() { |
|
3092 |
|
3093 date = new Date() / 1000; |
|
3094 |
|
3095 if ( !basePlayer.paused ) { |
|
3096 |
|
3097 basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime ); |
|
3098 basePlayer.dispatchEvent( "timeupdate" ); |
|
3099 timeout = setTimeout( timeupdate, 10 ); |
|
3100 } |
|
3101 |
|
3102 baselineTime = date; |
|
3103 }; |
|
3104 |
|
3105 basePlayer.play = function() { |
|
3106 |
|
3107 this.paused = false; |
|
3108 |
|
3109 if ( basePlayer.readyState >= 4 ) { |
|
3110 |
|
3111 baselineTime = new Date() / 1000; |
|
3112 basePlayer.dispatchEvent( "play" ); |
|
3113 timeupdate(); |
|
3114 } |
|
3115 }; |
|
3116 |
|
3117 basePlayer.pause = function() { |
|
3118 |
|
3119 this.paused = true; |
|
3120 basePlayer.dispatchEvent( "pause" ); |
|
3121 }; |
|
3122 |
|
3123 Popcorn.player.defineProperty( basePlayer, "currentTime", { |
|
3124 get: function() { |
|
3125 |
|
3126 return currentTime; |
|
3127 }, |
|
3128 set: function( val ) { |
|
3129 |
|
3130 // make sure val is a number |
|
3131 currentTime = +val; |
|
3132 basePlayer.dispatchEvent( "timeupdate" ); |
|
3133 |
|
3134 return currentTime; |
|
3135 }, |
|
3136 configurable: true |
|
3137 }); |
|
3138 |
|
3139 Popcorn.player.defineProperty( basePlayer, "volume", { |
|
3140 get: function() { |
|
3141 |
|
3142 return volume; |
|
3143 }, |
|
3144 set: function( val ) { |
|
3145 |
|
3146 // make sure val is a number |
|
3147 volume = +val; |
|
3148 basePlayer.dispatchEvent( "volumechange" ); |
|
3149 return volume; |
|
3150 }, |
|
3151 configurable: true |
|
3152 }); |
|
3153 |
|
3154 Popcorn.player.defineProperty( basePlayer, "muted", { |
|
3155 get: function() { |
|
3156 |
|
3157 return muted; |
|
3158 }, |
|
3159 set: function( val ) { |
|
3160 |
|
3161 // make sure val is a number |
|
3162 muted = +val; |
|
3163 basePlayer.dispatchEvent( "volumechange" ); |
|
3164 return muted; |
|
3165 }, |
|
3166 configurable: true |
|
3167 }); |
|
3168 |
|
3169 Popcorn.player.defineProperty( basePlayer, "readyState", { |
|
3170 get: function() { |
|
3171 |
|
3172 return readyState; |
|
3173 }, |
|
3174 set: function( val ) { |
|
3175 |
|
3176 readyState = val; |
|
3177 return readyState; |
|
3178 }, |
|
3179 configurable: true |
|
3180 }); |
|
3181 |
|
3182 // Adds an event listener to the object |
|
3183 basePlayer.addEventListener = function( evtName, fn ) { |
|
3184 |
|
3185 if ( !events[ evtName ] ) { |
|
3186 |
|
3187 events[ evtName ] = []; |
|
3188 } |
|
3189 |
|
3190 events[ evtName ].push( fn ); |
|
3191 return fn; |
|
3192 }; |
|
3193 |
|
3194 // Removes an event listener from the object |
|
3195 basePlayer.removeEventListener = function( evtName, fn ) { |
|
3196 |
|
3197 var i, |
|
3198 listeners = events[ evtName ]; |
|
3199 |
|
3200 if ( !listeners ){ |
|
3201 |
|
3202 return; |
|
3203 } |
|
3204 |
|
3205 // walk backwards so we can safely splice |
|
3206 for ( i = events[ evtName ].length - 1; i >= 0; i-- ) { |
|
3207 |
|
3208 if( fn === listeners[ i ] ) { |
|
3209 |
|
3210 listeners.splice(i, 1); |
|
3211 } |
|
3212 } |
|
3213 |
|
3214 return fn; |
|
3215 }; |
|
3216 |
|
3217 // Can take event object or simple string |
|
3218 basePlayer.dispatchEvent = function( oEvent ) { |
|
3219 |
|
3220 var evt, |
|
3221 self = this, |
|
3222 eventInterface, |
|
3223 eventName = oEvent.type; |
|
3224 |
|
3225 // A string was passed, create event object |
|
3226 if ( !eventName ) { |
|
3227 |
|
3228 eventName = oEvent; |
|
3229 eventInterface = Popcorn.events.getInterface( eventName ); |
|
3230 |
|
3231 if ( eventInterface ) { |
|
3232 |
|
3233 evt = document.createEvent( eventInterface ); |
|
3234 evt.initEvent( eventName, true, true, window, 1 ); |
|
3235 } |
|
3236 } |
|
3237 |
|
3238 if ( events[ eventName ] ) { |
|
3239 |
|
3240 for ( var i = events[ eventName ].length - 1; i >= 0; i-- ) { |
|
3241 |
|
3242 events[ eventName ][ i ].call( self, evt, self ); |
|
3243 } |
|
3244 } |
|
3245 }; |
|
3246 |
|
3247 // Attempt to get src from playerFn parameter |
|
3248 basePlayer.src = src || ""; |
|
3249 basePlayer.duration = 0; |
|
3250 basePlayer.paused = true; |
|
3251 basePlayer.ended = 0; |
|
3252 |
|
3253 options && options.events && Popcorn.forEach( options.events, function( val, key ) { |
|
3254 |
|
3255 basePlayer.addEventListener( key, val, false ); |
|
3256 }); |
|
3257 |
|
3258 // true and undefined returns on canPlayType means we should attempt to use it, |
|
3259 // false means we cannot play this type |
|
3260 if ( player._canPlayType( container.nodeName, src ) !== false ) { |
|
3261 |
|
3262 if ( player._setup ) { |
|
3263 |
|
3264 player._setup.call( basePlayer, options ); |
|
3265 } else { |
|
3266 |
|
3267 // there is no setup, which means there is nothing to load |
|
3268 basePlayer.readyState = 4; |
|
3269 basePlayer.dispatchEvent( "loadedmetadata" ); |
|
3270 basePlayer.dispatchEvent( "loadeddata" ); |
|
3271 basePlayer.dispatchEvent( "canplaythrough" ); |
|
3272 } |
|
3273 } else { |
|
3274 |
|
3275 // Asynchronous so that users can catch this event |
|
3276 setTimeout( function() { |
|
3277 basePlayer.dispatchEvent( "error" ); |
|
3278 }, 0 ); |
|
3279 } |
|
3280 |
|
3281 popcorn = new Popcorn.p.init( basePlayer, options ); |
|
3282 |
|
3283 if ( player._teardown ) { |
|
3284 |
|
3285 popcorn.destroy = combineFn( popcorn.destroy, function() { |
|
3286 |
|
3287 player._teardown.call( basePlayer, options ); |
|
3288 }); |
|
3289 } |
|
3290 |
|
3291 return popcorn; |
|
3292 }; |
|
3293 |
|
3294 playerFn.canPlayType = player._canPlayType = player._canPlayType || Popcorn.nop; |
|
3295 |
|
3296 Popcorn[ name ] = Popcorn.player.registry[ name ] = playerFn; |
|
3297 }; |
|
3298 |
|
3299 Popcorn.player.registry = {}; |
|
3300 |
|
3301 Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) { |
|
3302 |
|
3303 object.__defineGetter__( description, options.get || Popcorn.nop ); |
|
3304 object.__defineSetter__( description, options.set || Popcorn.nop ); |
|
3305 }; |
|
3306 |
|
3307 // player queue is to help players queue things like play and pause |
|
3308 // HTML5 video's play and pause are asynch, but do fire in sequence |
|
3309 // play() should really mean "requestPlay()" or "queuePlay()" and |
|
3310 // stash a callback that will play the media resource when it's ready to be played |
|
3311 Popcorn.player.playerQueue = function() { |
|
3312 |
|
3313 var _queue = [], |
|
3314 _running = false; |
|
3315 |
|
3316 return { |
|
3317 next: function() { |
|
3318 |
|
3319 _running = false; |
|
3320 _queue.shift(); |
|
3321 _queue[ 0 ] && _queue[ 0 ](); |
|
3322 }, |
|
3323 add: function( callback ) { |
|
3324 |
|
3325 _queue.push(function() { |
|
3326 |
|
3327 _running = true; |
|
3328 callback && callback(); |
|
3329 }); |
|
3330 |
|
3331 // if there is only one item on the queue, start it |
|
3332 !_running && _queue[ 0 ](); |
|
3333 } |
|
3334 }; |
|
3335 }; |
|
3336 |
|
3337 // smart will attempt to find you a match, if it does not find a match, |
|
3338 // it will attempt to create a video element with the source, |
|
3339 // if that failed, it will throw. |
|
3340 Popcorn.smart = function( target, src, options ) { |
|
3341 var playerType, |
|
3342 elementTypes = [ "AUDIO", "VIDEO" ], |
|
3343 sourceNode, |
|
3344 firstSrc, |
|
3345 node = Popcorn.dom.find( target ), |
|
3346 i, srcResult, |
|
3347 canPlayTypeTester = document.createElement( "video" ), |
|
3348 canPlayTypes = { |
|
3349 "ogg": "video/ogg", |
|
3350 "ogv": "video/ogg", |
|
3351 "oga": "audio/ogg", |
|
3352 "webm": "video/webm", |
|
3353 "mp4": "video/mp4", |
|
3354 "mp3": "audio/mp3" |
|
3355 }; |
|
3356 |
|
3357 var canPlayType = function( type ) { |
|
3358 |
|
3359 return canPlayTypeTester.canPlayType( canPlayTypes[ type ] ); |
|
3360 }; |
|
3361 |
|
3362 var canPlaySrc = function( src ) { |
|
3363 |
|
3364 srcResult = mediaExtensionRegexp.exec( src ); |
|
3365 |
|
3366 if ( !srcResult || !srcResult[ 1 ] ) { |
|
3367 return false; |
|
3368 } |
|
3369 |
|
3370 return canPlayType( srcResult[ 1 ] ); |
|
3371 }; |
|
3372 |
|
3373 if ( !node ) { |
|
3374 |
|
3375 Popcorn.error( "Specified target " + target + " was not found." ); |
|
3376 return; |
|
3377 } |
|
3378 |
|
3379 // For when no src is defined. |
|
3380 // Usually this is a video element with a src already on it. |
|
3381 if ( elementTypes.indexOf( node.nodeName ) > -1 && !src ) { |
|
3382 |
|
3383 if ( typeof src === "object" ) { |
|
3384 |
|
3385 options = src; |
|
3386 src = undefined; |
|
3387 } |
|
3388 |
|
3389 return Popcorn( node, options ); |
|
3390 } |
|
3391 |
|
3392 // if our src is not an array, create an array of one. |
|
3393 if ( typeof( src ) === "string" ) { |
|
3394 |
|
3395 src = [ src ]; |
|
3396 } |
|
3397 |
|
3398 // go through each src, and find the first playable. |
|
3399 // this only covers player sources popcorn knows of, |
|
3400 // and not things like a youtube src that is private. |
|
3401 // it will still consider a private youtube video to be playable. |
|
3402 for ( i = 0, srcLength = src.length; i < srcLength; i++ ) { |
|
3403 |
|
3404 // src is a playable HTML5 video, we don't need to check custom players. |
|
3405 if ( canPlaySrc( src[ i ] ) ) { |
|
3406 |
|
3407 src = src[ i ]; |
|
3408 break; |
|
3409 } |
|
3410 |
|
3411 // for now we loop through and use the first valid player we find. |
|
3412 for ( var key in Popcorn.player.registry ) { |
|
3413 |
|
3414 if ( Popcorn.player.registry.hasOwnProperty( key ) ) { |
|
3415 |
|
3416 if ( Popcorn.player.registry[ key ].canPlayType( node.nodeName, src[ i ] ) ) { |
|
3417 |
|
3418 // Popcorn.smart( player, src, /* options */ ) |
|
3419 return Popcorn[ key ]( node, src[ i ], options ); |
|
3420 } |
|
3421 } |
|
3422 } |
|
3423 } |
|
3424 |
|
3425 // Popcorn.smart( div, src, /* options */ ) |
|
3426 // attempting to create a video in a container |
|
3427 if ( elementTypes.indexOf( node.nodeName ) === -1 ) { |
|
3428 |
|
3429 firstSrc = typeof( src ) === "string" ? src : src.length ? src[ 0 ] : src; |
|
3430 |
|
3431 target = document.createElement( !!audioExtensionRegexp.exec( firstSrc ) ? elementTypes[ 0 ] : elementTypes[ 1 ] ); |
|
3432 |
|
3433 // Controls are defaulted to being present |
|
3434 target.controls = true; |
|
3435 |
|
3436 node.appendChild( target ); |
|
3437 node = target; |
|
3438 } |
|
3439 |
|
3440 options && options.events && options.events.error && node.addEventListener( "error", options.events.error, false ); |
|
3441 node.src = src; |
|
3442 |
|
3443 return Popcorn( node, options ); |
|
3444 |
|
3445 }; |
|
3446 })( Popcorn ); |
|
3447 // PLUGIN: mediaspawner |
|
3448 /** |
|
3449 * mediaspawner Popcorn Plugin. |
|
3450 * Adds Video/Audio to the page using Popcorns players |
|
3451 * Start is the time that you want this plug-in to execute |
|
3452 * End is the time that you want this plug-in to stop executing |
|
3453 * |
|
3454 * @param {HTML} options |
|
3455 * |
|
3456 * Example: |
|
3457 var p = Popcorn('#video') |
|
3458 .mediaspawner( { |
|
3459 source: "http://www.youtube.com/watch?v=bUB1L3zGVvc", |
|
3460 target: "mediaspawnerdiv", |
|
3461 start: 1, |
|
3462 end: 10, |
|
3463 caption: "This is a test. We are assuming conrol. We are assuming control." |
|
3464 }) |
|
3465 * |
|
3466 */ |
|
3467 (function ( Popcorn, global ) { |
|
3468 var PLAYER_URL = "http://popcornjs.org/code/modules/player/popcorn.player.js", |
|
3469 urlRegex = /(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu|vimeo|soundcloud|baseplayer)/, |
|
3470 forEachPlayer, |
|
3471 playerTypeLoading = {}, |
|
3472 playerTypesLoaded = { |
|
3473 "vimeo": false, |
|
3474 "youtube": false, |
|
3475 "soundcloud": false, |
|
3476 "module": false |
|
3477 }; |
|
3478 |
|
3479 Object.defineProperty( playerTypeLoading, forEachPlayer, { |
|
3480 get: function() { |
|
3481 return playerTypesLoaded[ forEachPlayer ]; |
|
3482 }, |
|
3483 set: function( val ) { |
|
3484 playerTypesLoaded[ forEachPlayer ] = val; |
|
3485 } |
|
3486 }); |
|
3487 |
|
3488 Popcorn.plugin( "mediaspawner", { |
|
3489 manifest: { |
|
3490 about: { |
|
3491 name: "Popcorn Media Spawner Plugin", |
|
3492 version: "0.1", |
|
3493 author: "Matthew Schranz, @mjschranz", |
|
3494 website: "mschranz.wordpress.com" |
|
3495 }, |
|
3496 options: { |
|
3497 source: { |
|
3498 elem: "input", |
|
3499 type: "text", |
|
3500 label: "Media Source", |
|
3501 "default": "http://www.youtube.com/watch?v=CXDstfD9eJ0" |
|
3502 }, |
|
3503 caption: { |
|
3504 elem: "input", |
|
3505 type: "text", |
|
3506 label: "Media Caption", |
|
3507 "default": "Popcorn Popping", |
|
3508 optional: true |
|
3509 }, |
|
3510 target: "mediaspawner-container", |
|
3511 start: { |
|
3512 elem: "input", |
|
3513 type: "number", |
|
3514 label: "Start" |
|
3515 }, |
|
3516 end: { |
|
3517 elem: "input", |
|
3518 type: "number", |
|
3519 label: "End" |
|
3520 }, |
|
3521 autoplay: { |
|
3522 elem: "input", |
|
3523 type: "checkbox", |
|
3524 label: "Autoplay Video", |
|
3525 optional: true |
|
3526 }, |
|
3527 width: { |
|
3528 elem: "input", |
|
3529 type: "number", |
|
3530 label: "Media Width", |
|
3531 "default": 400, |
|
3532 units: "px", |
|
3533 optional: true |
|
3534 }, |
|
3535 height: { |
|
3536 elem: "input", |
|
3537 type: "number", |
|
3538 label: "Media Height", |
|
3539 "default": 200, |
|
3540 units: "px", |
|
3541 optional: true |
|
3542 } |
|
3543 } |
|
3544 }, |
|
3545 _setup: function( options ) { |
|
3546 var target = document.getElementById( options.target ) || {}, |
|
3547 mediaType, |
|
3548 container, |
|
3549 capContainer, |
|
3550 regexResult; |
|
3551 |
|
3552 regexResult = urlRegex.exec( options.source ); |
|
3553 if ( regexResult ) { |
|
3554 mediaType = regexResult[ 1 ]; |
|
3555 // our regex only handles youtu ( incase the url looks something like youtu.be ) |
|
3556 if ( mediaType === "youtu" ) { |
|
3557 mediaType = "youtube"; |
|
3558 } |
|
3559 } |
|
3560 else { |
|
3561 // if the regex didn't return anything we know it's an HTML5 source |
|
3562 mediaType = "HTML5"; |
|
3563 } |
|
3564 |
|
3565 // Store Reference to Type for use in end |
|
3566 options._type = mediaType; |
|
3567 |
|
3568 // Create separate container for plugin |
|
3569 options._container = document.createElement( "div" ); |
|
3570 container = options._container; |
|
3571 container.id = "mediaSpawnerdiv-" + Popcorn.guid(); |
|
3572 |
|
3573 // Default width and height of media |
|
3574 options.width = options.width || 400; |
|
3575 options.height = options.height || 200; |
|
3576 |
|
3577 // Captions now need to be in their own container, due to the problem with flash players |
|
3578 // described in start/end |
|
3579 if ( options.caption ) { |
|
3580 capContainer = document.createElement( "div" ); |
|
3581 capContainer.innerHTML = options.caption; |
|
3582 capContainer.style.display = "none"; |
|
3583 options._capCont = capContainer; |
|
3584 container.appendChild( capContainer ); |
|
3585 } |
|
3586 |
|
3587 target && target.appendChild( container ); |
|
3588 |
|
3589 function constructMedia(){ |
|
3590 |
|
3591 function checkPlayerTypeLoaded() { |
|
3592 if ( mediaType !== "HTML5" && !window.Popcorn[ mediaType ] ) { |
|
3593 setTimeout( function() { |
|
3594 checkPlayerTypeLoaded(); |
|
3595 }, 300 ); |
|
3596 } else { |
|
3597 options.id = options._container.id; |
|
3598 // Set the width/height of the container before calling Popcorn.smart |
|
3599 // Allows youtube to pickup on the specified height an create the player |
|
3600 // with specified dimensions |
|
3601 options._container.style.width = options.width + "px"; |
|
3602 options._container.style.height = options.height + "px"; |
|
3603 options.popcorn = Popcorn.smart( "#" + options.id, options.source ); |
|
3604 |
|
3605 if ( mediaType === "HTML5" ) { |
|
3606 options.popcorn.controls( true ); |
|
3607 } |
|
3608 |
|
3609 // Set them to 0 now so it is hidden |
|
3610 options._container.style.width = "0px"; |
|
3611 options._container.style.height = "0px"; |
|
3612 options._container.style.visibility = "hidden"; |
|
3613 options._container.style.overflow = "hidden"; |
|
3614 } |
|
3615 } |
|
3616 |
|
3617 if ( mediaType !== "HTML5" && !window.Popcorn[ mediaType ] && !playerTypeLoading[ mediaType ] ) { |
|
3618 playerTypeLoading[ mediaType ] = true; |
|
3619 Popcorn.getScript( "http://popcornjs.org/code/players/" + mediaType + "/popcorn." + mediaType + ".js", function() { |
|
3620 checkPlayerTypeLoaded(); |
|
3621 }); |
|
3622 } |
|
3623 else { |
|
3624 checkPlayerTypeLoaded(); |
|
3625 } |
|
3626 |
|
3627 } |
|
3628 |
|
3629 // If Player script needed to be loaded, keep checking until it is and then fire readycallback |
|
3630 function isPlayerReady() { |
|
3631 if ( !window.Popcorn.player ) { |
|
3632 setTimeout( function () { |
|
3633 isPlayerReady(); |
|
3634 }, 300 ); |
|
3635 } else { |
|
3636 constructMedia(); |
|
3637 } |
|
3638 } |
|
3639 |
|
3640 // If player script isn't present, retrieve script |
|
3641 if ( !window.Popcorn.player && !playerTypeLoading.module ) { |
|
3642 playerTypeLoading.module = true; |
|
3643 Popcorn.getScript( PLAYER_URL, isPlayerReady ); |
|
3644 } else { |
|
3645 isPlayerReady(); |
|
3646 } |
|
3647 |
|
3648 }, |
|
3649 start: function( event, options ) { |
|
3650 if( options._capCont ) { |
|
3651 options._capCont.style.display = ""; |
|
3652 } |
|
3653 |
|
3654 /* Using this style for Start/End is required because of the flash players |
|
3655 * Without it on end an internal cleanup is called, causing the flash players |
|
3656 * to be out of sync with Popcorn, as they are then rebuilt. |
|
3657 */ |
|
3658 options._container.style.width = options.width + "px"; |
|
3659 options._container.style.height = options.height + "px"; |
|
3660 options._container.style.visibility = "visible"; |
|
3661 options._container.style.overflow = "visible"; |
|
3662 |
|
3663 if ( options.autoplay ) { |
|
3664 options.popcorn.play(); |
|
3665 } |
|
3666 }, |
|
3667 end: function( event, options ) { |
|
3668 if( options._capCont ) { |
|
3669 options._capCont.style.display = "none"; |
|
3670 } |
|
3671 |
|
3672 /* Using this style for Start/End is required because of the flash players |
|
3673 * Without it on end an internal cleanup is called, causing the flash players |
|
3674 * to be out of sync with Popcorn, as they are then rebuilt. |
|
3675 */ |
|
3676 options._container.style.width = "0px"; |
|
3677 options._container.style.height = "0px"; |
|
3678 options._container.style.visibility = "hidden"; |
|
3679 options._container.style.overflow = "hidden"; |
|
3680 |
|
3681 // Pause all popcorn instances on exit |
|
3682 options.popcorn.pause(); |
|
3683 |
|
3684 }, |
|
3685 _teardown: function( options ) { |
|
3686 if ( options.popcorn && options.popcorn.destory ) { |
|
3687 options.popcorn.destroy(); |
|
3688 } |
|
3689 document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._container ); |
|
3690 } |
|
3691 }); |
|
3692 })( Popcorn, this ); |
|
3693 // PLUGIN: Code |
|
3694 |
|
3695 (function ( Popcorn ) { |
|
3696 |
|
3697 /** |
|
3698 * Code Popcorn Plug-in |
|
3699 * |
|
3700 * Adds the ability to run arbitrary code (JavaScript functions) according to video timing. |
|
3701 * |
|
3702 * @param {Object} options |
|
3703 * |
|
3704 * Required parameters: start, end, template, data, and target. |
|
3705 * Optional parameter: static. |
|
3706 * |
|
3707 * start: the time in seconds when the mustache template should be rendered |
|
3708 * in the target div. |
|
3709 * |
|
3710 * end: the time in seconds when the rendered mustache template should be |
|
3711 * removed from the target div. |
|
3712 * |
|
3713 * onStart: the function to be run when the start time is reached. |
|
3714 * |
|
3715 * onFrame: [optional] a function to be run on each paint call |
|
3716 * (e.g., called ~60 times per second) between the start and end times. |
|
3717 * |
|
3718 * onEnd: [optional] a function to be run when the end time is reached. |
|
3719 * |
|
3720 * Example: |
|
3721 var p = Popcorn('#video') |
|
3722 |
|
3723 // onStart function only |
|
3724 .code({ |
|
3725 start: 1, |
|
3726 end: 4, |
|
3727 onStart: function( options ) { |
|
3728 // called on start |
|
3729 } |
|
3730 }) |
|
3731 |
|
3732 // onStart + onEnd only |
|
3733 .code({ |
|
3734 start: 6, |
|
3735 end: 8, |
|
3736 onStart: function( options ) { |
|
3737 // called on start |
|
3738 }, |
|
3739 onEnd: function ( options ) { |
|
3740 // called on end |
|
3741 } |
|
3742 }) |
|
3743 |
|
3744 // onStart, onEnd, onFrame |
|
3745 .code({ |
|
3746 start: 10, |
|
3747 end: 14, |
|
3748 onStart: function( options ) { |
|
3749 // called on start |
|
3750 }, |
|
3751 onFrame: function ( options ) { |
|
3752 // called on every paint frame between start and end. |
|
3753 // uses mozRequestAnimationFrame, webkitRequestAnimationFrame, |
|
3754 // or setTimeout with 16ms window. |
|
3755 }, |
|
3756 onEnd: function ( options ) { |
|
3757 // called on end |
|
3758 } |
|
3759 }); |
|
3760 * |
|
3761 */ |
|
3762 |
|
3763 Popcorn.plugin( "code" , function( options ) { |
|
3764 var running = false, |
|
3765 instance = this; |
|
3766 |
|
3767 // Setup a proper frame interval function (60fps), favouring paint events. |
|
3768 var step = (function() { |
|
3769 |
|
3770 var buildFrameRunner = function( runner ) { |
|
3771 return function( f, options ) { |
|
3772 |
|
3773 var _f = function() { |
|
3774 running && f.call( instance, options ); |
|
3775 running && runner( _f ); |
|
3776 }; |
|
3777 |
|
3778 _f(); |
|
3779 }; |
|
3780 }; |
|
3781 |
|
3782 // Figure out which level of browser support we have for this |
|
3783 if ( window.webkitRequestAnimationFrame ) { |
|
3784 return buildFrameRunner( window.webkitRequestAnimationFrame ); |
|
3785 } else if ( window.mozRequestAnimationFrame ) { |
|
3786 return buildFrameRunner( window.mozRequestAnimationFrame ); |
|
3787 } else { |
|
3788 return buildFrameRunner( function( f ) { |
|
3789 window.setTimeout( f, 16 ); |
|
3790 }); |
|
3791 } |
|
3792 |
|
3793 })(); |
|
3794 |
|
3795 if ( !options.onStart || typeof options.onStart !== "function" ) { |
|
3796 |
|
3797 options.onStart = Popcorn.nop; |
|
3798 } |
|
3799 |
|
3800 if ( options.onEnd && typeof options.onEnd !== "function" ) { |
|
3801 |
|
3802 options.onEnd = undefined; |
|
3803 } |
|
3804 |
|
3805 if ( options.onFrame && typeof options.onFrame !== "function" ) { |
|
3806 |
|
3807 options.onFrame = undefined; |
|
3808 } |
|
3809 |
|
3810 return { |
|
3811 start: function( event, options ) { |
|
3812 options.onStart.call( instance, options ); |
|
3813 |
|
3814 if ( options.onFrame ) { |
|
3815 running = true; |
|
3816 step( options.onFrame, options ); |
|
3817 } |
|
3818 }, |
|
3819 |
|
3820 end: function( event, options ) { |
|
3821 if ( options.onFrame ) { |
|
3822 running = false; |
|
3823 } |
|
3824 |
|
3825 if ( options.onEnd ) { |
|
3826 options.onEnd.call( instance, options ); |
|
3827 } |
|
3828 } |
|
3829 }; |
|
3830 }, |
|
3831 { |
|
3832 about: { |
|
3833 name: "Popcorn Code Plugin", |
|
3834 version: "0.1", |
|
3835 author: "David Humphrey (@humphd)", |
|
3836 website: "http://vocamus.net/dave" |
|
3837 }, |
|
3838 options: { |
|
3839 start: { |
|
3840 elem: "input", |
|
3841 type: "number", |
|
3842 label: "Start" |
|
3843 }, |
|
3844 end: { |
|
3845 elem: "input", |
|
3846 type: "number", |
|
3847 label: "End" |
|
3848 }, |
|
3849 onStart: { |
|
3850 elem: "input", |
|
3851 type: "function", |
|
3852 label: "onStart" |
|
3853 }, |
|
3854 onFrame: { |
|
3855 elem: "input", |
|
3856 type: "function", |
|
3857 label: "onFrame", |
|
3858 optional: true |
|
3859 }, |
|
3860 onEnd: { |
|
3861 elem: "input", |
|
3862 type: "function", |
|
3863 label: "onEnd" |
|
3864 } |
|
3865 } |
|
3866 }); |
|
3867 })( Popcorn ); |
|
3868 // PLUGIN: Flickr |
|
3869 (function (Popcorn) { |
|
3870 |
|
3871 /** |
|
3872 * Flickr popcorn plug-in |
|
3873 * Appends a users Flickr images to an element on the page. |
|
3874 * Options parameter will need a start, end, target and userid or username and api_key. |
|
3875 * Optional parameters are numberofimages, height, width, padding, and border |
|
3876 * Start is the time that you want this plug-in to execute (in seconds) |
|
3877 * End is the time that you want this plug-in to stop executing (in seconds) |
|
3878 * Userid is the id of who's Flickr images you wish to show |
|
3879 * Tags is a mutually exclusive list of image descriptor tags |
|
3880 * Username is the username of who's Flickr images you wish to show |
|
3881 * using both userid and username is redundant |
|
3882 * an api_key is required when using username |
|
3883 * Apikey is your own api key provided by Flickr |
|
3884 * Target is the id of the document element that the images are |
|
3885 * appended to, this target element must exist on the DOM |
|
3886 * Numberofimages specify the number of images to retreive from flickr, defaults to 4 |
|
3887 * Height the height of the image, defaults to '50px' |
|
3888 * Width the width of the image, defaults to '50px' |
|
3889 * Padding number of pixels between images, defaults to '5px' |
|
3890 * Border border size in pixels around images, defaults to '0px' |
|
3891 * |
|
3892 * @param {Object} options |
|
3893 * |
|
3894 * Example: |
|
3895 var p = Popcorn('#video') |
|
3896 .flickr({ |
|
3897 start: 5, // seconds, mandatory |
|
3898 end: 15, // seconds, mandatory |
|
3899 userid: '35034346917@N01', // optional |
|
3900 tags: 'dogs,cats', // optional |
|
3901 numberofimages: '8', // optional |
|
3902 height: '50px', // optional |
|
3903 width: '50px', // optional |
|
3904 padding: '5px', // optional |
|
3905 border: '0px', // optional |
|
3906 target: 'flickrdiv' // mandatory |
|
3907 } ) |
|
3908 * |
|
3909 */ |
|
3910 |
|
3911 var idx = 0; |
|
3912 |
|
3913 Popcorn.plugin( "flickr" , function( options ) { |
|
3914 var containerDiv, |
|
3915 target = document.getElementById( options.target ), |
|
3916 _userid, |
|
3917 _uri, |
|
3918 _link, |
|
3919 _image, |
|
3920 _count = options.numberofimages || 4, |
|
3921 _height = options.height || "50px", |
|
3922 _width = options.width || "50px", |
|
3923 _padding = options.padding || "5px", |
|
3924 _border = options.border || "0px"; |
|
3925 |
|
3926 // create a new div this way anything in the target div is left intact |
|
3927 // this is later populated with Flickr images |
|
3928 containerDiv = document.createElement( "div" ); |
|
3929 containerDiv.id = "flickr" + idx; |
|
3930 containerDiv.style.width = "100%"; |
|
3931 containerDiv.style.height = "100%"; |
|
3932 containerDiv.style.display = "none"; |
|
3933 idx++; |
|
3934 |
|
3935 target && target.appendChild( containerDiv ); |
|
3936 |
|
3937 // get the userid from Flickr API by using the username and apikey |
|
3938 var isUserIDReady = function() { |
|
3939 if ( !_userid ) { |
|
3940 |
|
3941 _uri = "http://api.flickr.com/services/rest/?method=flickr.people.findByUsername&"; |
|
3942 _uri += "username=" + options.username + "&api_key=" + options.apikey + "&format=json&jsoncallback=flickr"; |
|
3943 Popcorn.getJSONP( _uri, function( data ) { |
|
3944 _userid = data.user.nsid; |
|
3945 getFlickrData(); |
|
3946 }); |
|
3947 |
|
3948 } else { |
|
3949 |
|
3950 setTimeout(function () { |
|
3951 isUserIDReady(); |
|
3952 }, 5 ); |
|
3953 } |
|
3954 }; |
|
3955 |
|
3956 // get the photos from Flickr API by using the user_id and/or tags |
|
3957 var getFlickrData = function() { |
|
3958 |
|
3959 _uri = "http://api.flickr.com/services/feeds/photos_public.gne?"; |
|
3960 |
|
3961 if ( _userid ) { |
|
3962 _uri += "id=" + _userid + "&"; |
|
3963 } |
|
3964 if ( options.tags ) { |
|
3965 _uri += "tags=" + options.tags + "&"; |
|
3966 } |
|
3967 |
|
3968 _uri += "lang=en-us&format=json&jsoncallback=flickr"; |
|
3969 |
|
3970 Popcorn.xhr.getJSONP( _uri, function( data ) { |
|
3971 |
|
3972 var fragment = document.createElement( "div" ); |
|
3973 |
|
3974 fragment.innerHTML = "<p style='padding:" + _padding + ";'>" + data.title + "<p/>"; |
|
3975 |
|
3976 Popcorn.forEach( data.items, function ( item, i ) { |
|
3977 if ( i < _count ) { |
|
3978 |
|
3979 _link = document.createElement( "a" ); |
|
3980 _link.setAttribute( "href", item.link ); |
|
3981 _link.setAttribute( "target", "_blank" ); |
|
3982 _image = document.createElement( "img" ); |
|
3983 _image.setAttribute( "src", item.media.m ); |
|
3984 _image.setAttribute( "height",_height ); |
|
3985 _image.setAttribute( "width", _width ); |
|
3986 _image.setAttribute( "style", "border:" + _border + ";padding:" + _padding ); |
|
3987 _link.appendChild( _image ); |
|
3988 fragment.appendChild( _link ); |
|
3989 |
|
3990 } else { |
|
3991 |
|
3992 return false; |
|
3993 } |
|
3994 }); |
|
3995 |
|
3996 containerDiv.appendChild( fragment ); |
|
3997 }); |
|
3998 }; |
|
3999 |
|
4000 if ( options.username && options.apikey ) { |
|
4001 isUserIDReady(); |
|
4002 } |
|
4003 else { |
|
4004 _userid = options.userid; |
|
4005 getFlickrData(); |
|
4006 } |
|
4007 return { |
|
4008 /** |
|
4009 * @member flickr |
|
4010 * The start function will be executed when the currentTime |
|
4011 * of the video reaches the start time provided by the |
|
4012 * options variable |
|
4013 */ |
|
4014 start: function( event, options ) { |
|
4015 containerDiv.style.display = "inline"; |
|
4016 }, |
|
4017 /** |
|
4018 * @member flickr |
|
4019 * The end function will be executed when the currentTime |
|
4020 * of the video reaches the end time provided by the |
|
4021 * options variable |
|
4022 */ |
|
4023 end: function( event, options ) { |
|
4024 containerDiv.style.display = "none"; |
|
4025 }, |
|
4026 _teardown: function( options ) { |
|
4027 document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( containerDiv ); |
|
4028 } |
|
4029 }; |
|
4030 }, |
|
4031 { |
|
4032 about: { |
|
4033 name: "Popcorn Flickr Plugin", |
|
4034 version: "0.2", |
|
4035 author: "Scott Downe, Steven Weerdenburg, Annasob", |
|
4036 website: "http://scottdowne.wordpress.com/" |
|
4037 }, |
|
4038 options: { |
|
4039 start: { |
|
4040 elem: "input", |
|
4041 type: "number", |
|
4042 label: "Start" |
|
4043 }, |
|
4044 end: { |
|
4045 elem: "input", |
|
4046 type: "number", |
|
4047 label: "End" |
|
4048 }, |
|
4049 userid: { |
|
4050 elem: "input", |
|
4051 type: "text", |
|
4052 label: "User ID", |
|
4053 optional: true |
|
4054 }, |
|
4055 tags: { |
|
4056 elem: "input", |
|
4057 type: "text", |
|
4058 label: "Tags" |
|
4059 }, |
|
4060 username: { |
|
4061 elem: "input", |
|
4062 type: "text", |
|
4063 label: "Username", |
|
4064 optional: true |
|
4065 }, |
|
4066 apikey: { |
|
4067 elem: "input", |
|
4068 type: "text", |
|
4069 label: "API Key", |
|
4070 optional: true |
|
4071 }, |
|
4072 target: "flickr-container", |
|
4073 height: { |
|
4074 elem: "input", |
|
4075 type: "text", |
|
4076 label: "Height", |
|
4077 "default": "50px", |
|
4078 optional: true |
|
4079 }, |
|
4080 width: { |
|
4081 elem: "input", |
|
4082 type: "text", |
|
4083 label: "Width", |
|
4084 "default": "50px", |
|
4085 optional: true |
|
4086 }, |
|
4087 padding: { |
|
4088 elem: "input", |
|
4089 type: "text", |
|
4090 label: "Padding", |
|
4091 optional: true |
|
4092 }, |
|
4093 border: { |
|
4094 elem: "input", |
|
4095 type: "text", |
|
4096 label: "Border", |
|
4097 "default": "5px", |
|
4098 optional: true |
|
4099 }, |
|
4100 numberofimages: { |
|
4101 elem: "input", |
|
4102 type: "number", |
|
4103 "default": 4, |
|
4104 label: "Number of Images" |
|
4105 } |
|
4106 } |
|
4107 }); |
|
4108 })( Popcorn ); |
|
4109 // PLUGIN: Footnote/Text |
|
4110 |
|
4111 (function ( Popcorn ) { |
|
4112 |
|
4113 /** |
|
4114 * Footnote popcorn plug-in |
|
4115 * Adds text to an element on the page. |
|
4116 * Options parameter will need a start, end, target and text. |
|
4117 * Start is the time that you want this plug-in to execute |
|
4118 * End is the time that you want this plug-in to stop executing |
|
4119 * Text is the text that you want to appear in the target |
|
4120 * Target is the id of the document element that the text needs to be |
|
4121 * attached to, this target element must exist on the DOM |
|
4122 * |
|
4123 * @param {Object} options |
|
4124 * |
|
4125 * Example: |
|
4126 * var p = Popcorn('#video') |
|
4127 * .footnote({ |
|
4128 * start: 5, // seconds |
|
4129 * end: 15, // seconds |
|
4130 * text: 'This video made exclusively for drumbeat.org', |
|
4131 * target: 'footnotediv' |
|
4132 * }); |
|
4133 **/ |
|
4134 |
|
4135 Popcorn.plugin( "footnote", { |
|
4136 |
|
4137 manifest: { |
|
4138 about: { |
|
4139 name: "Popcorn Footnote Plugin", |
|
4140 version: "0.2", |
|
4141 author: "@annasob, @rwaldron", |
|
4142 website: "annasob.wordpress.com" |
|
4143 }, |
|
4144 options: { |
|
4145 start: { |
|
4146 elem: "input", |
|
4147 type: "number", |
|
4148 label: "Start" |
|
4149 }, |
|
4150 end: { |
|
4151 elem: "input", |
|
4152 type: "number", |
|
4153 label: "End" |
|
4154 }, |
|
4155 text: { |
|
4156 elem: "input", |
|
4157 type: "text", |
|
4158 label: "Text" |
|
4159 }, |
|
4160 target: "footnote-container" |
|
4161 } |
|
4162 }, |
|
4163 |
|
4164 _setup: function( options ) { |
|
4165 |
|
4166 var target = Popcorn.dom.find( options.target ); |
|
4167 |
|
4168 options._container = document.createElement( "div" ); |
|
4169 options._container.style.display = "none"; |
|
4170 options._container.innerHTML = options.text; |
|
4171 |
|
4172 target.appendChild( options._container ); |
|
4173 }, |
|
4174 |
|
4175 /** |
|
4176 * @member footnote |
|
4177 * The start function will be executed when the currentTime |
|
4178 * of the video reaches the start time provided by the |
|
4179 * options variable |
|
4180 */ |
|
4181 start: function( event, options ){ |
|
4182 options._container.style.display = "inline"; |
|
4183 }, |
|
4184 |
|
4185 /** |
|
4186 * @member footnote |
|
4187 * The end function will be executed when the currentTime |
|
4188 * of the video reaches the end time provided by the |
|
4189 * options variable |
|
4190 */ |
|
4191 end: function( event, options ){ |
|
4192 options._container.style.display = "none"; |
|
4193 }, |
|
4194 |
|
4195 _teardown: function( options ) { |
|
4196 var target = Popcorn.dom.find( options.target ); |
|
4197 if ( target ) { |
|
4198 target.removeChild( options._container ); |
|
4199 } |
|
4200 } |
|
4201 |
|
4202 }); |
|
4203 })( Popcorn ); |
|
4204 // PLUGIN: Text |
|
4205 |
|
4206 (function ( Popcorn ) { |
|
4207 |
|
4208 /** |
|
4209 * Text Popcorn plug-in |
|
4210 * |
|
4211 * Places text in an element on the page. Plugin options include: |
|
4212 * Options parameter will need a start, end. |
|
4213 * Start: Is the time that you want this plug-in to execute |
|
4214 * End: Is the time that you want this plug-in to stop executing |
|
4215 * Text: Is the text that you want to appear in the target |
|
4216 * Escape: {true|false} Whether to escape the text (e.g., html strings) |
|
4217 * Multiline: {true|false} Whether newlines should be turned into <br>s |
|
4218 * Target: Is the ID of the element where the text should be placed. An empty target |
|
4219 * will be placed on top of the media element |
|
4220 * |
|
4221 * @param {Object} options |
|
4222 * |
|
4223 * Example: |
|
4224 * var p = Popcorn('#video') |
|
4225 * |
|
4226 * // Simple text |
|
4227 * .text({ |
|
4228 * start: 5, // seconds |
|
4229 * end: 15, // seconds |
|
4230 * text: 'This video made exclusively for drumbeat.org', |
|
4231 * target: 'textdiv' |
|
4232 * }) |
|
4233 * |
|
4234 * // HTML text, rendered as HTML |
|
4235 * .text({ |
|
4236 * start: 15, // seconds |
|
4237 * end: 20, // seconds |
|
4238 * text: '<p>This video made <em>exclusively</em> for drumbeat.org</p>', |
|
4239 * target: 'textdiv' |
|
4240 * }) |
|
4241 * |
|
4242 * // HTML text, escaped and rendered as plain text |
|
4243 * .text({ |
|
4244 * start: 20, // seconds |
|
4245 * end: 25, // seconds |
|
4246 * text: 'This is an HTML p element: <p>paragraph</p>', |
|
4247 * escape: true, |
|
4248 * target: 'textdiv' |
|
4249 * }) |
|
4250 * |
|
4251 * // Multi-Line HTML text, escaped and rendered as plain text |
|
4252 * .text({ |
|
4253 * start: 25, // seconds |
|
4254 * end: 30, // seconds |
|
4255 * text: 'This is an HTML p element: <p>paragraph</p>\nThis is an HTML b element: <b>bold</b>', |
|
4256 * escape: true, |
|
4257 * multiline: true, |
|
4258 * target: 'textdiv' |
|
4259 * }); |
|
4260 * |
|
4261 * // Subtitle text |
|
4262 * .text({ |
|
4263 * start: 30, // seconds |
|
4264 * end: 40, // seconds |
|
4265 * text: 'This will be overlayed on the video', |
|
4266 * }) |
|
4267 **/ |
|
4268 |
|
4269 /** |
|
4270 * HTML escape code from mustache.js, used under MIT Licence |
|
4271 * https://github.com/janl/mustache.js/blob/master/mustache.js |
|
4272 **/ |
|
4273 var escapeMap = { |
|
4274 "&": "&", |
|
4275 "<": "<", |
|
4276 ">": ">", |
|
4277 '"': '"', |
|
4278 "'": ''' |
|
4279 }; |
|
4280 |
|
4281 function escapeHTML( string, multiline ) { |
|
4282 return String( string ).replace( /&(?!\w+;)|[<>"']/g, function ( s ) { |
|
4283 return escapeMap[ s ] || s; |
|
4284 }); |
|
4285 } |
|
4286 |
|
4287 function newlineToBreak( string ) { |
|
4288 // Deal with both \r\n and \n |
|
4289 return string.replace( /\r?\n/gm, "<br>" ); |
|
4290 } |
|
4291 |
|
4292 // Subtitle specific functionality |
|
4293 function createSubtitleContainer( context, id ) { |
|
4294 |
|
4295 var ctxContainer = context.container = document.createElement( "div" ), |
|
4296 style = ctxContainer.style, |
|
4297 media = context.media; |
|
4298 |
|
4299 var updatePosition = function() { |
|
4300 var position = context.position(); |
|
4301 // the video element must have height and width defined |
|
4302 style.fontSize = "18px"; |
|
4303 style.width = media.offsetWidth + "px"; |
|
4304 style.top = position.top + media.offsetHeight - ctxContainer.offsetHeight - 40 + "px"; |
|
4305 style.left = position.left + "px"; |
|
4306 |
|
4307 setTimeout( updatePosition, 10 ); |
|
4308 }; |
|
4309 |
|
4310 ctxContainer.id = id || ""; |
|
4311 style.position = "absolute"; |
|
4312 style.color = "white"; |
|
4313 style.textShadow = "black 2px 2px 6px"; |
|
4314 style.fontWeight = "bold"; |
|
4315 style.textAlign = "center"; |
|
4316 |
|
4317 updatePosition(); |
|
4318 |
|
4319 context.media.parentNode.appendChild( ctxContainer ); |
|
4320 |
|
4321 return ctxContainer; |
|
4322 } |
|
4323 |
|
4324 Popcorn.plugin( "text", { |
|
4325 |
|
4326 manifest: { |
|
4327 about: { |
|
4328 name: "Popcorn Text Plugin", |
|
4329 version: "0.1", |
|
4330 author: "@humphd" |
|
4331 }, |
|
4332 options: { |
|
4333 start: { |
|
4334 elem: "input", |
|
4335 type: "number", |
|
4336 label: "Start" |
|
4337 }, |
|
4338 end: { |
|
4339 elem: "input", |
|
4340 type: "number", |
|
4341 label: "End" |
|
4342 }, |
|
4343 text: { |
|
4344 elem: "input", |
|
4345 type: "text", |
|
4346 label: "Text", |
|
4347 "default": "Popcorn.js" |
|
4348 }, |
|
4349 escape: { |
|
4350 elem: "input", |
|
4351 type: "checkbox", |
|
4352 label: "Escape" |
|
4353 }, |
|
4354 multiline: { |
|
4355 elem: "input", |
|
4356 type: "checkbox", |
|
4357 label: "Multiline" |
|
4358 } |
|
4359 } |
|
4360 }, |
|
4361 |
|
4362 _setup: function( options ) { |
|
4363 |
|
4364 var target, |
|
4365 text, |
|
4366 container = options._container = document.createElement( "div" ); |
|
4367 |
|
4368 container.style.display = "none"; |
|
4369 |
|
4370 if ( options.target ) { |
|
4371 // Try to use supplied target |
|
4372 target = Popcorn.dom.find( options.target ); |
|
4373 |
|
4374 if ( !target ) { |
|
4375 target = createSubtitleContainer( this, options.target ); |
|
4376 } |
|
4377 else if ( [ "VIDEO", "AUDIO" ].indexOf( target.nodeName ) > -1 ) { |
|
4378 target = createSubtitleContainer( this, options.target + "-overlay" ); |
|
4379 } |
|
4380 |
|
4381 } else if ( !this.container ) { |
|
4382 // Creates a div for all subtitles to use |
|
4383 target = createSubtitleContainer( this ); |
|
4384 |
|
4385 } else { |
|
4386 // Use subtitle container |
|
4387 target = this.container; |
|
4388 } |
|
4389 |
|
4390 // cache reference to actual target container |
|
4391 options._target = target; |
|
4392 |
|
4393 // Escape HTML text if requested |
|
4394 text = !!options.escape ? escapeHTML( options.text ) : |
|
4395 options.text; |
|
4396 |
|
4397 // Swap newline for <br> if requested |
|
4398 text = !!options.multiline ? newlineToBreak ( text ) : text; |
|
4399 container.innerHTML = text || ""; |
|
4400 |
|
4401 target.appendChild( container ); |
|
4402 }, |
|
4403 |
|
4404 /** |
|
4405 * @member text |
|
4406 * The start function will be executed when the currentTime |
|
4407 * of the video reaches the start time provided by the |
|
4408 * options variable |
|
4409 */ |
|
4410 start: function( event, options ) { |
|
4411 options._container.style.display = "inline"; |
|
4412 }, |
|
4413 |
|
4414 /** |
|
4415 * @member text |
|
4416 * The end function will be executed when the currentTime |
|
4417 * of the video reaches the end time provided by the |
|
4418 * options variable |
|
4419 */ |
|
4420 end: function( event, options ) { |
|
4421 options._container.style.display = "none"; |
|
4422 }, |
|
4423 |
|
4424 _teardown: function( options ) { |
|
4425 var target = options._target; |
|
4426 if ( target ) { |
|
4427 target.removeChild( options._container ); |
|
4428 } |
|
4429 } |
|
4430 }); |
|
4431 })( Popcorn ); |
|
4432 |
|
4433 // PLUGIN: Google Maps |
|
4434 var googleCallback; |
|
4435 (function ( Popcorn ) { |
|
4436 |
|
4437 var i = 1, |
|
4438 _mapFired = false, |
|
4439 _mapLoaded = false, |
|
4440 geocoder, loadMaps; |
|
4441 //google api callback |
|
4442 googleCallback = function ( data ) { |
|
4443 // ensure all of the maps functions needed are loaded |
|
4444 // before setting _maploaded to true |
|
4445 if ( typeof google !== "undefined" && google.maps && google.maps.Geocoder && google.maps.LatLng ) { |
|
4446 geocoder = new google.maps.Geocoder(); |
|
4447 Popcorn.getScript( "//maps.stamen.com/js/tile.stamen.js", function() { |
|
4448 _mapLoaded = true; |
|
4449 }); |
|
4450 } else { |
|
4451 setTimeout(function () { |
|
4452 googleCallback( data ); |
|
4453 }, 1); |
|
4454 } |
|
4455 }; |
|
4456 // function that loads the google api |
|
4457 loadMaps = function () { |
|
4458 // for some reason the Google Map API adds content to the body |
|
4459 if ( document.body ) { |
|
4460 _mapFired = true; |
|
4461 Popcorn.getScript( "//maps.google.com/maps/api/js?sensor=false&callback=googleCallback" ); |
|
4462 } else { |
|
4463 setTimeout(function () { |
|
4464 loadMaps(); |
|
4465 }, 1); |
|
4466 } |
|
4467 }; |
|
4468 |
|
4469 function buildMap( options, location, mapDiv ) { |
|
4470 var type = options.type ? options.type.toUpperCase() : "HYBRID", |
|
4471 layer; |
|
4472 |
|
4473 // See if we need to make a custom Stamen map layer |
|
4474 if ( type === "STAMEN-WATERCOLOR" || |
|
4475 type === "STAMEN-TERRAIN" || |
|
4476 type === "STAMEN-TONER" ) { |
|
4477 // Stamen types are lowercase |
|
4478 layer = type.replace("STAMEN-", "").toLowerCase(); |
|
4479 } |
|
4480 |
|
4481 var map = new google.maps.Map( mapDiv, { |
|
4482 // If a custom layer was specified, use that, otherwise use type |
|
4483 mapTypeId: layer ? layer : google.maps.MapTypeId[ type ], |
|
4484 // Hide the layer selection UI |
|
4485 mapTypeControlOptions: { mapTypeIds: [] } |
|
4486 }); |
|
4487 |
|
4488 if ( layer ) { |
|
4489 map.mapTypes.set( layer, new google.maps.StamenMapType( layer )); |
|
4490 } |
|
4491 map.getDiv().style.display = "none"; |
|
4492 |
|
4493 return map; |
|
4494 } |
|
4495 |
|
4496 /** |
|
4497 * googlemap popcorn plug-in |
|
4498 * Adds a map to the target div centered on the location specified by the user |
|
4499 * Options parameter will need a start, end, target, type, zoom, lat and lng, and location |
|
4500 * -Start is the time that you want this plug-in to execute |
|
4501 * -End is the time that you want this plug-in to stop executing |
|
4502 * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM |
|
4503 * -Type [optional] either: HYBRID (default), ROADMAP, SATELLITE, TERRAIN, STREETVIEW, or one of the |
|
4504 * Stamen custom map types (http://http://maps.stamen.com): STAMEN-TONER, |
|
4505 * STAMEN-WATERCOLOR, or STAMEN-TERRAIN. |
|
4506 * -Zoom [optional] defaults to 0 |
|
4507 * -Heading [optional] STREETVIEW orientation of camera in degrees relative to true north (0 north, 90 true east, ect) |
|
4508 * -Pitch [optional] STREETVIEW vertical orientation of the camera (between 1 and 3 is recommended) |
|
4509 * -Lat and Lng: the coordinates of the map must be present if location is not specified. |
|
4510 * -Height [optional] the height of the map, in "px" or "%". Defaults to "100%". |
|
4511 * -Width [optional] the width of the map, in "px" or "%". Defaults to "100%". |
|
4512 * -Location: the adress you want the map to display, must be present if lat and lng are not specified. |
|
4513 * Note: using location requires extra loading time, also not specifying both lat/lng and location will |
|
4514 * cause and error. |
|
4515 * |
|
4516 * Tweening works using the following specifications: |
|
4517 * -location is the start point when using an auto generated route |
|
4518 * -tween when used in this context is a string which specifies the end location for your route |
|
4519 * Note that both location and tween must be present when using an auto generated route, or the map will not tween |
|
4520 * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds ) |
|
4521 * Heading, Zoom, and Pitch streetview values are also used in tweening with the autogenerated route |
|
4522 * |
|
4523 * -tween is an array of objects, each containing data for one frame of a tween |
|
4524 * -position is an object with has two paramaters, lat and lng, both which are mandatory for a tween to work |
|
4525 * -pov is an object which houses heading, pitch, and zoom paramters, which are all optional, if undefined, these values default to 0 |
|
4526 * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds ) |
|
4527 * |
|
4528 * @param {Object} options |
|
4529 * |
|
4530 * Example: |
|
4531 var p = Popcorn("#video") |
|
4532 .googlemap({ |
|
4533 start: 5, // seconds |
|
4534 end: 15, // seconds |
|
4535 type: "ROADMAP", |
|
4536 target: "map", |
|
4537 lat: 43.665429, |
|
4538 lng: -79.403323 |
|
4539 } ) |
|
4540 * |
|
4541 */ |
|
4542 Popcorn.plugin( "googlemap", function ( options ) { |
|
4543 var newdiv, map, location, |
|
4544 target = document.getElementById( options.target ); |
|
4545 |
|
4546 options.type = options.type || "ROADMAP"; |
|
4547 options.zoom = options.zoom || 1; |
|
4548 options.lat = options.lat || 0; |
|
4549 options.lng = options.lng || 0; |
|
4550 |
|
4551 // if this is the firest time running the plugins |
|
4552 // call the function that gets the sctipt |
|
4553 if ( !_mapFired ) { |
|
4554 loadMaps(); |
|
4555 } |
|
4556 |
|
4557 // create a new div this way anything in the target div is left intact |
|
4558 // this is later passed on to the maps api |
|
4559 newdiv = document.createElement( "div" ); |
|
4560 newdiv.id = "actualmap" + i; |
|
4561 newdiv.style.width = options.width || "100%"; |
|
4562 |
|
4563 // height is a little more complicated than width. |
|
4564 if ( options.height ) { |
|
4565 newdiv.style.height = options.height; |
|
4566 } else if ( target && target.clientHeight ) { |
|
4567 newdiv.style.height = target.clientHeight + "px"; |
|
4568 } else { |
|
4569 newdiv.style.height = "100%"; |
|
4570 } |
|
4571 |
|
4572 i++; |
|
4573 |
|
4574 target && target.appendChild( newdiv ); |
|
4575 |
|
4576 // ensure that google maps and its functions are loaded |
|
4577 // before setting up the map parameters |
|
4578 var isMapReady = function () { |
|
4579 if ( _mapLoaded ) { |
|
4580 if ( newdiv ) { |
|
4581 if ( options.location ) { |
|
4582 // calls an anonymous google function called on separate thread |
|
4583 geocoder.geocode({ |
|
4584 "address": options.location |
|
4585 }, function ( results, status ) { |
|
4586 // second check for newdiv since it could have disappeared before |
|
4587 // this callback is actual run |
|
4588 if ( newdiv && status === google.maps.GeocoderStatus.OK ) { |
|
4589 options.lat = results[ 0 ].geometry.location.lat(); |
|
4590 options.lng = results[ 0 ].geometry.location.lng(); |
|
4591 location = new google.maps.LatLng( options.lat, options.lng ); |
|
4592 map = buildMap( options, location, newdiv ); |
|
4593 } |
|
4594 }); |
|
4595 } else { |
|
4596 location = new google.maps.LatLng( options.lat, options.lng ); |
|
4597 map = buildMap( options, location, newdiv ); |
|
4598 } |
|
4599 } |
|
4600 } else { |
|
4601 setTimeout(function () { |
|
4602 isMapReady(); |
|
4603 }, 5); |
|
4604 } |
|
4605 }; |
|
4606 |
|
4607 isMapReady(); |
|
4608 |
|
4609 return { |
|
4610 /** |
|
4611 * @member webpage |
|
4612 * The start function will be executed when the currentTime |
|
4613 * of the video reaches the start time provided by the |
|
4614 * options variable |
|
4615 */ |
|
4616 start: function( event, options ) { |
|
4617 var that = this, |
|
4618 sView; |
|
4619 |
|
4620 // ensure the map has been initialized in the setup function above |
|
4621 var isMapSetup = function() { |
|
4622 if ( map ) { |
|
4623 options._map = map; |
|
4624 |
|
4625 map.getDiv().style.display = "block"; |
|
4626 // reset the location and zoom just in case the user plaid with the map |
|
4627 google.maps.event.trigger( map, "resize" ); |
|
4628 map.setCenter( location ); |
|
4629 |
|
4630 // make sure options.zoom is a number |
|
4631 if ( options.zoom && typeof options.zoom !== "number" ) { |
|
4632 options.zoom = +options.zoom; |
|
4633 } |
|
4634 |
|
4635 map.setZoom( options.zoom ); |
|
4636 |
|
4637 //Make sure heading is a number |
|
4638 if ( options.heading && typeof options.heading !== "number" ) { |
|
4639 options.heading = +options.heading; |
|
4640 } |
|
4641 //Make sure pitch is a number |
|
4642 if ( options.pitch && typeof options.pitch !== "number" ) { |
|
4643 options.pitch = +options.pitch; |
|
4644 } |
|
4645 |
|
4646 if ( options.type === "STREETVIEW" ) { |
|
4647 // Switch this map into streeview mode |
|
4648 map.setStreetView( |
|
4649 // Pass a new StreetViewPanorama instance into our map |
|
4650 |
|
4651 sView = new google.maps.StreetViewPanorama( newdiv, { |
|
4652 position: location, |
|
4653 pov: { |
|
4654 heading: options.heading = options.heading || 0, |
|
4655 pitch: options.pitch = options.pitch || 0, |
|
4656 zoom: options.zoom |
|
4657 } |
|
4658 }) |
|
4659 ); |
|
4660 |
|
4661 // Function to handle tweening using a set timeout |
|
4662 var tween = function( rM, t ) { |
|
4663 |
|
4664 var computeHeading = google.maps.geometry.spherical.computeHeading; |
|
4665 setTimeout(function() { |
|
4666 |
|
4667 var current_time = that.media.currentTime; |
|
4668 |
|
4669 // Checks whether this is a generated route or not |
|
4670 if ( typeof options.tween === "object" ) { |
|
4671 |
|
4672 for ( var i = 0, m = rM.length; i < m; i++ ) { |
|
4673 |
|
4674 var waypoint = rM[ i ]; |
|
4675 |
|
4676 // Checks if this position along the tween should be displayed or not |
|
4677 if ( current_time >= ( waypoint.interval * ( i + 1 ) ) / 1000 && |
|
4678 ( current_time <= ( waypoint.interval * ( i + 2 ) ) / 1000 || |
|
4679 current_time >= waypoint.interval * ( m ) / 1000 ) ) { |
|
4680 |
|
4681 sView3.setPosition( new google.maps.LatLng( waypoint.position.lat, waypoint.position.lng ) ); |
|
4682 |
|
4683 sView3.setPov({ |
|
4684 heading: waypoint.pov.heading || computeHeading( waypoint, rM[ i + 1 ] ) || 0, |
|
4685 zoom: waypoint.pov.zoom || 0, |
|
4686 pitch: waypoint.pov.pitch || 0 |
|
4687 }); |
|
4688 } |
|
4689 } |
|
4690 |
|
4691 // Calls the tween function again at the interval set by the user |
|
4692 tween( rM, rM[ 0 ].interval ); |
|
4693 } else { |
|
4694 |
|
4695 for ( var k = 0, l = rM.length; k < l; k++ ) { |
|
4696 |
|
4697 var interval = options.interval; |
|
4698 |
|
4699 if( current_time >= (interval * ( k + 1 ) ) / 1000 && |
|
4700 ( current_time <= (interval * ( k + 2 ) ) / 1000 || |
|
4701 current_time >= interval * ( l ) / 1000 ) ) { |
|
4702 |
|
4703 sView2.setPov({ |
|
4704 heading: computeHeading( rM[ k ], rM[ k + 1 ] ) || 0, |
|
4705 zoom: options.zoom, |
|
4706 pitch: options.pitch || 0 |
|
4707 }); |
|
4708 sView2.setPosition( checkpoints[ k ] ); |
|
4709 } |
|
4710 } |
|
4711 |
|
4712 tween( checkpoints, options.interval ); |
|
4713 } |
|
4714 }, t ); |
|
4715 }; |
|
4716 |
|
4717 // Determines if we should use hardcoded values ( using options.tween ), |
|
4718 // or if we should use a start and end location and let google generate |
|
4719 // the route for us |
|
4720 if ( options.location && typeof options.tween === "string" ) { |
|
4721 |
|
4722 // Creating another variable to hold the streetview map for tweening, |
|
4723 // Doing this because if there was more then one streetview map, the tweening would sometimes appear in other maps |
|
4724 var sView2 = sView; |
|
4725 |
|
4726 // Create an array to store all the lat/lang values along our route |
|
4727 var checkpoints = []; |
|
4728 |
|
4729 // Creates a new direction service, later used to create a route |
|
4730 var directionsService = new google.maps.DirectionsService(); |
|
4731 |
|
4732 // Creates a new direction renderer using the current map |
|
4733 // This enables us to access all of the route data that is returned to us |
|
4734 var directionsDisplay = new google.maps.DirectionsRenderer( sView2 ); |
|
4735 |
|
4736 var request = { |
|
4737 origin: options.location, |
|
4738 destination: options.tween, |
|
4739 travelMode: google.maps.TravelMode.DRIVING |
|
4740 }; |
|
4741 |
|
4742 // Create the route using the direction service and renderer |
|
4743 directionsService.route( request, function( response, status ) { |
|
4744 |
|
4745 if ( status == google.maps.DirectionsStatus.OK ) { |
|
4746 directionsDisplay.setDirections( response ); |
|
4747 showSteps( response, that ); |
|
4748 } |
|
4749 |
|
4750 }); |
|
4751 |
|
4752 var showSteps = function ( directionResult, that ) { |
|
4753 |
|
4754 // Push new google map lat and lng values into an array from our list of lat and lng values |
|
4755 var routes = directionResult.routes[ 0 ].overview_path; |
|
4756 for ( var j = 0, k = routes.length; j < k; j++ ) { |
|
4757 checkpoints.push( new google.maps.LatLng( routes[ j ].lat(), routes[ j ].lng() ) ); |
|
4758 } |
|
4759 |
|
4760 // Check to make sure the interval exists, if not, set to a default of 1000 |
|
4761 options.interval = options.interval || 1000; |
|
4762 tween( checkpoints, 10 ); |
|
4763 |
|
4764 }; |
|
4765 } else if ( typeof options.tween === "object" ) { |
|
4766 |
|
4767 // Same as the above to stop streetview maps from overflowing into one another |
|
4768 var sView3 = sView; |
|
4769 |
|
4770 for ( var i = 0, l = options.tween.length; i < l; i++ ) { |
|
4771 |
|
4772 // Make sure interval exists, if not, set to 1000 |
|
4773 options.tween[ i ].interval = options.tween[ i ].interval || 1000; |
|
4774 tween( options.tween, 10 ); |
|
4775 } |
|
4776 } |
|
4777 } |
|
4778 |
|
4779 if ( options.onmaploaded ) { |
|
4780 options.onmaploaded( options, map ); |
|
4781 } |
|
4782 |
|
4783 } else { |
|
4784 setTimeout(function () { |
|
4785 isMapSetup(); |
|
4786 }, 13); |
|
4787 } |
|
4788 |
|
4789 }; |
|
4790 isMapSetup(); |
|
4791 }, |
|
4792 /** |
|
4793 * @member webpage |
|
4794 * The end function will be executed when the currentTime |
|
4795 * of the video reaches the end time provided by the |
|
4796 * options variable |
|
4797 */ |
|
4798 end: function ( event, options ) { |
|
4799 // if the map exists hide it do not delete the map just in |
|
4800 // case the user seeks back to time b/w start and end |
|
4801 if ( map ) { |
|
4802 map.getDiv().style.display = "none"; |
|
4803 } |
|
4804 }, |
|
4805 _teardown: function ( options ) { |
|
4806 |
|
4807 var target = document.getElementById( options.target ); |
|
4808 |
|
4809 // the map must be manually removed |
|
4810 target && target.removeChild( newdiv ); |
|
4811 newdiv = map = location = null; |
|
4812 |
|
4813 options._map = null; |
|
4814 } |
|
4815 }; |
|
4816 }, { |
|
4817 about: { |
|
4818 name: "Popcorn Google Map Plugin", |
|
4819 version: "0.1", |
|
4820 author: "@annasob", |
|
4821 website: "annasob.wordpress.com" |
|
4822 }, |
|
4823 options: { |
|
4824 start: { |
|
4825 elem: "input", |
|
4826 type: "start", |
|
4827 label: "Start" |
|
4828 }, |
|
4829 end: { |
|
4830 elem: "input", |
|
4831 type: "start", |
|
4832 label: "End" |
|
4833 }, |
|
4834 target: "map-container", |
|
4835 type: { |
|
4836 elem: "select", |
|
4837 options: [ "ROADMAP", "SATELLITE", "STREETVIEW", "HYBRID", "TERRAIN", "STAMEN-WATERCOLOR", "STAMEN-TERRAIN", "STAMEN-TONER" ], |
|
4838 label: "Map Type", |
|
4839 optional: true |
|
4840 }, |
|
4841 zoom: { |
|
4842 elem: "input", |
|
4843 type: "text", |
|
4844 label: "Zoom", |
|
4845 "default": 0, |
|
4846 optional: true |
|
4847 }, |
|
4848 lat: { |
|
4849 elem: "input", |
|
4850 type: "text", |
|
4851 label: "Lat", |
|
4852 optional: true |
|
4853 }, |
|
4854 lng: { |
|
4855 elem: "input", |
|
4856 type: "text", |
|
4857 label: "Lng", |
|
4858 optional: true |
|
4859 }, |
|
4860 location: { |
|
4861 elem: "input", |
|
4862 type: "text", |
|
4863 label: "Location", |
|
4864 "default": "Toronto, Ontario, Canada" |
|
4865 }, |
|
4866 heading: { |
|
4867 elem: "input", |
|
4868 type: "text", |
|
4869 label: "Heading", |
|
4870 "default": 0, |
|
4871 optional: true |
|
4872 }, |
|
4873 pitch: { |
|
4874 elem: "input", |
|
4875 type: "text", |
|
4876 label: "Pitch", |
|
4877 "default": 1, |
|
4878 optional: true |
|
4879 } |
|
4880 } |
|
4881 }); |
|
4882 })( Popcorn ); |
|
4883 |
|
4884 // PLUGIN: IMAGE |
|
4885 |
|
4886 (function ( Popcorn ) { |
|
4887 |
|
4888 /** |
|
4889 * Images popcorn plug-in |
|
4890 * Shows an image element |
|
4891 * Options parameter will need a start, end, href, target and src. |
|
4892 * Start is the time that you want this plug-in to execute |
|
4893 * End is the time that you want this plug-in to stop executing |
|
4894 * href is the url of the destination of a anchor - optional |
|
4895 * Target is the id of the document element that the iframe needs to be attached to, |
|
4896 * this target element must exist on the DOM |
|
4897 * Src is the url of the image that you want to display |
|
4898 * text is the overlayed text on the image - optional |
|
4899 * |
|
4900 * @param {Object} options |
|
4901 * |
|
4902 * Example: |
|
4903 var p = Popcorn('#video') |
|
4904 .image({ |
|
4905 start: 5, // seconds |
|
4906 end: 15, // seconds |
|
4907 href: 'http://www.drumbeat.org/', |
|
4908 src: 'http://www.drumbeat.org/sites/default/files/domain-2/drumbeat_logo.png', |
|
4909 text: 'DRUMBEAT', |
|
4910 target: 'imagediv' |
|
4911 } ) |
|
4912 * |
|
4913 */ |
|
4914 |
|
4915 var VIDEO_OVERLAY_Z = 2000, |
|
4916 CHECK_INTERVAL_DURATION = 10; |
|
4917 |
|
4918 function trackMediaElement( mediaElement ) { |
|
4919 var checkInterval = -1, |
|
4920 container = document.createElement( "div" ), |
|
4921 videoZ = getComputedStyle( mediaElement ).zIndex; |
|
4922 |
|
4923 container.setAttribute( "data-popcorn-helper-container", true ); |
|
4924 |
|
4925 container.style.position = "absolute"; |
|
4926 |
|
4927 if ( !isNaN( videoZ ) ) { |
|
4928 container.style.zIndex = videoZ + 1; |
|
4929 } |
|
4930 else { |
|
4931 container.style.zIndex = VIDEO_OVERLAY_Z; |
|
4932 } |
|
4933 |
|
4934 document.body.appendChild( container ); |
|
4935 |
|
4936 function check() { |
|
4937 var mediaRect = mediaElement.getBoundingClientRect(), |
|
4938 containerRect = container.getBoundingClientRect(); |
|
4939 |
|
4940 if ( containerRect.left !== mediaRect.left ) { |
|
4941 container.style.left = mediaRect.left + "px"; |
|
4942 } |
|
4943 if ( containerRect.top !== mediaRect.top ) { |
|
4944 container.style.top = mediaRect.top + "px"; |
|
4945 } |
|
4946 } |
|
4947 |
|
4948 return { |
|
4949 element: container, |
|
4950 start: function() { |
|
4951 checkInterval = setInterval( check, CHECK_INTERVAL_DURATION ); |
|
4952 }, |
|
4953 stop: function() { |
|
4954 clearInterval( checkInterval ); |
|
4955 checkInterval = -1; |
|
4956 }, |
|
4957 destroy: function() { |
|
4958 document.body.removeChild( container ); |
|
4959 if ( checkInterval !== -1 ) { |
|
4960 clearInterval( checkInterval ); |
|
4961 } |
|
4962 } |
|
4963 }; |
|
4964 } |
|
4965 |
|
4966 Popcorn.plugin( "image", { |
|
4967 manifest: { |
|
4968 about: { |
|
4969 name: "Popcorn image Plugin", |
|
4970 version: "0.1", |
|
4971 author: "Scott Downe", |
|
4972 website: "http://scottdowne.wordpress.com/" |
|
4973 }, |
|
4974 options: { |
|
4975 start: { |
|
4976 elem: "input", |
|
4977 type: "number", |
|
4978 label: "Start" |
|
4979 }, |
|
4980 end: { |
|
4981 elem: "input", |
|
4982 type: "number", |
|
4983 label: "End" |
|
4984 }, |
|
4985 src: { |
|
4986 elem: "input", |
|
4987 type: "url", |
|
4988 label: "Image URL", |
|
4989 "default": "http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png" |
|
4990 }, |
|
4991 href: { |
|
4992 elem: "input", |
|
4993 type: "url", |
|
4994 label: "Link", |
|
4995 "default": "http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png", |
|
4996 optional: true |
|
4997 }, |
|
4998 target: "image-container", |
|
4999 text: { |
|
5000 elem: "input", |
|
5001 type: "text", |
|
5002 label: "Caption", |
|
5003 "default": "Popcorn.js", |
|
5004 optional: true |
|
5005 } |
|
5006 } |
|
5007 }, |
|
5008 _setup: function( options ) { |
|
5009 var img = document.createElement( "img" ), |
|
5010 target = document.getElementById( options.target ); |
|
5011 |
|
5012 options.anchor = document.createElement( "a" ); |
|
5013 options.anchor.style.position = "relative"; |
|
5014 options.anchor.style.textDecoration = "none"; |
|
5015 options.anchor.style.display = "none"; |
|
5016 |
|
5017 // add the widget's div to the target div. |
|
5018 // if target is <video> or <audio>, create a container and routinely |
|
5019 // update its size/position to be that of the media |
|
5020 if ( target ) { |
|
5021 if ( [ "VIDEO", "AUDIO" ].indexOf( target.nodeName ) > -1 ) { |
|
5022 options.trackedContainer = trackMediaElement( target ); |
|
5023 options.trackedContainer.element.appendChild( options.anchor ); |
|
5024 } |
|
5025 else { |
|
5026 target && target.appendChild( options.anchor ); |
|
5027 } |
|
5028 } |
|
5029 |
|
5030 img.addEventListener( "load", function() { |
|
5031 |
|
5032 // borders look really bad, if someone wants it they can put it on their div target |
|
5033 img.style.borderStyle = "none"; |
|
5034 |
|
5035 options.anchor.href = options.href || options.src || "#"; |
|
5036 options.anchor.target = "_blank"; |
|
5037 |
|
5038 var fontHeight, divText; |
|
5039 |
|
5040 img.style.height = target.style.height; |
|
5041 img.style.width = target.style.width; |
|
5042 |
|
5043 options.anchor.appendChild( img ); |
|
5044 |
|
5045 // If display text was provided, display it: |
|
5046 if ( options.text ) { |
|
5047 fontHeight = ( img.height / 12 ) + "px"; |
|
5048 divText = document.createElement( "div" ); |
|
5049 |
|
5050 Popcorn.extend( divText.style, { |
|
5051 color: "black", |
|
5052 fontSize: fontHeight, |
|
5053 fontWeight: "bold", |
|
5054 position: "relative", |
|
5055 textAlign: "center", |
|
5056 width: img.style.width || img.width + "px", |
|
5057 zIndex: "10" |
|
5058 }); |
|
5059 |
|
5060 divText.innerHTML = options.text || ""; |
|
5061 |
|
5062 divText.style.top = ( ( img.style.height.replace( "px", "" ) || img.height ) / 2 ) - ( divText.offsetHeight / 2 ) + "px"; |
|
5063 options.anchor.insertBefore( divText, img ); |
|
5064 } |
|
5065 }, false ); |
|
5066 |
|
5067 img.src = options.src; |
|
5068 }, |
|
5069 |
|
5070 /** |
|
5071 * @member image |
|
5072 * The start function will be executed when the currentTime |
|
5073 * of the video reaches the start time provided by the |
|
5074 * options variable |
|
5075 */ |
|
5076 start: function( event, options ) { |
|
5077 options.anchor.style.display = "inline"; |
|
5078 if ( options.trackedContainer ) { |
|
5079 options.trackedContainer.start(); |
|
5080 } |
|
5081 }, |
|
5082 /** |
|
5083 * @member image |
|
5084 * The end function will be executed when the currentTime |
|
5085 * of the video reaches the end time provided by the |
|
5086 * options variable |
|
5087 */ |
|
5088 end: function( event, options ) { |
|
5089 options.anchor.style.display = "none"; |
|
5090 if ( options.trackedContainer ) { |
|
5091 options.trackedContainer.stop(); |
|
5092 } |
|
5093 }, |
|
5094 _teardown: function( options ) { |
|
5095 if ( options.trackedContainer ) { |
|
5096 options.trackedContainer.destroy(); |
|
5097 } |
|
5098 else if ( options.anchor.parentNode ) { |
|
5099 options.anchor.parentNode.removeChild( options.anchor ); |
|
5100 } |
|
5101 } |
|
5102 }); |
|
5103 })( Popcorn ); |
|
5104 // PLUGIN: Google Feed |
|
5105 (function ( Popcorn ) { |
|
5106 |
|
5107 var i = 1, |
|
5108 scriptLoaded = false; |
|
5109 |
|
5110 /** |
|
5111 * googlefeed popcorn plug-in |
|
5112 * Adds a feed from the specified blog url at the target div |
|
5113 * Options parameter will need a start, end, target, url and title |
|
5114 * -Start is the time that you want this plug-in to execute |
|
5115 * -End is the time that you want this plug-in to stop executing |
|
5116 * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM |
|
5117 * -Url is the url of the blog's feed you are trying to access |
|
5118 * -Title is the title of the blog you want displayed above the feed |
|
5119 * -Orientation is the orientation of the blog, accepts either Horizontal or Vertical, defaults to Vertical |
|
5120 * @param {Object} options |
|
5121 * |
|
5122 * Example: |
|
5123 var p = Popcorn("#video") |
|
5124 .googlefeed({ |
|
5125 start: 5, // seconds |
|
5126 end: 15, // seconds |
|
5127 target: "map", |
|
5128 url: "http://zenit.senecac.on.ca/~chris.tyler/planet/rss20.xml", |
|
5129 title: "Planet Feed" |
|
5130 } ) |
|
5131 * |
|
5132 */ |
|
5133 |
|
5134 Popcorn.plugin( "googlefeed", function( options ) { |
|
5135 |
|
5136 var dynamicFeedLoad = function() { |
|
5137 var dontLoad = false, |
|
5138 k = 0, |
|
5139 links = document.getElementsByTagName( "link" ), |
|
5140 len = links.length, |
|
5141 head = document.head || document.getElementsByTagName( "head" )[ 0 ], |
|
5142 css = document.createElement( "link" ), |
|
5143 resource = "//www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol."; |
|
5144 |
|
5145 if ( !window.GFdynamicFeedControl ) { |
|
5146 |
|
5147 Popcorn.getScript( resource + "js", function() { |
|
5148 scriptLoaded = true; |
|
5149 }); |
|
5150 |
|
5151 } else { |
|
5152 scriptLoaded = true; |
|
5153 } |
|
5154 |
|
5155 // Checking if the css file is already included |
|
5156 for ( ; k < len; k++ ){ |
|
5157 if ( links[ k ].href === resource + "css" ) { |
|
5158 dontLoad = true; |
|
5159 } |
|
5160 } |
|
5161 |
|
5162 if ( !dontLoad ) { |
|
5163 css.type = "text/css"; |
|
5164 css.rel = "stylesheet"; |
|
5165 css.href = resource + "css"; |
|
5166 head.insertBefore( css, head.firstChild ); |
|
5167 } |
|
5168 }; |
|
5169 |
|
5170 if ( !window.google ) { |
|
5171 |
|
5172 Popcorn.getScript( "//www.google.com/jsapi", function() { |
|
5173 |
|
5174 google.load( "feeds", "1", { |
|
5175 |
|
5176 callback: function () { |
|
5177 |
|
5178 dynamicFeedLoad(); |
|
5179 } |
|
5180 }); |
|
5181 }); |
|
5182 |
|
5183 } else { |
|
5184 dynamicFeedLoad(); |
|
5185 } |
|
5186 |
|
5187 // create a new div and append it to the parent div so nothing |
|
5188 // that already exists in the parent div gets overwritten |
|
5189 var newdiv = document.createElement( "div" ), |
|
5190 target = document.getElementById( options.target ), |
|
5191 initialize = function() { |
|
5192 //ensure that the script has been loaded |
|
5193 if ( !scriptLoaded ) { |
|
5194 setTimeout( function () { |
|
5195 initialize(); |
|
5196 }, 5 ); |
|
5197 } else { |
|
5198 // Create the feed control using the user entered url and title |
|
5199 options.feed = new GFdynamicFeedControl( options.url, newdiv, { |
|
5200 vertical: options.orientation.toLowerCase() === "vertical" ? true : false, |
|
5201 horizontal: options.orientation.toLowerCase() === "horizontal" ? true : false, |
|
5202 title: options.title = options.title || "Blog" |
|
5203 }); |
|
5204 } |
|
5205 }; |
|
5206 |
|
5207 // Default to vertical orientation if empty or incorrect input |
|
5208 if( !options.orientation || ( options.orientation.toLowerCase() !== "vertical" && |
|
5209 options.orientation.toLowerCase() !== "horizontal" ) ) { |
|
5210 options.orientation = "vertical"; |
|
5211 } |
|
5212 |
|
5213 newdiv.style.display = "none"; |
|
5214 newdiv.id = "_feed" + i; |
|
5215 newdiv.style.width = "100%"; |
|
5216 newdiv.style.height = "100%"; |
|
5217 i++; |
|
5218 |
|
5219 target && target.appendChild( newdiv ); |
|
5220 |
|
5221 initialize(); |
|
5222 |
|
5223 return { |
|
5224 /** |
|
5225 * @member webpage |
|
5226 * The start function will be executed when the currentTime |
|
5227 * of the video reaches the start time provided by the |
|
5228 * options variable |
|
5229 */ |
|
5230 start: function( event, options ){ |
|
5231 newdiv.setAttribute( "style", "display:inline" ); |
|
5232 }, |
|
5233 /** |
|
5234 * @member webpage |
|
5235 * The end function will be executed when the currentTime |
|
5236 * of the video reaches the end time provided by the |
|
5237 * options variable |
|
5238 */ |
|
5239 end: function( event, options ){ |
|
5240 newdiv.setAttribute( "style", "display:none" ); |
|
5241 }, |
|
5242 _teardown: function( options ) { |
|
5243 document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( newdiv ); |
|
5244 delete options.feed; |
|
5245 } |
|
5246 }; |
|
5247 }, |
|
5248 { |
|
5249 about: { |
|
5250 name: "Popcorn Google Feed Plugin", |
|
5251 version: "0.1", |
|
5252 author: "David Seifried", |
|
5253 website: "dseifried.wordpress.com" |
|
5254 }, |
|
5255 options: { |
|
5256 start: { |
|
5257 elem: "input", |
|
5258 type: "number", |
|
5259 label: "Start" |
|
5260 }, |
|
5261 end: { |
|
5262 elem: "input", |
|
5263 type: "number", |
|
5264 label: "End" |
|
5265 }, |
|
5266 target: "feed-container", |
|
5267 url: { |
|
5268 elem: "input", |
|
5269 type: "url", |
|
5270 label: "Feed URL", |
|
5271 "default": "http://planet.mozilla.org/rss20.xml" |
|
5272 }, |
|
5273 title: { |
|
5274 elem: "input", |
|
5275 type: "text", |
|
5276 label: "Title", |
|
5277 "default": "Planet Mozilla", |
|
5278 optional: true |
|
5279 }, |
|
5280 orientation: { |
|
5281 elem: "select", |
|
5282 options: [ "Vertical", "Horizontal" ], |
|
5283 label: "Orientation", |
|
5284 "default": "Vertical", |
|
5285 optional: true |
|
5286 } |
|
5287 } |
|
5288 }); |
|
5289 })( Popcorn ); |
|
5290 // PLUGIN: Subtitle |
|
5291 |
|
5292 (function ( Popcorn ) { |
|
5293 |
|
5294 var i = 0, |
|
5295 createDefaultContainer = function( context, id ) { |
|
5296 |
|
5297 var ctxContainer = context.container = document.createElement( "div" ), |
|
5298 style = ctxContainer.style, |
|
5299 media = context.media; |
|
5300 |
|
5301 var updatePosition = function() { |
|
5302 var position = context.position(); |
|
5303 // the video element must have height and width defined |
|
5304 style.fontSize = "18px"; |
|
5305 style.width = media.offsetWidth + "px"; |
|
5306 style.top = position.top + media.offsetHeight - ctxContainer.offsetHeight - 40 + "px"; |
|
5307 style.left = position.left + "px"; |
|
5308 |
|
5309 setTimeout( updatePosition, 10 ); |
|
5310 }; |
|
5311 |
|
5312 ctxContainer.id = id || Popcorn.guid(); |
|
5313 style.position = "absolute"; |
|
5314 style.color = "white"; |
|
5315 style.textShadow = "black 2px 2px 6px"; |
|
5316 style.fontWeight = "bold"; |
|
5317 style.textAlign = "center"; |
|
5318 |
|
5319 updatePosition(); |
|
5320 |
|
5321 context.media.parentNode.appendChild( ctxContainer ); |
|
5322 |
|
5323 return ctxContainer; |
|
5324 }; |
|
5325 |
|
5326 /** |
|
5327 * Subtitle popcorn plug-in |
|
5328 * Displays a subtitle over the video, or in the target div |
|
5329 * Options parameter will need a start, and end. |
|
5330 * Optional parameters are target and text. |
|
5331 * Start is the time that you want this plug-in to execute |
|
5332 * End is the time that you want this plug-in to stop executing |
|
5333 * Target is the id of the document element that the content is |
|
5334 * appended to, this target element must exist on the DOM |
|
5335 * Text is the text of the subtitle you want to display. |
|
5336 * |
|
5337 * @param {Object} options |
|
5338 * |
|
5339 * Example: |
|
5340 var p = Popcorn('#video') |
|
5341 .subtitle({ |
|
5342 start: 5, // seconds, mandatory |
|
5343 end: 15, // seconds, mandatory |
|
5344 text: 'Hellow world', // optional |
|
5345 target: 'subtitlediv', // optional |
|
5346 } ) |
|
5347 * |
|
5348 */ |
|
5349 |
|
5350 Popcorn.plugin( "subtitle" , { |
|
5351 |
|
5352 manifest: { |
|
5353 about: { |
|
5354 name: "Popcorn Subtitle Plugin", |
|
5355 version: "0.1", |
|
5356 author: "Scott Downe", |
|
5357 website: "http://scottdowne.wordpress.com/" |
|
5358 }, |
|
5359 options: { |
|
5360 start: { |
|
5361 elem: "input", |
|
5362 type: "text", |
|
5363 label: "Start" |
|
5364 }, |
|
5365 end: { |
|
5366 elem: "input", |
|
5367 type: "text", |
|
5368 label: "End" |
|
5369 }, |
|
5370 target: "subtitle-container", |
|
5371 text: { |
|
5372 elem: "input", |
|
5373 type: "text", |
|
5374 label: "Text" |
|
5375 } |
|
5376 } |
|
5377 }, |
|
5378 |
|
5379 _setup: function( options ) { |
|
5380 var newdiv = document.createElement( "div" ); |
|
5381 |
|
5382 newdiv.id = "subtitle-" + i++; |
|
5383 newdiv.style.display = "none"; |
|
5384 |
|
5385 // Creates a div for all subtitles to use |
|
5386 ( !this.container && ( !options.target || options.target === "subtitle-container" ) ) && |
|
5387 createDefaultContainer( this ); |
|
5388 |
|
5389 // if a target is specified, use that |
|
5390 if ( options.target && options.target !== "subtitle-container" ) { |
|
5391 // In case the target doesn't exist in the DOM |
|
5392 options.container = document.getElementById( options.target ) || createDefaultContainer( this, options.target ); |
|
5393 } else { |
|
5394 // use shared default container |
|
5395 options.container = this.container; |
|
5396 } |
|
5397 |
|
5398 document.getElementById( options.container.id ) && document.getElementById( options.container.id ).appendChild( newdiv ); |
|
5399 options.innerContainer = newdiv; |
|
5400 |
|
5401 options.showSubtitle = function() { |
|
5402 options.innerContainer.innerHTML = options.text || ""; |
|
5403 }; |
|
5404 }, |
|
5405 /** |
|
5406 * @member subtitle |
|
5407 * The start function will be executed when the currentTime |
|
5408 * of the video reaches the start time provided by the |
|
5409 * options variable |
|
5410 */ |
|
5411 start: function( event, options ){ |
|
5412 options.innerContainer.style.display = "inline"; |
|
5413 options.showSubtitle( options, options.text ); |
|
5414 }, |
|
5415 /** |
|
5416 * @member subtitle |
|
5417 * The end function will be executed when the currentTime |
|
5418 * of the video reaches the end time provided by the |
|
5419 * options variable |
|
5420 */ |
|
5421 end: function( event, options ) { |
|
5422 options.innerContainer.style.display = "none"; |
|
5423 options.innerContainer.innerHTML = ""; |
|
5424 }, |
|
5425 |
|
5426 _teardown: function ( options ) { |
|
5427 options.container.removeChild( options.innerContainer ); |
|
5428 } |
|
5429 |
|
5430 }); |
|
5431 |
|
5432 })( Popcorn ); |
|
5433 // PLUGIN: TWITTER |
|
5434 |
|
5435 (function ( Popcorn ) { |
|
5436 var scriptLoading = false; |
|
5437 |
|
5438 /** |
|
5439 * Twitter popcorn plug-in |
|
5440 * Appends a Twitter widget to an element on the page. |
|
5441 * Options parameter will need a start, end, target and source. |
|
5442 * Optional parameters are height and width. |
|
5443 * Start is the time that you want this plug-in to execute |
|
5444 * End is the time that you want this plug-in to stop executing |
|
5445 * Src is the hash tag or twitter user to get tweets from |
|
5446 * Target is the id of the document element that the images are |
|
5447 * appended to, this target element must exist on the DOM |
|
5448 * Height is the height of the widget, defaults to 200 |
|
5449 * Width is the width of the widget, defaults to 250 |
|
5450 * |
|
5451 * @param {Object} options |
|
5452 * |
|
5453 * Example: |
|
5454 var p = Popcorn('#video') |
|
5455 .twitter({ |
|
5456 start: 5, // seconds, mandatory |
|
5457 end: 15, // seconds, mandatory |
|
5458 src: '@stevesong', // mandatory, also accepts hash tags |
|
5459 height: 200, // optional |
|
5460 width: 250, // optional |
|
5461 target: 'twitterdiv' // mandatory |
|
5462 } ) |
|
5463 * |
|
5464 */ |
|
5465 |
|
5466 Popcorn.plugin( "twitter" , { |
|
5467 |
|
5468 manifest: { |
|
5469 about: { |
|
5470 name: "Popcorn Twitter Plugin", |
|
5471 version: "0.1", |
|
5472 author: "Scott Downe", |
|
5473 website: "http://scottdowne.wordpress.com/" |
|
5474 }, |
|
5475 options:{ |
|
5476 start: { |
|
5477 elem: "input", |
|
5478 type: "number", |
|
5479 label: "Start" |
|
5480 }, |
|
5481 end: { |
|
5482 elem: "input", |
|
5483 type: "number", |
|
5484 label: "End" |
|
5485 }, |
|
5486 src: { |
|
5487 elem: "input", |
|
5488 type: "text", |
|
5489 label: "Tweet Source (# or @)", |
|
5490 "default": "@popcornjs" |
|
5491 }, |
|
5492 target: "twitter-container", |
|
5493 height: { |
|
5494 elem: "input", |
|
5495 type: "number", |
|
5496 label: "Height", |
|
5497 "default": "200", |
|
5498 optional: true |
|
5499 }, |
|
5500 width: { |
|
5501 elem: "input", |
|
5502 type: "number", |
|
5503 label: "Width", |
|
5504 "default": "250", |
|
5505 optional: true |
|
5506 } |
|
5507 } |
|
5508 }, |
|
5509 |
|
5510 _setup: function( options ) { |
|
5511 |
|
5512 if ( !window.TWTR && !scriptLoading ) { |
|
5513 scriptLoading = true; |
|
5514 Popcorn.getScript( "//widgets.twimg.com/j/2/widget.js" ); |
|
5515 } |
|
5516 |
|
5517 var target = document.getElementById( options.target ); |
|
5518 // create the div to store the widget |
|
5519 // setup widget div that is unique per track |
|
5520 options.container = document.createElement( "div" ); |
|
5521 // use this id to connect it to the widget |
|
5522 options.container.setAttribute( "id", Popcorn.guid() ); |
|
5523 // display none by default |
|
5524 options.container.style.display = "none"; |
|
5525 |
|
5526 // add the widget's div to the target div |
|
5527 target && target.appendChild( options.container ); |
|
5528 |
|
5529 // setup info for the widget |
|
5530 var src = options.src || "", |
|
5531 width = options.width || 250, |
|
5532 height = options.height || 200, |
|
5533 profile = /^@/.test( src ), |
|
5534 widgetOptions = { |
|
5535 version: 2, |
|
5536 // use this id to connect it to the div |
|
5537 id: options.container.getAttribute( "id" ), |
|
5538 rpp: 30, |
|
5539 width: width, |
|
5540 height: height, |
|
5541 interval: 6000, |
|
5542 theme: { |
|
5543 shell: { |
|
5544 background: "#ffffff", |
|
5545 color: "#000000" |
|
5546 }, |
|
5547 tweets: { |
|
5548 background: "#ffffff", |
|
5549 color: "#444444", |
|
5550 links: "#1985b5" |
|
5551 } |
|
5552 }, |
|
5553 features: { |
|
5554 loop: true, |
|
5555 timestamp: true, |
|
5556 avatars: true, |
|
5557 hashtags: true, |
|
5558 toptweets: true, |
|
5559 live: true, |
|
5560 scrollbar: false, |
|
5561 behavior: 'default' |
|
5562 } |
|
5563 }; |
|
5564 |
|
5565 // create widget |
|
5566 var isReady = function( that ) { |
|
5567 if ( window.TWTR ) { |
|
5568 if ( profile ) { |
|
5569 |
|
5570 widgetOptions.type = "profile"; |
|
5571 |
|
5572 new TWTR.Widget( widgetOptions ).render().setUser( src ).start(); |
|
5573 |
|
5574 } else { |
|
5575 |
|
5576 widgetOptions.type = "search"; |
|
5577 widgetOptions.search = src; |
|
5578 widgetOptions.subject = src; |
|
5579 |
|
5580 new TWTR.Widget( widgetOptions ).render().start(); |
|
5581 |
|
5582 } |
|
5583 } else { |
|
5584 setTimeout( function() { |
|
5585 isReady( that ); |
|
5586 }, 1); |
|
5587 } |
|
5588 }; |
|
5589 |
|
5590 isReady( this ); |
|
5591 }, |
|
5592 |
|
5593 /** |
|
5594 * @member Twitter |
|
5595 * The start function will be executed when the currentTime |
|
5596 * of the video reaches the start time provided by the |
|
5597 * options variable |
|
5598 */ |
|
5599 start: function( event, options ) { |
|
5600 options.container.style.display = "inline"; |
|
5601 }, |
|
5602 |
|
5603 /** |
|
5604 * @member Twitter |
|
5605 * The end function will be executed when the currentTime |
|
5606 * of the video reaches the end time provided by the |
|
5607 * options variable |
|
5608 */ |
|
5609 end: function( event, options ) { |
|
5610 options.container.style.display = "none"; |
|
5611 }, |
|
5612 _teardown: function( options ) { |
|
5613 |
|
5614 document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.container ); |
|
5615 } |
|
5616 }); |
|
5617 |
|
5618 })( Popcorn ); |
|
5619 // PLUGIN: WEBPAGE |
|
5620 |
|
5621 (function ( Popcorn ) { |
|
5622 |
|
5623 /** |
|
5624 * Webpages popcorn plug-in |
|
5625 * Creates an iframe showing a website specified by the user |
|
5626 * Options parameter will need a start, end, id, target and src. |
|
5627 * Start is the time that you want this plug-in to execute |
|
5628 * End is the time that you want this plug-in to stop executing |
|
5629 * Id is the id that you want assigned to the iframe |
|
5630 * Target is the id of the document element that the iframe needs to be attached to, |
|
5631 * this target element must exist on the DOM |
|
5632 * Src is the url of the website that you want the iframe to display |
|
5633 * |
|
5634 * @param {Object} options |
|
5635 * |
|
5636 * Example: |
|
5637 var p = Popcorn('#video') |
|
5638 .webpage({ |
|
5639 id: "webpages-a", |
|
5640 start: 5, // seconds |
|
5641 end: 15, // seconds |
|
5642 src: 'http://www.webmademovies.org', |
|
5643 target: 'webpagediv' |
|
5644 } ) |
|
5645 * |
|
5646 */ |
|
5647 Popcorn.plugin( "webpage" , { |
|
5648 manifest: { |
|
5649 about: { |
|
5650 name: "Popcorn Webpage Plugin", |
|
5651 version: "0.1", |
|
5652 author: "@annasob", |
|
5653 website: "annasob.wordpress.com" |
|
5654 }, |
|
5655 options: { |
|
5656 id: { |
|
5657 elem: "input", |
|
5658 type: "text", |
|
5659 label: "Id", |
|
5660 optional: true |
|
5661 }, |
|
5662 start: { |
|
5663 elem: "input", |
|
5664 type: "number", |
|
5665 label: "Start" |
|
5666 }, |
|
5667 end: { |
|
5668 elem: "input", |
|
5669 type: "number", |
|
5670 label: "End" |
|
5671 }, |
|
5672 src: { |
|
5673 elem: "input", |
|
5674 type: "url", |
|
5675 label: "Webpage URL", |
|
5676 "default": "http://mozillapopcorn.org" |
|
5677 }, |
|
5678 target: "iframe-container" |
|
5679 } |
|
5680 }, |
|
5681 _setup: function( options ) { |
|
5682 |
|
5683 var target = document.getElementById( options.target ); |
|
5684 |
|
5685 // make src an iframe acceptable string |
|
5686 options.src = options.src.replace( /^(https?:)?(\/\/)?/, "//" ); |
|
5687 |
|
5688 // make an iframe |
|
5689 options._iframe = document.createElement( "iframe" ); |
|
5690 options._iframe.setAttribute( "width", "100%" ); |
|
5691 options._iframe.setAttribute( "height", "100%" ); |
|
5692 options._iframe.id = options.id; |
|
5693 options._iframe.src = options.src; |
|
5694 options._iframe.style.display = "none"; |
|
5695 |
|
5696 // add the hidden iframe to the DOM |
|
5697 target && target.appendChild( options._iframe ); |
|
5698 |
|
5699 }, |
|
5700 /** |
|
5701 * @member webpage |
|
5702 * The start function will be executed when the currentTime |
|
5703 * of the video reaches the start time provided by the |
|
5704 * options variable |
|
5705 */ |
|
5706 start: function( event, options ){ |
|
5707 // make the iframe visible |
|
5708 options._iframe.src = options.src; |
|
5709 options._iframe.style.display = "inline"; |
|
5710 }, |
|
5711 /** |
|
5712 * @member webpage |
|
5713 * The end function will be executed when the currentTime |
|
5714 * of the video reaches the end time provided by the |
|
5715 * options variable |
|
5716 */ |
|
5717 end: function( event, options ){ |
|
5718 // make the iframe invisible |
|
5719 options._iframe.style.display = "none"; |
|
5720 }, |
|
5721 _teardown: function( options ) { |
|
5722 |
|
5723 document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._iframe ); |
|
5724 } |
|
5725 }); |
|
5726 })( Popcorn ); |
|
5727 // PLUGIN: WIKIPEDIA |
|
5728 |
|
5729 |
|
5730 var wikiCallback; |
|
5731 |
|
5732 (function ( Popcorn ) { |
|
5733 |
|
5734 /** |
|
5735 * Wikipedia popcorn plug-in |
|
5736 * Displays a wikipedia aricle in the target specified by the user by using |
|
5737 * new DOM element instead overwriting them |
|
5738 * Options parameter will need a start, end, target, lang, src, title and numberofwords. |
|
5739 * -Start is the time that you want this plug-in to execute |
|
5740 * -End is the time that you want this plug-in to stop executing |
|
5741 * -Target is the id of the document element that the text from the article needs to be |
|
5742 * attached to, this target element must exist on the DOM |
|
5743 * -Lang (optional, defaults to english) is the language in which the article is in. |
|
5744 * -Src is the url of the article |
|
5745 * -Title (optional) is the title of the article |
|
5746 * -numberofwords (optional, defaults to 200) is the number of words you want displaid. |
|
5747 * |
|
5748 * @param {Object} options |
|
5749 * |
|
5750 * Example: |
|
5751 var p = Popcorn("#video") |
|
5752 .wikipedia({ |
|
5753 start: 5, // seconds |
|
5754 end: 15, // seconds |
|
5755 src: "http://en.wikipedia.org/wiki/Cape_Town", |
|
5756 target: "wikidiv" |
|
5757 } ) |
|
5758 * |
|
5759 */ |
|
5760 Popcorn.plugin( "wikipedia" , { |
|
5761 |
|
5762 manifest: { |
|
5763 about:{ |
|
5764 name: "Popcorn Wikipedia Plugin", |
|
5765 version: "0.1", |
|
5766 author: "@annasob", |
|
5767 website: "annasob.wordpress.com" |
|
5768 }, |
|
5769 options:{ |
|
5770 start: { |
|
5771 elem: "input", |
|
5772 type: "number", |
|
5773 label: "Start" |
|
5774 }, |
|
5775 end: { |
|
5776 elem: "input", |
|
5777 type: "number", |
|
5778 label: "End" |
|
5779 }, |
|
5780 lang: { |
|
5781 elem: "input", |
|
5782 type: "text", |
|
5783 label: "Language", |
|
5784 "default": "english", |
|
5785 optional: true |
|
5786 }, |
|
5787 src: { |
|
5788 elem: "input", |
|
5789 type: "url", |
|
5790 label: "Wikipedia URL", |
|
5791 "default": "http://en.wikipedia.org/wiki/Cat" |
|
5792 }, |
|
5793 title: { |
|
5794 elem: "input", |
|
5795 type: "text", |
|
5796 label: "Title", |
|
5797 "default": "Cats", |
|
5798 optional: true |
|
5799 }, |
|
5800 numberofwords: { |
|
5801 elem: "input", |
|
5802 type: "number", |
|
5803 label: "Number of Words", |
|
5804 "default": "200", |
|
5805 optional: true |
|
5806 }, |
|
5807 target: "wikipedia-container" |
|
5808 } |
|
5809 }, |
|
5810 /** |
|
5811 * @member wikipedia |
|
5812 * The setup function will get all of the needed |
|
5813 * items in place before the start function is called. |
|
5814 * This includes getting data from wikipedia, if the data |
|
5815 * is not received and processed before start is called start |
|
5816 * will not do anything |
|
5817 */ |
|
5818 _setup : function( options ) { |
|
5819 // declare needed variables |
|
5820 // get a guid to use for the global wikicallback function |
|
5821 var _text, _guid = Popcorn.guid(); |
|
5822 |
|
5823 // if the user didn't specify a language default to english |
|
5824 if ( !options.lang ) { |
|
5825 options.lang = "en"; |
|
5826 } |
|
5827 |
|
5828 // if the user didn't specify number of words to use default to 200 |
|
5829 options.numberofwords = options.numberofwords || 200; |
|
5830 |
|
5831 // wiki global callback function with a unique id |
|
5832 // function gets the needed information from wikipedia |
|
5833 // and stores it by appending values to the options object |
|
5834 window[ "wikiCallback" + _guid ] = function ( data ) { |
|
5835 |
|
5836 options._link = document.createElement( "a" ); |
|
5837 options._link.setAttribute( "href", options.src ); |
|
5838 options._link.setAttribute( "target", "_blank" ); |
|
5839 |
|
5840 // add the title of the article to the link |
|
5841 options._link.innerHTML = options.title || data.parse.displaytitle; |
|
5842 |
|
5843 // get the content of the wiki article |
|
5844 options._desc = document.createElement( "p" ); |
|
5845 |
|
5846 // get the article text and remove any special characters |
|
5847 _text = data.parse.text[ "*" ].substr( data.parse.text[ "*" ].indexOf( "<p>" ) ); |
|
5848 _text = _text.replace( /((<(.|\n)+?>)|(\((.*?)\) )|(\[(.*?)\]))/g, "" ); |
|
5849 |
|
5850 _text = _text.split( " " ); |
|
5851 options._desc.innerHTML = ( _text.slice( 0, ( _text.length >= options.numberofwords ? options.numberofwords : _text.length ) ).join (" ") + " ..." ) ; |
|
5852 |
|
5853 options._fired = true; |
|
5854 }; |
|
5855 |
|
5856 if ( options.src ) { |
|
5857 Popcorn.getScript( "//" + options.lang + ".wikipedia.org/w/api.php?action=parse&props=text&redirects&page=" + |
|
5858 options.src.slice( options.src.lastIndexOf( "/" ) + 1 ) + "&format=json&callback=wikiCallback" + _guid ); |
|
5859 } |
|
5860 |
|
5861 }, |
|
5862 /** |
|
5863 * @member wikipedia |
|
5864 * The start function will be executed when the currentTime |
|
5865 * of the video reaches the start time provided by the |
|
5866 * options variable |
|
5867 */ |
|
5868 start: function( event, options ){ |
|
5869 // dont do anything if the information didn't come back from wiki |
|
5870 var isReady = function () { |
|
5871 |
|
5872 if ( !options._fired ) { |
|
5873 setTimeout( function () { |
|
5874 isReady(); |
|
5875 }, 13); |
|
5876 } else { |
|
5877 |
|
5878 if ( options._link && options._desc ) { |
|
5879 if ( document.getElementById( options.target ) ) { |
|
5880 document.getElementById( options.target ).appendChild( options._link ); |
|
5881 document.getElementById( options.target ).appendChild( options._desc ); |
|
5882 options._added = true; |
|
5883 } |
|
5884 } |
|
5885 } |
|
5886 }; |
|
5887 |
|
5888 isReady(); |
|
5889 }, |
|
5890 /** |
|
5891 * @member wikipedia |
|
5892 * The end function will be executed when the currentTime |
|
5893 * of the video reaches the end time provided by the |
|
5894 * options variable |
|
5895 */ |
|
5896 end: function( event, options ){ |
|
5897 // ensure that the data was actually added to the |
|
5898 // DOM before removal |
|
5899 if ( options._added ) { |
|
5900 document.getElementById( options.target ).removeChild( options._link ); |
|
5901 document.getElementById( options.target ).removeChild( options._desc ); |
|
5902 } |
|
5903 }, |
|
5904 |
|
5905 _teardown: function( options ){ |
|
5906 |
|
5907 if ( options._added ) { |
|
5908 options._link.parentNode && document.getElementById( options.target ).removeChild( options._link ); |
|
5909 options._desc.parentNode && document.getElementById( options.target ).removeChild( options._desc ); |
|
5910 delete options.target; |
|
5911 } |
|
5912 } |
|
5913 }); |
|
5914 |
|
5915 })( Popcorn ); |
|
5916 // PLUGIN: Mustache |
|
5917 |
|
5918 (function ( Popcorn ) { |
|
5919 |
|
5920 /** |
|
5921 * Mustache Popcorn Plug-in |
|
5922 * |
|
5923 * Adds the ability to render JSON using templates via the Mustache templating library. |
|
5924 * |
|
5925 * @param {Object} options |
|
5926 * |
|
5927 * Required parameters: start, end, template, data, and target. |
|
5928 * Optional parameter: static. |
|
5929 * |
|
5930 * start: the time in seconds when the mustache template should be rendered |
|
5931 * in the target div. |
|
5932 * |
|
5933 * end: the time in seconds when the rendered mustache template should be |
|
5934 * removed from the target div. |
|
5935 * |
|
5936 * target: a String -- the target div's id. |
|
5937 * |
|
5938 * template: the mustache template for the plugin to use when rendering. This can be |
|
5939 * a String containing the template, or a Function that returns the template's |
|
5940 * String. |
|
5941 * |
|
5942 * data: the data to be rendered using the mustache template. This can be a JSON String, |
|
5943 * a JavaScript Object literal, or a Function returning a String or Literal. |
|
5944 * |
|
5945 * dynamic: an optional argument indicating that the template and json data are dynamic |
|
5946 * and need to be loaded dynamically on every use. Defaults to True. |
|
5947 * |
|
5948 * Example: |
|
5949 var p = Popcorn('#video') |
|
5950 |
|
5951 // Example using template and JSON strings. |
|
5952 .mustache({ |
|
5953 start: 5, // seconds |
|
5954 end: 15, // seconds |
|
5955 target: 'mustache', |
|
5956 template: '<h1>{{header}}</h1>' + |
|
5957 '{{#bug}}' + |
|
5958 '{{/bug}}' + |
|
5959 '' + |
|
5960 '{{#items}}' + |
|
5961 ' {{#first}}' + |
|
5962 ' <li><strong>{{name}}</strong></li>' + |
|
5963 ' {{/first}}' + |
|
5964 ' {{#link}}' + |
|
5965 ' <li><a href="{{url}}">{{name}}</a></li>' + |
|
5966 ' {{/link}}' + |
|
5967 '{{/items}}' + |
|
5968 '' + |
|
5969 '{{#empty}}' + |
|
5970 ' <p>The list is empty.</p>' + |
|
5971 '{{/empty}}' , |
|
5972 |
|
5973 data: '{' + |
|
5974 ' "header": "Colors", ' + |
|
5975 ' "items": [ ' + |
|
5976 ' {"name": "red", "first": true, "url": "#Red"}, ' + |
|
5977 ' {"name": "green", "link": true, "url": "#Green"}, ' + |
|
5978 ' {"name": "blue", "link": true, "url": "#Blue"} ' + |
|
5979 ' ],' + |
|
5980 ' 'empty': false' + |
|
5981 '}', |
|
5982 dynamic: false // The json is not going to change, load it early. |
|
5983 } ) |
|
5984 |
|
5985 // Example showing Functions instead of Strings. |
|
5986 .mustache({ |
|
5987 start: 20, // seconds |
|
5988 end: 25, // seconds |
|
5989 target: 'mustache', |
|
5990 template: function(instance, options) { |
|
5991 var template = // load your template file here... |
|
5992 return template; |
|
5993 }, |
|
5994 data: function(instance, options) { |
|
5995 var json = // load your json here... |
|
5996 return json; |
|
5997 } |
|
5998 } ); |
|
5999 * |
|
6000 */ |
|
6001 |
|
6002 Popcorn.plugin( "mustache" , function( options ){ |
|
6003 |
|
6004 var getData, data, getTemplate, template; |
|
6005 |
|
6006 Popcorn.getScript( "http://mustache.github.com/extras/mustache.js" ); |
|
6007 |
|
6008 var shouldReload = !!options.dynamic, |
|
6009 typeOfTemplate = typeof options.template, |
|
6010 typeOfData = typeof options.data, |
|
6011 target = document.getElementById( options.target ); |
|
6012 |
|
6013 options.container = target || document.createElement( "div" ); |
|
6014 |
|
6015 if ( typeOfTemplate === "function" ) { |
|
6016 if ( !shouldReload ) { |
|
6017 template = options.template( options ); |
|
6018 } else { |
|
6019 getTemplate = options.template; |
|
6020 } |
|
6021 } else if ( typeOfTemplate === "string" ) { |
|
6022 template = options.template; |
|
6023 } else { |
|
6024 template = ""; |
|
6025 } |
|
6026 |
|
6027 if ( typeOfData === "function" ) { |
|
6028 if ( !shouldReload ) { |
|
6029 data = options.data( options ); |
|
6030 } else { |
|
6031 getData = options.data; |
|
6032 } |
|
6033 } else if ( typeOfData === "string" ) { |
|
6034 data = JSON.parse( options.data ); |
|
6035 } else if ( typeOfData === "object" ) { |
|
6036 data = options.data; |
|
6037 } else { |
|
6038 data = ""; |
|
6039 } |
|
6040 |
|
6041 return { |
|
6042 start: function( event, options ) { |
|
6043 |
|
6044 var interval = function() { |
|
6045 |
|
6046 if( !window.Mustache ) { |
|
6047 setTimeout( function() { |
|
6048 interval(); |
|
6049 }, 10 ); |
|
6050 } else { |
|
6051 |
|
6052 // if dynamic, freshen json data on every call to start, just in case. |
|
6053 if ( getData ) { |
|
6054 data = getData( options ); |
|
6055 } |
|
6056 |
|
6057 if ( getTemplate ) { |
|
6058 template = getTemplate( options ); |
|
6059 } |
|
6060 |
|
6061 var html = Mustache.to_html( template, |
|
6062 data |
|
6063 ).replace( /^\s*/mg, "" ); |
|
6064 options.container.innerHTML = html; |
|
6065 } |
|
6066 }; |
|
6067 |
|
6068 interval(); |
|
6069 |
|
6070 }, |
|
6071 |
|
6072 end: function( event, options ) { |
|
6073 options.container.innerHTML = ""; |
|
6074 }, |
|
6075 _teardown: function( options ) { |
|
6076 getData = data = getTemplate = template = null; |
|
6077 } |
|
6078 }; |
|
6079 }, |
|
6080 { |
|
6081 about: { |
|
6082 name: "Popcorn Mustache Plugin", |
|
6083 version: "0.1", |
|
6084 author: "David Humphrey (@humphd)", |
|
6085 website: "http://vocamus.net/dave" |
|
6086 }, |
|
6087 options: { |
|
6088 start: { |
|
6089 elem: "input", |
|
6090 type: "number", |
|
6091 label: "Start" |
|
6092 }, |
|
6093 end: { |
|
6094 elem: "input", |
|
6095 type: "number", |
|
6096 label: "End" |
|
6097 }, |
|
6098 target: "mustache-container", |
|
6099 template: { |
|
6100 elem: "input", |
|
6101 type: "text", |
|
6102 label: "Template" |
|
6103 }, |
|
6104 data: { |
|
6105 elem: "input", |
|
6106 type: "text", |
|
6107 label: "Data" |
|
6108 }, |
|
6109 dynamic: { |
|
6110 elem: "input", |
|
6111 type: "checkbox", |
|
6112 label: "Dynamic", |
|
6113 "default": true |
|
6114 } |
|
6115 } |
|
6116 }); |
|
6117 })( Popcorn ); |
|
6118 // PLUGIN: OPENMAP |
|
6119 ( function ( Popcorn ) { |
|
6120 |
|
6121 /** |
|
6122 * openmap popcorn plug-in |
|
6123 * Adds an OpenLayers map and open map tiles (OpenStreetMap [default], NASA WorldWind, or USGS Topographic) |
|
6124 * Based on the googlemap popcorn plug-in. No StreetView support |
|
6125 * Options parameter will need a start, end, target, type, zoom, lat and lng |
|
6126 * -Start is the time that you want this plug-in to execute |
|
6127 * -End is the time that you want this plug-in to stop executing |
|
6128 * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM |
|
6129 * -Type [optional] either: ROADMAP (OpenStreetMap), SATELLITE (NASA WorldWind / LandSat), or TERRAIN (USGS). |
|
6130 * The Stamen custom map types can also be used (http://maps.stamen.com): STAMEN-TONER, |
|
6131 * STAMEN-TERRAIN, or STAMEN-WATERCOLOR. |
|
6132 * -Zoom [optional] defaults to 2 |
|
6133 * -Lat and Lng are the coordinates of the map if location is not named |
|
6134 * -Location is a name of a place to center the map, geocoded to coordinates using TinyGeocoder.com |
|
6135 * -Markers [optional] is an array of map marker objects, with the following properties: |
|
6136 * --Icon is the URL of a map marker image |
|
6137 * --Size [optional] is the radius in pixels of the scaled marker image (default is 14) |
|
6138 * --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 |
|
6139 * --Lat and Lng are coordinates of the map marker if location is not specified |
|
6140 * --Location is a name of a place for the map marker, geocoded to coordinates using TinyGeocoder.com |
|
6141 * Note: using location requires extra loading time, also not specifying both lat/lng and location will |
|
6142 * cause a JavaScript error. |
|
6143 * @param {Object} options |
|
6144 * |
|
6145 * Example: |
|
6146 var p = Popcorn( '#video' ) |
|
6147 .openmap({ |
|
6148 start: 5, |
|
6149 end: 15, |
|
6150 type: 'ROADMAP', |
|
6151 target: 'map', |
|
6152 lat: 43.665429, |
|
6153 lng: -79.403323 |
|
6154 }) |
|
6155 * |
|
6156 */ |
|
6157 var newdiv, |
|
6158 i = 1; |
|
6159 |
|
6160 function toggle( container, display ) { |
|
6161 if ( container.map ) { |
|
6162 container.map.div.style.display = display; |
|
6163 return; |
|
6164 } |
|
6165 |
|
6166 setTimeout(function() { |
|
6167 toggle( container, display ); |
|
6168 }, 10 ); |
|
6169 } |
|
6170 |
|
6171 Popcorn.plugin( "openmap", function( options ){ |
|
6172 var newdiv, |
|
6173 centerlonlat, |
|
6174 projection, |
|
6175 displayProjection, |
|
6176 pointLayer, |
|
6177 selectControl, |
|
6178 popup, |
|
6179 isGeoReady, |
|
6180 target = document.getElementById( options.target ); |
|
6181 |
|
6182 // create a new div within the target div |
|
6183 // this is later passed on to the maps api |
|
6184 newdiv = document.createElement( "div" ); |
|
6185 newdiv.id = "openmapdiv" + i; |
|
6186 newdiv.style.width = "100%"; |
|
6187 newdiv.style.height = "100%"; |
|
6188 i++; |
|
6189 |
|
6190 target && target.appendChild( newdiv ); |
|
6191 |
|
6192 // callback function fires when the script is run |
|
6193 isGeoReady = function() { |
|
6194 if ( ! ( window.OpenLayers && window.OpenLayers.Layer.Stamen ) ) { |
|
6195 setTimeout(function() { |
|
6196 isGeoReady(); |
|
6197 }, 50); |
|
6198 } else { |
|
6199 if ( options.location ) { |
|
6200 // set a dummy center at start |
|
6201 location = new OpenLayers.LonLat( 0, 0 ); |
|
6202 // query TinyGeocoder and re-center in callback |
|
6203 Popcorn.getJSONP( |
|
6204 "//tinygeocoder.com/create-api.php?q=" + options.location + "&callback=jsonp", |
|
6205 function( latlng ) { |
|
6206 centerlonlat = new OpenLayers.LonLat( latlng[ 1 ], latlng[ 0 ] ); |
|
6207 } |
|
6208 ); |
|
6209 } else { |
|
6210 centerlonlat = new OpenLayers.LonLat( options.lng, options.lat ); |
|
6211 } |
|
6212 |
|
6213 options.type = options.type || "ROADMAP"; |
|
6214 switch( options.type ) { |
|
6215 case "SATELLITE" : |
|
6216 // add NASA WorldWind / LANDSAT map |
|
6217 options.map = new OpenLayers.Map({ |
|
6218 div: newdiv, |
|
6219 maxResolution: 0.28125, |
|
6220 tileSize: new OpenLayers.Size( 512, 512 ) |
|
6221 }); |
|
6222 var worldwind = new OpenLayers.Layer.WorldWind( |
|
6223 "LANDSAT", |
|
6224 "//worldwind25.arc.nasa.gov/tile/tile.aspx", |
|
6225 2.25, 4, |
|
6226 { T: "105" } |
|
6227 ); |
|
6228 options.map.addLayer( worldwind ); |
|
6229 displayProjection = new OpenLayers.Projection( "EPSG:4326" ); |
|
6230 projection = new OpenLayers.Projection( "EPSG:4326" ); |
|
6231 break; |
|
6232 case "TERRAIN": |
|
6233 // add terrain map ( USGS ) |
|
6234 displayProjection = new OpenLayers.Projection( "EPSG:4326" ); |
|
6235 projection = new OpenLayers.Projection( "EPSG:4326" ); |
|
6236 options.map = new OpenLayers.Map({ |
|
6237 div: newdiv, |
|
6238 projection: projection |
|
6239 }); |
|
6240 var relief = new OpenLayers.Layer.WMS( |
|
6241 "USGS Terraserver", |
|
6242 "//terraserver-usa.org/ogcmap.ashx?", |
|
6243 { layers: "DRG" } |
|
6244 ); |
|
6245 options.map.addLayer( relief ); |
|
6246 break; |
|
6247 case "STAMEN-TONER": |
|
6248 case "STAMEN-WATERCOLOR": |
|
6249 case "STAMEN-TERRAIN": |
|
6250 var layerName = options.type.replace("STAMEN-", "").toLowerCase(); |
|
6251 var sLayer = new OpenLayers.Layer.Stamen( layerName ); |
|
6252 displayProjection = new OpenLayers.Projection( "EPSG:4326" ); |
|
6253 projection = new OpenLayers.Projection( 'EPSG:900913' ); |
|
6254 centerlonlat = centerlonlat.transform( displayProjection, projection ); |
|
6255 options.map = new OpenLayers.Map( { |
|
6256 div: newdiv, |
|
6257 projection: projection, |
|
6258 displayProjection: displayProjection, |
|
6259 controls: [ |
|
6260 new OpenLayers.Control.Navigation(), |
|
6261 new OpenLayers.Control.PanPanel(), |
|
6262 new OpenLayers.Control.ZoomPanel() |
|
6263 ] |
|
6264 } ); |
|
6265 options.map.addLayer( sLayer ); |
|
6266 break; |
|
6267 default: /* case "ROADMAP": */ |
|
6268 // add OpenStreetMap layer |
|
6269 projection = new OpenLayers.Projection( 'EPSG:900913' ); |
|
6270 displayProjection = new OpenLayers.Projection( 'EPSG:4326' ); |
|
6271 centerlonlat = centerlonlat.transform( displayProjection, projection ); |
|
6272 options.map = new OpenLayers.Map({ |
|
6273 div: newdiv, |
|
6274 projection: projection, |
|
6275 "displayProjection": displayProjection |
|
6276 }); |
|
6277 var osm = new OpenLayers.Layer.OSM(); |
|
6278 options.map.addLayer( osm ); |
|
6279 break; |
|
6280 } |
|
6281 |
|
6282 if ( options.map ) { |
|
6283 options.map.setCenter(centerlonlat, options.zoom || 10); |
|
6284 options.map.div.style.display = "none"; |
|
6285 } |
|
6286 } |
|
6287 }; |
|
6288 |
|
6289 isGeoReady(); |
|
6290 |
|
6291 return { |
|
6292 |
|
6293 /** |
|
6294 * @member openmap |
|
6295 * The setup function will be executed when the plug-in is instantiated |
|
6296 */ |
|
6297 _setup: function( options ) { |
|
6298 |
|
6299 // insert openlayers api script once |
|
6300 if ( !window.OpenLayers ) { |
|
6301 Popcorn.getScript( "//openlayers.org/api/OpenLayers.js", function() { |
|
6302 Popcorn.getScript( "//maps.stamen.com/js/tile.stamen.js" ); |
|
6303 } ); |
|
6304 } |
|
6305 |
|
6306 var isReady = function() { |
|
6307 // wait until OpenLayers has been loaded, and the start function is run, before adding map |
|
6308 if ( !options.map ) { |
|
6309 setTimeout(function() { |
|
6310 isReady(); |
|
6311 }, 13 ); |
|
6312 } else { |
|
6313 |
|
6314 // default zoom is 2 |
|
6315 options.zoom = options.zoom || 2; |
|
6316 |
|
6317 // make sure options.zoom is a number |
|
6318 if ( options.zoom && typeof options.zoom !== "number" ) { |
|
6319 options.zoom = +options.zoom; |
|
6320 } |
|
6321 |
|
6322 // reset the location and zoom just in case the user played with the map |
|
6323 options.map.setCenter( centerlonlat, options.zoom ); |
|
6324 if ( options.markers ) { |
|
6325 var layerStyle = OpenLayers.Util.extend( {} , OpenLayers.Feature.Vector.style[ "default" ] ), |
|
6326 featureSelected = function( clickInfo ) { |
|
6327 clickedFeature = clickInfo.feature; |
|
6328 if ( !clickedFeature.attributes.text ) { |
|
6329 return; |
|
6330 } |
|
6331 popup = new OpenLayers.Popup.FramedCloud( |
|
6332 "featurePopup", |
|
6333 clickedFeature.geometry.getBounds().getCenterLonLat(), |
|
6334 new OpenLayers.Size( 120, 250 ), |
|
6335 clickedFeature.attributes.text, |
|
6336 null, |
|
6337 true, |
|
6338 function( closeInfo ) { |
|
6339 selectControl.unselect( this.feature ); |
|
6340 } |
|
6341 ); |
|
6342 clickedFeature.popup = popup; |
|
6343 popup.feature = clickedFeature; |
|
6344 options.map.addPopup( popup ); |
|
6345 }, |
|
6346 featureUnSelected = function( clickInfo ) { |
|
6347 feature = clickInfo.feature; |
|
6348 if ( feature.popup ) { |
|
6349 popup.feature = null; |
|
6350 options.map.removePopup( feature.popup ); |
|
6351 feature.popup.destroy(); |
|
6352 feature.popup = null; |
|
6353 } |
|
6354 }, |
|
6355 gcThenPlotMarker = function( myMarker ) { |
|
6356 Popcorn.getJSONP( |
|
6357 "//tinygeocoder.com/create-api.php?q=" + myMarker.location + "&callback=jsonp", |
|
6358 function( latlng ) { |
|
6359 var myPoint = new OpenLayers.Geometry.Point( latlng[1], latlng[0] ).transform( displayProjection, projection ), |
|
6360 myPointStyle = OpenLayers.Util.extend( {}, layerStyle ); |
|
6361 if ( !myMarker.size || isNaN( myMarker.size ) ) { |
|
6362 myMarker.size = 14; |
|
6363 } |
|
6364 myPointStyle.pointRadius = myMarker.size; |
|
6365 myPointStyle.graphicOpacity = 1; |
|
6366 myPointStyle.externalGraphic = myMarker.icon; |
|
6367 var myPointFeature = new OpenLayers.Feature.Vector( myPoint, null, myPointStyle ); |
|
6368 if ( myMarker.text ) { |
|
6369 myPointFeature.attributes = { |
|
6370 text: myMarker.text |
|
6371 }; |
|
6372 } |
|
6373 pointLayer.addFeatures( [ myPointFeature ] ); |
|
6374 } |
|
6375 ); |
|
6376 }; |
|
6377 pointLayer = new OpenLayers.Layer.Vector( "Point Layer", { style: layerStyle } ); |
|
6378 options.map.addLayer( pointLayer ); |
|
6379 for ( var m = 0, l = options.markers.length; m < l ; m++ ) { |
|
6380 var myMarker = options.markers[ m ]; |
|
6381 if( myMarker.text ){ |
|
6382 if( !selectControl ){ |
|
6383 selectControl = new OpenLayers.Control.SelectFeature( pointLayer ); |
|
6384 options.map.addControl( selectControl ); |
|
6385 selectControl.activate(); |
|
6386 pointLayer.events.on({ |
|
6387 "featureselected": featureSelected, |
|
6388 "featureunselected": featureUnSelected |
|
6389 }); |
|
6390 } |
|
6391 } |
|
6392 if ( myMarker.location ) { |
|
6393 var geocodeThenPlotMarker = gcThenPlotMarker; |
|
6394 geocodeThenPlotMarker( myMarker ); |
|
6395 } else { |
|
6396 var myPoint = new OpenLayers.Geometry.Point( myMarker.lng, myMarker.lat ).transform( displayProjection, projection ), |
|
6397 myPointStyle = OpenLayers.Util.extend( {}, layerStyle ); |
|
6398 if ( !myMarker.size || isNaN( myMarker.size ) ) { |
|
6399 myMarker.size = 14; |
|
6400 } |
|
6401 myPointStyle.pointRadius = myMarker.size; |
|
6402 myPointStyle.graphicOpacity = 1; |
|
6403 myPointStyle.externalGraphic = myMarker.icon; |
|
6404 var myPointFeature = new OpenLayers.Feature.Vector( myPoint, null, myPointStyle ); |
|
6405 if ( myMarker.text ) { |
|
6406 myPointFeature.attributes = { |
|
6407 text: myMarker.text |
|
6408 }; |
|
6409 } |
|
6410 pointLayer.addFeatures( [ myPointFeature ] ); |
|
6411 } |
|
6412 } |
|
6413 } |
|
6414 } |
|
6415 }; |
|
6416 |
|
6417 isReady(); |
|
6418 }, |
|
6419 |
|
6420 /** |
|
6421 * @member openmap |
|
6422 * The start function will be executed when the currentTime |
|
6423 * of the video reaches the start time provided by the |
|
6424 * options variable |
|
6425 */ |
|
6426 start: function( event, options ) { |
|
6427 toggle( options, "block" ); |
|
6428 }, |
|
6429 |
|
6430 /** |
|
6431 * @member openmap |
|
6432 * The end function will be executed when the currentTime |
|
6433 * of the video reaches the end time provided by the |
|
6434 * options variable |
|
6435 */ |
|
6436 end: function( event, options ) { |
|
6437 toggle( options, "none" ); |
|
6438 }, |
|
6439 |
|
6440 _teardown: function( options ) { |
|
6441 |
|
6442 target && target.removeChild( newdiv ); |
|
6443 newdiv = map = centerlonlat = projection = displayProjection = pointLayer = selectControl = popup = null; |
|
6444 } |
|
6445 }; |
|
6446 }, |
|
6447 { |
|
6448 about:{ |
|
6449 name: "Popcorn OpenMap Plugin", |
|
6450 version: "0.3", |
|
6451 author: "@mapmeld", |
|
6452 website: "mapadelsur.blogspot.com" |
|
6453 }, |
|
6454 options:{ |
|
6455 start: { |
|
6456 elem: "input", |
|
6457 type: "number", |
|
6458 label: "Start" |
|
6459 }, |
|
6460 end: { |
|
6461 elem: "input", |
|
6462 type: "number", |
|
6463 label: "End" |
|
6464 }, |
|
6465 target: "map-container", |
|
6466 type: { |
|
6467 elem: "select", |
|
6468 options: [ "ROADMAP", "SATELLITE", "TERRAIN" ], |
|
6469 label: "Map Type", |
|
6470 optional: true |
|
6471 }, |
|
6472 zoom: { |
|
6473 elem: "input", |
|
6474 type: "number", |
|
6475 label: "Zoom", |
|
6476 "default": 2 |
|
6477 }, |
|
6478 lat: { |
|
6479 elem: "input", |
|
6480 type: "text", |
|
6481 label: "Lat", |
|
6482 optional: true |
|
6483 }, |
|
6484 lng: { |
|
6485 elem: "input", |
|
6486 type: "text", |
|
6487 label: "Lng", |
|
6488 optional: true |
|
6489 }, |
|
6490 location: { |
|
6491 elem: "input", |
|
6492 type: "text", |
|
6493 label: "Location", |
|
6494 "default": "Toronto, Ontario, Canada" |
|
6495 }, |
|
6496 markers: { |
|
6497 elem: "input", |
|
6498 type: "text", |
|
6499 label: "List Markers", |
|
6500 optional: true |
|
6501 } |
|
6502 } |
|
6503 }); |
|
6504 }) ( Popcorn ); |
|
6505 /** |
|
6506 * Pause Popcorn Plug-in |
|
6507 * |
|
6508 * When this plugin is used, links on the webpage, when clicked, will pause |
|
6509 * popcorn videos that especified 'pauseOnLinkClicked' as an option. Links may |
|
6510 * cause a new page to display on a new window, or may cause a new page to |
|
6511 * display in the current window, in which case the videos won't be available |
|
6512 * anymore. It only affects anchor tags. It does not affect objects with click |
|
6513 * events that act as anchors. |
|
6514 * |
|
6515 * Example: |
|
6516 var p = Popcorn('#video', { pauseOnLinkClicked : true } ) |
|
6517 .play(); |
|
6518 * |
|
6519 */ |
|
6520 |
|
6521 document.addEventListener( "click", function( event ) { |
|
6522 |
|
6523 var targetElement = event.target; |
|
6524 |
|
6525 //Some browsers use an element as the target, some use the text node inside |
|
6526 if ( targetElement.nodeName === "A" || targetElement.parentNode && targetElement.parentNode.nodeName === "A" ) { |
|
6527 Popcorn.instances.forEach( function( video ) { |
|
6528 if ( video.options.pauseOnLinkClicked ) { |
|
6529 video.pause(); |
|
6530 } |
|
6531 }); |
|
6532 } |
|
6533 }, false ); |
|
6534 // PLUGIN: Wordriver |
|
6535 |
|
6536 (function ( Popcorn ) { |
|
6537 |
|
6538 var container = {}, |
|
6539 spanLocation = 0, |
|
6540 setupContainer = function( target ) { |
|
6541 |
|
6542 container[ target ] = document.createElement( "div" ); |
|
6543 |
|
6544 var t = document.getElementById( target ); |
|
6545 t && t.appendChild( container[ target ] ); |
|
6546 |
|
6547 container[ target ].style.height = "100%"; |
|
6548 container[ target ].style.position = "relative"; |
|
6549 |
|
6550 return container[ target ]; |
|
6551 }, |
|
6552 // creates an object of supported, cross platform css transitions |
|
6553 span = document.createElement( "span" ), |
|
6554 prefixes = [ "webkit", "Moz", "ms", "O", "" ], |
|
6555 specProp = [ "Transform", "TransitionDuration", "TransitionTimingFunction" ], |
|
6556 supports = {}, |
|
6557 prop; |
|
6558 |
|
6559 document.getElementsByTagName( "head" )[ 0 ].appendChild( span ); |
|
6560 |
|
6561 for ( var sIdx = 0, sLen = specProp.length; sIdx < sLen; sIdx++ ) { |
|
6562 |
|
6563 for ( var pIdx = 0, pLen = prefixes.length; pIdx < pLen; pIdx++ ) { |
|
6564 |
|
6565 prop = prefixes[ pIdx ] + specProp[ sIdx ]; |
|
6566 |
|
6567 if ( prop in span.style ) { |
|
6568 |
|
6569 supports[ specProp[ sIdx ].toLowerCase() ] = prop; |
|
6570 break; |
|
6571 } |
|
6572 } |
|
6573 } |
|
6574 |
|
6575 // Garbage collect support test span |
|
6576 document.getElementsByTagName( "head" )[ 0 ].appendChild( span ); |
|
6577 |
|
6578 /** |
|
6579 * Word River popcorn plug-in |
|
6580 * Displays a string of text, fading it in and out |
|
6581 * while transitioning across the height of the parent container |
|
6582 * for the duration of the instance (duration = end - start) |
|
6583 * |
|
6584 * @param {Object} options |
|
6585 * |
|
6586 * Example: |
|
6587 var p = Popcorn( '#video' ) |
|
6588 .wordriver({ |
|
6589 start: 5, // When to begin the Word River animation |
|
6590 end: 15, // When to finish the Word River animation |
|
6591 text: 'Hello World', // The text you want to be displayed by Word River |
|
6592 target: 'wordRiverDiv', // The target div to append the text to |
|
6593 color: "blue" // The color of the text. (can be Hex value i.e. #FFFFFF ) |
|
6594 } ) |
|
6595 * |
|
6596 */ |
|
6597 |
|
6598 Popcorn.plugin( "wordriver" , { |
|
6599 |
|
6600 manifest: { |
|
6601 about:{ |
|
6602 name: "Popcorn WordRiver Plugin" |
|
6603 }, |
|
6604 options: { |
|
6605 start: { |
|
6606 elem: "input", |
|
6607 type: "number", |
|
6608 label: "Start" |
|
6609 }, |
|
6610 end: { |
|
6611 elem: "input", |
|
6612 type: "number", |
|
6613 label: "End" |
|
6614 }, |
|
6615 target: "wordriver-container", |
|
6616 text: { |
|
6617 elem: "input", |
|
6618 type: "text", |
|
6619 label: "Text", |
|
6620 "default": "Popcorn.js" |
|
6621 }, |
|
6622 color: { |
|
6623 elem: "input", |
|
6624 type: "text", |
|
6625 label: "Color", |
|
6626 "default": "Green", |
|
6627 optional: true |
|
6628 } |
|
6629 } |
|
6630 }, |
|
6631 |
|
6632 _setup: function( options ) { |
|
6633 |
|
6634 options._duration = options.end - options.start; |
|
6635 options._container = container[ options.target ] || setupContainer( options.target ); |
|
6636 |
|
6637 options.word = document.createElement( "span" ); |
|
6638 options.word.style.position = "absolute"; |
|
6639 |
|
6640 options.word.style.whiteSpace = "nowrap"; |
|
6641 options.word.style.opacity = 0; |
|
6642 |
|
6643 options.word.style.MozTransitionProperty = "opacity, -moz-transform"; |
|
6644 options.word.style.webkitTransitionProperty = "opacity, -webkit-transform"; |
|
6645 options.word.style.OTransitionProperty = "opacity, -o-transform"; |
|
6646 options.word.style.transitionProperty = "opacity, transform"; |
|
6647 |
|
6648 options.word.style[ supports.transitionduration ] = 1 + "s, " + options._duration + "s"; |
|
6649 options.word.style[ supports.transitiontimingfunction ] = "linear"; |
|
6650 |
|
6651 options.word.innerHTML = options.text; |
|
6652 options.word.style.color = options.color || "black"; |
|
6653 }, |
|
6654 start: function( event, options ){ |
|
6655 |
|
6656 options._container.appendChild( options.word ); |
|
6657 |
|
6658 // Resets the transform when changing to a new currentTime before the end event occurred. |
|
6659 options.word.style[ supports.transform ] = ""; |
|
6660 |
|
6661 options.word.style.fontSize = ~~( 30 + 20 * Math.random() ) + "px"; |
|
6662 spanLocation = spanLocation % ( options._container.offsetWidth - options.word.offsetWidth ); |
|
6663 options.word.style.left = spanLocation + "px"; |
|
6664 spanLocation += options.word.offsetWidth + 10; |
|
6665 options.word.style[ supports.transform ] = "translateY(" + |
|
6666 ( options._container.offsetHeight - options.word.offsetHeight ) + "px)"; |
|
6667 |
|
6668 options.word.style.opacity = 1; |
|
6669 |
|
6670 // automatically clears the word based on time |
|
6671 setTimeout( function() { |
|
6672 |
|
6673 options.word.style.opacity = 0; |
|
6674 // ensures at least one second exists, because the fade animation is 1 second |
|
6675 }, ( ( (options.end - options.start) - 1 ) || 1 ) * 1000 ); |
|
6676 }, |
|
6677 end: function( event, options ){ |
|
6678 |
|
6679 // manually clears the word based on user interaction |
|
6680 options.word.style.opacity = 0; |
|
6681 }, |
|
6682 _teardown: function( options ) { |
|
6683 |
|
6684 var target = document.getElementById( options.target ); |
|
6685 // removes word span from generated container |
|
6686 options.word.parentNode && options._container.removeChild( options.word ); |
|
6687 |
|
6688 // if no more word spans exist in container, remove container |
|
6689 container[ options.target ] && |
|
6690 !container[ options.target ].childElementCount && |
|
6691 target && target.removeChild( container[ options.target ] ) && |
|
6692 delete container[ options.target ]; |
|
6693 } |
|
6694 }); |
|
6695 |
|
6696 })( Popcorn ); |
|
6697 // PLUGIN: Timeline |
|
6698 (function ( Popcorn ) { |
|
6699 |
|
6700 /** |
|
6701 * timeline popcorn plug-in |
|
6702 * Adds data associated with a certain time in the video, which creates a scrolling view of each item as the video progresses |
|
6703 * Options parameter will need a start, target, title, and text |
|
6704 * -Start is the time that you want this plug-in to execute |
|
6705 * -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 ) |
|
6706 * -Target is the id of the DOM element that you want the timeline to appear in. This element must be in the DOM |
|
6707 * -Title is the title of the current timeline box |
|
6708 * -Text is text is simply related text that will be displayed |
|
6709 * -innerHTML gives the user the option to add things such as links, buttons and so on |
|
6710 * -direction specifies whether the timeline will grow from the top or the bottom, receives input as "UP" or "DOWN" |
|
6711 * @param {Object} options |
|
6712 * |
|
6713 * Example: |
|
6714 var p = Popcorn("#video") |
|
6715 .timeline( { |
|
6716 start: 5, // seconds |
|
6717 target: "timeline", |
|
6718 title: "Seneca", |
|
6719 text: "Welcome to seneca", |
|
6720 innerHTML: "Click this link <a href='http://www.google.ca'>Google</a>" |
|
6721 } ) |
|
6722 * |
|
6723 */ |
|
6724 |
|
6725 var i = 1; |
|
6726 |
|
6727 Popcorn.plugin( "timeline" , function( options ) { |
|
6728 |
|
6729 var target = document.getElementById( options.target ), |
|
6730 contentDiv = document.createElement( "div" ), |
|
6731 container, |
|
6732 goingUp = true; |
|
6733 |
|
6734 if ( target && !target.firstChild ) { |
|
6735 target.appendChild ( container = document.createElement( "div" ) ); |
|
6736 container.style.width = "inherit"; |
|
6737 container.style.height = "inherit"; |
|
6738 container.style.overflow = "auto"; |
|
6739 } else { |
|
6740 container = target.firstChild; |
|
6741 } |
|
6742 |
|
6743 contentDiv.style.display = "none"; |
|
6744 contentDiv.id = "timelineDiv" + i; |
|
6745 |
|
6746 // Default to up if options.direction is non-existant or not up or down |
|
6747 options.direction = options.direction || "up"; |
|
6748 if ( options.direction.toLowerCase() === "down" ) { |
|
6749 |
|
6750 goingUp = false; |
|
6751 } |
|
6752 |
|
6753 if ( target && container ) { |
|
6754 // if this isnt the first div added to the target div |
|
6755 if( goingUp ){ |
|
6756 // insert the current div before the previous div inserted |
|
6757 container.insertBefore( contentDiv, container.firstChild ); |
|
6758 } |
|
6759 else { |
|
6760 |
|
6761 container.appendChild( contentDiv ); |
|
6762 } |
|
6763 |
|
6764 } |
|
6765 |
|
6766 i++; |
|
6767 |
|
6768 // Default to empty if not used |
|
6769 //options.innerHTML = options.innerHTML || ""; |
|
6770 |
|
6771 contentDiv.innerHTML = "<p><span id='big' style='font-size:24px; line-height: 130%;' >" + options.title + "</span><br />" + |
|
6772 "<span id='mid' style='font-size: 16px;'>" + options.text + "</span><br />" + options.innerHTML; |
|
6773 |
|
6774 return { |
|
6775 |
|
6776 start: function( event, options ) { |
|
6777 contentDiv.style.display = "block"; |
|
6778 |
|
6779 if( options.direction === "down" ) { |
|
6780 container.scrollTop = container.scrollHeight; |
|
6781 } |
|
6782 }, |
|
6783 |
|
6784 end: function( event, options ) { |
|
6785 contentDiv.style.display = "none"; |
|
6786 }, |
|
6787 |
|
6788 _teardown: function( options ) { |
|
6789 |
|
6790 ( container && contentDiv ) && container.removeChild( contentDiv ) && !container.firstChild && target.removeChild( container ); |
|
6791 } |
|
6792 }; |
|
6793 }, |
|
6794 { |
|
6795 |
|
6796 about: { |
|
6797 name: "Popcorn Timeline Plugin", |
|
6798 version: "0.1", |
|
6799 author: "David Seifried @dcseifried", |
|
6800 website: "dseifried.wordpress.com" |
|
6801 }, |
|
6802 |
|
6803 options: { |
|
6804 start: { |
|
6805 elem: "input", |
|
6806 type: "number", |
|
6807 label: "Start" |
|
6808 }, |
|
6809 end: { |
|
6810 elem: "input", |
|
6811 type: "number", |
|
6812 label: "End" |
|
6813 }, |
|
6814 target: "feed-container", |
|
6815 title: { |
|
6816 elem: "input", |
|
6817 type: "text", |
|
6818 label: "Title" |
|
6819 }, |
|
6820 text: { |
|
6821 elem: "input", |
|
6822 type: "text", |
|
6823 label: "Text" |
|
6824 }, |
|
6825 innerHTML: { |
|
6826 elem: "input", |
|
6827 type: "text", |
|
6828 label: "HTML Code", |
|
6829 optional: true |
|
6830 }, |
|
6831 direction: { |
|
6832 elem: "select", |
|
6833 options: [ "DOWN", "UP" ], |
|
6834 label: "Direction", |
|
6835 optional: true |
|
6836 } |
|
6837 } |
|
6838 }); |
|
6839 |
|
6840 })( Popcorn ); |
|
6841 // PLUGIN: documentcloud |
|
6842 |
|
6843 (function( Popcorn, document ) { |
|
6844 |
|
6845 /** |
|
6846 * Document Cloud popcorn plug-in |
|
6847 * |
|
6848 * @param {Object} options |
|
6849 * |
|
6850 * Example: |
|
6851 * var p = Popcorn("#video") |
|
6852 * // Let the pdf plugin load your PDF file for you using pdfUrl. |
|
6853 * .documentcloud({ |
|
6854 * start: 45 |
|
6855 * url: "http://www.documentcloud.org/documents/70050-urbina-day-1-in-progress.html", // or .js |
|
6856 * width: ..., |
|
6857 * height: ..., |
|
6858 * zoom: ..., |
|
6859 * page: ..., |
|
6860 * container: ... |
|
6861 * }); |
|
6862 |
|
6863 api - https://github.com/documentcloud/document-viewer/blob/master/public/javascripts/DV/controllers/api.js |
|
6864 |
|
6865 */ |
|
6866 |
|
6867 // track registered plugins by document |
|
6868 var documentRegistry = {}; |
|
6869 |
|
6870 Popcorn.plugin( "documentcloud", { |
|
6871 |
|
6872 manifest: { |
|
6873 about: { |
|
6874 name: "Popcorn Document Cloud Plugin", |
|
6875 version: "0.1", |
|
6876 author: "@humphd, @ChrisDeCairos", |
|
6877 website: "http://vocamus.net/dave" |
|
6878 }, |
|
6879 options: { |
|
6880 start: { |
|
6881 elem: "input", |
|
6882 type: "number", |
|
6883 label: "Start" |
|
6884 }, |
|
6885 end: { |
|
6886 elem: "input", |
|
6887 type: "number", |
|
6888 label: "End" |
|
6889 }, |
|
6890 target: "documentcloud-container", |
|
6891 width: { |
|
6892 elem: "input", |
|
6893 type: "text", |
|
6894 label: "Width", |
|
6895 optional: true |
|
6896 }, |
|
6897 height: { |
|
6898 elem: "input", |
|
6899 type: "text", |
|
6900 label: "Height", |
|
6901 optional: true |
|
6902 }, |
|
6903 src: { |
|
6904 elem: "input", |
|
6905 type: "url", |
|
6906 label: "PDF URL", |
|
6907 "default": "http://www.documentcloud.org/documents/70050-urbina-day-1-in-progress.html" |
|
6908 }, |
|
6909 preload: { |
|
6910 elem: "input", |
|
6911 type: "checkbox", |
|
6912 label: "Preload", |
|
6913 "default": true |
|
6914 }, |
|
6915 page: { |
|
6916 elem: "input", |
|
6917 type: "number", |
|
6918 label: "Page Number", |
|
6919 optional: true |
|
6920 }, |
|
6921 aid: { |
|
6922 elem: "input", |
|
6923 type: "number", |
|
6924 label: "Annotation Id", |
|
6925 optional: true |
|
6926 } |
|
6927 } |
|
6928 }, |
|
6929 |
|
6930 _setup: function( options ) { |
|
6931 var DV = window.DV = window.DV || {}, |
|
6932 that = this; |
|
6933 |
|
6934 //setup elem... |
|
6935 function load() { |
|
6936 DV.loaded = false; |
|
6937 // swap .html URL to .js for API call |
|
6938 var url = options.url.replace( /\.html$/, ".js" ), |
|
6939 target = options.target, |
|
6940 targetDiv = document.getElementById( target ), |
|
6941 containerDiv = document.createElement( "div" ), |
|
6942 containerDivSize = Popcorn.position( targetDiv ), |
|
6943 // need to use size of div if not given |
|
6944 width = options.width || containerDivSize.width, |
|
6945 height = options.height || containerDivSize.height, |
|
6946 sidebar = options.sidebar || true, |
|
6947 text = options.text || true, |
|
6948 pdf = options.pdf || true, |
|
6949 showAnnotations = options.showAnnotations || true, |
|
6950 zoom = options.zoom || 700, |
|
6951 search = options.search || true, |
|
6952 page = options.page, |
|
6953 container; |
|
6954 |
|
6955 function setOptions( viewer ) { |
|
6956 options._key = viewer.api.getId(); |
|
6957 |
|
6958 options._changeView = function ( viewer ) { |
|
6959 if ( options.aid ) { |
|
6960 viewer.pageSet.showAnnotation( viewer.api.getAnnotation( options.aid ) ); |
|
6961 } else { |
|
6962 viewer.api.setCurrentPage( options.page ); |
|
6963 } |
|
6964 }; |
|
6965 } |
|
6966 |
|
6967 function documentIsLoaded( url ) { |
|
6968 var found = false; |
|
6969 Popcorn.forEach( DV.viewers, function( viewer, idx ) { |
|
6970 if( viewer.api.getSchema().canonicalURL === url ) { |
|
6971 var targetDoc; |
|
6972 setOptions( viewer ); |
|
6973 targetDoc = documentRegistry[ options._key ]; |
|
6974 options._containerId = targetDoc.id; |
|
6975 targetDoc.num += 1; |
|
6976 found = true; |
|
6977 DV.loaded = true; |
|
6978 } |
|
6979 }); |
|
6980 return found; |
|
6981 } |
|
6982 |
|
6983 function createRegistryEntry() { |
|
6984 var entry = { |
|
6985 num: 1, |
|
6986 id: options._containerId |
|
6987 }; |
|
6988 documentRegistry[ options._key ] = entry; |
|
6989 DV.loaded = true; |
|
6990 } |
|
6991 |
|
6992 if ( !documentIsLoaded( options.url ) ) { |
|
6993 |
|
6994 containerDiv.id = options._containerId = Popcorn.guid( target ); |
|
6995 container = "#" + containerDiv.id; |
|
6996 targetDiv.appendChild( containerDiv ); |
|
6997 that.trigger( "documentready" ); |
|
6998 |
|
6999 // Figure out if we need a callback to change the page # |
|
7000 var afterLoad = options.page || options.aid ? |
|
7001 function( viewer ) { |
|
7002 setOptions( viewer ); |
|
7003 options._changeView( viewer ); |
|
7004 containerDiv.style.visibility = "hidden"; |
|
7005 viewer.elements.pages.hide(); |
|
7006 createRegistryEntry(); |
|
7007 } : |
|
7008 function( viewer ) { |
|
7009 setOptions( viewer ); |
|
7010 createRegistryEntry(); |
|
7011 containerDiv.style.visibility = "hidden"; |
|
7012 viewer.elements.pages.hide(); |
|
7013 }; |
|
7014 DV.load( url, { |
|
7015 width: width, |
|
7016 height: height, |
|
7017 sidebar: sidebar, |
|
7018 text: text, |
|
7019 pdf: pdf, |
|
7020 showAnnotations: showAnnotations, |
|
7021 zoom: zoom, |
|
7022 search: search, |
|
7023 container: container, |
|
7024 afterLoad: afterLoad |
|
7025 }); |
|
7026 } |
|
7027 } |
|
7028 function readyCheck() { |
|
7029 if( window.DV.loaded ) { |
|
7030 load(); |
|
7031 } else { |
|
7032 setTimeout( readyCheck, 25 ); |
|
7033 } |
|
7034 } |
|
7035 |
|
7036 // If the viewer is already loaded, don't repeat the process. |
|
7037 if ( !DV.loading ) { |
|
7038 DV.loading = true; |
|
7039 DV.recordHit = "//www.documentcloud.org/pixel.gif"; |
|
7040 |
|
7041 var link = document.createElement( "link" ), |
|
7042 head = document.getElementsByTagName( "head" )[ 0 ]; |
|
7043 |
|
7044 link.rel = "stylesheet"; |
|
7045 link.type = "text/css"; |
|
7046 link.media = "screen"; |
|
7047 link.href = "//s3.documentcloud.org/viewer/viewer-datauri.css"; |
|
7048 |
|
7049 head.appendChild( link ); |
|
7050 |
|
7051 // Record the fact that the viewer is loaded. |
|
7052 DV.loaded = false; |
|
7053 |
|
7054 // Request the viewer JavaScript. |
|
7055 Popcorn.getScript( "http://s3.documentcloud.org/viewer/viewer.js", function() { |
|
7056 DV.loading = false; |
|
7057 load(); |
|
7058 }); |
|
7059 } else { |
|
7060 |
|
7061 readyCheck(); |
|
7062 } |
|
7063 |
|
7064 }, |
|
7065 |
|
7066 start: function( event, options ) { |
|
7067 var elem = document.getElementById( options._containerId ), |
|
7068 viewer = DV.viewers[ options._key ]; |
|
7069 ( options.page || options.aid ) && viewer && |
|
7070 options._changeView( viewer ); |
|
7071 |
|
7072 if ( elem && viewer) { |
|
7073 elem.style.visibility = "visible"; |
|
7074 viewer.elements.pages.show(); |
|
7075 } |
|
7076 }, |
|
7077 |
|
7078 end: function( event, options ) { |
|
7079 var elem = document.getElementById( options._containerId ); |
|
7080 |
|
7081 if ( elem && DV.viewers[ options._key ] ) { |
|
7082 elem.style.visibility = "hidden"; |
|
7083 DV.viewers[ options._key ].elements.pages.hide(); |
|
7084 } |
|
7085 }, |
|
7086 |
|
7087 _teardown: function( options ) { |
|
7088 var elem = document.getElementById( options._containerId ), |
|
7089 key = options._key; |
|
7090 if ( key && DV.viewers[ key ] && --documentRegistry[ key ].num === 0 ) { |
|
7091 DV.viewers[ key ].api.unload(); |
|
7092 |
|
7093 while ( elem.hasChildNodes() ) { |
|
7094 elem.removeChild( elem.lastChild ); |
|
7095 } |
|
7096 elem.parentNode.removeChild( elem ); |
|
7097 } |
|
7098 } |
|
7099 }); |
|
7100 })( Popcorn, window.document ); |
|
7101 // PARSER: 0.3 JSON |
|
7102 |
|
7103 (function (Popcorn) { |
|
7104 Popcorn.parser( "parseJSON", "JSON", function( data ) { |
|
7105 |
|
7106 // declare needed variables |
|
7107 var retObj = { |
|
7108 title: "", |
|
7109 remote: "", |
|
7110 data: [] |
|
7111 }, |
|
7112 manifestData = {}, |
|
7113 dataObj = data; |
|
7114 |
|
7115 |
|
7116 /* |
|
7117 TODO: add support for filling in source children of the video element |
|
7118 |
|
7119 |
|
7120 remote: [ |
|
7121 { |
|
7122 src: "whatever.mp4", |
|
7123 type: 'video/mp4; codecs="avc1, mp4a"' |
|
7124 }, |
|
7125 { |
|
7126 src: "whatever.ogv", |
|
7127 type: 'video/ogg; codecs="theora, vorbis"' |
|
7128 } |
|
7129 ] |
|
7130 |
|
7131 */ |
|
7132 |
|
7133 |
|
7134 Popcorn.forEach( dataObj.data, function ( obj, key ) { |
|
7135 retObj.data.push( obj ); |
|
7136 }); |
|
7137 |
|
7138 return retObj; |
|
7139 }); |
|
7140 |
|
7141 })( Popcorn ); |
|
7142 // PARSER: 0.1 SBV |
|
7143 |
|
7144 (function (Popcorn) { |
|
7145 |
|
7146 /** |
|
7147 * SBV popcorn parser plug-in |
|
7148 * Parses subtitle files in the SBV format. |
|
7149 * Times are expected in H:MM:SS.MIL format, with hours optional |
|
7150 * Subtitles which don't match expected format are ignored |
|
7151 * Data parameter is given by Popcorn, will need a text. |
|
7152 * Text is the file contents to be parsed |
|
7153 * |
|
7154 * @param {Object} data |
|
7155 * |
|
7156 * Example: |
|
7157 0:00:02.400,0:00:07.200 |
|
7158 Senator, we're making our final approach into Coruscant. |
|
7159 */ |
|
7160 Popcorn.parser( "parseSBV", function( data ) { |
|
7161 |
|
7162 // declare needed variables |
|
7163 var retObj = { |
|
7164 title: "", |
|
7165 remote: "", |
|
7166 data: [] |
|
7167 }, |
|
7168 subs = [], |
|
7169 lines, |
|
7170 i = 0, |
|
7171 len = 0, |
|
7172 idx = 0; |
|
7173 |
|
7174 // [H:]MM:SS.MIL string to SS.MIL |
|
7175 // Will thrown exception on bad time format |
|
7176 var toSeconds = function( t_in ) { |
|
7177 var t = t_in.split( ":" ), |
|
7178 l = t.length-1, |
|
7179 time; |
|
7180 |
|
7181 try { |
|
7182 time = parseInt( t[l-1], 10 )*60 + parseFloat( t[l], 10 ); |
|
7183 |
|
7184 // Hours optionally given |
|
7185 if ( l === 2 ) { |
|
7186 time += parseInt( t[0], 10 )*3600; |
|
7187 } |
|
7188 } catch ( e ) { |
|
7189 throw "Bad cue"; |
|
7190 } |
|
7191 |
|
7192 return time; |
|
7193 }; |
|
7194 |
|
7195 var createTrack = function( name, attributes ) { |
|
7196 var track = {}; |
|
7197 track[name] = attributes; |
|
7198 return track; |
|
7199 }; |
|
7200 |
|
7201 // Here is where the magic happens |
|
7202 // Split on line breaks |
|
7203 lines = data.text.split( /(?:\r\n|\r|\n)/gm ); |
|
7204 len = lines.length; |
|
7205 |
|
7206 while ( i < len ) { |
|
7207 var sub = {}, |
|
7208 text = [], |
|
7209 time = lines[i++].split( "," ); |
|
7210 |
|
7211 try { |
|
7212 sub.start = toSeconds( time[0] ); |
|
7213 sub.end = toSeconds( time[1] ); |
|
7214 |
|
7215 // Gather all lines of text |
|
7216 while ( i < len && lines[i] ) { |
|
7217 text.push( lines[i++] ); |
|
7218 } |
|
7219 |
|
7220 // Join line breaks in text |
|
7221 sub.text = text.join( "<br />" ); |
|
7222 subs.push( createTrack( "subtitle", sub ) ); |
|
7223 } catch ( e ) { |
|
7224 // Bad cue, advance to end of cue |
|
7225 while ( i < len && lines[i] ) { |
|
7226 i++; |
|
7227 } |
|
7228 } |
|
7229 |
|
7230 // Consume empty whitespace |
|
7231 while ( i < len && !lines[i] ) { |
|
7232 i++; |
|
7233 } |
|
7234 } |
|
7235 |
|
7236 retObj.data = subs; |
|
7237 |
|
7238 return retObj; |
|
7239 }); |
|
7240 |
|
7241 })( Popcorn ); |
|
7242 // PARSER: 0.3 SRT |
|
7243 (function (Popcorn) { |
|
7244 /** |
|
7245 * SRT popcorn parser plug-in |
|
7246 * Parses subtitle files in the SRT format. |
|
7247 * Times are expected in HH:MM:SS,MIL format, though HH:MM:SS.MIL also supported |
|
7248 * Ignore styling, which may occur after the end time or in-text |
|
7249 * While not part of the "official" spec, majority of players support HTML and SSA styling tags |
|
7250 * SSA-style tags are stripped, HTML style tags are left for the browser to handle: |
|
7251 * HTML: <font>, <b>, <i>, <u>, <s> |
|
7252 * SSA: \N or \n, {\cmdArg1}, {\cmd(arg1, arg2, ...)} |
|
7253 |
|
7254 * Data parameter is given by Popcorn, will need a text. |
|
7255 * Text is the file contents to be parsed |
|
7256 * |
|
7257 * @param {Object} data |
|
7258 * |
|
7259 * Example: |
|
7260 1 |
|
7261 00:00:25,712 --> 00:00:30.399 |
|
7262 This text is <font color="red">RED</font> and has not been {\pos(142,120)} positioned. |
|
7263 This takes \Nup three \nentire lines. |
|
7264 This contains nested <b>bold, <i>italic, <u>underline</u> and <s>strike-through</s></u></i></b> HTML tags |
|
7265 Unclosed but <b>supported tags are left in |
|
7266 <ggg>Unsupported</ggg> HTML tags are left in, even if <hhh>not closed. |
|
7267 SSA tags with {\i1} would open and close italicize {\i0}, but are stripped |
|
7268 Multiple {\pos(142,120)\b1}SSA tags are stripped |
|
7269 */ |
|
7270 Popcorn.parser( "parseSRT", function( data ) { |
|
7271 |
|
7272 // declare needed variables |
|
7273 var retObj = { |
|
7274 title: "", |
|
7275 remote: "", |
|
7276 data: [] |
|
7277 }, |
|
7278 subs = [], |
|
7279 i = 0, |
|
7280 idx = 0, |
|
7281 lines, |
|
7282 time, |
|
7283 text, |
|
7284 endIdx, |
|
7285 sub; |
|
7286 |
|
7287 // Here is where the magic happens |
|
7288 // Split on line breaks |
|
7289 lines = data.text.split( /(?:\r\n|\r|\n)/gm ); |
|
7290 endIdx = lastNonEmptyLine( lines ) + 1; |
|
7291 |
|
7292 for( i=0; i < endIdx; i++ ) { |
|
7293 sub = {}; |
|
7294 text = []; |
|
7295 |
|
7296 sub.id = parseInt( lines[i++], 10 ); |
|
7297 |
|
7298 // Split on '-->' delimiter, trimming spaces as well |
|
7299 time = lines[i++].split( /[\t ]*-->[\t ]*/ ); |
|
7300 |
|
7301 sub.start = toSeconds( time[0] ); |
|
7302 |
|
7303 // So as to trim positioning information from end |
|
7304 idx = time[1].indexOf( " " ); |
|
7305 if ( idx !== -1) { |
|
7306 time[1] = time[1].substr( 0, idx ); |
|
7307 } |
|
7308 sub.end = toSeconds( time[1] ); |
|
7309 |
|
7310 // Build single line of text from multi-line subtitle in file |
|
7311 while ( i < endIdx && lines[i] ) { |
|
7312 text.push( lines[i++] ); |
|
7313 } |
|
7314 |
|
7315 // Join into 1 line, SSA-style linebreaks |
|
7316 // Strip out other SSA-style tags |
|
7317 sub.text = text.join( "\\N" ).replace( /\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi, "" ); |
|
7318 |
|
7319 // Escape HTML entities |
|
7320 sub.text = sub.text.replace( /</g, "<" ).replace( />/g, ">" ); |
|
7321 |
|
7322 // Unescape great than and less than when it makes a valid html tag of a supported style (font, b, u, s, i) |
|
7323 // Modified version of regex from Phil Haack's blog: http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx |
|
7324 // Later modified by kev: http://kevin.deldycke.com/2007/03/ultimate-regular-expression-for-html-tag-parsing-with-php/ |
|
7325 sub.text = sub.text.replace( /<(\/?(font|b|u|i|s))((\s+(\w|\w[\w\-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)(\/?)>/gi, "<$1$3$7>" ); |
|
7326 sub.text = sub.text.replace( /\\N/gi, "<br />" ); |
|
7327 subs.push( createTrack( "subtitle", sub ) ); |
|
7328 } |
|
7329 |
|
7330 retObj.data = subs; |
|
7331 return retObj; |
|
7332 }); |
|
7333 |
|
7334 function createTrack( name, attributes ) { |
|
7335 var track = {}; |
|
7336 track[name] = attributes; |
|
7337 return track; |
|
7338 } |
|
7339 |
|
7340 // Simple function to convert HH:MM:SS,MMM or HH:MM:SS.MMM to SS.MMM |
|
7341 // Assume valid, returns 0 on error |
|
7342 function toSeconds( t_in ) { |
|
7343 var t = t_in.split( ':' ); |
|
7344 |
|
7345 try { |
|
7346 var s = t[2].split( ',' ); |
|
7347 |
|
7348 // Just in case a . is decimal seperator |
|
7349 if ( s.length === 1 ) { |
|
7350 s = t[2].split( '.' ); |
|
7351 } |
|
7352 |
|
7353 return parseFloat( t[0], 10 ) * 3600 + parseFloat( t[1], 10 ) * 60 + parseFloat( s[0], 10 ) + parseFloat( s[1], 10 ) / 1000; |
|
7354 } catch ( e ) { |
|
7355 return 0; |
|
7356 } |
|
7357 } |
|
7358 |
|
7359 function lastNonEmptyLine( linesArray ) { |
|
7360 var idx = linesArray.length - 1; |
|
7361 |
|
7362 while ( idx >= 0 && !linesArray[idx] ) { |
|
7363 idx--; |
|
7364 } |
|
7365 |
|
7366 return idx; |
|
7367 } |
|
7368 })( Popcorn ); |
|
7369 // PARSER: 0.3 SSA/ASS |
|
7370 |
|
7371 (function ( Popcorn ) { |
|
7372 /** |
|
7373 * SSA/ASS popcorn parser plug-in |
|
7374 * Parses subtitle files in the identical SSA and ASS formats. |
|
7375 * Style information is ignored, and may be found in these |
|
7376 * formats: (\N \n {\pos(400,570)} {\kf89}) |
|
7377 * Out of the [Script Info], [V4 Styles], [Events], [Pictures], |
|
7378 * and [Fonts] sections, only [Events] is processed. |
|
7379 * Data parameter is given by Popcorn, will need a text. |
|
7380 * Text is the file contents to be parsed |
|
7381 * |
|
7382 * @param {Object} data |
|
7383 * |
|
7384 * Example: |
|
7385 [Script Info] |
|
7386 Title: Testing subtitles for the SSA Format |
|
7387 [V4 Styles] |
|
7388 Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding |
|
7389 Style: Default,Arial,20,65535,65535,65535,-2147483640,-1,0,1,3,0,2,30,30,30,0,0 |
|
7390 [Events] |
|
7391 Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text |
|
7392 Dialogue: 0,0:00:02.40,0:00:07.20,Default,,0000,0000,0000,,Senator, {\kf89}we're \Nmaking our final \napproach into Coruscant. |
|
7393 Dialogue: 0,0:00:09.71,0:00:13.39,Default,,0000,0000,0000,,{\pos(400,570)}Very good, Lieutenant. |
|
7394 Dialogue: 0,0:00:15.04,0:00:18.04,Default,,0000,0000,0000,,It's \Na \ntrap! |
|
7395 * |
|
7396 */ |
|
7397 |
|
7398 // Register for SSA extensions |
|
7399 Popcorn.parser( "parseSSA", function( data ) { |
|
7400 // declare needed variables |
|
7401 var retObj = { |
|
7402 title: "", |
|
7403 remote: "", |
|
7404 data: [ ] |
|
7405 }, |
|
7406 rNewLineFile = /(?:\r\n|\r|\n)/gm, |
|
7407 subs = [ ], |
|
7408 lines, |
|
7409 headers, |
|
7410 i = 0, |
|
7411 len; |
|
7412 |
|
7413 // Here is where the magic happens |
|
7414 // Split on line breaks |
|
7415 lines = data.text.split( rNewLineFile ); |
|
7416 len = lines.length; |
|
7417 |
|
7418 // Ignore non-textual info |
|
7419 while ( i < len && lines[ i ] !== "[Events]" ) { |
|
7420 i++; |
|
7421 } |
|
7422 |
|
7423 headers = parseFieldHeaders( lines[ ++i ] ); |
|
7424 |
|
7425 while ( ++i < len && lines[ i ] && lines[ i ][ 0 ] !== "[" ) { |
|
7426 try { |
|
7427 subs.push( createTrack( "subtitle", parseSub( lines[ i ], headers ) ) ); |
|
7428 } catch ( e ) {} |
|
7429 } |
|
7430 |
|
7431 retObj.data = subs; |
|
7432 return retObj; |
|
7433 }); |
|
7434 |
|
7435 function parseSub( line, headers ) { |
|
7436 // Trim beginning 'Dialogue: ' and split on delim |
|
7437 var fields = line.substr( 10 ).split( "," ), |
|
7438 rAdvancedStyles = /\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi, |
|
7439 rNewLineSSA = /\\N/gi, |
|
7440 sub; |
|
7441 |
|
7442 sub = { |
|
7443 start: toSeconds( fields[ headers.start ] ), |
|
7444 end: toSeconds( fields[ headers.end ] ) |
|
7445 }; |
|
7446 |
|
7447 // Invalid time, skip |
|
7448 if ( sub.start === -1 || sub.end === -1 ) { |
|
7449 throw "Invalid time"; |
|
7450 } |
|
7451 |
|
7452 // Eliminate advanced styles and convert forced line breaks |
|
7453 sub.text = getTextFromFields( fields, headers.text ).replace( rAdvancedStyles, "" ).replace( rNewLineSSA, "<br />" ); |
|
7454 |
|
7455 return sub; |
|
7456 } |
|
7457 |
|
7458 // h:mm:ss.cc (centisec) string to SS.mmm |
|
7459 // Returns -1 if invalid |
|
7460 function toSeconds( t_in ) { |
|
7461 var t = t_in.split( ":" ); |
|
7462 |
|
7463 // Not all there |
|
7464 if ( t_in.length !== 10 || t.length < 3 ) { |
|
7465 return -1; |
|
7466 } |
|
7467 |
|
7468 return parseInt( t[ 0 ], 10 ) * 3600 + parseInt( t[ 1 ], 10 ) * 60 + parseFloat( t[ 2 ], 10 ); |
|
7469 } |
|
7470 |
|
7471 function getTextFromFields( fields, startIdx ) { |
|
7472 var fieldLen = fields.length, |
|
7473 text = [ ], |
|
7474 i = startIdx; |
|
7475 |
|
7476 // There may be commas in the text which were split, append back together into one line |
|
7477 for( ; i < fieldLen; i++ ) { |
|
7478 text.push( fields[ i ] ); |
|
7479 } |
|
7480 |
|
7481 return text.join( "," ); |
|
7482 } |
|
7483 |
|
7484 function createTrack( name, attributes ) { |
|
7485 var track = {}; |
|
7486 track[ name ] = attributes; |
|
7487 return track; |
|
7488 } |
|
7489 |
|
7490 function parseFieldHeaders( line ) { |
|
7491 // Trim 'Format: ' off front, split on delim |
|
7492 var fields = line.substr( 8 ).split( ", " ), |
|
7493 result = {}, |
|
7494 len, |
|
7495 i; |
|
7496 |
|
7497 //Find where in Dialogue string the start, end and text info is |
|
7498 for ( i = 0, len = fields.length; i < len; i++ ) { |
|
7499 if ( fields[ i ] === "Start" ) { |
|
7500 result.start = i; |
|
7501 } else if ( fields[ i ] === "End" ) { |
|
7502 result.end = i; |
|
7503 } else if ( fields[ i ] === "Text" ) { |
|
7504 result.text = i; |
|
7505 } |
|
7506 } |
|
7507 |
|
7508 return result; |
|
7509 } |
|
7510 })( Popcorn ); |
|
7511 // PARSER: 1.0 TTML |
|
7512 (function ( Popcorn ) { |
|
7513 /** |
|
7514 * TTML popcorn parser plug-in |
|
7515 * Parses subtitle files in the TTML format. |
|
7516 * Times may be absolute to the timeline or relative |
|
7517 * Absolute times are ISO 8601 format (hh:mm:ss[.mmm]) |
|
7518 * Relative times are a fraction followed by a unit metric (d.ddu) |
|
7519 * Relative times are relative to the time given on the parent node |
|
7520 * Styling information is ignored |
|
7521 * Data parameter is given by Popcorn, will need an xml. |
|
7522 * Xml is the file contents to be processed |
|
7523 * |
|
7524 * @param {Object} data |
|
7525 * |
|
7526 * Example: |
|
7527 <tt xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling" xmlns="http://www.w3.org/2006/04/ttaf1"> |
|
7528 <body region="subtitleArea"> |
|
7529 <div> |
|
7530 <p xml:id="subtitle1" begin="0.76s" end="3.45s"> |
|
7531 It seems a paradox, does it not, |
|
7532 </p> |
|
7533 </div> |
|
7534 </body> |
|
7535 </tt> |
|
7536 */ |
|
7537 |
|
7538 var rWhitespace = /^[\s]+|[\s]+$/gm, |
|
7539 rLineBreak = /(?:\r\n|\r|\n)/gm; |
|
7540 |
|
7541 Popcorn.parser( "parseTTML", function( data ) { |
|
7542 var returnData = { |
|
7543 title: "", |
|
7544 remote: "", |
|
7545 data: [] |
|
7546 }, |
|
7547 node; |
|
7548 |
|
7549 // Null checks |
|
7550 if ( !data.xml || !data.xml.documentElement ) { |
|
7551 return returnData; |
|
7552 } |
|
7553 |
|
7554 node = data.xml.documentElement.firstChild; |
|
7555 |
|
7556 if ( !node ) { |
|
7557 return returnData; |
|
7558 } |
|
7559 |
|
7560 // Find body tag |
|
7561 while ( node.nodeName !== "body" ) { |
|
7562 node = node.nextSibling; |
|
7563 } |
|
7564 |
|
7565 if ( node ) { |
|
7566 returnData.data = parseChildren( node, 0 ); |
|
7567 } |
|
7568 |
|
7569 return returnData; |
|
7570 }); |
|
7571 |
|
7572 // Parse the children of the given node |
|
7573 function parseChildren( node, timeOffset, region ) { |
|
7574 var currNode = node.firstChild, |
|
7575 currRegion = getNodeRegion( node, region ), |
|
7576 retVal = [], |
|
7577 newOffset; |
|
7578 |
|
7579 while ( currNode ) { |
|
7580 if ( currNode.nodeType === 1 ) { |
|
7581 if ( currNode.nodeName === "p" ) { |
|
7582 // p is a textual node, process contents as subtitle |
|
7583 retVal.push( parseNode( currNode, timeOffset, currRegion ) ); |
|
7584 } else if ( currNode.nodeName === "div" ) { |
|
7585 // div is container for subtitles, recurse |
|
7586 newOffset = toSeconds( currNode.getAttribute( "begin" ) ); |
|
7587 |
|
7588 if (newOffset < 0 ) { |
|
7589 newOffset = timeOffset; |
|
7590 } |
|
7591 |
|
7592 retVal.push.apply( retVal, parseChildren( currNode, newOffset, currRegion ) ); |
|
7593 } |
|
7594 } |
|
7595 |
|
7596 currNode = currNode.nextSibling; |
|
7597 } |
|
7598 |
|
7599 return retVal; |
|
7600 } |
|
7601 |
|
7602 // Get the "region" attribute of a node, to know where to put the subtitles |
|
7603 function getNodeRegion( node, defaultTo ) { |
|
7604 var region = node.getAttribute( "region" ); |
|
7605 |
|
7606 if ( region !== null ) { |
|
7607 return region; |
|
7608 } else { |
|
7609 return defaultTo || ""; |
|
7610 } |
|
7611 } |
|
7612 |
|
7613 // Parse a node for text content |
|
7614 function parseNode( node, timeOffset, region ) { |
|
7615 var sub = {}; |
|
7616 |
|
7617 // Trim left and right whitespace from text and convert non-explicit line breaks |
|
7618 sub.text = ( node.textContent || node.text ).replace( rWhitespace, "" ).replace( rLineBreak, "<br />" ); |
|
7619 sub.id = node.getAttribute( "xml:id" ) || node.getAttribute( "id" ); |
|
7620 sub.start = toSeconds ( node.getAttribute( "begin" ), timeOffset ); |
|
7621 sub.end = toSeconds( node.getAttribute( "end" ), timeOffset ); |
|
7622 sub.target = getNodeRegion( node, region ); |
|
7623 |
|
7624 if ( sub.end < 0 ) { |
|
7625 // No end given, infer duration if possible |
|
7626 // Otherwise, give end as MAX_VALUE |
|
7627 sub.end = toSeconds( node.getAttribute( "duration" ), 0 ); |
|
7628 |
|
7629 if ( sub.end >= 0 ) { |
|
7630 sub.end += sub.start; |
|
7631 } else { |
|
7632 sub.end = Number.MAX_VALUE; |
|
7633 } |
|
7634 } |
|
7635 |
|
7636 return { subtitle : sub }; |
|
7637 } |
|
7638 |
|
7639 // Convert time expression to SS.mmm |
|
7640 // Expression may be absolute to timeline (hh:mm:ss.ms) |
|
7641 // or relative ( decimal followed by metric ) ex: 3.4s, 5.7m |
|
7642 // Returns -1 if invalid |
|
7643 function toSeconds( t_in, offset ) { |
|
7644 var i; |
|
7645 |
|
7646 if ( !t_in ) { |
|
7647 return -1; |
|
7648 } |
|
7649 |
|
7650 try { |
|
7651 return Popcorn.util.toSeconds( t_in ); |
|
7652 } catch ( e ) { |
|
7653 i = getMetricIndex( t_in ); |
|
7654 return parseFloat( t_in.substring( 0, i ) ) * getMultipler( t_in.substring( i ) ) + ( offset || 0 ); |
|
7655 } |
|
7656 } |
|
7657 |
|
7658 // In a time string such as 3.4ms, get the index of the first character (m) of the time metric (ms) |
|
7659 function getMetricIndex( t_in ) { |
|
7660 var i = t_in.length - 1; |
|
7661 |
|
7662 while ( i >= 0 && t_in[ i ] <= "9" && t_in[ i ] >= "0" ) { |
|
7663 i--; |
|
7664 } |
|
7665 |
|
7666 return i; |
|
7667 } |
|
7668 |
|
7669 // Determine multiplier for metric relative to seconds |
|
7670 function getMultipler( metric ) { |
|
7671 return { |
|
7672 "h" : 3600, |
|
7673 "m" : 60, |
|
7674 "s" : 1, |
|
7675 "ms" : 0.001 |
|
7676 }[ metric ] || -1; |
|
7677 } |
|
7678 })( Popcorn ); |
|
7679 // PARSER: 0.1 TTXT |
|
7680 |
|
7681 (function (Popcorn) { |
|
7682 |
|
7683 /** |
|
7684 * TTXT popcorn parser plug-in |
|
7685 * Parses subtitle files in the TTXT format. |
|
7686 * Style information is ignored. |
|
7687 * Data parameter is given by Popcorn, will need an xml. |
|
7688 * Xml is the file contents to be parsed as a DOM tree |
|
7689 * |
|
7690 * @param {Object} data |
|
7691 * |
|
7692 * Example: |
|
7693 <TextSample sampleTime="00:00:00.000" text=""></TextSample> |
|
7694 */ |
|
7695 Popcorn.parser( "parseTTXT", function( data ) { |
|
7696 |
|
7697 // declare needed variables |
|
7698 var returnData = { |
|
7699 title: "", |
|
7700 remote: "", |
|
7701 data: [] |
|
7702 }; |
|
7703 |
|
7704 // Simple function to convert HH:MM:SS.MMM to SS.MMM |
|
7705 // Assume valid, returns 0 on error |
|
7706 var toSeconds = function(t_in) { |
|
7707 var t = t_in.split(":"); |
|
7708 var time = 0; |
|
7709 |
|
7710 try { |
|
7711 return parseFloat(t[0], 10)*60*60 + parseFloat(t[1], 10)*60 + parseFloat(t[2], 10); |
|
7712 } catch (e) { time = 0; } |
|
7713 |
|
7714 return time; |
|
7715 }; |
|
7716 |
|
7717 // creates an object of all atrributes keyed by name |
|
7718 var createTrack = function( name, attributes ) { |
|
7719 var track = {}; |
|
7720 track[name] = attributes; |
|
7721 return track; |
|
7722 }; |
|
7723 |
|
7724 // this is where things actually start |
|
7725 var node = data.xml.lastChild.lastChild; // Last Child of TextStreamHeader |
|
7726 var lastStart = Number.MAX_VALUE; |
|
7727 var cmds = []; |
|
7728 |
|
7729 // Work backwards through DOM, processing TextSample nodes |
|
7730 while (node) { |
|
7731 if ( node.nodeType === 1 && node.nodeName === "TextSample") { |
|
7732 var sub = {}; |
|
7733 sub.start = toSeconds(node.getAttribute('sampleTime')); |
|
7734 sub.text = node.getAttribute('text'); |
|
7735 |
|
7736 if (sub.text) { // Only process if text to display |
|
7737 // Infer end time from prior element, ms accuracy |
|
7738 sub.end = lastStart - 0.001; |
|
7739 cmds.push( createTrack("subtitle", sub) ); |
|
7740 } |
|
7741 lastStart = sub.start; |
|
7742 } |
|
7743 node = node.previousSibling; |
|
7744 } |
|
7745 |
|
7746 returnData.data = cmds.reverse(); |
|
7747 |
|
7748 return returnData; |
|
7749 }); |
|
7750 |
|
7751 })( Popcorn ); |
|
7752 // PARSER: 0.3 WebSRT/VTT |
|
7753 |
|
7754 (function ( Popcorn ) { |
|
7755 /** |
|
7756 * WebVTT popcorn parser plug-in |
|
7757 * Parses subtitle files in the WebVTT format. |
|
7758 * Specification here: http://www.whatwg.org/specs/web-apps/current-work/webvtt.html |
|
7759 * Styles which appear after timing information are presently ignored. |
|
7760 * Inline styling tags follow HTML conventions and are left in for the browser to handle (or ignore if VTT-specific) |
|
7761 * Data parameter is given by Popcorn, text property holds file contents. |
|
7762 * Text is the file contents to be parsed |
|
7763 * |
|
7764 * @param {Object} data |
|
7765 * |
|
7766 * Example: |
|
7767 00:32.500 --> 00:00:33.500 A:start S:50% D:vertical L:98% |
|
7768 <v Neil DeGrass Tyson><i>Laughs</i> |
|
7769 */ |
|
7770 Popcorn.parser( "parseVTT", function( data ) { |
|
7771 |
|
7772 // declare needed variables |
|
7773 var retObj = { |
|
7774 title: "", |
|
7775 remote: "", |
|
7776 data: [] |
|
7777 }, |
|
7778 subs = [], |
|
7779 i = 0, |
|
7780 len = 0, |
|
7781 lines, |
|
7782 text, |
|
7783 sub, |
|
7784 rNewLine = /(?:\r\n|\r|\n)/gm; |
|
7785 |
|
7786 // Here is where the magic happens |
|
7787 // Split on line breaks |
|
7788 lines = data.text.split( rNewLine ); |
|
7789 len = lines.length; |
|
7790 |
|
7791 // Check for BOF token |
|
7792 if ( len === 0 || lines[ 0 ] !== "WEBVTT" ) { |
|
7793 return retObj; |
|
7794 } |
|
7795 |
|
7796 i++; |
|
7797 |
|
7798 while ( i < len ) { |
|
7799 text = []; |
|
7800 |
|
7801 try { |
|
7802 i = skipWhitespace( lines, len, i ); |
|
7803 sub = parseCueHeader( lines[ i++ ] ); |
|
7804 |
|
7805 // Build single line of text from multi-line subtitle in file |
|
7806 while ( i < len && lines[ i ] ) { |
|
7807 text.push( lines[ i++ ] ); |
|
7808 } |
|
7809 |
|
7810 // Join lines together to one and build subtitle text |
|
7811 sub.text = text.join( "<br />" ); |
|
7812 subs.push( createTrack( "subtitle", sub ) ); |
|
7813 } catch ( e ) { |
|
7814 i = skipNonWhitespace( lines, len, i ); |
|
7815 } |
|
7816 } |
|
7817 |
|
7818 retObj.data = subs; |
|
7819 return retObj; |
|
7820 }); |
|
7821 |
|
7822 // [HH:]MM:SS.mmm string to SS.mmm float |
|
7823 // Throws exception if invalid |
|
7824 function toSeconds ( t_in ) { |
|
7825 var t = t_in.split( ":" ), |
|
7826 l = t_in.length, |
|
7827 time; |
|
7828 |
|
7829 // Invalid time string provided |
|
7830 if ( l !== 12 && l !== 9 ) { |
|
7831 throw "Bad cue"; |
|
7832 } |
|
7833 |
|
7834 l = t.length - 1; |
|
7835 |
|
7836 try { |
|
7837 time = parseInt( t[ l-1 ], 10 ) * 60 + parseFloat( t[ l ], 10 ); |
|
7838 |
|
7839 // Hours were given |
|
7840 if ( l === 2 ) { |
|
7841 time += parseInt( t[ 0 ], 10 ) * 3600; |
|
7842 } |
|
7843 } catch ( e ) { |
|
7844 throw "Bad cue"; |
|
7845 } |
|
7846 |
|
7847 return time; |
|
7848 } |
|
7849 |
|
7850 function createTrack( name, attributes ) { |
|
7851 var track = {}; |
|
7852 track[ name ] = attributes; |
|
7853 return track; |
|
7854 } |
|
7855 |
|
7856 function parseCueHeader ( line ) { |
|
7857 var lineSegments, |
|
7858 args, |
|
7859 sub = {}, |
|
7860 rToken = /-->/, |
|
7861 rWhitespace = /[\t ]+/; |
|
7862 |
|
7863 if ( !line || line.indexOf( "-->" ) === -1 ) { |
|
7864 throw "Bad cue"; |
|
7865 } |
|
7866 |
|
7867 lineSegments = line.replace( rToken, " --> " ).split( rWhitespace ); |
|
7868 |
|
7869 if ( lineSegments.length < 2 ) { |
|
7870 throw "Bad cue"; |
|
7871 } |
|
7872 |
|
7873 sub.id = line; |
|
7874 sub.start = toSeconds( lineSegments[ 0 ] ); |
|
7875 sub.end = toSeconds( lineSegments[ 2 ] ); |
|
7876 |
|
7877 return sub; |
|
7878 } |
|
7879 |
|
7880 function skipWhitespace ( lines, len, i ) { |
|
7881 while ( i < len && !lines[ i ] ) { |
|
7882 i++; |
|
7883 } |
|
7884 |
|
7885 return i; |
|
7886 } |
|
7887 |
|
7888 function skipNonWhitespace ( lines, len, i ) { |
|
7889 while ( i < len && lines[ i ] ) { |
|
7890 i++; |
|
7891 } |
|
7892 |
|
7893 return i; |
|
7894 } |
|
7895 })( Popcorn ); |
|
7896 // PARSER: 0.1 XML |
|
7897 |
|
7898 (function (Popcorn) { |
|
7899 |
|
7900 /** |
|
7901 * |
|
7902 * |
|
7903 */ |
|
7904 Popcorn.parser( "parseXML", "XML", function( data ) { |
|
7905 |
|
7906 // declare needed variables |
|
7907 var returnData = { |
|
7908 title: "", |
|
7909 remote: "", |
|
7910 data: [] |
|
7911 }, |
|
7912 manifestData = {}; |
|
7913 |
|
7914 // Simple function to convert 0:05 to 0.5 in seconds |
|
7915 // acceptable formats are HH:MM:SS:MM, MM:SS:MM, SS:MM, SS |
|
7916 var toSeconds = function(time) { |
|
7917 var t = time.split(":"); |
|
7918 if (t.length === 1) { |
|
7919 return parseFloat(t[0], 10); |
|
7920 } else if (t.length === 2) { |
|
7921 return parseFloat(t[0], 10) + parseFloat(t[1] / 12, 10); |
|
7922 } else if (t.length === 3) { |
|
7923 return parseInt(t[0] * 60, 10) + parseFloat(t[1], 10) + parseFloat(t[2] / 12, 10); |
|
7924 } else if (t.length === 4) { |
|
7925 return parseInt(t[0] * 3600, 10) + parseInt(t[1] * 60, 10) + parseFloat(t[2], 10) + parseFloat(t[3] / 12, 10); |
|
7926 } |
|
7927 }; |
|
7928 |
|
7929 // turns a node tree element into a straight up javascript object |
|
7930 // also converts in and out to start and end |
|
7931 // also links manifest data with ids |
|
7932 var objectifyAttributes = function ( nodeAttributes ) { |
|
7933 |
|
7934 var returnObject = {}; |
|
7935 |
|
7936 for ( var i = 0, nal = nodeAttributes.length; i < nal; i++ ) { |
|
7937 |
|
7938 var key = nodeAttributes.item(i).nodeName, |
|
7939 data = nodeAttributes.item(i).nodeValue, |
|
7940 manifestItem = manifestData[ data ]; |
|
7941 |
|
7942 // converts in into start |
|
7943 if (key === "in") { |
|
7944 returnObject.start = toSeconds( data ); |
|
7945 // converts out into end |
|
7946 } else if ( key === "out" ){ |
|
7947 returnObject.end = toSeconds( data ); |
|
7948 // this is where ids in the manifest are linked |
|
7949 } else if ( key === "resourceid" ) { |
|
7950 for ( var item in manifestItem ) { |
|
7951 if ( manifestItem.hasOwnProperty( item ) ) { |
|
7952 if ( !returnObject[ item ] && item !== "id" ) { |
|
7953 returnObject[ item ] = manifestItem[ item ]; |
|
7954 } |
|
7955 } |
|
7956 } |
|
7957 // everything else |
|
7958 } else { |
|
7959 returnObject[key] = data; |
|
7960 } |
|
7961 |
|
7962 } |
|
7963 |
|
7964 return returnObject; |
|
7965 }; |
|
7966 |
|
7967 // creates an object of all atrributes keyd by name |
|
7968 var createTrack = function( name, attributes ) { |
|
7969 var track = {}; |
|
7970 track[name] = attributes; |
|
7971 return track; |
|
7972 }; |
|
7973 |
|
7974 // recursive function to process a node, or process the next child node |
|
7975 var parseNode = function ( node, allAttributes, manifest ) { |
|
7976 var attributes = {}; |
|
7977 Popcorn.extend( attributes, allAttributes, objectifyAttributes( node.attributes ), { text: node.textContent || node.text } ); |
|
7978 |
|
7979 var childNodes = node.childNodes; |
|
7980 |
|
7981 // processes the node |
|
7982 if ( childNodes.length < 1 || ( childNodes.length === 1 && childNodes[0].nodeType === 3 ) ) { |
|
7983 |
|
7984 if ( !manifest ) { |
|
7985 returnData.data.push( createTrack( node.nodeName, attributes ) ); |
|
7986 } else { |
|
7987 manifestData[attributes.id] = attributes; |
|
7988 } |
|
7989 |
|
7990 // process the next child node |
|
7991 } else { |
|
7992 |
|
7993 for ( var i = 0; i < childNodes.length; i++ ) { |
|
7994 |
|
7995 if ( childNodes[i].nodeType === 1 ) { |
|
7996 parseNode( childNodes[i], attributes, manifest ); |
|
7997 } |
|
7998 |
|
7999 } |
|
8000 } |
|
8001 }; |
|
8002 |
|
8003 // this is where things actually start |
|
8004 var x = data.documentElement.childNodes; |
|
8005 |
|
8006 for ( var i = 0, xl = x.length; i < xl; i++ ) { |
|
8007 |
|
8008 if ( x[i].nodeType === 1 ) { |
|
8009 |
|
8010 // start the process of each main node type, manifest or timeline |
|
8011 if ( x[i].nodeName === "manifest" ) { |
|
8012 parseNode( x[i], {}, true ); |
|
8013 } else { // timeline |
|
8014 parseNode( x[i], {}, false ); |
|
8015 } |
|
8016 |
|
8017 } |
|
8018 } |
|
8019 |
|
8020 return returnData; |
|
8021 }); |
|
8022 |
|
8023 })( Popcorn ); |
|
8024 (function() { |
|
8025 |
|
8026 var scriptLoaded = false, |
|
8027 loading = false; |
|
8028 Popcorn.player( "soundcloud", { |
|
8029 _canPlayType: function( nodeName, url ) { |
|
8030 |
|
8031 return (/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(soundcloud)/).test( url ) && nodeName.toLowerCase() !== "video"; |
|
8032 }, |
|
8033 _setup: function( options ) { |
|
8034 |
|
8035 var media = this, |
|
8036 container = document.createElement( "iframe" ), |
|
8037 lastVolume = 1, |
|
8038 currentTime = 0, |
|
8039 paused = true, |
|
8040 realPaused = true, |
|
8041 widget, |
|
8042 duration = 0, |
|
8043 muted = false, |
|
8044 playerQueue = Popcorn.player.playerQueue(); |
|
8045 |
|
8046 options._container = container; |
|
8047 media.style.visibility = "hidden"; |
|
8048 |
|
8049 media.play = function() { |
|
8050 |
|
8051 paused = false; |
|
8052 playerQueue.add(function() { |
|
8053 |
|
8054 if ( realPaused ) { |
|
8055 |
|
8056 widget && widget.play(); |
|
8057 } else { |
|
8058 playerQueue.next(); |
|
8059 } |
|
8060 }); |
|
8061 }; |
|
8062 |
|
8063 media.pause = function() { |
|
8064 |
|
8065 paused = true; |
|
8066 |
|
8067 playerQueue.add(function() { |
|
8068 |
|
8069 if ( !realPaused ) { |
|
8070 |
|
8071 widget && widget.pause(); |
|
8072 } else { |
|
8073 playerQueue.next(); |
|
8074 } |
|
8075 }); |
|
8076 }; |
|
8077 |
|
8078 // getter and setter for muted property, multiply volume by 100 as that is the scale soundcloud works on |
|
8079 Object.defineProperties( media, { |
|
8080 muted: { |
|
8081 set: function( val ) { |
|
8082 if ( val ) { |
|
8083 widget && widget.getVolume(function( data ) { |
|
8084 lastVolume = data / 100; |
|
8085 }); |
|
8086 widget && widget.setVolume( 0 ); |
|
8087 muted = true; |
|
8088 } else { |
|
8089 widget && widget.setVolume( lastVolume * 100 ); |
|
8090 muted = false; |
|
8091 } |
|
8092 media.dispatchEvent( "volumechange" ); |
|
8093 }, |
|
8094 get: function() { |
|
8095 return muted; |
|
8096 } |
|
8097 }, |
|
8098 volume: { |
|
8099 set: function( val ) { |
|
8100 widget && widget.setVolume( val * 100 ); |
|
8101 lastVolume = val ; |
|
8102 media.dispatchEvent( "volumechange" ); |
|
8103 }, |
|
8104 get: function() { |
|
8105 return muted ? 0 : lastVolume; |
|
8106 } |
|
8107 }, |
|
8108 currentTime: { |
|
8109 set: function( val ) { |
|
8110 currentTime = val; |
|
8111 widget && widget.seekTo( val * 1000 ); |
|
8112 media.dispatchEvent( "seeked" ); |
|
8113 media.dispatchEvent( "timeupdate" ); |
|
8114 }, |
|
8115 get: function() { |
|
8116 return currentTime; |
|
8117 } |
|
8118 }, |
|
8119 duration: { |
|
8120 get: function() { |
|
8121 return duration; |
|
8122 } |
|
8123 }, |
|
8124 paused: { |
|
8125 get: function() { |
|
8126 return paused; |
|
8127 } |
|
8128 } |
|
8129 }); |
|
8130 // called when the SoundCloud api script has loaded |
|
8131 function scriptReady() { |
|
8132 scriptLoaded = true; |
|
8133 |
|
8134 SC.initialize({ |
|
8135 client_id: "PRaNFlda6Bhf5utPjUsptg" |
|
8136 }); |
|
8137 |
|
8138 SC.get( "/resolve", { |
|
8139 url: media.src |
|
8140 }, function( data ) { |
|
8141 media.width = media.style.width ? "" + media.offsetWidth : "560"; |
|
8142 media.height = media.style.height ? "" + media.offsetHeight : "315"; |
|
8143 // TODO: There are quite a few options here that we should pass on to the user |
|
8144 container.scrolling = "no"; |
|
8145 container.frameborder = "no"; |
|
8146 container.id = "soundcloud-" + Popcorn.guid(); |
|
8147 container.src = "http://w.soundcloud.com/player/?url=" + data.uri + |
|
8148 "&show_artwork=false" + |
|
8149 "&buying=false" + |
|
8150 "&liking=false" + |
|
8151 "&sharing=false"; |
|
8152 |
|
8153 container.width = "100%"; |
|
8154 container.height = "100%"; |
|
8155 |
|
8156 options.loadListener = function( e ) { |
|
8157 options.widget = widget = SC.Widget( container.id ); |
|
8158 // setup all of our listeners |
|
8159 widget.bind(SC.Widget.Events.FINISH, function() { |
|
8160 media.pause(); |
|
8161 |
|
8162 media.dispatchEvent( "ended" ); |
|
8163 }); |
|
8164 |
|
8165 widget.bind(SC.Widget.Events.PLAY_PROGRESS, function( data ) { |
|
8166 |
|
8167 currentTime = data.currentPosition / 1000; |
|
8168 media.dispatchEvent( "timeupdate" ); |
|
8169 }); |
|
8170 |
|
8171 widget.bind(SC.Widget.Events.PLAY, function( data ) { |
|
8172 |
|
8173 paused = realPaused = false; |
|
8174 |
|
8175 media.dispatchEvent( "play" ); |
|
8176 media.dispatchEvent( "playing" ); |
|
8177 media.currentTime = currentTime; |
|
8178 |
|
8179 playerQueue.next(); |
|
8180 }); |
|
8181 |
|
8182 widget.bind(SC.Widget.Events.PAUSE, function( data ) { |
|
8183 |
|
8184 paused = realPaused = true; |
|
8185 media.dispatchEvent( "pause" ); |
|
8186 |
|
8187 playerQueue.next(); |
|
8188 }); |
|
8189 widget.bind(SC.Widget.Events.READY, function( data ) { |
|
8190 widget.getDuration(function( data ) { |
|
8191 |
|
8192 duration = data / 1000; |
|
8193 |
|
8194 media.style.visibility = "visible"; |
|
8195 media.dispatchEvent( "durationchange" ); |
|
8196 // update the readyState after we have the duration |
|
8197 media.readyState = 4; |
|
8198 media.dispatchEvent( "readystatechange" ); |
|
8199 media.dispatchEvent( "loadedmetadata" ); |
|
8200 media.dispatchEvent( "loadeddata" ); |
|
8201 media.dispatchEvent( "canplaythrough" ); |
|
8202 media.dispatchEvent( "load" ); |
|
8203 !media.paused && media.play(); |
|
8204 }); |
|
8205 widget.getVolume(function( data ) { |
|
8206 lastVolume = data / 100; |
|
8207 }); |
|
8208 }); |
|
8209 }; |
|
8210 |
|
8211 container.addEventListener( "load", options.loadListener, false ); |
|
8212 media.appendChild( container ); |
|
8213 }); |
|
8214 } |
|
8215 |
|
8216 // load the SoundCloud API script if it doesn't exist |
|
8217 function loadScript() { |
|
8218 if ( !loading ) { |
|
8219 loading = true; |
|
8220 Popcorn.getScript( "http://w.soundcloud.com/player/api.js", function() { |
|
8221 Popcorn.getScript( "http://connect.soundcloud.com/sdk.js", function() { |
|
8222 scriptReady(); |
|
8223 }); |
|
8224 }); |
|
8225 } else { |
|
8226 (function isReady() { |
|
8227 setTimeout(function() { |
|
8228 if ( !scriptLoaded ) { |
|
8229 isReady(); |
|
8230 } else { |
|
8231 scriptReady(); |
|
8232 } |
|
8233 }, 100 ); |
|
8234 })(); |
|
8235 } |
|
8236 } |
|
8237 |
|
8238 if ( !scriptLoaded ) { |
|
8239 loadScript(); |
|
8240 } else { |
|
8241 scriptReady(); |
|
8242 } |
|
8243 }, |
|
8244 _teardown: function( options ) { |
|
8245 var widget = options.widget, |
|
8246 events = SC.Widget.Events, |
|
8247 container = options._container, |
|
8248 parentContainer = container.parentNode; |
|
8249 |
|
8250 options.destroyed = true; |
|
8251 |
|
8252 // if the widget never got setup, remove the containers load listener and return |
|
8253 if ( !widget ) { |
|
8254 container.removeEventListener( "load", options.loadEventListener, false ); |
|
8255 return; |
|
8256 } |
|
8257 |
|
8258 // remove all bound soundcloud listeners |
|
8259 for ( var prop in events ) { |
|
8260 widget && widget.unbind( events[ prop ] ); |
|
8261 } |
|
8262 } |
|
8263 }); |
|
8264 })(); |
|
8265 (function() { |
|
8266 |
|
8267 // parseUri 1.2.2 |
|
8268 // http://blog.stevenlevithan.com/archives/parseuri |
|
8269 // (c) Steven Levithan <stevenlevithan.com> |
|
8270 // MIT License |
|
8271 |
|
8272 function parseUri (str) { |
|
8273 var o = parseUri.options, |
|
8274 m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), |
|
8275 uri = {}, |
|
8276 i = 14; |
|
8277 |
|
8278 while (i--) { |
|
8279 uri[o.key[i]] = m[i] || ""; |
|
8280 } |
|
8281 |
|
8282 uri[o.q.name] = {}; |
|
8283 uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { |
|
8284 if ($1) { |
|
8285 uri[o.q.name][$1] = $2; |
|
8286 } |
|
8287 }); |
|
8288 |
|
8289 return uri; |
|
8290 } |
|
8291 |
|
8292 parseUri.options = { |
|
8293 strictMode: false, |
|
8294 key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], |
|
8295 q: { |
|
8296 name: "queryKey", |
|
8297 parser: /(?:^|&)([^&=]*)=?([^&]*)/g |
|
8298 }, |
|
8299 parser: { |
|
8300 strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, |
|
8301 loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ |
|
8302 } |
|
8303 }; |
|
8304 |
|
8305 function canPlayType( nodeName, url ) { |
|
8306 return ( /player.vimeo.com\/video\/\d+/ ).test( url ) || |
|
8307 ( /vimeo.com\/\d+/ ).test( url ); |
|
8308 } |
|
8309 |
|
8310 Popcorn.player( "vimeo", { |
|
8311 |
|
8312 _canPlayType: canPlayType, |
|
8313 _setup: function( options ) { |
|
8314 |
|
8315 var TIMEUPDATE_INTERVAL_MS = 250, |
|
8316 CURRENT_TIME_MONITOR_MS = 16, |
|
8317 MediaErrorInterface = { |
|
8318 MEDIA_ERR_ABORTED: 1, |
|
8319 MEDIA_ERR_NETWORK: 2, |
|
8320 MEDIA_ERR_DECODE: 3, |
|
8321 MEDIA_ERR_SRC_NOT_SUPPORTED: 4 |
|
8322 }, |
|
8323 guid, |
|
8324 media = this, |
|
8325 commands = { |
|
8326 q: [], |
|
8327 queue: function queue( fn ) { |
|
8328 this.q.push( fn ); |
|
8329 this.process(); |
|
8330 }, |
|
8331 process: function process() { |
|
8332 if ( !vimeoReady ) { |
|
8333 return; |
|
8334 } |
|
8335 |
|
8336 while ( this.q.length ) { |
|
8337 var fn = this.q.shift(); |
|
8338 fn(); |
|
8339 } |
|
8340 } |
|
8341 }, |
|
8342 currentTimeId, |
|
8343 timeUpdateId, |
|
8344 vimeoReady, |
|
8345 vimeoContainer = document.createElement( "iframe" ), |
|
8346 // Loosely based on HTMLMediaElement + HTMLVideoElement IDL |
|
8347 impl = { |
|
8348 // error state |
|
8349 error: null, |
|
8350 |
|
8351 // network state |
|
8352 src: media.src, |
|
8353 NETWORK_EMPTY: 0, |
|
8354 NETWORK_IDLE: 1, |
|
8355 NETWORK_LOADING: 2, |
|
8356 NETWORK_NO_SOURCE: 3, |
|
8357 networkState: 0, |
|
8358 |
|
8359 // ready state |
|
8360 HAVE_NOTHING: 0, |
|
8361 HAVE_METADATA: 1, |
|
8362 HAVE_CURRENT_DATA: 2, |
|
8363 HAVE_FUTURE_DATA: 3, |
|
8364 HAVE_ENOUGH_DATA: 4, |
|
8365 readyState: 0, |
|
8366 seeking: false, |
|
8367 |
|
8368 // playback state |
|
8369 currentTime: 0, |
|
8370 duration: NaN, |
|
8371 paused: true, |
|
8372 ended: false, |
|
8373 autoplay: false, |
|
8374 loop: false, |
|
8375 |
|
8376 // controls |
|
8377 volume: 1, |
|
8378 muted: false, |
|
8379 |
|
8380 // Video attributes |
|
8381 width: 0, |
|
8382 height: 0 |
|
8383 }; |
|
8384 |
|
8385 var readOnlyAttrs = "error networkState readyState seeking duration paused ended"; |
|
8386 Popcorn.forEach( readOnlyAttrs.split(" "), function( value ) { |
|
8387 Object.defineProperty( media, value, { |
|
8388 get: function() { |
|
8389 return impl[ value ]; |
|
8390 } |
|
8391 }); |
|
8392 }); |
|
8393 |
|
8394 Object.defineProperties( media, { |
|
8395 "src": { |
|
8396 get: function() { |
|
8397 return impl.src; |
|
8398 }, |
|
8399 set: function( value ) { |
|
8400 // Is there any sort of logic that determines whether to load the video or not? |
|
8401 impl.src = value; |
|
8402 media.load(); |
|
8403 } |
|
8404 }, |
|
8405 "currentTime": { |
|
8406 get: function() { |
|
8407 return impl.currentTime; |
|
8408 }, |
|
8409 set: function( value ) { |
|
8410 commands.queue(function() { |
|
8411 sendMessage( "seekTo", value ); |
|
8412 }); |
|
8413 impl.seeking = true; |
|
8414 media.dispatchEvent( "seeking" ); |
|
8415 } |
|
8416 }, |
|
8417 "autoplay": { |
|
8418 get: function() { |
|
8419 return impl.autoplay; |
|
8420 }, |
|
8421 set: function( value ) { |
|
8422 impl.autoplay = !!value; |
|
8423 } |
|
8424 }, |
|
8425 "loop": { |
|
8426 get: function() { |
|
8427 return impl.loop; |
|
8428 }, |
|
8429 set: function( value) { |
|
8430 impl.loop = !!value; |
|
8431 commands.queue(function() { |
|
8432 sendMessage( "setLoop", loop ); |
|
8433 }); |
|
8434 } |
|
8435 }, |
|
8436 "volume": { |
|
8437 get: function() { |
|
8438 return impl.volume; |
|
8439 }, |
|
8440 set: function( value ) { |
|
8441 impl.volume = value; |
|
8442 commands.queue(function() { |
|
8443 sendMessage( "setVolume", impl.muted ? 0 : impl.volume ); |
|
8444 }); |
|
8445 media.dispatchEvent( "volumechange" ); |
|
8446 } |
|
8447 }, |
|
8448 "muted": { |
|
8449 get: function() { |
|
8450 return impl.muted; |
|
8451 }, |
|
8452 set: function( value ) { |
|
8453 impl.muted = !!value; |
|
8454 commands.queue(function() { |
|
8455 sendMessage( "setVolume", impl.muted ? 0 : impl.volume ); |
|
8456 }); |
|
8457 media.dispatchEvent( "volumechange" ); |
|
8458 } |
|
8459 }, |
|
8460 "width": { |
|
8461 get: function() { |
|
8462 return vimeoContainer.width; |
|
8463 }, |
|
8464 set: function( value ) { |
|
8465 vimeoContainer.width = value; |
|
8466 } |
|
8467 }, |
|
8468 "height": { |
|
8469 get: function() { |
|
8470 return vimeoContainer.height; |
|
8471 }, |
|
8472 set: function( value ) { |
|
8473 vimeoContainer.height = value; |
|
8474 } |
|
8475 } |
|
8476 }); |
|
8477 |
|
8478 function sendMessage( method, params ) { |
|
8479 var url = vimeoContainer.src.split( "?" )[ 0 ], |
|
8480 data = JSON.stringify({ |
|
8481 method: method, |
|
8482 value: params |
|
8483 }); |
|
8484 |
|
8485 if ( url.substr( 0, 2 ) === "//" ) { |
|
8486 url = window.location.protocol + url; |
|
8487 } |
|
8488 |
|
8489 // The iframe has been destroyed, it just doesn't know it |
|
8490 if ( !vimeoContainer.contentWindow ) { |
|
8491 media.unload(); |
|
8492 return; |
|
8493 } |
|
8494 |
|
8495 vimeoContainer.contentWindow.postMessage( data, url ); |
|
8496 } |
|
8497 |
|
8498 var vimeoAPIMethods = { |
|
8499 "getCurrentTime": function( data ) { |
|
8500 impl.currentTime = parseFloat( data.value ); |
|
8501 }, |
|
8502 "getDuration": function( data ) { |
|
8503 impl.duration = parseFloat( data.value ); |
|
8504 maybeReady(); |
|
8505 }, |
|
8506 "getVolume": function( data ) { |
|
8507 impl.volume = parseFloat( data.value ); |
|
8508 } |
|
8509 }; |
|
8510 |
|
8511 var vimeoAPIEvents = { |
|
8512 "ready": function( data ) { |
|
8513 sendMessage( "addEventListener", "loadProgress" ); |
|
8514 sendMessage( "addEventListener", "playProgress" ); |
|
8515 sendMessage( "addEventListener", "play" ); |
|
8516 sendMessage( "addEventListener", "pause" ); |
|
8517 sendMessage( "addEventListener", "finish" ); |
|
8518 sendMessage( "addEventListener", "seek" ); |
|
8519 sendMessage( "getDuration" ); |
|
8520 vimeoReady = true; |
|
8521 commands.process(); |
|
8522 media.dispatchEvent( "loadstart" ); |
|
8523 }, |
|
8524 "loadProgress": function( data ) { |
|
8525 media.dispatchEvent( "progress" ); |
|
8526 // loadProgress has a more accurate duration than getDuration |
|
8527 impl.duration = parseFloat( data.data.duration ); |
|
8528 }, |
|
8529 "playProgress": function( data ) { |
|
8530 impl.currentTime = parseFloat( data.data.seconds ); |
|
8531 }, |
|
8532 "play": function( data ) { |
|
8533 // Vimeo plays video if seeking from an unloaded state |
|
8534 if ( impl.seeking ) { |
|
8535 impl.seeking = false; |
|
8536 media.dispatchEvent( "seeked" ); |
|
8537 } |
|
8538 impl.paused = false; |
|
8539 impl.ended = false; |
|
8540 startUpdateLoops(); |
|
8541 media.dispatchEvent( "play" ); |
|
8542 }, |
|
8543 "pause": function( data ) { |
|
8544 impl.paused = true; |
|
8545 stopUpdateLoops(); |
|
8546 media.dispatchEvent( "pause" ); |
|
8547 }, |
|
8548 "finish": function( data ) { |
|
8549 impl.ended = true; |
|
8550 stopUpdateLoops(); |
|
8551 media.dispatchEvent( "ended" ); |
|
8552 }, |
|
8553 "seek": function( data ) { |
|
8554 impl.currentTime = parseFloat( data.data.seconds ); |
|
8555 impl.seeking = false; |
|
8556 impl.ended = false; |
|
8557 media.dispatchEvent( "timeupdate" ); |
|
8558 media.dispatchEvent( "seeked" ); |
|
8559 } |
|
8560 }; |
|
8561 |
|
8562 function messageListener( event ) { |
|
8563 if ( event.origin !== "http://player.vimeo.com" ) { |
|
8564 return; |
|
8565 } |
|
8566 |
|
8567 var data; |
|
8568 try { |
|
8569 data = JSON.parse( event.data ); |
|
8570 } catch ( ex ) { |
|
8571 console.warn( ex ); |
|
8572 } |
|
8573 |
|
8574 if ( data.player_id != guid ) { |
|
8575 return; |
|
8576 } |
|
8577 |
|
8578 // Methods |
|
8579 if ( data.method && vimeoAPIMethods[ data.method ] ) { |
|
8580 vimeoAPIMethods[ data.method ]( data ); |
|
8581 } |
|
8582 |
|
8583 // Events |
|
8584 if ( data.event && vimeoAPIEvents[ data.event ] ) { |
|
8585 vimeoAPIEvents[ data.event ]( data ); |
|
8586 } |
|
8587 } |
|
8588 |
|
8589 media.load = function() { |
|
8590 vimeoReady = false; |
|
8591 guid = Popcorn.guid(); |
|
8592 |
|
8593 var src = parseUri( impl.src ), |
|
8594 combinedOptions = {}, |
|
8595 optionsArray = [], |
|
8596 vimeoAPIOptions = { |
|
8597 api: 1, |
|
8598 player_id: guid |
|
8599 }; |
|
8600 |
|
8601 if ( !canPlayType( media.nodeName, src.source ) ) { |
|
8602 setErrorAttr( impl.MEDIA_ERR_SRC_NOT_SUPPORTED ); |
|
8603 return; |
|
8604 } |
|
8605 |
|
8606 // Add Popcorn ctor options, url options, then the Vimeo API options |
|
8607 Popcorn.extend( combinedOptions, options ); |
|
8608 Popcorn.extend( combinedOptions, src.queryKey ); |
|
8609 Popcorn.extend( combinedOptions, vimeoAPIOptions ); |
|
8610 |
|
8611 // Create the base vimeo player string. It will always have query string options |
|
8612 src = "http://player.vimeo.com/video/" + ( /\d+$/ ).exec( src.path ) + "?"; |
|
8613 |
|
8614 for ( var key in combinedOptions ) { |
|
8615 if ( combinedOptions.hasOwnProperty( key ) ) { |
|
8616 optionsArray.push( encodeURIComponent( key ) + "=" + encodeURIComponent( combinedOptions[ key ] ) ); |
|
8617 } |
|
8618 } |
|
8619 src += optionsArray.join( "&" ); |
|
8620 |
|
8621 impl.loop = !!src.match( /loop=1/ ); |
|
8622 impl.autoplay = !!src.match( /autoplay=1/ ); |
|
8623 |
|
8624 vimeoContainer.width = media.style.width ? media.style.width : 500; |
|
8625 vimeoContainer.height = media.style.height ? media.style.height : 281; |
|
8626 vimeoContainer.frameBorder = 0; |
|
8627 vimeoContainer.webkitAllowFullScreen = true; |
|
8628 vimeoContainer.mozAllowFullScreen = true; |
|
8629 vimeoContainer.allowFullScreen = true; |
|
8630 vimeoContainer.src = src; |
|
8631 media.appendChild( vimeoContainer ); |
|
8632 }; |
|
8633 |
|
8634 function setErrorAttr( value ) { |
|
8635 impl.error = {}; |
|
8636 Popcorn.extend( impl.error, MediaErrorInterface ); |
|
8637 impl.error.code = value; |
|
8638 media.dispatchEvent( "error" ); |
|
8639 } |
|
8640 |
|
8641 function maybeReady() { |
|
8642 if ( !isNaN( impl.duration ) ) { |
|
8643 impl.readyState = 4; |
|
8644 media.dispatchEvent( "durationchange" ); |
|
8645 media.dispatchEvent( "loadedmetadata" ); |
|
8646 media.dispatchEvent( "loadeddata" ); |
|
8647 media.dispatchEvent( "canplay" ); |
|
8648 media.dispatchEvent( "canplaythrough" ); |
|
8649 } |
|
8650 } |
|
8651 |
|
8652 function startUpdateLoops() { |
|
8653 if ( !timeUpdateId ) { |
|
8654 timeUpdateId = setInterval(function() { |
|
8655 media.dispatchEvent( "timeupdate" ); |
|
8656 }, TIMEUPDATE_INTERVAL_MS ); |
|
8657 } |
|
8658 |
|
8659 if ( !currentTimeId ) { |
|
8660 currentTimeId = setInterval(function() { |
|
8661 sendMessage( "getCurrentTime" ); |
|
8662 }, CURRENT_TIME_MONITOR_MS ); |
|
8663 } |
|
8664 } |
|
8665 |
|
8666 function stopUpdateLoops() { |
|
8667 if ( timeUpdateId ) { |
|
8668 clearInterval( timeUpdateId ); |
|
8669 timeUpdateId = 0; |
|
8670 } |
|
8671 |
|
8672 if ( currentTimeId ) { |
|
8673 clearInterval( currentTimeId ); |
|
8674 currentTimeId = 0; |
|
8675 } |
|
8676 } |
|
8677 |
|
8678 media.unload = function() { |
|
8679 stopUpdateLoops(); |
|
8680 window.removeEventListener( "message", messageListener, false ); |
|
8681 }; |
|
8682 |
|
8683 media.play = function() { |
|
8684 commands.queue(function() { |
|
8685 sendMessage( "play" ); |
|
8686 }); |
|
8687 }; |
|
8688 |
|
8689 media.pause = function() { |
|
8690 commands.queue(function() { |
|
8691 sendMessage( "pause" ); |
|
8692 }); |
|
8693 }; |
|
8694 |
|
8695 // Start the load process now, players behave like `preload="metadata"` is set |
|
8696 // Do it asynchronously so that users can attach event listeners |
|
8697 setTimeout(function() { |
|
8698 window.addEventListener( "message", messageListener, false ); |
|
8699 media.load(); |
|
8700 }, 0 ); |
|
8701 }, |
|
8702 _teardown: function( options ) { |
|
8703 // If the baseplayer doesn't call _setup |
|
8704 if ( this.unload ) { |
|
8705 this.unload(); |
|
8706 } |
|
8707 } |
|
8708 }); |
|
8709 })(); |
|
8710 (function( window, Popcorn ) { |
|
8711 // A global callback for youtube... that makes me angry |
|
8712 window.onYouTubePlayerAPIReady = function() { |
|
8713 |
|
8714 onYouTubePlayerAPIReady.ready = true; |
|
8715 for ( var i = 0; i < onYouTubePlayerAPIReady.waiting.length; i++ ) { |
|
8716 onYouTubePlayerAPIReady.waiting[ i ](); |
|
8717 } |
|
8718 }; |
|
8719 |
|
8720 // existing youtube references can break us. |
|
8721 // remove it and use the one we can trust. |
|
8722 if ( window.YT ) { |
|
8723 window.quarantineYT = window.YT; |
|
8724 window.YT = null; |
|
8725 } |
|
8726 |
|
8727 onYouTubePlayerAPIReady.waiting = []; |
|
8728 |
|
8729 var _loading = false; |
|
8730 |
|
8731 Popcorn.player( "youtube", { |
|
8732 _canPlayType: function( nodeName, url ) { |
|
8733 |
|
8734 return typeof url === "string" && (/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu)/).test( url ) && nodeName.toLowerCase() !== "video"; |
|
8735 }, |
|
8736 _setup: function( options ) { |
|
8737 if ( !window.YT && !_loading ) { |
|
8738 _loading = true; |
|
8739 Popcorn.getScript( "//youtube.com/player_api" ); |
|
8740 } |
|
8741 |
|
8742 var media = this, |
|
8743 autoPlay = false, |
|
8744 container = document.createElement( "div" ), |
|
8745 currentTime = 0, |
|
8746 paused = true, |
|
8747 seekTime = 0, |
|
8748 firstGo = true, |
|
8749 seeking = false, |
|
8750 fragmentStart = 0, |
|
8751 |
|
8752 // state code for volume changed polling |
|
8753 lastMuted = false, |
|
8754 lastVolume = 100, |
|
8755 playerQueue = Popcorn.player.playerQueue(); |
|
8756 |
|
8757 var createProperties = function() { |
|
8758 |
|
8759 Popcorn.player.defineProperty( media, "currentTime", { |
|
8760 set: function( val ) { |
|
8761 |
|
8762 if ( options.destroyed ) { |
|
8763 return; |
|
8764 } |
|
8765 |
|
8766 seeking = true; |
|
8767 // make sure val is a number |
|
8768 currentTime = Math.round( +val * 100 ) / 100; |
|
8769 }, |
|
8770 get: function() { |
|
8771 |
|
8772 return currentTime; |
|
8773 } |
|
8774 }); |
|
8775 |
|
8776 Popcorn.player.defineProperty( media, "paused", { |
|
8777 get: function() { |
|
8778 |
|
8779 return paused; |
|
8780 } |
|
8781 }); |
|
8782 |
|
8783 Popcorn.player.defineProperty( media, "muted", { |
|
8784 set: function( val ) { |
|
8785 |
|
8786 if ( options.destroyed ) { |
|
8787 |
|
8788 return val; |
|
8789 } |
|
8790 |
|
8791 if ( options.youtubeObject.isMuted() !== val ) { |
|
8792 |
|
8793 if ( val ) { |
|
8794 |
|
8795 options.youtubeObject.mute(); |
|
8796 } else { |
|
8797 |
|
8798 options.youtubeObject.unMute(); |
|
8799 } |
|
8800 |
|
8801 lastMuted = options.youtubeObject.isMuted(); |
|
8802 media.dispatchEvent( "volumechange" ); |
|
8803 } |
|
8804 |
|
8805 return options.youtubeObject.isMuted(); |
|
8806 }, |
|
8807 get: function() { |
|
8808 |
|
8809 if ( options.destroyed ) { |
|
8810 |
|
8811 return 0; |
|
8812 } |
|
8813 |
|
8814 return options.youtubeObject.isMuted(); |
|
8815 } |
|
8816 }); |
|
8817 |
|
8818 Popcorn.player.defineProperty( media, "volume", { |
|
8819 set: function( val ) { |
|
8820 |
|
8821 if ( options.destroyed ) { |
|
8822 |
|
8823 return val; |
|
8824 } |
|
8825 |
|
8826 if ( options.youtubeObject.getVolume() / 100 !== val ) { |
|
8827 |
|
8828 options.youtubeObject.setVolume( val * 100 ); |
|
8829 lastVolume = options.youtubeObject.getVolume(); |
|
8830 media.dispatchEvent( "volumechange" ); |
|
8831 } |
|
8832 |
|
8833 return options.youtubeObject.getVolume() / 100; |
|
8834 }, |
|
8835 get: function() { |
|
8836 |
|
8837 if ( options.destroyed ) { |
|
8838 |
|
8839 return 0; |
|
8840 } |
|
8841 |
|
8842 return options.youtubeObject.getVolume() / 100; |
|
8843 } |
|
8844 }); |
|
8845 |
|
8846 media.play = function() { |
|
8847 |
|
8848 if ( options.destroyed ) { |
|
8849 |
|
8850 return; |
|
8851 } |
|
8852 |
|
8853 paused = false; |
|
8854 playerQueue.add(function() { |
|
8855 |
|
8856 if ( options.youtubeObject.getPlayerState() !== 1 ) { |
|
8857 |
|
8858 seeking = false; |
|
8859 options.youtubeObject.playVideo(); |
|
8860 } else { |
|
8861 playerQueue.next(); |
|
8862 } |
|
8863 }); |
|
8864 }; |
|
8865 |
|
8866 media.pause = function() { |
|
8867 |
|
8868 if ( options.destroyed ) { |
|
8869 |
|
8870 return; |
|
8871 } |
|
8872 |
|
8873 paused = true; |
|
8874 playerQueue.add(function() { |
|
8875 |
|
8876 if ( options.youtubeObject.getPlayerState() !== 2 ) { |
|
8877 |
|
8878 options.youtubeObject.pauseVideo(); |
|
8879 } else { |
|
8880 playerQueue.next(); |
|
8881 } |
|
8882 }); |
|
8883 }; |
|
8884 }; |
|
8885 |
|
8886 container.id = media.id + Popcorn.guid(); |
|
8887 options._container = container; |
|
8888 media.appendChild( container ); |
|
8889 |
|
8890 var youtubeInit = function() { |
|
8891 |
|
8892 var src, query, params, playerVars, queryStringItem, firstPlay = true; |
|
8893 |
|
8894 var timeUpdate = function() { |
|
8895 |
|
8896 if ( options.destroyed ) { |
|
8897 return; |
|
8898 } |
|
8899 |
|
8900 if ( !seeking ) { |
|
8901 currentTime = options.youtubeObject.getCurrentTime(); |
|
8902 media.dispatchEvent( "timeupdate" ); |
|
8903 } else if ( currentTime === options.youtubeObject.getCurrentTime() ) { |
|
8904 |
|
8905 seeking = false; |
|
8906 media.dispatchEvent( "seeked" ); |
|
8907 media.dispatchEvent( "timeupdate" ); |
|
8908 } else { |
|
8909 |
|
8910 // keep trying the seek until it is right. |
|
8911 options.youtubeObject.seekTo( currentTime ); |
|
8912 } |
|
8913 setTimeout( timeUpdate, 250 ); |
|
8914 }; |
|
8915 |
|
8916 // delay is in seconds |
|
8917 var fetchDuration = function( delay ) { |
|
8918 var ytDuration = options.youtubeObject.getDuration(); |
|
8919 |
|
8920 if ( isNaN( ytDuration ) || ytDuration === 0 ) { |
|
8921 setTimeout( function() { |
|
8922 fetchDuration( delay * 2 ); |
|
8923 }, delay*1000 ); |
|
8924 } else { |
|
8925 // set duration and dispatch ready events |
|
8926 media.duration = ytDuration; |
|
8927 media.dispatchEvent( "durationchange" ); |
|
8928 |
|
8929 media.dispatchEvent( "loadedmetadata" ); |
|
8930 media.dispatchEvent( "loadeddata" ); |
|
8931 |
|
8932 media.readyState = 4; |
|
8933 |
|
8934 timeUpdate(); |
|
8935 media.dispatchEvent( "canplaythrough" ); |
|
8936 } |
|
8937 }; |
|
8938 |
|
8939 options.controls = +options.controls === 0 || +options.controls === 1 ? options.controls : 1; |
|
8940 options.annotations = +options.annotations === 1 || +options.annotations === 3 ? options.annotations : 1; |
|
8941 |
|
8942 src = /^.*(?:\/|v=)(.{11})/.exec( media.src )[ 1 ]; |
|
8943 |
|
8944 query = ( media.src.split( "?" )[ 1 ] || "" ) |
|
8945 .replace( /v=.{11}/, "" ); |
|
8946 query = query.replace( /&t=(?:(\d+)m)?(?:(\d+)s)?/, function( all, minutes, seconds ) { |
|
8947 |
|
8948 // Make sure we have real zeros |
|
8949 minutes = minutes | 0; // bit-wise OR |
|
8950 seconds = seconds | 0; // bit-wise OR |
|
8951 |
|
8952 fragmentStart = ( +seconds + ( minutes * 60 ) ); |
|
8953 return ""; |
|
8954 }); |
|
8955 query = query.replace( /&start=(\d+)?/, function( all, seconds ) { |
|
8956 |
|
8957 // Make sure we have real zeros |
|
8958 seconds = seconds | 0; // bit-wise OR |
|
8959 |
|
8960 fragmentStart = seconds; |
|
8961 return ""; |
|
8962 }); |
|
8963 |
|
8964 autoPlay = ( /autoplay=1/.test( query ) ); |
|
8965 |
|
8966 params = query.split( /[\&\?]/g ); |
|
8967 playerVars = { wmode: "transparent" }; |
|
8968 |
|
8969 for( var i = 0; i < params.length; i++ ) { |
|
8970 queryStringItem = params[ i ].split( "=" ); |
|
8971 playerVars[ queryStringItem[ 0 ] ] = queryStringItem[ 1 ]; |
|
8972 } |
|
8973 |
|
8974 options.youtubeObject = new YT.Player( container.id, { |
|
8975 height: "100%", |
|
8976 width: "100%", |
|
8977 wmode: "transparent", |
|
8978 playerVars: playerVars, |
|
8979 videoId: src, |
|
8980 events: { |
|
8981 "onReady": function(){ |
|
8982 |
|
8983 // pulling initial volume states form baseplayer |
|
8984 lastVolume = media.volume; |
|
8985 lastMuted = media.muted; |
|
8986 |
|
8987 volumeupdate(); |
|
8988 |
|
8989 paused = media.paused; |
|
8990 createProperties(); |
|
8991 options.youtubeObject.playVideo(); |
|
8992 |
|
8993 media.currentTime = fragmentStart; |
|
8994 // wait to dispatch ready events until we get a duration |
|
8995 }, |
|
8996 "onStateChange": function( state ){ |
|
8997 |
|
8998 if ( options.destroyed || state.data === -1 ) { |
|
8999 return; |
|
9000 } |
|
9001 |
|
9002 // state.data === 2 is for pause events |
|
9003 // state.data === 1 is for play events |
|
9004 if ( state.data === 2 ) { |
|
9005 paused = true; |
|
9006 media.dispatchEvent( "pause" ); |
|
9007 playerQueue.next(); |
|
9008 } else if ( state.data === 1 && !firstPlay ) { |
|
9009 paused = false; |
|
9010 media.dispatchEvent( "play" ); |
|
9011 media.dispatchEvent( "playing" ); |
|
9012 playerQueue.next(); |
|
9013 } else if ( state.data === 0 ) { |
|
9014 media.dispatchEvent( "ended" ); |
|
9015 } else if ( state.data === 1 && firstPlay ) { |
|
9016 firstPlay = false; |
|
9017 |
|
9018 // pulling initial paused state from autoplay or the baseplayer |
|
9019 // also need to explicitly set to paused otherwise. |
|
9020 if ( autoPlay || !media.paused ) { |
|
9021 paused = false; |
|
9022 } |
|
9023 |
|
9024 if ( paused ) { |
|
9025 options.youtubeObject.pauseVideo(); |
|
9026 } |
|
9027 |
|
9028 fetchDuration( 0.025 ); |
|
9029 } |
|
9030 }, |
|
9031 "onError": function( error ) { |
|
9032 |
|
9033 if ( [ 2, 100, 101, 150 ].indexOf( error.data ) !== -1 ) { |
|
9034 media.error = { |
|
9035 customCode: error.data |
|
9036 }; |
|
9037 media.dispatchEvent( "error" ); |
|
9038 } |
|
9039 } |
|
9040 } |
|
9041 }); |
|
9042 |
|
9043 var volumeupdate = function() { |
|
9044 |
|
9045 if ( options.destroyed ) { |
|
9046 |
|
9047 return; |
|
9048 } |
|
9049 |
|
9050 if ( lastMuted !== options.youtubeObject.isMuted() ) { |
|
9051 |
|
9052 lastMuted = options.youtubeObject.isMuted(); |
|
9053 media.dispatchEvent( "volumechange" ); |
|
9054 } |
|
9055 |
|
9056 if ( lastVolume !== options.youtubeObject.getVolume() ) { |
|
9057 |
|
9058 lastVolume = options.youtubeObject.getVolume(); |
|
9059 media.dispatchEvent( "volumechange" ); |
|
9060 } |
|
9061 |
|
9062 setTimeout( volumeupdate, 250 ); |
|
9063 }; |
|
9064 }; |
|
9065 |
|
9066 if ( onYouTubePlayerAPIReady.ready ) { |
|
9067 |
|
9068 youtubeInit(); |
|
9069 } else { |
|
9070 |
|
9071 onYouTubePlayerAPIReady.waiting.push( youtubeInit ); |
|
9072 } |
|
9073 }, |
|
9074 _teardown: function( options ) { |
|
9075 |
|
9076 options.destroyed = true; |
|
9077 |
|
9078 var youtubeObject = options.youtubeObject; |
|
9079 if( youtubeObject ){ |
|
9080 youtubeObject.stopVideo(); |
|
9081 youtubeObject.clearVideo && youtubeObject.clearVideo(); |
|
9082 } |
|
9083 |
|
9084 this.removeChild( document.getElementById( options._container.id ) ); |
|
9085 } |
|
9086 }); |
|
9087 }( window, Popcorn )); |
|
9088 // EFFECT: applyclass |
|
9089 |
|
9090 (function (Popcorn) { |
|
9091 |
|
9092 /** |
|
9093 * apply css class to jquery selector |
|
9094 * selector is relative to plugin target's id |
|
9095 * so .overlay is actually jQuery( "#target .overlay") |
|
9096 * |
|
9097 * @param {Object} options |
|
9098 * |
|
9099 * Example: |
|
9100 var p = Popcorn('#video') |
|
9101 .footnote({ |
|
9102 start: 5, // seconds |
|
9103 end: 15, // seconds |
|
9104 text: 'This video made exclusively for drumbeat.org', |
|
9105 target: 'footnotediv', |
|
9106 effect: 'applyclass', |
|
9107 applyclass: 'selector: class' |
|
9108 }) |
|
9109 * |
|
9110 */ |
|
9111 |
|
9112 var toggleClass = function( event, options ) { |
|
9113 |
|
9114 var idx = 0, len = 0, elements; |
|
9115 |
|
9116 Popcorn.forEach( options.classes, function( key, val ) { |
|
9117 |
|
9118 elements = []; |
|
9119 |
|
9120 if ( key === "parent" ) { |
|
9121 |
|
9122 elements[ 0 ] = document.querySelectorAll("#" + options.target )[ 0 ].parentNode; |
|
9123 } else { |
|
9124 |
|
9125 elements = document.querySelectorAll("#" + options.target + " " + key ); |
|
9126 } |
|
9127 |
|
9128 for ( idx = 0, len = elements.length; idx < len; idx++ ) { |
|
9129 |
|
9130 elements[ idx ].classList.toggle( val ); |
|
9131 } |
|
9132 }); |
|
9133 }; |
|
9134 |
|
9135 Popcorn.compose( "applyclass", { |
|
9136 |
|
9137 manifest: { |
|
9138 about: { |
|
9139 name: "Popcorn applyclass Effect", |
|
9140 version: "0.1", |
|
9141 author: "@scottdowne", |
|
9142 website: "scottdowne.wordpress.com" |
|
9143 }, |
|
9144 options: {} |
|
9145 }, |
|
9146 _setup: function( options ) { |
|
9147 |
|
9148 options.classes = {}; |
|
9149 options.applyclass = options.applyclass || ""; |
|
9150 |
|
9151 var classes = options.applyclass.replace( /\s/g, "" ).split( "," ), |
|
9152 item = [], |
|
9153 idx = 0, len = classes.length; |
|
9154 |
|
9155 for ( ; idx < len; idx++ ) { |
|
9156 |
|
9157 item = classes[ idx ].split( ":" ); |
|
9158 |
|
9159 if ( item[ 0 ] ) { |
|
9160 options.classes[ item[ 0 ] ] = item[ 1 ] || ""; |
|
9161 } |
|
9162 } |
|
9163 }, |
|
9164 start: toggleClass, |
|
9165 end: toggleClass |
|
9166 }); |
|
9167 })( Popcorn ); |