flowplayer.js 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222
  1. /*!
  2. Flowplayer v5.3.2 (Monday, 28. January 2013 10:02AM) | flowplayer.org/license
  3. */
  4. !function($) {
  5. /*
  6. jQuery.browser for 1.9+
  7. We all love feature detection but that's sometimes not enough.
  8. @author Tero Piirainen
  9. */
  10. !function($) {
  11. if (!$.browser) {
  12. var b = $.browser = {},
  13. ua = navigator.userAgent.toLowerCase(),
  14. match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
  15. /(webkit)[ \/]([\w.]+)/.exec(ua) ||
  16. /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
  17. /(msie) ([\w.]+)/.exec(ua) ||
  18. ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [];
  19. if (match[1]) {
  20. b[match[1]] = true;
  21. b.version = match[2] || "0";
  22. }
  23. }
  24. }(jQuery);
  25. // auto-install (any video tag with parent .flowplayer)
  26. $(function() {
  27. if (typeof $.fn.flowplayer == 'function') {
  28. $("video").parent(".flowplayer").flowplayer();
  29. }
  30. });
  31. var instances = [],
  32. extensions = [],
  33. UA = navigator.userAgent,
  34. use_native = /Android/.test(UA) && /Firefox/.test(UA);
  35. /* flowplayer() */
  36. window.flowplayer = function(fn) {
  37. return use_native ? 0 :
  38. $.isFunction(fn) ? extensions.push(fn) :
  39. typeof fn == 'number' || fn === undefined ? instances[fn || 0] :
  40. $(fn).data("flowplayer");
  41. };
  42. $.extend(flowplayer, {
  43. version: '5.3.2',
  44. engine: {},
  45. conf: {},
  46. support: {},
  47. defaults: {
  48. debug: false,
  49. // true = forced playback
  50. disabled: false,
  51. // first engine to try
  52. engine: 'html5',
  53. fullscreen: window == window.top,
  54. // keyboard shortcuts
  55. keyboard: true,
  56. // default aspect ratio
  57. ratio: 9 / 16,
  58. // scale flash object to video's aspect ratio in normal mode?
  59. flashfit: false,
  60. rtmp: 0,
  61. splash: false,
  62. swf: "http://releases.flowplayer.org/5.3.2/flowplayer.swf",
  63. speeds: [0.25, 0.5, 1, 1.5, 2],
  64. tooltip: true,
  65. // initial volume level
  66. volume: 1,
  67. // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes
  68. errors: [
  69. // video exceptions
  70. '',
  71. 'Video loading aborted',
  72. 'Network error',
  73. 'Video not properly encoded',
  74. 'Video file not found',
  75. // player exceptions
  76. 'Unsupported video',
  77. 'Skin not found',
  78. 'SWF file not found',
  79. 'Subtitles not found',
  80. 'Invalid RTMP URL',
  81. 'Unsupported video format. Try installing Adobe Flash.'
  82. ],
  83. errorUrls: ['','','','','','','','','','',
  84. 'http://get.adobe.com/flashplayer/'
  85. ]
  86. }
  87. });
  88. // smartphones simply use native controls
  89. if (use_native) {
  90. return $(function() { $("video").attr("controls", "controls"); });
  91. }
  92. // keep track of players
  93. var playerCount = 0;
  94. // jQuery plugin
  95. $.fn.flowplayer = function(opts, callback) {
  96. if (typeof opts == 'string') opts = { swf: opts }
  97. if ($.isFunction(opts)) { callback = opts; opts = {} }
  98. return !opts && this.data("flowplayer") || this.each(function() {
  99. // private variables
  100. var root = $(this).addClass("is-loading"),
  101. conf = $.extend({}, flowplayer.defaults, flowplayer.conf, opts, root.data()),
  102. videoTag = $("video", root).addClass("fp-engine").removeAttr("controls"),
  103. urlResolver = new URLResolver(videoTag),
  104. storage = {},
  105. lastSeekPosition,
  106. engine;
  107. root.data('fp-player_id', root.data('fp-player_id') || playerCount++);
  108. try {
  109. storage = window.localStorage || storage;
  110. } catch(e) {}
  111. /*** API ***/
  112. var api = {
  113. // properties
  114. conf: conf,
  115. currentSpeed: 1,
  116. volumeLevel: storage.volume * 1 || conf.volume,
  117. video: {},
  118. // states
  119. disabled: false,
  120. finished: false,
  121. loading: false,
  122. muted: storage.muted == "true" || conf.muted,
  123. paused: false,
  124. playing: false,
  125. ready: false,
  126. splash: false,
  127. // methods
  128. load: function(video, callback) {
  129. if (api.error || api.loading || api.disabled) return;
  130. // resolve URL
  131. video = urlResolver.resolve(video);
  132. $.extend(video, engine.pick(video.sources));
  133. if (video.src) {
  134. var e = $.Event("load");
  135. root.trigger(e, [api, video, engine]);
  136. if (!e.isDefaultPrevented()) {
  137. engine.load(video);
  138. // callback
  139. if ($.isFunction(video)) callback = video;
  140. if (callback) root.one("ready", callback);
  141. }
  142. }
  143. return api;
  144. },
  145. pause: function(fn) {
  146. if (api.ready && !api.seeking && !api.disabled && !api.loading) {
  147. engine.pause();
  148. api.one("pause", fn);
  149. }
  150. return api;
  151. },
  152. resume: function() {
  153. if (api.ready && api.paused && !api.disabled) {
  154. engine.resume();
  155. // Firefox (+others?) does not fire "resume" after finish
  156. if (api.finished) {
  157. api.trigger("resume");
  158. api.finished = false;
  159. }
  160. }
  161. return api;
  162. },
  163. toggle: function() {
  164. return api.ready ? api.paused ? api.resume() : api.pause() : api.load();
  165. },
  166. /*
  167. seek(1.4) -> 1.4s time
  168. seek(true) -> 10% forward
  169. seek(false) -> 10% backward
  170. */
  171. seek: function(time, callback) {
  172. if (api.ready) {
  173. if (typeof time == "boolean") {
  174. var delta = api.video.duration * 0.1;
  175. time = api.video.time + (time ? delta : -delta);
  176. }
  177. time = lastSeekPosition = Math.min(Math.max(time, 0), api.video.duration);
  178. engine.seek(time);
  179. if ($.isFunction(callback)) root.one("seek", callback);
  180. }
  181. return api;
  182. },
  183. /*
  184. seekTo(1) -> 10%
  185. seekTo(2) -> 20%
  186. seekTo(3) -> 30%
  187. ...
  188. seekTo() -> last position
  189. */
  190. seekTo: function(position, fn) {
  191. var time = position === undefined ? lastSeekPosition : api.video.duration * 0.1 * position;
  192. return api.seek(time, fn);
  193. },
  194. mute: function(flag) {
  195. if (flag == undefined) flag = !api.muted;
  196. storage.muted = api.muted = flag;
  197. api.volume(flag ? 0 : storage.volume);
  198. api.trigger("mute", flag);
  199. },
  200. volume: function(level) {
  201. if (api.ready) {
  202. level = Math.min(Math.max(level, 0), 1);
  203. storage.volume = level;
  204. engine.volume(level);
  205. }
  206. return api;
  207. },
  208. speed: function(val, callback) {
  209. if (api.ready) {
  210. // increase / decrease
  211. if (typeof val == "boolean") {
  212. val = conf.speeds[$.inArray(api.currentSpeed, conf.speeds) + (val ? 1 : -1)] || api.currentSpeed;
  213. }
  214. engine.speed(val);
  215. if (callback) root.one("speed", callback);
  216. }
  217. return api;
  218. },
  219. stop: function() {
  220. if (api.ready) {
  221. api.pause();
  222. api.seek(0, function() {
  223. root.trigger("stop");
  224. });
  225. }
  226. return api;
  227. },
  228. unload: function() {
  229. if (!root.hasClass("is-embedding")) {
  230. if (conf.splash) {
  231. api.trigger("unload");
  232. engine.unload();
  233. } else {
  234. api.stop();
  235. }
  236. }
  237. return api;
  238. },
  239. disable: function(flag) {
  240. if (flag === undefined) flag = !api.disabled;
  241. if (flag != api.disabled) {
  242. api.disabled = flag;
  243. api.trigger("disable", flag);
  244. }
  245. }
  246. };
  247. /* event binding / unbinding */
  248. $.each(['bind', 'one', 'unbind'], function(i, key) {
  249. api[key] = function(type, fn) {
  250. root[key](type, fn);
  251. return api;
  252. };
  253. });
  254. api.trigger = function(event, arg) {
  255. root.trigger(event, [api, arg]);
  256. return api;
  257. };
  258. /*** Behaviour ***/
  259. root.bind("boot", function() {
  260. // conf
  261. $.each(['autoplay', 'loop', 'preload', 'poster'], function(i, key) {
  262. var val = videoTag.attr(key);
  263. if (val !== undefined) conf[key] = val ? val : true;
  264. });
  265. // splash
  266. if (conf.splash || root.hasClass("is-splash") || !flowplayer.support.firstframe) {
  267. api.splash = conf.splash = conf.autoplay = true;
  268. root.addClass("is-splash");
  269. videoTag.attr("preload", "none");
  270. }
  271. // extensions
  272. $.each(extensions, function(i) {
  273. this(api, root);
  274. });
  275. // 1. use the configured engine
  276. engine = flowplayer.engine[conf.engine];
  277. if (engine) engine = engine(api, root);
  278. if (engine.pick(urlResolver.initialSources)) {
  279. api.engine = conf.engine;
  280. // 2. failed -> try another
  281. } else {
  282. $.each(flowplayer.engine, function(name, impl) {
  283. if (name != conf.engine) {
  284. engine = this(api, root);
  285. if (engine.pick(urlResolver.initialSources)) api.engine = name;
  286. return false;
  287. }
  288. });
  289. }
  290. // no engine
  291. if (!api.engine) return api.trigger("error", { code: flowplayer.support.flash ? 5 : 10 });
  292. // start
  293. conf.splash ? api.unload() : api.load();
  294. // disabled
  295. if (conf.disabled) api.disable();
  296. // initial callback
  297. root.one("ready", callback);
  298. // instances
  299. instances.push(api);
  300. }).bind("load", function(e, api, video) {
  301. // unload others
  302. if (conf.splash) {
  303. $(".flowplayer").filter(".is-ready, .is-loading").not(root).each(function() {
  304. var api = $(this).data("flowplayer");
  305. if (api.conf.splash) api.unload();
  306. });
  307. }
  308. // loading
  309. root.addClass("is-loading");
  310. api.loading = true;
  311. }).bind("ready", function(e, api, video) {
  312. video.time = 0;
  313. api.video = video;
  314. function notLoading() {
  315. root.removeClass("is-loading");
  316. api.loading = false;
  317. }
  318. if (conf.splash) root.one("progress", notLoading);
  319. else notLoading();
  320. // saved state
  321. if (api.muted) api.mute(true);
  322. else api.volume(api.volumeLevel);
  323. }).bind("unload", function(e) {
  324. if (conf.splash) videoTag.remove();
  325. root.removeClass("is-loading");
  326. api.loading = false;
  327. }).bind("ready unload", function(e) {
  328. var is_ready = e.type == "ready";
  329. root.toggleClass("is-splash", !is_ready).toggleClass("is-ready", is_ready);
  330. api.ready = is_ready;
  331. api.splash = !is_ready;
  332. }).bind("progress", function(e, api, time) {
  333. api.video.time = time;
  334. }).bind("speed", function(e, api, val) {
  335. api.currentSpeed = val;
  336. }).bind("volume", function(e, api, level) {
  337. api.volumeLevel = Math.round(level * 100) / 100;
  338. if (!api.muted) storage.volume = level;
  339. else if (level) api.mute(false);
  340. }).bind("beforeseek seek", function(e) {
  341. api.seeking = e.type == "beforeseek";
  342. root.toggleClass("is-seeking", api.seeking);
  343. }).bind("ready pause resume unload finish stop", function(e, _api, video) {
  344. // PAUSED: pause / finish
  345. api.paused = /pause|finish|unload|stop/.test(e.type);
  346. // SHAKY HACK: first-frame / preload=none
  347. if (e.type == "ready") {
  348. if (video) {
  349. api.paused = !video.duration || !conf.autoplay && (conf.preload != 'none' || api.engine == 'flash');
  350. }
  351. }
  352. // the opposite
  353. api.playing = !api.paused;
  354. // CSS classes
  355. root.toggleClass("is-paused", api.paused).toggleClass("is-playing", api.playing);
  356. // sanity check
  357. if (!api.load.ed) api.pause();
  358. }).bind("finish", function(e) {
  359. api.finished = true;
  360. }).bind("error", function() {
  361. videoTag.remove();
  362. });
  363. // boot
  364. root.trigger("boot", [api, root]).data("flowplayer", api);
  365. });
  366. };
  367. !function() {
  368. var s = flowplayer.support,
  369. browser = $.browser,
  370. video = $("<video loop autoplay preload/>")[0],
  371. IS_IE = browser.msie,
  372. UA = navigator.userAgent,
  373. IS_IPAD = /iPad|MeeGo/.test(UA),
  374. IS_IPHONE = /iP(hone|od)/i.test(UA),
  375. IS_ANDROID = /Android/.test(UA),
  376. IS_SILK = /Silk/.test(UA),
  377. IPAD_VER = IS_IPAD ? parseFloat(/Version\/(\d\.\d)/.exec(UA)[1], 10) : 0;
  378. $.extend(s, {
  379. video: !!video.canPlayType,
  380. subtitles: !!video.addTextTrack,
  381. fullscreen: typeof document.webkitCancelFullScreen == 'function'
  382. && !/Mac OS X 10_5.+Version\/5\.0\.\d Safari/.test(UA) || document.mozFullScreenEnabled,
  383. fullscreen_keyboard: !browser.safari || browser.version > "536",
  384. inlineBlock: !(IS_IE && browser.version < 8),
  385. touch: ('ontouchstart' in window),
  386. dataload: !IS_IPAD && !IS_IPHONE,
  387. zeropreload: !IS_IE && !IS_ANDROID, // IE supports only preload=metadata
  388. volume: !IS_IPAD && !IS_ANDROID && !IS_IPHONE && !IS_SILK,
  389. cachedVideoTag: !IS_IPAD && !IS_IPHONE,
  390. firstframe: !IS_IPHONE && !IS_IPAD && !IS_ANDROID && !IS_SILK
  391. });
  392. // flashVideo
  393. try {
  394. var ver = IS_IE ? new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable('$version') :
  395. navigator.plugins["Shockwave Flash"].description;
  396. ver = ver.split(/\D+/);
  397. if (ver.length && !ver[0]) ver = ver.slice(1);
  398. s.flashVideo = ver[0] > 9 || ver[0] == 9 && ver[3] >= 115;
  399. } catch (ignored) {}
  400. // animation
  401. s.animation = (function() {
  402. var vendors = ['','Webkit','Moz','O','ms','Khtml'], el = $("<p/>")[0];
  403. for (var i = 0; i < vendors.length; i++) {
  404. if (el.style[vendors[i] + 'AnimationName'] !== 'undefined') return true;
  405. }
  406. })();
  407. }();
  408. /* The most minimal Flash embedding */
  409. // movie required in opts
  410. function embed(swf, flashvars) {
  411. var id = "obj" + ("" + Math.random()).slice(2, 15),
  412. tag = '<object class="fp-engine" id="' + id+ '" name="' + id + '" ';
  413. tag += $.browser.msie ? 'classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">' :
  414. ' data="' + swf + '" type="application/x-shockwave-flash">';
  415. var opts = {
  416. width: "100%",
  417. height: "100%",
  418. allowscriptaccess: "always",
  419. wmode: "transparent",
  420. quality: "high",
  421. flashvars: "",
  422. // https://github.com/flowplayer/flowplayer/issues/13#issuecomment-9369919
  423. movie: swf + ($.browser.msie ? "?" + id : ""),
  424. name: id
  425. };
  426. // flashvars
  427. $.each(flashvars, function(key, value) {
  428. opts.flashvars += key + "=" + value + "&";
  429. });
  430. // parameters
  431. $.each(opts, function(key, value) {
  432. tag += '<param name="' + key + '" value="'+ value +'"/>';
  433. });
  434. tag += "</object>";
  435. return $(tag);
  436. }
  437. // Flash is buggy allover
  438. if (window.attachEvent) {
  439. window.attachEvent("onbeforeunload", function() {
  440. __flash_savedUnloadHandler = __flash_unloadHandler = function() {};
  441. });
  442. }
  443. flowplayer.engine.flash = function(player, root) {
  444. var conf = player.conf,
  445. video = player.video,
  446. callbackId,
  447. objectTag,
  448. api;
  449. var engine = {
  450. pick: function(sources) {
  451. if (flowplayer.support.flashVideo) {
  452. // always pick video/flash first
  453. var flash = $.grep(sources, function(source) { return source.type == 'flash'; })[0];
  454. if (flash) return flash;
  455. for (var i = 0, source; i < sources.length; i++) {
  456. source = sources[i];
  457. if (/mp4|flv/.test(source.type)) return source;
  458. }
  459. }
  460. },
  461. load: function(video) {
  462. var html5Tag = $("video", root),
  463. url = video.src.replace(/&amp;/g, '%26').replace(/&/g, '%26').replace(/=/g, '%3D'),
  464. is_absolute = /^https?:/.test(url);
  465. // html5 tag not needed (pause needed for firefox)
  466. if (html5Tag.length > 0 && flowplayer.support.video) html5Tag[0].pause();
  467. html5Tag.remove();
  468. // convert to absolute
  469. if (!is_absolute && !conf.rtmp) url = $("<a/>").attr("href", url)[0].href;
  470. if (api) {
  471. api.__play(url);
  472. } else {
  473. callbackId = "fp" + ("" + Math.random()).slice(3, 15);
  474. var opts = {
  475. hostname: conf.embedded ? conf.hostname : location.hostname,
  476. url: url,
  477. callback: "jQuery."+ callbackId
  478. };
  479. if (is_absolute) delete conf.rtmp;
  480. // optional conf
  481. $.each(['key', 'autoplay', 'preload', 'rtmp', 'loop', 'debug'], function(i, key) {
  482. if (conf[key]) opts[key] = conf[key];
  483. });
  484. objectTag = embed(conf.swf, opts);
  485. objectTag.prependTo(root);
  486. api = objectTag[0];
  487. // throw error if no loading occurs
  488. setTimeout(function() {
  489. try {
  490. if (!api.PercentLoaded()) {
  491. return root.trigger("error", [player, { code: 7, url: conf.swf }]);
  492. }
  493. } catch (e) {}
  494. }, 5000);
  495. // listen
  496. $[callbackId] = function(type, arg) {
  497. if (conf.debug && type != "status") console.log("--", type, arg);
  498. var event = $.Event(type);
  499. switch (type) {
  500. // RTMP sends a lot of finish events in vain
  501. // case "finish": if (conf.rtmp) return;
  502. case "ready": arg = $.extend(video, arg); break;
  503. case "click": event.flash = true; break;
  504. case "keydown": event.which = arg; break;
  505. case "seek": video.time = arg; break;
  506. case "buffered": video.buffered = true; break;
  507. case "status":
  508. player.trigger("progress", arg.time);
  509. if (arg.buffer <= video.bytes && !video.buffered) {
  510. video.buffer = arg.buffer / video.bytes * video.duration;
  511. player.trigger("buffer", video.buffer);
  512. } else if (video.buffered) player.trigger("buffered");
  513. break;
  514. }
  515. // add some delay to that player is truly ready after an event
  516. setTimeout(function() { player.trigger(event, arg); }, 1)
  517. };
  518. }
  519. },
  520. // not supported yet
  521. speed: $.noop,
  522. unload: function() {
  523. api && api.__unload && api.__unload();
  524. delete $[callbackId];
  525. $("object", root).remove();
  526. api = 0;
  527. }
  528. };
  529. $.each("pause,resume,seek,volume".split(","), function(i, name) {
  530. engine[name] = function(arg) {
  531. if (player.ready) {
  532. if (name == 'seek' && player.video.time && !player.paused) {
  533. player.trigger("beforeseek");
  534. }
  535. if (arg === undefined) {
  536. api["__" + name]();
  537. } else {
  538. api["__" + name](arg);
  539. }
  540. }
  541. };
  542. });
  543. var win = $(window),
  544. origH = root.height(),
  545. origW = root.width();
  546. // handle Flash object aspect ratio
  547. player.bind("ready fullscreen fullscreen-exit", function(e) {
  548. if (player.conf.flashfit || /full/.test(e.type)) {
  549. var fs = player.isFullscreen,
  550. truefs = fs && FS_SUPPORT,
  551. ie7 = !flowplayer.support.inlineBlock,
  552. screenW = fs ? (truefs ? screen.availWidth : win.width()) : origW,
  553. screenH = fs ? (truefs ? screen.availHeight : win.height()) : origH,
  554. // default values for fullscreen-exit without flashfit
  555. hmargin = truefs ? screen.width - screen.availWidth : 0,
  556. vmargin = truefs ? screen.height - screen.availHeight : 0,
  557. objwidth = ie7 ? origW : '',
  558. objheight = ie7 ? origH : '',
  559. aspectratio, dataratio;
  560. if (player.conf.flashfit || e.type === "fullscreen") {
  561. aspectratio = player.video.width / player.video.height,
  562. dataratio = player.video.height / player.video.width,
  563. objheight = Math.max(dataratio * screenW),
  564. objwidth = Math.max(aspectratio * screenH);
  565. objheight = objheight > screenH ? objwidth * dataratio : objheight;
  566. objheight = Math.min(Math.round(objheight), screenH);
  567. objwidth = objwidth > screenW ? objheight * aspectratio : objwidth;
  568. objwidth = Math.min(Math.round(objwidth), screenW);
  569. vmargin = Math.max(Math.round((screenH + vmargin - objheight) / 2), 0);
  570. hmargin = Math.max(Math.round((screenW + hmargin - objwidth) / 2), 0);
  571. }
  572. $("object", root).css({
  573. width: objwidth,
  574. height: objheight,
  575. marginTop: vmargin,
  576. marginLeft: hmargin
  577. });
  578. }
  579. });
  580. return engine;
  581. };
  582. var VIDEO = $('<video/>')[0];
  583. // HTML5 --> Flowplayer event
  584. var EVENTS = {
  585. // fired
  586. ended: 'finish',
  587. pause: 'pause',
  588. play: 'resume',
  589. progress: 'buffer',
  590. timeupdate: 'progress',
  591. volumechange: 'volume',
  592. ratechange: 'speed',
  593. seeking: 'beforeseek',
  594. seeked: 'seek',
  595. // abort: 'resume',
  596. // not fired
  597. loadeddata: 'ready',
  598. // loadedmetadata: 0,
  599. // canplay: 0,
  600. // error events
  601. // load: 0,
  602. // emptied: 0,
  603. // empty: 0,
  604. error: 'error',
  605. dataunavailable: 'error'
  606. };
  607. function round(val) {
  608. return Math.round(val * 100) / 100;
  609. }
  610. function getType(type) {
  611. return /mpegurl/i.test(type) ? "application/x-mpegurl" : "video/" + type;
  612. }
  613. function canPlay(type) {
  614. if (!/^(video|application)/.test(type))
  615. type = getType(type);
  616. return !!VIDEO.canPlayType(type).replace("no", '');
  617. }
  618. var videoTagCache;
  619. var createVideoTag = function(video) {
  620. if (videoTagCache) {
  621. return videoTagCache.attr({type: getType(video.type), src: video.src});
  622. }
  623. return (videoTagCache = $("<video/>", {
  624. src: video.src,
  625. type: getType(video.type),
  626. 'class': 'fp-engine',
  627. 'autoplay': 'autoplay',
  628. preload: 'none'
  629. }));
  630. }
  631. flowplayer.engine.html5 = function(player, root) {
  632. var videoTag = $("video", root),
  633. support = flowplayer.support,
  634. track = $("track", videoTag),
  635. conf = player.conf,
  636. self,
  637. timer,
  638. api;
  639. return self = {
  640. pick: function(sources) {
  641. if (support.video) {
  642. for (var i = 0, source; i < sources.length; i++) {
  643. if (canPlay(sources[i].type)) return sources[i];
  644. }
  645. }
  646. },
  647. load: function(video) {
  648. if (conf.splash && !api) {
  649. videoTag = createVideoTag(video).prependTo(root);
  650. if (track.length) videoTag.append(track.attr("default", ""));
  651. if (conf.loop) videoTag.attr("loop", "loop");
  652. api = videoTag[0];
  653. } else {
  654. api = videoTag[0];
  655. // change of clip
  656. if (player.video.src && video.src != player.video.src) {
  657. videoTag.attr("autoplay", "autoplay");
  658. api.src = video.src;
  659. // preload=none or no initial "loadeddata" event
  660. } else if (conf.preload == 'none' || !support.dataload) {
  661. if (support.zeropreload) {
  662. player.trigger("ready", video).trigger("pause").one("ready", function() {
  663. root.trigger("resume");
  664. });
  665. } else {
  666. player.one("ready", function() {
  667. root.trigger("pause");
  668. });
  669. }
  670. }
  671. }
  672. listen(api, $("source", videoTag).add(videoTag), video);
  673. // iPad (+others?) demands load()
  674. if (conf.preload != 'none' || !support.zeropreload || !support.dataload) api.load();
  675. if (conf.splash) api.load();
  676. },
  677. pause: function() {
  678. api.pause();
  679. },
  680. resume: function() {
  681. api.play();
  682. },
  683. speed: function(val) {
  684. api.playbackRate = val;
  685. },
  686. seek: function(time) {
  687. try {
  688. api.currentTime = time;
  689. } catch (ignored) {}
  690. },
  691. volume: function(level) {
  692. api.volume = level;
  693. },
  694. unload: function() {
  695. $("video", root).remove();
  696. if (!support.cachedVideoTag) videoTagCache = null;
  697. timer = clearInterval(timer);
  698. api = 0;
  699. }
  700. };
  701. function listen(api, sources, video) {
  702. // listen only once
  703. if (api.listeners && api.listeners.hasOwnProperty(root.data('fp-player_id'))) return;
  704. (api.listeners || (api.listeners = {}))[root.data('fp-player_id')] = true;
  705. sources.bind("error", function(e) {
  706. try {
  707. if (e.originalEvent && $(e.originalEvent.originalTarget).is('img')) return e.preventDefault();
  708. if (canPlay($(e.target).attr("type"))) {
  709. player.trigger("error", { code: 4 });
  710. }
  711. } catch (er) {
  712. // Most likely: https://bugzilla.mozilla.org/show_bug.cgi?id=208427
  713. }
  714. });
  715. $.each(EVENTS, function(type, flow) {
  716. api.addEventListener(type, function(e) {
  717. // safari hack for bad URL (10s before fails)
  718. if (flow == "progress" && e.srcElement && e.srcElement.readyState === 0) {
  719. setTimeout(function() {
  720. if (!player.video.duration) {
  721. flow = "error";
  722. player.trigger(flow, { code: 4 });
  723. }
  724. }, 10000);
  725. }
  726. if (conf.debug && !/progress/.test(flow)) console.log(type, "->", flow, e);
  727. // no events if player not ready
  728. if (!player.ready && !/ready|error/.test(flow) || !flow || !$("video", root).length) { return; }
  729. var event = $.Event(flow), arg;
  730. switch (flow) {
  731. case "ready":
  732. arg = $.extend(video, {
  733. duration: api.duration,
  734. width: api.videoWidth,
  735. height: api.videoHeight,
  736. url: api.currentSrc,
  737. src: api.currentSrc
  738. });
  739. try {
  740. arg.seekable = api.seekable && api.seekable.end(null);
  741. } catch (ignored) {}
  742. // buffer
  743. timer = timer || setInterval(function() {
  744. try {
  745. arg.buffer = api.buffered.end(null);
  746. } catch (ignored) {}
  747. if (arg.buffer) {
  748. if (arg.buffer <= arg.duration && !arg.buffered) {
  749. player.trigger("buffer", e);
  750. } else if (!arg.buffered) {
  751. arg.buffered = true;
  752. player.trigger("buffer", e).trigger("buffered", e);
  753. clearInterval(timer);
  754. timer = 0;
  755. }
  756. }
  757. }, 250);
  758. break;
  759. case "progress": case "seek":
  760. var dur = player.video.duration
  761. if (api.currentTime > 0) {
  762. arg = Math.max(api.currentTime, 0);
  763. if (dur && arg && arg >= dur) event.type = "finish";
  764. break;
  765. } else if (flow == 'progress') {
  766. return;
  767. }
  768. case "speed":
  769. arg = round(api.playbackRate);
  770. break;
  771. case "volume":
  772. arg = round(api.volume);
  773. break;
  774. case "error":
  775. try {
  776. arg = (e.srcElement || e.originalTarget).error;
  777. } catch (er) {
  778. // Most likely https://bugzilla.mozilla.org/show_bug.cgi?id=208427
  779. return;
  780. }
  781. }
  782. player.trigger(event, arg);
  783. }, false);
  784. });
  785. }
  786. };
  787. var TYPE_RE = /.(\w{3,4})$/i;
  788. function parseSource(el) {
  789. var src = el.attr("src"),
  790. type = el.attr("type") || "",
  791. suffix = src.split(TYPE_RE)[1];
  792. type = /mpegurl/.test(type) ? "mpegurl" : type.replace("video/", "");
  793. return { src: src, suffix: suffix || type, type: type || suffix };
  794. }
  795. /* Resolves video object from initial configuration and from load() method */
  796. function URLResolver(videoTag) {
  797. var self = this,
  798. sources = [];
  799. // initial sources
  800. $("source", videoTag).each(function() {
  801. sources.push(parseSource($(this)));
  802. });
  803. if (!sources.length) sources.push(parseSource(videoTag));
  804. self.initialSources = sources;
  805. self.resolve = function(video) {
  806. if (!video) return { sources: sources };
  807. if ($.isArray(video)) {
  808. video = { sources: $.map(video, function(el) {
  809. var type; $.each(el, function(key, value) { type = key; });
  810. el.type = type;
  811. el.src = el[type];
  812. delete el[type];
  813. return el;
  814. })};
  815. } else if (typeof video == 'string') {
  816. video = { src: video, sources: [] };
  817. $.each(sources, function(i, source) {
  818. if (source.type != 'flash') {
  819. video.sources.push({
  820. type: source.type,
  821. src: video.src.replace(TYPE_RE, "") + "." + source.suffix
  822. });
  823. }
  824. });
  825. }
  826. return video;
  827. };
  828. };
  829. /* A minimal jQuery Slider plugin with all goodies */
  830. // skip IE policies
  831. // document.ondragstart = function () { return false; };
  832. // execute function every <delay> ms
  833. $.throttle = function(fn, delay) {
  834. var locked;
  835. return function () {
  836. if (!locked) {
  837. fn.apply(this, arguments);
  838. locked = 1;
  839. setTimeout(function () { locked = 0; }, delay);
  840. }
  841. };
  842. };
  843. $.fn.slider2 = function() {
  844. var IS_IPAD = /iPad/.test(navigator.userAgent);
  845. return this.each(function() {
  846. var root = $(this),
  847. doc = $(document),
  848. progress = root.children(":last"),
  849. disabled,
  850. offset,
  851. width,
  852. height,
  853. vertical,
  854. size,
  855. maxValue,
  856. max,
  857. /* private */
  858. calc = function() {
  859. offset = root.offset();
  860. width = root.width();
  861. height = root.height();
  862. /* exit from fullscreen can mess this up.*/
  863. // vertical = height > width;
  864. size = vertical ? height : width;
  865. max = toDelta(maxValue);
  866. },
  867. fire = function(value) {
  868. if (!disabled && value != api.value && (!maxValue || value < maxValue)) {
  869. root.trigger("slide", [ value ]);
  870. api.value = value;
  871. }
  872. },
  873. mousemove = function(e) {
  874. var delta = vertical ? e.pageY - offset.top : e.pageX - offset.left;
  875. delta = Math.max(0, Math.min(max || size, delta));
  876. var value = delta / size;
  877. if (vertical) value = 1 - value;
  878. return move(value, 0, true);
  879. },
  880. move = function(value, speed) {
  881. if (speed === undefined) { speed = 0; }
  882. if (value > 1) value = 1;
  883. var to = (Math.round(value * 1000) / 10) + "%";
  884. if (!maxValue || value <= maxValue) {
  885. if (!IS_IPAD) progress.stop(); // stop() broken on iPad
  886. progress.animate(vertical ? { height: to } : { width: to }, speed, "linear");
  887. }
  888. return value;
  889. },
  890. toDelta = function(value) {
  891. return Math.max(0, Math.min(size, vertical ? (1 - value) * height : value * width));
  892. },
  893. /* public */
  894. api = {
  895. max: function(value) {
  896. maxValue = value;
  897. },
  898. disable: function(flag) {
  899. disabled = flag;
  900. },
  901. slide: function(value, speed, fireEvent) {
  902. calc();
  903. if (fireEvent) fire(value);
  904. move(value, speed);
  905. }
  906. };
  907. calc();
  908. // bound dragging into document
  909. root.data("api", api).bind("mousedown.sld", function(e) {
  910. e.preventDefault();
  911. if (!disabled) {
  912. // begin --> recalculate. allows dynamic resizing of the slider
  913. var delayedFire = $.throttle(fire, 100);
  914. calc();
  915. api.dragging = true;
  916. fire(mousemove(e));
  917. doc.bind("mousemove.sld", function(e) {
  918. e.preventDefault();
  919. delayedFire(mousemove(e));
  920. }).one("mouseup", function() {
  921. api.dragging = false;
  922. doc.unbind("mousemove.sld");
  923. });
  924. }
  925. });
  926. });
  927. };
  928. function zeropad(val) {
  929. val = parseInt(val, 10);
  930. return val >= 10 ? val : "0" + val;
  931. }
  932. // display seconds in hh:mm:ss format
  933. function format(sec) {
  934. sec = sec || 0;
  935. var h = Math.floor(sec / 3600),
  936. min = Math.floor(sec / 60);
  937. sec = sec - (min * 60);
  938. if (h >= 1) {
  939. min -= h * 60;
  940. return h + ":" + zeropad(min) + ":" + zeropad(sec);
  941. }
  942. return zeropad(min) + ":" + zeropad(sec);
  943. }
  944. flowplayer(function(api, root) {
  945. var conf = api.conf,
  946. support = flowplayer.support,
  947. hovertimer;
  948. root.addClass("flowplayer").append('\
  949. <div class="ratio"/>\
  950. <div class="ui">\
  951. <div class="waiting"><em/><em/><em/></div>\
  952. <a class="fullscreen"/>\
  953. <a class="unload"/>\
  954. <p class="speed"/>\
  955. <div class="controls">\
  956. <a class="play"></a>\
  957. <div class="timeline">\
  958. <div class="buffer"/>\
  959. <div class="progress"/>\
  960. </div>\
  961. <div class="volume">\
  962. <a class="mute"></a>\
  963. <div class="volumeslider">\
  964. <div class="volumelevel"/>\
  965. </div>\
  966. </div>\
  967. </div>\
  968. <div class="time">\
  969. <em class="elapsed">00:00</em>\
  970. <em class="remaining"/>\
  971. <em class="duration">00:00</em>\
  972. </div>\
  973. <div class="message"><h2/><p/></div>\
  974. </div>'.replace(/class="/g, 'class="fp-')
  975. );
  976. function find(klass) {
  977. return $(".fp-" + klass, root);
  978. }
  979. // widgets
  980. var progress = find("progress"),
  981. buffer = find("buffer"),
  982. elapsed = find("elapsed"),
  983. remaining = find("remaining"),
  984. waiting = find("waiting"),
  985. ratio = find("ratio"),
  986. speed = find("speed"),
  987. durationEl = find("duration"),
  988. origRatio = ratio.css("paddingTop"),
  989. // sliders
  990. timeline = find("timeline").slider2(),
  991. timelineApi = timeline.data("api"),
  992. volume = find("volume"),
  993. fullscreen = find("fullscreen"),
  994. volumeSlider = find("volumeslider").slider2(),
  995. volumeApi = volumeSlider.data("api"),
  996. noToggle = root.is(".fixed-controls, .no-toggle");
  997. // aspect ratio
  998. function setRatio(val) {
  999. if (!parseInt(origRatio, 10)) ratio.css("paddingTop", val * 100 + "%");
  1000. if (!support.inlineBlock) $("object", root).height(root.height());
  1001. }
  1002. function hover(flag) {
  1003. root.toggleClass("is-mouseover", flag).toggleClass("is-mouseout", !flag);
  1004. }
  1005. // loading...
  1006. if (!support.animation) waiting.html("<p>loading &hellip;</p>");
  1007. setRatio(conf.ratio);
  1008. // no fullscreen in IFRAME
  1009. try {
  1010. if (!conf.fullscreen) fullscreen.remove();
  1011. } catch (e) {
  1012. fullscreen.remove();
  1013. }
  1014. api.bind("ready", function() {
  1015. var duration = api.video.duration;
  1016. timelineApi.disable(!duration);
  1017. setRatio(api.video.videoHeight / api.video.videoWidth);
  1018. // initial time & volume
  1019. durationEl.add(remaining).html(format(duration));
  1020. // do we need additional space for showing hour
  1021. ((duration >= 3600) && root.addClass('is-long')) || root.removeClass('is-long');
  1022. volumeApi.slide(api.volumeLevel);
  1023. }).bind("unload", function() {
  1024. if (!origRatio) ratio.css("paddingTop", "");
  1025. // buffer
  1026. }).bind("buffer", function() {
  1027. var video = api.video,
  1028. max = video.buffer / video.duration;
  1029. if (!video.seekable) timelineApi.max(max);
  1030. if (max < 1) buffer.css("width", (max * 100) + "%");
  1031. else buffer.css({ width: '100%' });
  1032. }).bind("speed", function(e, api, val) {
  1033. speed.text(val + "x").addClass("fp-hilite");
  1034. setTimeout(function() { speed.removeClass("fp-hilite") }, 1000);
  1035. }).bind("buffered", function() {
  1036. buffer.css({ width: '100%' });
  1037. timelineApi.max(1);
  1038. // progress
  1039. }).bind("progress", function() {
  1040. var time = api.video.time,
  1041. duration = api.video.duration;
  1042. if (!timelineApi.dragging) {
  1043. timelineApi.slide(time / duration, api.seeking ? 0 : 250);
  1044. }
  1045. elapsed.html(format(time));
  1046. remaining.html("-" + format(duration - time));
  1047. }).bind("finish resume seek", function(e) {
  1048. root.toggleClass("is-finished", e.type == "finish");
  1049. }).bind("stop", function() {
  1050. elapsed.html(format(0));
  1051. timelineApi.slide(0, 100);
  1052. }).bind("finish", function() {
  1053. elapsed.html(format(api.video.duration));
  1054. timelineApi.slide(1, 100);
  1055. root.removeClass("is-seeking");
  1056. // misc
  1057. }).bind("beforeseek", function() {
  1058. progress.stop();
  1059. }).bind("volume", function() {
  1060. volumeApi.slide(api.volumeLevel);
  1061. }).bind("disable", function() {
  1062. var flag = api.disabled;
  1063. timelineApi.disable(flag);
  1064. volumeApi.disable(flag);
  1065. root.toggleClass("is-disabled", api.disabled);
  1066. }).bind("mute", function(e, api, flag) {
  1067. root.toggleClass("is-muted", flag);
  1068. }).bind("error", function(e, api, error) {
  1069. root.removeClass("is-loading").addClass("is-error");
  1070. if (error) {
  1071. error.message = conf.errors[error.code];
  1072. api.error = true;
  1073. var el = $(".fp-message", root);
  1074. $("h2", el).text(api.engine + ": " + error.message);
  1075. $("p", el).text(error.url || api.video.url || api.video.src || conf.errorUrls[error.code]);
  1076. root.unbind("mouseenter click").removeClass("is-mouseover");
  1077. }
  1078. // hover
  1079. }).bind("mouseenter mouseleave", function(e) {
  1080. if (noToggle) return;
  1081. var is_over = e.type == "mouseenter",
  1082. lastMove;
  1083. // is-mouseover/out
  1084. hover(is_over);
  1085. if (is_over) {
  1086. root.bind("pause.x mousemove.x volume.x", function() {
  1087. hover(true);
  1088. lastMove = new Date;
  1089. });
  1090. hovertimer = setInterval(function() {
  1091. if (new Date - lastMove > 5000) {
  1092. hover(false)
  1093. lastMove = new Date;
  1094. }
  1095. }, 100);
  1096. } else {
  1097. root.unbind(".x");
  1098. clearInterval(hovertimer);
  1099. }
  1100. // allow dragging over the player edge
  1101. }).bind("mouseleave", function() {
  1102. if (timelineApi.dragging || volumeApi.dragging) {
  1103. root.addClass("is-mouseover").removeClass("is-mouseout");
  1104. }
  1105. // click
  1106. }).bind("click.player", function(e) {
  1107. if ($(e.target).is(".fp-ui, .fp-engine") || e.flash) {
  1108. e.preventDefault();
  1109. return api.toggle();
  1110. }
  1111. });
  1112. // poster -> background image
  1113. if (conf.poster) root.css("backgroundImage", "url(" + conf.poster + ")");
  1114. var bc = root.css("backgroundColor"),
  1115. has_bg = root.css("backgroundImage") != "none" || bc && bc != "rgba(0, 0, 0, 0)" && bc != "transparent";
  1116. // is-poster class
  1117. if (has_bg && !conf.splash && !conf.autoplay) {
  1118. api.bind("ready stop", function() {
  1119. root.addClass("is-poster").one("ready progress", function() {
  1120. root.removeClass("is-poster");
  1121. });
  1122. });
  1123. }
  1124. // default background color if not present
  1125. if (!has_bg && !support.firstframe) {
  1126. root.css("backgroundColor", "#555");
  1127. }
  1128. $(".fp-toggle, .fp-play", root).click(api.toggle);
  1129. /* controlbar elements */
  1130. $.each(['mute', 'fullscreen', 'unload'], function(i, key) {
  1131. find(key).click(function() {
  1132. api[key]();
  1133. });
  1134. });
  1135. timeline.bind("slide", function(e, val) {
  1136. api.seeking = true;
  1137. api.seek(val * api.video.duration);
  1138. });
  1139. volumeSlider.bind("slide", function(e, val) {
  1140. api.volume(val);
  1141. });
  1142. // times
  1143. find("time").click(function(e) {
  1144. $(this).toggleClass("is-inverted");
  1145. });
  1146. hover(noToggle);
  1147. });
  1148. var focused,
  1149. focusedRoot,
  1150. IS_HELP = "is-help";
  1151. // keyboard. single global listener
  1152. $(document).bind("keydown.fp", function(e) {
  1153. var el = focused,
  1154. metaKeyPressed = e.ctrlKey || e.metaKey || e.altKey,
  1155. key = e.which,
  1156. conf = el && el.conf;
  1157. if (!el || !conf.keyboard || el.disabled) return;
  1158. // help dialog (shift key not truly required)
  1159. if ($.inArray(key, [63, 187, 191, 219]) != -1) {
  1160. focusedRoot.toggleClass(IS_HELP);
  1161. return false;
  1162. }
  1163. // close help / unload
  1164. if (key == 27 && focusedRoot.hasClass(IS_HELP)) {
  1165. focusedRoot.toggleClass(IS_HELP);
  1166. return false;
  1167. }
  1168. if (!metaKeyPressed && el.ready) {
  1169. e.preventDefault();
  1170. // slow motion / fast forward
  1171. if (e.shiftKey) {
  1172. if (key == 39) el.speed(true);
  1173. else if (key == 37) el.speed(false);
  1174. return;
  1175. }
  1176. // 1, 2, 3, 4 ..
  1177. if (key < 58 && key > 47) return el.seekTo(key - 48);
  1178. switch (key) {
  1179. case 38: case 75: el.volume(el.volumeLevel + 0.15); break; // volume up
  1180. case 40: case 74: el.volume(el.volumeLevel - 0.15); break; // volume down
  1181. case 39: case 76: el.seeking = true; el.seek(true); break; // forward
  1182. case 37: case 72: el.seeking = true; el.seek(false); break; // backward
  1183. case 190: el.seekTo(); break; // to last seek position
  1184. case 32: el.toggle(); break; // spacebar
  1185. case 70: conf.fullscreen && el.fullscreen(); break; // toggle fullscreen
  1186. case 77: el.mute(); break; // mute
  1187. case 27: el[el.isFullscreen ? "fullscreen" : "unload"](); break; // esc
  1188. }
  1189. }
  1190. });
  1191. flowplayer(function(api, root) {
  1192. // no keyboard configured
  1193. if (!api.conf.keyboard) return;
  1194. // hover
  1195. root.bind("mouseenter mouseleave", function(e) {
  1196. focused = !api.disabled && e.type == 'mouseenter' ? api : 0;
  1197. if (focused) focusedRoot = root;
  1198. });
  1199. // TODO: add to player-layout.html
  1200. root.append('\
  1201. <div class="fp-help">\
  1202. <a class="fp-close"></a>\
  1203. <div class="fp-help-section fp-help-basics">\
  1204. <p><em>space</em>play / pause</p>\
  1205. <p><em>esc</em>stop</p>\
  1206. <p><em>f</em>fullscreen</p>\
  1207. <p><em>shift</em> + <em>&#8592;</em><em>&#8594;</em>slower / faster <small>(latest Chrome and Safari)</small></p>\
  1208. </div>\
  1209. <div class="fp-help-section">\
  1210. <p><em>&#8593;</em><em>&#8595;</em>volume</p>\
  1211. <p><em>m</em>mute</p>\
  1212. </div>\
  1213. <div class="fp-help-section">\
  1214. <p><em>&#8592;</em><em>&#8594;</em>seek</p>\
  1215. <p><em>&nbsp;. </em>seek to previous\
  1216. </p><p><em>1</em><em>2</em>&hellip;<em>6</em> seek to 10%, 20%, &hellip;60% </p>\
  1217. </div>\
  1218. </div>\
  1219. ');
  1220. if (api.conf.tooltip) {
  1221. $(".fp-ui", root).attr("title", "Hit ? for help").on("mouseout.tip", function() {
  1222. $(this).removeAttr("title").off("mouseout.tip");
  1223. });
  1224. }
  1225. $(".fp-close", root).click(function() {
  1226. root.toggleClass(IS_HELP);
  1227. });
  1228. });
  1229. var VENDOR = $.browser.mozilla ? "moz": "webkit",
  1230. FS_ENTER = "fullscreen",
  1231. FS_EXIT = "fullscreen-exit",
  1232. FULL_PLAYER,
  1233. FS_SUPPORT = flowplayer.support.fullscreen;
  1234. // esc button
  1235. $(document).bind(VENDOR + "fullscreenchange", function(e) {
  1236. var el = $(document.webkitCurrentFullScreenElement || document.mozFullScreenElement);
  1237. if (el.length) {
  1238. FULL_PLAYER = el.trigger(FS_ENTER, [el]);
  1239. } else {
  1240. FULL_PLAYER.trigger(FS_EXIT, [FULL_PLAYER]);
  1241. }
  1242. });
  1243. flowplayer(function(player, root) {
  1244. if (!player.conf.fullscreen) return;
  1245. var win = $(window),
  1246. fsSeek = {pos: 0, play: false},
  1247. scrollTop;
  1248. player.isFullscreen = false;
  1249. player.fullscreen = function(flag) {
  1250. if (player.disabled) return;
  1251. if (flag === undefined) flag = !player.isFullscreen;
  1252. if (flag) scrollTop = win.scrollTop();
  1253. if (FS_SUPPORT) {
  1254. if (flag) {
  1255. root[0][VENDOR + 'RequestFullScreen'](
  1256. flowplayer.support.fullscreen_keyboard ? Element.ALLOW_KEYBOARD_INPUT : undefined
  1257. );
  1258. } else {
  1259. document[VENDOR + 'CancelFullScreen']();
  1260. }
  1261. } else {
  1262. if (player.engine === "flash" && player.conf.rtmp)
  1263. fsSeek = {pos: player.video.time, play: player.playing};
  1264. player.trigger(flag ? FS_ENTER : FS_EXIT, [player])
  1265. }
  1266. return player;
  1267. };
  1268. var lastClick;
  1269. root.bind("mousedown.fs", function() {
  1270. if (+new Date - lastClick < 150 && player.ready) player.fullscreen();
  1271. lastClick = +new Date;
  1272. });
  1273. player.bind(FS_ENTER, function(e) {
  1274. root.addClass("is-fullscreen");
  1275. player.isFullscreen = true;
  1276. }).bind(FS_EXIT, function(e) {
  1277. root.removeClass("is-fullscreen");
  1278. player.isFullscreen = false;
  1279. win.scrollTop(scrollTop);
  1280. }).bind("ready", function () {
  1281. if (fsSeek.pos && !isNaN(fsSeek.pos)) {
  1282. setTimeout(function () {
  1283. player.play(); // avoid hang in buffering state
  1284. player.seek(fsSeek.pos);
  1285. if (!fsSeek.play) {
  1286. setTimeout(function () {
  1287. player.pause();
  1288. }, 100);
  1289. }
  1290. fsSeek = {pos: 0, play: false};
  1291. }, 250);
  1292. }
  1293. });
  1294. });
  1295. flowplayer(function(player, root) {
  1296. var conf = $.extend({ active: 'is-active', advance: true, query: ".fp-playlist a" }, player.conf),
  1297. klass = conf.active;
  1298. // getters
  1299. function els() {
  1300. return $(conf.query, root);
  1301. }
  1302. function active() {
  1303. return $(conf.query + "." + klass, root);
  1304. }
  1305. player.play = function(i) {
  1306. if (i === undefined) player.resume();
  1307. else if (typeof i != 'number') player.load.apply(null, arguments);
  1308. else els().eq(i).click();
  1309. return player;
  1310. };
  1311. if (els().length) {
  1312. /* click -> play */
  1313. root.on("click", conf.query, function(e) {
  1314. var el = $(e.target).closest(conf.query);
  1315. el.is("." + klass) ? player.toggle() : player.load(el.attr("href"));
  1316. e.preventDefault();
  1317. });
  1318. // disable single clip looping
  1319. player.conf.loop = false;
  1320. // playlist wide cuepoint support
  1321. var has_cuepoints = els().filter("[data-cuepoints]").length;
  1322. // highlight
  1323. player.bind("load", function(e, api, video) {
  1324. // active
  1325. var prev = active().removeClass(klass),
  1326. el = $("a[href*='" + video.src.replace(TYPE_RE, "") + ".']", root).addClass(klass),
  1327. clips = els(),
  1328. index = clips.index(el),
  1329. is_last = index == clips.length - 1;
  1330. // index
  1331. root.removeClass("video" + clips.index(prev)).addClass("video" + index).toggleClass("last-video", is_last);
  1332. // video properties
  1333. video.index = index;
  1334. video.is_last = is_last;
  1335. // cuepoints
  1336. if (has_cuepoints) player.cuepoints = el.data("cuepoints");
  1337. // without namespace callback called only once. unknown rason.
  1338. }).bind("unload.pl", function() {
  1339. active().toggleClass(klass);
  1340. });
  1341. // api.next() / api.prev()
  1342. $.each(['next', 'prev'], function(i, key) {
  1343. player[key] = function(e) {
  1344. e && e.preventDefault();
  1345. // next (or previous) entry
  1346. var el = active()[key]();
  1347. // cycle
  1348. if (!el.length) el = els().filter(key == 'next' ? ':first' : ':last');;
  1349. el.click();
  1350. };
  1351. $(".fp-" + key, root).click(player[key]);
  1352. });
  1353. if (conf.advance) {
  1354. root.unbind("finish.pl").bind("finish.pl", function() {
  1355. // next clip is found or loop
  1356. if (active().next().length || conf.loop) {
  1357. player.next();
  1358. // stop to last clip, play button starts from 1:st clip
  1359. } else {
  1360. root.addClass("is-playing"); // show play button
  1361. player.one("resume", function() {
  1362. player.next();
  1363. return false;
  1364. });
  1365. }
  1366. });
  1367. }
  1368. }
  1369. });
  1370. var CUE_RE = / ?cue\d+ ?/;
  1371. flowplayer(function(player, root) {
  1372. var lastTime = 0;
  1373. player.cuepoints = player.conf.cuepoints || [];
  1374. function setClass(index) {
  1375. root[0].className = root[0].className.replace(CUE_RE, " ");
  1376. if (index >= 0) root.addClass("cue" + index);
  1377. }
  1378. player.bind("progress", function(e, api, time) {
  1379. // avoid throwing multiple times
  1380. if (lastTime && time - lastTime < 0.015) return lastTime = time;
  1381. lastTime = time;
  1382. var cues = player.cuepoints || [];
  1383. for (var i = 0, cue; i < cues.length; i++) {
  1384. cue = cues[i];
  1385. if (1 * cue) cue = { time: cue }
  1386. if (cue.time < 0) cue.time = player.video.duration + cue.time;
  1387. cue.index = i;
  1388. // progress_interval / 2 = 0.125
  1389. if (Math.abs(cue.time - time) < 0.125 * player.currentSpeed) {
  1390. setClass(i);
  1391. root.trigger("cuepoint", [player, cue]);
  1392. }
  1393. }
  1394. // no CSS class name
  1395. }).bind("unload seek", setClass);
  1396. if (player.conf.generate_cuepoints) {
  1397. player.bind("ready", function() {
  1398. var cues = player.cuepoints || [],
  1399. duration = player.video.duration,
  1400. timeline = $(".fp-timeline", root).css("overflow", "visible");
  1401. $.each(cues, function(i, cue) {
  1402. var time = cue.time || cue;
  1403. if (time < 0) time = duration + cue;
  1404. var el = $("<a/>").addClass("fp-cuepoint fp-cuepoint" + i)
  1405. .css("left", (time / duration * 100) + "%");
  1406. el.appendTo(timeline).mousedown(function() {
  1407. player.seek(time);
  1408. // preventDefault() doesn't work
  1409. return false;
  1410. });
  1411. });
  1412. });
  1413. }
  1414. });
  1415. flowplayer(function(player, root, engine) {
  1416. var track = $("track", root),
  1417. conf = player.conf;
  1418. if (flowplayer.support.subtitles) {
  1419. player.subtitles = track.length && track[0].track;
  1420. // use native when supported
  1421. if (conf.nativesubtitles && conf.engine == 'html5') return;
  1422. }
  1423. // avoid duplicate loads
  1424. track.remove();
  1425. 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})(.*)/;
  1426. function seconds(timecode) {
  1427. var els = timecode.split(':');
  1428. if (els.length == 2) els.unshift(0);
  1429. return els[0] * 60 * 60 + els[1] * 60 + parseFloat(els[2].replace(',','.'));
  1430. }
  1431. player.subtitles = [];
  1432. var url = track.attr("src");
  1433. if (!url) return;
  1434. $.get(url, function(txt) {
  1435. for (var i = 0, lines = txt.split("\n"), len = lines.length, entry = {}, title, timecode, text, cue; i < len; i++) {
  1436. timecode = TIMECODE_RE.exec(lines[i]);
  1437. if (timecode) {
  1438. // title
  1439. title = lines[i - 1];
  1440. // text
  1441. text = "<p>" + lines[++i] + "</p><br/>";
  1442. while ($.trim(lines[++i]) && i < lines.length) text += "<p>" + lines[i] + "</p><br/>";
  1443. // entry
  1444. entry = {
  1445. title: title,
  1446. startTime: seconds(timecode[1]),
  1447. endTime: seconds(timecode[2] || timecode[3]),
  1448. text: text
  1449. };
  1450. cue = { time: entry.startTime, subtitle: entry };
  1451. player.subtitles.push(entry);
  1452. player.cuepoints.push(cue);
  1453. player.cuepoints.push({ time: entry.endTime, subtitleEnd: title });
  1454. // initial cuepoint
  1455. if (entry.startTime === 0) {
  1456. player.trigger("cuepoint", cue);
  1457. }
  1458. }
  1459. }
  1460. }).fail(function() {
  1461. player.trigger("error", {code: 8, url: url });
  1462. return false;
  1463. });
  1464. var wrap = $("<div class='fp-subtitle'/>", root).appendTo(root),
  1465. currentPoint;
  1466. player.bind("cuepoint", function(e, api, cue) {
  1467. if (cue.subtitle) {
  1468. currentPoint = cue.index;
  1469. wrap.html(cue.subtitle.text).addClass("fp-active");
  1470. } else if (cue.subtitleEnd) {
  1471. wrap.removeClass("fp-active");
  1472. }
  1473. }).bind("seek", function(e, api, time) {
  1474. $.each(player.cuepoints || [], function(i, cue) {
  1475. var entry = cue.subtitle;
  1476. if (entry && currentPoint != cue.index) {
  1477. if (time >= cue.time && time <= entry.endTime) player.trigger("cuepoint", cue);
  1478. else wrap.removeClass("fp-active");
  1479. }
  1480. });
  1481. });
  1482. });
  1483. flowplayer(function(player, root) {
  1484. var id = player.conf.analytics, time = 0, last = 0;
  1485. if (id) {
  1486. // load Analytics script if needed
  1487. if (typeof _gat == 'undefined') $.getScript("http://www.google-analytics.com/ga.js");
  1488. function track(e) {
  1489. if (time && typeof _gat != 'undefined') {
  1490. var tracker = _gat._getTracker(id),
  1491. video = player.video;
  1492. tracker._setAllowLinker(true);
  1493. // http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html
  1494. tracker._trackEvent(
  1495. "Video / Seconds played",
  1496. player.engine + "/" + video.type,
  1497. root.attr("title") || video.src.split("/").slice(-1)[0].replace(TYPE_RE, ''),
  1498. Math.round(time / 1000)
  1499. );
  1500. time = 0;
  1501. }
  1502. }
  1503. player.bind("load unload", track).bind("progress", function() {
  1504. if (!player.seeking) {
  1505. time += last ? (+new Date - last) : 0;
  1506. last = +new Date;
  1507. }
  1508. }).bind("pause", function() {
  1509. last = 0;
  1510. });
  1511. $(window).unload(track);
  1512. }
  1513. });
  1514. if (flowplayer.support.touch) {
  1515. flowplayer(function(player, root) {
  1516. var isAndroid = /Android/.test(UA),
  1517. isSilk = /Silk/.test(UA);
  1518. // hide volume
  1519. if (!flowplayer.support.volume) {
  1520. root.addClass("no-volume no-mute");
  1521. }
  1522. // fake mouseover effect with click
  1523. root.one("touchstart", function() {
  1524. isAndroid && player.toggle();
  1525. }).bind("touchstart", function(e) {
  1526. if (player.playing && !root.hasClass("is-mouseover")) {
  1527. root.addClass("is-mouseover").removeClass("is-mouseout");
  1528. return false;
  1529. }
  1530. if (player.paused && root.hasClass("is-mouseout")) {
  1531. player.toggle();
  1532. }
  1533. });
  1534. // native fullscreen
  1535. if (player.conf.native_fullscreen && $.browser.webkit) {
  1536. player.fullscreen = function() {
  1537. $('video', root)[0].webkitEnterFullScreen();
  1538. }
  1539. }
  1540. // Android browser gives video.duration == 1 until second 'timeupdate' event
  1541. (isAndroid || isSilk) && player.bind("ready", function() {
  1542. var video = $('video', root);
  1543. video.one('canplay', function() {
  1544. video[0].play();
  1545. });
  1546. video[0].play();
  1547. player.bind("progress.dur", function() {
  1548. var duration = video[0].duration;
  1549. if (duration !== 1) {
  1550. player.video.duration = duration;
  1551. $(".fp-duration", root).html(format(duration));
  1552. player.unbind("progress.dur");
  1553. }
  1554. });
  1555. });
  1556. });
  1557. }
  1558. flowplayer(function(player, root) {
  1559. // no embedding
  1560. if (player.conf.embed === false) return;
  1561. var conf = player.conf,
  1562. ui = $(".fp-ui", root),
  1563. trigger = $("<a/>", { "class": "fp-embed", title: 'Copy to your site'}).appendTo(ui),
  1564. target = $("<div/>", { 'class': 'fp-embed-code'})
  1565. .append("<label>Paste this HTML code on your site to embed.</label><textarea/>").appendTo(ui),
  1566. area = $("textarea", target);
  1567. player.embedCode = function() {
  1568. var video = player.video,
  1569. width = video.width || root.width(),
  1570. height = video.height || root.height(),
  1571. el = $("<div/>", { 'class': 'flowplayer', css: { width: width, height: height }}),
  1572. tag = $("<video/>").appendTo(el);
  1573. // configuration
  1574. $.each(['origin', 'analytics', 'logo', 'key', 'rtmp'], function(i, key) {
  1575. if (conf[key]) el.attr("data-" + key, conf[key]);
  1576. });
  1577. // sources
  1578. $.each(video.sources, function(i, src) {
  1579. tag.append($("<source/>", { type: "video/" + src.type, src: src.src }));
  1580. });
  1581. var code = $("<foo/>", { src: "http://embed.flowplayer.org/5.3.2/embed.min.js" }).append(el);
  1582. return $("<p/>").append(code).html().replace(/<(\/?)foo/g, "<$1script");
  1583. };
  1584. root.fptip(".fp-embed", "is-embedding");
  1585. area.click(function() {
  1586. this.select();
  1587. });
  1588. trigger.click(function() {
  1589. area.text(player.embedCode());
  1590. area[0].focus();
  1591. area[0].select();
  1592. });
  1593. });
  1594. $.fn.fptip = function(trigger, active) {
  1595. return this.each(function() {
  1596. var root = $(this);
  1597. function close() {
  1598. root.removeClass(active);
  1599. $(document).unbind(".st");
  1600. }
  1601. $(trigger || "a", this).click(function(e) {
  1602. e.preventDefault();
  1603. root.toggleClass(active);
  1604. if (root.hasClass(active)) {
  1605. $(document).bind("keydown.st", function(e) {
  1606. if (e.which == 27) close();
  1607. // click:close
  1608. }).bind("click.st", function(e) {
  1609. if (!$(e.target).parents("." + active).length) close();
  1610. });
  1611. }
  1612. });
  1613. });
  1614. };
  1615. }(jQuery);
  1616. flowplayer(function(a,b){function j(a){var b=c("<a/>")[0];return b.href=a,b.hostname}var c=jQuery,d=a.conf,e=d.swf.indexOf("flowplayer.org")&&d.e&&d.origin,f=e?j(e):location.hostname,g=d.key;location.protocol=="file:"&&(f="localhost"),a.load.ed=1,d.hostname=f,d.origin=e||location.href,e&&b.addClass("is-embedded"),typeof g=="string"&&(g=g.split(/,\s*/));if(g&&typeof key_check=="function"&&key_check(g,f))d.logo&&b.append(c("<a>",{"class":"fp-logo",href:e,target:"_top"}).append(c("<img/>",{src:d.logo})));else{var h=c("<a/>",{href:"http://flowplayer.org",target:"_top"}).appendTo(b),i=c(".fp-controls",b);a.bind("pause resume finish unload",function(b){/pause|resume/.test(b.type)&&a.engine!="flash"?(h.show().css({position:"absolute",left:16,bottom:36,zIndex:99999,width:100,height:20,backgroundImage:"url("+[".png","logo","/",".org",".flowplayer","stream","//"].reverse().join("")+")"}),a.load.ed=h.is(":visible")):h.hide()})}});