mediaelementplayer.js 84 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164
  1. /*!
  2. * MediaElementPlayer
  3. * http://mediaelementjs.com/
  4. *
  5. * Creates a controller bar for HTML5 <video> add <audio> tags
  6. * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper)
  7. *
  8. * Copyright 2010-2013, John Dyer (http://j.hn/)
  9. * License: MIT
  10. *
  11. */
  12. if (typeof jQuery != 'undefined') {
  13. mejs.$ = jQuery;
  14. } else if (typeof ender != 'undefined') {
  15. mejs.$ = ender;
  16. }
  17. (function ($) {
  18. // default player values
  19. mejs.MepDefaults = {
  20. // url to poster (to fix iOS 3.x)
  21. poster: '',
  22. // When the video is ended, we can show the poster.
  23. showPosterWhenEnded: false,
  24. // default if the <video width> is not specified
  25. defaultVideoWidth: 480,
  26. // default if the <video height> is not specified
  27. defaultVideoHeight: 270,
  28. // if set, overrides <video width>
  29. videoWidth: -1,
  30. // if set, overrides <video height>
  31. videoHeight: -1,
  32. // default if the user doesn't specify
  33. defaultAudioWidth: 400,
  34. // default if the user doesn't specify
  35. defaultAudioHeight: 30,
  36. // default amount to move back when back key is pressed
  37. defaultSeekBackwardInterval: function(media) {
  38. return (media.duration * 0.05);
  39. },
  40. // default amount to move forward when forward key is pressed
  41. defaultSeekForwardInterval: function(media) {
  42. return (media.duration * 0.05);
  43. },
  44. // width of audio player
  45. audioWidth: -1,
  46. // height of audio player
  47. audioHeight: -1,
  48. // initial volume when the player starts (overrided by user cookie)
  49. startVolume: 0.8,
  50. // useful for <audio> player loops
  51. loop: false,
  52. // rewind to beginning when media ends
  53. autoRewind: true,
  54. // resize to media dimensions
  55. enableAutosize: true,
  56. // forces the hour marker (##:00:00)
  57. alwaysShowHours: false,
  58. // show framecount in timecode (##:00:00:00)
  59. showTimecodeFrameCount: false,
  60. // used when showTimecodeFrameCount is set to true
  61. framesPerSecond: 25,
  62. // automatically calculate the width of the progress bar based on the sizes of other elements
  63. autosizeProgress : true,
  64. // Hide controls when playing and mouse is not over the video
  65. alwaysShowControls: false,
  66. // Display the video control
  67. hideVideoControlsOnLoad: false,
  68. // Enable click video element to toggle play/pause
  69. clickToPlayPause: true,
  70. // force iPad's native controls
  71. iPadUseNativeControls: false,
  72. // force iPhone's native controls
  73. iPhoneUseNativeControls: false,
  74. // force Android's native controls
  75. AndroidUseNativeControls: false,
  76. // features to show
  77. features: ['playpause','current','progress','duration','tracks','volume','fullscreen'],
  78. // only for dynamic
  79. isVideo: true,
  80. // turns keyboard support on and off for this instance
  81. enableKeyboard: true,
  82. // whenthis player starts, it will pause other players
  83. pauseOtherPlayers: true,
  84. // array of keyboard actions such as play pause
  85. keyActions: [
  86. {
  87. keys: [
  88. 32, // SPACE
  89. 179 // GOOGLE play/pause button
  90. ],
  91. action: function(player, media) {
  92. if (media.paused || media.ended) {
  93. player.play();
  94. } else {
  95. player.pause();
  96. }
  97. }
  98. },
  99. {
  100. keys: [38], // UP
  101. action: function(player, media) {
  102. var newVolume = Math.min(media.volume + 0.1, 1);
  103. media.setVolume(newVolume);
  104. }
  105. },
  106. {
  107. keys: [40], // DOWN
  108. action: function(player, media) {
  109. var newVolume = Math.max(media.volume - 0.1, 0);
  110. media.setVolume(newVolume);
  111. }
  112. },
  113. {
  114. keys: [
  115. 37, // LEFT
  116. 227 // Google TV rewind
  117. ],
  118. action: function(player, media) {
  119. if (!isNaN(media.duration) && media.duration > 0) {
  120. if (player.isVideo) {
  121. player.showControls();
  122. player.startControlsTimer();
  123. }
  124. // 5%
  125. var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0);
  126. media.setCurrentTime(newTime);
  127. }
  128. }
  129. },
  130. {
  131. keys: [
  132. 39, // RIGHT
  133. 228 // Google TV forward
  134. ],
  135. action: function(player, media) {
  136. if (!isNaN(media.duration) && media.duration > 0) {
  137. if (player.isVideo) {
  138. player.showControls();
  139. player.startControlsTimer();
  140. }
  141. // 5%
  142. var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration);
  143. media.setCurrentTime(newTime);
  144. }
  145. }
  146. },
  147. {
  148. keys: [70], // f
  149. action: function(player, media) {
  150. if (typeof player.enterFullScreen != 'undefined') {
  151. if (player.isFullScreen) {
  152. player.exitFullScreen();
  153. } else {
  154. player.enterFullScreen();
  155. }
  156. }
  157. }
  158. }
  159. ]
  160. };
  161. mejs.mepIndex = 0;
  162. mejs.players = {};
  163. // wraps a MediaElement object in player controls
  164. mejs.MediaElementPlayer = function(node, o) {
  165. // enforce object, even without "new" (via John Resig)
  166. if ( !(this instanceof mejs.MediaElementPlayer) ) {
  167. return new mejs.MediaElementPlayer(node, o);
  168. }
  169. var t = this;
  170. // these will be reset after the MediaElement.success fires
  171. t.$media = t.$node = $(node);
  172. t.node = t.media = t.$media[0];
  173. // check for existing player
  174. if (typeof t.node.player != 'undefined') {
  175. return t.node.player;
  176. } else {
  177. // attach player to DOM node for reference
  178. t.node.player = t;
  179. }
  180. // try to get options from data-mejsoptions
  181. if (typeof o == 'undefined') {
  182. o = t.$node.data('mejsoptions');
  183. }
  184. // extend default options
  185. t.options = $.extend({},mejs.MepDefaults,o);
  186. // unique ID
  187. t.id = 'mep_' + mejs.mepIndex++;
  188. // add to player array (for focus events)
  189. mejs.players[t.id] = t;
  190. // start up
  191. t.init();
  192. return t;
  193. };
  194. // actual player
  195. mejs.MediaElementPlayer.prototype = {
  196. hasFocus: false,
  197. controlsAreVisible: true,
  198. init: function() {
  199. var
  200. t = this,
  201. mf = mejs.MediaFeatures,
  202. // options for MediaElement (shim)
  203. meOptions = $.extend(true, {}, t.options, {
  204. success: function(media, domNode) { t.meReady(media, domNode); },
  205. error: function(e) { t.handleError(e);}
  206. }),
  207. tagName = t.media.tagName.toLowerCase();
  208. t.isDynamic = (tagName !== 'audio' && tagName !== 'video');
  209. if (t.isDynamic) {
  210. // get video from src or href?
  211. t.isVideo = t.options.isVideo;
  212. } else {
  213. t.isVideo = (tagName !== 'audio' && t.options.isVideo);
  214. }
  215. // use native controls in iPad, iPhone, and Android
  216. if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) {
  217. // add controls and stop
  218. t.$media.attr('controls', 'controls');
  219. // attempt to fix iOS 3 bug
  220. //t.$media.removeAttr('poster');
  221. // no Issue found on iOS3 -ttroxell
  222. // override Apple's autoplay override for iPads
  223. if (mf.isiPad && t.media.getAttribute('autoplay') !== null) {
  224. t.play();
  225. }
  226. } else if (mf.isAndroid && t.options.AndroidUseNativeControls) {
  227. // leave default player
  228. } else {
  229. // DESKTOP: use MediaElementPlayer controls
  230. // remove native controls
  231. t.$media.removeAttr('controls');
  232. // build container
  233. t.container =
  234. $('<div id="' + t.id + '" class="mejs-container ' + (mejs.MediaFeatures.svg ? 'svg' : 'no-svg') + '">'+
  235. '<div class="mejs-inner">'+
  236. '<div class="mejs-mediaelement"></div>'+
  237. '<div class="mejs-layers"></div>'+
  238. '<div class="mejs-controls"></div>'+
  239. '<div class="mejs-clear"></div>'+
  240. '</div>' +
  241. '</div>')
  242. .addClass(t.$media[0].className)
  243. .insertBefore(t.$media);
  244. // add classes for user and content
  245. t.container.addClass(
  246. (mf.isAndroid ? 'mejs-android ' : '') +
  247. (mf.isiOS ? 'mejs-ios ' : '') +
  248. (mf.isiPad ? 'mejs-ipad ' : '') +
  249. (mf.isiPhone ? 'mejs-iphone ' : '') +
  250. (t.isVideo ? 'mejs-video ' : 'mejs-audio ')
  251. );
  252. // move the <video/video> tag into the right spot
  253. if (mf.isiOS) {
  254. // sadly, you can't move nodes in iOS, so we have to destroy and recreate it!
  255. var $newMedia = t.$media.clone();
  256. t.container.find('.mejs-mediaelement').append($newMedia);
  257. t.$media.remove();
  258. t.$node = t.$media = $newMedia;
  259. t.node = t.media = $newMedia[0]
  260. } else {
  261. // normal way of moving it into place (doesn't work on iOS)
  262. t.container.find('.mejs-mediaelement').append(t.$media);
  263. }
  264. // find parts
  265. t.controls = t.container.find('.mejs-controls');
  266. t.layers = t.container.find('.mejs-layers');
  267. // determine the size
  268. /* size priority:
  269. (1) videoWidth (forced),
  270. (2) style="width;height;"
  271. (3) width attribute,
  272. (4) defaultVideoWidth (for unspecified cases)
  273. */
  274. var tagType = (t.isVideo ? 'video' : 'audio'),
  275. capsTagName = tagType.substring(0,1).toUpperCase() + tagType.substring(1);
  276. if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) {
  277. t.width = t.options[tagType + 'Width'];
  278. } else if (t.media.style.width !== '' && t.media.style.width !== null) {
  279. t.width = t.media.style.width;
  280. } else if (t.media.getAttribute('width') !== null) {
  281. t.width = t.$media.attr('width');
  282. } else {
  283. t.width = t.options['default' + capsTagName + 'Width'];
  284. }
  285. if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) {
  286. t.height = t.options[tagType + 'Height'];
  287. } else if (t.media.style.height !== '' && t.media.style.height !== null) {
  288. t.height = t.media.style.height;
  289. } else if (t.$media[0].getAttribute('height') !== null) {
  290. t.height = t.$media.attr('height');
  291. } else {
  292. t.height = t.options['default' + capsTagName + 'Height'];
  293. }
  294. // set the size, while we wait for the plugins to load below
  295. t.setPlayerSize(t.width, t.height);
  296. // create MediaElementShim
  297. meOptions.pluginWidth = t.width;
  298. meOptions.pluginHeight = t.height;
  299. }
  300. // create MediaElement shim
  301. mejs.MediaElement(t.$media[0], meOptions);
  302. if (typeof(t.container) != 'undefined' && t.controlsAreVisible){
  303. // controls are shown when loaded
  304. t.container.trigger('controlsshown');
  305. }
  306. },
  307. showControls: function(doAnimation) {
  308. var t = this;
  309. doAnimation = typeof doAnimation == 'undefined' || doAnimation;
  310. if (t.controlsAreVisible)
  311. return;
  312. if (doAnimation) {
  313. t.controls
  314. .css('visibility','visible')
  315. .stop(true, true).fadeIn(200, function() {
  316. t.controlsAreVisible = true;
  317. t.container.trigger('controlsshown');
  318. });
  319. // any additional controls people might add and want to hide
  320. t.container.find('.mejs-control')
  321. .css('visibility','visible')
  322. .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;});
  323. } else {
  324. t.controls
  325. .css('visibility','visible')
  326. .css('display','block');
  327. // any additional controls people might add and want to hide
  328. t.container.find('.mejs-control')
  329. .css('visibility','visible')
  330. .css('display','block');
  331. t.controlsAreVisible = true;
  332. t.container.trigger('controlsshown');
  333. }
  334. t.setControlsSize();
  335. },
  336. hideControls: function(doAnimation) {
  337. var t = this;
  338. doAnimation = typeof doAnimation == 'undefined' || doAnimation;
  339. if (!t.controlsAreVisible || t.options.alwaysShowControls)
  340. return;
  341. if (doAnimation) {
  342. // fade out main controls
  343. t.controls.stop(true, true).fadeOut(200, function() {
  344. $(this)
  345. .css('visibility','hidden')
  346. .css('display','block');
  347. t.controlsAreVisible = false;
  348. t.container.trigger('controlshidden');
  349. });
  350. // any additional controls people might add and want to hide
  351. t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() {
  352. $(this)
  353. .css('visibility','hidden')
  354. .css('display','block');
  355. });
  356. } else {
  357. // hide main controls
  358. t.controls
  359. .css('visibility','hidden')
  360. .css('display','block');
  361. // hide others
  362. t.container.find('.mejs-control')
  363. .css('visibility','hidden')
  364. .css('display','block');
  365. t.controlsAreVisible = false;
  366. t.container.trigger('controlshidden');
  367. }
  368. },
  369. controlsTimer: null,
  370. startControlsTimer: function(timeout) {
  371. var t = this;
  372. timeout = typeof timeout != 'undefined' ? timeout : 1500;
  373. t.killControlsTimer('start');
  374. t.controlsTimer = setTimeout(function() {
  375. //
  376. t.hideControls();
  377. t.killControlsTimer('hide');
  378. }, timeout);
  379. },
  380. killControlsTimer: function(src) {
  381. var t = this;
  382. if (t.controlsTimer !== null) {
  383. clearTimeout(t.controlsTimer);
  384. delete t.controlsTimer;
  385. t.controlsTimer = null;
  386. }
  387. },
  388. controlsEnabled: true,
  389. disableControls: function() {
  390. var t= this;
  391. t.killControlsTimer();
  392. t.hideControls(false);
  393. this.controlsEnabled = false;
  394. },
  395. enableControls: function() {
  396. var t= this;
  397. t.showControls(false);
  398. t.controlsEnabled = true;
  399. },
  400. // Sets up all controls and events
  401. meReady: function(media, domNode) {
  402. var t = this,
  403. mf = mejs.MediaFeatures,
  404. autoplayAttr = domNode.getAttribute('autoplay'),
  405. autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'),
  406. featureIndex,
  407. feature;
  408. // make sure it can't create itself again if a plugin reloads
  409. if (t.created) {
  410. return;
  411. } else {
  412. t.created = true;
  413. }
  414. t.media = media;
  415. t.domNode = domNode;
  416. if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) {
  417. // two built in features
  418. t.buildposter(t, t.controls, t.layers, t.media);
  419. t.buildkeyboard(t, t.controls, t.layers, t.media);
  420. t.buildoverlays(t, t.controls, t.layers, t.media);
  421. // grab for use by features
  422. t.findTracks();
  423. // add user-defined features/controls
  424. for (featureIndex in t.options.features) {
  425. feature = t.options.features[featureIndex];
  426. if (t['build' + feature]) {
  427. try {
  428. t['build' + feature](t, t.controls, t.layers, t.media);
  429. } catch (e) {
  430. // TODO: report control error
  431. //throw e;
  432. //
  433. //
  434. }
  435. }
  436. }
  437. t.container.trigger('controlsready');
  438. // reset all layers and controls
  439. t.setPlayerSize(t.width, t.height);
  440. t.setControlsSize();
  441. // controls fade
  442. if (t.isVideo) {
  443. if (mejs.MediaFeatures.hasTouch) {
  444. // for touch devices (iOS, Android)
  445. // show/hide without animation on touch
  446. t.$media.bind('touchstart', function() {
  447. // toggle controls
  448. if (t.controlsAreVisible) {
  449. t.hideControls(false);
  450. } else {
  451. if (t.controlsEnabled) {
  452. t.showControls(false);
  453. }
  454. }
  455. });
  456. } else {
  457. // create callback here since it needs access to current
  458. // MediaElement object
  459. mejs.MediaElementPlayer.prototype.clickToPlayPauseCallback = function() {
  460. //
  461. if (t.options.clickToPlayPause) {
  462. if (t.media.paused) {
  463. t.play();
  464. } else {
  465. t.pause();
  466. }
  467. }
  468. };
  469. // click to play/pause
  470. t.media.addEventListener('click', t.clickToPlayPauseCallback, false);
  471. // show/hide controls
  472. t.container
  473. .bind('mouseenter mouseover', function () {
  474. if (t.controlsEnabled) {
  475. if (!t.options.alwaysShowControls) {
  476. t.killControlsTimer('enter');
  477. t.showControls();
  478. t.startControlsTimer(2500);
  479. }
  480. }
  481. })
  482. .bind('mousemove', function() {
  483. if (t.controlsEnabled) {
  484. if (!t.controlsAreVisible) {
  485. t.showControls();
  486. }
  487. //t.killControlsTimer('move');
  488. if (!t.options.alwaysShowControls) {
  489. t.startControlsTimer(2500);
  490. }
  491. }
  492. })
  493. .bind('mouseleave', function () {
  494. if (t.controlsEnabled) {
  495. if (!t.media.paused && !t.options.alwaysShowControls) {
  496. t.startControlsTimer(1000);
  497. }
  498. }
  499. });
  500. }
  501. if(t.options.hideVideoControlsOnLoad) {
  502. t.hideControls(false);
  503. }
  504. // check for autoplay
  505. if (autoplay && !t.options.alwaysShowControls) {
  506. t.hideControls();
  507. }
  508. // resizer
  509. if (t.options.enableAutosize) {
  510. t.media.addEventListener('loadedmetadata', function(e) {
  511. // if the <video height> was not set and the options.videoHeight was not set
  512. // then resize to the real dimensions
  513. if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) {
  514. t.setPlayerSize(e.target.videoWidth, e.target.videoHeight);
  515. t.setControlsSize();
  516. t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight);
  517. }
  518. }, false);
  519. }
  520. }
  521. // EVENTS
  522. // FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them)
  523. media.addEventListener('play', function() {
  524. var playerIndex;
  525. // go through all other players
  526. for (playerIndex in mejs.players) {
  527. var p = mejs.players[playerIndex];
  528. if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) {
  529. p.pause();
  530. }
  531. p.hasFocus = false;
  532. }
  533. t.hasFocus = true;
  534. },false);
  535. // ended for all
  536. t.media.addEventListener('ended', function (e) {
  537. if(t.options.autoRewind) {
  538. try{
  539. t.media.setCurrentTime(0);
  540. } catch (exp) {
  541. }
  542. }
  543. t.media.pause();
  544. if (t.setProgressRail) {
  545. t.setProgressRail();
  546. }
  547. if (t.setCurrentRail) {
  548. t.setCurrentRail();
  549. }
  550. if (t.options.loop) {
  551. t.play();
  552. } else if (!t.options.alwaysShowControls && t.controlsEnabled) {
  553. t.showControls();
  554. }
  555. }, false);
  556. // resize on the first play
  557. t.media.addEventListener('loadedmetadata', function(e) {
  558. if (t.updateDuration) {
  559. t.updateDuration();
  560. }
  561. if (t.updateCurrent) {
  562. t.updateCurrent();
  563. }
  564. if (!t.isFullScreen) {
  565. t.setPlayerSize(t.width, t.height);
  566. t.setControlsSize();
  567. }
  568. }, false);
  569. // webkit has trouble doing this without a delay
  570. setTimeout(function () {
  571. t.setPlayerSize(t.width, t.height);
  572. t.setControlsSize();
  573. }, 50);
  574. // adjust controls whenever window sizes (used to be in fullscreen only)
  575. t.globalBind('resize', function() {
  576. // don't resize for fullscreen mode
  577. if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) {
  578. t.setPlayerSize(t.width, t.height);
  579. }
  580. // always adjust controls
  581. t.setControlsSize();
  582. });
  583. // TEMP: needs to be moved somewhere else
  584. if (t.media.pluginType == 'youtube') {
  585. t.container.find('.mejs-overlay-play').hide();
  586. }
  587. }
  588. // force autoplay for HTML5
  589. if (autoplay && media.pluginType == 'native') {
  590. t.play();
  591. }
  592. if (t.options.success) {
  593. if (typeof t.options.success == 'string') {
  594. window[t.options.success](t.media, t.domNode, t);
  595. } else {
  596. t.options.success(t.media, t.domNode, t);
  597. }
  598. }
  599. },
  600. handleError: function(e) {
  601. var t = this;
  602. t.controls.hide();
  603. // Tell user that the file cannot be played
  604. if (t.options.error) {
  605. t.options.error(e);
  606. }
  607. },
  608. setPlayerSize: function(width,height) {
  609. var t = this;
  610. if (typeof width != 'undefined') {
  611. t.width = width;
  612. }
  613. if (typeof height != 'undefined') {
  614. t.height = height;
  615. }
  616. // detect 100% mode - use currentStyle for IE since css() doesn't return percentages
  617. if (t.height.toString().indexOf('%') > 0 || t.$node.css('max-width') === '100%' || parseInt(t.$node.css('max-width').replace(/px/,''), 10) / t.$node.offsetParent().width() === 1 || (t.$node[0].currentStyle && t.$node[0].currentStyle.maxWidth === '100%')) {
  618. // do we have the native dimensions yet?
  619. var
  620. nativeWidth = t.isVideo ? ((t.media.videoWidth && t.media.videoWidth > 0) ? t.media.videoWidth : t.options.defaultVideoWidth) : t.options.defaultAudioWidth,
  621. nativeHeight = t.isVideo ? ((t.media.videoHeight && t.media.videoHeight > 0) ? t.media.videoHeight : t.options.defaultVideoHeight) : t.options.defaultAudioHeight,
  622. parentWidth = t.container.parent().closest(':visible').width(),
  623. newHeight = t.isVideo || !t.options.autosizeProgress ? parseInt(parentWidth * nativeHeight/nativeWidth, 10) : nativeHeight;
  624. if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) {
  625. parentWidth = $(window).width();
  626. newHeight = $(window).height();
  627. }
  628. if ( newHeight != 0 && parentWidth != 0 ) {
  629. // set outer container size
  630. t.container
  631. .width(parentWidth)
  632. .height(newHeight);
  633. // set native <video> or <audio> and shims
  634. t.$media.add(t.container.find('.mejs-shim'))
  635. .width('100%')
  636. .height('100%');
  637. // if shim is ready, send the size to the embeded plugin
  638. if (t.isVideo) {
  639. if (t.media.setVideoSize) {
  640. t.media.setVideoSize(parentWidth, newHeight);
  641. }
  642. }
  643. // set the layers
  644. t.layers.children('.mejs-layer')
  645. .width('100%')
  646. .height('100%');
  647. }
  648. } else {
  649. t.container
  650. .width(t.width)
  651. .height(t.height);
  652. t.layers.children('.mejs-layer')
  653. .width(t.width)
  654. .height(t.height);
  655. }
  656. // special case for big play button so it doesn't go over the controls area
  657. var playLayer = t.layers.find('.mejs-overlay-play'),
  658. playButton = playLayer.find('.mejs-overlay-button');
  659. playLayer.height(t.container.height() - t.controls.height());
  660. playButton.css('margin-top', '-' + (playButton.height()/2 - t.controls.height()/2).toString() + 'px' );
  661. },
  662. setControlsSize: function() {
  663. var t = this,
  664. usedWidth = 0,
  665. railWidth = 0,
  666. rail = t.controls.find('.mejs-time-rail'),
  667. total = t.controls.find('.mejs-time-total'),
  668. current = t.controls.find('.mejs-time-current'),
  669. loaded = t.controls.find('.mejs-time-loaded'),
  670. others = rail.siblings();
  671. // allow the size to come from custom CSS
  672. if (t.options && !t.options.autosizeProgress) {
  673. // Also, frontends devs can be more flexible
  674. // due the opportunity of absolute positioning.
  675. railWidth = parseInt(rail.css('width'));
  676. }
  677. // attempt to autosize
  678. if (railWidth === 0 || !railWidth) {
  679. // find the size of all the other controls besides the rail
  680. others.each(function() {
  681. var $this = $(this);
  682. if ($this.css('position') != 'absolute' && $this.is(':visible')) {
  683. usedWidth += $(this).outerWidth(true);
  684. }
  685. });
  686. // fit the rail into the remaining space
  687. railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width());
  688. }
  689. // outer area
  690. rail.width(railWidth);
  691. // dark space
  692. total.width(railWidth - (total.outerWidth(true) - total.width()));
  693. if (t.setProgressRail)
  694. t.setProgressRail();
  695. if (t.setCurrentRail)
  696. t.setCurrentRail();
  697. },
  698. buildposter: function(player, controls, layers, media) {
  699. var t = this,
  700. poster =
  701. $('<div class="mejs-poster mejs-layer">' +
  702. '</div>')
  703. .appendTo(layers),
  704. posterUrl = player.$media.attr('poster');
  705. // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster)
  706. if (player.options.poster !== '') {
  707. posterUrl = player.options.poster;
  708. }
  709. // second, try the real poster
  710. if (posterUrl !== '' && posterUrl != null) {
  711. t.setPoster(posterUrl);
  712. } else {
  713. poster.hide();
  714. }
  715. media.addEventListener('play',function() {
  716. poster.hide();
  717. }, false);
  718. if(player.options.showPosterWhenEnded && player.options.autoRewind){
  719. media.addEventListener('ended',function() {
  720. poster.show();
  721. }, false);
  722. }
  723. },
  724. setPoster: function(url) {
  725. var t = this,
  726. posterDiv = t.container.find('.mejs-poster'),
  727. posterImg = posterDiv.find('img');
  728. if (posterImg.length == 0) {
  729. posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv);
  730. }
  731. posterImg.attr('src', url);
  732. posterDiv.css({'background-image' : 'url(' + url + ')'});
  733. },
  734. buildoverlays: function(player, controls, layers, media) {
  735. var t = this;
  736. if (!player.isVideo)
  737. return;
  738. var
  739. loading =
  740. $('<div class="mejs-overlay mejs-layer">'+
  741. '<div class="mejs-overlay-loading"><span></span></div>'+
  742. '</div>')
  743. .hide() // start out hidden
  744. .appendTo(layers),
  745. error =
  746. $('<div class="mejs-overlay mejs-layer">'+
  747. '<div class="mejs-overlay-error"></div>'+
  748. '</div>')
  749. .hide() // start out hidden
  750. .appendTo(layers),
  751. // this needs to come last so it's on top
  752. bigPlay =
  753. $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+
  754. '<div class="mejs-overlay-button"></div>'+
  755. '</div>')
  756. .appendTo(layers)
  757. .bind('click touchstart', function() {
  758. if (t.options.clickToPlayPause) {
  759. if (media.paused) {
  760. t.play();
  761. }
  762. }
  763. });
  764. /*
  765. if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) {
  766. bigPlay.remove();
  767. loading.remove();
  768. }
  769. */
  770. // show/hide big play button
  771. media.addEventListener('play',function() {
  772. bigPlay.hide();
  773. loading.hide();
  774. controls.find('.mejs-time-buffering').hide();
  775. error.hide();
  776. }, false);
  777. media.addEventListener('playing', function() {
  778. bigPlay.hide();
  779. loading.hide();
  780. controls.find('.mejs-time-buffering').hide();
  781. error.hide();
  782. }, false);
  783. media.addEventListener('seeking', function() {
  784. loading.show();
  785. controls.find('.mejs-time-buffering').show();
  786. }, false);
  787. media.addEventListener('seeked', function() {
  788. loading.hide();
  789. controls.find('.mejs-time-buffering').hide();
  790. }, false);
  791. media.addEventListener('pause',function() {
  792. if (!mejs.MediaFeatures.isiPhone) {
  793. bigPlay.show();
  794. }
  795. }, false);
  796. media.addEventListener('waiting', function() {
  797. loading.show();
  798. controls.find('.mejs-time-buffering').show();
  799. }, false);
  800. // show/hide loading
  801. media.addEventListener('loadeddata',function() {
  802. // for some reason Chrome is firing this event
  803. //if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none')
  804. // return;
  805. loading.show();
  806. controls.find('.mejs-time-buffering').show();
  807. }, false);
  808. media.addEventListener('canplay',function() {
  809. loading.hide();
  810. controls.find('.mejs-time-buffering').hide();
  811. }, false);
  812. // error handling
  813. media.addEventListener('error',function() {
  814. loading.hide();
  815. controls.find('.mejs-time-buffering').hide();
  816. error.show();
  817. error.find('mejs-overlay-error').html("Error loading this resource");
  818. }, false);
  819. },
  820. buildkeyboard: function(player, controls, layers, media) {
  821. var t = this;
  822. // listen for key presses
  823. t.globalBind('keydown', function(e) {
  824. if (player.hasFocus && player.options.enableKeyboard) {
  825. // find a matching key
  826. for (var i=0, il=player.options.keyActions.length; i<il; i++) {
  827. var keyAction = player.options.keyActions[i];
  828. for (var j=0, jl=keyAction.keys.length; j<jl; j++) {
  829. if (e.keyCode == keyAction.keys[j]) {
  830. e.preventDefault();
  831. keyAction.action(player, media, e.keyCode);
  832. return false;
  833. }
  834. }
  835. }
  836. }
  837. return true;
  838. });
  839. // check if someone clicked outside a player region, then kill its focus
  840. t.globalBind('click', function(event) {
  841. if ($(event.target).closest('.mejs-container').length == 0) {
  842. player.hasFocus = false;
  843. }
  844. });
  845. },
  846. findTracks: function() {
  847. var t = this,
  848. tracktags = t.$media.find('track');
  849. // store for use by plugins
  850. t.tracks = [];
  851. tracktags.each(function(index, track) {
  852. track = $(track);
  853. t.tracks.push({
  854. srclang: (track.attr('srclang')) ? track.attr('srclang').toLowerCase() : '',
  855. src: track.attr('src'),
  856. kind: track.attr('kind'),
  857. label: track.attr('label') || '',
  858. entries: [],
  859. isLoaded: false
  860. });
  861. });
  862. },
  863. changeSkin: function(className) {
  864. this.container[0].className = 'mejs-container ' + className;
  865. this.setPlayerSize(this.width, this.height);
  866. this.setControlsSize();
  867. },
  868. play: function() {
  869. this.load();
  870. this.media.play();
  871. },
  872. pause: function() {
  873. try {
  874. this.media.pause();
  875. } catch (e) {}
  876. },
  877. load: function() {
  878. if (!this.isLoaded) {
  879. this.media.load();
  880. }
  881. this.isLoaded = true;
  882. },
  883. setMuted: function(muted) {
  884. this.media.setMuted(muted);
  885. },
  886. setCurrentTime: function(time) {
  887. this.media.setCurrentTime(time);
  888. },
  889. getCurrentTime: function() {
  890. return this.media.currentTime;
  891. },
  892. setVolume: function(volume) {
  893. this.media.setVolume(volume);
  894. },
  895. getVolume: function() {
  896. return this.media.volume;
  897. },
  898. setSrc: function(src) {
  899. this.media.setSrc(src);
  900. },
  901. remove: function() {
  902. var t = this, featureIndex, feature;
  903. // invoke features cleanup
  904. for (featureIndex in t.options.features) {
  905. feature = t.options.features[featureIndex];
  906. if (t['clean' + feature]) {
  907. try {
  908. t['clean' + feature](t);
  909. } catch (e) {
  910. // TODO: report control error
  911. //throw e;
  912. //
  913. //
  914. }
  915. }
  916. }
  917. // grab video and put it back in place
  918. if (!t.isDynamic) {
  919. t.$media.prop('controls', true);
  920. // detach events from the video
  921. // TODO: detach event listeners better than this;
  922. // also detach ONLY the events attached by this plugin!
  923. t.$node.clone().show().insertBefore(t.container);
  924. t.$node.remove();
  925. } else {
  926. t.$node.insertBefore(t.container);
  927. }
  928. if (t.media.pluginType !== 'native') {
  929. t.media.remove();
  930. }
  931. // Remove the player from the mejs.players object so that pauseOtherPlayers doesn't blow up when trying to pause a non existance flash api.
  932. delete mejs.players[t.id];
  933. t.container.remove();
  934. t.globalUnbind();
  935. delete t.node.player;
  936. }
  937. };
  938. (function(){
  939. var rwindow = /^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/;
  940. function splitEvents(events, id) {
  941. // add player ID as an event namespace so it's easier to unbind them all later
  942. var ret = {d: [], w: []};
  943. $.each((events || '').split(' '), function(k, v){
  944. var eventname = v + '.' + id;
  945. if (eventname.indexOf('.') === 0) {
  946. ret.d.push(eventname);
  947. ret.w.push(eventname);
  948. }
  949. else {
  950. ret[rwindow.test(v) ? 'w' : 'd'].push(eventname);
  951. }
  952. });
  953. ret.d = ret.d.join(' ');
  954. ret.w = ret.w.join(' ');
  955. return ret;
  956. }
  957. mejs.MediaElementPlayer.prototype.globalBind = function(events, data, callback) {
  958. var t = this;
  959. events = splitEvents(events, t.id);
  960. if (events.d) $(document).bind(events.d, data, callback);
  961. if (events.w) $(window).bind(events.w, data, callback);
  962. };
  963. mejs.MediaElementPlayer.prototype.globalUnbind = function(events, callback) {
  964. var t = this;
  965. events = splitEvents(events, t.id);
  966. if (events.d) $(document).unbind(events.d, callback);
  967. if (events.w) $(window).unbind(events.w, callback);
  968. };
  969. })();
  970. // turn into jQuery plugin
  971. if (typeof jQuery != 'undefined') {
  972. jQuery.fn.mediaelementplayer = function (options) {
  973. if (options === false) {
  974. this.each(function () {
  975. var player = jQuery(this).data('mediaelementplayer');
  976. if (player) {
  977. player.remove();
  978. }
  979. jQuery(this).removeData('mediaelementplayer');
  980. });
  981. }
  982. else {
  983. this.each(function () {
  984. jQuery(this).data('mediaelementplayer', new mejs.MediaElementPlayer(this, options));
  985. });
  986. }
  987. return this;
  988. };
  989. }
  990. $(document).ready(function() {
  991. // auto enable using JSON attribute
  992. $('.mejs-player').mediaelementplayer();
  993. });
  994. // push out to window
  995. window.MediaElementPlayer = mejs.MediaElementPlayer;
  996. })(mejs.$);
  997. (function($) {
  998. $.extend(mejs.MepDefaults, {
  999. playpauseText: mejs.i18n.t('Play/Pause')
  1000. });
  1001. // PLAY/pause BUTTON
  1002. $.extend(MediaElementPlayer.prototype, {
  1003. buildplaypause: function(player, controls, layers, media) {
  1004. var
  1005. t = this,
  1006. play =
  1007. $('<div class="mejs-button mejs-playpause-button mejs-play" >' +
  1008. '<button type="button" aria-controls="' + t.id + '" title="' + t.options.playpauseText + '" aria-label="' + t.options.playpauseText + '"></button>' +
  1009. '</div>')
  1010. .appendTo(controls)
  1011. .click(function(e) {
  1012. e.preventDefault();
  1013. if (media.paused) {
  1014. media.play();
  1015. } else {
  1016. media.pause();
  1017. }
  1018. return false;
  1019. });
  1020. media.addEventListener('play',function() {
  1021. play.removeClass('mejs-play').addClass('mejs-pause');
  1022. }, false);
  1023. media.addEventListener('playing',function() {
  1024. play.removeClass('mejs-play').addClass('mejs-pause');
  1025. }, false);
  1026. media.addEventListener('pause',function() {
  1027. play.removeClass('mejs-pause').addClass('mejs-play');
  1028. }, false);
  1029. media.addEventListener('paused',function() {
  1030. play.removeClass('mejs-pause').addClass('mejs-play');
  1031. }, false);
  1032. }
  1033. });
  1034. })(mejs.$);
  1035. (function($) {
  1036. $.extend(mejs.MepDefaults, {
  1037. stopText: 'Stop'
  1038. });
  1039. // STOP BUTTON
  1040. $.extend(MediaElementPlayer.prototype, {
  1041. buildstop: function(player, controls, layers, media) {
  1042. var t = this,
  1043. stop =
  1044. $('<div class="mejs-button mejs-stop-button mejs-stop">' +
  1045. '<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '" aria-label="' + t.options.stopText + '"></button>' +
  1046. '</div>')
  1047. .appendTo(controls)
  1048. .click(function() {
  1049. if (!media.paused) {
  1050. media.pause();
  1051. }
  1052. if (media.currentTime > 0) {
  1053. media.setCurrentTime(0);
  1054. media.pause();
  1055. controls.find('.mejs-time-current').width('0px');
  1056. controls.find('.mejs-time-handle').css('left', '0px');
  1057. controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) );
  1058. controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) );
  1059. layers.find('.mejs-poster').show();
  1060. }
  1061. });
  1062. }
  1063. });
  1064. })(mejs.$);
  1065. (function($) {
  1066. // progress/loaded bar
  1067. $.extend(MediaElementPlayer.prototype, {
  1068. buildprogress: function(player, controls, layers, media) {
  1069. $('<div class="mejs-time-rail">'+
  1070. '<span class="mejs-time-total">'+
  1071. '<span class="mejs-time-buffering"></span>'+
  1072. '<span class="mejs-time-loaded"></span>'+
  1073. '<span class="mejs-time-current"></span>'+
  1074. '<span class="mejs-time-handle"></span>'+
  1075. '<span class="mejs-time-float">' +
  1076. '<span class="mejs-time-float-current">00:00</span>' +
  1077. '<span class="mejs-time-float-corner"></span>' +
  1078. '</span>'+
  1079. '</span>'+
  1080. '</div>')
  1081. .appendTo(controls);
  1082. controls.find('.mejs-time-buffering').hide();
  1083. var
  1084. t = this,
  1085. total = controls.find('.mejs-time-total'),
  1086. loaded = controls.find('.mejs-time-loaded'),
  1087. current = controls.find('.mejs-time-current'),
  1088. handle = controls.find('.mejs-time-handle'),
  1089. timefloat = controls.find('.mejs-time-float'),
  1090. timefloatcurrent = controls.find('.mejs-time-float-current'),
  1091. handleMouseMove = function (e) {
  1092. // mouse position relative to the object
  1093. var x = e.pageX,
  1094. offset = total.offset(),
  1095. width = total.outerWidth(true),
  1096. percentage = 0,
  1097. newTime = 0,
  1098. pos = 0;
  1099. if (media.duration) {
  1100. if (x < offset.left) {
  1101. x = offset.left;
  1102. } else if (x > width + offset.left) {
  1103. x = width + offset.left;
  1104. }
  1105. pos = x - offset.left;
  1106. percentage = (pos / width);
  1107. newTime = (percentage <= 0.02) ? 0 : percentage * media.duration;
  1108. // seek to where the mouse is
  1109. if (mouseIsDown && newTime !== media.currentTime) {
  1110. media.setCurrentTime(newTime);
  1111. }
  1112. // position floating time box
  1113. if (!mejs.MediaFeatures.hasTouch) {
  1114. timefloat.css('left', pos);
  1115. timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) );
  1116. timefloat.show();
  1117. }
  1118. }
  1119. },
  1120. mouseIsDown = false,
  1121. mouseIsOver = false;
  1122. // handle clicks
  1123. //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove);
  1124. total
  1125. .bind('mousedown', function (e) {
  1126. // only handle left clicks
  1127. if (e.which === 1) {
  1128. mouseIsDown = true;
  1129. handleMouseMove(e);
  1130. t.globalBind('mousemove.dur', function(e) {
  1131. handleMouseMove(e);
  1132. });
  1133. t.globalBind('mouseup.dur', function (e) {
  1134. mouseIsDown = false;
  1135. timefloat.hide();
  1136. t.globalUnbind('.dur');
  1137. });
  1138. return false;
  1139. }
  1140. })
  1141. .bind('mouseenter', function(e) {
  1142. mouseIsOver = true;
  1143. t.globalBind('mousemove.dur', function(e) {
  1144. handleMouseMove(e);
  1145. });
  1146. if (!mejs.MediaFeatures.hasTouch) {
  1147. timefloat.show();
  1148. }
  1149. })
  1150. .bind('mouseleave',function(e) {
  1151. mouseIsOver = false;
  1152. if (!mouseIsDown) {
  1153. t.globalUnbind('.dur');
  1154. timefloat.hide();
  1155. }
  1156. });
  1157. // loading
  1158. media.addEventListener('progress', function (e) {
  1159. player.setProgressRail(e);
  1160. player.setCurrentRail(e);
  1161. }, false);
  1162. // current time
  1163. media.addEventListener('timeupdate', function(e) {
  1164. player.setProgressRail(e);
  1165. player.setCurrentRail(e);
  1166. }, false);
  1167. // store for later use
  1168. t.loaded = loaded;
  1169. t.total = total;
  1170. t.current = current;
  1171. t.handle = handle;
  1172. },
  1173. setProgressRail: function(e) {
  1174. var
  1175. t = this,
  1176. target = (e != undefined) ? e.target : t.media,
  1177. percent = null;
  1178. // newest HTML5 spec has buffered array (FF4, Webkit)
  1179. if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) {
  1180. // TODO: account for a real array with multiple values (only Firefox 4 has this so far)
  1181. percent = target.buffered.end(0) / target.duration;
  1182. }
  1183. // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end()
  1184. // to be anything other than 0. If the byte count is available we use this instead.
  1185. // Browsers that support the else if do not seem to have the bufferedBytes value and
  1186. // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8.
  1187. else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) {
  1188. percent = target.bufferedBytes / target.bytesTotal;
  1189. }
  1190. // Firefox 3 with an Ogg file seems to go this way
  1191. else if (e && e.lengthComputable && e.total != 0) {
  1192. percent = e.loaded/e.total;
  1193. }
  1194. // finally update the progress bar
  1195. if (percent !== null) {
  1196. percent = Math.min(1, Math.max(0, percent));
  1197. // update loaded bar
  1198. if (t.loaded && t.total) {
  1199. t.loaded.width(t.total.width() * percent);
  1200. }
  1201. }
  1202. },
  1203. setCurrentRail: function() {
  1204. var t = this;
  1205. if (t.media.currentTime != undefined && t.media.duration) {
  1206. // update bar and handle
  1207. if (t.total && t.handle) {
  1208. var
  1209. newWidth = Math.round(t.total.width() * t.media.currentTime / t.media.duration),
  1210. handlePos = newWidth - Math.round(t.handle.outerWidth(true) / 2);
  1211. t.current.width(newWidth);
  1212. t.handle.css('left', handlePos);
  1213. }
  1214. }
  1215. }
  1216. });
  1217. })(mejs.$);
  1218. (function($) {
  1219. // options
  1220. $.extend(mejs.MepDefaults, {
  1221. duration: -1,
  1222. timeAndDurationSeparator: '<span> | </span>'
  1223. });
  1224. // current and duration 00:00 / 00:00
  1225. $.extend(MediaElementPlayer.prototype, {
  1226. buildcurrent: function(player, controls, layers, media) {
  1227. var t = this;
  1228. $('<div class="mejs-time">'+
  1229. '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '')
  1230. + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')+ '</span>'+
  1231. '</div>')
  1232. .appendTo(controls);
  1233. t.currenttime = t.controls.find('.mejs-currenttime');
  1234. media.addEventListener('timeupdate',function() {
  1235. player.updateCurrent();
  1236. }, false);
  1237. },
  1238. buildduration: function(player, controls, layers, media) {
  1239. var t = this;
  1240. if (controls.children().last().find('.mejs-currenttime').length > 0) {
  1241. $(t.options.timeAndDurationSeparator +
  1242. '<span class="mejs-duration">' +
  1243. (t.options.duration > 0 ?
  1244. mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) :
  1245. ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00'))
  1246. ) +
  1247. '</span>')
  1248. .appendTo(controls.find('.mejs-time'));
  1249. } else {
  1250. // add class to current time
  1251. controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container');
  1252. $('<div class="mejs-time mejs-duration-container">'+
  1253. '<span class="mejs-duration">' +
  1254. (t.options.duration > 0 ?
  1255. mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) :
  1256. ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00'))
  1257. ) +
  1258. '</span>' +
  1259. '</div>')
  1260. .appendTo(controls);
  1261. }
  1262. t.durationD = t.controls.find('.mejs-duration');
  1263. media.addEventListener('timeupdate',function() {
  1264. player.updateDuration();
  1265. }, false);
  1266. },
  1267. updateCurrent: function() {
  1268. var t = this;
  1269. if (t.currenttime) {
  1270. t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25));
  1271. }
  1272. },
  1273. updateDuration: function() {
  1274. var t = this;
  1275. //Toggle the long video class if the video is longer than an hour.
  1276. t.container.toggleClass("mejs-long-video", t.media.duration > 3600);
  1277. if (t.durationD && (t.options.duration > 0 || t.media.duration)) {
  1278. t.durationD.html(mejs.Utility.secondsToTimeCode(t.options.duration > 0 ? t.options.duration : t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25));
  1279. }
  1280. }
  1281. });
  1282. })(mejs.$);
  1283. (function($) {
  1284. $.extend(mejs.MepDefaults, {
  1285. muteText: mejs.i18n.t('Mute Toggle'),
  1286. hideVolumeOnTouchDevices: true,
  1287. audioVolume: 'horizontal',
  1288. videoVolume: 'vertical'
  1289. });
  1290. $.extend(MediaElementPlayer.prototype, {
  1291. buildvolume: function(player, controls, layers, media) {
  1292. // Android and iOS don't support volume controls
  1293. if (mejs.MediaFeatures.hasTouch && this.options.hideVolumeOnTouchDevices)
  1294. return;
  1295. var t = this,
  1296. mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume,
  1297. mute = (mode == 'horizontal') ?
  1298. // horizontal version
  1299. $('<div class="mejs-button mejs-volume-button mejs-mute">'+
  1300. '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+
  1301. '</div>' +
  1302. '<div class="mejs-horizontal-volume-slider">'+ // outer background
  1303. '<div class="mejs-horizontal-volume-total"></div>'+ // line background
  1304. '<div class="mejs-horizontal-volume-current"></div>'+ // current volume
  1305. '<div class="mejs-horizontal-volume-handle"></div>'+ // handle
  1306. '</div>'
  1307. )
  1308. .appendTo(controls) :
  1309. // vertical version
  1310. $('<div class="mejs-button mejs-volume-button mejs-mute">'+
  1311. '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '" aria-label="' + t.options.muteText + '"></button>'+
  1312. '<div class="mejs-volume-slider">'+ // outer background
  1313. '<div class="mejs-volume-total"></div>'+ // line background
  1314. '<div class="mejs-volume-current"></div>'+ // current volume
  1315. '<div class="mejs-volume-handle"></div>'+ // handle
  1316. '</div>'+
  1317. '</div>')
  1318. .appendTo(controls),
  1319. volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'),
  1320. volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'),
  1321. volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'),
  1322. volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'),
  1323. positionVolumeHandle = function(volume, secondTry) {
  1324. if (!volumeSlider.is(':visible') && typeof secondTry == 'undefined') {
  1325. volumeSlider.show();
  1326. positionVolumeHandle(volume, true);
  1327. volumeSlider.hide()
  1328. return;
  1329. }
  1330. // correct to 0-1
  1331. volume = Math.max(0,volume);
  1332. volume = Math.min(volume,1);
  1333. // ajust mute button style
  1334. if (volume == 0) {
  1335. mute.removeClass('mejs-mute').addClass('mejs-unmute');
  1336. } else {
  1337. mute.removeClass('mejs-unmute').addClass('mejs-mute');
  1338. }
  1339. // position slider
  1340. if (mode == 'vertical') {
  1341. var
  1342. // height of the full size volume slider background
  1343. totalHeight = volumeTotal.height(),
  1344. // top/left of full size volume slider background
  1345. totalPosition = volumeTotal.position(),
  1346. // the new top position based on the current volume
  1347. // 70% volume on 100px height == top:30px
  1348. newTop = totalHeight - (totalHeight * volume);
  1349. // handle
  1350. volumeHandle.css('top', Math.round(totalPosition.top + newTop - (volumeHandle.height() / 2)));
  1351. // show the current visibility
  1352. volumeCurrent.height(totalHeight - newTop );
  1353. volumeCurrent.css('top', totalPosition.top + newTop);
  1354. } else {
  1355. var
  1356. // height of the full size volume slider background
  1357. totalWidth = volumeTotal.width(),
  1358. // top/left of full size volume slider background
  1359. totalPosition = volumeTotal.position(),
  1360. // the new left position based on the current volume
  1361. newLeft = totalWidth * volume;
  1362. // handle
  1363. volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2)));
  1364. // rezize the current part of the volume bar
  1365. volumeCurrent.width( Math.round(newLeft) );
  1366. }
  1367. },
  1368. handleVolumeMove = function(e) {
  1369. var volume = null,
  1370. totalOffset = volumeTotal.offset();
  1371. // calculate the new volume based on the moust position
  1372. if (mode == 'vertical') {
  1373. var
  1374. railHeight = volumeTotal.height(),
  1375. totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10),
  1376. newY = e.pageY - totalOffset.top;
  1377. volume = (railHeight - newY) / railHeight;
  1378. // the controls just hide themselves (usually when mouse moves too far up)
  1379. if (totalOffset.top == 0 || totalOffset.left == 0)
  1380. return;
  1381. } else {
  1382. var
  1383. railWidth = volumeTotal.width(),
  1384. newX = e.pageX - totalOffset.left;
  1385. volume = newX / railWidth;
  1386. }
  1387. // ensure the volume isn't outside 0-1
  1388. volume = Math.max(0,volume);
  1389. volume = Math.min(volume,1);
  1390. // position the slider and handle
  1391. positionVolumeHandle(volume);
  1392. // set the media object (this will trigger the volumechanged event)
  1393. if (volume == 0) {
  1394. media.setMuted(true);
  1395. } else {
  1396. media.setMuted(false);
  1397. }
  1398. media.setVolume(volume);
  1399. },
  1400. mouseIsDown = false,
  1401. mouseIsOver = false;
  1402. // SLIDER
  1403. mute
  1404. .hover(function() {
  1405. volumeSlider.show();
  1406. mouseIsOver = true;
  1407. }, function() {
  1408. mouseIsOver = false;
  1409. if (!mouseIsDown && mode == 'vertical') {
  1410. volumeSlider.hide();
  1411. }
  1412. });
  1413. volumeSlider
  1414. .bind('mouseover', function() {
  1415. mouseIsOver = true;
  1416. })
  1417. .bind('mousedown', function (e) {
  1418. handleVolumeMove(e);
  1419. t.globalBind('mousemove.vol', function(e) {
  1420. handleVolumeMove(e);
  1421. });
  1422. t.globalBind('mouseup.vol', function () {
  1423. mouseIsDown = false;
  1424. t.globalUnbind('.vol');
  1425. if (!mouseIsOver && mode == 'vertical') {
  1426. volumeSlider.hide();
  1427. }
  1428. });
  1429. mouseIsDown = true;
  1430. return false;
  1431. });
  1432. // MUTE button
  1433. mute.find('button').click(function() {
  1434. media.setMuted( !media.muted );
  1435. });
  1436. // listen for volume change events from other sources
  1437. media.addEventListener('volumechange', function(e) {
  1438. if (!mouseIsDown) {
  1439. if (media.muted) {
  1440. positionVolumeHandle(0);
  1441. mute.removeClass('mejs-mute').addClass('mejs-unmute');
  1442. } else {
  1443. positionVolumeHandle(media.volume);
  1444. mute.removeClass('mejs-unmute').addClass('mejs-mute');
  1445. }
  1446. }
  1447. }, false);
  1448. if (t.container.is(':visible')) {
  1449. // set initial volume
  1450. positionVolumeHandle(player.options.startVolume);
  1451. // mutes the media and sets the volume icon muted if the initial volume is set to 0
  1452. if (player.options.startVolume === 0) {
  1453. media.setMuted(true);
  1454. }
  1455. // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements
  1456. if (media.pluginType === 'native') {
  1457. media.setVolume(player.options.startVolume);
  1458. }
  1459. }
  1460. }
  1461. });
  1462. })(mejs.$);
  1463. (function($) {
  1464. $.extend(mejs.MepDefaults, {
  1465. usePluginFullScreen: true,
  1466. newWindowCallback: function() { return '';},
  1467. fullscreenText: mejs.i18n.t('Fullscreen')
  1468. });
  1469. $.extend(MediaElementPlayer.prototype, {
  1470. isFullScreen: false,
  1471. isNativeFullScreen: false,
  1472. isInIframe: false,
  1473. buildfullscreen: function(player, controls, layers, media) {
  1474. if (!player.isVideo)
  1475. return;
  1476. player.isInIframe = (window.location != window.parent.location);
  1477. // native events
  1478. if (mejs.MediaFeatures.hasTrueNativeFullScreen) {
  1479. // chrome doesn't alays fire this in an iframe
  1480. var func = function(e) {
  1481. if (player.isFullScreen) {
  1482. if (mejs.MediaFeatures.isFullScreen()) {
  1483. player.isNativeFullScreen = true;
  1484. // reset the controls once we are fully in full screen
  1485. player.setControlsSize();
  1486. } else {
  1487. player.isNativeFullScreen = false;
  1488. // when a user presses ESC
  1489. // make sure to put the player back into place
  1490. player.exitFullScreen();
  1491. }
  1492. }
  1493. };
  1494. if (mejs.MediaFeatures.hasMozNativeFullScreen) {
  1495. player.globalBind(mejs.MediaFeatures.fullScreenEventName, func);
  1496. } else {
  1497. player.container.bind(mejs.MediaFeatures.fullScreenEventName, func);
  1498. }
  1499. }
  1500. var t = this,
  1501. normalHeight = 0,
  1502. normalWidth = 0,
  1503. container = player.container,
  1504. fullscreenBtn =
  1505. $('<div class="mejs-button mejs-fullscreen-button">' +
  1506. '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '" aria-label="' + t.options.fullscreenText + '"></button>' +
  1507. '</div>')
  1508. .appendTo(controls);
  1509. if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) {
  1510. fullscreenBtn.click(function() {
  1511. var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen;
  1512. if (isFullScreen) {
  1513. player.exitFullScreen();
  1514. } else {
  1515. player.enterFullScreen();
  1516. }
  1517. });
  1518. } else {
  1519. var hideTimeout = null,
  1520. supportsPointerEvents = (function() {
  1521. // TAKEN FROM MODERNIZR
  1522. var element = document.createElement('x'),
  1523. documentElement = document.documentElement,
  1524. getComputedStyle = window.getComputedStyle,
  1525. supports;
  1526. if(!('pointerEvents' in element.style)){
  1527. return false;
  1528. }
  1529. element.style.pointerEvents = 'auto';
  1530. element.style.pointerEvents = 'x';
  1531. documentElement.appendChild(element);
  1532. supports = getComputedStyle &&
  1533. getComputedStyle(element, '').pointerEvents === 'auto';
  1534. documentElement.removeChild(element);
  1535. return !!supports;
  1536. })();
  1537. //
  1538. if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :(
  1539. // allows clicking through the fullscreen button and controls down directly to Flash
  1540. /*
  1541. When a user puts his mouse over the fullscreen button, the controls are disabled
  1542. So we put a div over the video and another one on iether side of the fullscreen button
  1543. that caputre mouse movement
  1544. and restore the controls once the mouse moves outside of the fullscreen button
  1545. */
  1546. var fullscreenIsDisabled = false,
  1547. restoreControls = function() {
  1548. if (fullscreenIsDisabled) {
  1549. // hide the hovers
  1550. for (var i in hoverDivs) {
  1551. hoverDivs[i].hide();
  1552. }
  1553. // restore the control bar
  1554. fullscreenBtn.css('pointer-events', '');
  1555. t.controls.css('pointer-events', '');
  1556. // prevent clicks from pausing video
  1557. t.media.removeEventListener('click', t.clickToPlayPauseCallback);
  1558. // store for later
  1559. fullscreenIsDisabled = false;
  1560. }
  1561. },
  1562. hoverDivs = {},
  1563. hoverDivNames = ['top', 'left', 'right', 'bottom'],
  1564. i, len,
  1565. positionHoverDivs = function() {
  1566. var fullScreenBtnOffsetLeft = fullscreenBtn.offset().left - t.container.offset().left,
  1567. fullScreenBtnOffsetTop = fullscreenBtn.offset().top - t.container.offset().top,
  1568. fullScreenBtnWidth = fullscreenBtn.outerWidth(true),
  1569. fullScreenBtnHeight = fullscreenBtn.outerHeight(true),
  1570. containerWidth = t.container.width(),
  1571. containerHeight = t.container.height();
  1572. for (i in hoverDivs) {
  1573. hoverDivs[i].css({position: 'absolute', top: 0, left: 0}); //, backgroundColor: '#f00'});
  1574. }
  1575. // over video, but not controls
  1576. hoverDivs['top']
  1577. .width( containerWidth )
  1578. .height( fullScreenBtnOffsetTop );
  1579. // over controls, but not the fullscreen button
  1580. hoverDivs['left']
  1581. .width( fullScreenBtnOffsetLeft )
  1582. .height( fullScreenBtnHeight )
  1583. .css({top: fullScreenBtnOffsetTop});
  1584. // after the fullscreen button
  1585. hoverDivs['right']
  1586. .width( containerWidth - fullScreenBtnOffsetLeft - fullScreenBtnWidth )
  1587. .height( fullScreenBtnHeight )
  1588. .css({top: fullScreenBtnOffsetTop,
  1589. left: fullScreenBtnOffsetLeft + fullScreenBtnWidth});
  1590. // under the fullscreen button
  1591. hoverDivs['bottom']
  1592. .width( containerWidth )
  1593. .height( containerHeight - fullScreenBtnHeight - fullScreenBtnOffsetTop )
  1594. .css({top: fullScreenBtnOffsetTop + fullScreenBtnHeight});
  1595. };
  1596. t.globalBind('resize', function() {
  1597. positionHoverDivs();
  1598. });
  1599. for (i = 0, len = hoverDivNames.length; i < len; i++) {
  1600. hoverDivs[hoverDivNames[i]] = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls).hide();
  1601. }
  1602. // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash
  1603. fullscreenBtn.on('mouseover',function() {
  1604. if (!t.isFullScreen) {
  1605. var buttonPos = fullscreenBtn.offset(),
  1606. containerPos = player.container.offset();
  1607. // move the button in Flash into place
  1608. media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false);
  1609. // allows click through
  1610. fullscreenBtn.css('pointer-events', 'none');
  1611. t.controls.css('pointer-events', 'none');
  1612. // restore click-to-play
  1613. t.media.addEventListener('click', t.clickToPlayPauseCallback);
  1614. // show the divs that will restore things
  1615. for (i in hoverDivs) {
  1616. hoverDivs[i].show();
  1617. }
  1618. positionHoverDivs();
  1619. fullscreenIsDisabled = true;
  1620. }
  1621. });
  1622. // restore controls anytime the user enters or leaves fullscreen
  1623. media.addEventListener('fullscreenchange', function(e) {
  1624. t.isFullScreen = !t.isFullScreen;
  1625. // don't allow plugin click to pause video - messes with
  1626. // plugin's controls
  1627. if (t.isFullScreen) {
  1628. t.media.removeEventListener('click', t.clickToPlayPauseCallback);
  1629. } else {
  1630. t.media.addEventListener('click', t.clickToPlayPauseCallback);
  1631. }
  1632. restoreControls();
  1633. });
  1634. // the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events
  1635. // so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button
  1636. t.globalBind('mousemove', function(e) {
  1637. // if the mouse is anywhere but the fullsceen button, then restore it all
  1638. if (fullscreenIsDisabled) {
  1639. var fullscreenBtnPos = fullscreenBtn.offset();
  1640. if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) ||
  1641. e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true)
  1642. ) {
  1643. fullscreenBtn.css('pointer-events', '');
  1644. t.controls.css('pointer-events', '');
  1645. fullscreenIsDisabled = false;
  1646. }
  1647. }
  1648. });
  1649. } else {
  1650. // the hover state will show the fullscreen button in Flash to hover up and click
  1651. fullscreenBtn
  1652. .on('mouseover', function() {
  1653. if (hideTimeout !== null) {
  1654. clearTimeout(hideTimeout);
  1655. delete hideTimeout;
  1656. }
  1657. var buttonPos = fullscreenBtn.offset(),
  1658. containerPos = player.container.offset();
  1659. media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true);
  1660. })
  1661. .on('mouseout', function() {
  1662. if (hideTimeout !== null) {
  1663. clearTimeout(hideTimeout);
  1664. delete hideTimeout;
  1665. }
  1666. hideTimeout = setTimeout(function() {
  1667. media.hideFullscreenButton();
  1668. }, 1500);
  1669. });
  1670. }
  1671. }
  1672. player.fullscreenBtn = fullscreenBtn;
  1673. t.globalBind('keydown',function (e) {
  1674. if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) {
  1675. player.exitFullScreen();
  1676. }
  1677. });
  1678. },
  1679. cleanfullscreen: function(player) {
  1680. player.exitFullScreen();
  1681. },
  1682. containerSizeTimeout: null,
  1683. enterFullScreen: function() {
  1684. var t = this;
  1685. // firefox+flash can't adjust plugin sizes without resetting :(
  1686. if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) {
  1687. //t.media.setFullscreen(true);
  1688. //player.isFullScreen = true;
  1689. return;
  1690. }
  1691. // set it to not show scroll bars so 100% will work
  1692. $(document.documentElement).addClass('mejs-fullscreen');
  1693. // store sizing
  1694. normalHeight = t.container.height();
  1695. normalWidth = t.container.width();
  1696. // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now)
  1697. if (t.media.pluginType === 'native') {
  1698. if (mejs.MediaFeatures.hasTrueNativeFullScreen) {
  1699. mejs.MediaFeatures.requestFullScreen(t.container[0]);
  1700. //return;
  1701. if (t.isInIframe) {
  1702. // sometimes exiting from fullscreen doesn't work
  1703. // notably in Chrome <iframe>. Fixed in version 17
  1704. setTimeout(function checkFullscreen() {
  1705. if (t.isNativeFullScreen) {
  1706. // check if the video is suddenly not really fullscreen
  1707. if ($(window).width() !== screen.width) {
  1708. // manually exit
  1709. t.exitFullScreen();
  1710. } else {
  1711. // test again
  1712. setTimeout(checkFullscreen, 500);
  1713. }
  1714. }
  1715. }, 500);
  1716. }
  1717. } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) {
  1718. t.media.webkitEnterFullscreen();
  1719. return;
  1720. }
  1721. }
  1722. // check for iframe launch
  1723. if (t.isInIframe) {
  1724. var url = t.options.newWindowCallback(this);
  1725. if (url !== '') {
  1726. // launch immediately
  1727. if (!mejs.MediaFeatures.hasTrueNativeFullScreen) {
  1728. t.pause();
  1729. window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no');
  1730. return;
  1731. } else {
  1732. setTimeout(function() {
  1733. if (!t.isNativeFullScreen) {
  1734. t.pause();
  1735. window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no');
  1736. }
  1737. }, 250);
  1738. }
  1739. }
  1740. }
  1741. // full window code
  1742. // make full size
  1743. t.container
  1744. .addClass('mejs-container-fullscreen')
  1745. .width('100%')
  1746. .height('100%');
  1747. //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000});
  1748. // Only needed for safari 5.1 native full screen, can cause display issues elsewhere
  1749. // Actually, it seems to be needed for IE8, too
  1750. //if (mejs.MediaFeatures.hasTrueNativeFullScreen) {
  1751. t.containerSizeTimeout = setTimeout(function() {
  1752. t.container.css({width: '100%', height: '100%'});
  1753. t.setControlsSize();
  1754. }, 500);
  1755. //}
  1756. if (t.media.pluginType === 'native') {
  1757. t.$media
  1758. .width('100%')
  1759. .height('100%');
  1760. } else {
  1761. t.container.find('.mejs-shim')
  1762. .width('100%')
  1763. .height('100%');
  1764. //if (!mejs.MediaFeatures.hasTrueNativeFullScreen) {
  1765. t.media.setVideoSize($(window).width(),$(window).height());
  1766. //}
  1767. }
  1768. t.layers.children('div')
  1769. .width('100%')
  1770. .height('100%');
  1771. if (t.fullscreenBtn) {
  1772. t.fullscreenBtn
  1773. .removeClass('mejs-fullscreen')
  1774. .addClass('mejs-unfullscreen');
  1775. }
  1776. t.setControlsSize();
  1777. t.isFullScreen = true;
  1778. },
  1779. exitFullScreen: function() {
  1780. var t = this;
  1781. // Prevent container from attempting to stretch a second time
  1782. clearTimeout(t.containerSizeTimeout);
  1783. // firefox can't adjust plugins
  1784. if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) {
  1785. t.media.setFullscreen(false);
  1786. //player.isFullScreen = false;
  1787. return;
  1788. }
  1789. // come outo of native fullscreen
  1790. if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) {
  1791. mejs.MediaFeatures.cancelFullScreen();
  1792. }
  1793. // restore scroll bars to document
  1794. $(document.documentElement).removeClass('mejs-fullscreen');
  1795. t.container
  1796. .removeClass('mejs-container-fullscreen')
  1797. .width(normalWidth)
  1798. .height(normalHeight);
  1799. //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1});
  1800. if (t.media.pluginType === 'native') {
  1801. t.$media
  1802. .width(normalWidth)
  1803. .height(normalHeight);
  1804. } else {
  1805. t.container.find('.mejs-shim')
  1806. .width(normalWidth)
  1807. .height(normalHeight);
  1808. t.media.setVideoSize(normalWidth, normalHeight);
  1809. }
  1810. t.layers.children('div')
  1811. .width(normalWidth)
  1812. .height(normalHeight);
  1813. t.fullscreenBtn
  1814. .removeClass('mejs-unfullscreen')
  1815. .addClass('mejs-fullscreen');
  1816. t.setControlsSize();
  1817. t.isFullScreen = false;
  1818. }
  1819. });
  1820. })(mejs.$);
  1821. (function($) {
  1822. // add extra default options
  1823. $.extend(mejs.MepDefaults, {
  1824. // this will automatically turn on a <track>
  1825. startLanguage: '',
  1826. tracksText: mejs.i18n.t('Captions/Subtitles'),
  1827. // option to remove the [cc] button when no <track kind="subtitles"> are present
  1828. hideCaptionsButtonWhenEmpty: true,
  1829. // If true and we only have one track, change captions to popup
  1830. toggleCaptionsButtonWhenOnlyOne: false,
  1831. // #id or .class
  1832. slidesSelector: ''
  1833. });
  1834. $.extend(MediaElementPlayer.prototype, {
  1835. hasChapters: false,
  1836. buildtracks: function(player, controls, layers, media) {
  1837. if (player.tracks.length == 0)
  1838. return;
  1839. var t = this,
  1840. i,
  1841. options = '';
  1842. if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide
  1843. for (var i = t.domNode.textTracks.length - 1; i >= 0; i--) {
  1844. t.domNode.textTracks[i].mode = "hidden";
  1845. }
  1846. }
  1847. player.chapters =
  1848. $('<div class="mejs-chapters mejs-layer"></div>')
  1849. .prependTo(layers).hide();
  1850. player.captions =
  1851. $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover"><span class="mejs-captions-text"></span></div></div>')
  1852. .prependTo(layers).hide();
  1853. player.captionsText = player.captions.find('.mejs-captions-text');
  1854. player.captionsButton =
  1855. $('<div class="mejs-button mejs-captions-button">'+
  1856. '<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '" aria-label="' + t.options.tracksText + '"></button>'+
  1857. '<div class="mejs-captions-selector">'+
  1858. '<ul>'+
  1859. '<li>'+
  1860. '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' +
  1861. '<label for="' + player.id + '_captions_none">' + mejs.i18n.t('None') +'</label>'+
  1862. '</li>' +
  1863. '</ul>'+
  1864. '</div>'+
  1865. '</div>')
  1866. .appendTo(controls);
  1867. var subtitleCount = 0;
  1868. for (i=0; i<player.tracks.length; i++) {
  1869. if (player.tracks[i].kind == 'subtitles') {
  1870. subtitleCount++;
  1871. }
  1872. }
  1873. // if only one language then just make the button a toggle
  1874. if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitleCount == 1){
  1875. // click
  1876. player.captionsButton.on('click',function() {
  1877. if (player.selectedTrack == null) {
  1878. var lang = player.tracks[0].srclang;
  1879. } else {
  1880. var lang = 'none';
  1881. }
  1882. player.setTrack(lang);
  1883. });
  1884. } else {
  1885. // hover
  1886. player.captionsButton.hover(function() {
  1887. $(this).find('.mejs-captions-selector').css('visibility','visible');
  1888. }, function() {
  1889. $(this).find('.mejs-captions-selector').css('visibility','hidden');
  1890. })
  1891. // handle clicks to the language radio buttons
  1892. .on('click','input[type=radio]',function() {
  1893. lang = this.value;
  1894. player.setTrack(lang);
  1895. });
  1896. }
  1897. if (!player.options.alwaysShowControls) {
  1898. // move with controls
  1899. player.container
  1900. .bind('controlsshown', function () {
  1901. // push captions above controls
  1902. player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
  1903. })
  1904. .bind('controlshidden', function () {
  1905. if (!media.paused) {
  1906. // move back to normal place
  1907. player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover');
  1908. }
  1909. });
  1910. } else {
  1911. player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
  1912. }
  1913. player.trackToLoad = -1;
  1914. player.selectedTrack = null;
  1915. player.isLoadingTrack = false;
  1916. // add to list
  1917. for (i=0; i<player.tracks.length; i++) {
  1918. if (player.tracks[i].kind == 'subtitles') {
  1919. player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label);
  1920. }
  1921. }
  1922. // start loading tracks
  1923. player.loadNextTrack();
  1924. media.addEventListener('timeupdate',function(e) {
  1925. player.displayCaptions();
  1926. }, false);
  1927. if (player.options.slidesSelector != '') {
  1928. player.slidesContainer = $(player.options.slidesSelector);
  1929. media.addEventListener('timeupdate',function(e) {
  1930. player.displaySlides();
  1931. }, false);
  1932. }
  1933. media.addEventListener('loadedmetadata', function(e) {
  1934. player.displayChapters();
  1935. }, false);
  1936. player.container.hover(
  1937. function () {
  1938. // chapters
  1939. if (player.hasChapters) {
  1940. player.chapters.css('visibility','visible');
  1941. player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight());
  1942. }
  1943. },
  1944. function () {
  1945. if (player.hasChapters && !media.paused) {
  1946. player.chapters.fadeOut(200, function() {
  1947. $(this).css('visibility','hidden');
  1948. $(this).css('display','block');
  1949. });
  1950. }
  1951. });
  1952. // check for autoplay
  1953. if (player.node.getAttribute('autoplay') !== null) {
  1954. player.chapters.css('visibility','hidden');
  1955. }
  1956. },
  1957. setTrack: function(lang){
  1958. var t = this,
  1959. i;
  1960. if (lang == 'none') {
  1961. t.selectedTrack = null;
  1962. t.captionsButton.removeClass('mejs-captions-enabled');
  1963. } else {
  1964. for (i=0; i<t.tracks.length; i++) {
  1965. if (t.tracks[i].srclang == lang) {
  1966. if (t.selectedTrack == null)
  1967. t.captionsButton.addClass('mejs-captions-enabled');
  1968. t.selectedTrack = t.tracks[i];
  1969. t.captions.attr('lang', t.selectedTrack.srclang);
  1970. t.displayCaptions();
  1971. break;
  1972. }
  1973. }
  1974. }
  1975. },
  1976. loadNextTrack: function() {
  1977. var t = this;
  1978. t.trackToLoad++;
  1979. if (t.trackToLoad < t.tracks.length) {
  1980. t.isLoadingTrack = true;
  1981. t.loadTrack(t.trackToLoad);
  1982. } else {
  1983. // add done?
  1984. t.isLoadingTrack = false;
  1985. t.checkForTracks();
  1986. }
  1987. },
  1988. loadTrack: function(index){
  1989. var
  1990. t = this,
  1991. track = t.tracks[index],
  1992. after = function() {
  1993. track.isLoaded = true;
  1994. // create button
  1995. //t.addTrackButton(track.srclang);
  1996. t.enableTrackButton(track.srclang, track.label);
  1997. t.loadNextTrack();
  1998. };
  1999. $.ajax({
  2000. url: track.src,
  2001. dataType: "text",
  2002. success: function(d) {
  2003. // parse the loaded file
  2004. if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) {
  2005. track.entries = mejs.TrackFormatParser.dfxp.parse(d);
  2006. } else {
  2007. track.entries = mejs.TrackFormatParser.webvvt.parse(d);
  2008. }
  2009. after();
  2010. if (track.kind == 'chapters') {
  2011. t.media.addEventListener('play', function(e) {
  2012. if (t.media.duration > 0) {
  2013. t.displayChapters(track);
  2014. }
  2015. }, false);
  2016. }
  2017. if (track.kind == 'slides') {
  2018. t.setupSlides(track);
  2019. }
  2020. },
  2021. error: function() {
  2022. t.loadNextTrack();
  2023. }
  2024. });
  2025. },
  2026. enableTrackButton: function(lang, label) {
  2027. var t = this;
  2028. if (label === '') {
  2029. label = mejs.language.codes[lang] || lang;
  2030. }
  2031. t.captionsButton
  2032. .find('input[value=' + lang + ']')
  2033. .prop('disabled',false)
  2034. .siblings('label')
  2035. .html( label );
  2036. // auto select
  2037. if (t.options.startLanguage == lang) {
  2038. $('#' + t.id + '_captions_' + lang).click();
  2039. }
  2040. t.adjustLanguageBox();
  2041. },
  2042. addTrackButton: function(lang, label) {
  2043. var t = this;
  2044. if (label === '') {
  2045. label = mejs.language.codes[lang] || lang;
  2046. }
  2047. t.captionsButton.find('ul').append(
  2048. $('<li>'+
  2049. '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' +
  2050. '<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+
  2051. '</li>')
  2052. );
  2053. t.adjustLanguageBox();
  2054. // remove this from the dropdownlist (if it exists)
  2055. t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove();
  2056. },
  2057. adjustLanguageBox:function() {
  2058. var t = this;
  2059. // adjust the size of the outer box
  2060. t.captionsButton.find('.mejs-captions-selector').height(
  2061. t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) +
  2062. t.captionsButton.find('.mejs-captions-translations').outerHeight(true)
  2063. );
  2064. },
  2065. checkForTracks: function() {
  2066. var
  2067. t = this,
  2068. hasSubtitles = false;
  2069. // check if any subtitles
  2070. if (t.options.hideCaptionsButtonWhenEmpty) {
  2071. for (i=0; i<t.tracks.length; i++) {
  2072. if (t.tracks[i].kind == 'subtitles') {
  2073. hasSubtitles = true;
  2074. break;
  2075. }
  2076. }
  2077. if (!hasSubtitles) {
  2078. t.captionsButton.hide();
  2079. t.setControlsSize();
  2080. }
  2081. }
  2082. },
  2083. displayCaptions: function() {
  2084. if (typeof this.tracks == 'undefined')
  2085. return;
  2086. var
  2087. t = this,
  2088. i,
  2089. track = t.selectedTrack;
  2090. if (track != null && track.isLoaded) {
  2091. for (i=0; i<track.entries.times.length; i++) {
  2092. if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop){
  2093. t.captionsText.html(track.entries.text[i]);
  2094. t.captions.show().height(0);
  2095. return; // exit out if one is visible;
  2096. }
  2097. }
  2098. t.captions.hide();
  2099. } else {
  2100. t.captions.hide();
  2101. }
  2102. },
  2103. setupSlides: function(track) {
  2104. var t = this;
  2105. t.slides = track;
  2106. t.slides.entries.imgs = [t.slides.entries.text.length];
  2107. t.showSlide(0);
  2108. },
  2109. showSlide: function(index) {
  2110. if (typeof this.tracks == 'undefined' || typeof this.slidesContainer == 'undefined') {
  2111. return;
  2112. }
  2113. var t = this,
  2114. url = t.slides.entries.text[index],
  2115. img = t.slides.entries.imgs[index];
  2116. if (typeof img == 'undefined' || typeof img.fadeIn == 'undefined') {
  2117. t.slides.entries.imgs[index] = img = $('<img src="' + url + '">')
  2118. .on('load', function() {
  2119. img.appendTo(t.slidesContainer)
  2120. .hide()
  2121. .fadeIn()
  2122. .siblings(':visible')
  2123. .fadeOut();
  2124. });
  2125. } else {
  2126. if (!img.is(':visible') && !img.is(':animated')) {
  2127. //
  2128. img.fadeIn()
  2129. .siblings(':visible')
  2130. .fadeOut();
  2131. }
  2132. }
  2133. },
  2134. displaySlides: function() {
  2135. if (typeof this.slides == 'undefined')
  2136. return;
  2137. var
  2138. t = this,
  2139. slides = t.slides,
  2140. i;
  2141. for (i=0; i<slides.entries.times.length; i++) {
  2142. if (t.media.currentTime >= slides.entries.times[i].start && t.media.currentTime <= slides.entries.times[i].stop){
  2143. t.showSlide(i);
  2144. return; // exit out if one is visible;
  2145. }
  2146. }
  2147. },
  2148. displayChapters: function() {
  2149. var
  2150. t = this,
  2151. i;
  2152. for (i=0; i<t.tracks.length; i++) {
  2153. if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) {
  2154. t.drawChapters(t.tracks[i]);
  2155. t.hasChapters = true;
  2156. break;
  2157. }
  2158. }
  2159. },
  2160. drawChapters: function(chapters) {
  2161. var
  2162. t = this,
  2163. i,
  2164. dur,
  2165. //width,
  2166. //left,
  2167. percent = 0,
  2168. usedPercent = 0;
  2169. t.chapters.empty();
  2170. for (i=0; i<chapters.entries.times.length; i++) {
  2171. dur = chapters.entries.times[i].stop - chapters.entries.times[i].start;
  2172. percent = Math.floor(dur / t.media.duration * 100);
  2173. if (percent + usedPercent > 100 || // too large
  2174. i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in
  2175. {
  2176. percent = 100 - usedPercent;
  2177. }
  2178. //width = Math.floor(t.width * dur / t.media.duration);
  2179. //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration);
  2180. //if (left + width > t.width) {
  2181. // width = t.width - left;
  2182. //}
  2183. t.chapters.append( $(
  2184. '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' +
  2185. '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' +
  2186. '<span class="ch-title">' + chapters.entries.text[i] + '</span>' +
  2187. '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '&ndash;' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' +
  2188. '</div>' +
  2189. '</div>'));
  2190. usedPercent += percent;
  2191. }
  2192. t.chapters.find('div.mejs-chapter').click(function() {
  2193. t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) );
  2194. if (t.media.paused) {
  2195. t.media.play();
  2196. }
  2197. });
  2198. t.chapters.show();
  2199. }
  2200. });
  2201. mejs.language = {
  2202. codes: {
  2203. af:'Afrikaans',
  2204. sq:'Albanian',
  2205. ar:'Arabic',
  2206. be:'Belarusian',
  2207. bg:'Bulgarian',
  2208. ca:'Catalan',
  2209. zh:'Chinese',
  2210. 'zh-cn':'Chinese Simplified',
  2211. 'zh-tw':'Chinese Traditional',
  2212. hr:'Croatian',
  2213. cs:'Czech',
  2214. da:'Danish',
  2215. nl:'Dutch',
  2216. en:'English',
  2217. et:'Estonian',
  2218. tl:'Filipino',
  2219. fi:'Finnish',
  2220. fr:'French',
  2221. gl:'Galician',
  2222. de:'German',
  2223. el:'Greek',
  2224. ht:'Haitian Creole',
  2225. iw:'Hebrew',
  2226. hi:'Hindi',
  2227. hu:'Hungarian',
  2228. is:'Icelandic',
  2229. id:'Indonesian',
  2230. ga:'Irish',
  2231. it:'Italian',
  2232. ja:'Japanese',
  2233. ko:'Korean',
  2234. lv:'Latvian',
  2235. lt:'Lithuanian',
  2236. mk:'Macedonian',
  2237. ms:'Malay',
  2238. mt:'Maltese',
  2239. no:'Norwegian',
  2240. fa:'Persian',
  2241. pl:'Polish',
  2242. pt:'Portuguese',
  2243. //'pt-pt':'Portuguese (Portugal)',
  2244. ro:'Romanian',
  2245. ru:'Russian',
  2246. sr:'Serbian',
  2247. sk:'Slovak',
  2248. sl:'Slovenian',
  2249. es:'Spanish',
  2250. sw:'Swahili',
  2251. sv:'Swedish',
  2252. tl:'Tagalog',
  2253. th:'Thai',
  2254. tr:'Turkish',
  2255. uk:'Ukrainian',
  2256. vi:'Vietnamese',
  2257. cy:'Welsh',
  2258. yi:'Yiddish'
  2259. }
  2260. };
  2261. /*
  2262. Parses WebVVT format which should be formatted as
  2263. ================================
  2264. WEBVTT
  2265. 1
  2266. 00:00:01,1 --> 00:00:05,000
  2267. A line of text
  2268. 2
  2269. 00:01:15,1 --> 00:02:05,000
  2270. A second line of text
  2271. ===============================
  2272. Adapted from: http://www.delphiki.com/html5/playr
  2273. */
  2274. mejs.TrackFormatParser = {
  2275. webvvt: {
  2276. // match start "chapter-" (or anythingelse)
  2277. pattern_identifier: /^([a-zA-z]+-)?[0-9]+$/,
  2278. pattern_timecode: /^([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/,
  2279. parse: function(trackText) {
  2280. var
  2281. i = 0,
  2282. lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/),
  2283. entries = {text:[], times:[]},
  2284. timecode,
  2285. text;
  2286. for(; i<lines.length; i++) {
  2287. // check for the line number
  2288. if (this.pattern_identifier.exec(lines[i])){
  2289. // skip to the next line where the start --> end time code should be
  2290. i++;
  2291. timecode = this.pattern_timecode.exec(lines[i]);
  2292. if (timecode && i<lines.length){
  2293. i++;
  2294. // grab all the (possibly multi-line) text that follows
  2295. text = lines[i];
  2296. i++;
  2297. while(lines[i] !== '' && i<lines.length){
  2298. text = text + '\n' + lines[i];
  2299. i++;
  2300. }
  2301. text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>");
  2302. // Text is in a different array so I can use .join
  2303. entries.text.push(text);
  2304. entries.times.push(
  2305. {
  2306. start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) == 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]),
  2307. stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]),
  2308. settings: timecode[5]
  2309. });
  2310. }
  2311. }
  2312. }
  2313. return entries;
  2314. }
  2315. },
  2316. // Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420
  2317. dfxp: {
  2318. parse: function(trackText) {
  2319. trackText = $(trackText).filter("tt");
  2320. var
  2321. i = 0,
  2322. container = trackText.children("div").eq(0),
  2323. lines = container.find("p"),
  2324. styleNode = trackText.find("#" + container.attr("style")),
  2325. styles,
  2326. begin,
  2327. end,
  2328. text,
  2329. entries = {text:[], times:[]};
  2330. if (styleNode.length) {
  2331. var attributes = styleNode.removeAttr("id").get(0).attributes;
  2332. if (attributes.length) {
  2333. styles = {};
  2334. for (i = 0; i < attributes.length; i++) {
  2335. styles[attributes[i].name.split(":")[1]] = attributes[i].value;
  2336. }
  2337. }
  2338. }
  2339. for(i = 0; i<lines.length; i++) {
  2340. var style;
  2341. var _temp_times = {
  2342. start: null,
  2343. stop: null,
  2344. style: null
  2345. };
  2346. if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin"));
  2347. if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end"));
  2348. if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end"));
  2349. if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin"));
  2350. if (styles) {
  2351. style = "";
  2352. for (var _style in styles) {
  2353. style += _style + ":" + styles[_style] + ";";
  2354. }
  2355. }
  2356. if (style) _temp_times.style = style;
  2357. if (_temp_times.start == 0) _temp_times.start = 0.200;
  2358. entries.times.push(_temp_times);
  2359. text = $.trim(lines.eq(i).html()).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>");
  2360. entries.text.push(text);
  2361. if (entries.times.start == 0) entries.times.start = 2;
  2362. }
  2363. return entries;
  2364. }
  2365. },
  2366. split2: function (text, regex) {
  2367. // normal version for compliant browsers
  2368. // see below for IE fix
  2369. return text.split(regex);
  2370. }
  2371. };
  2372. // test for browsers with bad String.split method.
  2373. if ('x\n\ny'.split(/\n/gi).length != 3) {
  2374. // add super slow IE8 and below version
  2375. mejs.TrackFormatParser.split2 = function(text, regex) {
  2376. var
  2377. parts = [],
  2378. chunk = '',
  2379. i;
  2380. for (i=0; i<text.length; i++) {
  2381. chunk += text.substring(i,i+1);
  2382. if (regex.test(chunk)) {
  2383. parts.push(chunk.replace(regex, ''));
  2384. chunk = '';
  2385. }
  2386. }
  2387. parts.push(chunk);
  2388. return parts;
  2389. }
  2390. }
  2391. })(mejs.$);
  2392. /*
  2393. * ContextMenu Plugin
  2394. *
  2395. *
  2396. */
  2397. (function($) {
  2398. $.extend(mejs.MepDefaults,
  2399. { 'contextMenuItems': [
  2400. // demo of a fullscreen option
  2401. {
  2402. render: function(player) {
  2403. // check for fullscreen plugin
  2404. if (typeof player.enterFullScreen == 'undefined')
  2405. return null;
  2406. if (player.isFullScreen) {
  2407. return mejs.i18n.t('Turn off Fullscreen');
  2408. } else {
  2409. return mejs.i18n.t('Go Fullscreen');
  2410. }
  2411. },
  2412. click: function(player) {
  2413. if (player.isFullScreen) {
  2414. player.exitFullScreen();
  2415. } else {
  2416. player.enterFullScreen();
  2417. }
  2418. }
  2419. }
  2420. ,
  2421. // demo of a mute/unmute button
  2422. {
  2423. render: function(player) {
  2424. if (player.media.muted) {
  2425. return mejs.i18n.t('Unmute');
  2426. } else {
  2427. return mejs.i18n.t('Mute');
  2428. }
  2429. },
  2430. click: function(player) {
  2431. if (player.media.muted) {
  2432. player.setMuted(false);
  2433. } else {
  2434. player.setMuted(true);
  2435. }
  2436. }
  2437. },
  2438. // separator
  2439. {
  2440. isSeparator: true
  2441. }
  2442. ,
  2443. // demo of simple download video
  2444. {
  2445. render: function(player) {
  2446. return mejs.i18n.t('Download Video');
  2447. },
  2448. click: function(player) {
  2449. window.location.href = player.media.currentSrc;
  2450. }
  2451. }
  2452. ]}
  2453. );
  2454. $.extend(MediaElementPlayer.prototype, {
  2455. buildcontextmenu: function(player, controls, layers, media) {
  2456. // create context menu
  2457. player.contextMenu = $('<div class="mejs-contextmenu"></div>')
  2458. .appendTo($('body'))
  2459. .hide();
  2460. // create events for showing context menu
  2461. player.container.bind('contextmenu', function(e) {
  2462. if (player.isContextMenuEnabled) {
  2463. e.preventDefault();
  2464. player.renderContextMenu(e.clientX-1, e.clientY-1);
  2465. return false;
  2466. }
  2467. });
  2468. player.container.bind('click', function() {
  2469. player.contextMenu.hide();
  2470. });
  2471. player.contextMenu.bind('mouseleave', function() {
  2472. //
  2473. player.startContextMenuTimer();
  2474. });
  2475. },
  2476. cleancontextmenu: function(player) {
  2477. player.contextMenu.remove();
  2478. },
  2479. isContextMenuEnabled: true,
  2480. enableContextMenu: function() {
  2481. this.isContextMenuEnabled = true;
  2482. },
  2483. disableContextMenu: function() {
  2484. this.isContextMenuEnabled = false;
  2485. },
  2486. contextMenuTimeout: null,
  2487. startContextMenuTimer: function() {
  2488. //
  2489. var t = this;
  2490. t.killContextMenuTimer();
  2491. t.contextMenuTimer = setTimeout(function() {
  2492. t.hideContextMenu();
  2493. t.killContextMenuTimer();
  2494. }, 750);
  2495. },
  2496. killContextMenuTimer: function() {
  2497. var timer = this.contextMenuTimer;
  2498. //
  2499. if (timer != null) {
  2500. clearTimeout(timer);
  2501. delete timer;
  2502. timer = null;
  2503. }
  2504. },
  2505. hideContextMenu: function() {
  2506. this.contextMenu.hide();
  2507. },
  2508. renderContextMenu: function(x,y) {
  2509. // alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly
  2510. var t = this,
  2511. html = '',
  2512. items = t.options.contextMenuItems;
  2513. for (var i=0, il=items.length; i<il; i++) {
  2514. if (items[i].isSeparator) {
  2515. html += '<div class="mejs-contextmenu-separator"></div>';
  2516. } else {
  2517. var rendered = items[i].render(t);
  2518. // render can return null if the item doesn't need to be used at the moment
  2519. if (rendered != null) {
  2520. html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>';
  2521. }
  2522. }
  2523. }
  2524. // position and show the context menu
  2525. t.contextMenu
  2526. .empty()
  2527. .append($(html))
  2528. .css({top:y, left:x})
  2529. .show();
  2530. // bind events
  2531. t.contextMenu.find('.mejs-contextmenu-item').each(function() {
  2532. // which one is this?
  2533. var $dom = $(this),
  2534. itemIndex = parseInt( $dom.data('itemindex'), 10 ),
  2535. item = t.options.contextMenuItems[itemIndex];
  2536. // bind extra functionality?
  2537. if (typeof item.show != 'undefined')
  2538. item.show( $dom , t);
  2539. // bind click action
  2540. $dom.click(function() {
  2541. // perform click action
  2542. if (typeof item.click != 'undefined')
  2543. item.click(t);
  2544. // close
  2545. t.contextMenu.hide();
  2546. });
  2547. });
  2548. // stop the controls from hiding
  2549. setTimeout(function() {
  2550. t.killControlsTimer('rev3');
  2551. }, 100);
  2552. }
  2553. });
  2554. })(mejs.$);
  2555. /**
  2556. * Postroll plugin
  2557. */
  2558. (function($) {
  2559. $.extend(mejs.MepDefaults, {
  2560. postrollCloseText: mejs.i18n.t('Close')
  2561. });
  2562. // Postroll
  2563. $.extend(MediaElementPlayer.prototype, {
  2564. buildpostroll: function(player, controls, layers, media) {
  2565. var
  2566. t = this,
  2567. postrollLink = t.container.find('link[rel="postroll"]').attr('href');
  2568. if (typeof postrollLink !== 'undefined') {
  2569. player.postroll =
  2570. $('<div class="mejs-postroll-layer mejs-layer"><a class="mejs-postroll-close" onclick="$(this).parent().hide();return false;">' + t.options.postrollCloseText + '</a><div class="mejs-postroll-layer-content"></div></div>').prependTo(layers).hide();
  2571. t.media.addEventListener('ended', function (e) {
  2572. $.ajax({
  2573. dataType: 'html',
  2574. url: postrollLink,
  2575. success: function (data, textStatus) {
  2576. layers.find('.mejs-postroll-layer-content').html(data);
  2577. }
  2578. });
  2579. player.postroll.show();
  2580. }, false);
  2581. }
  2582. }
  2583. });
  2584. })(mejs.$);