PageSourceSearch

https://tps.co.il/wp-content/plugins/wp-phraseanet/libs/flowplayer/flowplayer.js?ver=1.0.7

js tps.co.il collected 2026-10-02 05:06:38 UTC 71,779 bytes, 2,478 lines download raw bytes

1/*!
2
3   Flowplayer v5.4.3 (Wednesday, 19. June 2013 11:34PM) | flowplayer.org/license
4
5*/
6!function($) { 
7
8/*
9   jQuery.browser for 1.9+
10
11   We all love feature detection but that's sometimes not enough.
12
13   @author Tero Piirainen
14*/
15!function($) {
16
17   if (!$.browser) {
18
19      var b = $.browser = {},
20         ua = navigator.userAgent.toLowerCase(),
21         match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
22         /(safari)[ \/]([\w.]+)/.exec(ua) ||
23         /(webkit)[ \/]([\w.]+)/.exec(ua) ||
24         /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
25         /(msie) ([\w.]+)/.exec(ua) ||
26         ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [];
27
28      if (match[1]) {
29         b[match[1]] = true;
30         b.version = match[2] || "0";
31      }
32
33   }
34
35}(jQuery);
36// auto-install (any video tag with parent .flowplayer)
37$(function() {
38   if (typeof $.fn.flowplayer == 'function') {
39      $("video").parent(".flowplayer").flowplayer();
40   }
41});
42
43var instances = [],
44   extensions = [],
45   UA = window.navigator.userAgent;
46
47
48/* flowplayer()  */
49window.flowplayer = function(fn) {
50   return $.isFunction(fn) ? extensions.push(fn) :
51      typeof fn == 'number' || fn === undefined ? instances[fn || 0] :
52      $(fn).data("flowplayer");
53};
54
55$(window).on('beforeunload', function() {
56   $.each(instances, function(i, api) {
57      if (api.conf.splash) {
58         api.unload();
59      } else {
60         api.bind("error", function () {
61            $(".flowplayer.is-error .fp-message").remove();
62         });
63      }
64   });
65});
66
67$.extend(flowplayer, {
68
69   version: '5.4.3',
70
71   engine: {},
72
73   conf: {},
74
75   support: {},
76
77   defaults: {
78
79      debug: false,
80
81      // true = forced playback
82      disabled: false,
83
84      // first engine to try
85      engine: 'html5',
86
87      fullscreen: window == window.top,
88
89      // keyboard shortcuts
90      keyboard: true,
91
92      // default aspect ratio
93      ratio: 9 / 16,
94
95      adaptiveRatio: false,
96
97      // scale flash object to video's aspect ratio in normal mode?
98      flashfit: false,
99
100      rtmp: 0,
101
102      splash: false,
103
104      swf: "//releases.flowplayer.org/5.4.3/flowplayer.swf",
105
106      speeds: [0.25, 0.5, 1, 1.5, 2],
107
108      tooltip: true,
109
110      // initial volume level
111      volume: typeof localStorage != "object" ? 1 : localStorage.muted == "true" ? 0 : !isNaN(localStorage.volume) ? localStorage.volume || 1 : 1,
112
113      // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes
114      errors: [
115
116         // video exceptions
117         '',
118         'Video loading aborted',
119         'Network error',
120         'Video not properly encoded',
121         'Video file not found',
122
123         // player exceptions
124         'Unsupported video',
125         'Skin not found',
126         'SWF file not found',
127         'Subtitles not found',
128         'Invalid RTMP URL',
129         'Unsupported video format. Try installing Adobe Flash.'
130      ],
131      errorUrls: ['','','','','','','','','','',
132         'http://get.adobe.com/flashplayer/'
133      ],
134      playlist: []
135
136   }
137
138});
139
140// keep track of players
141var playerCount = 1;
142
143// jQuery plugin
144$.fn.flowplayer = function(opts, callback) {
145
146   if (typeof opts == 'string') opts = { swf: opts }
147   if ($.isFunction(opts)) { callback = opts; opts = {} }
148
149   return !opts && this.data("flowplayer") || this.each(function() {
150
151      // private variables
152      var root = $(this).addClass("is-loading"),
153         conf = $.extend({}, flowplayer.defaults, flowplayer.conf, opts, root.data()),
154         videoTag = $("video", root).addClass("fp-engine").removeAttr("controls"),
155         urlResolver = videoTag.length ? new URLResolver(videoTag) : null,
156         storage = {},
157         lastSeekPosition,
158         engine;
159
160      if (conf.playlist.length) { // Create initial video tag if called without
161         var preload = videoTag.attr('preload'), placeHolder;
162         if (videoTag.length) videoTag.replaceWith(placeHolder = $('<p />'));
163         videoTag = $('<video />').addClass('fp-engine');
164         placeHolder ? placeHolder.replaceWith(videoTag) : root.prepe
164nd(videoTag);
165         videoTag.attr('preload', preload);
166         if (typeof conf.playlist[0] === 'string') videoTag.attr('src', conf.playlist[0]);
167         else {
168            $.each(conf.playlist[0], function(i, plObj) {
169               for (var type in plObj) {
170                  if (plObj.hasOwnProperty(type)) {
171                     videoTag.append($('<source />').attr({type: 'video/' + type, src: plObj[type]}));
172                  }
173               }
174            });
175         }
176         urlResolver = new URLResolver(videoTag);
177
178      }
179
180      //stop old instance
181      var oldApi = root.data('flowplayer');
182      if (oldApi) oldApi.unload();
183
184      root.data('fp-player_id', root.data('fp-player_id') || playerCount++);
185
186      try {
187         storage = window.localStorage || storage;
188      } catch(e) {}
189
190      var isRTL = (this.currentStyle && this.currentStyle['direction'] === 'rtl')
191         || (window.getComputedStyle && window.getComputedStyle(this, null).getPropertyValue('direction') === 'rtl');
192
193      if (isRTL) root.addClass('is-rtl');
194
195      /*** API ***/
196      var api = oldApi || {
197
198         // properties
199         conf: conf,
200         currentSpeed: 1,
201         volumeLevel: typeof conf.volume === "undefined" ? storage.volume * 1 : conf.volume,
202         video: {},
203
204         // states
205         disabled: false,
206         finished: false,
207         loading: false,
208         muted: storage.muted == "true" || conf.muted,
209         paused: false,
210         playing: false,
211         ready: false,
212         splash: false,
213         rtl: isRTL,
214
215         // methods
216         load: function(video, callback) {
217
218            if (api.error || api.loading || api.disabled) return;
219
220            // resolve URL
221            video = urlResolver.resolve(video);
222            $.extend(video, engine.pick(video.sources));
223
224            if (video.src) {
225               var e = $.Event("load");
226               root.trigger(e, [api, video, engine]);
227
228               if (!e.isDefaultPrevented()) {
229                  engine.load(video);
230
231                  // callback
232                  if ($.isFunction(video)) callback = video;
233                  if (callback) root.one("ready", callback);
234               } else {
235                  api.loading = false;
236               }
237            }
238
239            return api;
240         },
241
242         pause: function(fn) {
243            if (api.ready && !api.seeking && !api.disabled && !api.loading) {
244               engine.pause();
245               api.one("pause", fn);
246            }
247            return api;
248         },
249
250         resume: function() {
251
252            if (api.ready && api.paused && !api.disabled) {
253               engine.resume();
254
255               // Firefox (+others?) does not fire "resume" after finish
256               if (api.finished) {
257                  api.trigger("resume");
258                  api.finished = false;
259               }
260            }
261
262            return api;
263         },
264
265         toggle: function() {
266            return api.ready ? api.paused ? api.resume() : api.pause() : api.load();
267         },
268
269         /*
270            seek(1.4)   -> 1.4s time
271            seek(true)  -> 10% forward
272            seek(false) -> 10% backward
273         */
274         seek: function(time, callback) {
275            if (api.ready) {
276
277               if (typeof time == "boolean") {
278                  var delta = api.video.duration * 0.1;
279                  time = api.video.time + (time ? delta : -delta);
280               }
281               time = lastSeekPosition = Math.min(Math.max(time, 0), api.video.duration).toFixed(1);
282               var ev = $.Event('beforeseek');
283               root.trigger(ev, [api, time]);
284               if (!ev.isDefaultPrevented()) {
285                  engine.seek(time);
286                  if ($.isFunction(callback)) root.one("seek", callback);
287               } else {
288                  api.seeking = false;
289                  root.toggleClass("is-seeking", api.seeking); // remove loading indicator
290               }
291            }
292            return api;
293         },
294
295         /*
296            seekTo(1) -> 10%
297            seekTo(2) -> 20%
298            seekTo(3) -> 30%
299            ...
300            seekTo()  -> last position
301         */
302         seekTo: function(position, fn) {
303            var time = position === undefined ? lastSeekPosition : api.video.duration * 0.1 * position;
304            return api.seek(time, fn);
305         },
306
307         mute: function(flag) {
308            if (flag === undefined) flag = !api.muted;
309            storage.muted = api.muted = flag;
310            storage.volume = !isNaN(storage.volume) ? storage.volume : conf.volume; // make sure storage has volume
311            api.volume(flag ? 0 : storage.volume, true);
312            api.trigger("mute", flag);
313            return api;
314         },
315
316         volume: function(level, skipStore) {
317            if (api.ready) {
318              level = Math.min(Math.max(level, 0), 1);
319              if (!skipStore) storage.volume = level;
320              engine.volume(level);
321            }
322
323            return api;
324         },
325
326         speed: function(val, callback) {
327
328            if (api.ready) {
329
330               // increase / decrease
331               if (typeof val == "boolean") {
332                  val = conf.speeds[$.inArray(api.currentSpeed, conf.speeds) + (val ? 1 : -1)] || api.currentSpeed;
333               }
334
335               engine.speed(val);
336               if (callback) root.one("speed", callback);
337            }
338
339            return api;
340         },
341
342
343         stop: function() {
344            if (api.ready) {
345               api.pause();
346               api.seek(0, function() {
347                  root.trigger("stop");
348               });
349            }
350            return api;
351         },
352
353         unload: function() {
354            if (!root.hasClass("is-embedding")) {
355
356               if (conf.splash) {
357                  api.trigger("unload");
358                  engine.unload();
359               } else {
360                  api.stop();
361               }
362            }
363            return api;
364         },
365
366         disable: function(flag) {
367            if (flag === undefined) flag = !api.disabled;
368
369            if (flag != api.disabled) {
370               api.disabled = flag;
371               api.trigger("disable", flag);
372            }
373            return api;
374         }
375
376      };
377
378      api.conf = $.extend(api.conf, conf);
379
380
381      /* event binding / unbinding */
382      $.each(['bind', 'one', 'unbind'], function(i, key) {
383         api[key] = function(type, fn) {
384            root[key](type, fn);
385            return api;
386         };
387      });
388
389      api.trigger = function(event, arg) {
390         root.trigger(event, [api, arg]);
391         return api;
392      };
393
394
395      /*** Behaviour ***/
396      if (!root.data('flowplayer')) { // Only bind once
397         root.bind("boot", function() {
398
399            // conf
400            $.each(['autoplay', 'loop', 'preload', 'poster'], function(i, key) {
401               var val = videoTag.attr(key);
402               if (val !== undefined) conf[key] = val ? val : true;
403            });
404
405            // splash
406            if (conf.splash || root.hasClass("is-splash") || !flowplayer.support.firstframe) {
407               api.forcedSplash = !conf.splash && !root.hasClass("is-splash");
408               api.splash = conf.splash = conf.autoplay = true;
409               root.addClass("is-splash");
410               videoTag.attr("preload", "none");
411            }
412
413            // extensions
414            $.each(extensions, function(i) {
415               this(api, root);
416            });
417
418            // 1. use the configured engine
419            engine = flowplayer.engine[conf.engine];
420            if (engine) engine = engine(api, root);
421
422            if (engine.pick(urlResolver.initialSources)) {
423               api.engine = conf.engine;
424
425            // 2. failed -> try another
426            } else {
427               $.each(flowplayer.engine, function(name, impl) {
428                  if (name != conf.engine) {
429                     engine = this(api, root);
430                     if (engine.pick(urlResolver.initialSources)) api.engine = name;
431                     return false;
432                  }
433               });
434            }
435
436            // instances
437            instances.push(api);
438
439            // no engine
440            if (!api.engine) return api.trigger("error", { code: flowplayer.support.flashVideo ? 5 : 10 });
441
442            // start
443            conf.splash ? api.unload() : api.load();
444
445            // disabled
446            if (conf.disabled) api.disable();
447
448            //initial volumelevel
449            engine.volume(api.volumeLevel);
450
451            // initial callback
452            root.one("ready", callback);
453
454
455         }).bind("load", function(e, api, video) {
456
457            // unload others
458            if (conf.splash) {
459               $(".flowplayer").filter(".is-ready, .is-loading").not(root).each(function() {
460                  var api = $(this).data("flowplayer");
461                  if (api.conf.splash) api.unload();
462               });
463            }
464
465            // loading
466            root.addClass("is-loading");
467            api.loading = true;
468
469
470         }).bind("ready", function(e, api, video) {
471            video.time = 0;
472            api.video = video;
473
474            function notLoading() {
475               root.removeClass("is-loading");
476               api.loading = false;
477            }
478
479            if (conf.splash) root.one("progress", notLoading);
480            else notLoading();
481
482            // saved state
483            if (api.muted) api.mute(true);
484            else api.volume(api.volumeLevel);
485
486
487         }).bind("unload", function(e) {
488            if (conf.splash) videoTag.remove();
489            root.removeClass("is-loading");
490            api.loading = false;
491
492
493         }).bind("ready unload", function(e) {
494            var is_ready = e.type == "ready";
495            root.toggleClass("is-splash", !is_ready).toggleClass("is-ready", is_ready);
496            api.ready = is_ready;
497            api.splash = !is_ready;
498
499
500         }).bind("progress", function(e, api, time) {
501            api.video.time = time;
502
503
504         }).bind("speed", function(e, api, val) {
505            api.currentSpeed = val;
506
507         }).bind("volume", function(e, api, level) {
508            api.volumeLevel = Math.round(level * 100) / 100;
509            if (!api.muted) storage.volume = level;
510            else if (level) api.mute(false);
511
512
513         }).bind("beforeseek seek", function(e) {
514            api.seeking = e.type == "beforeseek";
515            root.toggleClass("is-seeking", api.seeking);
516
517         }).bind("ready pause resume unload finish stop", function(e, _api, video) {
518
519            // PAUSED: pause / finish
520            api.paused = /pause|finish|unload|stop/.test(e.type);
521
522            // SHAKY HACK: first-frame / preload=none
523            if (e.type == "ready") {
524               api.paused = conf.preload == 'none';
525               if (video) {
526                  api.paused = !video.duration || !conf.autoplay && (conf.preload != 'none');
527               }
528            }
529
530            // the opposite
531            api.playing = !api.paused;
532
533            // CSS classes
534            root.toggleClass("is-paused", api.paused).toggleClass("is-playing", api.playing);
535
536            // sanity check
537            if (!api.load.ed) api.pause();
538
539         }).bind("finish", function(e) {
540            api.finished = true;
541
542         }).bind("error", function() {
543            videoTag.remove();
544         });
545      }
546
547      // boot
548      root.trigger("boot", [api, root]).data("flowplayer", api);
549
550   });
551
552};
553
554!function() {
555
556   var parseIpadVersion = function(UA) {
557      var e = /Version\/(\d\.\d)/.exec(UA);
558      if (e && e.length > 1) {
559         return parseFloat(e[1], 10);
560      }
561      return 0;
562   };
563
564   var s = flowplayer.support,
565      browser = $.browser,
566      video = $("<video loop autoplay preload/>")[0],
567      IS_IE = browser.msie,
568      UA = navigator.userAgent,
569      IS_IPAD = /iPad|MeeGo/.test(UA) && !/CriOS/.test(UA),
570      IS_IPAD_CHROME = /iPad/.test(UA) && /CriOS/.test(UA),
571      IS_IPHONE = /iP(hone|od)/i.test(UA) && !/iPad/.test(UA),
572      IS_ANDROID = /Android/.test(UA) && !/Firefox/.test(UA),
573      IS_ANDROID_FIREFOX = /Android/.test(UA) && /Firefox/.test(UA),
574      IS_SILK = /Silk/.test(UA),
575      IS_WP = /IEMobile/.test(UA),
576      IPAD_VER = IS_IPAD ? parseIpadVersion(UA) : 0,
577      ANDROID_VER = IS_ANDROID ? parseFloat(/Android\ (\d\.\d)/.exec(UA)[1], 10) : 0;
578   $.extend(s, {
579      subtitles: !!video.addTextTrack,
580      fullscreen: !IS_ANDROID &&
581         (typeof document.webkitCancelFullScreen == 'function' && !/Mac OS X 10_5.+Version\/5\.0\.\d S
581afari/.test(UA) ||
582            document.mozFullScreenEnabled ||
583            typeof document.exitFullscreen == 'function'),
584      inlineBlock: !(IS_IE && browser.version < 8),
585      touch: ('ontouchstart' in window),
586      dataload: !IS_IPAD && !IS_IPHONE && !IS_WP,
587      zeropreload: !IS_IE && !IS_ANDROID, // IE supports only preload=metadata
588      volume: !IS_IPAD && !IS_ANDROID && !IS_IPHONE && !IS_SILK && !IS_IPAD_CHROME,
589      cachedVideoTag: !IS_IPAD && !IS_IPHONE && !IS_IPAD_CHROME && !IS_WP,
590      firstframe: !IS_IPHONE && !IS_IPAD && !IS_ANDROID && !IS_SILK && !IS_IPAD_CHROME && !IS_WP && !IS_ANDROID_FIREFOX,
591      inlineVideo: !IS_IPHONE && !IS_SILK && !IS_WP && (!IS_ANDROID || ANDROID_VER >= 3),
592      hlsDuration: !browser.safari || IS_IPAD || IS_IPHONE || IS_IPAD_CHROME,
593      seekable: !IS_IPAD && !IS_IPAD_CHROME
594   });
595
596   // flashVideo
597   try {
598      var ver = IS_IE ? new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable('$version') :
599         navigator.plugins["Shockwave Flash"].description;
600
601      ver = ver.split(/\D+/);
602      if (ver.length && !ver[0]) ver = ver.slice(1);
603
604      s.flashVideo = ver[0] > 9 || ver[0] == 9 && ver[3] >= 115;
605
606   } catch (ignored) {}
607   try {
608      s.video = !!video.canPlayType;
609      s.video && video.canPlayType('video/mp4');
610   } catch (e) {
611      s.video = false;
612   }
613
614   // animation
615   s.animation = (function() {
616      var vendors = ['','Webkit','Moz','O','ms','Khtml'], el = $("<p/>")[0];
617
618      for (var i = 0; i < vendors.length; i++) {
619         if (el.style[vendors[i] + 'AnimationName'] !== 'undefined') return true;
620      }
621   })();
622
623
624
625}();
626
627
628/* The most minimal Flash embedding */
629
630// movie required in opts
631function embed(swf, flashvars) {
632
633   var id = "obj" + ("" + Math.random()).slice(2, 15),
634      tag = '<object class="fp-engine" id="' + id+ '" name="' + id + '" ';
635
636   tag += $.browser.msie ? 'classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">' :
637      ' data="' + swf  + '" type="application/x-shockwave-flash">';
638
639   var opts = {
640      width: "100%",
641      height: "100%",
642      allowscriptaccess: "always",
643      wmode: "transparent",
644      quality: "high",
645      flashvars: "",
646
647      // https://github.com/flowplayer/flowplayer/issues/13#issuecomment-9369919
648      movie: swf + ($.browser.msie ? "?" + id : ""),
649      name: id
650   };
651
652   // flashvars
653   $.each(flashvars, function(key, value) {
654      opts.flashvars += key + "=" + value + "&";
655   });
656
657   // parameters
658   $.each(opts, function(key, value) {
659      tag += '<param name="' + key + '" value="'+ value +'"/>';
660   });
661
662   tag += "</object>";
663
664   return $(tag);
665}
666
667
668// Flash is buggy allover
669if (window.attachEvent) {
670   window.attachEvent("onbeforeunload", function() {
671      __flash_savedUnloadHandler = __flash_unloadHandler = function() {};
672   });
673}
674
675
676flowplayer.engine.flash = function(player, root) {
677
678   var conf = player.conf,
679      video = player.video,
680      callbackId,
681      objectTag,
682      api;
683
684   var engine = {
685
686      pick: function(sources) {
687
688         if (flowplayer.support.flashVideo) {
689
690            // always pick video/flash first
691            var flash = $.grep(sources, function(source) { return source.type == 'flash'; })[0];
692            if (flash) return flash;
693
694            for (var i = 0, source; i < sources.length; i++) {
695               source = sources[i];
696               if (/mp4|flv/.test(source.type)) return source;
697            }
698         }
699      },
700
701      load: function(video) {
702
703         function escapeURL(url) {
704            return url.replace(/&amp;/g, '%26').replace(/&/g, '%26').replace(/=/g, '%3D');
705         }
706
707         var html5Tag = $("video", root),
708            url = escapeURL(video.src);
709            is_absolute = /^https?:/.test(url);
710
711         // html5 tag not needed (pause needed for firefox)
712         if (html5Tag.length > 0 && flowplayer.support.video) html5Tag[0].pause();
713         html5Tag.remove();
714
715         // convert to absolute
716         if (!is_absolute && !conf.rtmp) url = $("<img/>").attr("src", url)[0].src;
717
718         if (api) {
719            api.__play(url);
720
721         } else {
722
723            callbackId = "fp" + ("" + Math.random()).slice(3, 15);
724
725            var opts = {
726               hostname: conf.embedded ? conf.hostname : location.hostname,
727               url: url,
728               callback: "jQuery."+ callbackId
729            };
730            if (root.data("origin")) {
731               opts.origin = root.data("origin");
732            }
733
734            if (is_absolute) delete conf.rtmp;
735
736            // optional conf
737            $.each(['key', 'autoplay', 'preload', 'rtmp', 'loop', 'debug', 'preload', 'splash'], function(i, key) {
738               if (conf[key]) opts[key] = conf[key];
739            });
740
741            // issue #376
742            if (opts.rtmp) {
743               opts.rtmp = escapeURL(opts.rtmp);
744            }
745
746            objectTag = embed(conf.swf, opts);
747
748            objectTag.prependTo(root);
749
750            api = objectTag[0];
751
752            // throw error if no loading occurs
753            setTimeout(function() {
754               try {
755                  if (!api.PercentLoaded()) {
756                     return root.trigger("error", [player, { code: 7, url: conf.swf }]);
757                  }
758               } catch (e) {}
759            }, 5000);
760
761            // listen
762            $[callbackId] = function(type, arg) {
763
764               if (conf.debug && type != "status") console.log("--", type, arg);
765
766               var event = $.Event(type);
767
768               switch (type) {
769
770                  // RTMP sends a lot of finish events in vain
771                  // case "finish": if (conf.rtmp) return;
772                  case "ready": arg = $.extend(video, arg); break;
773                  case "click": event.flash = true; break;
774                  case "keydown": event.which = arg; break;
775                  case "seek": video.time = arg; break;
776                  case "buffered": video.buffered = true; break;
777
778                  case "status":
779                     player.trigger("progress", arg.time);
780
781                     if (arg.buffer <= video.bytes && !video.buffered) {
782                        video.buffer = arg.buffer / video.bytes * video.duration;
783                        player.trigger("buffer", video.buffer);
784
785                     } else if (video.buffered) player.trigger("buffered");
786
787                     break;
788
789               }
790
791               // add some delay to that player is truly ready after an event
792               setTimeout(function() { player.trigger(event, arg); }, 1)
793
794            };
795
796         }
797
798      },
799
800      // not supported yet
801      speed: $.noop,
802
803
804      unload: function() {
805         api && api.__unload && api.__unload();
806         delete $[callbackId];
807         $("object", root).remove();
808         api = 0;
809      }
810
811   };
812
813   $.each("pause,resume,seek,volume".split(","), function(i, name) {
814
815      engine[name] = function(arg) {
816
817         if (player.ready) {
818
819            if (name == 'seek' && player.video.time && !player.paused) {
820               player.trigger("beforeseek");
821            }
822
823            if (arg === undefined) {
824               api["__" + name]();
825
826            } else {
827               api["__" + name](arg);
828            }
829
830         }
831      };
832
833   });
834
835   var win = $(window);
836
837   // handle Flash object aspect ratio
838   player.bind("ready fullscreen fullscreen-exit", function(e) {
839      var origH = root.height(),
840         origW = root.width();
841      if (player.conf.flashfit || /full/.test(e.type)) {
842
843         var fs = player.isFullscreen,
844            truefs = fs && FS_SUPPORT,
845            ie7 = !flowplayer.support.inlineBlock,
846            screenW = fs ? (truefs ? screen.availWidth : win.width()) : origW,
847            screenH = fs ? (truefs ? screen.availHeight : win.height()) : origH,
848
849            // default values for fullscreen-exit without flashfit
850            hmargin = truefs ? screen.width - screen.availWidth : 0,
851            vmargin = truefs ? screen.height - screen.availHeight : 0,
852            objwidth = ie7 ? origW : '',
853            objheight = ie7 ? origH : '',
854
855            aspectratio, dataratio;
856
857         if (player.conf.flashfit || e.type === "fullscreen") {
858            aspectratio = player.video.width / player.video.height,
859            dataratio = player.video.height / player.video.width,
860            objheight = Math.max(dataratio * screenW),
861            objwidth = Math.max(aspectratio * screenH);
862            objheight = objheight > screenH ? objwidth * dataratio : objheight;
863            objheight = Math.min(Math.round(objheight), screenH);
864            objwidth = objwidth > screenW ? objheight * aspectratio : objwidth;
865            objwidth = Math.min(Math.round(objwidth), screenW);
866            vmargin = Math.max(Math.round((screenH + vmargin - objheight) / 2), 0);
867            hmargin = Math.max(Math.round((screenW + hmargin - objwidth) / 2), 0);
868         }
869
870         $("object", root).css({
871            width: objwidth,
872            height: objheight,
873            marginTop: vmargin,
874            marginLeft: hmargin
875         });
876      }
877   });
878
879   return engine;
880
881};
882
883
884var VIDEO = $('<video/>')[0];
885
886// HTML5 --> Flowplayer event
887var EVENTS = {
888
889   // fired
890   ended: 'finish',
891   pause: 'pause',
892   play: 'resume',
893   progress: 'buffer',
894   timeupdate: 'progress',
895   volumechange: 'volume',
896   ratechange: 'speed',
897   //seeking: 'beforeseek',
898   seeked: 'seek',
899   // abort: 'resume',
900
901   // not fired
902   loadeddata: 'ready',
903   // loadedmetadata: 0,
904   // canplay: 0,
905
906   // error events
907   // load: 0,
908   // emptied: 0,
909   // empty: 0,
910   error: 'error',
911   dataunavailable: 'error'
912
913};
914
915function round(val) {
916   return Math.round(val * 100) / 100;
917}
918
919function getType(type) {
920   return /mpegurl/i.test(type) ? "application/x-mpegurl" : "video/" + type;
921}
922
923function canPlay(type) {
924   if (!/^(video|application)/.test(type))
925      type = getType(type);
926   return !!VIDEO.canPlayType(type).replace("no", '');
927}
928
929function findFromSourcesByType(sources, type) {
930   var arr = $.grep(sources, function(s) {
931      return s.type === type;
932   });
933   return arr.length ? arr[0] : null;
934}
935
936var videoTagCache;
937var createVideoTag = function(video) {
938   if (videoTagCache) {
939      return videoTagCache.attr({type: getType(video.type), src: video.src});
940   }
941   return (videoTagCache = $("<video/>", {
942               src: video.src,
943               type: getType(video.type),
944               'class': 'fp-engine',
945               'autoplay': 'autoplay',
946               preload: 'none',
947               'x-webkit-airplay': 'allow'
948            }));
949}
950
951flowplayer.engine.html5 = function(player, root) {
952
953   var videoTag = $("video", root),
954      support = flowplayer.support,
955      track = $("track", videoTag),
956      conf = player.conf,
957      self,
958      timer,
959      api;
960
961   return self = {
962
963      pick: function(sources) {
964         if (support.video) {
965            if (conf.videoTypePreference) {
966               var mp4source = findFromSourcesByType(sources, conf.videoTypePreference);
967               if (mp4source) return mp4source;
968            }
969            for (var i = 0, source; i < sources.length; i++) {
970               if (canPlay(sources[i].type)) return sources[i];
971            }
972         }
973      },
974
975      load: function(video) {
976
977         if (conf.splash && !api) {
978
979            videoTag = createVideoTag(video).prependTo(root);
980
981            if (!support.inlineVideo) {
982               videoTag.css({
983                  position: 'absolute',
984                  top: '-9999em'
985               });
986            }
987
988            if (track.length) videoTag.append(track.attr("default", ""));
989
990            if (conf.loop) videoTag.attr("loop", "loop");
991
992            api = videoTag[0];
993
994         } else {
995
996            api = videoTag[0];
997            var sources = videoTag.find('source');
998            if (!api.src && sources.length) {
999               api.src = video.src;
1000               sources.remove();
1001            }
1002
1003            // change of clip
1004            if (player.video.src && video.src != player.video.src) {
1005               videoTag.attr("autoplay", "autoplay");
1006               api.src = video.src;
1007
1008            // preload=none or no initial "loadeddata" event
1009            } else if (conf.preload == 'none' || !support.dataload) {
1010
1011               if (support.zeropreload) {
1012                  player.trigger("ready", video).trigger("pause").one("ready", function() {
1013                     root.trigger("resume");
1014                  });
1015
1016               } else {
1017                  player.one("ready", function() {
1018                     root.trigger("pause");
1019                  });
1020               }
1021            }
1022
1023         }
1024
1025         listen(api, $("source", videoTag).add(videoTag), video);
1026
1027         // iPad (+others?) demands load()
1028         if (conf.preload != 'none' || !support.zeropreload || !support.dataload) api.load();
1029         if (conf.splash) api.load();
1030      },
1031
1032      pause: function() {
1033         api.pause();
1034      },
1035
1036      resume: function() {
1037         api.play();
1038      },
1039
1040      speed: function(val) {
1041         api.playbackRate = val;
1042      },
1043
1044      seek: function(time) {
1045         try {
1046            var pausedState = player.paused;
1047            api.currentTime = time;
1048            if (pausedState) api.pause();
1049         } catch (ignored) {}
1050      },
1051
1052      volume: function(level) {
1053         api.volume = level;
1054      },
1055
1056      unload: function() {
1057         $("video.fp-engine", root).remove();
1058         if (!support.cachedVideoTag) videoTagCache = null;
1059         timer = clearInterval(timer);
1060         api = 0;
1061      }
1062
1063   };
1064
1065   function listen(api, sources, video) {
1066      // listen only once
1067
1068      if (api.listeners && api.listeners.hasOwnProperty(root.data('fp-player_id'))) return;
1069      (api.listeners || (api.listeners = {}))[root.data('fp-player_id')] = true;
1070
1071      sources.bind("error", function(e) {
1072         try {
1073            if (e.originalEvent && $(e.originalEvent.originalTarget).is('img')) return e.preventDefault();
1074            if (canPlay($(e.target).attr("type"))) {
1075               player.trigger("error", { code: 4 });
1076            }
1077         } catch (er) {
1078            // Most likely: https://bugzilla.mozilla.org/show_bug.cgi?id=208427
1079         }
1080      });
1081
1082      $.each(EVENTS, function(type, flow) {
1083
1084         api.addEventListener(type, function(e) {
1085
1086            // safari hack for bad URL (10s before fails)
1087            if (flow == "progress" && e.srcElement && e.srcElement.readyState === 0) {
1088               setTimeout(function() {
1089                  if (!player.video.duration) {
1090                     flow = "error";
1091                     player.trigger(flow, { code: 4 });
1092                  }
1093               }, 10000);
1094            }
1095
1096            if (conf.debug && !/progress/.test(flow)) console.log(type, "->", flow, e);
1097
1098            // no events if player not ready
1099            if (!player.ready && !/ready|error/.test(flow) || !flow || !$("video", root).length) { return; }
1100
1101            var event = $.Event(flow), arg;
1102
1103            switch (flow) {
1104
1105               case "ready":
1106
1107                  arg = $.extend(video, {
1108                     duration: api.duration,
1109                     width: api.videoWidth,
1110                     height: api.videoHeight,
1111                     url: api.currentSrc,
1112                     src: api.currentSrc
1113                  });
1114
1115                  try {
1116                     arg.seekable = api.seekable && api.seekable.end(null);
1117
1118                  } catch (ignored) {}
1119
1120                  // buffer
1121                  timer = timer || setInterval(function() {
1122
1123                     try {
1124                        arg.buffer = api.buffered.end(null);
1125
1126                     } catch (ignored) {}
1127
1128                     if (arg.buffer) {
1129                        if (arg.buffer <= arg.duration && !arg.buffered) {
1130                           player.trigger("buffer", e);
1131
1132                        } else if (!arg.buffered) {
1133                           arg.buffered = true;
1134                           player.trigger("buffer", e).trigger("buffered", e);
1135                           clearInterval(timer);
1136                           timer = 0;
1137                        }
1138                     }
1139
1140                  }, 250);
1141
1142                  if (!arg.duration && !support.hlsDuration && type === "loadeddata") {
1143                     var durationChanged = function() {
1144                        arg.duration = api.duration;
1145                        try {
1146                           arg.seekable = api.seekable && api.seekable.end(null);
1147
1148                        } catch (ignored) {}
1149                        player.trigger(event, arg);
1150                        api.removeEventListener('durationchange', durationChanged);
1151                     };
1152                     api.addEventListener('durationchange', durationChanged);
1153                     return;
1154                  }
1155
1156                  break;
1157
1158               case "progress": case "seek":
1159
1160                  var dur = player.video.duration
1161
1162                  if (api.currentTime > 0) {
1163                     arg = Math.max(api.currentTime, 0);
1164                     break;
1165
1166                  } else if (flow == 'progress') {
1167                     return;
1168                  }
1169
1170
1171               case "speed":
1172                  arg = round(api.playbackRate);
1173                  break;
1174
1175               case "volume":
1176                  arg = round(api.volume);
1177                  break;
1178
1179               case "error":
1180                  try {
1181                     arg = (e.srcElement || e.originalTarget).error;
1182                  } catch (er) {
1183                     // Most likely https://bugzilla.mozilla.org/show_bug.cgi?id=208427
1184                     return;
1185                  }
1186            }
1187
1188            player.trigger(event, arg);
1189
1190         }, false);
1191
1192      });
1193
1194   }
1195
1196};var TYPE_RE = /\.(\w{3,4})(\?.*)?$/i;
1197
1198function parseSource(el) {
1199
1200   var src = el.attr("src"),
1201      type = el.attr("type") || "",
1202      suffix = src.split(TYPE_RE)[1];
1203
1204   type = /mpegurl/.test(type) ? "mpegurl" : type.replace("video/", "");
1205
1206   return { src: src, suffix: suffix || type, type: type || suffix };
1207}
1208
1209/* Resolves video object from initial configuration and from load() method */
1210function URLResolver(videoTag) {
1211
1212   var self = this,
1213      sources = [];
1214
1215   // initial sources
1216   $("source", videoTag).each(function() {
1217      sources.push(parseSource($(this)));
1218   });
1219
1220   if (!sources.length) sources.push(parseSource(videoTag));
1221
1222   self.initialSources = sources;
1223
1224   self.resolve = function(video) {
1225      if (!video) return { sources: sources };
1226
1227      if ($.isArray(video)) {
1228
1229         video = { sources: $.map(video, function(el) {
1230            var type, ret = $.extend({}, el);
1231            $.each(el, function(key, value) { type = key; });
1232            ret.type = type;
1233            ret.src = el[type];
1234            delete ret[type];
1235            return ret;
1236         })};
1237
1238      } else if (typeof video == 'string') {
1239
1240         video = { src: video, sources: [] };
1241
1242         $.each(sources, function(i, source) {
1243            if (source.type != 'flash') {
1244               video.sources.push({
1245                  type: source.type,
1246                  src: video.src.replace(TYPE_RE, "." + source.suffix + "$2")
1247               });
1248            }
1249         });
1250      }
1251
1252      return video;
1253   };
1254
1255};
1256/* A minimal jQuery Slider plugin with all goodies */
1257
1258// skip IE policies
1259// document.ondragstart = function () { return false; };
1260
1261
1262// execute function every <delay> ms
1263$.throttle = function(fn, delay) {
1264   var locked;
1265
1266   return function () {
1267      if (!locked) {
1268         fn.apply(this, arguments);
1269         locked = 1;
1270         setTimeout(function () { locked = 0; }, delay);
1271      }
1272   };
1273};
1274
1275
1276$.fn.slider2 = function(rtl) {
1277
1278   var IS_IPAD = /iPad/.test(navigator.userAgent) && !/CriOS/.test(navigator.userAgent);
1279
1280   return this.each(function() {
1281
1282      var root = $(this),
1283         doc = $(document),
1284         progress = root.children(":last"),
1285         disabled,
1286         offset,
1287         width,
1288         height,
1289         vertical,
1290         size,
1291         maxValue,
1292         max,
1293         skipAnimation = false,
1294
1295         /* private */
1296         calc = function() {
1297            offset = root.offset();
1298            width = root.width();
1299            height = root.height();
1300
1301            /* exit from fullscreen can mess this up.*/
1302            // vertical = height > width;
1303
1304            size = vertical ? height : width;
1305            max = toDelta(maxValue);
1306         },
1307
1308         fire = function(value) {
1309            if (!disabled && value != api.value && (!maxValue || value < maxValue)) {
1310               root.trigger("slide", [ value ]);
1311               api.value = value;
1312            }
1313         },
1314
1315         mousemove = function(e) {
1316            var pageX = e.pageX;
1317            if (!pageX && e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length) {
1318               pageX = e.originalEvent.touches[0].pageX;
1319            }
1320            var delta = vertical ? e.pageY - offset.top : pageX - offset.left;
1321            delta = Math.max(0, Math.min(max || size, delta));
1322
1323            var value = delta / size;
1324            if (vertical) value = 1 - value;
1325            if (rtl) value = 1 - value;
1326            return move(value, 0, true);
1327         },
1328
1329         move = function(value, speed) {
1330            if (speed === undefined) { speed = 0; }
1331            if (value > 1) value = 1;
1332
1333            var to = (Math.round(value * 1000) / 10) + "%";
1334
1335            if (!maxValue || value <= maxValue) {
1336               if (!IS_IPAD) progress.stop(); // stop() broken on iPad
1337               if (skipAnimation) {
1338                  progress.css('width', to);
1339               } else {
1340                  progress.animate(vertical ? { height: to } : { width: to }, speed, "linear");
1341               }
1342            }
1343
1344            return value;
1345         },
1346
1347         toDelta = function(value) {
1348            return Math.max(0, Math.min(size, vertical ? (1 - value) * height : value * width));
1349         },
1350
1351         /* public */
1352         api = {
1353
1354            max: function(value) {
1355               maxValue = value;
1356            },
1357
1358            disable: function(flag) {
1359               disabled = flag;
1360            },
1361
1362            slide: function(value, speed, fireEvent) {
1363               calc();
1364               if (fireEvent) fire(value);
1365               move(value, speed);
1366            },
1367
1368            // Should animation be handled via css
1369            disableAnimation: function(value) {
1370               skipAnimation = value !== false;
1371            }
1372
1373         };
1374
1375      calc();
1376
1377      // bound dragging into document
1378      root.data("api", api).bind("mousedown.sld touchstart", function(e) {
1379         e.preventDefault();
1380
1381         if (!disabled) {
1382
1383            // begin --> recalculate. allows dynamic resizing of the slider
1384            var delayedFire = $.throttle(fire, 100);
1385            calc();
1386            api.dragging = true;
1387            root.addClass('is-dragging');
1388            fire(mousemove(e));
1389
1390            doc.bind("mousemove.sld touchmove", function(e) {
1391               e.preventDefault();
1392               delayedFire(mousemove(e));
1393
1394            }).one("mouseup touchend", function() {
1395               api.dragging = false;
1396               root.removeClass('is-dragging');
1397               doc.unbind("mousemove.sld touchmove");
1398            });
1399
1400         }
1401
1402      });
1403
1404   });
1405
1406};
1407
1408function zeropad(val) {
1409   val = parseInt(val, 10);
1410   return val >= 10 ? val : "0" + val;
1411}
1412
1413// display seconds in hh:mm:ss format
1414function format(sec) {
1415
1416   sec = sec || 0;
1417
1418   var h = Math.floor(sec / 3600),
1419       min = Math.floor(sec / 60);
1420
1421   sec = sec - (min * 60);
1422
1423   if (h >= 1) {
1424      min -= h * 60;
1425      return h + ":" + zeropad(min) + ":" + zeropad(sec);
1426   }
1427
1428   return zeropad(min) + ":" + zeropad(sec);
1429}
1430
1431flowplayer(function(api, root) {
1432
1433   var conf = api.conf,
1434      support = flowplayer.support,
1435      hovertimer;
1436   root.find('.fp-ratio,.fp-ui').remove();
1437   root.addClass("flowplayer").append('\
1438      <div class="ratio"/>\
1439      <div class="ui">\
1440         <div class="waiting"><em/><em/><em/></div>\
1441         <a class="fullscreen"/>\
1442         <a class="unload"/>\
1443         <p class="speed"/>\
1444         <div class="controls">\
1445            <a class="play"></a>\
1446            <div class="timeline">\
1447               <div class="buffer"/>\
1448               <div class="progress"/>\
1449            </div>\
1450            <div class="volume">\
1451               <a class="mute"></a>\
1452               <div class="volumeslider">\
1453                  <div class="volumelevel"/>\
1454               </div>\
1455            </div>\
1456         </div>\
1457         <div class="time">\
1458            <em class="elapsed">00:00</em>\
1459            <em class="remaining"/>\
1460            <em class="duration">00:00</em>\
1461         </div>\
1462         <div class="message"><h2/><p/></div>\
1463      </div>'.replace(/class="/g, 'class="fp-')
1464   );
1465
1466   function find(klass) {
1467      return $(".fp-" + klass, root);
1468   }
1469
1470   // widgets
1471   var progress = find("progress"),
1472      buffer = find("buffer"),
1473      elapsed = find("elapsed"),
1474      remaining = find("remaining"),
1475      waiting = find("waiting"),
1476      ratio = find("ratio"),
1477      speed = find("speed"),
1478      durationEl = find("duration"),
1479      origRatio = ratio.css("paddingTop"),
1480
1481      // sliders
1482      timeline = find("timeline").slider2(api.rtl),
1483      timelineApi = timeline.data("api"),
1484
1485      volume = find("volume"),
1486      fullscreen = find("fullscreen"),
1487      volumeSlider = find("volumeslider").slider2(api.rtl),
1488      volumeApi = volumeSlider.data("api"),
1489      noToggle = root.is(".fixed-controls, .no-toggle");
1490
1491   timelineApi.disableAnimation(root.hasClass('is-touch'));
1492
1493   // aspect ratio
1494   function setRatio(val) {
1495      if ((root.css('width') === '0px' || root.css('height') === '0px') || val !== flowplayer.defaults.ratio) {
1496         if (!parseInt(origRatio, 10)) ratio.css("paddingTop", val * 100 + "%");
1497      }
1498      if (!support.inlineBlock) $("object", root).height(root.height());
1499   }
1500
1501   function hover(flag) {
1502      root.toggleClass("is-mouseover", flag).toggleClass("is-mouseout", !flag);
1503   }
1504
1505   // loading...
1506   if (!support.animation) waiting.html("<p>loading &hellip;</p>");
1507
1508   setRatio(conf.ratio);
1509
1510   // no fullscreen in IFRAME
1511   try {
1512      if (!conf.fullscreen) fullscreen.remove();
1513
1514   } catch (e) {
1515      fullscreen.remove();
1516   }
1517
1518
1519   api.bind("ready", function() {
1520
1521      var duration = api.video.duration;
1522
1523      timelineApi.disable(api.disabled || !duration);
1524
1525      conf.adaptiveRatio && setRatio(api.video.height / api.video.width);
1526
1527      // initial time & volume
1528      durationEl.add(remaining).html(format(duration));
1529
1530      // do we need additional space for showing hour
1531      ((duration >= 3600) && root.addClass('is-long')) || root.removeClass('is-long');
1532      volumeApi.slide(api.volumeLevel);
1533
1534
1535   }).bind("unload", function() {
1536      if (!origRatio) ratio.css("paddingTop", "");
1537
1538   // buffer
1539   }).bind("buffer", function() {
1540      var video = api.video,
1541         max = video.buffer / video.duration;
1542
1543      if (!video.seekable && support.seekable) timelineApi.max(max);
1544      if (max < 1) buffer.css("width", (max * 100) + "%");
1545      else buffer.css({ width: '100%' });
1546
1547   }).bind("speed", function(e, api, val) {
1548      speed.text(val + "x").addClass("fp-hilite");
1549      setTimeout(function() { speed.removeClass("fp-hilite") }, 1000);
1550
1551   }).bind("buffered", function() {
1552      buffer.css({ width: '100%' });
1553      timelineApi.max(1);
1554
1555   // progress
1556   }).bind("progress", function() {
1557
1558      var time = api.video.time,
1559         duration = api.video.duration;
1560
1561      if (!timelineApi.dragging) {
1562         timelineApi.slide(time / duration, api.seeking ? 0 : 250);
1563      }
1564
1565      elapsed.html(format(time));
1566      remaining.html("-" + format(duration - time));
1567
1568   }).bind("finish resume seek", function(e) {
1569      root.toggleClass("is-finished", e.type == "finish");
1570
1571   }).bind("stop", function() {
1572      elapsed.html(format(0));
1573      timelineApi.slide(0, 100);
1574
1575   }).bind("finish", function() {
1576      elapsed.html(format(api.video.duration));
1577      timelineApi.slide(1, 100);
1578      root.removeClass("is-seeking");
1579
1580   // misc
1581   }).bind("beforeseek", function() {
1582      progress.stop();
1583
1584   }).bind("volume", function() {
1585      volumeApi.slide(api.volumeLevel);
1586
1587
1588   }).bind("disable", function() {
1589      var flag = api.disabled;
1590      timelineApi.disable(flag);
1591      volumeApi.disable(flag);
1592      root.toggleClass("is-disabled", api.disabled);
1593
1594   }).bind("mute", function(e, api, flag) {
1595      root.toggleClass("is-muted", flag);
1596
1597   }).bind("error", function(e, api, error) {
1598      root.removeClass("is-loading").addClass("is-error");
1599
1600      if (error) {
1601         error.message = conf.errors[error.code];
1602         api.error = true;
1603
1604         var el = $(".fp-message", root);
1605         $("h2", el).text((api.engine || 'html5') + ": " + error.message);
1606         $("p", el).text(error.url || api.video.url || api.video.src || conf.errorUrls[error.code]);
1607         root.unbind("mouseenter click").removeClass("is-mouseover");
1608      }
1609
1610
1611   // hover
1612   }).bind("mouseenter mouseleave", function(e) {
1613      if (noToggle) return;
1614
1615      var is_over = e.type == "mouseenter",
1616         lastMove;
1617
1618      // is-mouseover/out
1619      hover(is_over);
1620
1621      if (is_over) {
1622
1623         root.bind("pause.x mousemove.x volume.x", function() {
1624            hover(true);
1625            lastMove = new Date;
1626         });
1627
1628         hovertimer = setInterval(function() {
1629            if (new Date - lastMove > 5000) {
1630               hover(false)
1631               lastMove = new Date;
1632            }
1633         }, 100);
1634
1635      } else {
1636         root.unbind(".x");
1637         clearInterval(hovertimer);
1638      }
1639
1640
1641   // allow dragging over the player edge
1642   }).bind("mouseleave", function() {
1643
1644      if (timelineApi.dragging || volumeApi.dragging) {
1645         root.addClass("is-mouseover").removeClass("is-mouseout");
1646      }
1647
1648   // click
1649   }).bind("click.player", function(e) {
1650      if ($(e.target).is(".fp-ui, .fp-engine") || e.flash) {
1651         e.preventDefault();
1652         return api.toggle();
1653      }
1654   });
1655
1656   // poster -> background image
1657   if (conf.poster) root.css("backgroundImage", "url(" + conf.poster + ")");
1658
1659   var bc = root.css("backgroundColor"),
1660      has_bg = root.css("backgroundImage") != "none" || bc && bc != "rgba(0, 0, 0, 0)" && bc != "transparent";
1661
1662   // is-poster class
1663   if (has_bg && !conf.splash && !conf.autoplay) {
1664
1665      api.bind("ready stop", function() {
1666         root.addClass("is-poster").one("progress", function() {
1667            root.removeClass("is-poster");
1668         });
1669      });
1670
1671   }
1672
1673   // default background color if not present
1674   if (!has_bg && api.forcedSplash) {
1675      root.css("backgroundColor", "#555");
1676   }
1677
1678   $(".fp-toggle, .fp-play", root).click(api.toggle);
1679
1680   /* controlbar elements */
1681   $.each(['mute', 'fullscreen', 'unload'], function(i, key) {
1682      find(key).click(function() {
1683         api[key]();
1684      });
1685   });
1686
1687   timeline.bind("slide", function(e, val) {
1688      api.seeking = true;
1689      api.seek(val * api.video.duration);
1690   });
1691
1692   volumeSlider.bind("slide", function(e, val) {
1693      api.volume(val);
1694   });
1695
1696   // times
1697   find("time").click(function(e) {
1698      $(this).toggleClass("is-inverted");
1699   });
1700
1701   hover(noToggle);
1702
1703});
1704
1705var focused,
1706   focusedRoot,
1707   IS_HELP = "is-help";
1708
1709 // keyboard. single global listener
1710$(document).bind("keydown.fp", function(e) {
1711
1712   var el = focused,
1713      metaKeyPressed = e.ctrlKey || e.metaKey || e.altKey,
1714      key = e.which,
1715      conf = el && el.conf;
1716
1717   if (!el || !conf.keyboard || el.disabled) return;
1718
1719   // help dialog (shift key not truly required)
1720   if ($.inArray(key, [63, 187, 191, 219]) != -1) {
1721      focusedRoot.toggleClass(IS_HELP);
1722      return false;
1723   }
1724
1725   // close help / unload
1726   if (key == 27 && focusedRoot.hasClass(IS_HELP)) {
1727      focusedRoot.toggleClass(IS_HELP);
1728      return false;
1729   }
1730
1731   if (!metaKeyPressed && el.ready) {
1732
1733      e.preventDefault();
1734
1735      // slow motion / fast forward
1736      if (e.shiftKey) {
1737         if (key == 39) el.speed(true);
1738         else if (key == 37) el.speed(false);
1739         return;
1740      }
1741
1742      // 1, 2, 3, 4 ..
1743      if (key < 58 && key > 47) return el.seekTo(key - 48);
1744
1745      switch (key) {
1746         case 38: case 75: el.volume(el.volumeLevel + 0.15); break;        // volume up
1747         case 40: case 74: el.volume(el.volumeLevel - 0.15); break;        // volume down
1748         case 39: case 76: el.seeking = true; el.seek(true); break;        // forward
1749         case 37: case 72: el.seeking = true; el.seek(false); break;       // backward
1750         case 190: el.seekTo(); break;                                     // to last seek position
1751         case 32: el.toggle(); break;                                      // spacebar
1752         case 70: conf.fullscreen && el.fullscreen(); break;               // toggle fullscreen
1753         case 77: el.mute(); break;                                        // mute
1754         case 27: el[el.isFullscreen ? "fullscreen" : "unload"](); break;  // esc
1755      }
1756
1757   }
1758
1759});
1760
1761flowplayer(function(api, root) {
1762
1763   // no keyboard configured
1764   if (!api.conf.keyboard) return;
1765
1766   // hover
1767   root.bind("mouseenter mouseleave", function(e) {
1768      focused = !api.disabled && e.type == 'mouseenter' ? api : 0;
1769      if (focused) focusedRoot = root;
1770   });
1771
1772   // TODO: add to player-layout.html
1773   root.append('\
1774      <div class="fp-help">\
1775         <a class="fp-close"></a>\
1776         <div class="fp-help-section fp-help-basics">\
1777            <p><em>space</em>play / pause</p>\
1778            <p><em>esc</em>stop</p>\
1779            <p><em>f</em>fullscreen</p>\
1780            <p><em>shift</em> + <em>&#8592;</em><em>&#8594;</em>slower / faster <small>(latest Chrome and Safari)</small></p>\
1781         </div>\
1782         <div class="fp-help-section">\
1783            <p><em>&#8593;</em><em>&#8595;</em>volume</p>\
1784            <p><em>m</em>mute</p>\
1785         </div>\
1786         <div class="fp-help-section">\
1787            <p><em>&#8592;</em><em>&#8594;</em>seek</p>\
1788            <p><em>&nbsp;. </em>seek to previous\
1789            </p><p><em>1</em><em>2</em>&hellip;<em>6</em> seek to 10%, 20%, &hellip;60% </p>\
1790         </div>\
1791      </div>\
1792   ');
1793
1794   if (api.conf.tooltip) {
1795      $(".fp-ui", root).attr("title", "Hit ? for help").on("mouseout.tip", function() {
1796         $(this).removeAttr("title").off("mouseout.tip");
1797      });
1798   }
1799
1800   $(".fp-close", root).click(function() {
1801      root.toggleClass(IS_HELP);
1802   });
1803
1804});
1805
1806var VENDOR = $.browser.mozilla ? "moz": "webkit",
1807   FS_ENTER = "fullscreen",
1808   FS_EXIT = "fullscreen-exit",
1809   FULL_PLAYER,
1810   FS_SUPPORT = flowplayer.support.fullscreen,
1811   FS_NATIVE_SUPPORT = typeof document.exitFullscreen == 'function',
1812   ua = navigator.userAgent.toLowerCase(),
1813   IS_SAFARI = /(safari)[ \/]([\w.]+)/.exec(ua) && !/(chrome)[ \/]([\w.]+)/.exec(ua);
1814
1815
1816// esc button
1817$(document).bind(FS_NATIVE_SUPPORT ? "fullscreenchange" : VENDOR + "fullscreenchange", function(e) {
1818   var el = $(document.webkitCurrentFullScreenElement || document.mozFullScreenElement || document.fullscreenElement || e.target);
1819   if (el.length && !FULL_PLAYER) {
1820      FULL_PLAYER = el.trigger(FS_ENTER, [el]);
1821   } else {
1822      FULL_PLAYER.trigger(FS_EXIT, [FULL_PLAYER]);
1823      FULL_PLAYER = null;
1824   }
1825
1826});
1827
1828
1829flowplayer(function(player, root) {
1830
1831   if (!player.conf.fullscreen) return;
1832
1833   var win = $(window),
1834      fsSeek = {pos: 0, play: false},
1835      scrollTop;
1836
1837   player.isFullscreen = false;
1838
1839   player.fullscreen = function(flag) {
1840
1841      if (player.disabled) return;
1842
1843      if (flag === undefined) flag = !player.isFullscreen;
1844
1845      if (flag) scrollTop = win.scrollTop();
1846
1847      if (FS_SUPPORT) {
1848
1849         if (flag) {
1850            if (FS_NATIVE_SUPPORT) {
1851               root[0].requestFullscreen();
1852            } else {
1853               root[0][VENDOR + 'RequestFullScreen'](Element.ALLOW_KEYBOARD_INPUT);
1854               if (IS_SAFARI && !document.webkitCurrentFullScreenElement && !document.mozFullScreenElement) { // Element.ALLOW_KEYBOARD_INPUT not allowed
1855                  root[0][VENDOR + 'RequestFullScreen']();
1856               }
1857            }
1858
1859         } else {
1860            if (FS_NATIVE_SUPPORT) {
1861              document.exitFullscreen();
1862            } else {
1863              document[VENDOR + 'CancelFullScreen']();
1864            }
1865         }
1866
1867      } else {
1868         if (player.engine === "flash" && player.conf.rtmp)
1869            fsSeek = {pos: player.video.time, play: player.playing};
1870         player.trigger(flag ? FS_ENTER : FS_EXIT, [player])
1871      }
1872
1873      return player;
1874   };
1875
1876   var lastClick;
1877
1878   root.bind("mousedown.fs", function() {
1879      if (+new Date - lastClick < 150 && player.ready) player.fullscreen();
1880      lastClick = +new Date;
1881   });
1882
1883   player.bind(FS_ENTER, function(e) {
1884      root.addClass("is-fullscreen");
1885      player.isFullscreen = true;
1886
1887   }).bind(FS_EXIT, function(e) {
1888      root.removeClass("is-fullscreen");
1889      player.isFullscreen = false;
1890      win.scrollTop(scrollTop);
1891
1892   }).bind("ready", function () {
1893      if (fsSeek.pos && !isNaN(fsSeek.pos)) {
1894         setTimeout(function () {
1895            player.play(); // avoid hang in buffering state
1896            player.seek(fsSeek.pos);
1897            if (!fsSeek.play) {
1898               setTimeout(function () {
1899                  player.pause();
1900               }, 100);
1901            }
1902            fsSeek = {pos: 0, play: false};
1903         }, 250);
1904      }
1905   });
1906
1907});
1908
1909
1910flowplayer(function(player, root) {
1911
1912   var conf = $.extend({ active: 'is-active', advance: true, query: ".fp-playlist a" }, player.conf),
1913      klass = conf.active;
1914
1915   // getters
1916   function els() {
1917      return $(conf.query, root);
1918   }
1919
1920   function active() {
1921      return $(conf.query + "." + klass, root);
1922   }
1923
1924
1925   player.play = function(i) {
1926      if (i === undefined) return player.resume();
1927      if (typeof i === 'number' && !player.conf.playlist[i]) return player;
1928      else if (typeof i != 'number') player.load.apply(null, arguments);
1929      player.unbind('resume.fromfirst'); // Don't start from beginning if clip explicitely chosen
1930      player.video.index = i;
1931      player.load(typeof player.conf.playlist[i] === 'string' ?
1932         player.conf.playlist[i].toString() :
1933         $.map(player.conf.playlist[i], function(item) { return $.extend({}, item); })
1934      );
1935      return player;
1936   };
1937
1938   player.next = function(e) {
1939      e && e.preventDefault();
1940      var current = player.video.index;
1941      if (current != -1) {
1942         current = current === player.conf.playlist.length - 1 ? 0 : current + 1;
1943         player.play(current);
1944      }
1945      return player;
1946   };
1947
1948   player.prev = function(e) {
1949      e && e.preventDefault();
1950      var current = player.video.index;
1951      if (current != -1) {
1952         current = current === 0 ? player.conf.playlist.length - 1 : current - 1;
1953         player.play(current);
1954      }
1955      return player;
1956   };
1957
1958   $('.fp-next', root).click(player.next);
1959   $('.fp-prev', root).click(player.prev);
1960
1961   if (conf.advance) {
1962      root.unbind("finish.pl").bind("finish.pl", function(e, player) {
1963
1964         // next clip is found or loop
1965         var next = player.video.index + 1;
1966         if (next < player.conf.playlist.length || conf.loop) {
1967            next = next === player.conf.playlist.length ? 0 : next;
1968            root.removeClass('is-finished');
1969            setTimeout(function() { // Let other finish callbacks fire first
1970               player.play(next);
1971            });
1972
1973         // stop to last clip, play button starts from 1:st clip
1974         } else {
1975            root.addClass("is-playing"); // show play button
1976
1977            // If we have multiple items in playlist, start from first
1978            if (player.conf.playlist.length > 1) player.one("resume.fromfirst", function() {
1979               player.play(0);
1980               return false;
1981            });
1982         }
1983      });
1984   }
1985
1986   var playlistInitialized = false;
1987   if (player.conf.playlist.length) { // playlist configured by javascript, generate playlist
1988      playlistInitialized = true;
1989      var plEl = root.find('.fp-playlist');
1990      if (!plEl.length) {
1991         plEl = $('<div class="fp-playlist"></div>');
1992         var cntrls = $('.fp-next,.fp-prev', root).eq(0).before(plEl);
1993         if (!cntrls.length) $('video', root).after(plEl);
1994      }
1995      plEl.empty();
1996      $.each(player.conf.playlist, function(i, item) {
1997         var href;
1998         if (typeof item === 'string') {
1999            href = item;
2000         } else {
2001            for (var key in item[0]) {
2002               if (item[0].hasOwnProperty(key)) {
2003                  href = item[0][key];
2004                  break;
2005               }
2006            }
2007         }
2008         plEl.append($('<a />').attr({
2009            href: href,
2010            'data-index': i
2011         }));
2012      });
2013   }
2014
2015   if (els().length) {
2016      if (!playlistInitialized) {
2017         player.conf.playlist = [];
2018         els().each(function() {
2019            var src = $(this).attr('href');
2020            $(this).attr('data-index', player.conf.playlist.length);
2021            player.conf.playlist.push(src);
2022         });
2023      }
2024
2025      /* click -> play */
2026      root.on("click", conf.query, function(e) {
2027         e.preventDefault();
2028         var el = $(e.target).closest(conf.query);
2029         var toPlay = Number(el.attr('data-index'));
2030         if (toPlay != -1) {
2031            player.play(toPlay);
2032         }
2033      });
2034
2035      // playlist wide cuepoint support
2036      var has_cuepoints = els().filter("[data-cuepoints]").length;
2037
2038      // highlight
2039      player.bind("load", function(e, api, video) {
2040         var prev = active().removeClass(klass),
2041            prevIndex = prev.attr('data-index'),
2042            index = video.index = player.video.index || 0,
2043            el = $('a[data-index="' + index + '"]', root).addClass(klass),
2044            is_last = index == player.conf.playlist.length - 1;
2045         // index
2046         root.removeClass("video" + prevIndex).addClass("video" + index).toggleClass("last-video", is_last);
2047
2048         // video properties
2049         video.index = api.video.index = index;
2050         video.is_last = api.video.is_last = is_last;
2051
2052         // cuepoints
2053         if (has_cuepoints) player.cuepoints = el.data("cuepoints");
2054
2055
2056      // without namespace callback called only once. unknown rason.
2057      }).bind("unload.pl", function() {
2058         active().toggleClass(klass);
2059
2060      });
2061
2062   }
2063
2064   if (player.conf.playlist.length) {
2065      // disable single clip looping
2066      player.conf.loop = false;
2067   }
2068
2069
2070});
2071
2072var CUE_RE = / ?cue\d+ ?/;
2073
2074flowplayer(function(player, root) {
2075
2076   var lastTime = 0;
2077
2078   player.cuepoints = player.conf.cuepoints || [];
2079
2080   function setClass(index) {
2081      root[0].className = root[0].className.replace(CUE_RE, " ");
2082      if (index >= 0) root.addClass("cue" + index);
2083   }
2084
2085   player.bind("progress", function(e, api, time) {
2086
2087      // avoid throwing multiple times
2088      if (lastTime && time - lastTime < 0.015) return lastTime = time;
2089      lastTime = time;
2090
2091      var cues = player.cuepoints || [];
2092
2093      for (var i = 0, cue; i < cues.length; i++) {
2094
2095         cue = cues[i];
2096         if (!isNaN(cue)) cue = { time: cue };
2097         if (cue.time < 0) cue.time = player.video.duration + cue.time;
2098         cue.index = i;
2099
2100         // progress_interval / 2 = 0.125
2101         if (Math.abs(cue.time - time) < 0.125 * player.currentSpeed) {
2102            setClass(i);
2103            root.trigger("cuepoint", [player, cue]);
2104         }
2105
2106      }
2107
2108   // no CSS class name
2109   }).bind("unload seek", setClass);
2110
2111   if (player.conf.generate_cuepoints) {
2112
2113      player.bind("load", function() {
2114
2115         // clean up cuepoint elements of previous playlist items
2116         $(".fp-cuepoint", root).remove();
2117
2118      }).bind("ready", function() {
2119
2120         var cues = player.cuepoints || [],
2121            duration = player.video.duration,
2122            timeline = $(".fp-timeline", root).css("overflow", "visible");
2123
2124         $.each(cues, function(i, cue) {
2125
2126            var time = cue.time || cue;
2127            if (time < 0) time = duration + cue;
2128
2129            var el = $("<a/>").addClass("fp-cuepoint fp-cuepoint" + i)
2130               .css("left", (time / duration * 100) + "%");
2131
2132            el.appendTo(timeline).mousedown(function() {
2133               player.seek(time);
2134
2135               // preventDefault() doesn't work
2136               return false;
2137            });
2138
2139         });
2140
2141      });
2142
2143   }
2144
2145});
2146flowplayer(function(player, root, engine) {
2147
2148   var track = $("track", root),
2149      conf = player.conf;
2150
2151   if (flowplayer.support.subtitles) {
2152
2153      player.subtitles = track.length && track[0].track;
2154
2155      // use native when supported
2156      if (conf.nativesubtitles && conf.engine == 'html5') return;
2157   }
2158
2159   // avoid duplicate loads
2160   track.remove();
2161
2162   var TIMECODE_RE = /^(([0-9]{2}:)?[0-9]{2}:[0-9]{2}[,.]{1}[0-9]{3}) --\> (([0-9]{2}:)?[0-9]{2}:[0-9]{2}[,.]{1}[0-9]{3})(.*)/;
2163
2164   function seconds(timecode) {
2165      var els = timecode.split(':');
2166      if (els.length == 2) els.unshift(0);
2167      return els[0] * 60 * 60 + els[1] * 60 + parseFloat(els[2].replace(',','.'));
2168   }
2169
2170   player.subtitles = [];
2171
2172   var url = track.attr("src");
2173
2174   if (!url) return;
2175
2176   $.get(url, function(txt) {
2177
2178      for (var i = 0, lines = txt.split("\n"), len = lines.length, entry = {}, title, timecode, text, cue; i < len; i++) {
2179
2180         timecode = TIMECODE_RE.exec(lines[i]);
2181
2182         if (timecode) {
2183
2184            // title
2185            title = lines[i - 1];
2186
2187            // text
2188            text = "<p>" + lines[++i] + "</p><br/>";
2189            while ($.trim(lines[++i]) && i < lines.length) text +=  "<p>" + lines[i] + "</p><br/>";
2190
2191            // entry
2192            entry = {
2193               title: title,
2194               startTime: seconds(timecode[1]),
2195               endTime: seconds(timecode[2] || timecode[3]),
2196               text: text
2197            };
2198
2199            cue = { time: entry.startTime, subtitle: entry };
2200
2201            player.subtitles.push(entry);
2202            player.cuepoints.push(cue);
2203            player.cuepoints.push({ time: entry.endTime, subtitleEnd: title });
2204
2205            // initial cuepoint
2206            if (entry.startTime === 0) {
2207               player.trigger("cuepoint", cue);
2208            }
2209
2210         }
2211
2212      }
2213
2214   }).fail(function() {
2215      player.trigger("error", {code: 8, url: url });
2216      return false;
2217   });
2218
2219   var wrap = $("<div class='fp-subtitle'/>", root).appendTo(root),
2220      currentPoint;
2221
2222   player.bind("cuepoint", function(e, api, cue) {
2223
2224      if (cue.subtitle) {
2225         currentPoint = cue.index;
2226         wrap.html(cue.subtitle.text).addClass("fp-active");
2227
2228      } else if (cue.subtitleEnd) {
2229         wrap.removeClass("fp-active");
2230         currentPoint = cue.index;
2231      }
2232
2233   }).bind("seek", function(e, api, time) {
2234      // Clear future subtitles if seeking backwards
2235      if (currentPoint && player.cuepoints[currentPoint] && player.cuepoints[currentPoint].time > time) {
2236         wrap.removeClass('fp-active');
2237         currentPoint = null;
2238      }
2239      $.each(player.cuepoints || [], function(i, cue) {
2240         var entry = cue.subtitle;
2241         //Trigger cuepoint if start time before seek position and end time nonexistent or in the future
2242         if (entry && currentPoint != cue.index) {
2243            if (time >= cue.time && (!entry.endTime || time <= entry.endTime)) player.trigger("cuepoint", cue);
2244         } // Also handle cuepoints that act as the removal trigger
2245         else if (cue.subtitleEnd && time >= cue.time && cue.index == currentPoint + 1) player.trigger("cuepoint", cue);
2246      });
2247
2248   });
2249
2250});
2251
2252
2253
2254flowplayer(function(player, root) {
2255
2256   var id = player.conf.analytics, time = 0, last = 0;
2257
2258   if (id) {
2259
2260      // load Analytics script if needed
2261      if (typeof _gat == 'undefined') $.getScript("//google-analytics.com/ga.js");
2262
2263      function track(e) {
2264
2265         if (time && typeof _gat != 'undefined') {
2266            var tracker = _gat._getTracker(id),
2267               video = player.video;
2268
2269            tracker._setAllowLinker(true);
2270
2271            // http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html
2272            tracker._trackEvent(
2273               "Video / Seconds played",
2274               player.engine + "/" + video.type,
2275               root.attr("title") || video.src.split("/").slice(-1)[0].replace(TYPE_RE, ''),
2276               Math.round(time / 1000)
2277            );
2278            time = 0;
2279         }
2280
2281      }
2282
2283      player.bind("load unload", track).bind("progress", function() {
2284
2285         if (!player.seeking) {
2286            time += last ? (+new Date - last) : 0;
2287            last = +new Date;
2288         }
2289
2290      }).bind("pause", function() {
2291         last = 0;
2292      });
2293
2294      $(window).unload(track);
2295
2296   }
2297
2298});var isIeMobile = /IEMobile/.test(UA);
2299if (flowplayer.support.touch || isIeMobile) {
2300
2301   flowplayer(function(player, root) {
2302      var isAndroid = /Android/.test(UA) && !/Firefox/.test(UA) && !/Opera/.test(UA),
2303          isSilk = /Silk/.test(UA);
2304
2305      // custom load for android
2306      if (isAndroid) {
2307         player.conf.videoTypePreference = "mp4"; // Android has problems with webm aspect ratio
2308         var originalLoad = player.load;
2309         player.load = function(video, callback) {
2310            var ret = originalLoad.apply(player, arguments);
2311            player.trigger('ready', player, player.video);
2312            return ret;
2313         };
2314      }
2315
2316      // hide volume
2317      if (!flowplayer.support.volume) {
2318         root.addClass("no-volume no-mute");
2319      }
2320      root.addClass("is-touch");
2321      root.find('.fp-timeline').data('api').disableAnimation();
2322
2323      // fake mouseover effect with click
2324      var hasMoved = false;
2325      root.bind('touchmove', function() {
2326         hasMoved = true;
2327      }).bind("touchend click", function(e) {
2328         if (hasMoved) { //not intentional, most likely scrolling
2329            hasMoved = false;
2330            return;
2331         }
2332
2333         if (player.playing && !root.hasClass("is-mouseover")) {
2334            root.addClass("is-mouseover").removeClass("is-mouseout");
2335            return false;
2336         }
2337
2338         if (player.paused && root.hasClass("is-mouseout")) {
2339            player.toggle();
2340         }
2341
2342         if (player.paused && isIeMobile) { // IE on WP7 need an additional api.play() call
2343            $('video', root)[0].play();
2344         }
2345
2346      });
2347
2348      // native fullscreen
2349      if (player.conf.native_fullscreen && ($.browser.webkit || $.browser.safari)) {
2350         player.fullscreen = function() {
2351            var video = $('video', root);
2352            video[0].webkitEnterFullScreen();
2353            video.one('webkitendfullscreen', function() {
2354               video.prop('controls', true).prop('controls', false);
2355            });
2356         };
2357      }
2358
2359
2360      // Android browser gives video.duration == 1 until second 'timeupdate' event
2361      (isAndroid || isSilk) && player.bind("ready", function() {
2362
2363         var video = $('video', root);
2364         video.one('canplay', function() {
2365            video[0].play();
2366         });
2367         video[0].play();
2368
2369         player.bind("progress.dur", function() {
2370
2371            var duration = video[0].duration;
2372
2373            if (duration !== 1) {
2374               player.video.duration = duration;
2375               $(".fp-duration", root).html(format(duration));
2376               player.unbind("progress.dur");
2377            }
2378         });
2379      });
2380
2381
2382   });
2383
2384}
2385
2386flowplayer(function(player, root) {
2387
2388   // no embedding
2389   if (player.conf.embed === false) return;
2390
2391   var conf = player.conf,
2392      ui = $(".fp-ui", root),
2393      trigger = $("<a/>", { "class": "fp-embed", title: 'Copy to your site'}).appendTo(ui),
2394      target = $("<div/>", { 'class': 'fp-embed-code'})
2395         .append("<label>Paste this HTML code on your site to embed.</label><textarea/>").appendTo(ui),
2396      area = $("textarea", target);
2397
2398   player.embedCode = function() {
2399
2400      var video = player.video,
2401         width = video.width || root.width(),
2402         height = video.height || root.height(),
2403         el = $("<div/>", { 'class': 'flowplayer', css: { width: width, height: height }}),
2404         tag = $("<video/>").appendTo(el);
2405
2406      // configuration
2407      $.each(['origin', 'analytics', 'logo', 'key', 'rtmp'], function(i, key) {
2408         if (conf[key]) el.attr("data-" + key, conf[key]);
2409      });
2410
2411      // sources
2412      $.each(video.sources, function(i, src) {
2413         tag.append($("<source/>", { type: "video/" + src.type, src: src.src }));
2414      });
2415
2416      var scriptAttrs = { src: "//embed.flowplayer.org/5.4.3/embed.min.js" };
2417      if ($.isPlainObject(conf.embed)) {
2418         scriptAttrs['data-swf'] = conf.embed.swf;
2419         scriptAttrs['data-library'] = conf.embed.library;
2420         scriptAttrs['src'] = conf.embed.script || scriptAttrs['src'];
2421         if (conf.embed.skin) { scriptAttrs['data-skin'] = conf.embed.skin; }
2422      }
2423
2424      var code = $("<foo/>", scriptAttrs).append(el);
2425      return $("<p/>").append(code).html().replace(/<(\/?)foo/g, "<$1script");
2426   };
2427
2428   root.fptip(".fp-embed", "is-embedding");
2429
2430   area.click(function() {
2431      this.select();
2432   });
2433
2434   trigger.click(function() {
2435      area.text(player.embedCode());
2436      area[0].focus();
2437      area[0].select();
2438   });
2439
2440});
2441
2442
2443$.fn.fptip = function(trigger, active) {
2444
2445   return this.each(function() {
2446
2447      var root = $(this);
2448
2449      function close() {
2450         root.removeClass(active);
2451         $(document).unbind(".st");
2452      }
2453
2454      $(trigger || "a", this).click(function(e) {
2455
2456         e.preventDefault();
2457
2458         root.toggleClass(active);
2459
2460         if (root.hasClass(active)) {
2461
2462            $(document).bind("keydown.st", function(e) {
2463               if (e.which == 27) close();
2464
2465            // click:close
2466            }).bind("click.st", function(e) {
2467               if (!$(e.target).parents("." + active).length) close();
2468            });
2469         }
2470
2471      });
2472
2473   });
2474
2475};
2476
2477}(jQuery);
2478flowplayer(function(e,o){function a(e){var o=n("<a/>")[0];return o.href=e,o.hostname}var n=jQuery,t=e.conf,i=t.swf.indexOf("flowplayer.org")&&t.e&&o.data("origin"),l=i?a(i):location.hostname,r=t.key;if("file:"==location.protocol&&(l="localhost"),e.load.ed=1,t.hostname=l,t.origin=i||location.href,i&&o.addClass("is-embedded"),"string"==typeof r&&(r=r.split(/,\s*/)),r&&"function"==typeof key_check&&key_check(r,l))t.logo&&o.append(n("<a>",{"class":"fp-logo",href:i}).append(n("<img/>",{src:t.logo})));else{var s=n("<a/>").attr("href","http://flowplayer.org").appendTo(o);n(".fp-controls",o),e.bind("pause resume finish unload",function(e,o){var a=o.video.src?o.video.src.indexOf("://my.flowplayer.org"):-1;/pause|resume/.test(e.type)&&"flash"!=o.engine&&4!=a&&5!=a?(s.show().css({position:"absolute",left:16,bottom:36,zIndex:99999,width:100,height:20,backgroundImage:"url("+[".png","logo","/",".net",".cloudfront","d32
2478wqyuo10o653","//"].reverse().join("")+")"}),o.load.ed=s.is(":visible"),o.load.ed||o.pause()):s.hide()})}});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.