mediaelementplayer.js 100 KB

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