1 (function(global, document) { |
|
2 |
|
3 // Popcorn.js does not support archaic browsers |
|
4 if ( !document.addEventListener ) { |
|
5 global.Popcorn = { |
|
6 isSupported: false |
|
7 }; |
|
8 |
|
9 var methods = ( "forEach extend effects error guid sizeOf isArray nop position disable enable destroy " + |
|
10 "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " + |
|
11 "timeUpdate plugin removePlugin compose effect parser xhr getJSONP getScript" ).split(/\s+/); |
|
12 |
|
13 while ( methods.length ) { |
|
14 global.Popcorn[ methods.shift() ] = function() {}; |
|
15 } |
|
16 return; |
|
17 } |
|
18 |
|
19 var |
|
20 |
|
21 AP = Array.prototype, |
|
22 OP = Object.prototype, |
|
23 |
|
24 forEach = AP.forEach, |
|
25 slice = AP.slice, |
|
26 hasOwn = OP.hasOwnProperty, |
|
27 toString = OP.toString, |
|
28 |
|
29 // Copy global Popcorn (may not exist) |
|
30 _Popcorn = global.Popcorn, |
|
31 |
|
32 // ID string matching |
|
33 rIdExp = /^(#([\w\-\_\.]+))$/, |
|
34 |
|
35 // Ready fn cache |
|
36 readyStack = [], |
|
37 readyBound = false, |
|
38 readyFired = false, |
|
39 |
|
40 // Non-public internal data object |
|
41 internal = { |
|
42 events: { |
|
43 hash: {}, |
|
44 apis: {} |
|
45 } |
|
46 }, |
|
47 |
|
48 // Non-public `requestAnimFrame` |
|
49 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ |
|
50 requestAnimFrame = (function(){ |
|
51 return global.requestAnimationFrame || |
|
52 global.webkitRequestAnimationFrame || |
|
53 global.mozRequestAnimationFrame || |
|
54 global.oRequestAnimationFrame || |
|
55 global.msRequestAnimationFrame || |
|
56 function( callback, element ) { |
|
57 global.setTimeout( callback, 16 ); |
|
58 }; |
|
59 }()), |
|
60 |
|
61 refresh = function( obj ) { |
|
62 var currentTime = obj.media.currentTime, |
|
63 animation = obj.options.frameAnimation, |
|
64 disabled = obj.data.disabled, |
|
65 tracks = obj.data.trackEvents, |
|
66 animating = tracks.animating, |
|
67 start = tracks.startIndex, |
|
68 registryByName = Popcorn.registryByName, |
|
69 animIndex = 0, |
|
70 byStart, natives, type; |
|
71 |
|
72 start = Math.min( start + 1, tracks.byStart.length - 2 ); |
|
73 |
|
74 while ( start > 0 && tracks.byStart[ start ] ) { |
|
75 |
|
76 byStart = tracks.byStart[ start ]; |
|
77 natives = byStart._natives; |
|
78 type = natives && natives.type; |
|
79 |
|
80 if ( !natives || |
|
81 ( !!registryByName[ type ] || !!obj[ type ] ) ) { |
|
82 |
|
83 if ( ( byStart.start <= currentTime && byStart.end > currentTime ) && |
|
84 disabled.indexOf( type ) === -1 ) { |
|
85 |
|
86 if ( !byStart._running ) { |
|
87 byStart._running = true; |
|
88 natives.start.call( obj, null, byStart ); |
|
89 |
|
90 // if the 'frameAnimation' option is used, |
|
91 // push the current byStart object into the `animating` cue |
|
92 if ( animation && |
|
93 ( byStart && byStart._running && byStart.natives.frame ) ) { |
|
94 |
|
95 natives.frame.call( obj, null, byStart, currentTime ); |
|
96 } |
|
97 } |
|
98 } else if ( byStart._running === true ) { |
|
99 |
|
100 byStart._running = false; |
|
101 natives.end.call( obj, null, byStart ); |
|
102 |
|
103 if ( animation && byStart._natives.frame ) { |
|
104 animIndex = animating.indexOf( byStart ); |
|
105 if ( animIndex >= 0 ) { |
|
106 animating.splice( animIndex, 1 ); |
|
107 } |
|
108 } |
|
109 } |
|
110 } |
|
111 |
|
112 start--; |
|
113 } |
|
114 }, |
|
115 |
|
116 // Declare constructor |
|
117 // Returns an instance object. |
|
118 Popcorn = function( entity, options ) { |
|
119 // Return new Popcorn object |
|
120 return new Popcorn.p.init( entity, options || null ); |
|
121 }; |
|
122 |
|
123 // Popcorn API version, automatically inserted via build system. |
|
124 Popcorn.version = "@VERSION"; |
|
125 |
|
126 // Boolean flag allowing a client to determine if Popcorn can be supported |
|
127 Popcorn.isSupported = true; |
|
128 |
|
129 // Instance caching |
|
130 Popcorn.instances = []; |
|
131 |
|
132 // Declare a shortcut (Popcorn.p) to and a definition of |
|
133 // the new prototype for our Popcorn constructor |
|
134 Popcorn.p = Popcorn.prototype = { |
|
135 |
|
136 init: function( entity, options ) { |
|
137 |
|
138 var matches; |
|
139 |
|
140 // Supports Popcorn(function () { /../ }) |
|
141 // Originally proposed by Daniel Brooks |
|
142 |
|
143 if ( typeof entity === "function" ) { |
|
144 |
|
145 // If document ready has already fired |
|
146 if ( document.readyState === "interactive" || document.readyState === "complete" ) { |
|
147 |
|
148 entity( document, Popcorn ); |
|
149 |
|
150 return; |
|
151 } |
|
152 // Add `entity` fn to ready stack |
|
153 readyStack.push( entity ); |
|
154 |
|
155 // This process should happen once per page load |
|
156 if ( !readyBound ) { |
|
157 |
|
158 // set readyBound flag |
|
159 readyBound = true; |
|
160 |
|
161 var DOMContentLoaded = function() { |
|
162 |
|
163 readyFired = true; |
|
164 |
|
165 // Remove global DOM ready listener |
|
166 document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); |
|
167 |
|
168 // Execute all ready function in the stack |
|
169 for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) { |
|
170 |
|
171 readyStack[ i ].call( document, Popcorn ); |
|
172 |
|
173 } |
|
174 // GC readyStack |
|
175 readyStack = null; |
|
176 }; |
|
177 |
|
178 // Register global DOM ready listener |
|
179 document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); |
|
180 } |
|
181 |
|
182 return; |
|
183 } |
|
184 |
|
185 // Check if entity is a valid string id |
|
186 matches = rIdExp.exec( entity ); |
|
187 |
|
188 // Get media element by id or object reference |
|
189 this.media = matches && matches.length && matches[ 2 ] ? |
|
190 document.getElementById( matches[ 2 ] ) : |
|
191 entity; |
|
192 |
|
193 // Create an audio or video element property reference |
|
194 this[ ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video" ] = this.media; |
|
195 |
|
196 // Register new instance |
|
197 Popcorn.instances.push( this ); |
|
198 |
|
199 this.options = options || {}; |
|
200 |
|
201 this.isDestroyed = false; |
|
202 |
|
203 this.data = { |
|
204 |
|
205 // Executed by either timeupdate event or in rAF loop |
|
206 timeUpdate: Popcorn.nop, |
|
207 |
|
208 // Allows disabling a plugin per instance |
|
209 disabled: [], |
|
210 |
|
211 // Stores DOM event queues by type |
|
212 events: {}, |
|
213 |
|
214 // Stores Special event hooks data |
|
215 hooks: {}, |
|
216 |
|
217 // Store track event history data |
|
218 history: [], |
|
219 |
|
220 // Stores ad-hoc state related data] |
|
221 state: { |
|
222 volume: this.media.volume |
|
223 }, |
|
224 |
|
225 // Store track event object references by trackId |
|
226 trackRefs: {}, |
|
227 |
|
228 // Playback track event queues |
|
229 trackEvents: { |
|
230 byStart: [{ |
|
231 |
|
232 start: -1, |
|
233 end: -1 |
|
234 }], |
|
235 byEnd: [{ |
|
236 start: -1, |
|
237 end: -1 |
|
238 }], |
|
239 animating: [], |
|
240 startIndex: 0, |
|
241 endIndex: 0, |
|
242 previousUpdateTime: -1 |
|
243 } |
|
244 }; |
|
245 |
|
246 // Wrap true ready check |
|
247 var isReady = function( that ) { |
|
248 |
|
249 var duration, videoDurationPlus; |
|
250 |
|
251 if ( that.media.readyState >= 2 ) { |
|
252 // Adding padding to the front and end of the arrays |
|
253 // this is so we do not fall off either end |
|
254 |
|
255 duration = that.media.duration; |
|
256 // Check for no duration info (NaN) |
|
257 videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1; |
|
258 |
|
259 Popcorn.addTrackEvent( that, { |
|
260 start: videoDurationPlus, |
|
261 end: videoDurationPlus |
|
262 }); |
|
263 |
|
264 if ( that.options.frameAnimation ) { |
|
265 // if Popcorn is created with frameAnimation option set to true, |
|
266 // requestAnimFrame is used instead of "timeupdate" media event. |
|
267 // This is for greater frame time accuracy, theoretically up to |
|
268 // 60 frames per second as opposed to ~4 ( ~every 15-250ms) |
|
269 that.data.timeUpdate = function () { |
|
270 |
|
271 Popcorn.timeUpdate( that, {} ); |
|
272 |
|
273 that.trigger( "timeupdate" ); |
|
274 |
|
275 !that.isDestroyed && requestAnimFrame( that.data.timeUpdate ); |
|
276 }; |
|
277 |
|
278 !that.isDestroyed && requestAnimFrame( that.data.timeUpdate ); |
|
279 |
|
280 } else { |
|
281 |
|
282 that.data.timeUpdate = function( event ) { |
|
283 Popcorn.timeUpdate( that, event ); |
|
284 }; |
|
285 |
|
286 if ( !that.isDestroyed ) { |
|
287 that.media.addEventListener( "timeupdate", that.data.timeUpdate, false ); |
|
288 } |
|
289 } |
|
290 } else { |
|
291 global.setTimeout(function() { |
|
292 isReady( that ); |
|
293 }, 1 ); |
|
294 } |
|
295 }; |
|
296 |
|
297 isReady( this ); |
|
298 |
|
299 return this; |
|
300 } |
|
301 }; |
|
302 |
|
303 // Extend constructor prototype to instance prototype |
|
304 // Allows chaining methods to instances |
|
305 Popcorn.p.init.prototype = Popcorn.p; |
|
306 |
|
307 Popcorn.forEach = function( obj, fn, context ) { |
|
308 |
|
309 if ( !obj || !fn ) { |
|
310 return {}; |
|
311 } |
|
312 |
|
313 context = context || this; |
|
314 |
|
315 var key, len; |
|
316 |
|
317 // Use native whenever possible |
|
318 if ( forEach && obj.forEach === forEach ) { |
|
319 return obj.forEach( fn, context ); |
|
320 } |
|
321 |
|
322 if ( toString.call( obj ) === "[object NodeList]" ) { |
|
323 for ( key = 0, len = obj.length; key < len; key++ ) { |
|
324 fn.call( context, obj[ key ], key, obj ); |
|
325 } |
|
326 return obj; |
|
327 } |
|
328 |
|
329 for ( key in obj ) { |
|
330 if ( hasOwn.call( obj, key ) ) { |
|
331 fn.call( context, obj[ key ], key, obj ); |
|
332 } |
|
333 } |
|
334 return obj; |
|
335 }; |
|
336 |
|
337 Popcorn.extend = function( obj ) { |
|
338 var dest = obj, src = slice.call( arguments, 1 ); |
|
339 |
|
340 Popcorn.forEach( src, function( copy ) { |
|
341 for ( var prop in copy ) { |
|
342 dest[ prop ] = copy[ prop ]; |
|
343 } |
|
344 }); |
|
345 |
|
346 return dest; |
|
347 }; |
|
348 |
|
349 |
|
350 // A Few reusable utils, memoized onto Popcorn |
|
351 Popcorn.extend( Popcorn, { |
|
352 noConflict: function( deep ) { |
|
353 |
|
354 if ( deep ) { |
|
355 global.Popcorn = _Popcorn; |
|
356 } |
|
357 |
|
358 return Popcorn; |
|
359 }, |
|
360 error: function( msg ) { |
|
361 throw new Error( msg ); |
|
362 }, |
|
363 guid: function( prefix ) { |
|
364 Popcorn.guid.counter++; |
|
365 return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter ); |
|
366 }, |
|
367 sizeOf: function( obj ) { |
|
368 var size = 0; |
|
369 |
|
370 for ( var prop in obj ) { |
|
371 size++; |
|
372 } |
|
373 |
|
374 return size; |
|
375 }, |
|
376 isArray: Array.isArray || function( array ) { |
|
377 return toString.call( array ) === "[object Array]"; |
|
378 }, |
|
379 |
|
380 nop: function() {}, |
|
381 |
|
382 position: function( elem ) { |
|
383 |
|
384 var clientRect = elem.getBoundingClientRect(), |
|
385 bounds = {}, |
|
386 doc = elem.ownerDocument, |
|
387 docElem = document.documentElement, |
|
388 body = document.body, |
|
389 clientTop, clientLeft, scrollTop, scrollLeft, top, left; |
|
390 |
|
391 // Determine correct clientTop/Left |
|
392 clientTop = docElem.clientTop || body.clientTop || 0; |
|
393 clientLeft = docElem.clientLeft || body.clientLeft || 0; |
|
394 |
|
395 // Determine correct scrollTop/Left |
|
396 scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop ); |
|
397 scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft ); |
|
398 |
|
399 // Temp top/left |
|
400 top = Math.ceil( clientRect.top + scrollTop - clientTop ); |
|
401 left = Math.ceil( clientRect.left + scrollLeft - clientLeft ); |
|
402 |
|
403 for ( var p in clientRect ) { |
|
404 bounds[ p ] = Math.round( clientRect[ p ] ); |
|
405 } |
|
406 |
|
407 return Popcorn.extend({}, bounds, { top: top, left: left }); |
|
408 }, |
|
409 |
|
410 disable: function( instance, plugin ) { |
|
411 |
|
412 var disabled = instance.data.disabled; |
|
413 |
|
414 if ( disabled.indexOf( plugin ) === -1 ) { |
|
415 disabled.push( plugin ); |
|
416 } |
|
417 |
|
418 refresh( instance ); |
|
419 |
|
420 return instance; |
|
421 }, |
|
422 enable: function( instance, plugin ) { |
|
423 |
|
424 var disabled = instance.data.disabled, |
|
425 index = disabled.indexOf( plugin ); |
|
426 |
|
427 if ( index > -1 ) { |
|
428 disabled.splice( index, 1 ); |
|
429 } |
|
430 |
|
431 refresh( instance ); |
|
432 |
|
433 return instance; |
|
434 }, |
|
435 destroy: function( instance ) { |
|
436 var events = instance.data.events, |
|
437 singleEvent, item, fn; |
|
438 |
|
439 // Iterate through all events and remove them |
|
440 for ( item in events ) { |
|
441 singleEvent = events[ item ]; |
|
442 for ( fn in singleEvent ) { |
|
443 delete singleEvent[ fn ]; |
|
444 } |
|
445 events[ item ] = null; |
|
446 } |
|
447 |
|
448 if ( !instance.isDestroyed ) { |
|
449 instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false ); |
|
450 instance.isDestroyed = true; |
|
451 } |
|
452 } |
|
453 }); |
|
454 |
|
455 // Memoized GUID Counter |
|
456 Popcorn.guid.counter = 1; |
|
457 |
|
458 // Factory to implement getters, setters and controllers |
|
459 // as Popcorn instance methods. The IIFE will create and return |
|
460 // an object with defined methods |
|
461 Popcorn.extend(Popcorn.p, (function() { |
|
462 |
|
463 var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " + |
|
464 "autoplay loop controls muted buffered readyState seeking paused played seekable ended", |
|
465 ret = {}; |
|
466 |
|
467 |
|
468 // Build methods, store in object that is returned and passed to extend |
|
469 Popcorn.forEach( methods.split( /\s+/g ), function( name ) { |
|
470 |
|
471 ret[ name ] = function( arg ) { |
|
472 |
|
473 if ( typeof this.media[ name ] === "function" ) { |
|
474 |
|
475 // Support for shorthanded play(n)/pause(n) jump to currentTime |
|
476 // If arg is not null or undefined and called by one of the |
|
477 // allowed shorthandable methods, then set the currentTime |
|
478 // Supports time as seconds or SMPTE |
|
479 if ( arg != null && /play|pause/.test( name ) ) { |
|
480 this.media.currentTime = Popcorn.util.toSeconds( arg ); |
|
481 } |
|
482 |
|
483 this.media[ name ](); |
|
484 |
|
485 return this; |
|
486 } |
|
487 |
|
488 |
|
489 if ( arg != null ) { |
|
490 |
|
491 this.media[ name ] = arg; |
|
492 |
|
493 return this; |
|
494 } |
|
495 |
|
496 return this.media[ name ]; |
|
497 }; |
|
498 }); |
|
499 |
|
500 return ret; |
|
501 |
|
502 })() |
|
503 ); |
|
504 |
|
505 Popcorn.forEach( "enable disable".split(" "), function( method ) { |
|
506 Popcorn.p[ method ] = function( plugin ) { |
|
507 return Popcorn[ method ]( this, plugin ); |
|
508 }; |
|
509 }); |
|
510 |
|
511 Popcorn.extend(Popcorn.p, { |
|
512 |
|
513 // Rounded currentTime |
|
514 roundTime: function() { |
|
515 return -~this.media.currentTime; |
|
516 }, |
|
517 |
|
518 // Attach an event to a single point in time |
|
519 exec: function( time, fn ) { |
|
520 |
|
521 // Creating a one second track event with an empty end |
|
522 Popcorn.addTrackEvent( this, { |
|
523 start: time, |
|
524 end: time + 1, |
|
525 _running: false, |
|
526 _natives: { |
|
527 start: fn || Popcorn.nop, |
|
528 end: Popcorn.nop, |
|
529 type: "exec" |
|
530 } |
|
531 }); |
|
532 |
|
533 return this; |
|
534 }, |
|
535 |
|
536 // Mute the calling media, optionally toggle |
|
537 mute: function( toggle ) { |
|
538 |
|
539 var event = toggle == null || toggle === true ? "muted" : "unmuted"; |
|
540 |
|
541 // If `toggle` is explicitly `false`, |
|
542 // unmute the media and restore the volume level |
|
543 if ( event === "unmuted" ) { |
|
544 this.media.muted = false; |
|
545 this.media.volume = this.data.state.volume; |
|
546 } |
|
547 |
|
548 // If `toggle` is either null or undefined, |
|
549 // save the current volume and mute the media element |
|
550 if ( event === "muted" ) { |
|
551 this.data.state.volume = this.media.volume; |
|
552 this.media.muted = true; |
|
553 } |
|
554 |
|
555 // Trigger either muted|unmuted event |
|
556 this.trigger( event ); |
|
557 |
|
558 return this; |
|
559 }, |
|
560 |
|
561 // Convenience method, unmute the calling media |
|
562 unmute: function( toggle ) { |
|
563 |
|
564 return this.mute( toggle == null ? false : !toggle ); |
|
565 }, |
|
566 |
|
567 // Get the client bounding box of an instance element |
|
568 position: function() { |
|
569 return Popcorn.position( this.media ); |
|
570 }, |
|
571 |
|
572 // Toggle a plugin's playback behaviour (on or off) per instance |
|
573 toggle: function( plugin ) { |
|
574 return Popcorn[ this.data.disabled.indexOf( plugin ) > -1 ? "enable" : "disable" ]( this, plugin ); |
|
575 }, |
|
576 |
|
577 // Set default values for plugin options objects per instance |
|
578 defaults: function( plugin, defaults ) { |
|
579 |
|
580 // If an array of default configurations is provided, |
|
581 // iterate and apply each to this instance |
|
582 if ( Popcorn.isArray( plugin ) ) { |
|
583 |
|
584 Popcorn.forEach( plugin, function( obj ) { |
|
585 for ( var name in obj ) { |
|
586 this.defaults( name, obj[ name ] ); |
|
587 } |
|
588 }, this ); |
|
589 |
|
590 return this; |
|
591 } |
|
592 |
|
593 if ( !this.options.defaults ) { |
|
594 this.options.defaults = {}; |
|
595 } |
|
596 |
|
597 if ( !this.options.defaults[ plugin ] ) { |
|
598 this.options.defaults[ plugin ] = {}; |
|
599 } |
|
600 |
|
601 Popcorn.extend( this.options.defaults[ plugin ], defaults ); |
|
602 |
|
603 return this; |
|
604 } |
|
605 }); |
|
606 |
|
607 Popcorn.Events = { |
|
608 UIEvents: "blur focus focusin focusout load resize scroll unload", |
|
609 MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick", |
|
610 Events: "loadstart progress suspend emptied stalled play pause " + |
|
611 "loadedmetadata loadeddata waiting playing canplay canplaythrough " + |
|
612 "seeking seeked timeupdate ended ratechange durationchange volumechange" |
|
613 }; |
|
614 |
|
615 Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " + |
|
616 Popcorn.Events.MouseEvents + " " + |
|
617 Popcorn.Events.Events; |
|
618 |
|
619 internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ]; |
|
620 |
|
621 // Privately compile events table at load time |
|
622 (function( events, data ) { |
|
623 |
|
624 var apis = internal.events.apiTypes, |
|
625 eventsList = events.Natives.split( /\s+/g ), |
|
626 idx = 0, len = eventsList.length, prop; |
|
627 |
|
628 for( ; idx < len; idx++ ) { |
|
629 data.hash[ eventsList[idx] ] = true; |
|
630 } |
|
631 |
|
632 apis.forEach(function( val, idx ) { |
|
633 |
|
634 data.apis[ val ] = {}; |
|
635 |
|
636 var apiEvents = events[ val ].split( /\s+/g ), |
|
637 len = apiEvents.length, |
|
638 k = 0; |
|
639 |
|
640 for ( ; k < len; k++ ) { |
|
641 data.apis[ val ][ apiEvents[ k ] ] = true; |
|
642 } |
|
643 }); |
|
644 })( Popcorn.Events, internal.events ); |
|
645 |
|
646 Popcorn.events = { |
|
647 |
|
648 isNative: function( type ) { |
|
649 return !!internal.events.hash[ type ]; |
|
650 }, |
|
651 getInterface: function( type ) { |
|
652 |
|
653 if ( !Popcorn.events.isNative( type ) ) { |
|
654 return false; |
|
655 } |
|
656 |
|
657 var eventApi = internal.events, |
|
658 apis = eventApi.apiTypes, |
|
659 apihash = eventApi.apis, |
|
660 idx = 0, len = apis.length, api, tmp; |
|
661 |
|
662 for ( ; idx < len; idx++ ) { |
|
663 tmp = apis[ idx ]; |
|
664 |
|
665 if ( apihash[ tmp ][ type ] ) { |
|
666 api = tmp; |
|
667 break; |
|
668 } |
|
669 } |
|
670 return api; |
|
671 }, |
|
672 // Compile all native events to single array |
|
673 all: Popcorn.Events.Natives.split( /\s+/g ), |
|
674 // Defines all Event handling static functions |
|
675 fn: { |
|
676 trigger: function( type, data ) { |
|
677 |
|
678 var eventInterface, evt; |
|
679 // setup checks for custom event system |
|
680 if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) { |
|
681 |
|
682 eventInterface = Popcorn.events.getInterface( type ); |
|
683 |
|
684 if ( eventInterface ) { |
|
685 |
|
686 evt = document.createEvent( eventInterface ); |
|
687 evt.initEvent( type, true, true, global, 1 ); |
|
688 |
|
689 this.media.dispatchEvent( evt ); |
|
690 |
|
691 return this; |
|
692 } |
|
693 |
|
694 // Custom events |
|
695 Popcorn.forEach( this.data.events[ type ], function( obj, key ) { |
|
696 |
|
697 obj.call( this, data ); |
|
698 |
|
699 }, this ); |
|
700 |
|
701 } |
|
702 |
|
703 return this; |
|
704 }, |
|
705 listen: function( type, fn ) { |
|
706 |
|
707 var self = this, |
|
708 hasEvents = true, |
|
709 eventHook = Popcorn.events.hooks[ type ], |
|
710 origType = type, |
|
711 tmp; |
|
712 |
|
713 if ( !this.data.events[ type ] ) { |
|
714 this.data.events[ type ] = {}; |
|
715 hasEvents = false; |
|
716 } |
|
717 |
|
718 // Check and setup event hooks |
|
719 if ( eventHook ) { |
|
720 |
|
721 // Execute hook add method if defined |
|
722 if ( eventHook.add ) { |
|
723 eventHook.add.call( this, {}, fn ); |
|
724 } |
|
725 |
|
726 // Reassign event type to our piggyback event type if defined |
|
727 if ( eventHook.bind ) { |
|
728 type = eventHook.bind; |
|
729 } |
|
730 |
|
731 // Reassign handler if defined |
|
732 if ( eventHook.handler ) { |
|
733 tmp = fn; |
|
734 |
|
735 fn = function wrapper( event ) { |
|
736 eventHook.handler.call( self, event, tmp ); |
|
737 }; |
|
738 } |
|
739 |
|
740 // assume the piggy back event is registered |
|
741 hasEvents = true; |
|
742 |
|
743 // Setup event registry entry |
|
744 if ( !this.data.events[ type ] ) { |
|
745 this.data.events[ type ] = {}; |
|
746 // Toggle if the previous assumption was untrue |
|
747 hasEvents = false; |
|
748 } |
|
749 } |
|
750 |
|
751 // Register event and handler |
|
752 this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn; |
|
753 |
|
754 // only attach one event of any type |
|
755 if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) { |
|
756 |
|
757 this.media.addEventListener( type, function( event ) { |
|
758 |
|
759 Popcorn.forEach( self.data.events[ type ], function( obj, key ) { |
|
760 if ( typeof obj === "function" ) { |
|
761 obj.call( self, event ); |
|
762 } |
|
763 }); |
|
764 |
|
765 }, false); |
|
766 } |
|
767 return this; |
|
768 }, |
|
769 unlisten: function( type, fn ) { |
|
770 |
|
771 if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) { |
|
772 |
|
773 delete this.data.events[ type ][ fn ]; |
|
774 |
|
775 return this; |
|
776 } |
|
777 |
|
778 this.data.events[ type ] = null; |
|
779 |
|
780 return this; |
|
781 } |
|
782 }, |
|
783 hooks: { |
|
784 canplayall: { |
|
785 bind: "canplaythrough", |
|
786 add: function( event, callback ) { |
|
787 |
|
788 var state = false; |
|
789 |
|
790 if ( this.media.readyState ) { |
|
791 |
|
792 callback.call( this, event ); |
|
793 |
|
794 state = true; |
|
795 } |
|
796 |
|
797 this.data.hooks.canplayall = { |
|
798 fired: state |
|
799 }; |
|
800 }, |
|
801 // declare special handling instructions |
|
802 handler: function canplayall( event, callback ) { |
|
803 |
|
804 if ( !this.data.hooks.canplayall.fired ) { |
|
805 // trigger original user callback once |
|
806 callback.call( this, event ); |
|
807 |
|
808 this.data.hooks.canplayall.fired = true; |
|
809 } |
|
810 } |
|
811 } |
|
812 } |
|
813 }; |
|
814 |
|
815 // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances |
|
816 Popcorn.forEach( [ "trigger", "listen", "unlisten" ], function( key ) { |
|
817 Popcorn.p[ key ] = Popcorn.events.fn[ key ]; |
|
818 }); |
|
819 |
|
820 // Internal Only - Adds track events to the instance object |
|
821 Popcorn.addTrackEvent = function( obj, track ) { |
|
822 |
|
823 // Determine if this track has default options set for it |
|
824 // If so, apply them to the track object |
|
825 if ( track && track._natives && track._natives.type && |
|
826 ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) { |
|
827 |
|
828 track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track ); |
|
829 } |
|
830 |
|
831 if ( track._natives ) { |
|
832 // Supports user defined track event id |
|
833 track._id = !track.id ? Popcorn.guid( track._natives.type ) : track.id; |
|
834 |
|
835 // Push track event ids into the history |
|
836 obj.data.history.push( track._id ); |
|
837 } |
|
838 |
|
839 track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate ); |
|
840 track.end = Popcorn.util.toSeconds( track.end, obj.options.framerate ); |
|
841 |
|
842 // Store this definition in an array sorted by times |
|
843 var byStart = obj.data.trackEvents.byStart, |
|
844 byEnd = obj.data.trackEvents.byEnd, |
|
845 startIndex, endIndex, |
|
846 currentTime; |
|
847 |
|
848 for ( startIndex = byStart.length - 1; startIndex >= 0; startIndex-- ) { |
|
849 |
|
850 if ( track.start >= byStart[ startIndex ].start ) { |
|
851 byStart.splice( startIndex + 1, 0, track ); |
|
852 break; |
|
853 } |
|
854 } |
|
855 |
|
856 for ( endIndex = byEnd.length - 1; endIndex >= 0; endIndex-- ) { |
|
857 |
|
858 if ( track.end > byEnd[ endIndex ].end ) { |
|
859 byEnd.splice( endIndex + 1, 0, track ); |
|
860 break; |
|
861 } |
|
862 } |
|
863 |
|
864 // Display track event immediately if it's enabled and current |
|
865 if ( track._natives && |
|
866 ( !!Popcorn.registryByName[ track._natives.type ] || !!obj[ track._natives.type ] ) ) { |
|
867 |
|
868 currentTime = obj.media.currentTime; |
|
869 if ( track.end > currentTime && |
|
870 track.start <= currentTime && |
|
871 obj.data.disabled.indexOf( track._natives.type ) === -1 ) { |
|
872 |
|
873 track._running = true; |
|
874 track._natives.start.call( obj, null, track ); |
|
875 |
|
876 if ( obj.options.frameAnimation && |
|
877 track._natives.frame ) { |
|
878 |
|
879 obj.data.trackEvents.animating.push( track ); |
|
880 track._natives.frame.call( obj, null, track, currentTime ); |
|
881 } |
|
882 } |
|
883 } |
|
884 |
|
885 // update startIndex and endIndex |
|
886 if ( startIndex <= obj.data.trackEvents.startIndex && |
|
887 track.start <= obj.data.trackEvents.previousUpdateTime ) { |
|
888 |
|
889 obj.data.trackEvents.startIndex++; |
|
890 } |
|
891 |
|
892 if ( endIndex <= obj.data.trackEvents.endIndex && |
|
893 track.end < obj.data.trackEvents.previousUpdateTime ) { |
|
894 |
|
895 obj.data.trackEvents.endIndex++; |
|
896 } |
|
897 |
|
898 this.timeUpdate( obj, null, true ); |
|
899 |
|
900 // Store references to user added trackevents in ref table |
|
901 if ( track._id ) { |
|
902 Popcorn.addTrackEvent.ref( obj, track ); |
|
903 } |
|
904 }; |
|
905 |
|
906 // Internal Only - Adds track event references to the instance object's trackRefs hash table |
|
907 Popcorn.addTrackEvent.ref = function( obj, track ) { |
|
908 obj.data.trackRefs[ track._id ] = track; |
|
909 |
|
910 return obj; |
|
911 }; |
|
912 |
|
913 Popcorn.removeTrackEvent = function( obj, trackId ) { |
|
914 |
|
915 var historyLen = obj.data.history.length, |
|
916 indexWasAt = 0, |
|
917 byStart = [], |
|
918 byEnd = [], |
|
919 animating = [], |
|
920 history = []; |
|
921 |
|
922 Popcorn.forEach( obj.data.trackEvents.byStart, function( o, i, context ) { |
|
923 // Preserve the original start/end trackEvents |
|
924 if ( !o._id ) { |
|
925 byStart.push( obj.data.trackEvents.byStart[i] ); |
|
926 byEnd.push( obj.data.trackEvents.byEnd[i] ); |
|
927 } |
|
928 |
|
929 // Filter for user track events (vs system track events) |
|
930 if ( o._id ) { |
|
931 |
|
932 // Filter for the trackevent to remove |
|
933 if ( o._id !== trackId ) { |
|
934 byStart.push( obj.data.trackEvents.byStart[i] ); |
|
935 byEnd.push( obj.data.trackEvents.byEnd[i] ); |
|
936 } |
|
937 |
|
938 // Capture the position of the track being removed. |
|
939 if ( o._id === trackId ) { |
|
940 indexWasAt = i; |
|
941 o._natives._teardown && o._natives._teardown.call( obj, o ); |
|
942 } |
|
943 } |
|
944 |
|
945 }); |
|
946 |
|
947 if ( obj.data.trackEvents.animating.length ) { |
|
948 Popcorn.forEach( obj.data.trackEvents.animating, function( o, i, context ) { |
|
949 // Preserve the original start/end trackEvents |
|
950 if ( !o._id ) { |
|
951 animating.push( obj.data.trackEvents.animating[i] ); |
|
952 } |
|
953 |
|
954 // Filter for user track events (vs system track events) |
|
955 if ( o._id ) { |
|
956 // Filter for the trackevent to remove |
|
957 if ( o._id !== trackId ) { |
|
958 animating.push( obj.data.trackEvents.animating[i] ); |
|
959 } |
|
960 } |
|
961 }); |
|
962 } |
|
963 |
|
964 // Update |
|
965 if ( indexWasAt <= obj.data.trackEvents.startIndex ) { |
|
966 obj.data.trackEvents.startIndex--; |
|
967 } |
|
968 |
|
969 if ( indexWasAt <= obj.data.trackEvents.endIndex ) { |
|
970 obj.data.trackEvents.endIndex--; |
|
971 } |
|
972 |
|
973 obj.data.trackEvents.byStart = byStart; |
|
974 obj.data.trackEvents.byEnd = byEnd; |
|
975 obj.data.trackEvents.animating = animating; |
|
976 |
|
977 for ( var i = 0; i < historyLen; i++ ) { |
|
978 if ( obj.data.history[ i ] !== trackId ) { |
|
979 history.push( obj.data.history[ i ] ); |
|
980 } |
|
981 } |
|
982 |
|
983 // Update ordered history array |
|
984 obj.data.history = history; |
|
985 |
|
986 // Update track event references |
|
987 Popcorn.removeTrackEvent.ref( obj, trackId ); |
|
988 }; |
|
989 |
|
990 // Internal Only - Removes track event references from instance object's trackRefs hash table |
|
991 Popcorn.removeTrackEvent.ref = function( obj, trackId ) { |
|
992 delete obj.data.trackRefs[ trackId ]; |
|
993 |
|
994 return obj; |
|
995 }; |
|
996 |
|
997 // Return an array of track events bound to this instance object |
|
998 Popcorn.getTrackEvents = function( obj ) { |
|
999 |
|
1000 var trackevents = [], |
|
1001 refs = obj.data.trackEvents.byStart, |
|
1002 length = refs.length, |
|
1003 idx = 0, |
|
1004 ref; |
|
1005 |
|
1006 for ( ; idx < length; idx++ ) { |
|
1007 ref = refs[ idx ]; |
|
1008 // Return only user attributed track event references |
|
1009 if ( ref._id ) { |
|
1010 trackevents.push( ref ); |
|
1011 } |
|
1012 } |
|
1013 |
|
1014 return trackevents; |
|
1015 }; |
|
1016 |
|
1017 // Internal Only - Returns an instance object's trackRefs hash table |
|
1018 Popcorn.getTrackEvents.ref = function( obj ) { |
|
1019 return obj.data.trackRefs; |
|
1020 }; |
|
1021 |
|
1022 // Return a single track event bound to this instance object |
|
1023 Popcorn.getTrackEvent = function( obj, trackId ) { |
|
1024 return obj.data.trackRefs[ trackId ]; |
|
1025 }; |
|
1026 |
|
1027 // Internal Only - Returns an instance object's track reference by track id |
|
1028 Popcorn.getTrackEvent.ref = function( obj, trackId ) { |
|
1029 return obj.data.trackRefs[ trackId ]; |
|
1030 }; |
|
1031 |
|
1032 Popcorn.getLastTrackEventId = function( obj ) { |
|
1033 return obj.data.history[ obj.data.history.length - 1 ]; |
|
1034 }; |
|
1035 |
|
1036 Popcorn.timeUpdate = function( obj, event ) { |
|
1037 |
|
1038 var currentTime = obj.media.currentTime, |
|
1039 previousTime = obj.data.trackEvents.previousUpdateTime, |
|
1040 tracks = obj.data.trackEvents, |
|
1041 animating = tracks.animating, |
|
1042 end = tracks.endIndex, |
|
1043 start = tracks.startIndex, |
|
1044 animIndex = 0, |
|
1045 |
|
1046 registryByName = Popcorn.registryByName, |
|
1047 |
|
1048 byEnd, byStart, byAnimate, natives, type; |
|
1049 |
|
1050 // Playbar advancing |
|
1051 if ( previousTime <= currentTime ) { |
|
1052 |
|
1053 while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) { |
|
1054 |
|
1055 byEnd = tracks.byEnd[ end ]; |
|
1056 natives = byEnd._natives; |
|
1057 type = natives && natives.type; |
|
1058 |
|
1059 // If plugin does not exist on this instance, remove it |
|
1060 if ( !natives || |
|
1061 ( !!registryByName[ type ] || |
|
1062 !!obj[ type ] ) ) { |
|
1063 |
|
1064 if ( byEnd._running === true ) { |
|
1065 byEnd._running = false; |
|
1066 natives.end.call( obj, event, byEnd ); |
|
1067 } |
|
1068 |
|
1069 end++; |
|
1070 } else { |
|
1071 // remove track event |
|
1072 Popcorn.removeTrackEvent( obj, byEnd._id ); |
|
1073 return; |
|
1074 } |
|
1075 } |
|
1076 |
|
1077 while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) { |
|
1078 |
|
1079 byStart = tracks.byStart[ start ]; |
|
1080 natives = byStart._natives; |
|
1081 type = natives && natives.type; |
|
1082 |
|
1083 // If plugin does not exist on this instance, remove it |
|
1084 if ( !natives || |
|
1085 ( !!registryByName[ type ] || |
|
1086 !!obj[ type ] ) ) { |
|
1087 |
|
1088 if ( byStart.end > currentTime && |
|
1089 byStart._running === false && |
|
1090 obj.data.disabled.indexOf( type ) === -1 ) { |
|
1091 |
|
1092 byStart._running = true; |
|
1093 natives.start.call( obj, event, byStart ); |
|
1094 |
|
1095 // If the `frameAnimation` option is used, |
|
1096 // push the current byStart object into the `animating` cue |
|
1097 if ( obj.options.frameAnimation && |
|
1098 ( byStart && byStart._running && byStart._natives.frame ) ) { |
|
1099 |
|
1100 animating.push( byStart ); |
|
1101 } |
|
1102 } |
|
1103 start++; |
|
1104 } else { |
|
1105 // remove track event |
|
1106 Popcorn.removeTrackEvent( obj, byStart._id ); |
|
1107 return; |
|
1108 } |
|
1109 } |
|
1110 |
|
1111 // If the `frameAnimation` option is used, iterate the animating track |
|
1112 // and execute the `frame` callback |
|
1113 if ( obj.options.frameAnimation ) { |
|
1114 while ( animIndex < animating.length ) { |
|
1115 |
|
1116 byAnimate = animating[ animIndex ]; |
|
1117 |
|
1118 if ( !byAnimate._running ) { |
|
1119 animating.splice( animIndex, 1 ); |
|
1120 } else { |
|
1121 byAnimate._natives.frame.call( obj, event, byAnimate, currentTime ); |
|
1122 animIndex++; |
|
1123 } |
|
1124 } |
|
1125 } |
|
1126 |
|
1127 // Playbar receding |
|
1128 } else if ( previousTime > currentTime ) { |
|
1129 |
|
1130 while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) { |
|
1131 |
|
1132 byStart = tracks.byStart[ start ]; |
|
1133 natives = byStart._natives; |
|
1134 type = natives && natives.type; |
|
1135 |
|
1136 // if plugin does not exist on this instance, remove it |
|
1137 if ( !natives || |
|
1138 ( !!registryByName[ type ] || |
|
1139 !!obj[ type ] ) ) { |
|
1140 |
|
1141 if ( byStart._running === true ) { |
|
1142 byStart._running = false; |
|
1143 natives.end.call( obj, event, byStart ); |
|
1144 } |
|
1145 start--; |
|
1146 } else { |
|
1147 // remove track event |
|
1148 Popcorn.removeTrackEvent( obj, byStart._id ); |
|
1149 return; |
|
1150 } |
|
1151 } |
|
1152 |
|
1153 while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) { |
|
1154 |
|
1155 byEnd = tracks.byEnd[ end ]; |
|
1156 natives = byEnd._natives; |
|
1157 type = natives && natives.type; |
|
1158 |
|
1159 // if plugin does not exist on this instance, remove it |
|
1160 if ( !natives || |
|
1161 ( !!registryByName[ type ] || |
|
1162 !!obj[ type ] ) ) { |
|
1163 |
|
1164 if ( byEnd.start <= currentTime && |
|
1165 byEnd._running === false && |
|
1166 obj.data.disabled.indexOf( type ) === -1 ) { |
|
1167 |
|
1168 byEnd._running = true; |
|
1169 natives.start.call( obj, event, byEnd ); |
|
1170 |
|
1171 // If the `frameAnimation` option is used, |
|
1172 // push the current byEnd object into the `animating` cue |
|
1173 if ( obj.options.frameAnimation && |
|
1174 ( byEnd && byEnd._running && byEnd._natives.frame ) ) { |
|
1175 |
|
1176 animating.push( byEnd ); |
|
1177 } |
|
1178 } |
|
1179 end--; |
|
1180 } else { |
|
1181 // remove track event |
|
1182 Popcorn.removeTrackEvent( obj, byEnd._id ); |
|
1183 return; |
|
1184 } |
|
1185 } |
|
1186 |
|
1187 // If the `frameAnimation` option is used, iterate the animating track |
|
1188 // and execute the `frame` callback |
|
1189 if ( obj.options.frameAnimation ) { |
|
1190 while ( animIndex < animating.length ) { |
|
1191 |
|
1192 byAnimate = animating[ animIndex ]; |
|
1193 |
|
1194 if ( !byAnimate._running ) { |
|
1195 animating.splice( animIndex, 1 ); |
|
1196 } else { |
|
1197 byAnimate._natives.frame.call( obj, event, byAnimate, currentTime ); |
|
1198 animIndex++; |
|
1199 } |
|
1200 } |
|
1201 } |
|
1202 // time bar is not moving ( video is paused ) |
|
1203 } |
|
1204 |
|
1205 tracks.endIndex = end; |
|
1206 tracks.startIndex = start; |
|
1207 tracks.previousUpdateTime = currentTime; |
|
1208 }; |
|
1209 |
|
1210 // Map and Extend TrackEvent functions to all Popcorn instances |
|
1211 Popcorn.extend( Popcorn.p, { |
|
1212 |
|
1213 getTrackEvents: function() { |
|
1214 return Popcorn.getTrackEvents.call( null, this ); |
|
1215 }, |
|
1216 |
|
1217 getTrackEvent: function( id ) { |
|
1218 return Popcorn.getTrackEvent.call( null, this, id ); |
|
1219 }, |
|
1220 |
|
1221 getLastTrackEventId: function() { |
|
1222 return Popcorn.getLastTrackEventId.call( null, this ); |
|
1223 }, |
|
1224 |
|
1225 removeTrackEvent: function( id ) { |
|
1226 |
|
1227 Popcorn.removeTrackEvent.call( null, this, id ); |
|
1228 return this; |
|
1229 }, |
|
1230 |
|
1231 removePlugin: function( name ) { |
|
1232 Popcorn.removePlugin.call( null, this, name ); |
|
1233 return this; |
|
1234 }, |
|
1235 |
|
1236 timeUpdate: function( event ) { |
|
1237 Popcorn.timeUpdate.call( null, this, event ); |
|
1238 return this; |
|
1239 }, |
|
1240 |
|
1241 destroy: function() { |
|
1242 Popcorn.destroy.call( null, this ); |
|
1243 return this; |
|
1244 } |
|
1245 }); |
|
1246 |
|
1247 // Plugin manifests |
|
1248 Popcorn.manifest = {}; |
|
1249 // Plugins are registered |
|
1250 Popcorn.registry = []; |
|
1251 Popcorn.registryByName = {}; |
|
1252 // An interface for extending Popcorn |
|
1253 // with plugin functionality |
|
1254 Popcorn.plugin = function( name, definition, manifest ) { |
|
1255 |
|
1256 if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { |
|
1257 Popcorn.error( "'" + name + "' is a protected function name" ); |
|
1258 return; |
|
1259 } |
|
1260 |
|
1261 // Provides some sugar, but ultimately extends |
|
1262 // the definition into Popcorn.p |
|
1263 var reserved = [ "start", "end" ], |
|
1264 plugin = {}, |
|
1265 setup, |
|
1266 isfn = typeof definition === "function", |
|
1267 methods = [ "_setup", "_teardown", "start", "end", "frame" ]; |
|
1268 |
|
1269 // combines calls of two function calls into one |
|
1270 var combineFn = function( first, second ) { |
|
1271 |
|
1272 first = first || Popcorn.nop; |
|
1273 second = second || Popcorn.nop; |
|
1274 |
|
1275 return function() { |
|
1276 first.apply( this, arguments ); |
|
1277 second.apply( this, arguments ); |
|
1278 }; |
|
1279 }; |
|
1280 |
|
1281 // If `manifest` arg is undefined, check for manifest within the `definition` object |
|
1282 // If no `definition.manifest`, an empty object is a sufficient fallback |
|
1283 Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {}; |
|
1284 |
|
1285 // apply safe, and empty default functions |
|
1286 methods.forEach(function( method ) { |
|
1287 definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name ); |
|
1288 }); |
|
1289 |
|
1290 var pluginFn = function( setup, options ) { |
|
1291 |
|
1292 if ( !options ) { |
|
1293 return this; |
|
1294 } |
|
1295 |
|
1296 // Storing the plugin natives |
|
1297 var natives = options._natives = {}, |
|
1298 compose = "", |
|
1299 defaults, originalOpts, manifestOpts, mergedSetupOpts; |
|
1300 |
|
1301 Popcorn.extend( natives, setup ); |
|
1302 |
|
1303 options._natives.type = name; |
|
1304 options._running = false; |
|
1305 |
|
1306 natives.start = natives.start || natives[ "in" ]; |
|
1307 natives.end = natives.end || natives[ "out" ]; |
|
1308 |
|
1309 // extend teardown to always call end if running |
|
1310 natives._teardown = combineFn(function() { |
|
1311 |
|
1312 var args = slice.call( arguments ); |
|
1313 |
|
1314 // end function signature is not the same as teardown, |
|
1315 // put null on the front of arguments for the event parameter |
|
1316 args.unshift( null ); |
|
1317 |
|
1318 // only call end if event is running |
|
1319 args[ 1 ]._running && natives.end.apply( this, args ); |
|
1320 }, natives._teardown ); |
|
1321 |
|
1322 // Check for previously set default options |
|
1323 defaults = this.options.defaults && this.options.defaults[ options._natives && options._natives.type ]; |
|
1324 |
|
1325 // default to an empty string if no effect exists |
|
1326 // split string into an array of effects |
|
1327 options.compose = options.compose && options.compose.split( " " ) || []; |
|
1328 options.effect = options.effect && options.effect.split( " " ) || []; |
|
1329 |
|
1330 // join the two arrays together |
|
1331 options.compose = options.compose.concat( options.effect ); |
|
1332 |
|
1333 options.compose.forEach(function( composeOption ) { |
|
1334 |
|
1335 // if the requested compose is garbage, throw it away |
|
1336 compose = Popcorn.compositions[ composeOption ] || {}; |
|
1337 |
|
1338 // extends previous functions with compose function |
|
1339 methods.forEach(function( method ) { |
|
1340 natives[ method ] = combineFn( natives[ method ], compose[ method ] ); |
|
1341 }); |
|
1342 }); |
|
1343 |
|
1344 // Ensure a manifest object, an empty object is a sufficient fallback |
|
1345 options._natives.manifest = manifest; |
|
1346 |
|
1347 // Checks for expected properties |
|
1348 if ( !( "start" in options ) ) { |
|
1349 options.start = options[ "in" ] || 0; |
|
1350 } |
|
1351 |
|
1352 if ( !( "end" in options ) ) { |
|
1353 options.end = options[ "out" ] || this.duration() || Number.MAX_VALUE; |
|
1354 } |
|
1355 |
|
1356 // Merge with defaults if they exist, make sure per call is prioritized |
|
1357 mergedSetupOpts = defaults ? Popcorn.extend( {}, defaults, options ) : |
|
1358 options; |
|
1359 |
|
1360 // Resolves 239, 241, 242 |
|
1361 if ( !mergedSetupOpts.target ) { |
|
1362 |
|
1363 // Sometimes the manifest may be missing entirely |
|
1364 // or it has an options object that doesn't have a `target` property |
|
1365 manifestOpts = "options" in manifest && manifest.options; |
|
1366 |
|
1367 mergedSetupOpts.target = manifestOpts && "target" in manifestOpts && manifestOpts.target; |
|
1368 } |
|
1369 |
|
1370 // Trigger _setup method if exists |
|
1371 options._natives._setup && options._natives._setup.call( this, mergedSetupOpts ); |
|
1372 |
|
1373 // Create new track event for this instance |
|
1374 Popcorn.addTrackEvent( this, Popcorn.extend( mergedSetupOpts, options ) ); |
|
1375 |
|
1376 // Future support for plugin event definitions |
|
1377 // for all of the native events |
|
1378 Popcorn.forEach( setup, function( callback, type ) { |
|
1379 |
|
1380 if ( type !== "type" ) { |
|
1381 |
|
1382 if ( reserved.indexOf( type ) === -1 ) { |
|
1383 |
|
1384 this.listen( type, callback ); |
|
1385 } |
|
1386 } |
|
1387 |
|
1388 }, this ); |
|
1389 |
|
1390 return this; |
|
1391 }; |
|
1392 |
|
1393 // Assign new named definition |
|
1394 plugin[ name ] = function( options ) { |
|
1395 return pluginFn.call( this, isfn ? definition.call( this, options ) : definition, |
|
1396 options ); |
|
1397 }; |
|
1398 |
|
1399 // Extend Popcorn.p with new named definition |
|
1400 Popcorn.extend( Popcorn.p, plugin ); |
|
1401 |
|
1402 // Push into the registry |
|
1403 var entry = { |
|
1404 fn: plugin[ name ], |
|
1405 definition: definition, |
|
1406 base: definition, |
|
1407 parents: [], |
|
1408 name: name |
|
1409 }; |
|
1410 Popcorn.registry.push( |
|
1411 Popcorn.extend( plugin, entry, { |
|
1412 type: name |
|
1413 }) |
|
1414 ); |
|
1415 Popcorn.registryByName[ name ] = entry; |
|
1416 |
|
1417 return plugin; |
|
1418 }; |
|
1419 |
|
1420 // Storage for plugin function errors |
|
1421 Popcorn.plugin.errors = []; |
|
1422 |
|
1423 // Returns wrapped plugin function |
|
1424 function safeTry( fn, pluginName ) { |
|
1425 return function() { |
|
1426 |
|
1427 // When Popcorn.plugin.debug is true, do not suppress errors |
|
1428 if ( Popcorn.plugin.debug ) { |
|
1429 return fn.apply( this, arguments ); |
|
1430 } |
|
1431 |
|
1432 try { |
|
1433 return fn.apply( this, arguments ); |
|
1434 } catch ( ex ) { |
|
1435 |
|
1436 // Push plugin function errors into logging queue |
|
1437 Popcorn.plugin.errors.push({ |
|
1438 plugin: pluginName, |
|
1439 thrown: ex, |
|
1440 source: fn.toString() |
|
1441 }); |
|
1442 |
|
1443 // Trigger an error that the instance can listen for |
|
1444 // and react to |
|
1445 this.trigger( "error", Popcorn.plugin.errors ); |
|
1446 } |
|
1447 }; |
|
1448 } |
|
1449 |
|
1450 // Debug-mode flag for plugin development |
|
1451 Popcorn.plugin.debug = false; |
|
1452 |
|
1453 // removePlugin( type ) removes all tracks of that from all instances of popcorn |
|
1454 // removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn |
|
1455 Popcorn.removePlugin = function( obj, name ) { |
|
1456 |
|
1457 // Check if we are removing plugin from an instance or from all of Popcorn |
|
1458 if ( !name ) { |
|
1459 |
|
1460 // Fix the order |
|
1461 name = obj; |
|
1462 obj = Popcorn.p; |
|
1463 |
|
1464 if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { |
|
1465 Popcorn.error( "'" + name + "' is a protected function name" ); |
|
1466 return; |
|
1467 } |
|
1468 |
|
1469 var registryLen = Popcorn.registry.length, |
|
1470 registryIdx; |
|
1471 |
|
1472 // remove plugin reference from registry |
|
1473 for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) { |
|
1474 if ( Popcorn.registry[ registryIdx ].name === name ) { |
|
1475 Popcorn.registry.splice( registryIdx, 1 ); |
|
1476 delete Popcorn.registryByName[ name ]; |
|
1477 delete Popcorn.manifest[ name ]; |
|
1478 |
|
1479 // delete the plugin |
|
1480 delete obj[ name ]; |
|
1481 |
|
1482 // plugin found and removed, stop checking, we are done |
|
1483 return; |
|
1484 } |
|
1485 } |
|
1486 |
|
1487 } |
|
1488 |
|
1489 var byStart = obj.data.trackEvents.byStart, |
|
1490 byEnd = obj.data.trackEvents.byEnd, |
|
1491 animating = obj.data.trackEvents.animating, |
|
1492 idx, sl; |
|
1493 |
|
1494 // remove all trackEvents |
|
1495 for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) { |
|
1496 |
|
1497 if ( ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) && |
|
1498 ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) ) { |
|
1499 |
|
1500 byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] ); |
|
1501 |
|
1502 byStart.splice( idx, 1 ); |
|
1503 byEnd.splice( idx, 1 ); |
|
1504 |
|
1505 // update for loop if something removed, but keep checking |
|
1506 idx--; sl--; |
|
1507 if ( obj.data.trackEvents.startIndex <= idx ) { |
|
1508 obj.data.trackEvents.startIndex--; |
|
1509 obj.data.trackEvents.endIndex--; |
|
1510 } |
|
1511 } |
|
1512 } |
|
1513 |
|
1514 //remove all animating events |
|
1515 for ( idx = 0, sl = animating.length; idx < sl; idx++ ) { |
|
1516 |
|
1517 if ( animating[ idx ] && animating[ idx ]._natives && animating[ idx ]._natives.type === name ) { |
|
1518 |
|
1519 animating.splice( idx, 1 ); |
|
1520 |
|
1521 // update for loop if something removed, but keep checking |
|
1522 idx--; sl--; |
|
1523 } |
|
1524 } |
|
1525 |
|
1526 }; |
|
1527 |
|
1528 Popcorn.compositions = {}; |
|
1529 |
|
1530 // Plugin inheritance |
|
1531 Popcorn.compose = function( name, definition, manifest ) { |
|
1532 |
|
1533 // If `manifest` arg is undefined, check for manifest within the `definition` object |
|
1534 // If no `definition.manifest`, an empty object is a sufficient fallback |
|
1535 Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {}; |
|
1536 |
|
1537 // register the effect by name |
|
1538 Popcorn.compositions[ name ] = definition; |
|
1539 }; |
|
1540 |
|
1541 Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose; |
|
1542 |
|
1543 // stores parsers keyed on filetype |
|
1544 Popcorn.parsers = {}; |
|
1545 |
|
1546 // An interface for extending Popcorn |
|
1547 // with parser functionality |
|
1548 Popcorn.parser = function( name, type, definition ) { |
|
1549 |
|
1550 if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { |
|
1551 Popcorn.error( "'" + name + "' is a protected function name" ); |
|
1552 return; |
|
1553 } |
|
1554 |
|
1555 // fixes parameters for overloaded function call |
|
1556 if ( typeof type === "function" && !definition ) { |
|
1557 definition = type; |
|
1558 type = ""; |
|
1559 } |
|
1560 |
|
1561 if ( typeof definition !== "function" || typeof type !== "string" ) { |
|
1562 return; |
|
1563 } |
|
1564 |
|
1565 // Provides some sugar, but ultimately extends |
|
1566 // the definition into Popcorn.p |
|
1567 |
|
1568 var natives = Popcorn.events.all, |
|
1569 parseFn, |
|
1570 parser = {}; |
|
1571 |
|
1572 parseFn = function( filename, callback ) { |
|
1573 |
|
1574 if ( !filename ) { |
|
1575 return this; |
|
1576 } |
|
1577 |
|
1578 var that = this; |
|
1579 |
|
1580 Popcorn.xhr({ |
|
1581 url: filename, |
|
1582 dataType: type, |
|
1583 success: function( data ) { |
|
1584 |
|
1585 var tracksObject = definition( data ), |
|
1586 tracksData, |
|
1587 tracksDataLen, |
|
1588 tracksDef, |
|
1589 idx = 0; |
|
1590 |
|
1591 tracksData = tracksObject.data || []; |
|
1592 tracksDataLen = tracksData.length; |
|
1593 tracksDef = null; |
|
1594 |
|
1595 // If no tracks to process, return immediately |
|
1596 if ( !tracksDataLen ) { |
|
1597 return; |
|
1598 } |
|
1599 |
|
1600 // Create tracks out of parsed object |
|
1601 for ( ; idx < tracksDataLen; idx++ ) { |
|
1602 |
|
1603 tracksDef = tracksData[ idx ]; |
|
1604 |
|
1605 for ( var key in tracksDef ) { |
|
1606 |
|
1607 if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) { |
|
1608 |
|
1609 that[ key ]( tracksDef[ key ] ); |
|
1610 } |
|
1611 } |
|
1612 } |
|
1613 if ( callback ) { |
|
1614 callback(); |
|
1615 } |
|
1616 } |
|
1617 }); |
|
1618 |
|
1619 return this; |
|
1620 }; |
|
1621 |
|
1622 // Assign new named definition |
|
1623 parser[ name ] = parseFn; |
|
1624 |
|
1625 // Extend Popcorn.p with new named definition |
|
1626 Popcorn.extend( Popcorn.p, parser ); |
|
1627 |
|
1628 // keys the function name by filetype extension |
|
1629 //Popcorn.parsers[ name ] = true; |
|
1630 |
|
1631 return parser; |
|
1632 }; |
|
1633 |
|
1634 Popcorn.player = function( name, player ) { |
|
1635 |
|
1636 player = player || {}; |
|
1637 |
|
1638 var playerFn = function( target, src, options ) { |
|
1639 |
|
1640 options = options || {}; |
|
1641 |
|
1642 // List of events |
|
1643 var date = new Date() / 1000, |
|
1644 baselineTime = date, |
|
1645 currentTime = 0, |
|
1646 volume = 1, |
|
1647 muted = false, |
|
1648 events = {}, |
|
1649 |
|
1650 // The container div of the resource |
|
1651 container = document.getElementById( rIdExp.exec( target ) && rIdExp.exec( target )[ 2 ] ) || |
|
1652 document.getElementById( target ) || |
|
1653 target, |
|
1654 basePlayer = {}, |
|
1655 timeout, |
|
1656 popcorn; |
|
1657 |
|
1658 // copies a div into the media object |
|
1659 for( var val in container ) { |
|
1660 |
|
1661 if ( typeof container[ val ] === "object" ) { |
|
1662 |
|
1663 basePlayer[ val ] = container[ val ]; |
|
1664 } else if ( typeof container[ val ] === "function" ) { |
|
1665 |
|
1666 basePlayer[ val ] = (function( value ) { |
|
1667 |
|
1668 // this is a stupid ugly kludgy hack in honour of Safari |
|
1669 // in Safari a NodeList is a function, not an object |
|
1670 if ( "length" in container[ value ] && !container[ value ].call ) { |
|
1671 |
|
1672 return container[ value ]; |
|
1673 } else { |
|
1674 |
|
1675 return function() { |
|
1676 |
|
1677 return container[ value ].apply( container, arguments ); |
|
1678 }; |
|
1679 } |
|
1680 }( val )); |
|
1681 } else { |
|
1682 |
|
1683 Popcorn.player.defineProperty( basePlayer, val, { |
|
1684 get: (function( value ) { |
|
1685 |
|
1686 return function() { |
|
1687 |
|
1688 return container[ value ]; |
|
1689 }; |
|
1690 }( val )), |
|
1691 set: Popcorn.nop, |
|
1692 configurable: true |
|
1693 }); |
|
1694 } |
|
1695 } |
|
1696 |
|
1697 var timeupdate = function() { |
|
1698 |
|
1699 date = new Date() / 1000; |
|
1700 |
|
1701 if ( !basePlayer.paused ) { |
|
1702 |
|
1703 basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime ); |
|
1704 basePlayer.dispatchEvent( "timeupdate" ); |
|
1705 timeout = setTimeout( timeupdate, 10 ); |
|
1706 } |
|
1707 |
|
1708 baselineTime = date; |
|
1709 }; |
|
1710 |
|
1711 basePlayer.play = function() { |
|
1712 |
|
1713 this.paused = false; |
|
1714 |
|
1715 if ( basePlayer.readyState >= 4 ) { |
|
1716 |
|
1717 baselineTime = new Date() / 1000; |
|
1718 basePlayer.dispatchEvent( "play" ); |
|
1719 timeupdate(); |
|
1720 } |
|
1721 }; |
|
1722 |
|
1723 basePlayer.pause = function() { |
|
1724 |
|
1725 this.paused = true; |
|
1726 basePlayer.dispatchEvent( "pause" ); |
|
1727 }; |
|
1728 |
|
1729 Popcorn.player.defineProperty( basePlayer, "currentTime", { |
|
1730 get: function() { |
|
1731 |
|
1732 return currentTime; |
|
1733 }, |
|
1734 set: function( val ) { |
|
1735 |
|
1736 // make sure val is a number |
|
1737 currentTime = +val; |
|
1738 basePlayer.dispatchEvent( "timeupdate" ); |
|
1739 return currentTime; |
|
1740 }, |
|
1741 configurable: true |
|
1742 }); |
|
1743 |
|
1744 Popcorn.player.defineProperty( basePlayer, "volume", { |
|
1745 get: function() { |
|
1746 |
|
1747 return volume; |
|
1748 }, |
|
1749 set: function( val ) { |
|
1750 |
|
1751 // make sure val is a number |
|
1752 volume = +val; |
|
1753 basePlayer.dispatchEvent( "volumechange" ); |
|
1754 return volume; |
|
1755 }, |
|
1756 configurable: true |
|
1757 }); |
|
1758 |
|
1759 Popcorn.player.defineProperty( basePlayer, "muted", { |
|
1760 get: function() { |
|
1761 |
|
1762 return muted; |
|
1763 }, |
|
1764 set: function( val ) { |
|
1765 |
|
1766 // make sure val is a number |
|
1767 muted = +val; |
|
1768 basePlayer.dispatchEvent( "volumechange" ); |
|
1769 return muted; |
|
1770 }, |
|
1771 configurable: true |
|
1772 }); |
|
1773 |
|
1774 // Adds an event listener to the object |
|
1775 basePlayer.addEventListener = function( evtName, fn ) { |
|
1776 |
|
1777 if ( !events[ evtName ] ) { |
|
1778 |
|
1779 events[ evtName ] = []; |
|
1780 } |
|
1781 |
|
1782 events[ evtName ].push( fn ); |
|
1783 return fn; |
|
1784 }; |
|
1785 |
|
1786 // Can take event object or simple string |
|
1787 basePlayer.dispatchEvent = function( oEvent ) { |
|
1788 |
|
1789 var evt, |
|
1790 self = this, |
|
1791 eventInterface, |
|
1792 eventName = oEvent.type; |
|
1793 |
|
1794 // A string was passed, create event object |
|
1795 if ( !eventName ) { |
|
1796 |
|
1797 eventName = oEvent; |
|
1798 eventInterface = Popcorn.events.getInterface( eventName ); |
|
1799 |
|
1800 if ( eventInterface ) { |
|
1801 |
|
1802 evt = document.createEvent( eventInterface ); |
|
1803 evt.initEvent( eventName, true, true, window, 1 ); |
|
1804 } |
|
1805 } |
|
1806 |
|
1807 Popcorn.forEach( events[ eventName ], function( val ) { |
|
1808 |
|
1809 val.call( self, evt, self ); |
|
1810 }); |
|
1811 }; |
|
1812 |
|
1813 // Attempt to get src from playerFn parameter |
|
1814 basePlayer.src = src || ""; |
|
1815 basePlayer.readyState = 0; |
|
1816 basePlayer.duration = 0; |
|
1817 basePlayer.paused = true; |
|
1818 basePlayer.ended = 0; |
|
1819 |
|
1820 if ( player._setup ) { |
|
1821 |
|
1822 player._setup.call( basePlayer, options ); |
|
1823 } else { |
|
1824 |
|
1825 // there is no setup, which means there is nothing to load |
|
1826 basePlayer.readyState = 4; |
|
1827 basePlayer.dispatchEvent( "load" ); |
|
1828 basePlayer.dispatchEvent( "loadeddata" ); |
|
1829 } |
|
1830 |
|
1831 // when a custom player is loaded, load basePlayer state into custom player |
|
1832 basePlayer.addEventListener( "load", function() { |
|
1833 |
|
1834 // if a player is not ready before currentTime is called, this will set it after it is ready |
|
1835 basePlayer.currentTime = currentTime; |
|
1836 |
|
1837 // same as above with volume and muted |
|
1838 basePlayer.volume = volume; |
|
1839 basePlayer.muted = muted; |
|
1840 }); |
|
1841 |
|
1842 basePlayer.addEventListener( "loadeddata", function() { |
|
1843 |
|
1844 // if play was called before player ready, start playing video |
|
1845 !basePlayer.paused && basePlayer.play(); |
|
1846 }); |
|
1847 |
|
1848 popcorn = new Popcorn.p.init( basePlayer, options ); |
|
1849 |
|
1850 return popcorn; |
|
1851 }; |
|
1852 |
|
1853 Popcorn[ name ] = Popcorn[ name ] || playerFn; |
|
1854 }; |
|
1855 |
|
1856 Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) { |
|
1857 |
|
1858 object.__defineGetter__( description, options.get || Popcorn.nop ); |
|
1859 object.__defineSetter__( description, options.set || Popcorn.nop ); |
|
1860 }; |
|
1861 |
|
1862 // Cache references to reused RegExps |
|
1863 var rparams = /\?/, |
|
1864 // XHR Setup object |
|
1865 setup = { |
|
1866 url: "", |
|
1867 data: "", |
|
1868 dataType: "", |
|
1869 success: Popcorn.nop, |
|
1870 type: "GET", |
|
1871 async: true, |
|
1872 xhr: function() { |
|
1873 return new global.XMLHttpRequest(); |
|
1874 } |
|
1875 }; |
|
1876 |
|
1877 Popcorn.xhr = function( options ) { |
|
1878 |
|
1879 options.dataType = options.dataType && options.dataType.toLowerCase() || null; |
|
1880 |
|
1881 if ( options.dataType && |
|
1882 ( options.dataType === "jsonp" || options.dataType === "script" ) ) { |
|
1883 |
|
1884 Popcorn.xhr.getJSONP( |
|
1885 options.url, |
|
1886 options.success, |
|
1887 options.dataType === "script" |
|
1888 ); |
|
1889 return; |
|
1890 } |
|
1891 |
|
1892 var settings = Popcorn.extend( {}, setup, options ); |
|
1893 |
|
1894 // Create new XMLHttpRequest object |
|
1895 settings.ajax = settings.xhr(); |
|
1896 |
|
1897 if ( settings.ajax ) { |
|
1898 |
|
1899 if ( settings.type === "GET" && settings.data ) { |
|
1900 |
|
1901 // append query string |
|
1902 settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data; |
|
1903 |
|
1904 // Garbage collect and reset settings.data |
|
1905 settings.data = null; |
|
1906 } |
|
1907 |
|
1908 |
|
1909 settings.ajax.open( settings.type, settings.url, settings.async ); |
|
1910 settings.ajax.send( settings.data || null ); |
|
1911 |
|
1912 return Popcorn.xhr.httpData( settings ); |
|
1913 } |
|
1914 }; |
|
1915 |
|
1916 |
|
1917 Popcorn.xhr.httpData = function( settings ) { |
|
1918 |
|
1919 var data, json = null, |
|
1920 parser, xml = null; |
|
1921 |
|
1922 settings.ajax.onreadystatechange = function() { |
|
1923 |
|
1924 if ( settings.ajax.readyState === 4 ) { |
|
1925 |
|
1926 try { |
|
1927 json = JSON.parse( settings.ajax.responseText ); |
|
1928 } catch( e ) { |
|
1929 //suppress |
|
1930 } |
|
1931 |
|
1932 data = { |
|
1933 xml: settings.ajax.responseXML, |
|
1934 text: settings.ajax.responseText, |
|
1935 json: json |
|
1936 }; |
|
1937 |
|
1938 // Normalize: data.xml is non-null in IE9 regardless of if response is valid xml |
|
1939 if ( !data.xml || !data.xml.documentElement ) { |
|
1940 data.xml = null; |
|
1941 |
|
1942 try { |
|
1943 parser = new DOMParser(); |
|
1944 xml = parser.parseFromString( settings.ajax.responseText, "text/xml" ); |
|
1945 |
|
1946 if ( !xml.getElementsByTagName( "parsererror" ).length ) { |
|
1947 data.xml = xml; |
|
1948 } |
|
1949 } catch ( e ) { |
|
1950 // data.xml remains null |
|
1951 } |
|
1952 } |
|
1953 |
|
1954 // If a dataType was specified, return that type of data |
|
1955 if ( settings.dataType ) { |
|
1956 data = data[ settings.dataType ]; |
|
1957 } |
|
1958 |
|
1959 |
|
1960 settings.success.call( settings.ajax, data ); |
|
1961 |
|
1962 } |
|
1963 }; |
|
1964 return data; |
|
1965 }; |
|
1966 |
|
1967 Popcorn.xhr.getJSONP = function( url, success, isScript ) { |
|
1968 |
|
1969 var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement, |
|
1970 script = document.createElement( "script" ), |
|
1971 paramStr = url.split( "?" )[ 1 ], |
|
1972 isFired = false, |
|
1973 params = [], |
|
1974 callback, parts, callparam; |
|
1975 |
|
1976 if ( paramStr && !isScript ) { |
|
1977 params = paramStr.split( "&" ); |
|
1978 } |
|
1979 |
|
1980 if ( params.length ) { |
|
1981 parts = params[ params.length - 1 ].split( "=" ); |
|
1982 } |
|
1983 |
|
1984 callback = params.length ? ( parts[ 1 ] ? parts[ 1 ] : parts[ 0 ] ) : "jsonp"; |
|
1985 |
|
1986 if ( !paramStr && !isScript ) { |
|
1987 url += "?callback=" + callback; |
|
1988 } |
|
1989 |
|
1990 if ( callback && !isScript ) { |
|
1991 |
|
1992 // If a callback name already exists |
|
1993 if ( !!window[ callback ] ) { |
|
1994 // Create a new unique callback name |
|
1995 callback = Popcorn.guid( callback ); |
|
1996 } |
|
1997 |
|
1998 // Define the JSONP success callback globally |
|
1999 window[ callback ] = function( data ) { |
|
2000 // Fire success callbacks |
|
2001 success && success( data ); |
|
2002 isFired = true; |
|
2003 }; |
|
2004 |
|
2005 // Replace callback param and callback name |
|
2006 url = url.replace( parts.join( "=" ), parts[ 0 ] + "=" + callback ); |
|
2007 } |
|
2008 |
|
2009 script.onload = function() { |
|
2010 |
|
2011 // Handling remote script loading callbacks |
|
2012 if ( isScript ) { |
|
2013 // getScript |
|
2014 success && success(); |
|
2015 } |
|
2016 |
|
2017 // Executing for JSONP requests |
|
2018 if ( isFired ) { |
|
2019 // Garbage collect the callback |
|
2020 delete window[ callback ]; |
|
2021 } |
|
2022 // Garbage collect the script resource |
|
2023 head.removeChild( script ); |
|
2024 }; |
|
2025 |
|
2026 script.src = url; |
|
2027 |
|
2028 head.insertBefore( script, head.firstChild ); |
|
2029 |
|
2030 return; |
|
2031 }; |
|
2032 |
|
2033 Popcorn.getJSONP = Popcorn.xhr.getJSONP; |
|
2034 |
|
2035 Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) { |
|
2036 |
|
2037 return Popcorn.xhr.getJSONP( url, success, true ); |
|
2038 }; |
|
2039 |
|
2040 Popcorn.util = { |
|
2041 // Simple function to parse a timestamp into seconds |
|
2042 // Acceptable formats are: |
|
2043 // HH:MM:SS.MMM |
|
2044 // HH:MM:SS;FF |
|
2045 // Hours and minutes are optional. They default to 0 |
|
2046 toSeconds: function( timeStr, framerate ) { |
|
2047 // Hours and minutes are optional |
|
2048 // Seconds must be specified |
|
2049 // Seconds can be followed by milliseconds OR by the frame information |
|
2050 var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/, |
|
2051 errorMessage = "Invalid time format", |
|
2052 digitPairs, lastIndex, lastPair, firstPair, |
|
2053 frameInfo, frameTime; |
|
2054 |
|
2055 if ( typeof timeStr === "number" ) { |
|
2056 return timeStr; |
|
2057 } |
|
2058 |
|
2059 if ( typeof timeStr === "string" && |
|
2060 !validTimeFormat.test( timeStr ) ) { |
|
2061 Popcorn.error( errorMessage ); |
|
2062 } |
|
2063 |
|
2064 digitPairs = timeStr.split( ":" ); |
|
2065 lastIndex = digitPairs.length - 1; |
|
2066 lastPair = digitPairs[ lastIndex ]; |
|
2067 |
|
2068 // Fix last element: |
|
2069 if ( lastPair.indexOf( ";" ) > -1 ) { |
|
2070 |
|
2071 frameInfo = lastPair.split( ";" ); |
|
2072 frameTime = 0; |
|
2073 |
|
2074 if ( framerate && ( typeof framerate === "number" ) ) { |
|
2075 frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate; |
|
2076 } |
|
2077 |
|
2078 digitPairs[ lastIndex ] = parseInt( frameInfo[ 0 ], 10 ) + frameTime; |
|
2079 } |
|
2080 |
|
2081 firstPair = digitPairs[ 0 ]; |
|
2082 |
|
2083 return { |
|
2084 |
|
2085 1: parseFloat( firstPair, 10 ), |
|
2086 |
|
2087 2: ( parseInt( firstPair, 10 ) * 60 ) + |
|
2088 parseFloat( digitPairs[ 1 ], 10 ), |
|
2089 |
|
2090 3: ( parseInt( firstPair, 10 ) * 3600 ) + |
|
2091 ( parseInt( digitPairs[ 1 ], 10 ) * 60 ) + |
|
2092 parseFloat( digitPairs[ 2 ], 10 ) |
|
2093 |
|
2094 }[ digitPairs.length || 1 ]; |
|
2095 } |
|
2096 }; |
|
2097 |
|
2098 |
|
2099 // Initialize locale data |
|
2100 // Based on http://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes |
|
2101 function initLocale( arg ) { |
|
2102 |
|
2103 var locale = typeof arg === "string" ? arg : [ arg.language, arg.region ].join( "-" ), |
|
2104 parts = locale.split( "-" ); |
|
2105 |
|
2106 // Setup locale data table |
|
2107 return { |
|
2108 iso6391: locale, |
|
2109 language: parts[ 0 ] || "", |
|
2110 region: parts[ 1 ] || "" |
|
2111 }; |
|
2112 } |
|
2113 |
|
2114 // Declare locale data table |
|
2115 var localeData = initLocale( global.navigator.userLanguage || global.navigator.language ); |
|
2116 |
|
2117 Popcorn.locale = { |
|
2118 |
|
2119 // Popcorn.locale.get() |
|
2120 // returns reference to privately |
|
2121 // defined localeData |
|
2122 get: function() { |
|
2123 return localeData; |
|
2124 }, |
|
2125 |
|
2126 // Popcorn.locale.set( string|object ); |
|
2127 set: function( arg ) { |
|
2128 |
|
2129 localeData = initLocale( arg ); |
|
2130 |
|
2131 Popcorn.locale.broadcast(); |
|
2132 |
|
2133 return localeData; |
|
2134 }, |
|
2135 |
|
2136 // Popcorn.locale.broadcast( type ) |
|
2137 // Sends events to all popcorn media instances that are |
|
2138 // listening for locale events |
|
2139 broadcast: function( type ) { |
|
2140 |
|
2141 var instances = Popcorn.instances, |
|
2142 length = instances.length, |
|
2143 idx = 0, |
|
2144 instance; |
|
2145 |
|
2146 type = type || "locale:changed"; |
|
2147 |
|
2148 // Iterate all current instances |
|
2149 for ( ; idx < length; idx++ ) { |
|
2150 instance = instances[ idx ]; |
|
2151 |
|
2152 // For those instances with locale event listeners, |
|
2153 // trigger a locale change event |
|
2154 if ( type in instance.data.events ) { |
|
2155 instance.trigger( type ); |
|
2156 } |
|
2157 } |
|
2158 } |
|
2159 }; |
|
2160 |
|
2161 // alias for exec function |
|
2162 Popcorn.p.cue = Popcorn.p.exec; |
|
2163 |
|
2164 function getItems() { |
|
2165 |
|
2166 var item, |
|
2167 list = []; |
|
2168 |
|
2169 if ( Object.keys ) { |
|
2170 list = Object.keys( Popcorn.p ); |
|
2171 } else { |
|
2172 |
|
2173 for ( item in Popcorn.p ) { |
|
2174 if ( hasOwn.call( Popcorn.p, item ) ) { |
|
2175 list.push( item ); |
|
2176 } |
|
2177 } |
|
2178 } |
|
2179 |
|
2180 return list.join( "," ).toLowerCase().split( ","); |
|
2181 } |
|
2182 |
|
2183 // Protected API methods |
|
2184 Popcorn.protect = { |
|
2185 natives: getItems() |
|
2186 }; |
|
2187 |
|
2188 // Exposes Popcorn to global context |
|
2189 global.Popcorn = Popcorn; |
|
2190 |
|
2191 })(window, window.document); |
|