video.es.js 682 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334
  1. /**
  2. * @license
  3. * Video.js 6.13.0 <http://videojs.com/>
  4. * Copyright Brightcove, Inc. <https://www.brightcove.com/>
  5. * Available under Apache License Version 2.0
  6. * <https://github.com/videojs/video.js/blob/master/LICENSE>
  7. *
  8. * Includes vtt.js <https://github.com/mozilla/vtt.js>
  9. * Available under Apache License Version 2.0
  10. * <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
  11. */
  12. import window from 'global/window';
  13. import document from 'global/document';
  14. import tsml from 'tsml';
  15. import safeParseTuple from 'safe-json-parse/tuple';
  16. import xhr from 'xhr';
  17. import vtt from 'videojs-vtt.js';
  18. var version = "6.13.0";
  19. /**
  20. * @file browser.js
  21. * @module browser
  22. */
  23. var USER_AGENT = window.navigator && window.navigator.userAgent || '';
  24. var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
  25. var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
  26. /*
  27. * Device is an iPhone
  28. *
  29. * @type {Boolean}
  30. * @constant
  31. * @private
  32. */
  33. var IS_IPAD = /iPad/i.test(USER_AGENT);
  34. // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
  35. // to identify iPhones, we need to exclude iPads.
  36. // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
  37. var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
  38. var IS_IPOD = /iPod/i.test(USER_AGENT);
  39. var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
  40. var IOS_VERSION = function () {
  41. var match = USER_AGENT.match(/OS (\d+)_/i);
  42. if (match && match[1]) {
  43. return match[1];
  44. }
  45. return null;
  46. }();
  47. var IS_ANDROID = /Android/i.test(USER_AGENT);
  48. var ANDROID_VERSION = function () {
  49. // This matches Android Major.Minor.Patch versions
  50. // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  51. var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
  52. if (!match) {
  53. return null;
  54. }
  55. var major = match[1] && parseFloat(match[1]);
  56. var minor = match[2] && parseFloat(match[2]);
  57. if (major && minor) {
  58. return parseFloat(match[1] + '.' + match[2]);
  59. } else if (major) {
  60. return major;
  61. }
  62. return null;
  63. }();
  64. // Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
  65. var IS_OLD_ANDROID = IS_ANDROID && /webkit/i.test(USER_AGENT) && ANDROID_VERSION < 2.3;
  66. var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
  67. var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
  68. var IS_EDGE = /Edge/i.test(USER_AGENT);
  69. var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
  70. var CHROME_VERSION = function () {
  71. var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
  72. if (match && match[2]) {
  73. return parseFloat(match[2]);
  74. }
  75. return null;
  76. }();
  77. var IS_IE8 = /MSIE\s8\.0/.test(USER_AGENT);
  78. var IE_VERSION = function () {
  79. var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
  80. var version = result && parseFloat(result[1]);
  81. if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
  82. // IE 11 has a different user agent string than other IE versions
  83. version = 11.0;
  84. }
  85. return version;
  86. }();
  87. var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
  88. var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
  89. var TOUCH_ENABLED = isReal() && ('ontouchstart' in window || window.navigator.maxTouchPoints || window.DocumentTouch && window.document instanceof window.DocumentTouch);
  90. var BACKGROUND_SIZE_SUPPORTED = isReal() && 'backgroundSize' in window.document.createElement('video').style;
  91. var browser = (Object.freeze || Object)({
  92. IS_IPAD: IS_IPAD,
  93. IS_IPHONE: IS_IPHONE,
  94. IS_IPOD: IS_IPOD,
  95. IS_IOS: IS_IOS,
  96. IOS_VERSION: IOS_VERSION,
  97. IS_ANDROID: IS_ANDROID,
  98. ANDROID_VERSION: ANDROID_VERSION,
  99. IS_OLD_ANDROID: IS_OLD_ANDROID,
  100. IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
  101. IS_FIREFOX: IS_FIREFOX,
  102. IS_EDGE: IS_EDGE,
  103. IS_CHROME: IS_CHROME,
  104. CHROME_VERSION: CHROME_VERSION,
  105. IS_IE8: IS_IE8,
  106. IE_VERSION: IE_VERSION,
  107. IS_SAFARI: IS_SAFARI,
  108. IS_ANY_SAFARI: IS_ANY_SAFARI,
  109. TOUCH_ENABLED: TOUCH_ENABLED,
  110. BACKGROUND_SIZE_SUPPORTED: BACKGROUND_SIZE_SUPPORTED
  111. });
  112. var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
  113. return typeof obj;
  114. } : function (obj) {
  115. return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
  116. };
  117. var classCallCheck = function (instance, Constructor) {
  118. if (!(instance instanceof Constructor)) {
  119. throw new TypeError("Cannot call a class as a function");
  120. }
  121. };
  122. var inherits = function (subClass, superClass) {
  123. if (typeof superClass !== "function" && superClass !== null) {
  124. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  125. }
  126. subClass.prototype = Object.create(superClass && superClass.prototype, {
  127. constructor: {
  128. value: subClass,
  129. enumerable: false,
  130. writable: true,
  131. configurable: true
  132. }
  133. });
  134. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  135. };
  136. var possibleConstructorReturn = function (self, call) {
  137. if (!self) {
  138. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  139. }
  140. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  141. };
  142. var taggedTemplateLiteralLoose = function (strings, raw) {
  143. strings.raw = raw;
  144. return strings;
  145. };
  146. /**
  147. * @file obj.js
  148. * @module obj
  149. */
  150. /**
  151. * @callback obj:EachCallback
  152. *
  153. * @param {Mixed} value
  154. * The current key for the object that is being iterated over.
  155. *
  156. * @param {string} key
  157. * The current key-value for object that is being iterated over
  158. */
  159. /**
  160. * @callback obj:ReduceCallback
  161. *
  162. * @param {Mixed} accum
  163. * The value that is accumulating over the reduce loop.
  164. *
  165. * @param {Mixed} value
  166. * The current key for the object that is being iterated over.
  167. *
  168. * @param {string} key
  169. * The current key-value for object that is being iterated over
  170. *
  171. * @return {Mixed}
  172. * The new accumulated value.
  173. */
  174. var toString = Object.prototype.toString;
  175. /**
  176. * Get the keys of an Object
  177. *
  178. * @param {Object}
  179. * The Object to get the keys from
  180. *
  181. * @return {string[]}
  182. * An array of the keys from the object. Returns an empty array if the
  183. * object passed in was invalid or had no keys.
  184. *
  185. * @private
  186. */
  187. var keys = function keys(object) {
  188. return isObject(object) ? Object.keys(object) : [];
  189. };
  190. /**
  191. * Array-like iteration for objects.
  192. *
  193. * @param {Object} object
  194. * The object to iterate over
  195. *
  196. * @param {obj:EachCallback} fn
  197. * The callback function which is called for each key in the object.
  198. */
  199. function each(object, fn) {
  200. keys(object).forEach(function (key) {
  201. return fn(object[key], key);
  202. });
  203. }
  204. /**
  205. * Array-like reduce for objects.
  206. *
  207. * @param {Object} object
  208. * The Object that you want to reduce.
  209. *
  210. * @param {Function} fn
  211. * A callback function which is called for each key in the object. It
  212. * receives the accumulated value and the per-iteration value and key
  213. * as arguments.
  214. *
  215. * @param {Mixed} [initial = 0]
  216. * Starting value
  217. *
  218. * @return {Mixed}
  219. * The final accumulated value.
  220. */
  221. function reduce(object, fn) {
  222. var initial = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  223. return keys(object).reduce(function (accum, key) {
  224. return fn(accum, object[key], key);
  225. }, initial);
  226. }
  227. /**
  228. * Object.assign-style object shallow merge/extend.
  229. *
  230. * @param {Object} target
  231. * @param {Object} ...sources
  232. * @return {Object}
  233. */
  234. function assign(target) {
  235. for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  236. sources[_key - 1] = arguments[_key];
  237. }
  238. if (Object.assign) {
  239. return Object.assign.apply(Object, [target].concat(sources));
  240. }
  241. sources.forEach(function (source) {
  242. if (!source) {
  243. return;
  244. }
  245. each(source, function (value, key) {
  246. target[key] = value;
  247. });
  248. });
  249. return target;
  250. }
  251. /**
  252. * Returns whether a value is an object of any kind - including DOM nodes,
  253. * arrays, regular expressions, etc. Not functions, though.
  254. *
  255. * This avoids the gotcha where using `typeof` on a `null` value
  256. * results in `'object'`.
  257. *
  258. * @param {Object} value
  259. * @return {Boolean}
  260. */
  261. function isObject(value) {
  262. return !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object';
  263. }
  264. /**
  265. * Returns whether an object appears to be a "plain" object - that is, a
  266. * direct instance of `Object`.
  267. *
  268. * @param {Object} value
  269. * @return {Boolean}
  270. */
  271. function isPlain(value) {
  272. return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
  273. }
  274. /**
  275. * @file create-logger.js
  276. * @module create-logger
  277. */
  278. // This is the private tracking variable for the logging history.
  279. var history = [];
  280. /**
  281. * Log messages to the console and history based on the type of message
  282. *
  283. * @private
  284. * @param {string} type
  285. * The name of the console method to use.
  286. *
  287. * @param {Array} args
  288. * The arguments to be passed to the matching console method.
  289. */
  290. var LogByTypeFactory = function LogByTypeFactory(name, log) {
  291. return function (type, level, args, stringify) {
  292. var lvl = log.levels[level];
  293. var lvlRegExp = new RegExp('^(' + lvl + ')$');
  294. if (type !== 'log') {
  295. // Add the type to the front of the message when it's not "log".
  296. args.unshift(type.toUpperCase() + ':');
  297. }
  298. // Add console prefix after adding to history.
  299. args.unshift(name + ':');
  300. // Add a clone of the args at this point to history.
  301. if (history) {
  302. history.push([].concat(args));
  303. }
  304. // If there's no console then don't try to output messages, but they will
  305. // still be stored in history.
  306. if (!window.console) {
  307. return;
  308. }
  309. // Was setting these once outside of this function, but containing them
  310. // in the function makes it easier to test cases where console doesn't exist
  311. // when the module is executed.
  312. var fn = window.console[type];
  313. if (!fn && type === 'debug') {
  314. // Certain browsers don't have support for console.debug. For those, we
  315. // should default to the closest comparable log.
  316. fn = window.console.info || window.console.log;
  317. }
  318. // Bail out if there's no console or if this type is not allowed by the
  319. // current logging level.
  320. if (!fn || !lvl || !lvlRegExp.test(type)) {
  321. return;
  322. }
  323. // IEs previous to 11 log objects uselessly as "[object Object]"; so, JSONify
  324. // objects and arrays for those less-capable browsers.
  325. if (stringify) {
  326. args = args.map(function (a) {
  327. if (isObject(a) || Array.isArray(a)) {
  328. try {
  329. return JSON.stringify(a);
  330. } catch (x) {
  331. return String(a);
  332. }
  333. }
  334. // Cast to string before joining, so we get null and undefined explicitly
  335. // included in output (as we would in a modern console).
  336. return String(a);
  337. }).join(' ');
  338. }
  339. // Old IE versions do not allow .apply() for console methods (they are
  340. // reported as objects rather than functions).
  341. if (!fn.apply) {
  342. fn(args);
  343. } else {
  344. fn[Array.isArray(args) ? 'apply' : 'call'](window.console, args);
  345. }
  346. };
  347. };
  348. function createLogger$1(name) {
  349. // This is the private tracking variable for logging level.
  350. var level = 'info';
  351. // the curried logByType bound to the specific log and history
  352. var logByType = void 0;
  353. /**
  354. * Logs plain debug messages. Similar to `console.log`.
  355. *
  356. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  357. * of our JSDoc template, we cannot properly document this as both a function
  358. * and a namespace, so its function signature is documented here.
  359. *
  360. * #### Arguments
  361. * ##### *args
  362. * Mixed[]
  363. *
  364. * Any combination of values that could be passed to `console.log()`.
  365. *
  366. * #### Return Value
  367. *
  368. * `undefined`
  369. *
  370. * @namespace
  371. * @param {Mixed[]} args
  372. * One or more messages or objects that should be logged.
  373. */
  374. var log = function log() {
  375. var stringify = log.stringify || IE_VERSION && IE_VERSION < 11;
  376. for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
  377. args[_key] = arguments[_key];
  378. }
  379. logByType('log', level, args, stringify);
  380. };
  381. // This is the logByType helper that the logging methods below use
  382. logByType = LogByTypeFactory(name, log);
  383. /**
  384. * Create a new sublogger which chains the old name to the new name.
  385. *
  386. * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
  387. * ```js
  388. * mylogger('foo');
  389. * // > VIDEOJS: player: foo
  390. * ```
  391. *
  392. * @param {string} name
  393. * The name to add call the new logger
  394. * @return {Object}
  395. */
  396. log.createLogger = function (subname) {
  397. return createLogger$1(name + ': ' + subname);
  398. };
  399. /**
  400. * Enumeration of available logging levels, where the keys are the level names
  401. * and the values are `|`-separated strings containing logging methods allowed
  402. * in that logging level. These strings are used to create a regular expression
  403. * matching the function name being called.
  404. *
  405. * Levels provided by Video.js are:
  406. *
  407. * - `off`: Matches no calls. Any value that can be cast to `false` will have
  408. * this effect. The most restrictive.
  409. * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
  410. * `log.warn`, and `log.error`).
  411. * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
  412. * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
  413. * - `warn`: Matches `log.warn` and `log.error` calls.
  414. * - `error`: Matches only `log.error` calls.
  415. *
  416. * @type {Object}
  417. */
  418. log.levels = {
  419. all: 'debug|log|warn|error',
  420. off: '',
  421. debug: 'debug|log|warn|error',
  422. info: 'log|warn|error',
  423. warn: 'warn|error',
  424. error: 'error',
  425. DEFAULT: level
  426. };
  427. /**
  428. * Get or set the current logging level.
  429. *
  430. * If a string matching a key from {@link module:log.levels} is provided, acts
  431. * as a setter.
  432. *
  433. * @param {string} [lvl]
  434. * Pass a valid level to set a new logging level.
  435. *
  436. * @return {string}
  437. * The current logging level.
  438. */
  439. log.level = function (lvl) {
  440. if (typeof lvl === 'string') {
  441. if (!log.levels.hasOwnProperty(lvl)) {
  442. throw new Error('"' + lvl + '" in not a valid log level');
  443. }
  444. level = lvl;
  445. }
  446. return level;
  447. };
  448. /**
  449. * Returns an array containing everything that has been logged to the history.
  450. *
  451. * This array is a shallow clone of the internal history record. However, its
  452. * contents are _not_ cloned; so, mutating objects inside this array will
  453. * mutate them in history.
  454. *
  455. * @return {Array}
  456. */
  457. log.history = function () {
  458. return history ? [].concat(history) : [];
  459. };
  460. /**
  461. * Allows you to filter the history by the given logger name
  462. *
  463. * @param {string} fname
  464. * The name to filter by
  465. *
  466. * @return {Array}
  467. * The filtered list to return
  468. */
  469. log.history.filter = function (fname) {
  470. return (history || []).filter(function (historyItem) {
  471. // if the first item in each historyItem includes `fname`, then it's a match
  472. return new RegExp('.*' + fname + '.*').test(historyItem[0]);
  473. });
  474. };
  475. /**
  476. * Clears the internal history tracking, but does not prevent further history
  477. * tracking.
  478. */
  479. log.history.clear = function () {
  480. if (history) {
  481. history.length = 0;
  482. }
  483. };
  484. /**
  485. * Disable history tracking if it is currently enabled.
  486. */
  487. log.history.disable = function () {
  488. if (history !== null) {
  489. history.length = 0;
  490. history = null;
  491. }
  492. };
  493. /**
  494. * Enable history tracking if it is currently disabled.
  495. */
  496. log.history.enable = function () {
  497. if (history === null) {
  498. history = [];
  499. }
  500. };
  501. /**
  502. * Logs error messages. Similar to `console.error`.
  503. *
  504. * @param {Mixed[]} args
  505. * One or more messages or objects that should be logged as an error
  506. */
  507. log.error = function () {
  508. for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  509. args[_key2] = arguments[_key2];
  510. }
  511. return logByType('error', level, args);
  512. };
  513. /**
  514. * Logs warning messages. Similar to `console.warn`.
  515. *
  516. * @param {Mixed[]} args
  517. * One or more messages or objects that should be logged as a warning.
  518. */
  519. log.warn = function () {
  520. for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  521. args[_key3] = arguments[_key3];
  522. }
  523. return logByType('warn', level, args);
  524. };
  525. /**
  526. * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
  527. * log if `console.debug` is not available
  528. *
  529. * @param {Mixed[]} args
  530. * One or more messages or objects that should be logged as debug.
  531. */
  532. log.debug = function () {
  533. for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
  534. args[_key4] = arguments[_key4];
  535. }
  536. return logByType('debug', level, args);
  537. };
  538. return log;
  539. }
  540. /**
  541. * @file log.js
  542. * @module log
  543. */
  544. var log = createLogger$1('VIDEOJS');
  545. var createLogger = log.createLogger;
  546. /**
  547. * @file computed-style.js
  548. * @module computed-style
  549. */
  550. /**
  551. * A safe getComputedStyle with an IE8 fallback.
  552. *
  553. * This is needed because in Firefox, if the player is loaded in an iframe with
  554. * `display:none`, then `getComputedStyle` returns `null`, so, we do a null-check to
  555. * make sure that the player doesn't break in these cases.
  556. *
  557. * @param {Element} el
  558. * The element you want the computed style of
  559. *
  560. * @param {string} prop
  561. * The property name you want
  562. *
  563. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  564. *
  565. * @static
  566. * @const
  567. */
  568. function computedStyle(el, prop) {
  569. if (!el || !prop) {
  570. return '';
  571. }
  572. if (typeof window.getComputedStyle === 'function') {
  573. var cs = window.getComputedStyle(el);
  574. return cs ? cs[prop] : '';
  575. }
  576. return el.currentStyle[prop] || '';
  577. }
  578. var _templateObject = taggedTemplateLiteralLoose(['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.'], ['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.']);
  579. /**
  580. * @file dom.js
  581. * @module dom
  582. */
  583. /**
  584. * Detect if a value is a string with any non-whitespace characters.
  585. *
  586. * @param {string} str
  587. * The string to check
  588. *
  589. * @return {boolean}
  590. * - True if the string is non-blank
  591. * - False otherwise
  592. *
  593. */
  594. function isNonBlankString(str) {
  595. return typeof str === 'string' && /\S/.test(str);
  596. }
  597. /**
  598. * Throws an error if the passed string has whitespace. This is used by
  599. * class methods to be relatively consistent with the classList API.
  600. *
  601. * @param {string} str
  602. * The string to check for whitespace.
  603. *
  604. * @throws {Error}
  605. * Throws an error if there is whitespace in the string.
  606. *
  607. */
  608. function throwIfWhitespace(str) {
  609. if (/\s/.test(str)) {
  610. throw new Error('class has illegal whitespace characters');
  611. }
  612. }
  613. /**
  614. * Produce a regular expression for matching a className within an elements className.
  615. *
  616. * @param {string} className
  617. * The className to generate the RegExp for.
  618. *
  619. * @return {RegExp}
  620. * The RegExp that will check for a specific `className` in an elements
  621. * className.
  622. */
  623. function classRegExp(className) {
  624. return new RegExp('(^|\\s)' + className + '($|\\s)');
  625. }
  626. /**
  627. * Whether the current DOM interface appears to be real.
  628. *
  629. * @return {Boolean}
  630. */
  631. function isReal() {
  632. return (
  633. // Both document and window will never be undefined thanks to `global`.
  634. document === window.document &&
  635. // In IE < 9, DOM methods return "object" as their type, so all we can
  636. // confidently check is that it exists.
  637. typeof document.createElement !== 'undefined'
  638. );
  639. }
  640. /**
  641. * Determines, via duck typing, whether or not a value is a DOM element.
  642. *
  643. * @param {Mixed} value
  644. * The thing to check
  645. *
  646. * @return {boolean}
  647. * - True if it is a DOM element
  648. * - False otherwise
  649. */
  650. function isEl(value) {
  651. return isObject(value) && value.nodeType === 1;
  652. }
  653. /**
  654. * Determines if the current DOM is embedded in an iframe.
  655. *
  656. * @return {boolean}
  657. *
  658. */
  659. function isInFrame() {
  660. // We need a try/catch here because Safari will throw errors when attempting
  661. // to get either `parent` or `self`
  662. try {
  663. return window.parent !== window.self;
  664. } catch (x) {
  665. return true;
  666. }
  667. }
  668. /**
  669. * Creates functions to query the DOM using a given method.
  670. *
  671. * @param {string} method
  672. * The method to create the query with.
  673. *
  674. * @return {Function}
  675. * The query method
  676. */
  677. function createQuerier(method) {
  678. return function (selector, context) {
  679. if (!isNonBlankString(selector)) {
  680. return document[method](null);
  681. }
  682. if (isNonBlankString(context)) {
  683. context = document.querySelector(context);
  684. }
  685. var ctx = isEl(context) ? context : document;
  686. return ctx[method] && ctx[method](selector);
  687. };
  688. }
  689. /**
  690. * Creates an element and applies properties.
  691. *
  692. * @param {string} [tagName='div']
  693. * Name of tag to be created.
  694. *
  695. * @param {Object} [properties={}]
  696. * Element properties to be applied.
  697. *
  698. * @param {Object} [attributes={}]
  699. * Element attributes to be applied.
  700. *
  701. * @param {String|Element|TextNode|Array|Function} [content]
  702. * Contents for the element (see: {@link dom:normalizeContent})
  703. *
  704. * @return {Element}
  705. * The element that was created.
  706. */
  707. function createEl() {
  708. var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';
  709. var properties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  710. var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  711. var content = arguments[3];
  712. var el = document.createElement(tagName);
  713. Object.getOwnPropertyNames(properties).forEach(function (propName) {
  714. var val = properties[propName];
  715. // See #2176
  716. // We originally were accepting both properties and attributes in the
  717. // same object, but that doesn't work so well.
  718. if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
  719. log.warn(tsml(_templateObject, propName, val));
  720. el.setAttribute(propName, val);
  721. // Handle textContent since it's not supported everywhere and we have a
  722. // method for it.
  723. } else if (propName === 'textContent') {
  724. textContent(el, val);
  725. } else {
  726. el[propName] = val;
  727. }
  728. });
  729. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  730. el.setAttribute(attrName, attributes[attrName]);
  731. });
  732. if (content) {
  733. appendContent(el, content);
  734. }
  735. return el;
  736. }
  737. /**
  738. * Injects text into an element, replacing any existing contents entirely.
  739. *
  740. * @param {Element} el
  741. * The element to add text content into
  742. *
  743. * @param {string} text
  744. * The text content to add.
  745. *
  746. * @return {Element}
  747. * The element with added text content.
  748. */
  749. function textContent(el, text) {
  750. if (typeof el.textContent === 'undefined') {
  751. el.innerText = text;
  752. } else {
  753. el.textContent = text;
  754. }
  755. return el;
  756. }
  757. /**
  758. * Insert an element as the first child node of another
  759. *
  760. * @param {Element} child
  761. * Element to insert
  762. *
  763. * @param {Element} parent
  764. * Element to insert child into
  765. */
  766. function prependTo(child, parent) {
  767. if (parent.firstChild) {
  768. parent.insertBefore(child, parent.firstChild);
  769. } else {
  770. parent.appendChild(child);
  771. }
  772. }
  773. /**
  774. * Check if an element has a CSS class
  775. *
  776. * @param {Element} element
  777. * Element to check
  778. *
  779. * @param {string} classToCheck
  780. * Class name to check for
  781. *
  782. * @return {boolean}
  783. * - True if the element had the class
  784. * - False otherwise.
  785. *
  786. * @throws {Error}
  787. * Throws an error if `classToCheck` has white space.
  788. */
  789. function hasClass(element, classToCheck) {
  790. throwIfWhitespace(classToCheck);
  791. if (element.classList) {
  792. return element.classList.contains(classToCheck);
  793. }
  794. return classRegExp(classToCheck).test(element.className);
  795. }
  796. /**
  797. * Add a CSS class name to an element
  798. *
  799. * @param {Element} element
  800. * Element to add class name to.
  801. *
  802. * @param {string} classToAdd
  803. * Class name to add.
  804. *
  805. * @return {Element}
  806. * The dom element with the added class name.
  807. */
  808. function addClass(element, classToAdd) {
  809. if (element.classList) {
  810. element.classList.add(classToAdd);
  811. // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
  812. // in the case of classList not being supported.
  813. } else if (!hasClass(element, classToAdd)) {
  814. element.className = (element.className + ' ' + classToAdd).trim();
  815. }
  816. return element;
  817. }
  818. /**
  819. * Remove a CSS class name from an element
  820. *
  821. * @param {Element} element
  822. * Element to remove a class name from.
  823. *
  824. * @param {string} classToRemove
  825. * Class name to remove
  826. *
  827. * @return {Element}
  828. * The dom element with class name removed.
  829. */
  830. function removeClass(element, classToRemove) {
  831. if (element.classList) {
  832. element.classList.remove(classToRemove);
  833. } else {
  834. throwIfWhitespace(classToRemove);
  835. element.className = element.className.split(/\s+/).filter(function (c) {
  836. return c !== classToRemove;
  837. }).join(' ');
  838. }
  839. return element;
  840. }
  841. /**
  842. * The callback definition for toggleElClass.
  843. *
  844. * @callback Dom~PredicateCallback
  845. * @param {Element} element
  846. * The DOM element of the Component.
  847. *
  848. * @param {string} classToToggle
  849. * The `className` that wants to be toggled
  850. *
  851. * @return {boolean|undefined}
  852. * - If true the `classToToggle` will get added to `element`.
  853. * - If false the `classToToggle` will get removed from `element`.
  854. * - If undefined this callback will be ignored
  855. */
  856. /**
  857. * Adds or removes a CSS class name on an element depending on an optional
  858. * condition or the presence/absence of the class name.
  859. *
  860. * @param {Element} element
  861. * The element to toggle a class name on.
  862. *
  863. * @param {string} classToToggle
  864. * The class that should be toggled
  865. *
  866. * @param {boolean|PredicateCallback} [predicate]
  867. * See the return value for {@link Dom~PredicateCallback}
  868. *
  869. * @return {Element}
  870. * The element with a class that has been toggled.
  871. */
  872. function toggleClass(element, classToToggle, predicate) {
  873. // This CANNOT use `classList` internally because IE does not support the
  874. // second parameter to the `classList.toggle()` method! Which is fine because
  875. // `classList` will be used by the add/remove functions.
  876. var has = hasClass(element, classToToggle);
  877. if (typeof predicate === 'function') {
  878. predicate = predicate(element, classToToggle);
  879. }
  880. if (typeof predicate !== 'boolean') {
  881. predicate = !has;
  882. }
  883. // If the necessary class operation matches the current state of the
  884. // element, no action is required.
  885. if (predicate === has) {
  886. return;
  887. }
  888. if (predicate) {
  889. addClass(element, classToToggle);
  890. } else {
  891. removeClass(element, classToToggle);
  892. }
  893. return element;
  894. }
  895. /**
  896. * Apply attributes to an HTML element.
  897. *
  898. * @param {Element} el
  899. * Element to add attributes to.
  900. *
  901. * @param {Object} [attributes]
  902. * Attributes to be applied.
  903. */
  904. function setAttributes(el, attributes) {
  905. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  906. var attrValue = attributes[attrName];
  907. if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
  908. el.removeAttribute(attrName);
  909. } else {
  910. el.setAttribute(attrName, attrValue === true ? '' : attrValue);
  911. }
  912. });
  913. }
  914. /**
  915. * Get an element's attribute values, as defined on the HTML tag
  916. * Attributes are not the same as properties. They're defined on the tag
  917. * or with setAttribute (which shouldn't be used with HTML)
  918. * This will return true or false for boolean attributes.
  919. *
  920. * @param {Element} tag
  921. * Element from which to get tag attributes.
  922. *
  923. * @return {Object}
  924. * All attributes of the element.
  925. */
  926. function getAttributes(tag) {
  927. var obj = {};
  928. // known boolean attributes
  929. // we can check for matching boolean properties, but older browsers
  930. // won't know about HTML5 boolean attributes that we still read from
  931. var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
  932. if (tag && tag.attributes && tag.attributes.length > 0) {
  933. var attrs = tag.attributes;
  934. for (var i = attrs.length - 1; i >= 0; i--) {
  935. var attrName = attrs[i].name;
  936. var attrVal = attrs[i].value;
  937. // check for known booleans
  938. // the matching element property will return a value for typeof
  939. if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
  940. // the value of an included boolean attribute is typically an empty
  941. // string ('') which would equal false if we just check for a false value.
  942. // we also don't want support bad code like autoplay='false'
  943. attrVal = attrVal !== null ? true : false;
  944. }
  945. obj[attrName] = attrVal;
  946. }
  947. }
  948. return obj;
  949. }
  950. /**
  951. * Get the value of an element's attribute
  952. *
  953. * @param {Element} el
  954. * A DOM element
  955. *
  956. * @param {string} attribute
  957. * Attribute to get the value of
  958. *
  959. * @return {string}
  960. * value of the attribute
  961. */
  962. function getAttribute(el, attribute) {
  963. return el.getAttribute(attribute);
  964. }
  965. /**
  966. * Set the value of an element's attribute
  967. *
  968. * @param {Element} el
  969. * A DOM element
  970. *
  971. * @param {string} attribute
  972. * Attribute to set
  973. *
  974. * @param {string} value
  975. * Value to set the attribute to
  976. */
  977. function setAttribute(el, attribute, value) {
  978. el.setAttribute(attribute, value);
  979. }
  980. /**
  981. * Remove an element's attribute
  982. *
  983. * @param {Element} el
  984. * A DOM element
  985. *
  986. * @param {string} attribute
  987. * Attribute to remove
  988. */
  989. function removeAttribute(el, attribute) {
  990. el.removeAttribute(attribute);
  991. }
  992. /**
  993. * Attempt to block the ability to select text while dragging controls
  994. */
  995. function blockTextSelection() {
  996. document.body.focus();
  997. document.onselectstart = function () {
  998. return false;
  999. };
  1000. }
  1001. /**
  1002. * Turn off text selection blocking
  1003. */
  1004. function unblockTextSelection() {
  1005. document.onselectstart = function () {
  1006. return true;
  1007. };
  1008. }
  1009. /**
  1010. * Identical to the native `getBoundingClientRect` function, but ensures that
  1011. * the method is supported at all (it is in all browsers we claim to support)
  1012. * and that the element is in the DOM before continuing.
  1013. *
  1014. * This wrapper function also shims properties which are not provided by some
  1015. * older browsers (namely, IE8).
  1016. *
  1017. * Additionally, some browsers do not support adding properties to a
  1018. * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
  1019. * properties (except `x` and `y` which are not widely supported). This helps
  1020. * avoid implementations where keys are non-enumerable.
  1021. *
  1022. * @param {Element} el
  1023. * Element whose `ClientRect` we want to calculate.
  1024. *
  1025. * @return {Object|undefined}
  1026. * Always returns a plain
  1027. */
  1028. function getBoundingClientRect(el) {
  1029. if (el && el.getBoundingClientRect && el.parentNode) {
  1030. var rect = el.getBoundingClientRect();
  1031. var result = {};
  1032. ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
  1033. if (rect[k] !== undefined) {
  1034. result[k] = rect[k];
  1035. }
  1036. });
  1037. if (!result.height) {
  1038. result.height = parseFloat(computedStyle(el, 'height'));
  1039. }
  1040. if (!result.width) {
  1041. result.width = parseFloat(computedStyle(el, 'width'));
  1042. }
  1043. return result;
  1044. }
  1045. }
  1046. /**
  1047. * The postion of a DOM element on the page.
  1048. *
  1049. * @typedef {Object} module:dom~Position
  1050. *
  1051. * @property {number} left
  1052. * Pixels to the left
  1053. *
  1054. * @property {number} top
  1055. * Pixels on top
  1056. */
  1057. /**
  1058. * Offset Left.
  1059. * getBoundingClientRect technique from
  1060. * John Resig
  1061. *
  1062. * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
  1063. *
  1064. * @param {Element} el
  1065. * Element from which to get offset
  1066. *
  1067. * @return {module:dom~Position}
  1068. * The position of the element that was passed in.
  1069. */
  1070. function findPosition(el) {
  1071. var box = void 0;
  1072. if (el.getBoundingClientRect && el.parentNode) {
  1073. box = el.getBoundingClientRect();
  1074. }
  1075. if (!box) {
  1076. return {
  1077. left: 0,
  1078. top: 0
  1079. };
  1080. }
  1081. var docEl = document.documentElement;
  1082. var body = document.body;
  1083. var clientLeft = docEl.clientLeft || body.clientLeft || 0;
  1084. var scrollLeft = window.pageXOffset || body.scrollLeft;
  1085. var left = box.left + scrollLeft - clientLeft;
  1086. var clientTop = docEl.clientTop || body.clientTop || 0;
  1087. var scrollTop = window.pageYOffset || body.scrollTop;
  1088. var top = box.top + scrollTop - clientTop;
  1089. // Android sometimes returns slightly off decimal values, so need to round
  1090. return {
  1091. left: Math.round(left),
  1092. top: Math.round(top)
  1093. };
  1094. }
  1095. /**
  1096. * x and y coordinates for a dom element or mouse pointer
  1097. *
  1098. * @typedef {Object} Dom~Coordinates
  1099. *
  1100. * @property {number} x
  1101. * x coordinate in pixels
  1102. *
  1103. * @property {number} y
  1104. * y coordinate in pixels
  1105. */
  1106. /**
  1107. * Get pointer position in element
  1108. * Returns an object with x and y coordinates.
  1109. * The base on the coordinates are the bottom left of the element.
  1110. *
  1111. * @param {Element} el
  1112. * Element on which to get the pointer position on
  1113. *
  1114. * @param {EventTarget~Event} event
  1115. * Event object
  1116. *
  1117. * @return {Dom~Coordinates}
  1118. * A Coordinates object corresponding to the mouse position.
  1119. *
  1120. */
  1121. function getPointerPosition(el, event) {
  1122. var position = {};
  1123. var box = findPosition(el);
  1124. var boxW = el.offsetWidth;
  1125. var boxH = el.offsetHeight;
  1126. var boxY = box.top;
  1127. var boxX = box.left;
  1128. var pageY = event.pageY;
  1129. var pageX = event.pageX;
  1130. if (event.changedTouches) {
  1131. pageX = event.changedTouches[0].pageX;
  1132. pageY = event.changedTouches[0].pageY;
  1133. }
  1134. position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
  1135. position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
  1136. return position;
  1137. }
  1138. /**
  1139. * Determines, via duck typing, whether or not a value is a text node.
  1140. *
  1141. * @param {Mixed} value
  1142. * Check if this value is a text node.
  1143. *
  1144. * @return {boolean}
  1145. * - True if it is a text node
  1146. * - False otherwise
  1147. */
  1148. function isTextNode(value) {
  1149. return isObject(value) && value.nodeType === 3;
  1150. }
  1151. /**
  1152. * Empties the contents of an element.
  1153. *
  1154. * @param {Element} el
  1155. * The element to empty children from
  1156. *
  1157. * @return {Element}
  1158. * The element with no children
  1159. */
  1160. function emptyEl(el) {
  1161. while (el.firstChild) {
  1162. el.removeChild(el.firstChild);
  1163. }
  1164. return el;
  1165. }
  1166. /**
  1167. * Normalizes content for eventual insertion into the DOM.
  1168. *
  1169. * This allows a wide range of content definition methods, but protects
  1170. * from falling into the trap of simply writing to `innerHTML`, which is
  1171. * an XSS concern.
  1172. *
  1173. * The content for an element can be passed in multiple types and
  1174. * combinations, whose behavior is as follows:
  1175. *
  1176. * @param {String|Element|TextNode|Array|Function} content
  1177. * - String: Normalized into a text node.
  1178. * - Element/TextNode: Passed through.
  1179. * - Array: A one-dimensional array of strings, elements, nodes, or functions
  1180. * (which return single strings, elements, or nodes).
  1181. * - Function: If the sole argument, is expected to produce a string, element,
  1182. * node, or array as defined above.
  1183. *
  1184. * @return {Array}
  1185. * All of the content that was passed in normalized.
  1186. */
  1187. function normalizeContent(content) {
  1188. // First, invoke content if it is a function. If it produces an array,
  1189. // that needs to happen before normalization.
  1190. if (typeof content === 'function') {
  1191. content = content();
  1192. }
  1193. // Next up, normalize to an array, so one or many items can be normalized,
  1194. // filtered, and returned.
  1195. return (Array.isArray(content) ? content : [content]).map(function (value) {
  1196. // First, invoke value if it is a function to produce a new value,
  1197. // which will be subsequently normalized to a Node of some kind.
  1198. if (typeof value === 'function') {
  1199. value = value();
  1200. }
  1201. if (isEl(value) || isTextNode(value)) {
  1202. return value;
  1203. }
  1204. if (typeof value === 'string' && /\S/.test(value)) {
  1205. return document.createTextNode(value);
  1206. }
  1207. }).filter(function (value) {
  1208. return value;
  1209. });
  1210. }
  1211. /**
  1212. * Normalizes and appends content to an element.
  1213. *
  1214. * @param {Element} el
  1215. * Element to append normalized content to.
  1216. *
  1217. *
  1218. * @param {String|Element|TextNode|Array|Function} content
  1219. * See the `content` argument of {@link dom:normalizeContent}
  1220. *
  1221. * @return {Element}
  1222. * The element with appended normalized content.
  1223. */
  1224. function appendContent(el, content) {
  1225. normalizeContent(content).forEach(function (node) {
  1226. return el.appendChild(node);
  1227. });
  1228. return el;
  1229. }
  1230. /**
  1231. * Normalizes and inserts content into an element; this is identical to
  1232. * `appendContent()`, except it empties the element first.
  1233. *
  1234. * @param {Element} el
  1235. * Element to insert normalized content into.
  1236. *
  1237. * @param {String|Element|TextNode|Array|Function} content
  1238. * See the `content` argument of {@link dom:normalizeContent}
  1239. *
  1240. * @return {Element}
  1241. * The element with inserted normalized content.
  1242. *
  1243. */
  1244. function insertContent(el, content) {
  1245. return appendContent(emptyEl(el), content);
  1246. }
  1247. /**
  1248. * Check if event was a single left click
  1249. *
  1250. * @param {EventTarget~Event} event
  1251. * Event object
  1252. *
  1253. * @return {boolean}
  1254. * - True if a left click
  1255. * - False if not a left click
  1256. */
  1257. function isSingleLeftClick(event) {
  1258. // Note: if you create something draggable, be sure to
  1259. // call it on both `mousedown` and `mousemove` event,
  1260. // otherwise `mousedown` should be enough for a button
  1261. if (event.button === undefined && event.buttons === undefined) {
  1262. // Why do we need `buttons` ?
  1263. // Because, middle mouse sometimes have this:
  1264. // e.button === 0 and e.buttons === 4
  1265. // Furthermore, we want to prevent combination click, something like
  1266. // HOLD middlemouse then left click, that would be
  1267. // e.button === 0, e.buttons === 5
  1268. // just `button` is not gonna work
  1269. // Alright, then what this block does ?
  1270. // this is for chrome `simulate mobile devices`
  1271. // I want to support this as well
  1272. return true;
  1273. }
  1274. if (event.button === 0 && event.buttons === undefined) {
  1275. // Touch screen, sometimes on some specific device, `buttons`
  1276. // doesn't have anything (safari on ios, blackberry...)
  1277. return true;
  1278. }
  1279. if (IE_VERSION === 9) {
  1280. // Ignore IE9
  1281. return true;
  1282. }
  1283. if (event.button !== 0 || event.buttons !== 1) {
  1284. // This is the reason we have those if else block above
  1285. // if any special case we can catch and let it slide
  1286. // we do it above, when get to here, this definitely
  1287. // is-not-left-click
  1288. return false;
  1289. }
  1290. return true;
  1291. }
  1292. /**
  1293. * Finds a single DOM element matching `selector` within the optional
  1294. * `context` of another DOM element (defaulting to `document`).
  1295. *
  1296. * @param {string} selector
  1297. * A valid CSS selector, which will be passed to `querySelector`.
  1298. *
  1299. * @param {Element|String} [context=document]
  1300. * A DOM element within which to query. Can also be a selector
  1301. * string in which case the first matching element will be used
  1302. * as context. If missing (or no element matches selector), falls
  1303. * back to `document`.
  1304. *
  1305. * @return {Element|null}
  1306. * The element that was found or null.
  1307. */
  1308. var $ = createQuerier('querySelector');
  1309. /**
  1310. * Finds a all DOM elements matching `selector` within the optional
  1311. * `context` of another DOM element (defaulting to `document`).
  1312. *
  1313. * @param {string} selector
  1314. * A valid CSS selector, which will be passed to `querySelectorAll`.
  1315. *
  1316. * @param {Element|String} [context=document]
  1317. * A DOM element within which to query. Can also be a selector
  1318. * string in which case the first matching element will be used
  1319. * as context. If missing (or no element matches selector), falls
  1320. * back to `document`.
  1321. *
  1322. * @return {NodeList}
  1323. * A element list of elements that were found. Will be empty if none were found.
  1324. *
  1325. */
  1326. var $$ = createQuerier('querySelectorAll');
  1327. var Dom = (Object.freeze || Object)({
  1328. isReal: isReal,
  1329. isEl: isEl,
  1330. isInFrame: isInFrame,
  1331. createEl: createEl,
  1332. textContent: textContent,
  1333. prependTo: prependTo,
  1334. hasClass: hasClass,
  1335. addClass: addClass,
  1336. removeClass: removeClass,
  1337. toggleClass: toggleClass,
  1338. setAttributes: setAttributes,
  1339. getAttributes: getAttributes,
  1340. getAttribute: getAttribute,
  1341. setAttribute: setAttribute,
  1342. removeAttribute: removeAttribute,
  1343. blockTextSelection: blockTextSelection,
  1344. unblockTextSelection: unblockTextSelection,
  1345. getBoundingClientRect: getBoundingClientRect,
  1346. findPosition: findPosition,
  1347. getPointerPosition: getPointerPosition,
  1348. isTextNode: isTextNode,
  1349. emptyEl: emptyEl,
  1350. normalizeContent: normalizeContent,
  1351. appendContent: appendContent,
  1352. insertContent: insertContent,
  1353. isSingleLeftClick: isSingleLeftClick,
  1354. $: $,
  1355. $$: $$
  1356. });
  1357. /**
  1358. * @file guid.js
  1359. * @module guid
  1360. */
  1361. /**
  1362. * Unique ID for an element or function
  1363. * @type {Number}
  1364. */
  1365. var _guid = 1;
  1366. /**
  1367. * Get a unique auto-incrementing ID by number that has not been returned before.
  1368. *
  1369. * @return {number}
  1370. * A new unique ID.
  1371. */
  1372. function newGUID() {
  1373. return _guid++;
  1374. }
  1375. /**
  1376. * @file dom-data.js
  1377. * @module dom-data
  1378. */
  1379. /**
  1380. * Element Data Store.
  1381. *
  1382. * Allows for binding data to an element without putting it directly on the
  1383. * element. Ex. Event listeners are stored here.
  1384. * (also from jsninja.com, slightly modified and updated for closure compiler)
  1385. *
  1386. * @type {Object}
  1387. * @private
  1388. */
  1389. var elData = {};
  1390. /*
  1391. * Unique attribute name to store an element's guid in
  1392. *
  1393. * @type {String}
  1394. * @constant
  1395. * @private
  1396. */
  1397. var elIdAttr = 'vdata' + new Date().getTime();
  1398. /**
  1399. * Returns the cache object where data for an element is stored
  1400. *
  1401. * @param {Element} el
  1402. * Element to store data for.
  1403. *
  1404. * @return {Object}
  1405. * The cache object for that el that was passed in.
  1406. */
  1407. function getData(el) {
  1408. var id = el[elIdAttr];
  1409. if (!id) {
  1410. id = el[elIdAttr] = newGUID();
  1411. }
  1412. if (!elData[id]) {
  1413. elData[id] = {};
  1414. }
  1415. return elData[id];
  1416. }
  1417. /**
  1418. * Returns whether or not an element has cached data
  1419. *
  1420. * @param {Element} el
  1421. * Check if this element has cached data.
  1422. *
  1423. * @return {boolean}
  1424. * - True if the DOM element has cached data.
  1425. * - False otherwise.
  1426. */
  1427. function hasData(el) {
  1428. var id = el[elIdAttr];
  1429. if (!id) {
  1430. return false;
  1431. }
  1432. return !!Object.getOwnPropertyNames(elData[id]).length;
  1433. }
  1434. /**
  1435. * Delete data for the element from the cache and the guid attr from getElementById
  1436. *
  1437. * @param {Element} el
  1438. * Remove cached data for this element.
  1439. */
  1440. function removeData(el) {
  1441. var id = el[elIdAttr];
  1442. if (!id) {
  1443. return;
  1444. }
  1445. // Remove all stored data
  1446. delete elData[id];
  1447. // Remove the elIdAttr property from the DOM node
  1448. try {
  1449. delete el[elIdAttr];
  1450. } catch (e) {
  1451. if (el.removeAttribute) {
  1452. el.removeAttribute(elIdAttr);
  1453. } else {
  1454. // IE doesn't appear to support removeAttribute on the document element
  1455. el[elIdAttr] = null;
  1456. }
  1457. }
  1458. }
  1459. /**
  1460. * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
  1461. * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
  1462. * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
  1463. * robust as jquery's, so there's probably some differences.
  1464. *
  1465. * @module events
  1466. */
  1467. /**
  1468. * Clean up the listener cache and dispatchers
  1469. *
  1470. * @param {Element|Object} elem
  1471. * Element to clean up
  1472. *
  1473. * @param {string} type
  1474. * Type of event to clean up
  1475. */
  1476. function _cleanUpEvents(elem, type) {
  1477. var data = getData(elem);
  1478. // Remove the events of a particular type if there are none left
  1479. if (data.handlers[type].length === 0) {
  1480. delete data.handlers[type];
  1481. // data.handlers[type] = null;
  1482. // Setting to null was causing an error with data.handlers
  1483. // Remove the meta-handler from the element
  1484. if (elem.removeEventListener) {
  1485. elem.removeEventListener(type, data.dispatcher, false);
  1486. } else if (elem.detachEvent) {
  1487. elem.detachEvent('on' + type, data.dispatcher);
  1488. }
  1489. }
  1490. // Remove the events object if there are no types left
  1491. if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
  1492. delete data.handlers;
  1493. delete data.dispatcher;
  1494. delete data.disabled;
  1495. }
  1496. // Finally remove the element data if there is no data left
  1497. if (Object.getOwnPropertyNames(data).length === 0) {
  1498. removeData(elem);
  1499. }
  1500. }
  1501. /**
  1502. * Loops through an array of event types and calls the requested method for each type.
  1503. *
  1504. * @param {Function} fn
  1505. * The event method we want to use.
  1506. *
  1507. * @param {Element|Object} elem
  1508. * Element or object to bind listeners to
  1509. *
  1510. * @param {string} type
  1511. * Type of event to bind to.
  1512. *
  1513. * @param {EventTarget~EventListener} callback
  1514. * Event listener.
  1515. */
  1516. function _handleMultipleEvents(fn, elem, types, callback) {
  1517. types.forEach(function (type) {
  1518. // Call the event method for each one of the types
  1519. fn(elem, type, callback);
  1520. });
  1521. }
  1522. /**
  1523. * Fix a native event to have standard property values
  1524. *
  1525. * @param {Object} event
  1526. * Event object to fix.
  1527. *
  1528. * @return {Object}
  1529. * Fixed event object.
  1530. */
  1531. function fixEvent(event) {
  1532. function returnTrue() {
  1533. return true;
  1534. }
  1535. function returnFalse() {
  1536. return false;
  1537. }
  1538. // Test if fixing up is needed
  1539. // Used to check if !event.stopPropagation instead of isPropagationStopped
  1540. // But native events return true for stopPropagation, but don't have
  1541. // other expected methods like isPropagationStopped. Seems to be a problem
  1542. // with the Javascript Ninja code. So we're just overriding all events now.
  1543. if (!event || !event.isPropagationStopped) {
  1544. var old = event || window.event;
  1545. event = {};
  1546. // Clone the old object so that we can modify the values event = {};
  1547. // IE8 Doesn't like when you mess with native event properties
  1548. // Firefox returns false for event.hasOwnProperty('type') and other props
  1549. // which makes copying more difficult.
  1550. // TODO: Probably best to create a whitelist of event props
  1551. for (var key in old) {
  1552. // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
  1553. // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
  1554. // and webkitMovementX/Y
  1555. if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
  1556. // Chrome 32+ warns if you try to copy deprecated returnValue, but
  1557. // we still want to if preventDefault isn't supported (IE8).
  1558. if (!(key === 'returnValue' && old.preventDefault)) {
  1559. event[key] = old[key];
  1560. }
  1561. }
  1562. }
  1563. // The event occurred on this element
  1564. if (!event.target) {
  1565. event.target = event.srcElement || document;
  1566. }
  1567. // Handle which other element the event is related to
  1568. if (!event.relatedTarget) {
  1569. event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
  1570. }
  1571. // Stop the default browser action
  1572. event.preventDefault = function () {
  1573. if (old.preventDefault) {
  1574. old.preventDefault();
  1575. }
  1576. event.returnValue = false;
  1577. old.returnValue = false;
  1578. event.defaultPrevented = true;
  1579. };
  1580. event.defaultPrevented = false;
  1581. // Stop the event from bubbling
  1582. event.stopPropagation = function () {
  1583. if (old.stopPropagation) {
  1584. old.stopPropagation();
  1585. }
  1586. event.cancelBubble = true;
  1587. old.cancelBubble = true;
  1588. event.isPropagationStopped = returnTrue;
  1589. };
  1590. event.isPropagationStopped = returnFalse;
  1591. // Stop the event from bubbling and executing other handlers
  1592. event.stopImmediatePropagation = function () {
  1593. if (old.stopImmediatePropagation) {
  1594. old.stopImmediatePropagation();
  1595. }
  1596. event.isImmediatePropagationStopped = returnTrue;
  1597. event.stopPropagation();
  1598. };
  1599. event.isImmediatePropagationStopped = returnFalse;
  1600. // Handle mouse position
  1601. if (event.clientX !== null && event.clientX !== undefined) {
  1602. var doc = document.documentElement;
  1603. var body = document.body;
  1604. event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
  1605. event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
  1606. }
  1607. // Handle key presses
  1608. event.which = event.charCode || event.keyCode;
  1609. // Fix button for mouse clicks:
  1610. // 0 == left; 1 == middle; 2 == right
  1611. if (event.button !== null && event.button !== undefined) {
  1612. // The following is disabled because it does not pass videojs-standard
  1613. // and... yikes.
  1614. /* eslint-disable */
  1615. event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
  1616. /* eslint-enable */
  1617. }
  1618. }
  1619. // Returns fixed-up instance
  1620. return event;
  1621. }
  1622. /**
  1623. * Whether passive event listeners are supported
  1624. */
  1625. var _supportsPassive = false;
  1626. (function () {
  1627. try {
  1628. var opts = Object.defineProperty({}, 'passive', {
  1629. get: function get() {
  1630. _supportsPassive = true;
  1631. }
  1632. });
  1633. window.addEventListener('test', null, opts);
  1634. window.removeEventListener('test', null, opts);
  1635. } catch (e) {
  1636. // disregard
  1637. }
  1638. })();
  1639. /**
  1640. * Touch events Chrome expects to be passive
  1641. */
  1642. var passiveEvents = ['touchstart', 'touchmove'];
  1643. /**
  1644. * Add an event listener to element
  1645. * It stores the handler function in a separate cache object
  1646. * and adds a generic handler to the element's event,
  1647. * along with a unique id (guid) to the element.
  1648. *
  1649. * @param {Element|Object} elem
  1650. * Element or object to bind listeners to
  1651. *
  1652. * @param {string|string[]} type
  1653. * Type of event to bind to.
  1654. *
  1655. * @param {EventTarget~EventListener} fn
  1656. * Event listener.
  1657. */
  1658. function on(elem, type, fn) {
  1659. if (Array.isArray(type)) {
  1660. return _handleMultipleEvents(on, elem, type, fn);
  1661. }
  1662. var data = getData(elem);
  1663. // We need a place to store all our handler data
  1664. if (!data.handlers) {
  1665. data.handlers = {};
  1666. }
  1667. if (!data.handlers[type]) {
  1668. data.handlers[type] = [];
  1669. }
  1670. if (!fn.guid) {
  1671. fn.guid = newGUID();
  1672. }
  1673. data.handlers[type].push(fn);
  1674. if (!data.dispatcher) {
  1675. data.disabled = false;
  1676. data.dispatcher = function (event, hash) {
  1677. if (data.disabled) {
  1678. return;
  1679. }
  1680. event = fixEvent(event);
  1681. var handlers = data.handlers[event.type];
  1682. if (handlers) {
  1683. // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
  1684. var handlersCopy = handlers.slice(0);
  1685. for (var m = 0, n = handlersCopy.length; m < n; m++) {
  1686. if (event.isImmediatePropagationStopped()) {
  1687. break;
  1688. } else {
  1689. try {
  1690. handlersCopy[m].call(elem, event, hash);
  1691. } catch (e) {
  1692. log.error(e);
  1693. }
  1694. }
  1695. }
  1696. }
  1697. };
  1698. }
  1699. if (data.handlers[type].length === 1) {
  1700. if (elem.addEventListener) {
  1701. var options = false;
  1702. if (_supportsPassive && passiveEvents.indexOf(type) > -1) {
  1703. options = { passive: true };
  1704. }
  1705. elem.addEventListener(type, data.dispatcher, options);
  1706. } else if (elem.attachEvent) {
  1707. elem.attachEvent('on' + type, data.dispatcher);
  1708. }
  1709. }
  1710. }
  1711. /**
  1712. * Removes event listeners from an element
  1713. *
  1714. * @param {Element|Object} elem
  1715. * Object to remove listeners from.
  1716. *
  1717. * @param {string|string[]} [type]
  1718. * Type of listener to remove. Don't include to remove all events from element.
  1719. *
  1720. * @param {EventTarget~EventListener} [fn]
  1721. * Specific listener to remove. Don't include to remove listeners for an event
  1722. * type.
  1723. */
  1724. function off(elem, type, fn) {
  1725. // Don't want to add a cache object through getElData if not needed
  1726. if (!hasData(elem)) {
  1727. return;
  1728. }
  1729. var data = getData(elem);
  1730. // If no events exist, nothing to unbind
  1731. if (!data.handlers) {
  1732. return;
  1733. }
  1734. if (Array.isArray(type)) {
  1735. return _handleMultipleEvents(off, elem, type, fn);
  1736. }
  1737. // Utility function
  1738. var removeType = function removeType(el, t) {
  1739. data.handlers[t] = [];
  1740. _cleanUpEvents(el, t);
  1741. };
  1742. // Are we removing all bound events?
  1743. if (type === undefined) {
  1744. for (var t in data.handlers) {
  1745. if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
  1746. removeType(elem, t);
  1747. }
  1748. }
  1749. return;
  1750. }
  1751. var handlers = data.handlers[type];
  1752. // If no handlers exist, nothing to unbind
  1753. if (!handlers) {
  1754. return;
  1755. }
  1756. // If no listener was provided, remove all listeners for type
  1757. if (!fn) {
  1758. removeType(elem, type);
  1759. return;
  1760. }
  1761. // We're only removing a single handler
  1762. if (fn.guid) {
  1763. for (var n = 0; n < handlers.length; n++) {
  1764. if (handlers[n].guid === fn.guid) {
  1765. handlers.splice(n--, 1);
  1766. }
  1767. }
  1768. }
  1769. _cleanUpEvents(elem, type);
  1770. }
  1771. /**
  1772. * Trigger an event for an element
  1773. *
  1774. * @param {Element|Object} elem
  1775. * Element to trigger an event on
  1776. *
  1777. * @param {EventTarget~Event|string} event
  1778. * A string (the type) or an event object with a type attribute
  1779. *
  1780. * @param {Object} [hash]
  1781. * data hash to pass along with the event
  1782. *
  1783. * @return {boolean|undefined}
  1784. * - Returns the opposite of `defaultPrevented` if default was prevented
  1785. * - Otherwise returns undefined
  1786. */
  1787. function trigger(elem, event, hash) {
  1788. // Fetches element data and a reference to the parent (for bubbling).
  1789. // Don't want to add a data object to cache for every parent,
  1790. // so checking hasElData first.
  1791. var elemData = hasData(elem) ? getData(elem) : {};
  1792. var parent = elem.parentNode || elem.ownerDocument;
  1793. // type = event.type || event,
  1794. // handler;
  1795. // If an event name was passed as a string, creates an event out of it
  1796. if (typeof event === 'string') {
  1797. event = { type: event, target: elem };
  1798. } else if (!event.target) {
  1799. event.target = elem;
  1800. }
  1801. // Normalizes the event properties.
  1802. event = fixEvent(event);
  1803. // If the passed element has a dispatcher, executes the established handlers.
  1804. if (elemData.dispatcher) {
  1805. elemData.dispatcher.call(elem, event, hash);
  1806. }
  1807. // Unless explicitly stopped or the event does not bubble (e.g. media events)
  1808. // recursively calls this function to bubble the event up the DOM.
  1809. if (parent && !event.isPropagationStopped() && event.bubbles === true) {
  1810. trigger.call(null, parent, event, hash);
  1811. // If at the top of the DOM, triggers the default action unless disabled.
  1812. } else if (!parent && !event.defaultPrevented) {
  1813. var targetData = getData(event.target);
  1814. // Checks if the target has a default action for this event.
  1815. if (event.target[event.type]) {
  1816. // Temporarily disables event dispatching on the target as we have already executed the handler.
  1817. targetData.disabled = true;
  1818. // Executes the default action.
  1819. if (typeof event.target[event.type] === 'function') {
  1820. event.target[event.type]();
  1821. }
  1822. // Re-enables event dispatching.
  1823. targetData.disabled = false;
  1824. }
  1825. }
  1826. // Inform the triggerer if the default was prevented by returning false
  1827. return !event.defaultPrevented;
  1828. }
  1829. /**
  1830. * Trigger a listener only once for an event
  1831. *
  1832. * @param {Element|Object} elem
  1833. * Element or object to bind to.
  1834. *
  1835. * @param {string|string[]} type
  1836. * Name/type of event
  1837. *
  1838. * @param {Event~EventListener} fn
  1839. * Event Listener function
  1840. */
  1841. function one(elem, type, fn) {
  1842. if (Array.isArray(type)) {
  1843. return _handleMultipleEvents(one, elem, type, fn);
  1844. }
  1845. var func = function func() {
  1846. off(elem, type, func);
  1847. fn.apply(this, arguments);
  1848. };
  1849. // copy the guid to the new function so it can removed using the original function's ID
  1850. func.guid = fn.guid = fn.guid || newGUID();
  1851. on(elem, type, func);
  1852. }
  1853. var Events = (Object.freeze || Object)({
  1854. fixEvent: fixEvent,
  1855. on: on,
  1856. off: off,
  1857. trigger: trigger,
  1858. one: one
  1859. });
  1860. /**
  1861. * @file setup.js - Functions for setting up a player without
  1862. * user interaction based on the data-setup `attribute` of the video tag.
  1863. *
  1864. * @module setup
  1865. */
  1866. var _windowLoaded = false;
  1867. var videojs$2 = void 0;
  1868. /**
  1869. * Set up any tags that have a data-setup `attribute` when the player is started.
  1870. */
  1871. var autoSetup = function autoSetup() {
  1872. // Protect against breakage in non-browser environments and check global autoSetup option.
  1873. if (!isReal() || videojs$2.options.autoSetup === false) {
  1874. return;
  1875. }
  1876. // One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack*
  1877. // var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
  1878. // var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
  1879. // var mediaEls = vids.concat(audios);
  1880. // Because IE8 doesn't support calling slice on a node list, we need to loop
  1881. // through each list of elements to build up a new, combined list of elements.
  1882. var vids = document.getElementsByTagName('video');
  1883. var audios = document.getElementsByTagName('audio');
  1884. var divs = document.getElementsByTagName('video-js');
  1885. var mediaEls = [];
  1886. if (vids && vids.length > 0) {
  1887. for (var i = 0, e = vids.length; i < e; i++) {
  1888. mediaEls.push(vids[i]);
  1889. }
  1890. }
  1891. if (audios && audios.length > 0) {
  1892. for (var _i = 0, _e = audios.length; _i < _e; _i++) {
  1893. mediaEls.push(audios[_i]);
  1894. }
  1895. }
  1896. if (divs && divs.length > 0) {
  1897. for (var _i2 = 0, _e2 = divs.length; _i2 < _e2; _i2++) {
  1898. mediaEls.push(divs[_i2]);
  1899. }
  1900. }
  1901. // Check if any media elements exist
  1902. if (mediaEls && mediaEls.length > 0) {
  1903. for (var _i3 = 0, _e3 = mediaEls.length; _i3 < _e3; _i3++) {
  1904. var mediaEl = mediaEls[_i3];
  1905. // Check if element exists, has getAttribute func.
  1906. // IE seems to consider typeof el.getAttribute == 'object' instead of
  1907. // 'function' like expected, at least when loading the player immediately.
  1908. if (mediaEl && mediaEl.getAttribute) {
  1909. // Make sure this player hasn't already been set up.
  1910. if (mediaEl.player === undefined) {
  1911. var options = mediaEl.getAttribute('data-setup');
  1912. // Check if data-setup attr exists.
  1913. // We only auto-setup if they've added the data-setup attr.
  1914. if (options !== null) {
  1915. // Create new video.js instance.
  1916. videojs$2(mediaEl);
  1917. }
  1918. }
  1919. // If getAttribute isn't defined, we need to wait for the DOM.
  1920. } else {
  1921. autoSetupTimeout(1);
  1922. break;
  1923. }
  1924. }
  1925. // No videos were found, so keep looping unless page is finished loading.
  1926. } else if (!_windowLoaded) {
  1927. autoSetupTimeout(1);
  1928. }
  1929. };
  1930. /**
  1931. * Wait until the page is loaded before running autoSetup. This will be called in
  1932. * autoSetup if `hasLoaded` returns false.
  1933. *
  1934. * @param {number} wait
  1935. * How long to wait in ms
  1936. *
  1937. * @param {module:videojs} [vjs]
  1938. * The videojs library function
  1939. */
  1940. function autoSetupTimeout(wait, vjs) {
  1941. if (vjs) {
  1942. videojs$2 = vjs;
  1943. }
  1944. window.setTimeout(autoSetup, wait);
  1945. }
  1946. if (isReal() && document.readyState === 'complete') {
  1947. _windowLoaded = true;
  1948. } else {
  1949. /**
  1950. * Listen for the load event on window, and set _windowLoaded to true.
  1951. *
  1952. * @listens load
  1953. */
  1954. one(window, 'load', function () {
  1955. _windowLoaded = true;
  1956. });
  1957. }
  1958. /**
  1959. * @file stylesheet.js
  1960. * @module stylesheet
  1961. */
  1962. /**
  1963. * Create a DOM syle element given a className for it.
  1964. *
  1965. * @param {string} className
  1966. * The className to add to the created style element.
  1967. *
  1968. * @return {Element}
  1969. * The element that was created.
  1970. */
  1971. var createStyleElement = function createStyleElement(className) {
  1972. var style = document.createElement('style');
  1973. style.className = className;
  1974. return style;
  1975. };
  1976. /**
  1977. * Add text to a DOM element.
  1978. *
  1979. * @param {Element} el
  1980. * The Element to add text content to.
  1981. *
  1982. * @param {string} content
  1983. * The text to add to the element.
  1984. */
  1985. var setTextContent = function setTextContent(el, content) {
  1986. if (el.styleSheet) {
  1987. el.styleSheet.cssText = content;
  1988. } else {
  1989. el.textContent = content;
  1990. }
  1991. };
  1992. /**
  1993. * @file fn.js
  1994. * @module fn
  1995. */
  1996. /**
  1997. * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
  1998. * It also stores a unique id on the function so it can be easily removed from events.
  1999. *
  2000. * @param {Mixed} context
  2001. * The object to bind as scope.
  2002. *
  2003. * @param {Function} fn
  2004. * The function to be bound to a scope.
  2005. *
  2006. * @param {number} [uid]
  2007. * An optional unique ID for the function to be set
  2008. *
  2009. * @return {Function}
  2010. * The new function that will be bound into the context given
  2011. */
  2012. var bind = function bind(context, fn, uid) {
  2013. // Make sure the function has a unique ID
  2014. if (!fn.guid) {
  2015. fn.guid = newGUID();
  2016. }
  2017. // Create the new function that changes the context
  2018. var bound = function bound() {
  2019. return fn.apply(context, arguments);
  2020. };
  2021. // Allow for the ability to individualize this function
  2022. // Needed in the case where multiple objects might share the same prototype
  2023. // IF both items add an event listener with the same function, then you try to remove just one
  2024. // it will remove both because they both have the same guid.
  2025. // when using this, you need to use the bind method when you remove the listener as well.
  2026. // currently used in text tracks
  2027. bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
  2028. return bound;
  2029. };
  2030. /**
  2031. * Wraps the given function, `fn`, with a new function that only invokes `fn`
  2032. * at most once per every `wait` milliseconds.
  2033. *
  2034. * @param {Function} fn
  2035. * The function to be throttled.
  2036. *
  2037. * @param {Number} wait
  2038. * The number of milliseconds by which to throttle.
  2039. *
  2040. * @return {Function}
  2041. */
  2042. var throttle = function throttle(fn, wait) {
  2043. var last = Date.now();
  2044. var throttled = function throttled() {
  2045. var now = Date.now();
  2046. if (now - last >= wait) {
  2047. fn.apply(undefined, arguments);
  2048. last = now;
  2049. }
  2050. };
  2051. return throttled;
  2052. };
  2053. /**
  2054. * Creates a debounced function that delays invoking `func` until after `wait`
  2055. * milliseconds have elapsed since the last time the debounced function was
  2056. * invoked.
  2057. *
  2058. * Inspired by lodash and underscore implementations.
  2059. *
  2060. * @param {Function} func
  2061. * The function to wrap with debounce behavior.
  2062. *
  2063. * @param {number} wait
  2064. * The number of milliseconds to wait after the last invocation.
  2065. *
  2066. * @param {boolean} [immediate]
  2067. * Whether or not to invoke the function immediately upon creation.
  2068. *
  2069. * @param {Object} [context=window]
  2070. * The "context" in which the debounced function should debounce. For
  2071. * example, if this function should be tied to a Video.js player,
  2072. * the player can be passed here. Alternatively, defaults to the
  2073. * global `window` object.
  2074. *
  2075. * @return {Function}
  2076. * A debounced function.
  2077. */
  2078. var debounce = function debounce(func, wait, immediate) {
  2079. var context = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : window;
  2080. var timeout = void 0;
  2081. var cancel = function cancel() {
  2082. context.clearTimeout(timeout);
  2083. timeout = null;
  2084. };
  2085. /* eslint-disable consistent-this */
  2086. var debounced = function debounced() {
  2087. var self = this;
  2088. var args = arguments;
  2089. var _later = function later() {
  2090. timeout = null;
  2091. _later = null;
  2092. if (!immediate) {
  2093. func.apply(self, args);
  2094. }
  2095. };
  2096. if (!timeout && immediate) {
  2097. func.apply(self, args);
  2098. }
  2099. context.clearTimeout(timeout);
  2100. timeout = context.setTimeout(_later, wait);
  2101. };
  2102. /* eslint-enable consistent-this */
  2103. debounced.cancel = cancel;
  2104. return debounced;
  2105. };
  2106. /**
  2107. * @file src/js/event-target.js
  2108. */
  2109. /**
  2110. * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
  2111. * adds shorthand functions that wrap around lengthy functions. For example:
  2112. * the `on` function is a wrapper around `addEventListener`.
  2113. *
  2114. * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
  2115. * @class EventTarget
  2116. */
  2117. var EventTarget = function EventTarget() {};
  2118. /**
  2119. * A Custom DOM event.
  2120. *
  2121. * @typedef {Object} EventTarget~Event
  2122. * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
  2123. */
  2124. /**
  2125. * All event listeners should follow the following format.
  2126. *
  2127. * @callback EventTarget~EventListener
  2128. * @this {EventTarget}
  2129. *
  2130. * @param {EventTarget~Event} event
  2131. * the event that triggered this function
  2132. *
  2133. * @param {Object} [hash]
  2134. * hash of data sent during the event
  2135. */
  2136. /**
  2137. * An object containing event names as keys and booleans as values.
  2138. *
  2139. * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
  2140. * will have extra functionality. See that function for more information.
  2141. *
  2142. * @property EventTarget.prototype.allowedEvents_
  2143. * @private
  2144. */
  2145. EventTarget.prototype.allowedEvents_ = {};
  2146. /**
  2147. * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
  2148. * function that will get called when an event with a certain name gets triggered.
  2149. *
  2150. * @param {string|string[]} type
  2151. * An event name or an array of event names.
  2152. *
  2153. * @param {EventTarget~EventListener} fn
  2154. * The function to call with `EventTarget`s
  2155. */
  2156. EventTarget.prototype.on = function (type, fn) {
  2157. // Remove the addEventListener alias before calling Events.on
  2158. // so we don't get into an infinite type loop
  2159. var ael = this.addEventListener;
  2160. this.addEventListener = function () {};
  2161. on(this, type, fn);
  2162. this.addEventListener = ael;
  2163. };
  2164. /**
  2165. * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
  2166. * the standard DOM API.
  2167. *
  2168. * @function
  2169. * @see {@link EventTarget#on}
  2170. */
  2171. EventTarget.prototype.addEventListener = EventTarget.prototype.on;
  2172. /**
  2173. * Removes an `event listener` for a specific event from an instance of `EventTarget`.
  2174. * This makes it so that the `event listener` will no longer get called when the
  2175. * named event happens.
  2176. *
  2177. * @param {string|string[]} type
  2178. * An event name or an array of event names.
  2179. *
  2180. * @param {EventTarget~EventListener} fn
  2181. * The function to remove.
  2182. */
  2183. EventTarget.prototype.off = function (type, fn) {
  2184. off(this, type, fn);
  2185. };
  2186. /**
  2187. * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
  2188. * the standard DOM API.
  2189. *
  2190. * @function
  2191. * @see {@link EventTarget#off}
  2192. */
  2193. EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
  2194. /**
  2195. * This function will add an `event listener` that gets triggered only once. After the
  2196. * first trigger it will get removed. This is like adding an `event listener`
  2197. * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
  2198. *
  2199. * @param {string|string[]} type
  2200. * An event name or an array of event names.
  2201. *
  2202. * @param {EventTarget~EventListener} fn
  2203. * The function to be called once for each event name.
  2204. */
  2205. EventTarget.prototype.one = function (type, fn) {
  2206. // Remove the addEventListener alialing Events.on
  2207. // so we don't get into an infinite type loop
  2208. var ael = this.addEventListener;
  2209. this.addEventListener = function () {};
  2210. one(this, type, fn);
  2211. this.addEventListener = ael;
  2212. };
  2213. /**
  2214. * This function causes an event to happen. This will then cause any `event listeners`
  2215. * that are waiting for that event, to get called. If there are no `event listeners`
  2216. * for an event then nothing will happen.
  2217. *
  2218. * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
  2219. * Trigger will also call the `on` + `uppercaseEventName` function.
  2220. *
  2221. * Example:
  2222. * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
  2223. * `onClick` if it exists.
  2224. *
  2225. * @param {string|EventTarget~Event|Object} event
  2226. * The name of the event, an `Event`, or an object with a key of type set to
  2227. * an event name.
  2228. */
  2229. EventTarget.prototype.trigger = function (event) {
  2230. var type = event.type || event;
  2231. if (typeof event === 'string') {
  2232. event = { type: type };
  2233. }
  2234. event = fixEvent(event);
  2235. if (this.allowedEvents_[type] && this['on' + type]) {
  2236. this['on' + type](event);
  2237. }
  2238. trigger(this, event);
  2239. };
  2240. /**
  2241. * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
  2242. * the standard DOM API.
  2243. *
  2244. * @function
  2245. * @see {@link EventTarget#trigger}
  2246. */
  2247. EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
  2248. /**
  2249. * @file mixins/evented.js
  2250. * @module evented
  2251. */
  2252. /**
  2253. * Returns whether or not an object has had the evented mixin applied.
  2254. *
  2255. * @param {Object} object
  2256. * An object to test.
  2257. *
  2258. * @return {boolean}
  2259. * Whether or not the object appears to be evented.
  2260. */
  2261. var isEvented = function isEvented(object) {
  2262. return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
  2263. return typeof object[k] === 'function';
  2264. });
  2265. };
  2266. /**
  2267. * Whether a value is a valid event type - non-empty string or array.
  2268. *
  2269. * @private
  2270. * @param {string|Array} type
  2271. * The type value to test.
  2272. *
  2273. * @return {boolean}
  2274. * Whether or not the type is a valid event type.
  2275. */
  2276. var isValidEventType = function isValidEventType(type) {
  2277. return (
  2278. // The regex here verifies that the `type` contains at least one non-
  2279. // whitespace character.
  2280. typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
  2281. );
  2282. };
  2283. /**
  2284. * Validates a value to determine if it is a valid event target. Throws if not.
  2285. *
  2286. * @private
  2287. * @throws {Error}
  2288. * If the target does not appear to be a valid event target.
  2289. *
  2290. * @param {Object} target
  2291. * The object to test.
  2292. */
  2293. var validateTarget = function validateTarget(target) {
  2294. if (!target.nodeName && !isEvented(target)) {
  2295. throw new Error('Invalid target; must be a DOM node or evented object.');
  2296. }
  2297. };
  2298. /**
  2299. * Validates a value to determine if it is a valid event target. Throws if not.
  2300. *
  2301. * @private
  2302. * @throws {Error}
  2303. * If the type does not appear to be a valid event type.
  2304. *
  2305. * @param {string|Array} type
  2306. * The type to test.
  2307. */
  2308. var validateEventType = function validateEventType(type) {
  2309. if (!isValidEventType(type)) {
  2310. throw new Error('Invalid event type; must be a non-empty string or array.');
  2311. }
  2312. };
  2313. /**
  2314. * Validates a value to determine if it is a valid listener. Throws if not.
  2315. *
  2316. * @private
  2317. * @throws {Error}
  2318. * If the listener is not a function.
  2319. *
  2320. * @param {Function} listener
  2321. * The listener to test.
  2322. */
  2323. var validateListener = function validateListener(listener) {
  2324. if (typeof listener !== 'function') {
  2325. throw new Error('Invalid listener; must be a function.');
  2326. }
  2327. };
  2328. /**
  2329. * Takes an array of arguments given to `on()` or `one()`, validates them, and
  2330. * normalizes them into an object.
  2331. *
  2332. * @private
  2333. * @param {Object} self
  2334. * The evented object on which `on()` or `one()` was called. This
  2335. * object will be bound as the `this` value for the listener.
  2336. *
  2337. * @param {Array} args
  2338. * An array of arguments passed to `on()` or `one()`.
  2339. *
  2340. * @return {Object}
  2341. * An object containing useful values for `on()` or `one()` calls.
  2342. */
  2343. var normalizeListenArgs = function normalizeListenArgs(self, args) {
  2344. // If the number of arguments is less than 3, the target is always the
  2345. // evented object itself.
  2346. var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
  2347. var target = void 0;
  2348. var type = void 0;
  2349. var listener = void 0;
  2350. if (isTargetingSelf) {
  2351. target = self.eventBusEl_;
  2352. // Deal with cases where we got 3 arguments, but we are still listening to
  2353. // the evented object itself.
  2354. if (args.length >= 3) {
  2355. args.shift();
  2356. }
  2357. type = args[0];
  2358. listener = args[1];
  2359. } else {
  2360. target = args[0];
  2361. type = args[1];
  2362. listener = args[2];
  2363. }
  2364. validateTarget(target);
  2365. validateEventType(type);
  2366. validateListener(listener);
  2367. listener = bind(self, listener);
  2368. return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener };
  2369. };
  2370. /**
  2371. * Adds the listener to the event type(s) on the target, normalizing for
  2372. * the type of target.
  2373. *
  2374. * @private
  2375. * @param {Element|Object} target
  2376. * A DOM node or evented object.
  2377. *
  2378. * @param {string} method
  2379. * The event binding method to use ("on" or "one").
  2380. *
  2381. * @param {string|Array} type
  2382. * One or more event type(s).
  2383. *
  2384. * @param {Function} listener
  2385. * A listener function.
  2386. */
  2387. var listen = function listen(target, method, type, listener) {
  2388. validateTarget(target);
  2389. if (target.nodeName) {
  2390. Events[method](target, type, listener);
  2391. } else {
  2392. target[method](type, listener);
  2393. }
  2394. };
  2395. /**
  2396. * Contains methods that provide event capabilites to an object which is passed
  2397. * to {@link module:evented|evented}.
  2398. *
  2399. * @mixin EventedMixin
  2400. */
  2401. var EventedMixin = {
  2402. /**
  2403. * Add a listener to an event (or events) on this object or another evented
  2404. * object.
  2405. *
  2406. * @param {string|Array|Element|Object} targetOrType
  2407. * If this is a string or array, it represents the event type(s)
  2408. * that will trigger the listener.
  2409. *
  2410. * Another evented object can be passed here instead, which will
  2411. * cause the listener to listen for events on _that_ object.
  2412. *
  2413. * In either case, the listener's `this` value will be bound to
  2414. * this object.
  2415. *
  2416. * @param {string|Array|Function} typeOrListener
  2417. * If the first argument was a string or array, this should be the
  2418. * listener function. Otherwise, this is a string or array of event
  2419. * type(s).
  2420. *
  2421. * @param {Function} [listener]
  2422. * If the first argument was another evented object, this will be
  2423. * the listener function.
  2424. */
  2425. on: function on$$1() {
  2426. var _this = this;
  2427. for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
  2428. args[_key] = arguments[_key];
  2429. }
  2430. var _normalizeListenArgs = normalizeListenArgs(this, args),
  2431. isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
  2432. target = _normalizeListenArgs.target,
  2433. type = _normalizeListenArgs.type,
  2434. listener = _normalizeListenArgs.listener;
  2435. listen(target, 'on', type, listener);
  2436. // If this object is listening to another evented object.
  2437. if (!isTargetingSelf) {
  2438. // If this object is disposed, remove the listener.
  2439. var removeListenerOnDispose = function removeListenerOnDispose() {
  2440. return _this.off(target, type, listener);
  2441. };
  2442. // Use the same function ID as the listener so we can remove it later it
  2443. // using the ID of the original listener.
  2444. removeListenerOnDispose.guid = listener.guid;
  2445. // Add a listener to the target's dispose event as well. This ensures
  2446. // that if the target is disposed BEFORE this object, we remove the
  2447. // removal listener that was just added. Otherwise, we create a memory leak.
  2448. var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
  2449. return _this.off('dispose', removeListenerOnDispose);
  2450. };
  2451. // Use the same function ID as the listener so we can remove it later
  2452. // it using the ID of the original listener.
  2453. removeRemoverOnTargetDispose.guid = listener.guid;
  2454. listen(this, 'on', 'dispose', removeListenerOnDispose);
  2455. listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
  2456. }
  2457. },
  2458. /**
  2459. * Add a listener to an event (or events) on this object or another evented
  2460. * object. The listener will only be called once and then removed.
  2461. *
  2462. * @param {string|Array|Element|Object} targetOrType
  2463. * If this is a string or array, it represents the event type(s)
  2464. * that will trigger the listener.
  2465. *
  2466. * Another evented object can be passed here instead, which will
  2467. * cause the listener to listen for events on _that_ object.
  2468. *
  2469. * In either case, the listener's `this` value will be bound to
  2470. * this object.
  2471. *
  2472. * @param {string|Array|Function} typeOrListener
  2473. * If the first argument was a string or array, this should be the
  2474. * listener function. Otherwise, this is a string or array of event
  2475. * type(s).
  2476. *
  2477. * @param {Function} [listener]
  2478. * If the first argument was another evented object, this will be
  2479. * the listener function.
  2480. */
  2481. one: function one$$1() {
  2482. var _this2 = this;
  2483. for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  2484. args[_key2] = arguments[_key2];
  2485. }
  2486. var _normalizeListenArgs2 = normalizeListenArgs(this, args),
  2487. isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
  2488. target = _normalizeListenArgs2.target,
  2489. type = _normalizeListenArgs2.type,
  2490. listener = _normalizeListenArgs2.listener;
  2491. // Targeting this evented object.
  2492. if (isTargetingSelf) {
  2493. listen(target, 'one', type, listener);
  2494. // Targeting another evented object.
  2495. } else {
  2496. var wrapper = function wrapper() {
  2497. for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  2498. largs[_key3] = arguments[_key3];
  2499. }
  2500. _this2.off(target, type, wrapper);
  2501. listener.apply(null, largs);
  2502. };
  2503. // Use the same function ID as the listener so we can remove it later
  2504. // it using the ID of the original listener.
  2505. wrapper.guid = listener.guid;
  2506. listen(target, 'one', type, wrapper);
  2507. }
  2508. },
  2509. /**
  2510. * Removes listener(s) from event(s) on an evented object.
  2511. *
  2512. * @param {string|Array|Element|Object} [targetOrType]
  2513. * If this is a string or array, it represents the event type(s).
  2514. *
  2515. * Another evented object can be passed here instead, in which case
  2516. * ALL 3 arguments are _required_.
  2517. *
  2518. * @param {string|Array|Function} [typeOrListener]
  2519. * If the first argument was a string or array, this may be the
  2520. * listener function. Otherwise, this is a string or array of event
  2521. * type(s).
  2522. *
  2523. * @param {Function} [listener]
  2524. * If the first argument was another evented object, this will be
  2525. * the listener function; otherwise, _all_ listeners bound to the
  2526. * event type(s) will be removed.
  2527. */
  2528. off: function off$$1(targetOrType, typeOrListener, listener) {
  2529. // Targeting this evented object.
  2530. if (!targetOrType || isValidEventType(targetOrType)) {
  2531. off(this.eventBusEl_, targetOrType, typeOrListener);
  2532. // Targeting another evented object.
  2533. } else {
  2534. var target = targetOrType;
  2535. var type = typeOrListener;
  2536. // Fail fast and in a meaningful way!
  2537. validateTarget(target);
  2538. validateEventType(type);
  2539. validateListener(listener);
  2540. // Ensure there's at least a guid, even if the function hasn't been used
  2541. listener = bind(this, listener);
  2542. // Remove the dispose listener on this evented object, which was given
  2543. // the same guid as the event listener in on().
  2544. this.off('dispose', listener);
  2545. if (target.nodeName) {
  2546. off(target, type, listener);
  2547. off(target, 'dispose', listener);
  2548. } else if (isEvented(target)) {
  2549. target.off(type, listener);
  2550. target.off('dispose', listener);
  2551. }
  2552. }
  2553. },
  2554. /**
  2555. * Fire an event on this evented object, causing its listeners to be called.
  2556. *
  2557. * @param {string|Object} event
  2558. * An event type or an object with a type property.
  2559. *
  2560. * @param {Object} [hash]
  2561. * An additional object to pass along to listeners.
  2562. *
  2563. * @returns {boolean}
  2564. * Whether or not the default behavior was prevented.
  2565. */
  2566. trigger: function trigger$$1(event, hash) {
  2567. return trigger(this.eventBusEl_, event, hash);
  2568. }
  2569. };
  2570. /**
  2571. * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
  2572. *
  2573. * @param {Object} target
  2574. * The object to which to add event methods.
  2575. *
  2576. * @param {Object} [options={}]
  2577. * Options for customizing the mixin behavior.
  2578. *
  2579. * @param {String} [options.eventBusKey]
  2580. * By default, adds a `eventBusEl_` DOM element to the target object,
  2581. * which is used as an event bus. If the target object already has a
  2582. * DOM element that should be used, pass its key here.
  2583. *
  2584. * @return {Object}
  2585. * The target object.
  2586. */
  2587. function evented(target) {
  2588. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  2589. var eventBusKey = options.eventBusKey;
  2590. // Set or create the eventBusEl_.
  2591. if (eventBusKey) {
  2592. if (!target[eventBusKey].nodeName) {
  2593. throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.');
  2594. }
  2595. target.eventBusEl_ = target[eventBusKey];
  2596. } else {
  2597. target.eventBusEl_ = createEl('span', { className: 'vjs-event-bus' });
  2598. }
  2599. assign(target, EventedMixin);
  2600. // When any evented object is disposed, it removes all its listeners.
  2601. target.on('dispose', function () {
  2602. target.off();
  2603. window.setTimeout(function () {
  2604. target.eventBusEl_ = null;
  2605. }, 0);
  2606. });
  2607. return target;
  2608. }
  2609. /**
  2610. * @file mixins/stateful.js
  2611. * @module stateful
  2612. */
  2613. /**
  2614. * Contains methods that provide statefulness to an object which is passed
  2615. * to {@link module:stateful}.
  2616. *
  2617. * @mixin StatefulMixin
  2618. */
  2619. var StatefulMixin = {
  2620. /**
  2621. * A hash containing arbitrary keys and values representing the state of
  2622. * the object.
  2623. *
  2624. * @type {Object}
  2625. */
  2626. state: {},
  2627. /**
  2628. * Set the state of an object by mutating its
  2629. * {@link module:stateful~StatefulMixin.state|state} object in place.
  2630. *
  2631. * @fires module:stateful~StatefulMixin#statechanged
  2632. * @param {Object|Function} stateUpdates
  2633. * A new set of properties to shallow-merge into the plugin state.
  2634. * Can be a plain object or a function returning a plain object.
  2635. *
  2636. * @returns {Object|undefined}
  2637. * An object containing changes that occurred. If no changes
  2638. * occurred, returns `undefined`.
  2639. */
  2640. setState: function setState(stateUpdates) {
  2641. var _this = this;
  2642. // Support providing the `stateUpdates` state as a function.
  2643. if (typeof stateUpdates === 'function') {
  2644. stateUpdates = stateUpdates();
  2645. }
  2646. var changes = void 0;
  2647. each(stateUpdates, function (value, key) {
  2648. // Record the change if the value is different from what's in the
  2649. // current state.
  2650. if (_this.state[key] !== value) {
  2651. changes = changes || {};
  2652. changes[key] = {
  2653. from: _this.state[key],
  2654. to: value
  2655. };
  2656. }
  2657. _this.state[key] = value;
  2658. });
  2659. // Only trigger "statechange" if there were changes AND we have a trigger
  2660. // function. This allows us to not require that the target object be an
  2661. // evented object.
  2662. if (changes && isEvented(this)) {
  2663. /**
  2664. * An event triggered on an object that is both
  2665. * {@link module:stateful|stateful} and {@link module:evented|evented}
  2666. * indicating that its state has changed.
  2667. *
  2668. * @event module:stateful~StatefulMixin#statechanged
  2669. * @type {Object}
  2670. * @property {Object} changes
  2671. * A hash containing the properties that were changed and
  2672. * the values they were changed `from` and `to`.
  2673. */
  2674. this.trigger({
  2675. changes: changes,
  2676. type: 'statechanged'
  2677. });
  2678. }
  2679. return changes;
  2680. }
  2681. };
  2682. /**
  2683. * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
  2684. * object.
  2685. *
  2686. * If the target object is {@link module:evented|evented} and has a
  2687. * `handleStateChanged` method, that method will be automatically bound to the
  2688. * `statechanged` event on itself.
  2689. *
  2690. * @param {Object} target
  2691. * The object to be made stateful.
  2692. *
  2693. * @param {Object} [defaultState]
  2694. * A default set of properties to populate the newly-stateful object's
  2695. * `state` property.
  2696. *
  2697. * @returns {Object}
  2698. * Returns the `target`.
  2699. */
  2700. function stateful(target, defaultState) {
  2701. assign(target, StatefulMixin);
  2702. // This happens after the mixing-in because we need to replace the `state`
  2703. // added in that step.
  2704. target.state = assign({}, target.state, defaultState);
  2705. // Auto-bind the `handleStateChanged` method of the target object if it exists.
  2706. if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
  2707. target.on('statechanged', target.handleStateChanged);
  2708. }
  2709. return target;
  2710. }
  2711. /**
  2712. * @file to-title-case.js
  2713. * @module to-title-case
  2714. */
  2715. /**
  2716. * Uppercase the first letter of a string.
  2717. *
  2718. * @param {string} string
  2719. * String to be uppercased
  2720. *
  2721. * @return {string}
  2722. * The string with an uppercased first letter
  2723. */
  2724. function toTitleCase(string) {
  2725. if (typeof string !== 'string') {
  2726. return string;
  2727. }
  2728. return string.charAt(0).toUpperCase() + string.slice(1);
  2729. }
  2730. /**
  2731. * Compares the TitleCase versions of the two strings for equality.
  2732. *
  2733. * @param {string} str1
  2734. * The first string to compare
  2735. *
  2736. * @param {string} str2
  2737. * The second string to compare
  2738. *
  2739. * @return {boolean}
  2740. * Whether the TitleCase versions of the strings are equal
  2741. */
  2742. function titleCaseEquals(str1, str2) {
  2743. return toTitleCase(str1) === toTitleCase(str2);
  2744. }
  2745. /**
  2746. * @file merge-options.js
  2747. * @module merge-options
  2748. */
  2749. /**
  2750. * Deep-merge one or more options objects, recursively merging **only** plain
  2751. * object properties.
  2752. *
  2753. * @param {Object[]} sources
  2754. * One or more objects to merge into a new object.
  2755. *
  2756. * @returns {Object}
  2757. * A new object that is the merged result of all sources.
  2758. */
  2759. function mergeOptions() {
  2760. var result = {};
  2761. for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) {
  2762. sources[_key] = arguments[_key];
  2763. }
  2764. sources.forEach(function (source) {
  2765. if (!source) {
  2766. return;
  2767. }
  2768. each(source, function (value, key) {
  2769. if (!isPlain(value)) {
  2770. result[key] = value;
  2771. return;
  2772. }
  2773. if (!isPlain(result[key])) {
  2774. result[key] = {};
  2775. }
  2776. result[key] = mergeOptions(result[key], value);
  2777. });
  2778. });
  2779. return result;
  2780. }
  2781. /**
  2782. * Player Component - Base class for all UI objects
  2783. *
  2784. * @file component.js
  2785. */
  2786. /**
  2787. * Base class for all UI Components.
  2788. * Components are UI objects which represent both a javascript object and an element
  2789. * in the DOM. They can be children of other components, and can have
  2790. * children themselves.
  2791. *
  2792. * Components can also use methods from {@link EventTarget}
  2793. */
  2794. var Component = function () {
  2795. /**
  2796. * A callback that is called when a component is ready. Does not have any
  2797. * paramters and any callback value will be ignored.
  2798. *
  2799. * @callback Component~ReadyCallback
  2800. * @this Component
  2801. */
  2802. /**
  2803. * Creates an instance of this class.
  2804. *
  2805. * @param {Player} player
  2806. * The `Player` that this class should be attached to.
  2807. *
  2808. * @param {Object} [options]
  2809. * The key/value store of player options.
  2810. *
  2811. * @param {Object[]} [options.children]
  2812. * An array of children objects to intialize this component with. Children objects have
  2813. * a name property that will be used if more than one component of the same type needs to be
  2814. * added.
  2815. *
  2816. * @param {Component~ReadyCallback} [ready]
  2817. * Function that gets called when the `Component` is ready.
  2818. */
  2819. function Component(player, options, ready) {
  2820. classCallCheck(this, Component);
  2821. // The component might be the player itself and we can't pass `this` to super
  2822. if (!player && this.play) {
  2823. this.player_ = player = this; // eslint-disable-line
  2824. } else {
  2825. this.player_ = player;
  2826. }
  2827. // Make a copy of prototype.options_ to protect against overriding defaults
  2828. this.options_ = mergeOptions({}, this.options_);
  2829. // Updated options with supplied options
  2830. options = this.options_ = mergeOptions(this.options_, options);
  2831. // Get ID from options or options element if one is supplied
  2832. this.id_ = options.id || options.el && options.el.id;
  2833. // If there was no ID from the options, generate one
  2834. if (!this.id_) {
  2835. // Don't require the player ID function in the case of mock players
  2836. var id = player && player.id && player.id() || 'no_player';
  2837. this.id_ = id + '_component_' + newGUID();
  2838. }
  2839. this.name_ = options.name || null;
  2840. // Create element if one wasn't provided in options
  2841. if (options.el) {
  2842. this.el_ = options.el;
  2843. } else if (options.createEl !== false) {
  2844. this.el_ = this.createEl();
  2845. }
  2846. // if evented is anything except false, we want to mixin in evented
  2847. if (options.evented !== false) {
  2848. // Make this an evented object and use `el_`, if available, as its event bus
  2849. evented(this, { eventBusKey: this.el_ ? 'el_' : null });
  2850. }
  2851. stateful(this, this.constructor.defaultState);
  2852. this.children_ = [];
  2853. this.childIndex_ = {};
  2854. this.childNameIndex_ = {};
  2855. // Add any child components in options
  2856. if (options.initChildren !== false) {
  2857. this.initChildren();
  2858. }
  2859. this.ready(ready);
  2860. // Don't want to trigger ready here or it will before init is actually
  2861. // finished for all children that run this constructor
  2862. if (options.reportTouchActivity !== false) {
  2863. this.enableTouchActivity();
  2864. }
  2865. }
  2866. /**
  2867. * Dispose of the `Component` and all child components.
  2868. *
  2869. * @fires Component#dispose
  2870. */
  2871. Component.prototype.dispose = function dispose() {
  2872. /**
  2873. * Triggered when a `Component` is disposed.
  2874. *
  2875. * @event Component#dispose
  2876. * @type {EventTarget~Event}
  2877. *
  2878. * @property {boolean} [bubbles=false]
  2879. * set to false so that the close event does not
  2880. * bubble up
  2881. */
  2882. this.trigger({ type: 'dispose', bubbles: false });
  2883. // Dispose all children.
  2884. if (this.children_) {
  2885. for (var i = this.children_.length - 1; i >= 0; i--) {
  2886. if (this.children_[i].dispose) {
  2887. this.children_[i].dispose();
  2888. }
  2889. }
  2890. }
  2891. // Delete child references
  2892. this.children_ = null;
  2893. this.childIndex_ = null;
  2894. this.childNameIndex_ = null;
  2895. if (this.el_) {
  2896. // Remove element from DOM
  2897. if (this.el_.parentNode) {
  2898. this.el_.parentNode.removeChild(this.el_);
  2899. }
  2900. removeData(this.el_);
  2901. this.el_ = null;
  2902. }
  2903. // remove reference to the player after disposing of the element
  2904. this.player_ = null;
  2905. };
  2906. /**
  2907. * Return the {@link Player} that the `Component` has attached to.
  2908. *
  2909. * @return {Player}
  2910. * The player that this `Component` has attached to.
  2911. */
  2912. Component.prototype.player = function player() {
  2913. return this.player_;
  2914. };
  2915. /**
  2916. * Deep merge of options objects with new options.
  2917. * > Note: When both `obj` and `options` contain properties whose values are objects.
  2918. * The two properties get merged using {@link module:mergeOptions}
  2919. *
  2920. * @param {Object} obj
  2921. * The object that contains new options.
  2922. *
  2923. * @return {Object}
  2924. * A new object of `this.options_` and `obj` merged together.
  2925. *
  2926. * @deprecated since version 5
  2927. */
  2928. Component.prototype.options = function options(obj) {
  2929. log.warn('this.options() has been deprecated and will be moved to the constructor in 6.0');
  2930. if (!obj) {
  2931. return this.options_;
  2932. }
  2933. this.options_ = mergeOptions(this.options_, obj);
  2934. return this.options_;
  2935. };
  2936. /**
  2937. * Get the `Component`s DOM element
  2938. *
  2939. * @return {Element}
  2940. * The DOM element for this `Component`.
  2941. */
  2942. Component.prototype.el = function el() {
  2943. return this.el_;
  2944. };
  2945. /**
  2946. * Create the `Component`s DOM element.
  2947. *
  2948. * @param {string} [tagName]
  2949. * Element's DOM node type. e.g. 'div'
  2950. *
  2951. * @param {Object} [properties]
  2952. * An object of properties that should be set.
  2953. *
  2954. * @param {Object} [attributes]
  2955. * An object of attributes that should be set.
  2956. *
  2957. * @return {Element}
  2958. * The element that gets created.
  2959. */
  2960. Component.prototype.createEl = function createEl$$1(tagName, properties, attributes) {
  2961. return createEl(tagName, properties, attributes);
  2962. };
  2963. /**
  2964. * Localize a string given the string in english.
  2965. *
  2966. * If tokens are provided, it'll try and run a simple token replacement on the provided string.
  2967. * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
  2968. *
  2969. * If a `defaultValue` is provided, it'll use that over `string`,
  2970. * if a value isn't found in provided language files.
  2971. * This is useful if you want to have a descriptive key for token replacement
  2972. * but have a succinct localized string and not require `en.json` to be included.
  2973. *
  2974. * Currently, it is used for the progress bar timing.
  2975. * ```js
  2976. * {
  2977. * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
  2978. * }
  2979. * ```
  2980. * It is then used like so:
  2981. * ```js
  2982. * this.localize('progress bar timing: currentTime={1} duration{2}',
  2983. * [this.player_.currentTime(), this.player_.duration()],
  2984. * '{1} of {2}');
  2985. * ```
  2986. *
  2987. * Which outputs something like: `01:23 of 24:56`.
  2988. *
  2989. *
  2990. * @param {string} string
  2991. * The string to localize and the key to lookup in the language files.
  2992. * @param {string[]} [tokens]
  2993. * If the current item has token replacements, provide the tokens here.
  2994. * @param {string} [defaultValue]
  2995. * Defaults to `string`. Can be a default value to use for token replacement
  2996. * if the lookup key is needed to be separate.
  2997. *
  2998. * @return {string}
  2999. * The localized string or if no localization exists the english string.
  3000. */
  3001. Component.prototype.localize = function localize(string, tokens) {
  3002. var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : string;
  3003. var code = this.player_.language && this.player_.language();
  3004. var languages = this.player_.languages && this.player_.languages();
  3005. var language = languages && languages[code];
  3006. var primaryCode = code && code.split('-')[0];
  3007. var primaryLang = languages && languages[primaryCode];
  3008. var localizedString = defaultValue;
  3009. if (language && language[string]) {
  3010. localizedString = language[string];
  3011. } else if (primaryLang && primaryLang[string]) {
  3012. localizedString = primaryLang[string];
  3013. }
  3014. if (tokens) {
  3015. localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
  3016. var value = tokens[index - 1];
  3017. var ret = value;
  3018. if (typeof value === 'undefined') {
  3019. ret = match;
  3020. }
  3021. return ret;
  3022. });
  3023. }
  3024. return localizedString;
  3025. };
  3026. /**
  3027. * Return the `Component`s DOM element. This is where children get inserted.
  3028. * This will usually be the the same as the element returned in {@link Component#el}.
  3029. *
  3030. * @return {Element}
  3031. * The content element for this `Component`.
  3032. */
  3033. Component.prototype.contentEl = function contentEl() {
  3034. return this.contentEl_ || this.el_;
  3035. };
  3036. /**
  3037. * Get this `Component`s ID
  3038. *
  3039. * @return {string}
  3040. * The id of this `Component`
  3041. */
  3042. Component.prototype.id = function id() {
  3043. return this.id_;
  3044. };
  3045. /**
  3046. * Get the `Component`s name. The name gets used to reference the `Component`
  3047. * and is set during registration.
  3048. *
  3049. * @return {string}
  3050. * The name of this `Component`.
  3051. */
  3052. Component.prototype.name = function name() {
  3053. return this.name_;
  3054. };
  3055. /**
  3056. * Get an array of all child components
  3057. *
  3058. * @return {Array}
  3059. * The children
  3060. */
  3061. Component.prototype.children = function children() {
  3062. return this.children_;
  3063. };
  3064. /**
  3065. * Returns the child `Component` with the given `id`.
  3066. *
  3067. * @param {string} id
  3068. * The id of the child `Component` to get.
  3069. *
  3070. * @return {Component|undefined}
  3071. * The child `Component` with the given `id` or undefined.
  3072. */
  3073. Component.prototype.getChildById = function getChildById(id) {
  3074. return this.childIndex_[id];
  3075. };
  3076. /**
  3077. * Returns the child `Component` with the given `name`.
  3078. *
  3079. * @param {string} name
  3080. * The name of the child `Component` to get.
  3081. *
  3082. * @return {Component|undefined}
  3083. * The child `Component` with the given `name` or undefined.
  3084. */
  3085. Component.prototype.getChild = function getChild(name) {
  3086. if (!name) {
  3087. return;
  3088. }
  3089. name = toTitleCase(name);
  3090. return this.childNameIndex_[name];
  3091. };
  3092. /**
  3093. * Add a child `Component` inside the current `Component`.
  3094. *
  3095. *
  3096. * @param {string|Component} child
  3097. * The name or instance of a child to add.
  3098. *
  3099. * @param {Object} [options={}]
  3100. * The key/value store of options that will get passed to children of
  3101. * the child.
  3102. *
  3103. * @param {number} [index=this.children_.length]
  3104. * The index to attempt to add a child into.
  3105. *
  3106. * @return {Component}
  3107. * The `Component` that gets added as a child. When using a string the
  3108. * `Component` will get created by this process.
  3109. */
  3110. Component.prototype.addChild = function addChild(child) {
  3111. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  3112. var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length;
  3113. var component = void 0;
  3114. var componentName = void 0;
  3115. // If child is a string, create component with options
  3116. if (typeof child === 'string') {
  3117. componentName = toTitleCase(child);
  3118. var componentClassName = options.componentClass || componentName;
  3119. // Set name through options
  3120. options.name = componentName;
  3121. // Create a new object & element for this controls set
  3122. // If there's no .player_, this is a player
  3123. var ComponentClass = Component.getComponent(componentClassName);
  3124. if (!ComponentClass) {
  3125. throw new Error('Component ' + componentClassName + ' does not exist');
  3126. }
  3127. // data stored directly on the videojs object may be
  3128. // misidentified as a component to retain
  3129. // backwards-compatibility with 4.x. check to make sure the
  3130. // component class can be instantiated.
  3131. if (typeof ComponentClass !== 'function') {
  3132. return null;
  3133. }
  3134. component = new ComponentClass(this.player_ || this, options);
  3135. // child is a component instance
  3136. } else {
  3137. component = child;
  3138. }
  3139. this.children_.splice(index, 0, component);
  3140. if (typeof component.id === 'function') {
  3141. this.childIndex_[component.id()] = component;
  3142. }
  3143. // If a name wasn't used to create the component, check if we can use the
  3144. // name function of the component
  3145. componentName = componentName || component.name && toTitleCase(component.name());
  3146. if (componentName) {
  3147. this.childNameIndex_[componentName] = component;
  3148. }
  3149. // Add the UI object's element to the container div (box)
  3150. // Having an element is not required
  3151. if (typeof component.el === 'function' && component.el()) {
  3152. var childNodes = this.contentEl().children;
  3153. var refNode = childNodes[index] || null;
  3154. this.contentEl().insertBefore(component.el(), refNode);
  3155. }
  3156. // Return so it can stored on parent object if desired.
  3157. return component;
  3158. };
  3159. /**
  3160. * Remove a child `Component` from this `Component`s list of children. Also removes
  3161. * the child `Component`s element from this `Component`s element.
  3162. *
  3163. * @param {Component} component
  3164. * The child `Component` to remove.
  3165. */
  3166. Component.prototype.removeChild = function removeChild(component) {
  3167. if (typeof component === 'string') {
  3168. component = this.getChild(component);
  3169. }
  3170. if (!component || !this.children_) {
  3171. return;
  3172. }
  3173. var childFound = false;
  3174. for (var i = this.children_.length - 1; i >= 0; i--) {
  3175. if (this.children_[i] === component) {
  3176. childFound = true;
  3177. this.children_.splice(i, 1);
  3178. break;
  3179. }
  3180. }
  3181. if (!childFound) {
  3182. return;
  3183. }
  3184. this.childIndex_[component.id()] = null;
  3185. this.childNameIndex_[component.name()] = null;
  3186. var compEl = component.el();
  3187. if (compEl && compEl.parentNode === this.contentEl()) {
  3188. this.contentEl().removeChild(component.el());
  3189. }
  3190. };
  3191. /**
  3192. * Add and initialize default child `Component`s based upon options.
  3193. */
  3194. Component.prototype.initChildren = function initChildren() {
  3195. var _this = this;
  3196. var children = this.options_.children;
  3197. if (children) {
  3198. // `this` is `parent`
  3199. var parentOptions = this.options_;
  3200. var handleAdd = function handleAdd(child) {
  3201. var name = child.name;
  3202. var opts = child.opts;
  3203. // Allow options for children to be set at the parent options
  3204. // e.g. videojs(id, { controlBar: false });
  3205. // instead of videojs(id, { children: { controlBar: false });
  3206. if (parentOptions[name] !== undefined) {
  3207. opts = parentOptions[name];
  3208. }
  3209. // Allow for disabling default components
  3210. // e.g. options['children']['posterImage'] = false
  3211. if (opts === false) {
  3212. return;
  3213. }
  3214. // Allow options to be passed as a simple boolean if no configuration
  3215. // is necessary.
  3216. if (opts === true) {
  3217. opts = {};
  3218. }
  3219. // We also want to pass the original player options
  3220. // to each component as well so they don't need to
  3221. // reach back into the player for options later.
  3222. opts.playerOptions = _this.options_.playerOptions;
  3223. // Create and add the child component.
  3224. // Add a direct reference to the child by name on the parent instance.
  3225. // If two of the same component are used, different names should be supplied
  3226. // for each
  3227. var newChild = _this.addChild(name, opts);
  3228. if (newChild) {
  3229. _this[name] = newChild;
  3230. }
  3231. };
  3232. // Allow for an array of children details to passed in the options
  3233. var workingChildren = void 0;
  3234. var Tech = Component.getComponent('Tech');
  3235. if (Array.isArray(children)) {
  3236. workingChildren = children;
  3237. } else {
  3238. workingChildren = Object.keys(children);
  3239. }
  3240. workingChildren
  3241. // children that are in this.options_ but also in workingChildren would
  3242. // give us extra children we do not want. So, we want to filter them out.
  3243. .concat(Object.keys(this.options_).filter(function (child) {
  3244. return !workingChildren.some(function (wchild) {
  3245. if (typeof wchild === 'string') {
  3246. return child === wchild;
  3247. }
  3248. return child === wchild.name;
  3249. });
  3250. })).map(function (child) {
  3251. var name = void 0;
  3252. var opts = void 0;
  3253. if (typeof child === 'string') {
  3254. name = child;
  3255. opts = children[name] || _this.options_[name] || {};
  3256. } else {
  3257. name = child.name;
  3258. opts = child;
  3259. }
  3260. return { name: name, opts: opts };
  3261. }).filter(function (child) {
  3262. // we have to make sure that child.name isn't in the techOrder since
  3263. // techs are registerd as Components but can't aren't compatible
  3264. // See https://github.com/videojs/video.js/issues/2772
  3265. var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
  3266. return c && !Tech.isTech(c);
  3267. }).forEach(handleAdd);
  3268. }
  3269. };
  3270. /**
  3271. * Builds the default DOM class name. Should be overriden by sub-components.
  3272. *
  3273. * @return {string}
  3274. * The DOM class name for this object.
  3275. *
  3276. * @abstract
  3277. */
  3278. Component.prototype.buildCSSClass = function buildCSSClass() {
  3279. // Child classes can include a function that does:
  3280. // return 'CLASS NAME' + this._super();
  3281. return '';
  3282. };
  3283. /**
  3284. * Bind a listener to the component's ready state.
  3285. * Different from event listeners in that if the ready event has already happened
  3286. * it will trigger the function immediately.
  3287. *
  3288. * @return {Component}
  3289. * Returns itself; method can be chained.
  3290. */
  3291. Component.prototype.ready = function ready(fn) {
  3292. var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  3293. if (!fn) {
  3294. return;
  3295. }
  3296. if (!this.isReady_) {
  3297. this.readyQueue_ = this.readyQueue_ || [];
  3298. this.readyQueue_.push(fn);
  3299. return;
  3300. }
  3301. if (sync) {
  3302. fn.call(this);
  3303. } else {
  3304. // Call the function asynchronously by default for consistency
  3305. this.setTimeout(fn, 1);
  3306. }
  3307. };
  3308. /**
  3309. * Trigger all the ready listeners for this `Component`.
  3310. *
  3311. * @fires Component#ready
  3312. */
  3313. Component.prototype.triggerReady = function triggerReady() {
  3314. this.isReady_ = true;
  3315. // Ensure ready is triggered asynchronously
  3316. this.setTimeout(function () {
  3317. var readyQueue = this.readyQueue_;
  3318. // Reset Ready Queue
  3319. this.readyQueue_ = [];
  3320. if (readyQueue && readyQueue.length > 0) {
  3321. readyQueue.forEach(function (fn) {
  3322. fn.call(this);
  3323. }, this);
  3324. }
  3325. // Allow for using event listeners also
  3326. /**
  3327. * Triggered when a `Component` is ready.
  3328. *
  3329. * @event Component#ready
  3330. * @type {EventTarget~Event}
  3331. */
  3332. this.trigger('ready');
  3333. }, 1);
  3334. };
  3335. /**
  3336. * Find a single DOM element matching a `selector`. This can be within the `Component`s
  3337. * `contentEl()` or another custom context.
  3338. *
  3339. * @param {string} selector
  3340. * A valid CSS selector, which will be passed to `querySelector`.
  3341. *
  3342. * @param {Element|string} [context=this.contentEl()]
  3343. * A DOM element within which to query. Can also be a selector string in
  3344. * which case the first matching element will get used as context. If
  3345. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3346. * nothing it falls back to `document`.
  3347. *
  3348. * @return {Element|null}
  3349. * the dom element that was found, or null
  3350. *
  3351. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3352. */
  3353. Component.prototype.$ = function $$$1(selector, context) {
  3354. return $(selector, context || this.contentEl());
  3355. };
  3356. /**
  3357. * Finds all DOM element matching a `selector`. This can be within the `Component`s
  3358. * `contentEl()` or another custom context.
  3359. *
  3360. * @param {string} selector
  3361. * A valid CSS selector, which will be passed to `querySelectorAll`.
  3362. *
  3363. * @param {Element|string} [context=this.contentEl()]
  3364. * A DOM element within which to query. Can also be a selector string in
  3365. * which case the first matching element will get used as context. If
  3366. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3367. * nothing it falls back to `document`.
  3368. *
  3369. * @return {NodeList}
  3370. * a list of dom elements that were found
  3371. *
  3372. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3373. */
  3374. Component.prototype.$$ = function $$$$1(selector, context) {
  3375. return $$(selector, context || this.contentEl());
  3376. };
  3377. /**
  3378. * Check if a component's element has a CSS class name.
  3379. *
  3380. * @param {string} classToCheck
  3381. * CSS class name to check.
  3382. *
  3383. * @return {boolean}
  3384. * - True if the `Component` has the class.
  3385. * - False if the `Component` does not have the class`
  3386. */
  3387. Component.prototype.hasClass = function hasClass$$1(classToCheck) {
  3388. return hasClass(this.el_, classToCheck);
  3389. };
  3390. /**
  3391. * Add a CSS class name to the `Component`s element.
  3392. *
  3393. * @param {string} classToAdd
  3394. * CSS class name to add
  3395. */
  3396. Component.prototype.addClass = function addClass$$1(classToAdd) {
  3397. addClass(this.el_, classToAdd);
  3398. };
  3399. /**
  3400. * Remove a CSS class name from the `Component`s element.
  3401. *
  3402. * @param {string} classToRemove
  3403. * CSS class name to remove
  3404. */
  3405. Component.prototype.removeClass = function removeClass$$1(classToRemove) {
  3406. removeClass(this.el_, classToRemove);
  3407. };
  3408. /**
  3409. * Add or remove a CSS class name from the component's element.
  3410. * - `classToToggle` gets added when {@link Component#hasClass} would return false.
  3411. * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
  3412. *
  3413. * @param {string} classToToggle
  3414. * The class to add or remove based on (@link Component#hasClass}
  3415. *
  3416. * @param {boolean|Dom~predicate} [predicate]
  3417. * An {@link Dom~predicate} function or a boolean
  3418. */
  3419. Component.prototype.toggleClass = function toggleClass$$1(classToToggle, predicate) {
  3420. toggleClass(this.el_, classToToggle, predicate);
  3421. };
  3422. /**
  3423. * Show the `Component`s element if it is hidden by removing the
  3424. * 'vjs-hidden' class name from it.
  3425. */
  3426. Component.prototype.show = function show() {
  3427. this.removeClass('vjs-hidden');
  3428. };
  3429. /**
  3430. * Hide the `Component`s element if it is currently showing by adding the
  3431. * 'vjs-hidden` class name to it.
  3432. */
  3433. Component.prototype.hide = function hide() {
  3434. this.addClass('vjs-hidden');
  3435. };
  3436. /**
  3437. * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
  3438. * class name to it. Used during fadeIn/fadeOut.
  3439. *
  3440. * @private
  3441. */
  3442. Component.prototype.lockShowing = function lockShowing() {
  3443. this.addClass('vjs-lock-showing');
  3444. };
  3445. /**
  3446. * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
  3447. * class name from it. Used during fadeIn/fadeOut.
  3448. *
  3449. * @private
  3450. */
  3451. Component.prototype.unlockShowing = function unlockShowing() {
  3452. this.removeClass('vjs-lock-showing');
  3453. };
  3454. /**
  3455. * Get the value of an attribute on the `Component`s element.
  3456. *
  3457. * @param {string} attribute
  3458. * Name of the attribute to get the value from.
  3459. *
  3460. * @return {string|null}
  3461. * - The value of the attribute that was asked for.
  3462. * - Can be an empty string on some browsers if the attribute does not exist
  3463. * or has no value
  3464. * - Most browsers will return null if the attibute does not exist or has
  3465. * no value.
  3466. *
  3467. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
  3468. */
  3469. Component.prototype.getAttribute = function getAttribute$$1(attribute) {
  3470. return getAttribute(this.el_, attribute);
  3471. };
  3472. /**
  3473. * Set the value of an attribute on the `Component`'s element
  3474. *
  3475. * @param {string} attribute
  3476. * Name of the attribute to set.
  3477. *
  3478. * @param {string} value
  3479. * Value to set the attribute to.
  3480. *
  3481. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
  3482. */
  3483. Component.prototype.setAttribute = function setAttribute$$1(attribute, value) {
  3484. setAttribute(this.el_, attribute, value);
  3485. };
  3486. /**
  3487. * Remove an attribute from the `Component`s element.
  3488. *
  3489. * @param {string} attribute
  3490. * Name of the attribute to remove.
  3491. *
  3492. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
  3493. */
  3494. Component.prototype.removeAttribute = function removeAttribute$$1(attribute) {
  3495. removeAttribute(this.el_, attribute);
  3496. };
  3497. /**
  3498. * Get or set the width of the component based upon the CSS styles.
  3499. * See {@link Component#dimension} for more detailed information.
  3500. *
  3501. * @param {number|string} [num]
  3502. * The width that you want to set postfixed with '%', 'px' or nothing.
  3503. *
  3504. * @param {boolean} [skipListeners]
  3505. * Skip the componentresize event trigger
  3506. *
  3507. * @return {number|string}
  3508. * The width when getting, zero if there is no width. Can be a string
  3509. * postpixed with '%' or 'px'.
  3510. */
  3511. Component.prototype.width = function width(num, skipListeners) {
  3512. return this.dimension('width', num, skipListeners);
  3513. };
  3514. /**
  3515. * Get or set the height of the component based upon the CSS styles.
  3516. * See {@link Component#dimension} for more detailed information.
  3517. *
  3518. * @param {number|string} [num]
  3519. * The height that you want to set postfixed with '%', 'px' or nothing.
  3520. *
  3521. * @param {boolean} [skipListeners]
  3522. * Skip the componentresize event trigger
  3523. *
  3524. * @return {number|string}
  3525. * The width when getting, zero if there is no width. Can be a string
  3526. * postpixed with '%' or 'px'.
  3527. */
  3528. Component.prototype.height = function height(num, skipListeners) {
  3529. return this.dimension('height', num, skipListeners);
  3530. };
  3531. /**
  3532. * Set both the width and height of the `Component` element at the same time.
  3533. *
  3534. * @param {number|string} width
  3535. * Width to set the `Component`s element to.
  3536. *
  3537. * @param {number|string} height
  3538. * Height to set the `Component`s element to.
  3539. */
  3540. Component.prototype.dimensions = function dimensions(width, height) {
  3541. // Skip componentresize listeners on width for optimization
  3542. this.width(width, true);
  3543. this.height(height);
  3544. };
  3545. /**
  3546. * Get or set width or height of the `Component` element. This is the shared code
  3547. * for the {@link Component#width} and {@link Component#height}.
  3548. *
  3549. * Things to know:
  3550. * - If the width or height in an number this will return the number postfixed with 'px'.
  3551. * - If the width/height is a percent this will return the percent postfixed with '%'
  3552. * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
  3553. * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
  3554. * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
  3555. * for more information
  3556. * - If you want the computed style of the component, use {@link Component#currentWidth}
  3557. * and {@link {Component#currentHeight}
  3558. *
  3559. * @fires Component#componentresize
  3560. *
  3561. * @param {string} widthOrHeight
  3562. 8 'width' or 'height'
  3563. *
  3564. * @param {number|string} [num]
  3565. 8 New dimension
  3566. *
  3567. * @param {boolean} [skipListeners]
  3568. * Skip componentresize event trigger
  3569. *
  3570. * @return {number}
  3571. * The dimension when getting or 0 if unset
  3572. */
  3573. Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) {
  3574. if (num !== undefined) {
  3575. // Set to zero if null or literally NaN (NaN !== NaN)
  3576. if (num === null || num !== num) {
  3577. num = 0;
  3578. }
  3579. // Check if using css width/height (% or px) and adjust
  3580. if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
  3581. this.el_.style[widthOrHeight] = num;
  3582. } else if (num === 'auto') {
  3583. this.el_.style[widthOrHeight] = '';
  3584. } else {
  3585. this.el_.style[widthOrHeight] = num + 'px';
  3586. }
  3587. // skipListeners allows us to avoid triggering the resize event when setting both width and height
  3588. if (!skipListeners) {
  3589. /**
  3590. * Triggered when a component is resized.
  3591. *
  3592. * @event Component#componentresize
  3593. * @type {EventTarget~Event}
  3594. */
  3595. this.trigger('componentresize');
  3596. }
  3597. return;
  3598. }
  3599. // Not setting a value, so getting it
  3600. // Make sure element exists
  3601. if (!this.el_) {
  3602. return 0;
  3603. }
  3604. // Get dimension value from style
  3605. var val = this.el_.style[widthOrHeight];
  3606. var pxIndex = val.indexOf('px');
  3607. if (pxIndex !== -1) {
  3608. // Return the pixel value with no 'px'
  3609. return parseInt(val.slice(0, pxIndex), 10);
  3610. }
  3611. // No px so using % or no style was set, so falling back to offsetWidth/height
  3612. // If component has display:none, offset will return 0
  3613. // TODO: handle display:none and no dimension style using px
  3614. return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
  3615. };
  3616. /**
  3617. * Get the computed width or the height of the component's element.
  3618. *
  3619. * Uses `window.getComputedStyle`.
  3620. *
  3621. * @param {string} widthOrHeight
  3622. * A string containing 'width' or 'height'. Whichever one you want to get.
  3623. *
  3624. * @return {number}
  3625. * The dimension that gets asked for or 0 if nothing was set
  3626. * for that dimension.
  3627. */
  3628. Component.prototype.currentDimension = function currentDimension(widthOrHeight) {
  3629. var computedWidthOrHeight = 0;
  3630. if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
  3631. throw new Error('currentDimension only accepts width or height value');
  3632. }
  3633. if (typeof window.getComputedStyle === 'function') {
  3634. var computedStyle = window.getComputedStyle(this.el_);
  3635. computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
  3636. }
  3637. // remove 'px' from variable and parse as integer
  3638. computedWidthOrHeight = parseFloat(computedWidthOrHeight);
  3639. // if the computed value is still 0, it's possible that the browser is lying
  3640. // and we want to check the offset values.
  3641. // This code also runs on IE8 and wherever getComputedStyle doesn't exist.
  3642. if (computedWidthOrHeight === 0) {
  3643. var rule = 'offset' + toTitleCase(widthOrHeight);
  3644. computedWidthOrHeight = this.el_[rule];
  3645. }
  3646. return computedWidthOrHeight;
  3647. };
  3648. /**
  3649. * An object that contains width and height values of the `Component`s
  3650. * computed style. Uses `window.getComputedStyle`.
  3651. *
  3652. * @typedef {Object} Component~DimensionObject
  3653. *
  3654. * @property {number} width
  3655. * The width of the `Component`s computed style.
  3656. *
  3657. * @property {number} height
  3658. * The height of the `Component`s computed style.
  3659. */
  3660. /**
  3661. * Get an object that contains computed width and height values of the
  3662. * component's element.
  3663. *
  3664. * Uses `window.getComputedStyle`.
  3665. *
  3666. * @return {Component~DimensionObject}
  3667. * The computed dimensions of the component's element.
  3668. */
  3669. Component.prototype.currentDimensions = function currentDimensions() {
  3670. return {
  3671. width: this.currentDimension('width'),
  3672. height: this.currentDimension('height')
  3673. };
  3674. };
  3675. /**
  3676. * Get the computed width of the component's element.
  3677. *
  3678. * Uses `window.getComputedStyle`.
  3679. *
  3680. * @return {number}
  3681. * The computed width of the component's element.
  3682. */
  3683. Component.prototype.currentWidth = function currentWidth() {
  3684. return this.currentDimension('width');
  3685. };
  3686. /**
  3687. * Get the computed height of the component's element.
  3688. *
  3689. * Uses `window.getComputedStyle`.
  3690. *
  3691. * @return {number}
  3692. * The computed height of the component's element.
  3693. */
  3694. Component.prototype.currentHeight = function currentHeight() {
  3695. return this.currentDimension('height');
  3696. };
  3697. /**
  3698. * Set the focus to this component
  3699. */
  3700. Component.prototype.focus = function focus() {
  3701. this.el_.focus();
  3702. };
  3703. /**
  3704. * Remove the focus from this component
  3705. */
  3706. Component.prototype.blur = function blur() {
  3707. this.el_.blur();
  3708. };
  3709. /**
  3710. * Emit a 'tap' events when touch event support gets detected. This gets used to
  3711. * support toggling the controls through a tap on the video. They get enabled
  3712. * because every sub-component would have extra overhead otherwise.
  3713. *
  3714. * @private
  3715. * @fires Component#tap
  3716. * @listens Component#touchstart
  3717. * @listens Component#touchmove
  3718. * @listens Component#touchleave
  3719. * @listens Component#touchcancel
  3720. * @listens Component#touchend
  3721. */
  3722. Component.prototype.emitTapEvents = function emitTapEvents() {
  3723. // Track the start time so we can determine how long the touch lasted
  3724. var touchStart = 0;
  3725. var firstTouch = null;
  3726. // Maximum movement allowed during a touch event to still be considered a tap
  3727. // Other popular libs use anywhere from 2 (hammer.js) to 15,
  3728. // so 10 seems like a nice, round number.
  3729. var tapMovementThreshold = 10;
  3730. // The maximum length a touch can be while still being considered a tap
  3731. var touchTimeThreshold = 200;
  3732. var couldBeTap = void 0;
  3733. this.on('touchstart', function (event) {
  3734. // If more than one finger, don't consider treating this as a click
  3735. if (event.touches.length === 1) {
  3736. // Copy pageX/pageY from the object
  3737. firstTouch = {
  3738. pageX: event.touches[0].pageX,
  3739. pageY: event.touches[0].pageY
  3740. };
  3741. // Record start time so we can detect a tap vs. "touch and hold"
  3742. touchStart = new Date().getTime();
  3743. // Reset couldBeTap tracking
  3744. couldBeTap = true;
  3745. }
  3746. });
  3747. this.on('touchmove', function (event) {
  3748. // If more than one finger, don't consider treating this as a click
  3749. if (event.touches.length > 1) {
  3750. couldBeTap = false;
  3751. } else if (firstTouch) {
  3752. // Some devices will throw touchmoves for all but the slightest of taps.
  3753. // So, if we moved only a small distance, this could still be a tap
  3754. var xdiff = event.touches[0].pageX - firstTouch.pageX;
  3755. var ydiff = event.touches[0].pageY - firstTouch.pageY;
  3756. var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
  3757. if (touchDistance > tapMovementThreshold) {
  3758. couldBeTap = false;
  3759. }
  3760. }
  3761. });
  3762. var noTap = function noTap() {
  3763. couldBeTap = false;
  3764. };
  3765. // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
  3766. this.on('touchleave', noTap);
  3767. this.on('touchcancel', noTap);
  3768. // When the touch ends, measure how long it took and trigger the appropriate
  3769. // event
  3770. this.on('touchend', function (event) {
  3771. firstTouch = null;
  3772. // Proceed only if the touchmove/leave/cancel event didn't happen
  3773. if (couldBeTap === true) {
  3774. // Measure how long the touch lasted
  3775. var touchTime = new Date().getTime() - touchStart;
  3776. // Make sure the touch was less than the threshold to be considered a tap
  3777. if (touchTime < touchTimeThreshold) {
  3778. // Don't let browser turn this into a click
  3779. event.preventDefault();
  3780. /**
  3781. * Triggered when a `Component` is tapped.
  3782. *
  3783. * @event Component#tap
  3784. * @type {EventTarget~Event}
  3785. */
  3786. this.trigger('tap');
  3787. // It may be good to copy the touchend event object and change the
  3788. // type to tap, if the other event properties aren't exact after
  3789. // Events.fixEvent runs (e.g. event.target)
  3790. }
  3791. }
  3792. });
  3793. };
  3794. /**
  3795. * This function reports user activity whenever touch events happen. This can get
  3796. * turned off by any sub-components that wants touch events to act another way.
  3797. *
  3798. * Report user touch activity when touch events occur. User activity gets used to
  3799. * determine when controls should show/hide. It is simple when it comes to mouse
  3800. * events, because any mouse event should show the controls. So we capture mouse
  3801. * events that bubble up to the player and report activity when that happens.
  3802. * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
  3803. * controls. So touch events can't help us at the player level either.
  3804. *
  3805. * User activity gets checked asynchronously. So what could happen is a tap event
  3806. * on the video turns the controls off. Then the `touchend` event bubbles up to
  3807. * the player. Which, if it reported user activity, would turn the controls right
  3808. * back on. We also don't want to completely block touch events from bubbling up.
  3809. * Furthermore a `touchmove` event and anything other than a tap, should not turn
  3810. * controls back on.
  3811. *
  3812. * @listens Component#touchstart
  3813. * @listens Component#touchmove
  3814. * @listens Component#touchend
  3815. * @listens Component#touchcancel
  3816. */
  3817. Component.prototype.enableTouchActivity = function enableTouchActivity() {
  3818. // Don't continue if the root player doesn't support reporting user activity
  3819. if (!this.player() || !this.player().reportUserActivity) {
  3820. return;
  3821. }
  3822. // listener for reporting that the user is active
  3823. var report = bind(this.player(), this.player().reportUserActivity);
  3824. var touchHolding = void 0;
  3825. this.on('touchstart', function () {
  3826. report();
  3827. // For as long as the they are touching the device or have their mouse down,
  3828. // we consider them active even if they're not moving their finger or mouse.
  3829. // So we want to continue to update that they are active
  3830. this.clearInterval(touchHolding);
  3831. // report at the same interval as activityCheck
  3832. touchHolding = this.setInterval(report, 250);
  3833. });
  3834. var touchEnd = function touchEnd(event) {
  3835. report();
  3836. // stop the interval that maintains activity if the touch is holding
  3837. this.clearInterval(touchHolding);
  3838. };
  3839. this.on('touchmove', report);
  3840. this.on('touchend', touchEnd);
  3841. this.on('touchcancel', touchEnd);
  3842. };
  3843. /**
  3844. * A callback that has no parameters and is bound into `Component`s context.
  3845. *
  3846. * @callback Component~GenericCallback
  3847. * @this Component
  3848. */
  3849. /**
  3850. * Creates a function that runs after an `x` millisecond timeout. This function is a
  3851. * wrapper around `window.setTimeout`. There are a few reasons to use this one
  3852. * instead though:
  3853. * 1. It gets cleared via {@link Component#clearTimeout} when
  3854. * {@link Component#dispose} gets called.
  3855. * 2. The function callback will gets turned into a {@link Component~GenericCallback}
  3856. *
  3857. * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
  3858. * will cause its dispose listener not to get cleaned up! Please use
  3859. * {@link Component#clearTimeout} or {@link Component#dispose} instead.
  3860. *
  3861. * @param {Component~GenericCallback} fn
  3862. * The function that will be run after `timeout`.
  3863. *
  3864. * @param {number} timeout
  3865. * Timeout in milliseconds to delay before executing the specified function.
  3866. *
  3867. * @return {number}
  3868. * Returns a timeout ID that gets used to identify the timeout. It can also
  3869. * get used in {@link Component#clearTimeout} to clear the timeout that
  3870. * was set.
  3871. *
  3872. * @listens Component#dispose
  3873. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
  3874. */
  3875. Component.prototype.setTimeout = function setTimeout(fn, timeout) {
  3876. var _this2 = this;
  3877. // declare as variables so they are properly available in timeout function
  3878. // eslint-disable-next-line
  3879. var timeoutId, disposeFn;
  3880. fn = bind(this, fn);
  3881. timeoutId = window.setTimeout(function () {
  3882. _this2.off('dispose', disposeFn);
  3883. fn();
  3884. }, timeout);
  3885. disposeFn = function disposeFn() {
  3886. return _this2.clearTimeout(timeoutId);
  3887. };
  3888. disposeFn.guid = 'vjs-timeout-' + timeoutId;
  3889. this.on('dispose', disposeFn);
  3890. return timeoutId;
  3891. };
  3892. /**
  3893. * Clears a timeout that gets created via `window.setTimeout` or
  3894. * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
  3895. * use this function instead of `window.clearTimout`. If you don't your dispose
  3896. * listener will not get cleaned up until {@link Component#dispose}!
  3897. *
  3898. * @param {number} timeoutId
  3899. * The id of the timeout to clear. The return value of
  3900. * {@link Component#setTimeout} or `window.setTimeout`.
  3901. *
  3902. * @return {number}
  3903. * Returns the timeout id that was cleared.
  3904. *
  3905. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
  3906. */
  3907. Component.prototype.clearTimeout = function clearTimeout(timeoutId) {
  3908. window.clearTimeout(timeoutId);
  3909. var disposeFn = function disposeFn() {};
  3910. disposeFn.guid = 'vjs-timeout-' + timeoutId;
  3911. this.off('dispose', disposeFn);
  3912. return timeoutId;
  3913. };
  3914. /**
  3915. * Creates a function that gets run every `x` milliseconds. This function is a wrapper
  3916. * around `window.setInterval`. There are a few reasons to use this one instead though.
  3917. * 1. It gets cleared via {@link Component#clearInterval} when
  3918. * {@link Component#dispose} gets called.
  3919. * 2. The function callback will be a {@link Component~GenericCallback}
  3920. *
  3921. * @param {Component~GenericCallback} fn
  3922. * The function to run every `x` seconds.
  3923. *
  3924. * @param {number} interval
  3925. * Execute the specified function every `x` milliseconds.
  3926. *
  3927. * @return {number}
  3928. * Returns an id that can be used to identify the interval. It can also be be used in
  3929. * {@link Component#clearInterval} to clear the interval.
  3930. *
  3931. * @listens Component#dispose
  3932. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
  3933. */
  3934. Component.prototype.setInterval = function setInterval(fn, interval) {
  3935. var _this3 = this;
  3936. fn = bind(this, fn);
  3937. var intervalId = window.setInterval(fn, interval);
  3938. var disposeFn = function disposeFn() {
  3939. return _this3.clearInterval(intervalId);
  3940. };
  3941. disposeFn.guid = 'vjs-interval-' + intervalId;
  3942. this.on('dispose', disposeFn);
  3943. return intervalId;
  3944. };
  3945. /**
  3946. * Clears an interval that gets created via `window.setInterval` or
  3947. * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
  3948. * use this function instead of `window.clearInterval`. If you don't your dispose
  3949. * listener will not get cleaned up until {@link Component#dispose}!
  3950. *
  3951. * @param {number} intervalId
  3952. * The id of the interval to clear. The return value of
  3953. * {@link Component#setInterval} or `window.setInterval`.
  3954. *
  3955. * @return {number}
  3956. * Returns the interval id that was cleared.
  3957. *
  3958. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
  3959. */
  3960. Component.prototype.clearInterval = function clearInterval(intervalId) {
  3961. window.clearInterval(intervalId);
  3962. var disposeFn = function disposeFn() {};
  3963. disposeFn.guid = 'vjs-interval-' + intervalId;
  3964. this.off('dispose', disposeFn);
  3965. return intervalId;
  3966. };
  3967. /**
  3968. * Queues up a callback to be passed to requestAnimationFrame (rAF), but
  3969. * with a few extra bonuses:
  3970. *
  3971. * - Supports browsers that do not support rAF by falling back to
  3972. * {@link Component#setTimeout}.
  3973. *
  3974. * - The callback is turned into a {@link Component~GenericCallback} (i.e.
  3975. * bound to the component).
  3976. *
  3977. * - Automatic cancellation of the rAF callback is handled if the component
  3978. * is disposed before it is called.
  3979. *
  3980. * @param {Component~GenericCallback} fn
  3981. * A function that will be bound to this component and executed just
  3982. * before the browser's next repaint.
  3983. *
  3984. * @return {number}
  3985. * Returns an rAF ID that gets used to identify the timeout. It can
  3986. * also be used in {@link Component#cancelAnimationFrame} to cancel
  3987. * the animation frame callback.
  3988. *
  3989. * @listens Component#dispose
  3990. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
  3991. */
  3992. Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) {
  3993. var _this4 = this;
  3994. // declare as variables so they are properly available in rAF function
  3995. // eslint-disable-next-line
  3996. var id, disposeFn;
  3997. if (this.supportsRaf_) {
  3998. fn = bind(this, fn);
  3999. id = window.requestAnimationFrame(function () {
  4000. _this4.off('dispose', disposeFn);
  4001. fn();
  4002. });
  4003. disposeFn = function disposeFn() {
  4004. return _this4.cancelAnimationFrame(id);
  4005. };
  4006. disposeFn.guid = 'vjs-raf-' + id;
  4007. this.on('dispose', disposeFn);
  4008. return id;
  4009. }
  4010. // Fall back to using a timer.
  4011. return this.setTimeout(fn, 1000 / 60);
  4012. };
  4013. /**
  4014. * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
  4015. * (rAF).
  4016. *
  4017. * If you queue an rAF callback via {@link Component#requestAnimationFrame},
  4018. * use this function instead of `window.cancelAnimationFrame`. If you don't,
  4019. * your dispose listener will not get cleaned up until {@link Component#dispose}!
  4020. *
  4021. * @param {number} id
  4022. * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
  4023. *
  4024. * @return {number}
  4025. * Returns the rAF ID that was cleared.
  4026. *
  4027. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
  4028. */
  4029. Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) {
  4030. if (this.supportsRaf_) {
  4031. window.cancelAnimationFrame(id);
  4032. var disposeFn = function disposeFn() {};
  4033. disposeFn.guid = 'vjs-raf-' + id;
  4034. this.off('dispose', disposeFn);
  4035. return id;
  4036. }
  4037. // Fall back to using a timer.
  4038. return this.clearTimeout(id);
  4039. };
  4040. /**
  4041. * Register a `Component` with `videojs` given the name and the component.
  4042. *
  4043. * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
  4044. * should be registered using {@link Tech.registerTech} or
  4045. * {@link videojs:videojs.registerTech}.
  4046. *
  4047. * > NOTE: This function can also be seen on videojs as
  4048. * {@link videojs:videojs.registerComponent}.
  4049. *
  4050. * @param {string} name
  4051. * The name of the `Component` to register.
  4052. *
  4053. * @param {Component} ComponentToRegister
  4054. * The `Component` class to register.
  4055. *
  4056. * @return {Component}
  4057. * The `Component` that was registered.
  4058. */
  4059. Component.registerComponent = function registerComponent(name, ComponentToRegister) {
  4060. if (typeof name !== 'string' || !name) {
  4061. throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.');
  4062. }
  4063. var Tech = Component.getComponent('Tech');
  4064. // We need to make sure this check is only done if Tech has been registered.
  4065. var isTech = Tech && Tech.isTech(ComponentToRegister);
  4066. var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
  4067. if (isTech || !isComp) {
  4068. var reason = void 0;
  4069. if (isTech) {
  4070. reason = 'techs must be registered using Tech.registerTech()';
  4071. } else {
  4072. reason = 'must be a Component subclass';
  4073. }
  4074. throw new Error('Illegal component, "' + name + '"; ' + reason + '.');
  4075. }
  4076. name = toTitleCase(name);
  4077. if (!Component.components_) {
  4078. Component.components_ = {};
  4079. }
  4080. var Player = Component.getComponent('Player');
  4081. if (name === 'Player' && Player && Player.players) {
  4082. var players = Player.players;
  4083. var playerNames = Object.keys(players);
  4084. // If we have players that were disposed, then their name will still be
  4085. // in Players.players. So, we must loop through and verify that the value
  4086. // for each item is not null. This allows registration of the Player component
  4087. // after all players have been disposed or before any were created.
  4088. if (players && playerNames.length > 0 && playerNames.map(function (pname) {
  4089. return players[pname];
  4090. }).every(Boolean)) {
  4091. throw new Error('Can not register Player component after player has been created.');
  4092. }
  4093. }
  4094. Component.components_[name] = ComponentToRegister;
  4095. return ComponentToRegister;
  4096. };
  4097. /**
  4098. * Get a `Component` based on the name it was registered with.
  4099. *
  4100. * @param {string} name
  4101. * The Name of the component to get.
  4102. *
  4103. * @return {Component}
  4104. * The `Component` that got registered under the given name.
  4105. *
  4106. * @deprecated In `videojs` 6 this will not return `Component`s that were not
  4107. * registered using {@link Component.registerComponent}. Currently we
  4108. * check the global `videojs` object for a `Component` name and
  4109. * return that if it exists.
  4110. */
  4111. Component.getComponent = function getComponent(name) {
  4112. if (!name) {
  4113. return;
  4114. }
  4115. name = toTitleCase(name);
  4116. if (Component.components_ && Component.components_[name]) {
  4117. return Component.components_[name];
  4118. }
  4119. };
  4120. return Component;
  4121. }();
  4122. /**
  4123. * Whether or not this component supports `requestAnimationFrame`.
  4124. *
  4125. * This is exposed primarily for testing purposes.
  4126. *
  4127. * @private
  4128. * @type {Boolean}
  4129. */
  4130. Component.prototype.supportsRaf_ = typeof window.requestAnimationFrame === 'function' && typeof window.cancelAnimationFrame === 'function';
  4131. Component.registerComponent('Component', Component);
  4132. /**
  4133. * @file time-ranges.js
  4134. * @module time-ranges
  4135. */
  4136. /**
  4137. * Returns the time for the specified index at the start or end
  4138. * of a TimeRange object.
  4139. *
  4140. * @function time-ranges:indexFunction
  4141. *
  4142. * @param {number} [index=0]
  4143. * The range number to return the time for.
  4144. *
  4145. * @return {number}
  4146. * The time that offset at the specified index.
  4147. *
  4148. * @depricated index must be set to a value, in the future this will throw an error.
  4149. */
  4150. /**
  4151. * An object that contains ranges of time for various reasons.
  4152. *
  4153. * @typedef {Object} TimeRange
  4154. *
  4155. * @property {number} length
  4156. * The number of time ranges represented by this Object
  4157. *
  4158. * @property {time-ranges:indexFunction} start
  4159. * Returns the time offset at which a specified time range begins.
  4160. *
  4161. * @property {time-ranges:indexFunction} end
  4162. * Returns the time offset at which a specified time range ends.
  4163. *
  4164. * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
  4165. */
  4166. /**
  4167. * Check if any of the time ranges are over the maximum index.
  4168. *
  4169. * @param {string} fnName
  4170. * The function name to use for logging
  4171. *
  4172. * @param {number} index
  4173. * The index to check
  4174. *
  4175. * @param {number} maxIndex
  4176. * The maximum possible index
  4177. *
  4178. * @throws {Error} if the timeRanges provided are over the maxIndex
  4179. */
  4180. function rangeCheck(fnName, index, maxIndex) {
  4181. if (typeof index !== 'number' || index < 0 || index > maxIndex) {
  4182. throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is non-numeric or out of bounds (0-' + maxIndex + ').');
  4183. }
  4184. }
  4185. /**
  4186. * Get the time for the specified index at the start or end
  4187. * of a TimeRange object.
  4188. *
  4189. * @param {string} fnName
  4190. * The function name to use for logging
  4191. *
  4192. * @param {string} valueIndex
  4193. * The proprety that should be used to get the time. should be 'start' or 'end'
  4194. *
  4195. * @param {Array} ranges
  4196. * An array of time ranges
  4197. *
  4198. * @param {Array} [rangeIndex=0]
  4199. * The index to start the search at
  4200. *
  4201. * @return {number}
  4202. * The time that offset at the specified index.
  4203. *
  4204. *
  4205. * @depricated rangeIndex must be set to a value, in the future this will throw an error.
  4206. * @throws {Error} if rangeIndex is more than the length of ranges
  4207. */
  4208. function getRange(fnName, valueIndex, ranges, rangeIndex) {
  4209. rangeCheck(fnName, rangeIndex, ranges.length - 1);
  4210. return ranges[rangeIndex][valueIndex];
  4211. }
  4212. /**
  4213. * Create a time range object given ranges of time.
  4214. *
  4215. * @param {Array} [ranges]
  4216. * An array of time ranges.
  4217. */
  4218. function createTimeRangesObj(ranges) {
  4219. if (ranges === undefined || ranges.length === 0) {
  4220. return {
  4221. length: 0,
  4222. start: function start() {
  4223. throw new Error('This TimeRanges object is empty');
  4224. },
  4225. end: function end() {
  4226. throw new Error('This TimeRanges object is empty');
  4227. }
  4228. };
  4229. }
  4230. return {
  4231. length: ranges.length,
  4232. start: getRange.bind(null, 'start', 0, ranges),
  4233. end: getRange.bind(null, 'end', 1, ranges)
  4234. };
  4235. }
  4236. /**
  4237. * Should create a fake `TimeRange` object which mimics an HTML5 time range instance.
  4238. *
  4239. * @param {number|Array} start
  4240. * The start of a single range or an array of ranges
  4241. *
  4242. * @param {number} end
  4243. * The end of a single range.
  4244. *
  4245. * @private
  4246. */
  4247. function createTimeRanges(start, end) {
  4248. if (Array.isArray(start)) {
  4249. return createTimeRangesObj(start);
  4250. } else if (start === undefined || end === undefined) {
  4251. return createTimeRangesObj();
  4252. }
  4253. return createTimeRangesObj([[start, end]]);
  4254. }
  4255. /**
  4256. * @file buffer.js
  4257. * @module buffer
  4258. */
  4259. /**
  4260. * Compute the percentage of the media that has been buffered.
  4261. *
  4262. * @param {TimeRange} buffered
  4263. * The current `TimeRange` object representing buffered time ranges
  4264. *
  4265. * @param {number} duration
  4266. * Total duration of the media
  4267. *
  4268. * @return {number}
  4269. * Percent buffered of the total duration in decimal form.
  4270. */
  4271. function bufferedPercent(buffered, duration) {
  4272. var bufferedDuration = 0;
  4273. var start = void 0;
  4274. var end = void 0;
  4275. if (!duration) {
  4276. return 0;
  4277. }
  4278. if (!buffered || !buffered.length) {
  4279. buffered = createTimeRanges(0, 0);
  4280. }
  4281. for (var i = 0; i < buffered.length; i++) {
  4282. start = buffered.start(i);
  4283. end = buffered.end(i);
  4284. // buffered end can be bigger than duration by a very small fraction
  4285. if (end > duration) {
  4286. end = duration;
  4287. }
  4288. bufferedDuration += end - start;
  4289. }
  4290. return bufferedDuration / duration;
  4291. }
  4292. /**
  4293. * @file fullscreen-api.js
  4294. * @module fullscreen-api
  4295. * @private
  4296. */
  4297. /**
  4298. * Store the browser-specific methods for the fullscreen API.
  4299. *
  4300. * @type {Object}
  4301. * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
  4302. * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
  4303. */
  4304. var FullscreenApi = {};
  4305. // browser API methods
  4306. var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'],
  4307. // WebKit
  4308. ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'],
  4309. // Old WebKit (Safari 5.1)
  4310. ['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'],
  4311. // Mozilla
  4312. ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'],
  4313. // Microsoft
  4314. ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']];
  4315. var specApi = apiMap[0];
  4316. var browserApi = void 0;
  4317. // determine the supported set of functions
  4318. for (var i = 0; i < apiMap.length; i++) {
  4319. // check for exitFullscreen function
  4320. if (apiMap[i][1] in document) {
  4321. browserApi = apiMap[i];
  4322. break;
  4323. }
  4324. }
  4325. // map the browser API names to the spec API names
  4326. if (browserApi) {
  4327. for (var _i = 0; _i < browserApi.length; _i++) {
  4328. FullscreenApi[specApi[_i]] = browserApi[_i];
  4329. }
  4330. }
  4331. /**
  4332. * @file media-error.js
  4333. */
  4334. /**
  4335. * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
  4336. *
  4337. * @param {number|string|Object|MediaError} value
  4338. * This can be of multiple types:
  4339. * - number: should be a standard error code
  4340. * - string: an error message (the code will be 0)
  4341. * - Object: arbitrary properties
  4342. * - `MediaError` (native): used to populate a video.js `MediaError` object
  4343. * - `MediaError` (video.js): will return itself if it's already a
  4344. * video.js `MediaError` object.
  4345. *
  4346. * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
  4347. * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
  4348. *
  4349. * @class MediaError
  4350. */
  4351. function MediaError(value) {
  4352. // Allow redundant calls to this constructor to avoid having `instanceof`
  4353. // checks peppered around the code.
  4354. if (value instanceof MediaError) {
  4355. return value;
  4356. }
  4357. if (typeof value === 'number') {
  4358. this.code = value;
  4359. } else if (typeof value === 'string') {
  4360. // default code is zero, so this is a custom error
  4361. this.message = value;
  4362. } else if (isObject(value)) {
  4363. // We assign the `code` property manually because native `MediaError` objects
  4364. // do not expose it as an own/enumerable property of the object.
  4365. if (typeof value.code === 'number') {
  4366. this.code = value.code;
  4367. }
  4368. assign(this, value);
  4369. }
  4370. if (!this.message) {
  4371. this.message = MediaError.defaultMessages[this.code] || '';
  4372. }
  4373. }
  4374. /**
  4375. * The error code that refers two one of the defined `MediaError` types
  4376. *
  4377. * @type {Number}
  4378. */
  4379. MediaError.prototype.code = 0;
  4380. /**
  4381. * An optional message that to show with the error. Message is not part of the HTML5
  4382. * video spec but allows for more informative custom errors.
  4383. *
  4384. * @type {String}
  4385. */
  4386. MediaError.prototype.message = '';
  4387. /**
  4388. * An optional status code that can be set by plugins to allow even more detail about
  4389. * the error. For example a plugin might provide a specific HTTP status code and an
  4390. * error message for that code. Then when the plugin gets that error this class will
  4391. * know how to display an error message for it. This allows a custom message to show
  4392. * up on the `Player` error overlay.
  4393. *
  4394. * @type {Array}
  4395. */
  4396. MediaError.prototype.status = null;
  4397. /**
  4398. * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
  4399. * specification listed under {@link MediaError} for more information.
  4400. *
  4401. * @enum {array}
  4402. * @readonly
  4403. * @property {string} 0 - MEDIA_ERR_CUSTOM
  4404. * @property {string} 1 - MEDIA_ERR_CUSTOM
  4405. * @property {string} 2 - MEDIA_ERR_ABORTED
  4406. * @property {string} 3 - MEDIA_ERR_NETWORK
  4407. * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
  4408. * @property {string} 5 - MEDIA_ERR_ENCRYPTED
  4409. */
  4410. MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
  4411. /**
  4412. * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
  4413. *
  4414. * @type {Array}
  4415. * @constant
  4416. */
  4417. MediaError.defaultMessages = {
  4418. 1: 'You aborted the media playback',
  4419. 2: 'A network error caused the media download to fail part-way.',
  4420. 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
  4421. 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
  4422. 5: 'The media is encrypted and we do not have the keys to decrypt it.'
  4423. };
  4424. // Add types as properties on MediaError
  4425. // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
  4426. for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
  4427. MediaError[MediaError.errorTypes[errNum]] = errNum;
  4428. // values should be accessible on both the class and instance
  4429. MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
  4430. }
  4431. /**
  4432. * Returns whether an object is `Promise`-like (i.e. has a `then` method).
  4433. *
  4434. * @param {Object} value
  4435. * An object that may or may not be `Promise`-like.
  4436. *
  4437. * @return {Boolean}
  4438. * Whether or not the object is `Promise`-like.
  4439. */
  4440. function isPromise(value) {
  4441. return value !== undefined && value !== null && typeof value.then === 'function';
  4442. }
  4443. /**
  4444. * Silence a Promise-like object.
  4445. *
  4446. * This is useful for avoiding non-harmful, but potentially confusing "uncaught
  4447. * play promise" rejection error messages.
  4448. *
  4449. * @param {Object} value
  4450. * An object that may or may not be `Promise`-like.
  4451. */
  4452. function silencePromise(value) {
  4453. if (isPromise(value)) {
  4454. value.then(null, function (e) {});
  4455. }
  4456. }
  4457. /**
  4458. * @file text-track-list-converter.js Utilities for capturing text track state and
  4459. * re-creating tracks based on a capture.
  4460. *
  4461. * @module text-track-list-converter
  4462. */
  4463. /**
  4464. * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
  4465. * represents the {@link TextTrack}'s state.
  4466. *
  4467. * @param {TextTrack} track
  4468. * The text track to query.
  4469. *
  4470. * @return {Object}
  4471. * A serializable javascript representation of the TextTrack.
  4472. * @private
  4473. */
  4474. var trackToJson_ = function trackToJson_(track) {
  4475. var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
  4476. if (track[prop]) {
  4477. acc[prop] = track[prop];
  4478. }
  4479. return acc;
  4480. }, {
  4481. cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
  4482. return {
  4483. startTime: cue.startTime,
  4484. endTime: cue.endTime,
  4485. text: cue.text,
  4486. id: cue.id
  4487. };
  4488. })
  4489. });
  4490. return ret;
  4491. };
  4492. /**
  4493. * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
  4494. * state of all {@link TextTrack}s currently configured. The return array is compatible with
  4495. * {@link text-track-list-converter:jsonToTextTracks}.
  4496. *
  4497. * @param {Tech} tech
  4498. * The tech object to query
  4499. *
  4500. * @return {Array}
  4501. * A serializable javascript representation of the {@link Tech}s
  4502. * {@link TextTrackList}.
  4503. */
  4504. var textTracksToJson = function textTracksToJson(tech) {
  4505. var trackEls = tech.$$('track');
  4506. var trackObjs = Array.prototype.map.call(trackEls, function (t) {
  4507. return t.track;
  4508. });
  4509. var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
  4510. var json = trackToJson_(trackEl.track);
  4511. if (trackEl.src) {
  4512. json.src = trackEl.src;
  4513. }
  4514. return json;
  4515. });
  4516. return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
  4517. return trackObjs.indexOf(track) === -1;
  4518. }).map(trackToJson_));
  4519. };
  4520. /**
  4521. * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
  4522. * object {@link TextTrack} representations.
  4523. *
  4524. * @param {Array} json
  4525. * An array of `TextTrack` representation objects, like those that would be
  4526. * produced by `textTracksToJson`.
  4527. *
  4528. * @param {Tech} tech
  4529. * The `Tech` to create the `TextTrack`s on.
  4530. */
  4531. var jsonToTextTracks = function jsonToTextTracks(json, tech) {
  4532. json.forEach(function (track) {
  4533. var addedTrack = tech.addRemoteTextTrack(track).track;
  4534. if (!track.src && track.cues) {
  4535. track.cues.forEach(function (cue) {
  4536. return addedTrack.addCue(cue);
  4537. });
  4538. }
  4539. });
  4540. return tech.textTracks();
  4541. };
  4542. var textTrackConverter = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ };
  4543. /**
  4544. * @file modal-dialog.js
  4545. */
  4546. var MODAL_CLASS_NAME = 'vjs-modal-dialog';
  4547. var ESC = 27;
  4548. /**
  4549. * The `ModalDialog` displays over the video and its controls, which blocks
  4550. * interaction with the player until it is closed.
  4551. *
  4552. * Modal dialogs include a "Close" button and will close when that button
  4553. * is activated - or when ESC is pressed anywhere.
  4554. *
  4555. * @extends Component
  4556. */
  4557. var ModalDialog = function (_Component) {
  4558. inherits(ModalDialog, _Component);
  4559. /**
  4560. * Create an instance of this class.
  4561. *
  4562. * @param {Player} player
  4563. * The `Player` that this class should be attached to.
  4564. *
  4565. * @param {Object} [options]
  4566. * The key/value store of player options.
  4567. *
  4568. * @param {Mixed} [options.content=undefined]
  4569. * Provide customized content for this modal.
  4570. *
  4571. * @param {string} [options.description]
  4572. * A text description for the modal, primarily for accessibility.
  4573. *
  4574. * @param {boolean} [options.fillAlways=false]
  4575. * Normally, modals are automatically filled only the first time
  4576. * they open. This tells the modal to refresh its content
  4577. * every time it opens.
  4578. *
  4579. * @param {string} [options.label]
  4580. * A text label for the modal, primarily for accessibility.
  4581. *
  4582. * @param {boolean} [options.temporary=true]
  4583. * If `true`, the modal can only be opened once; it will be
  4584. * disposed as soon as it's closed.
  4585. *
  4586. * @param {boolean} [options.uncloseable=false]
  4587. * If `true`, the user will not be able to close the modal
  4588. * through the UI in the normal ways. Programmatic closing is
  4589. * still possible.
  4590. */
  4591. function ModalDialog(player, options) {
  4592. classCallCheck(this, ModalDialog);
  4593. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  4594. _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
  4595. _this.closeable(!_this.options_.uncloseable);
  4596. _this.content(_this.options_.content);
  4597. // Make sure the contentEl is defined AFTER any children are initialized
  4598. // because we only want the contents of the modal in the contentEl
  4599. // (not the UI elements like the close button).
  4600. _this.contentEl_ = createEl('div', {
  4601. className: MODAL_CLASS_NAME + '-content'
  4602. }, {
  4603. role: 'document'
  4604. });
  4605. _this.descEl_ = createEl('p', {
  4606. className: MODAL_CLASS_NAME + '-description vjs-control-text',
  4607. id: _this.el().getAttribute('aria-describedby')
  4608. });
  4609. textContent(_this.descEl_, _this.description());
  4610. _this.el_.appendChild(_this.descEl_);
  4611. _this.el_.appendChild(_this.contentEl_);
  4612. return _this;
  4613. }
  4614. /**
  4615. * Create the `ModalDialog`'s DOM element
  4616. *
  4617. * @return {Element}
  4618. * The DOM element that gets created.
  4619. */
  4620. ModalDialog.prototype.createEl = function createEl$$1() {
  4621. return _Component.prototype.createEl.call(this, 'div', {
  4622. className: this.buildCSSClass(),
  4623. tabIndex: -1
  4624. }, {
  4625. 'aria-describedby': this.id() + '_description',
  4626. 'aria-hidden': 'true',
  4627. 'aria-label': this.label(),
  4628. 'role': 'dialog'
  4629. });
  4630. };
  4631. ModalDialog.prototype.dispose = function dispose() {
  4632. this.contentEl_ = null;
  4633. this.descEl_ = null;
  4634. this.previouslyActiveEl_ = null;
  4635. _Component.prototype.dispose.call(this);
  4636. };
  4637. /**
  4638. * Builds the default DOM `className`.
  4639. *
  4640. * @return {string}
  4641. * The DOM `className` for this object.
  4642. */
  4643. ModalDialog.prototype.buildCSSClass = function buildCSSClass() {
  4644. return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this);
  4645. };
  4646. /**
  4647. * Handles `keydown` events on the document, looking for ESC, which closes
  4648. * the modal.
  4649. *
  4650. * @param {EventTarget~Event} e
  4651. * The keypress that triggered this event.
  4652. *
  4653. * @listens keydown
  4654. */
  4655. ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) {
  4656. if (e.which === ESC && this.closeable()) {
  4657. this.close();
  4658. }
  4659. };
  4660. /**
  4661. * Returns the label string for this modal. Primarily used for accessibility.
  4662. *
  4663. * @return {string}
  4664. * the localized or raw label of this modal.
  4665. */
  4666. ModalDialog.prototype.label = function label() {
  4667. return this.localize(this.options_.label || 'Modal Window');
  4668. };
  4669. /**
  4670. * Returns the description string for this modal. Primarily used for
  4671. * accessibility.
  4672. *
  4673. * @return {string}
  4674. * The localized or raw description of this modal.
  4675. */
  4676. ModalDialog.prototype.description = function description() {
  4677. var desc = this.options_.description || this.localize('This is a modal window.');
  4678. // Append a universal closeability message if the modal is closeable.
  4679. if (this.closeable()) {
  4680. desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
  4681. }
  4682. return desc;
  4683. };
  4684. /**
  4685. * Opens the modal.
  4686. *
  4687. * @fires ModalDialog#beforemodalopen
  4688. * @fires ModalDialog#modalopen
  4689. */
  4690. ModalDialog.prototype.open = function open() {
  4691. if (!this.opened_) {
  4692. var player = this.player();
  4693. /**
  4694. * Fired just before a `ModalDialog` is opened.
  4695. *
  4696. * @event ModalDialog#beforemodalopen
  4697. * @type {EventTarget~Event}
  4698. */
  4699. this.trigger('beforemodalopen');
  4700. this.opened_ = true;
  4701. // Fill content if the modal has never opened before and
  4702. // never been filled.
  4703. if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
  4704. this.fill();
  4705. }
  4706. // If the player was playing, pause it and take note of its previously
  4707. // playing state.
  4708. this.wasPlaying_ = !player.paused();
  4709. if (this.options_.pauseOnOpen && this.wasPlaying_) {
  4710. player.pause();
  4711. }
  4712. if (this.closeable()) {
  4713. this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  4714. }
  4715. // Hide controls and note if they were enabled.
  4716. this.hadControls_ = player.controls();
  4717. player.controls(false);
  4718. this.show();
  4719. this.conditionalFocus_();
  4720. this.el().setAttribute('aria-hidden', 'false');
  4721. /**
  4722. * Fired just after a `ModalDialog` is opened.
  4723. *
  4724. * @event ModalDialog#modalopen
  4725. * @type {EventTarget~Event}
  4726. */
  4727. this.trigger('modalopen');
  4728. this.hasBeenOpened_ = true;
  4729. }
  4730. };
  4731. /**
  4732. * If the `ModalDialog` is currently open or closed.
  4733. *
  4734. * @param {boolean} [value]
  4735. * If given, it will open (`true`) or close (`false`) the modal.
  4736. *
  4737. * @return {boolean}
  4738. * the current open state of the modaldialog
  4739. */
  4740. ModalDialog.prototype.opened = function opened(value) {
  4741. if (typeof value === 'boolean') {
  4742. this[value ? 'open' : 'close']();
  4743. }
  4744. return this.opened_;
  4745. };
  4746. /**
  4747. * Closes the modal, does nothing if the `ModalDialog` is
  4748. * not open.
  4749. *
  4750. * @fires ModalDialog#beforemodalclose
  4751. * @fires ModalDialog#modalclose
  4752. */
  4753. ModalDialog.prototype.close = function close() {
  4754. if (!this.opened_) {
  4755. return;
  4756. }
  4757. var player = this.player();
  4758. /**
  4759. * Fired just before a `ModalDialog` is closed.
  4760. *
  4761. * @event ModalDialog#beforemodalclose
  4762. * @type {EventTarget~Event}
  4763. */
  4764. this.trigger('beforemodalclose');
  4765. this.opened_ = false;
  4766. if (this.wasPlaying_ && this.options_.pauseOnOpen) {
  4767. player.play();
  4768. }
  4769. if (this.closeable()) {
  4770. this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  4771. }
  4772. if (this.hadControls_) {
  4773. player.controls(true);
  4774. }
  4775. this.hide();
  4776. this.el().setAttribute('aria-hidden', 'true');
  4777. /**
  4778. * Fired just after a `ModalDialog` is closed.
  4779. *
  4780. * @event ModalDialog#modalclose
  4781. * @type {EventTarget~Event}
  4782. */
  4783. this.trigger('modalclose');
  4784. this.conditionalBlur_();
  4785. if (this.options_.temporary) {
  4786. this.dispose();
  4787. }
  4788. };
  4789. /**
  4790. * Check to see if the `ModalDialog` is closeable via the UI.
  4791. *
  4792. * @param {boolean} [value]
  4793. * If given as a boolean, it will set the `closeable` option.
  4794. *
  4795. * @return {boolean}
  4796. * Returns the final value of the closable option.
  4797. */
  4798. ModalDialog.prototype.closeable = function closeable(value) {
  4799. if (typeof value === 'boolean') {
  4800. var closeable = this.closeable_ = !!value;
  4801. var close = this.getChild('closeButton');
  4802. // If this is being made closeable and has no close button, add one.
  4803. if (closeable && !close) {
  4804. // The close button should be a child of the modal - not its
  4805. // content element, so temporarily change the content element.
  4806. var temp = this.contentEl_;
  4807. this.contentEl_ = this.el_;
  4808. close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' });
  4809. this.contentEl_ = temp;
  4810. this.on(close, 'close', this.close);
  4811. }
  4812. // If this is being made uncloseable and has a close button, remove it.
  4813. if (!closeable && close) {
  4814. this.off(close, 'close', this.close);
  4815. this.removeChild(close);
  4816. close.dispose();
  4817. }
  4818. }
  4819. return this.closeable_;
  4820. };
  4821. /**
  4822. * Fill the modal's content element with the modal's "content" option.
  4823. * The content element will be emptied before this change takes place.
  4824. */
  4825. ModalDialog.prototype.fill = function fill() {
  4826. this.fillWith(this.content());
  4827. };
  4828. /**
  4829. * Fill the modal's content element with arbitrary content.
  4830. * The content element will be emptied before this change takes place.
  4831. *
  4832. * @fires ModalDialog#beforemodalfill
  4833. * @fires ModalDialog#modalfill
  4834. *
  4835. * @param {Mixed} [content]
  4836. * The same rules apply to this as apply to the `content` option.
  4837. */
  4838. ModalDialog.prototype.fillWith = function fillWith(content) {
  4839. var contentEl = this.contentEl();
  4840. var parentEl = contentEl.parentNode;
  4841. var nextSiblingEl = contentEl.nextSibling;
  4842. /**
  4843. * Fired just before a `ModalDialog` is filled with content.
  4844. *
  4845. * @event ModalDialog#beforemodalfill
  4846. * @type {EventTarget~Event}
  4847. */
  4848. this.trigger('beforemodalfill');
  4849. this.hasBeenFilled_ = true;
  4850. // Detach the content element from the DOM before performing
  4851. // manipulation to avoid modifying the live DOM multiple times.
  4852. parentEl.removeChild(contentEl);
  4853. this.empty();
  4854. insertContent(contentEl, content);
  4855. /**
  4856. * Fired just after a `ModalDialog` is filled with content.
  4857. *
  4858. * @event ModalDialog#modalfill
  4859. * @type {EventTarget~Event}
  4860. */
  4861. this.trigger('modalfill');
  4862. // Re-inject the re-filled content element.
  4863. if (nextSiblingEl) {
  4864. parentEl.insertBefore(contentEl, nextSiblingEl);
  4865. } else {
  4866. parentEl.appendChild(contentEl);
  4867. }
  4868. // make sure that the close button is last in the dialog DOM
  4869. var closeButton = this.getChild('closeButton');
  4870. if (closeButton) {
  4871. parentEl.appendChild(closeButton.el_);
  4872. }
  4873. };
  4874. /**
  4875. * Empties the content element. This happens anytime the modal is filled.
  4876. *
  4877. * @fires ModalDialog#beforemodalempty
  4878. * @fires ModalDialog#modalempty
  4879. */
  4880. ModalDialog.prototype.empty = function empty() {
  4881. /**
  4882. * Fired just before a `ModalDialog` is emptied.
  4883. *
  4884. * @event ModalDialog#beforemodalempty
  4885. * @type {EventTarget~Event}
  4886. */
  4887. this.trigger('beforemodalempty');
  4888. emptyEl(this.contentEl());
  4889. /**
  4890. * Fired just after a `ModalDialog` is emptied.
  4891. *
  4892. * @event ModalDialog#modalempty
  4893. * @type {EventTarget~Event}
  4894. */
  4895. this.trigger('modalempty');
  4896. };
  4897. /**
  4898. * Gets or sets the modal content, which gets normalized before being
  4899. * rendered into the DOM.
  4900. *
  4901. * This does not update the DOM or fill the modal, but it is called during
  4902. * that process.
  4903. *
  4904. * @param {Mixed} [value]
  4905. * If defined, sets the internal content value to be used on the
  4906. * next call(s) to `fill`. This value is normalized before being
  4907. * inserted. To "clear" the internal content value, pass `null`.
  4908. *
  4909. * @return {Mixed}
  4910. * The current content of the modal dialog
  4911. */
  4912. ModalDialog.prototype.content = function content(value) {
  4913. if (typeof value !== 'undefined') {
  4914. this.content_ = value;
  4915. }
  4916. return this.content_;
  4917. };
  4918. /**
  4919. * conditionally focus the modal dialog if focus was previously on the player.
  4920. *
  4921. * @private
  4922. */
  4923. ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() {
  4924. var activeEl = document.activeElement;
  4925. var playerEl = this.player_.el_;
  4926. this.previouslyActiveEl_ = null;
  4927. if (playerEl.contains(activeEl) || playerEl === activeEl) {
  4928. this.previouslyActiveEl_ = activeEl;
  4929. this.focus();
  4930. this.on(document, 'keydown', this.handleKeyDown);
  4931. }
  4932. };
  4933. /**
  4934. * conditionally blur the element and refocus the last focused element
  4935. *
  4936. * @private
  4937. */
  4938. ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() {
  4939. if (this.previouslyActiveEl_) {
  4940. this.previouslyActiveEl_.focus();
  4941. this.previouslyActiveEl_ = null;
  4942. }
  4943. this.off(document, 'keydown', this.handleKeyDown);
  4944. };
  4945. /**
  4946. * Keydown handler. Attached when modal is focused.
  4947. *
  4948. * @listens keydown
  4949. */
  4950. ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) {
  4951. // exit early if it isn't a tab key
  4952. if (event.which !== 9) {
  4953. return;
  4954. }
  4955. var focusableEls = this.focusableEls_();
  4956. var activeEl = this.el_.querySelector(':focus');
  4957. var focusIndex = void 0;
  4958. for (var i = 0; i < focusableEls.length; i++) {
  4959. if (activeEl === focusableEls[i]) {
  4960. focusIndex = i;
  4961. break;
  4962. }
  4963. }
  4964. if (document.activeElement === this.el_) {
  4965. focusIndex = 0;
  4966. }
  4967. if (event.shiftKey && focusIndex === 0) {
  4968. focusableEls[focusableEls.length - 1].focus();
  4969. event.preventDefault();
  4970. } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
  4971. focusableEls[0].focus();
  4972. event.preventDefault();
  4973. }
  4974. };
  4975. /**
  4976. * get all focusable elements
  4977. *
  4978. * @private
  4979. */
  4980. ModalDialog.prototype.focusableEls_ = function focusableEls_() {
  4981. var allChildren = this.el_.querySelectorAll('*');
  4982. return Array.prototype.filter.call(allChildren, function (child) {
  4983. return (child instanceof window.HTMLAnchorElement || child instanceof window.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window.HTMLInputElement || child instanceof window.HTMLSelectElement || child instanceof window.HTMLTextAreaElement || child instanceof window.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window.HTMLIFrameElement || child instanceof window.HTMLObjectElement || child instanceof window.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
  4984. });
  4985. };
  4986. return ModalDialog;
  4987. }(Component);
  4988. /**
  4989. * Default options for `ModalDialog` default options.
  4990. *
  4991. * @type {Object}
  4992. * @private
  4993. */
  4994. ModalDialog.prototype.options_ = {
  4995. pauseOnOpen: true,
  4996. temporary: true
  4997. };
  4998. Component.registerComponent('ModalDialog', ModalDialog);
  4999. /**
  5000. * @file track-list.js
  5001. */
  5002. /**
  5003. * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
  5004. * {@link VideoTrackList}
  5005. *
  5006. * @extends EventTarget
  5007. */
  5008. var TrackList = function (_EventTarget) {
  5009. inherits(TrackList, _EventTarget);
  5010. /**
  5011. * Create an instance of this class
  5012. *
  5013. * @param {Track[]} tracks
  5014. * A list of tracks to initialize the list with.
  5015. *
  5016. * @param {Object} [list]
  5017. * The child object with inheritance done manually for ie8.
  5018. *
  5019. * @abstract
  5020. */
  5021. function TrackList() {
  5022. var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  5023. var _ret;
  5024. var list = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
  5025. classCallCheck(this, TrackList);
  5026. var _this = possibleConstructorReturn(this, _EventTarget.call(this));
  5027. if (!list) {
  5028. list = _this; // eslint-disable-line
  5029. if (IS_IE8) {
  5030. list = document.createElement('custom');
  5031. for (var prop in TrackList.prototype) {
  5032. if (prop !== 'constructor') {
  5033. list[prop] = TrackList.prototype[prop];
  5034. }
  5035. }
  5036. }
  5037. }
  5038. list.tracks_ = [];
  5039. /**
  5040. * @memberof TrackList
  5041. * @member {number} length
  5042. * The current number of `Track`s in the this Trackist.
  5043. * @instance
  5044. */
  5045. Object.defineProperty(list, 'length', {
  5046. get: function get$$1() {
  5047. return this.tracks_.length;
  5048. }
  5049. });
  5050. for (var i = 0; i < tracks.length; i++) {
  5051. list.addTrack(tracks[i]);
  5052. }
  5053. // must return the object, as for ie8 it will not be this
  5054. // but a reference to a document object
  5055. return _ret = list, possibleConstructorReturn(_this, _ret);
  5056. }
  5057. /**
  5058. * Add a {@link Track} to the `TrackList`
  5059. *
  5060. * @param {Track} track
  5061. * The audio, video, or text track to add to the list.
  5062. *
  5063. * @fires TrackList#addtrack
  5064. */
  5065. TrackList.prototype.addTrack = function addTrack(track) {
  5066. var index = this.tracks_.length;
  5067. if (!('' + index in this)) {
  5068. Object.defineProperty(this, index, {
  5069. get: function get$$1() {
  5070. return this.tracks_[index];
  5071. }
  5072. });
  5073. }
  5074. // Do not add duplicate tracks
  5075. if (this.tracks_.indexOf(track) === -1) {
  5076. this.tracks_.push(track);
  5077. /**
  5078. * Triggered when a track is added to a track list.
  5079. *
  5080. * @event TrackList#addtrack
  5081. * @type {EventTarget~Event}
  5082. * @property {Track} track
  5083. * A reference to track that was added.
  5084. */
  5085. this.trigger({
  5086. track: track,
  5087. type: 'addtrack'
  5088. });
  5089. }
  5090. };
  5091. /**
  5092. * Remove a {@link Track} from the `TrackList`
  5093. *
  5094. * @param {Track} rtrack
  5095. * The audio, video, or text track to remove from the list.
  5096. *
  5097. * @fires TrackList#removetrack
  5098. */
  5099. TrackList.prototype.removeTrack = function removeTrack(rtrack) {
  5100. var track = void 0;
  5101. for (var i = 0, l = this.length; i < l; i++) {
  5102. if (this[i] === rtrack) {
  5103. track = this[i];
  5104. if (track.off) {
  5105. track.off();
  5106. }
  5107. this.tracks_.splice(i, 1);
  5108. break;
  5109. }
  5110. }
  5111. if (!track) {
  5112. return;
  5113. }
  5114. /**
  5115. * Triggered when a track is removed from track list.
  5116. *
  5117. * @event TrackList#removetrack
  5118. * @type {EventTarget~Event}
  5119. * @property {Track} track
  5120. * A reference to track that was removed.
  5121. */
  5122. this.trigger({
  5123. track: track,
  5124. type: 'removetrack'
  5125. });
  5126. };
  5127. /**
  5128. * Get a Track from the TrackList by a tracks id
  5129. *
  5130. * @param {String} id - the id of the track to get
  5131. * @method getTrackById
  5132. * @return {Track}
  5133. * @private
  5134. */
  5135. TrackList.prototype.getTrackById = function getTrackById(id) {
  5136. var result = null;
  5137. for (var i = 0, l = this.length; i < l; i++) {
  5138. var track = this[i];
  5139. if (track.id === id) {
  5140. result = track;
  5141. break;
  5142. }
  5143. }
  5144. return result;
  5145. };
  5146. return TrackList;
  5147. }(EventTarget);
  5148. /**
  5149. * Triggered when a different track is selected/enabled.
  5150. *
  5151. * @event TrackList#change
  5152. * @type {EventTarget~Event}
  5153. */
  5154. /**
  5155. * Events that can be called with on + eventName. See {@link EventHandler}.
  5156. *
  5157. * @property {Object} TrackList#allowedEvents_
  5158. * @private
  5159. */
  5160. TrackList.prototype.allowedEvents_ = {
  5161. change: 'change',
  5162. addtrack: 'addtrack',
  5163. removetrack: 'removetrack'
  5164. };
  5165. // emulate attribute EventHandler support to allow for feature detection
  5166. for (var event in TrackList.prototype.allowedEvents_) {
  5167. TrackList.prototype['on' + event] = null;
  5168. }
  5169. /**
  5170. * @file audio-track-list.js
  5171. */
  5172. /**
  5173. * Anywhere we call this function we diverge from the spec
  5174. * as we only support one enabled audiotrack at a time
  5175. *
  5176. * @param {AudioTrackList} list
  5177. * list to work on
  5178. *
  5179. * @param {AudioTrack} track
  5180. * The track to skip
  5181. *
  5182. * @private
  5183. */
  5184. var disableOthers = function disableOthers(list, track) {
  5185. for (var i = 0; i < list.length; i++) {
  5186. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5187. continue;
  5188. }
  5189. // another audio track is enabled, disable it
  5190. list[i].enabled = false;
  5191. }
  5192. };
  5193. /**
  5194. * The current list of {@link AudioTrack} for a media file.
  5195. *
  5196. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
  5197. * @extends TrackList
  5198. */
  5199. var AudioTrackList = function (_TrackList) {
  5200. inherits(AudioTrackList, _TrackList);
  5201. /**
  5202. * Create an instance of this class.
  5203. *
  5204. * @param {AudioTrack[]} [tracks=[]]
  5205. * A list of `AudioTrack` to instantiate the list with.
  5206. */
  5207. function AudioTrackList() {
  5208. var _this, _ret;
  5209. var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  5210. classCallCheck(this, AudioTrackList);
  5211. var list = void 0;
  5212. // make sure only 1 track is enabled
  5213. // sorted from last index to first index
  5214. for (var i = tracks.length - 1; i >= 0; i--) {
  5215. if (tracks[i].enabled) {
  5216. disableOthers(tracks, tracks[i]);
  5217. break;
  5218. }
  5219. }
  5220. // IE8 forces us to implement inheritance ourselves
  5221. // as it does not support Object.defineProperty properly
  5222. if (IS_IE8) {
  5223. list = document.createElement('custom');
  5224. for (var prop in TrackList.prototype) {
  5225. if (prop !== 'constructor') {
  5226. list[prop] = TrackList.prototype[prop];
  5227. }
  5228. }
  5229. for (var _prop in AudioTrackList.prototype) {
  5230. if (_prop !== 'constructor') {
  5231. list[_prop] = AudioTrackList.prototype[_prop];
  5232. }
  5233. }
  5234. }
  5235. list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this);
  5236. list.changing_ = false;
  5237. return _ret = list, possibleConstructorReturn(_this, _ret);
  5238. }
  5239. /**
  5240. * Add an {@link AudioTrack} to the `AudioTrackList`.
  5241. *
  5242. * @param {AudioTrack} track
  5243. * The AudioTrack to add to the list
  5244. *
  5245. * @fires TrackList#addtrack
  5246. */
  5247. AudioTrackList.prototype.addTrack = function addTrack(track) {
  5248. var _this2 = this;
  5249. if (track.enabled) {
  5250. disableOthers(this, track);
  5251. }
  5252. _TrackList.prototype.addTrack.call(this, track);
  5253. // native tracks don't have this
  5254. if (!track.addEventListener) {
  5255. return;
  5256. }
  5257. /**
  5258. * @listens AudioTrack#enabledchange
  5259. * @fires TrackList#change
  5260. */
  5261. track.addEventListener('enabledchange', function () {
  5262. // when we are disabling other tracks (since we don't support
  5263. // more than one track at a time) we will set changing_
  5264. // to true so that we don't trigger additional change events
  5265. if (_this2.changing_) {
  5266. return;
  5267. }
  5268. _this2.changing_ = true;
  5269. disableOthers(_this2, track);
  5270. _this2.changing_ = false;
  5271. _this2.trigger('change');
  5272. });
  5273. };
  5274. return AudioTrackList;
  5275. }(TrackList);
  5276. /**
  5277. * @file video-track-list.js
  5278. */
  5279. /**
  5280. * Un-select all other {@link VideoTrack}s that are selected.
  5281. *
  5282. * @param {VideoTrackList} list
  5283. * list to work on
  5284. *
  5285. * @param {VideoTrack} track
  5286. * The track to skip
  5287. *
  5288. * @private
  5289. */
  5290. var disableOthers$1 = function disableOthers(list, track) {
  5291. for (var i = 0; i < list.length; i++) {
  5292. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5293. continue;
  5294. }
  5295. // another video track is enabled, disable it
  5296. list[i].selected = false;
  5297. }
  5298. };
  5299. /**
  5300. * The current list of {@link VideoTrack} for a video.
  5301. *
  5302. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
  5303. * @extends TrackList
  5304. */
  5305. var VideoTrackList = function (_TrackList) {
  5306. inherits(VideoTrackList, _TrackList);
  5307. /**
  5308. * Create an instance of this class.
  5309. *
  5310. * @param {VideoTrack[]} [tracks=[]]
  5311. * A list of `VideoTrack` to instantiate the list with.
  5312. */
  5313. function VideoTrackList() {
  5314. var _this, _ret;
  5315. var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  5316. classCallCheck(this, VideoTrackList);
  5317. var list = void 0;
  5318. // make sure only 1 track is enabled
  5319. // sorted from last index to first index
  5320. for (var i = tracks.length - 1; i >= 0; i--) {
  5321. if (tracks[i].selected) {
  5322. disableOthers$1(tracks, tracks[i]);
  5323. break;
  5324. }
  5325. }
  5326. // IE8 forces us to implement inheritance ourselves
  5327. // as it does not support Object.defineProperty properly
  5328. if (IS_IE8) {
  5329. list = document.createElement('custom');
  5330. for (var prop in TrackList.prototype) {
  5331. if (prop !== 'constructor') {
  5332. list[prop] = TrackList.prototype[prop];
  5333. }
  5334. }
  5335. for (var _prop in VideoTrackList.prototype) {
  5336. if (_prop !== 'constructor') {
  5337. list[_prop] = VideoTrackList.prototype[_prop];
  5338. }
  5339. }
  5340. }
  5341. list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this);
  5342. list.changing_ = false;
  5343. /**
  5344. * @member {number} VideoTrackList#selectedIndex
  5345. * The current index of the selected {@link VideoTrack`}.
  5346. */
  5347. Object.defineProperty(list, 'selectedIndex', {
  5348. get: function get$$1() {
  5349. for (var _i = 0; _i < this.length; _i++) {
  5350. if (this[_i].selected) {
  5351. return _i;
  5352. }
  5353. }
  5354. return -1;
  5355. },
  5356. set: function set$$1() {}
  5357. });
  5358. return _ret = list, possibleConstructorReturn(_this, _ret);
  5359. }
  5360. /**
  5361. * Add a {@link VideoTrack} to the `VideoTrackList`.
  5362. *
  5363. * @param {VideoTrack} track
  5364. * The VideoTrack to add to the list
  5365. *
  5366. * @fires TrackList#addtrack
  5367. */
  5368. VideoTrackList.prototype.addTrack = function addTrack(track) {
  5369. var _this2 = this;
  5370. if (track.selected) {
  5371. disableOthers$1(this, track);
  5372. }
  5373. _TrackList.prototype.addTrack.call(this, track);
  5374. // native tracks don't have this
  5375. if (!track.addEventListener) {
  5376. return;
  5377. }
  5378. /**
  5379. * @listens VideoTrack#selectedchange
  5380. * @fires TrackList#change
  5381. */
  5382. track.addEventListener('selectedchange', function () {
  5383. if (_this2.changing_) {
  5384. return;
  5385. }
  5386. _this2.changing_ = true;
  5387. disableOthers$1(_this2, track);
  5388. _this2.changing_ = false;
  5389. _this2.trigger('change');
  5390. });
  5391. };
  5392. return VideoTrackList;
  5393. }(TrackList);
  5394. /**
  5395. * @file text-track-list.js
  5396. */
  5397. /**
  5398. * The current list of {@link TextTrack} for a media file.
  5399. *
  5400. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
  5401. * @extends TrackList
  5402. */
  5403. var TextTrackList = function (_TrackList) {
  5404. inherits(TextTrackList, _TrackList);
  5405. /**
  5406. * Create an instance of this class.
  5407. *
  5408. * @param {TextTrack[]} [tracks=[]]
  5409. * A list of `TextTrack` to instantiate the list with.
  5410. */
  5411. function TextTrackList() {
  5412. var _this, _ret;
  5413. var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  5414. classCallCheck(this, TextTrackList);
  5415. var list = void 0;
  5416. // IE8 forces us to implement inheritance ourselves
  5417. // as it does not support Object.defineProperty properly
  5418. if (IS_IE8) {
  5419. list = document.createElement('custom');
  5420. for (var prop in TrackList.prototype) {
  5421. if (prop !== 'constructor') {
  5422. list[prop] = TrackList.prototype[prop];
  5423. }
  5424. }
  5425. for (var _prop in TextTrackList.prototype) {
  5426. if (_prop !== 'constructor') {
  5427. list[_prop] = TextTrackList.prototype[_prop];
  5428. }
  5429. }
  5430. }
  5431. list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this);
  5432. return _ret = list, possibleConstructorReturn(_this, _ret);
  5433. }
  5434. /**
  5435. * Add a {@link TextTrack} to the `TextTrackList`
  5436. *
  5437. * @param {TextTrack} track
  5438. * The text track to add to the list.
  5439. *
  5440. * @fires TrackList#addtrack
  5441. */
  5442. TextTrackList.prototype.addTrack = function addTrack(track) {
  5443. _TrackList.prototype.addTrack.call(this, track);
  5444. /**
  5445. * @listens TextTrack#modechange
  5446. * @fires TrackList#change
  5447. */
  5448. track.addEventListener('modechange', bind(this, function () {
  5449. this.trigger('change');
  5450. }));
  5451. var nonLanguageTextTrackKind = ['metadata', 'chapters'];
  5452. if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
  5453. track.addEventListener('modechange', bind(this, function () {
  5454. this.trigger('selectedlanguagechange');
  5455. }));
  5456. }
  5457. };
  5458. return TextTrackList;
  5459. }(TrackList);
  5460. /**
  5461. * @file html-track-element-list.js
  5462. */
  5463. /**
  5464. * The current list of {@link HtmlTrackElement}s.
  5465. */
  5466. var HtmlTrackElementList = function () {
  5467. /**
  5468. * Create an instance of this class.
  5469. *
  5470. * @param {HtmlTrackElement[]} [tracks=[]]
  5471. * A list of `HtmlTrackElement` to instantiate the list with.
  5472. */
  5473. function HtmlTrackElementList() {
  5474. var trackElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  5475. classCallCheck(this, HtmlTrackElementList);
  5476. var list = this; // eslint-disable-line
  5477. if (IS_IE8) {
  5478. list = document.createElement('custom');
  5479. for (var prop in HtmlTrackElementList.prototype) {
  5480. if (prop !== 'constructor') {
  5481. list[prop] = HtmlTrackElementList.prototype[prop];
  5482. }
  5483. }
  5484. }
  5485. list.trackElements_ = [];
  5486. /**
  5487. * @memberof HtmlTrackElementList
  5488. * @member {number} length
  5489. * The current number of `Track`s in the this Trackist.
  5490. * @instance
  5491. */
  5492. Object.defineProperty(list, 'length', {
  5493. get: function get$$1() {
  5494. return this.trackElements_.length;
  5495. }
  5496. });
  5497. for (var i = 0, length = trackElements.length; i < length; i++) {
  5498. list.addTrackElement_(trackElements[i]);
  5499. }
  5500. if (IS_IE8) {
  5501. return list;
  5502. }
  5503. }
  5504. /**
  5505. * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
  5506. *
  5507. * @param {HtmlTrackElement} trackElement
  5508. * The track element to add to the list.
  5509. *
  5510. * @private
  5511. */
  5512. HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) {
  5513. var index = this.trackElements_.length;
  5514. if (!('' + index in this)) {
  5515. Object.defineProperty(this, index, {
  5516. get: function get$$1() {
  5517. return this.trackElements_[index];
  5518. }
  5519. });
  5520. }
  5521. // Do not add duplicate elements
  5522. if (this.trackElements_.indexOf(trackElement) === -1) {
  5523. this.trackElements_.push(trackElement);
  5524. }
  5525. };
  5526. /**
  5527. * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
  5528. * {@link TextTrack}.
  5529. *
  5530. * @param {TextTrack} track
  5531. * The track associated with a track element.
  5532. *
  5533. * @return {HtmlTrackElement|undefined}
  5534. * The track element that was found or undefined.
  5535. *
  5536. * @private
  5537. */
  5538. HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
  5539. var trackElement_ = void 0;
  5540. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5541. if (track === this.trackElements_[i].track) {
  5542. trackElement_ = this.trackElements_[i];
  5543. break;
  5544. }
  5545. }
  5546. return trackElement_;
  5547. };
  5548. /**
  5549. * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
  5550. *
  5551. * @param {HtmlTrackElement} trackElement
  5552. * The track element to remove from the list.
  5553. *
  5554. * @private
  5555. */
  5556. HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) {
  5557. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5558. if (trackElement === this.trackElements_[i]) {
  5559. this.trackElements_.splice(i, 1);
  5560. break;
  5561. }
  5562. }
  5563. };
  5564. return HtmlTrackElementList;
  5565. }();
  5566. /**
  5567. * @file text-track-cue-list.js
  5568. */
  5569. /**
  5570. * @typedef {Object} TextTrackCueList~TextTrackCue
  5571. *
  5572. * @property {string} id
  5573. * The unique id for this text track cue
  5574. *
  5575. * @property {number} startTime
  5576. * The start time for this text track cue
  5577. *
  5578. * @property {number} endTime
  5579. * The end time for this text track cue
  5580. *
  5581. * @property {boolean} pauseOnExit
  5582. * Pause when the end time is reached if true.
  5583. *
  5584. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
  5585. */
  5586. /**
  5587. * A List of TextTrackCues.
  5588. *
  5589. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
  5590. */
  5591. var TextTrackCueList = function () {
  5592. /**
  5593. * Create an instance of this class..
  5594. *
  5595. * @param {Array} cues
  5596. * A list of cues to be initialized with
  5597. */
  5598. function TextTrackCueList(cues) {
  5599. classCallCheck(this, TextTrackCueList);
  5600. var list = this; // eslint-disable-line
  5601. if (IS_IE8) {
  5602. list = document.createElement('custom');
  5603. for (var prop in TextTrackCueList.prototype) {
  5604. if (prop !== 'constructor') {
  5605. list[prop] = TextTrackCueList.prototype[prop];
  5606. }
  5607. }
  5608. }
  5609. TextTrackCueList.prototype.setCues_.call(list, cues);
  5610. /**
  5611. * @memberof TextTrackCueList
  5612. * @member {number} length
  5613. * The current number of `TextTrackCue`s in the TextTrackCueList.
  5614. * @instance
  5615. */
  5616. Object.defineProperty(list, 'length', {
  5617. get: function get$$1() {
  5618. return this.length_;
  5619. }
  5620. });
  5621. if (IS_IE8) {
  5622. return list;
  5623. }
  5624. }
  5625. /**
  5626. * A setter for cues in this list. Creates getters
  5627. * an an index for the cues.
  5628. *
  5629. * @param {Array} cues
  5630. * An array of cues to set
  5631. *
  5632. * @private
  5633. */
  5634. TextTrackCueList.prototype.setCues_ = function setCues_(cues) {
  5635. var oldLength = this.length || 0;
  5636. var i = 0;
  5637. var l = cues.length;
  5638. this.cues_ = cues;
  5639. this.length_ = cues.length;
  5640. var defineProp = function defineProp(index) {
  5641. if (!('' + index in this)) {
  5642. Object.defineProperty(this, '' + index, {
  5643. get: function get$$1() {
  5644. return this.cues_[index];
  5645. }
  5646. });
  5647. }
  5648. };
  5649. if (oldLength < l) {
  5650. i = oldLength;
  5651. for (; i < l; i++) {
  5652. defineProp.call(this, i);
  5653. }
  5654. }
  5655. };
  5656. /**
  5657. * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
  5658. *
  5659. * @param {string} id
  5660. * The id of the cue that should be searched for.
  5661. *
  5662. * @return {TextTrackCueList~TextTrackCue|null}
  5663. * A single cue or null if none was found.
  5664. */
  5665. TextTrackCueList.prototype.getCueById = function getCueById(id) {
  5666. var result = null;
  5667. for (var i = 0, l = this.length; i < l; i++) {
  5668. var cue = this[i];
  5669. if (cue.id === id) {
  5670. result = cue;
  5671. break;
  5672. }
  5673. }
  5674. return result;
  5675. };
  5676. return TextTrackCueList;
  5677. }();
  5678. /**
  5679. * @file track-kinds.js
  5680. */
  5681. /**
  5682. * All possible `VideoTrackKind`s
  5683. *
  5684. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
  5685. * @typedef VideoTrack~Kind
  5686. * @enum
  5687. */
  5688. var VideoTrackKind = {
  5689. alternative: 'alternative',
  5690. captions: 'captions',
  5691. main: 'main',
  5692. sign: 'sign',
  5693. subtitles: 'subtitles',
  5694. commentary: 'commentary'
  5695. };
  5696. /**
  5697. * All possible `AudioTrackKind`s
  5698. *
  5699. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
  5700. * @typedef AudioTrack~Kind
  5701. * @enum
  5702. */
  5703. var AudioTrackKind = {
  5704. 'alternative': 'alternative',
  5705. 'descriptions': 'descriptions',
  5706. 'main': 'main',
  5707. 'main-desc': 'main-desc',
  5708. 'translation': 'translation',
  5709. 'commentary': 'commentary'
  5710. };
  5711. /**
  5712. * All possible `TextTrackKind`s
  5713. *
  5714. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
  5715. * @typedef TextTrack~Kind
  5716. * @enum
  5717. */
  5718. var TextTrackKind = {
  5719. subtitles: 'subtitles',
  5720. captions: 'captions',
  5721. descriptions: 'descriptions',
  5722. chapters: 'chapters',
  5723. metadata: 'metadata'
  5724. };
  5725. /**
  5726. * All possible `TextTrackMode`s
  5727. *
  5728. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
  5729. * @typedef TextTrack~Mode
  5730. * @enum
  5731. */
  5732. var TextTrackMode = {
  5733. disabled: 'disabled',
  5734. hidden: 'hidden',
  5735. showing: 'showing'
  5736. };
  5737. /**
  5738. * @file track.js
  5739. */
  5740. /**
  5741. * A Track class that contains all of the common functionality for {@link AudioTrack},
  5742. * {@link VideoTrack}, and {@link TextTrack}.
  5743. *
  5744. * > Note: This class should not be used directly
  5745. *
  5746. * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
  5747. * @extends EventTarget
  5748. * @abstract
  5749. */
  5750. var Track = function (_EventTarget) {
  5751. inherits(Track, _EventTarget);
  5752. /**
  5753. * Create an instance of this class.
  5754. *
  5755. * @param {Object} [options={}]
  5756. * Object of option names and values
  5757. *
  5758. * @param {string} [options.kind='']
  5759. * A valid kind for the track type you are creating.
  5760. *
  5761. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  5762. * A unique id for this AudioTrack.
  5763. *
  5764. * @param {string} [options.label='']
  5765. * The menu label for this track.
  5766. *
  5767. * @param {string} [options.language='']
  5768. * A valid two character language code.
  5769. *
  5770. * @abstract
  5771. */
  5772. function Track() {
  5773. var _ret;
  5774. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  5775. classCallCheck(this, Track);
  5776. var _this = possibleConstructorReturn(this, _EventTarget.call(this));
  5777. var track = _this; // eslint-disable-line
  5778. if (IS_IE8) {
  5779. track = document.createElement('custom');
  5780. for (var prop in Track.prototype) {
  5781. if (prop !== 'constructor') {
  5782. track[prop] = Track.prototype[prop];
  5783. }
  5784. }
  5785. }
  5786. var trackProps = {
  5787. id: options.id || 'vjs_track_' + newGUID(),
  5788. kind: options.kind || '',
  5789. label: options.label || '',
  5790. language: options.language || ''
  5791. };
  5792. /**
  5793. * @memberof Track
  5794. * @member {string} id
  5795. * The id of this track. Cannot be changed after creation.
  5796. * @instance
  5797. *
  5798. * @readonly
  5799. */
  5800. /**
  5801. * @memberof Track
  5802. * @member {string} kind
  5803. * The kind of track that this is. Cannot be changed after creation.
  5804. * @instance
  5805. *
  5806. * @readonly
  5807. */
  5808. /**
  5809. * @memberof Track
  5810. * @member {string} label
  5811. * The label of this track. Cannot be changed after creation.
  5812. * @instance
  5813. *
  5814. * @readonly
  5815. */
  5816. /**
  5817. * @memberof Track
  5818. * @member {string} language
  5819. * The two letter language code for this track. Cannot be changed after
  5820. * creation.
  5821. * @instance
  5822. *
  5823. * @readonly
  5824. */
  5825. var _loop = function _loop(key) {
  5826. Object.defineProperty(track, key, {
  5827. get: function get$$1() {
  5828. return trackProps[key];
  5829. },
  5830. set: function set$$1() {}
  5831. });
  5832. };
  5833. for (var key in trackProps) {
  5834. _loop(key);
  5835. }
  5836. return _ret = track, possibleConstructorReturn(_this, _ret);
  5837. }
  5838. return Track;
  5839. }(EventTarget);
  5840. /**
  5841. * @file url.js
  5842. * @module url
  5843. */
  5844. /**
  5845. * @typedef {Object} url:URLObject
  5846. *
  5847. * @property {string} protocol
  5848. * The protocol of the url that was parsed.
  5849. *
  5850. * @property {string} hostname
  5851. * The hostname of the url that was parsed.
  5852. *
  5853. * @property {string} port
  5854. * The port of the url that was parsed.
  5855. *
  5856. * @property {string} pathname
  5857. * The pathname of the url that was parsed.
  5858. *
  5859. * @property {string} search
  5860. * The search query of the url that was parsed.
  5861. *
  5862. * @property {string} hash
  5863. * The hash of the url that was parsed.
  5864. *
  5865. * @property {string} host
  5866. * The host of the url that was parsed.
  5867. */
  5868. /**
  5869. * Resolve and parse the elements of a URL.
  5870. *
  5871. * @param {String} url
  5872. * The url to parse
  5873. *
  5874. * @return {url:URLObject}
  5875. * An object of url details
  5876. */
  5877. var parseUrl = function parseUrl(url) {
  5878. var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
  5879. // add the url to an anchor and let the browser parse the URL
  5880. var a = document.createElement('a');
  5881. a.href = url;
  5882. // IE8 (and 9?) Fix
  5883. // ie8 doesn't parse the URL correctly until the anchor is actually
  5884. // added to the body, and an innerHTML is needed to trigger the parsing
  5885. var addToBody = a.host === '' && a.protocol !== 'file:';
  5886. var div = void 0;
  5887. if (addToBody) {
  5888. div = document.createElement('div');
  5889. div.innerHTML = '<a href="' + url + '"></a>';
  5890. a = div.firstChild;
  5891. // prevent the div from affecting layout
  5892. div.setAttribute('style', 'display:none; position:absolute;');
  5893. document.body.appendChild(div);
  5894. }
  5895. // Copy the specific URL properties to a new object
  5896. // This is also needed for IE8 because the anchor loses its
  5897. // properties when it's removed from the dom
  5898. var details = {};
  5899. for (var i = 0; i < props.length; i++) {
  5900. details[props[i]] = a[props[i]];
  5901. }
  5902. // IE9 adds the port to the host property unlike everyone else. If
  5903. // a port identifier is added for standard ports, strip it.
  5904. if (details.protocol === 'http:') {
  5905. details.host = details.host.replace(/:80$/, '');
  5906. }
  5907. if (details.protocol === 'https:') {
  5908. details.host = details.host.replace(/:443$/, '');
  5909. }
  5910. if (!details.protocol) {
  5911. details.protocol = window.location.protocol;
  5912. }
  5913. if (addToBody) {
  5914. document.body.removeChild(div);
  5915. }
  5916. return details;
  5917. };
  5918. /**
  5919. * Get absolute version of relative URL. Used to tell flash correct URL.
  5920. *
  5921. *
  5922. * @param {string} url
  5923. * URL to make absolute
  5924. *
  5925. * @return {string}
  5926. * Absolute URL
  5927. *
  5928. * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
  5929. */
  5930. var getAbsoluteURL = function getAbsoluteURL(url) {
  5931. // Check if absolute URL
  5932. if (!url.match(/^https?:\/\//)) {
  5933. // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
  5934. var div = document.createElement('div');
  5935. div.innerHTML = '<a href="' + url + '">x</a>';
  5936. url = div.firstChild.href;
  5937. }
  5938. return url;
  5939. };
  5940. /**
  5941. * Returns the extension of the passed file name. It will return an empty string
  5942. * if passed an invalid path.
  5943. *
  5944. * @param {string} path
  5945. * The fileName path like '/path/to/file.mp4'
  5946. *
  5947. * @returns {string}
  5948. * The extension in lower case or an empty string if no
  5949. * extension could be found.
  5950. */
  5951. var getFileExtension = function getFileExtension(path) {
  5952. if (typeof path === 'string') {
  5953. var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
  5954. var pathParts = splitPathRe.exec(path);
  5955. if (pathParts) {
  5956. return pathParts.pop().toLowerCase();
  5957. }
  5958. }
  5959. return '';
  5960. };
  5961. /**
  5962. * Returns whether the url passed is a cross domain request or not.
  5963. *
  5964. * @param {string} url
  5965. * The url to check.
  5966. *
  5967. * @return {boolean}
  5968. * Whether it is a cross domain request or not.
  5969. */
  5970. var isCrossOrigin = function isCrossOrigin(url) {
  5971. var winLoc = window.location;
  5972. var urlInfo = parseUrl(url);
  5973. // IE8 protocol relative urls will return ':' for protocol
  5974. var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol;
  5975. // Check if url is for another domain/origin
  5976. // IE8 doesn't know location.origin, so we won't rely on it here
  5977. var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
  5978. return crossOrigin;
  5979. };
  5980. var Url = (Object.freeze || Object)({
  5981. parseUrl: parseUrl,
  5982. getAbsoluteURL: getAbsoluteURL,
  5983. getFileExtension: getFileExtension,
  5984. isCrossOrigin: isCrossOrigin
  5985. });
  5986. /**
  5987. * @file text-track.js
  5988. */
  5989. /**
  5990. * Takes a webvtt file contents and parses it into cues
  5991. *
  5992. * @param {string} srcContent
  5993. * webVTT file contents
  5994. *
  5995. * @param {TextTrack} track
  5996. * TextTrack to add cues to. Cues come from the srcContent.
  5997. *
  5998. * @private
  5999. */
  6000. var parseCues = function parseCues(srcContent, track) {
  6001. var parser = new window.WebVTT.Parser(window, window.vttjs, window.WebVTT.StringDecoder());
  6002. var errors = [];
  6003. parser.oncue = function (cue) {
  6004. track.addCue(cue);
  6005. };
  6006. parser.onparsingerror = function (error) {
  6007. errors.push(error);
  6008. };
  6009. parser.onflush = function () {
  6010. track.trigger({
  6011. type: 'loadeddata',
  6012. target: track
  6013. });
  6014. };
  6015. parser.parse(srcContent);
  6016. if (errors.length > 0) {
  6017. if (window.console && window.console.groupCollapsed) {
  6018. window.console.groupCollapsed('Text Track parsing errors for ' + track.src);
  6019. }
  6020. errors.forEach(function (error) {
  6021. return log.error(error);
  6022. });
  6023. if (window.console && window.console.groupEnd) {
  6024. window.console.groupEnd();
  6025. }
  6026. }
  6027. parser.flush();
  6028. };
  6029. /**
  6030. * Load a `TextTrack` from a specifed url.
  6031. *
  6032. * @param {string} src
  6033. * Url to load track from.
  6034. *
  6035. * @param {TextTrack} track
  6036. * Track to add cues to. Comes from the content at the end of `url`.
  6037. *
  6038. * @private
  6039. */
  6040. var loadTrack = function loadTrack(src, track) {
  6041. var opts = {
  6042. uri: src
  6043. };
  6044. var crossOrigin = isCrossOrigin(src);
  6045. if (crossOrigin) {
  6046. opts.cors = crossOrigin;
  6047. }
  6048. xhr(opts, bind(this, function (err, response, responseBody) {
  6049. if (err) {
  6050. return log.error(err, response);
  6051. }
  6052. track.loaded_ = true;
  6053. // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  6054. // NOTE: this is only used for the alt/video.novtt.js build
  6055. if (typeof window.WebVTT !== 'function') {
  6056. if (track.tech_) {
  6057. var loadHandler = function loadHandler() {
  6058. return parseCues(responseBody, track);
  6059. };
  6060. track.tech_.on('vttjsloaded', loadHandler);
  6061. track.tech_.on('vttjserror', function () {
  6062. log.error('vttjs failed to load, stopping trying to process ' + track.src);
  6063. track.tech_.off('vttjsloaded', loadHandler);
  6064. });
  6065. }
  6066. } else {
  6067. parseCues(responseBody, track);
  6068. }
  6069. }));
  6070. };
  6071. /**
  6072. * A representation of a single `TextTrack`.
  6073. *
  6074. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
  6075. * @extends Track
  6076. */
  6077. var TextTrack = function (_Track) {
  6078. inherits(TextTrack, _Track);
  6079. /**
  6080. * Create an instance of this class.
  6081. *
  6082. * @param {Object} options={}
  6083. * Object of option names and values
  6084. *
  6085. * @param {Tech} options.tech
  6086. * A reference to the tech that owns this TextTrack.
  6087. *
  6088. * @param {TextTrack~Kind} [options.kind='subtitles']
  6089. * A valid text track kind.
  6090. *
  6091. * @param {TextTrack~Mode} [options.mode='disabled']
  6092. * A valid text track mode.
  6093. *
  6094. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6095. * A unique id for this TextTrack.
  6096. *
  6097. * @param {string} [options.label='']
  6098. * The menu label for this track.
  6099. *
  6100. * @param {string} [options.language='']
  6101. * A valid two character language code.
  6102. *
  6103. * @param {string} [options.srclang='']
  6104. * A valid two character language code. An alternative, but deprioritized
  6105. * vesion of `options.language`
  6106. *
  6107. * @param {string} [options.src]
  6108. * A url to TextTrack cues.
  6109. *
  6110. * @param {boolean} [options.default]
  6111. * If this track should default to on or off.
  6112. */
  6113. function TextTrack() {
  6114. var _this, _ret;
  6115. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  6116. classCallCheck(this, TextTrack);
  6117. if (!options.tech) {
  6118. throw new Error('A tech was not provided.');
  6119. }
  6120. var settings = mergeOptions(options, {
  6121. kind: TextTrackKind[options.kind] || 'subtitles',
  6122. language: options.language || options.srclang || ''
  6123. });
  6124. var mode = TextTrackMode[settings.mode] || 'disabled';
  6125. var default_ = settings['default'];
  6126. if (settings.kind === 'metadata' || settings.kind === 'chapters') {
  6127. mode = 'hidden';
  6128. }
  6129. // on IE8 this will be a document element
  6130. // for every other browser this will be a normal object
  6131. var tt = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this);
  6132. tt.tech_ = settings.tech;
  6133. if (IS_IE8) {
  6134. for (var prop in TextTrack.prototype) {
  6135. if (prop !== 'constructor') {
  6136. tt[prop] = TextTrack.prototype[prop];
  6137. }
  6138. }
  6139. }
  6140. tt.cues_ = [];
  6141. tt.activeCues_ = [];
  6142. var cues = new TextTrackCueList(tt.cues_);
  6143. var activeCues = new TextTrackCueList(tt.activeCues_);
  6144. var changed = false;
  6145. var timeupdateHandler = bind(tt, function () {
  6146. // Accessing this.activeCues for the side-effects of updating itself
  6147. // due to it's nature as a getter function. Do not remove or cues will
  6148. // stop updating!
  6149. // Use the setter to prevent deletion from uglify (pure_getters rule)
  6150. this.activeCues = this.activeCues;
  6151. if (changed) {
  6152. this.trigger('cuechange');
  6153. changed = false;
  6154. }
  6155. });
  6156. if (mode !== 'disabled') {
  6157. tt.tech_.ready(function () {
  6158. tt.tech_.on('timeupdate', timeupdateHandler);
  6159. }, true);
  6160. }
  6161. /**
  6162. * @memberof TextTrack
  6163. * @member {boolean} default
  6164. * If this track was set to be on or off by default. Cannot be changed after
  6165. * creation.
  6166. * @instance
  6167. *
  6168. * @readonly
  6169. */
  6170. Object.defineProperty(tt, 'default', {
  6171. get: function get$$1() {
  6172. return default_;
  6173. },
  6174. set: function set$$1() {}
  6175. });
  6176. /**
  6177. * @memberof TextTrack
  6178. * @member {string} mode
  6179. * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
  6180. * not be set if setting to an invalid mode.
  6181. * @instance
  6182. *
  6183. * @fires TextTrack#modechange
  6184. */
  6185. Object.defineProperty(tt, 'mode', {
  6186. get: function get$$1() {
  6187. return mode;
  6188. },
  6189. set: function set$$1(newMode) {
  6190. var _this2 = this;
  6191. if (!TextTrackMode[newMode]) {
  6192. return;
  6193. }
  6194. mode = newMode;
  6195. if (mode !== 'disabled') {
  6196. this.tech_.ready(function () {
  6197. _this2.tech_.on('timeupdate', timeupdateHandler);
  6198. }, true);
  6199. } else {
  6200. this.tech_.off('timeupdate', timeupdateHandler);
  6201. }
  6202. /**
  6203. * An event that fires when mode changes on this track. This allows
  6204. * the TextTrackList that holds this track to act accordingly.
  6205. *
  6206. * > Note: This is not part of the spec!
  6207. *
  6208. * @event TextTrack#modechange
  6209. * @type {EventTarget~Event}
  6210. */
  6211. this.trigger('modechange');
  6212. }
  6213. });
  6214. /**
  6215. * @memberof TextTrack
  6216. * @member {TextTrackCueList} cues
  6217. * The text track cue list for this TextTrack.
  6218. * @instance
  6219. */
  6220. Object.defineProperty(tt, 'cues', {
  6221. get: function get$$1() {
  6222. if (!this.loaded_) {
  6223. return null;
  6224. }
  6225. return cues;
  6226. },
  6227. set: function set$$1() {}
  6228. });
  6229. /**
  6230. * @memberof TextTrack
  6231. * @member {TextTrackCueList} activeCues
  6232. * The list text track cues that are currently active for this TextTrack.
  6233. * @instance
  6234. */
  6235. Object.defineProperty(tt, 'activeCues', {
  6236. get: function get$$1() {
  6237. if (!this.loaded_) {
  6238. return null;
  6239. }
  6240. // nothing to do
  6241. if (this.cues.length === 0) {
  6242. return activeCues;
  6243. }
  6244. var ct = this.tech_.currentTime();
  6245. var active = [];
  6246. for (var i = 0, l = this.cues.length; i < l; i++) {
  6247. var cue = this.cues[i];
  6248. if (cue.startTime <= ct && cue.endTime >= ct) {
  6249. active.push(cue);
  6250. } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
  6251. active.push(cue);
  6252. }
  6253. }
  6254. changed = false;
  6255. if (active.length !== this.activeCues_.length) {
  6256. changed = true;
  6257. } else {
  6258. for (var _i = 0; _i < active.length; _i++) {
  6259. if (this.activeCues_.indexOf(active[_i]) === -1) {
  6260. changed = true;
  6261. }
  6262. }
  6263. }
  6264. this.activeCues_ = active;
  6265. activeCues.setCues_(this.activeCues_);
  6266. return activeCues;
  6267. },
  6268. // /!\ Keep this setter empty (see the timeupdate handler above)
  6269. set: function set$$1() {}
  6270. });
  6271. if (settings.src) {
  6272. tt.src = settings.src;
  6273. loadTrack(settings.src, tt);
  6274. } else {
  6275. tt.loaded_ = true;
  6276. }
  6277. return _ret = tt, possibleConstructorReturn(_this, _ret);
  6278. }
  6279. /**
  6280. * Add a cue to the internal list of cues.
  6281. *
  6282. * @param {TextTrack~Cue} cue
  6283. * The cue to add to our internal list
  6284. */
  6285. TextTrack.prototype.addCue = function addCue(originalCue) {
  6286. var cue = originalCue;
  6287. if (window.vttjs && !(originalCue instanceof window.vttjs.VTTCue)) {
  6288. cue = new window.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
  6289. for (var prop in originalCue) {
  6290. if (!(prop in cue)) {
  6291. cue[prop] = originalCue[prop];
  6292. }
  6293. }
  6294. // make sure that `id` is copied over
  6295. cue.id = originalCue.id;
  6296. cue.originalCue_ = originalCue;
  6297. }
  6298. var tracks = this.tech_.textTracks();
  6299. for (var i = 0; i < tracks.length; i++) {
  6300. if (tracks[i] !== this) {
  6301. tracks[i].removeCue(cue);
  6302. }
  6303. }
  6304. this.cues_.push(cue);
  6305. this.cues.setCues_(this.cues_);
  6306. };
  6307. /**
  6308. * Remove a cue from our internal list
  6309. *
  6310. * @param {TextTrack~Cue} removeCue
  6311. * The cue to remove from our internal list
  6312. */
  6313. TextTrack.prototype.removeCue = function removeCue(_removeCue) {
  6314. var i = this.cues_.length;
  6315. while (i--) {
  6316. var cue = this.cues_[i];
  6317. if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
  6318. this.cues_.splice(i, 1);
  6319. this.cues.setCues_(this.cues_);
  6320. break;
  6321. }
  6322. }
  6323. };
  6324. return TextTrack;
  6325. }(Track);
  6326. /**
  6327. * cuechange - One or more cues in the track have become active or stopped being active.
  6328. */
  6329. TextTrack.prototype.allowedEvents_ = {
  6330. cuechange: 'cuechange'
  6331. };
  6332. /**
  6333. * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
  6334. * only one `AudioTrack` in the list will be enabled at a time.
  6335. *
  6336. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
  6337. * @extends Track
  6338. */
  6339. var AudioTrack = function (_Track) {
  6340. inherits(AudioTrack, _Track);
  6341. /**
  6342. * Create an instance of this class.
  6343. *
  6344. * @param {Object} [options={}]
  6345. * Object of option names and values
  6346. *
  6347. * @param {AudioTrack~Kind} [options.kind='']
  6348. * A valid audio track kind
  6349. *
  6350. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6351. * A unique id for this AudioTrack.
  6352. *
  6353. * @param {string} [options.label='']
  6354. * The menu label for this track.
  6355. *
  6356. * @param {string} [options.language='']
  6357. * A valid two character language code.
  6358. *
  6359. * @param {boolean} [options.enabled]
  6360. * If this track is the one that is currently playing. If this track is part of
  6361. * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
  6362. */
  6363. function AudioTrack() {
  6364. var _this, _ret;
  6365. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  6366. classCallCheck(this, AudioTrack);
  6367. var settings = mergeOptions(options, {
  6368. kind: AudioTrackKind[options.kind] || ''
  6369. });
  6370. // on IE8 this will be a document element
  6371. // for every other browser this will be a normal object
  6372. var track = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this);
  6373. var enabled = false;
  6374. if (IS_IE8) {
  6375. for (var prop in AudioTrack.prototype) {
  6376. if (prop !== 'constructor') {
  6377. track[prop] = AudioTrack.prototype[prop];
  6378. }
  6379. }
  6380. }
  6381. /**
  6382. * @memberof AudioTrack
  6383. * @member {boolean} enabled
  6384. * If this `AudioTrack` is enabled or not. When setting this will
  6385. * fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
  6386. * @instance
  6387. *
  6388. * @fires VideoTrack#selectedchange
  6389. */
  6390. Object.defineProperty(track, 'enabled', {
  6391. get: function get$$1() {
  6392. return enabled;
  6393. },
  6394. set: function set$$1(newEnabled) {
  6395. // an invalid or unchanged value
  6396. if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
  6397. return;
  6398. }
  6399. enabled = newEnabled;
  6400. /**
  6401. * An event that fires when enabled changes on this track. This allows
  6402. * the AudioTrackList that holds this track to act accordingly.
  6403. *
  6404. * > Note: This is not part of the spec! Native tracks will do
  6405. * this internally without an event.
  6406. *
  6407. * @event AudioTrack#enabledchange
  6408. * @type {EventTarget~Event}
  6409. */
  6410. this.trigger('enabledchange');
  6411. }
  6412. });
  6413. // if the user sets this track to selected then
  6414. // set selected to that true value otherwise
  6415. // we keep it false
  6416. if (settings.enabled) {
  6417. track.enabled = settings.enabled;
  6418. }
  6419. track.loaded_ = true;
  6420. return _ret = track, possibleConstructorReturn(_this, _ret);
  6421. }
  6422. return AudioTrack;
  6423. }(Track);
  6424. /**
  6425. * A representation of a single `VideoTrack`.
  6426. *
  6427. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
  6428. * @extends Track
  6429. */
  6430. var VideoTrack = function (_Track) {
  6431. inherits(VideoTrack, _Track);
  6432. /**
  6433. * Create an instance of this class.
  6434. *
  6435. * @param {Object} [options={}]
  6436. * Object of option names and values
  6437. *
  6438. * @param {string} [options.kind='']
  6439. * A valid {@link VideoTrack~Kind}
  6440. *
  6441. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6442. * A unique id for this AudioTrack.
  6443. *
  6444. * @param {string} [options.label='']
  6445. * The menu label for this track.
  6446. *
  6447. * @param {string} [options.language='']
  6448. * A valid two character language code.
  6449. *
  6450. * @param {boolean} [options.selected]
  6451. * If this track is the one that is currently playing.
  6452. */
  6453. function VideoTrack() {
  6454. var _this, _ret;
  6455. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  6456. classCallCheck(this, VideoTrack);
  6457. var settings = mergeOptions(options, {
  6458. kind: VideoTrackKind[options.kind] || ''
  6459. });
  6460. // on IE8 this will be a document element
  6461. // for every other browser this will be a normal object
  6462. var track = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this);
  6463. var selected = false;
  6464. if (IS_IE8) {
  6465. for (var prop in VideoTrack.prototype) {
  6466. if (prop !== 'constructor') {
  6467. track[prop] = VideoTrack.prototype[prop];
  6468. }
  6469. }
  6470. }
  6471. /**
  6472. * @memberof VideoTrack
  6473. * @member {boolean} selected
  6474. * If this `VideoTrack` is selected or not. When setting this will
  6475. * fire {@link VideoTrack#selectedchange} if the state of selected changed.
  6476. * @instance
  6477. *
  6478. * @fires VideoTrack#selectedchange
  6479. */
  6480. Object.defineProperty(track, 'selected', {
  6481. get: function get$$1() {
  6482. return selected;
  6483. },
  6484. set: function set$$1(newSelected) {
  6485. // an invalid or unchanged value
  6486. if (typeof newSelected !== 'boolean' || newSelected === selected) {
  6487. return;
  6488. }
  6489. selected = newSelected;
  6490. /**
  6491. * An event that fires when selected changes on this track. This allows
  6492. * the VideoTrackList that holds this track to act accordingly.
  6493. *
  6494. * > Note: This is not part of the spec! Native tracks will do
  6495. * this internally without an event.
  6496. *
  6497. * @event VideoTrack#selectedchange
  6498. * @type {EventTarget~Event}
  6499. */
  6500. this.trigger('selectedchange');
  6501. }
  6502. });
  6503. // if the user sets this track to selected then
  6504. // set selected to that true value otherwise
  6505. // we keep it false
  6506. if (settings.selected) {
  6507. track.selected = settings.selected;
  6508. }
  6509. return _ret = track, possibleConstructorReturn(_this, _ret);
  6510. }
  6511. return VideoTrack;
  6512. }(Track);
  6513. /**
  6514. * @file html-track-element.js
  6515. */
  6516. /**
  6517. * @memberof HTMLTrackElement
  6518. * @typedef {HTMLTrackElement~ReadyState}
  6519. * @enum {number}
  6520. */
  6521. var NONE = 0;
  6522. var LOADING = 1;
  6523. var LOADED = 2;
  6524. var ERROR = 3;
  6525. /**
  6526. * A single track represented in the DOM.
  6527. *
  6528. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
  6529. * @extends EventTarget
  6530. */
  6531. var HTMLTrackElement = function (_EventTarget) {
  6532. inherits(HTMLTrackElement, _EventTarget);
  6533. /**
  6534. * Create an instance of this class.
  6535. *
  6536. * @param {Object} options={}
  6537. * Object of option names and values
  6538. *
  6539. * @param {Tech} options.tech
  6540. * A reference to the tech that owns this HTMLTrackElement.
  6541. *
  6542. * @param {TextTrack~Kind} [options.kind='subtitles']
  6543. * A valid text track kind.
  6544. *
  6545. * @param {TextTrack~Mode} [options.mode='disabled']
  6546. * A valid text track mode.
  6547. *
  6548. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6549. * A unique id for this TextTrack.
  6550. *
  6551. * @param {string} [options.label='']
  6552. * The menu label for this track.
  6553. *
  6554. * @param {string} [options.language='']
  6555. * A valid two character language code.
  6556. *
  6557. * @param {string} [options.srclang='']
  6558. * A valid two character language code. An alternative, but deprioritized
  6559. * vesion of `options.language`
  6560. *
  6561. * @param {string} [options.src]
  6562. * A url to TextTrack cues.
  6563. *
  6564. * @param {boolean} [options.default]
  6565. * If this track should default to on or off.
  6566. */
  6567. function HTMLTrackElement() {
  6568. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  6569. classCallCheck(this, HTMLTrackElement);
  6570. var _this = possibleConstructorReturn(this, _EventTarget.call(this));
  6571. var readyState = void 0;
  6572. var trackElement = _this; // eslint-disable-line
  6573. if (IS_IE8) {
  6574. trackElement = document.createElement('custom');
  6575. for (var prop in HTMLTrackElement.prototype) {
  6576. if (prop !== 'constructor') {
  6577. trackElement[prop] = HTMLTrackElement.prototype[prop];
  6578. }
  6579. }
  6580. }
  6581. var track = new TextTrack(options);
  6582. trackElement.kind = track.kind;
  6583. trackElement.src = track.src;
  6584. trackElement.srclang = track.language;
  6585. trackElement.label = track.label;
  6586. trackElement['default'] = track['default'];
  6587. /**
  6588. * @memberof HTMLTrackElement
  6589. * @member {HTMLTrackElement~ReadyState} readyState
  6590. * The current ready state of the track element.
  6591. * @instance
  6592. */
  6593. Object.defineProperty(trackElement, 'readyState', {
  6594. get: function get$$1() {
  6595. return readyState;
  6596. }
  6597. });
  6598. /**
  6599. * @memberof HTMLTrackElement
  6600. * @member {TextTrack} track
  6601. * The underlying TextTrack object.
  6602. * @instance
  6603. *
  6604. */
  6605. Object.defineProperty(trackElement, 'track', {
  6606. get: function get$$1() {
  6607. return track;
  6608. }
  6609. });
  6610. readyState = NONE;
  6611. /**
  6612. * @listens TextTrack#loadeddata
  6613. * @fires HTMLTrackElement#load
  6614. */
  6615. track.addEventListener('loadeddata', function () {
  6616. readyState = LOADED;
  6617. trackElement.trigger({
  6618. type: 'load',
  6619. target: trackElement
  6620. });
  6621. });
  6622. if (IS_IE8) {
  6623. var _ret;
  6624. return _ret = trackElement, possibleConstructorReturn(_this, _ret);
  6625. }
  6626. return _this;
  6627. }
  6628. return HTMLTrackElement;
  6629. }(EventTarget);
  6630. HTMLTrackElement.prototype.allowedEvents_ = {
  6631. load: 'load'
  6632. };
  6633. HTMLTrackElement.NONE = NONE;
  6634. HTMLTrackElement.LOADING = LOADING;
  6635. HTMLTrackElement.LOADED = LOADED;
  6636. HTMLTrackElement.ERROR = ERROR;
  6637. /*
  6638. * This file contains all track properties that are used in
  6639. * player.js, tech.js, html5.js and possibly other techs in the future.
  6640. */
  6641. var NORMAL = {
  6642. audio: {
  6643. ListClass: AudioTrackList,
  6644. TrackClass: AudioTrack,
  6645. capitalName: 'Audio'
  6646. },
  6647. video: {
  6648. ListClass: VideoTrackList,
  6649. TrackClass: VideoTrack,
  6650. capitalName: 'Video'
  6651. },
  6652. text: {
  6653. ListClass: TextTrackList,
  6654. TrackClass: TextTrack,
  6655. capitalName: 'Text'
  6656. }
  6657. };
  6658. Object.keys(NORMAL).forEach(function (type) {
  6659. NORMAL[type].getterName = type + 'Tracks';
  6660. NORMAL[type].privateName = type + 'Tracks_';
  6661. });
  6662. var REMOTE = {
  6663. remoteText: {
  6664. ListClass: TextTrackList,
  6665. TrackClass: TextTrack,
  6666. capitalName: 'RemoteText',
  6667. getterName: 'remoteTextTracks',
  6668. privateName: 'remoteTextTracks_'
  6669. },
  6670. remoteTextEl: {
  6671. ListClass: HtmlTrackElementList,
  6672. TrackClass: HTMLTrackElement,
  6673. capitalName: 'RemoteTextTrackEls',
  6674. getterName: 'remoteTextTrackEls',
  6675. privateName: 'remoteTextTrackEls_'
  6676. }
  6677. };
  6678. var ALL = mergeOptions(NORMAL, REMOTE);
  6679. REMOTE.names = Object.keys(REMOTE);
  6680. NORMAL.names = Object.keys(NORMAL);
  6681. ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
  6682. /**
  6683. * @file tech.js
  6684. */
  6685. /**
  6686. * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
  6687. * that just contains the src url alone.
  6688. * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
  6689. * `var SourceString = 'http://example.com/some-video.mp4';`
  6690. *
  6691. * @typedef {Object|string} Tech~SourceObject
  6692. *
  6693. * @property {string} src
  6694. * The url to the source
  6695. *
  6696. * @property {string} type
  6697. * The mime type of the source
  6698. */
  6699. /**
  6700. * A function used by {@link Tech} to create a new {@link TextTrack}.
  6701. *
  6702. * @private
  6703. *
  6704. * @param {Tech} self
  6705. * An instance of the Tech class.
  6706. *
  6707. * @param {string} kind
  6708. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  6709. *
  6710. * @param {string} [label]
  6711. * Label to identify the text track
  6712. *
  6713. * @param {string} [language]
  6714. * Two letter language abbreviation
  6715. *
  6716. * @param {Object} [options={}]
  6717. * An object with additional text track options
  6718. *
  6719. * @return {TextTrack}
  6720. * The text track that was created.
  6721. */
  6722. function createTrackHelper(self, kind, label, language) {
  6723. var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
  6724. var tracks = self.textTracks();
  6725. options.kind = kind;
  6726. if (label) {
  6727. options.label = label;
  6728. }
  6729. if (language) {
  6730. options.language = language;
  6731. }
  6732. options.tech = self;
  6733. var track = new ALL.text.TrackClass(options);
  6734. tracks.addTrack(track);
  6735. return track;
  6736. }
  6737. /**
  6738. * This is the base class for media playback technology controllers, such as
  6739. * {@link Flash} and {@link HTML5}
  6740. *
  6741. * @extends Component
  6742. */
  6743. var Tech = function (_Component) {
  6744. inherits(Tech, _Component);
  6745. /**
  6746. * Create an instance of this Tech.
  6747. *
  6748. * @param {Object} [options]
  6749. * The key/value store of player options.
  6750. *
  6751. * @param {Component~ReadyCallback} ready
  6752. * Callback function to call when the `HTML5` Tech is ready.
  6753. */
  6754. function Tech() {
  6755. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  6756. var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
  6757. classCallCheck(this, Tech);
  6758. // we don't want the tech to report user activity automatically.
  6759. // This is done manually in addControlsListeners
  6760. options.reportTouchActivity = false;
  6761. // keep track of whether the current source has played at all to
  6762. // implement a very limited played()
  6763. var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready));
  6764. _this.hasStarted_ = false;
  6765. _this.on('playing', function () {
  6766. this.hasStarted_ = true;
  6767. });
  6768. _this.on('loadstart', function () {
  6769. this.hasStarted_ = false;
  6770. });
  6771. ALL.names.forEach(function (name) {
  6772. var props = ALL[name];
  6773. if (options && options[props.getterName]) {
  6774. _this[props.privateName] = options[props.getterName];
  6775. }
  6776. });
  6777. // Manually track progress in cases where the browser/flash player doesn't report it.
  6778. if (!_this.featuresProgressEvents) {
  6779. _this.manualProgressOn();
  6780. }
  6781. // Manually track timeupdates in cases where the browser/flash player doesn't report it.
  6782. if (!_this.featuresTimeupdateEvents) {
  6783. _this.manualTimeUpdatesOn();
  6784. }
  6785. ['Text', 'Audio', 'Video'].forEach(function (track) {
  6786. if (options['native' + track + 'Tracks'] === false) {
  6787. _this['featuresNative' + track + 'Tracks'] = false;
  6788. }
  6789. });
  6790. if (options.nativeCaptions === false || options.nativeTextTracks === false) {
  6791. _this.featuresNativeTextTracks = false;
  6792. } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
  6793. _this.featuresNativeTextTracks = true;
  6794. }
  6795. if (!_this.featuresNativeTextTracks) {
  6796. _this.emulateTextTracks();
  6797. }
  6798. _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
  6799. _this.initTrackListeners();
  6800. // Turn on component tap events only if not using native controls
  6801. if (!options.nativeControlsForTouch) {
  6802. _this.emitTapEvents();
  6803. }
  6804. if (_this.constructor) {
  6805. _this.name_ = _this.constructor.name || 'Unknown Tech';
  6806. }
  6807. return _this;
  6808. }
  6809. /**
  6810. * A special function to trigger source set in a way that will allow player
  6811. * to re-trigger if the player or tech are not ready yet.
  6812. *
  6813. * @fires Tech#sourceset
  6814. * @param {string} src The source string at the time of the source changing.
  6815. */
  6816. Tech.prototype.triggerSourceset = function triggerSourceset(src) {
  6817. var _this2 = this;
  6818. if (!this.isReady_) {
  6819. // on initial ready we have to trigger source set
  6820. // 1ms after ready so that player can watch for it.
  6821. this.one('ready', function () {
  6822. return _this2.setTimeout(function () {
  6823. return _this2.triggerSourceset(src);
  6824. }, 1);
  6825. });
  6826. }
  6827. /**
  6828. * Fired when the source is set on the tech causing the media element
  6829. * to reload.
  6830. *
  6831. * @see {@link Player#event:sourceset}
  6832. * @event Tech#sourceset
  6833. * @type {EventTarget~Event}
  6834. */
  6835. this.trigger({
  6836. src: src,
  6837. type: 'sourceset'
  6838. });
  6839. };
  6840. /* Fallbacks for unsupported event types
  6841. ================================================================================ */
  6842. /**
  6843. * Polyfill the `progress` event for browsers that don't support it natively.
  6844. *
  6845. * @see {@link Tech#trackProgress}
  6846. */
  6847. Tech.prototype.manualProgressOn = function manualProgressOn() {
  6848. this.on('durationchange', this.onDurationChange);
  6849. this.manualProgress = true;
  6850. // Trigger progress watching when a source begins loading
  6851. this.one('ready', this.trackProgress);
  6852. };
  6853. /**
  6854. * Turn off the polyfill for `progress` events that was created in
  6855. * {@link Tech#manualProgressOn}
  6856. */
  6857. Tech.prototype.manualProgressOff = function manualProgressOff() {
  6858. this.manualProgress = false;
  6859. this.stopTrackingProgress();
  6860. this.off('durationchange', this.onDurationChange);
  6861. };
  6862. /**
  6863. * This is used to trigger a `progress` event when the buffered percent changes. It
  6864. * sets an interval function that will be called every 500 milliseconds to check if the
  6865. * buffer end percent has changed.
  6866. *
  6867. * > This function is called by {@link Tech#manualProgressOn}
  6868. *
  6869. * @param {EventTarget~Event} event
  6870. * The `ready` event that caused this to run.
  6871. *
  6872. * @listens Tech#ready
  6873. * @fires Tech#progress
  6874. */
  6875. Tech.prototype.trackProgress = function trackProgress(event) {
  6876. this.stopTrackingProgress();
  6877. this.progressInterval = this.setInterval(bind(this, function () {
  6878. // Don't trigger unless buffered amount is greater than last time
  6879. var numBufferedPercent = this.bufferedPercent();
  6880. if (this.bufferedPercent_ !== numBufferedPercent) {
  6881. /**
  6882. * See {@link Player#progress}
  6883. *
  6884. * @event Tech#progress
  6885. * @type {EventTarget~Event}
  6886. */
  6887. this.trigger('progress');
  6888. }
  6889. this.bufferedPercent_ = numBufferedPercent;
  6890. if (numBufferedPercent === 1) {
  6891. this.stopTrackingProgress();
  6892. }
  6893. }), 500);
  6894. };
  6895. /**
  6896. * Update our internal duration on a `durationchange` event by calling
  6897. * {@link Tech#duration}.
  6898. *
  6899. * @param {EventTarget~Event} event
  6900. * The `durationchange` event that caused this to run.
  6901. *
  6902. * @listens Tech#durationchange
  6903. */
  6904. Tech.prototype.onDurationChange = function onDurationChange(event) {
  6905. this.duration_ = this.duration();
  6906. };
  6907. /**
  6908. * Get and create a `TimeRange` object for buffering.
  6909. *
  6910. * @return {TimeRange}
  6911. * The time range object that was created.
  6912. */
  6913. Tech.prototype.buffered = function buffered() {
  6914. return createTimeRanges(0, 0);
  6915. };
  6916. /**
  6917. * Get the percentage of the current video that is currently buffered.
  6918. *
  6919. * @return {number}
  6920. * A number from 0 to 1 that represents the decimal percentage of the
  6921. * video that is buffered.
  6922. *
  6923. */
  6924. Tech.prototype.bufferedPercent = function bufferedPercent$$1() {
  6925. return bufferedPercent(this.buffered(), this.duration_);
  6926. };
  6927. /**
  6928. * Turn off the polyfill for `progress` events that was created in
  6929. * {@link Tech#manualProgressOn}
  6930. * Stop manually tracking progress events by clearing the interval that was set in
  6931. * {@link Tech#trackProgress}.
  6932. */
  6933. Tech.prototype.stopTrackingProgress = function stopTrackingProgress() {
  6934. this.clearInterval(this.progressInterval);
  6935. };
  6936. /**
  6937. * Polyfill the `timeupdate` event for browsers that don't support it.
  6938. *
  6939. * @see {@link Tech#trackCurrentTime}
  6940. */
  6941. Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
  6942. this.manualTimeUpdates = true;
  6943. this.on('play', this.trackCurrentTime);
  6944. this.on('pause', this.stopTrackingCurrentTime);
  6945. };
  6946. /**
  6947. * Turn off the polyfill for `timeupdate` events that was created in
  6948. * {@link Tech#manualTimeUpdatesOn}
  6949. */
  6950. Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
  6951. this.manualTimeUpdates = false;
  6952. this.stopTrackingCurrentTime();
  6953. this.off('play', this.trackCurrentTime);
  6954. this.off('pause', this.stopTrackingCurrentTime);
  6955. };
  6956. /**
  6957. * Sets up an interval function to track current time and trigger `timeupdate` every
  6958. * 250 milliseconds.
  6959. *
  6960. * @listens Tech#play
  6961. * @triggers Tech#timeupdate
  6962. */
  6963. Tech.prototype.trackCurrentTime = function trackCurrentTime() {
  6964. if (this.currentTimeInterval) {
  6965. this.stopTrackingCurrentTime();
  6966. }
  6967. this.currentTimeInterval = this.setInterval(function () {
  6968. /**
  6969. * Triggered at an interval of 250ms to indicated that time is passing in the video.
  6970. *
  6971. * @event Tech#timeupdate
  6972. * @type {EventTarget~Event}
  6973. */
  6974. this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
  6975. // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  6976. }, 250);
  6977. };
  6978. /**
  6979. * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
  6980. * `timeupdate` event is no longer triggered.
  6981. *
  6982. * @listens {Tech#pause}
  6983. */
  6984. Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
  6985. this.clearInterval(this.currentTimeInterval);
  6986. // #1002 - if the video ends right before the next timeupdate would happen,
  6987. // the progress bar won't make it all the way to the end
  6988. this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
  6989. };
  6990. /**
  6991. * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
  6992. * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
  6993. *
  6994. * @fires Component#dispose
  6995. */
  6996. Tech.prototype.dispose = function dispose() {
  6997. // clear out all tracks because we can't reuse them between techs
  6998. this.clearTracks(NORMAL.names);
  6999. // Turn off any manual progress or timeupdate tracking
  7000. if (this.manualProgress) {
  7001. this.manualProgressOff();
  7002. }
  7003. if (this.manualTimeUpdates) {
  7004. this.manualTimeUpdatesOff();
  7005. }
  7006. _Component.prototype.dispose.call(this);
  7007. };
  7008. /**
  7009. * Clear out a single `TrackList` or an array of `TrackLists` given their names.
  7010. *
  7011. * > Note: Techs without source handlers should call this between sources for `video`
  7012. * & `audio` tracks. You don't want to use them between tracks!
  7013. *
  7014. * @param {string[]|string} types
  7015. * TrackList names to clear, valid names are `video`, `audio`, and
  7016. * `text`.
  7017. */
  7018. Tech.prototype.clearTracks = function clearTracks(types) {
  7019. var _this3 = this;
  7020. types = [].concat(types);
  7021. // clear out all tracks because we can't reuse them between techs
  7022. types.forEach(function (type) {
  7023. var list = _this3[type + 'Tracks']() || [];
  7024. var i = list.length;
  7025. while (i--) {
  7026. var track = list[i];
  7027. if (type === 'text') {
  7028. _this3.removeRemoteTextTrack(track);
  7029. }
  7030. list.removeTrack(track);
  7031. }
  7032. });
  7033. };
  7034. /**
  7035. * Remove any TextTracks added via addRemoteTextTrack that are
  7036. * flagged for automatic garbage collection
  7037. */
  7038. Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
  7039. var list = this.autoRemoteTextTracks_ || [];
  7040. var i = list.length;
  7041. while (i--) {
  7042. var track = list[i];
  7043. this.removeRemoteTextTrack(track);
  7044. }
  7045. };
  7046. /**
  7047. * Reset the tech, which will removes all sources and reset the internal readyState.
  7048. *
  7049. * @abstract
  7050. */
  7051. Tech.prototype.reset = function reset() {};
  7052. /**
  7053. * Get or set an error on the Tech.
  7054. *
  7055. * @param {MediaError} [err]
  7056. * Error to set on the Tech
  7057. *
  7058. * @return {MediaError|null}
  7059. * The current error object on the tech, or null if there isn't one.
  7060. */
  7061. Tech.prototype.error = function error(err) {
  7062. if (err !== undefined) {
  7063. this.error_ = new MediaError(err);
  7064. this.trigger('error');
  7065. }
  7066. return this.error_;
  7067. };
  7068. /**
  7069. * Returns the `TimeRange`s that have been played through for the current source.
  7070. *
  7071. * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
  7072. * It only checks wether the source has played at all or not.
  7073. *
  7074. * @return {TimeRange}
  7075. * - A single time range if this video has played
  7076. * - An empty set of ranges if not.
  7077. */
  7078. Tech.prototype.played = function played() {
  7079. if (this.hasStarted_) {
  7080. return createTimeRanges(0, 0);
  7081. }
  7082. return createTimeRanges();
  7083. };
  7084. /**
  7085. * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
  7086. * previously called.
  7087. *
  7088. * @fires Tech#timeupdate
  7089. */
  7090. Tech.prototype.setCurrentTime = function setCurrentTime() {
  7091. // improve the accuracy of manual timeupdates
  7092. if (this.manualTimeUpdates) {
  7093. /**
  7094. * A manual `timeupdate` event.
  7095. *
  7096. * @event Tech#timeupdate
  7097. * @type {EventTarget~Event}
  7098. */
  7099. this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
  7100. }
  7101. };
  7102. /**
  7103. * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
  7104. * {@link TextTrackList} events.
  7105. *
  7106. * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
  7107. *
  7108. * @fires Tech#audiotrackchange
  7109. * @fires Tech#videotrackchange
  7110. * @fires Tech#texttrackchange
  7111. */
  7112. Tech.prototype.initTrackListeners = function initTrackListeners() {
  7113. var _this4 = this;
  7114. /**
  7115. * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
  7116. *
  7117. * @event Tech#audiotrackchange
  7118. * @type {EventTarget~Event}
  7119. */
  7120. /**
  7121. * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
  7122. *
  7123. * @event Tech#videotrackchange
  7124. * @type {EventTarget~Event}
  7125. */
  7126. /**
  7127. * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
  7128. *
  7129. * @event Tech#texttrackchange
  7130. * @type {EventTarget~Event}
  7131. */
  7132. NORMAL.names.forEach(function (name) {
  7133. var props = NORMAL[name];
  7134. var trackListChanges = function trackListChanges() {
  7135. _this4.trigger(name + 'trackchange');
  7136. };
  7137. var tracks = _this4[props.getterName]();
  7138. tracks.addEventListener('removetrack', trackListChanges);
  7139. tracks.addEventListener('addtrack', trackListChanges);
  7140. _this4.on('dispose', function () {
  7141. tracks.removeEventListener('removetrack', trackListChanges);
  7142. tracks.removeEventListener('addtrack', trackListChanges);
  7143. });
  7144. });
  7145. };
  7146. /**
  7147. * Emulate TextTracks using vtt.js if necessary
  7148. *
  7149. * @fires Tech#vttjsloaded
  7150. * @fires Tech#vttjserror
  7151. */
  7152. Tech.prototype.addWebVttScript_ = function addWebVttScript_() {
  7153. var _this5 = this;
  7154. if (window.WebVTT) {
  7155. return;
  7156. }
  7157. // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
  7158. // signals that the Tech is ready at which point Tech.el_ is part of the DOM
  7159. // before inserting the WebVTT script
  7160. if (document.body.contains(this.el())) {
  7161. // load via require if available and vtt.js script location was not passed in
  7162. // as an option. novtt builds will turn the above require call into an empty object
  7163. // which will cause this if check to always fail.
  7164. if (!this.options_['vtt.js'] && isPlain(vtt) && Object.keys(vtt).length > 0) {
  7165. this.trigger('vttjsloaded');
  7166. return;
  7167. }
  7168. // load vtt.js via the script location option or the cdn of no location was
  7169. // passed in
  7170. var script = document.createElement('script');
  7171. script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.12.4/vtt.min.js';
  7172. script.onload = function () {
  7173. /**
  7174. * Fired when vtt.js is loaded.
  7175. *
  7176. * @event Tech#vttjsloaded
  7177. * @type {EventTarget~Event}
  7178. */
  7179. _this5.trigger('vttjsloaded');
  7180. };
  7181. script.onerror = function () {
  7182. /**
  7183. * Fired when vtt.js was not loaded due to an error
  7184. *
  7185. * @event Tech#vttjsloaded
  7186. * @type {EventTarget~Event}
  7187. */
  7188. _this5.trigger('vttjserror');
  7189. };
  7190. this.on('dispose', function () {
  7191. script.onload = null;
  7192. script.onerror = null;
  7193. });
  7194. // but have not loaded yet and we set it to true before the inject so that
  7195. // we don't overwrite the injected window.WebVTT if it loads right away
  7196. window.WebVTT = true;
  7197. this.el().parentNode.appendChild(script);
  7198. } else {
  7199. this.ready(this.addWebVttScript_);
  7200. }
  7201. };
  7202. /**
  7203. * Emulate texttracks
  7204. *
  7205. */
  7206. Tech.prototype.emulateTextTracks = function emulateTextTracks() {
  7207. var _this6 = this;
  7208. var tracks = this.textTracks();
  7209. var remoteTracks = this.remoteTextTracks();
  7210. var handleAddTrack = function handleAddTrack(e) {
  7211. return tracks.addTrack(e.track);
  7212. };
  7213. var handleRemoveTrack = function handleRemoveTrack(e) {
  7214. return tracks.removeTrack(e.track);
  7215. };
  7216. remoteTracks.on('addtrack', handleAddTrack);
  7217. remoteTracks.on('removetrack', handleRemoveTrack);
  7218. this.addWebVttScript_();
  7219. var updateDisplay = function updateDisplay() {
  7220. return _this6.trigger('texttrackchange');
  7221. };
  7222. var textTracksChanges = function textTracksChanges() {
  7223. updateDisplay();
  7224. for (var i = 0; i < tracks.length; i++) {
  7225. var track = tracks[i];
  7226. track.removeEventListener('cuechange', updateDisplay);
  7227. if (track.mode === 'showing') {
  7228. track.addEventListener('cuechange', updateDisplay);
  7229. }
  7230. }
  7231. };
  7232. textTracksChanges();
  7233. tracks.addEventListener('change', textTracksChanges);
  7234. tracks.addEventListener('addtrack', textTracksChanges);
  7235. tracks.addEventListener('removetrack', textTracksChanges);
  7236. this.on('dispose', function () {
  7237. remoteTracks.off('addtrack', handleAddTrack);
  7238. remoteTracks.off('removetrack', handleRemoveTrack);
  7239. tracks.removeEventListener('change', textTracksChanges);
  7240. tracks.removeEventListener('addtrack', textTracksChanges);
  7241. tracks.removeEventListener('removetrack', textTracksChanges);
  7242. for (var i = 0; i < tracks.length; i++) {
  7243. var track = tracks[i];
  7244. track.removeEventListener('cuechange', updateDisplay);
  7245. }
  7246. });
  7247. };
  7248. /**
  7249. * Create and returns a remote {@link TextTrack} object.
  7250. *
  7251. * @param {string} kind
  7252. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  7253. *
  7254. * @param {string} [label]
  7255. * Label to identify the text track
  7256. *
  7257. * @param {string} [language]
  7258. * Two letter language abbreviation
  7259. *
  7260. * @return {TextTrack}
  7261. * The TextTrack that gets created.
  7262. */
  7263. Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) {
  7264. if (!kind) {
  7265. throw new Error('TextTrack kind is required but was not provided');
  7266. }
  7267. return createTrackHelper(this, kind, label, language);
  7268. };
  7269. /**
  7270. * Create an emulated TextTrack for use by addRemoteTextTrack
  7271. *
  7272. * This is intended to be overridden by classes that inherit from
  7273. * Tech in order to create native or custom TextTracks.
  7274. *
  7275. * @param {Object} options
  7276. * The object should contain the options to initialize the TextTrack with.
  7277. *
  7278. * @param {string} [options.kind]
  7279. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  7280. *
  7281. * @param {string} [options.label].
  7282. * Label to identify the text track
  7283. *
  7284. * @param {string} [options.language]
  7285. * Two letter language abbreviation.
  7286. *
  7287. * @return {HTMLTrackElement}
  7288. * The track element that gets created.
  7289. */
  7290. Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) {
  7291. var track = mergeOptions(options, {
  7292. tech: this
  7293. });
  7294. return new REMOTE.remoteTextEl.TrackClass(track);
  7295. };
  7296. /**
  7297. * Creates a remote text track object and returns an html track element.
  7298. *
  7299. * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
  7300. *
  7301. * @param {Object} options
  7302. * See {@link Tech#createRemoteTextTrack} for more detailed properties.
  7303. *
  7304. * @param {boolean} [manualCleanup=true]
  7305. * - When false: the TextTrack will be automatically removed from the video
  7306. * element whenever the source changes
  7307. * - When True: The TextTrack will have to be cleaned up manually
  7308. *
  7309. * @return {HTMLTrackElement}
  7310. * An Html Track Element.
  7311. *
  7312. * @deprecated The default functionality for this function will be equivalent
  7313. * to "manualCleanup=false" in the future. The manualCleanup parameter will
  7314. * also be removed.
  7315. */
  7316. Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() {
  7317. var _this7 = this;
  7318. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  7319. var manualCleanup = arguments[1];
  7320. var htmlTrackElement = this.createRemoteTextTrack(options);
  7321. if (manualCleanup !== true && manualCleanup !== false) {
  7322. // deprecation warning
  7323. log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
  7324. manualCleanup = true;
  7325. }
  7326. // store HTMLTrackElement and TextTrack to remote list
  7327. this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
  7328. this.remoteTextTracks().addTrack(htmlTrackElement.track);
  7329. if (manualCleanup !== true) {
  7330. // create the TextTrackList if it doesn't exist
  7331. this.ready(function () {
  7332. return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
  7333. });
  7334. }
  7335. return htmlTrackElement;
  7336. };
  7337. /**
  7338. * Remove a remote text track from the remote `TextTrackList`.
  7339. *
  7340. * @param {TextTrack} track
  7341. * `TextTrack` to remove from the `TextTrackList`
  7342. */
  7343. Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  7344. var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
  7345. // remove HTMLTrackElement and TextTrack from remote list
  7346. this.remoteTextTrackEls().removeTrackElement_(trackElement);
  7347. this.remoteTextTracks().removeTrack(track);
  7348. this.autoRemoteTextTracks_.removeTrack(track);
  7349. };
  7350. /**
  7351. * Gets available media playback quality metrics as specified by the W3C's Media
  7352. * Playback Quality API.
  7353. *
  7354. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  7355. *
  7356. * @return {Object}
  7357. * An object with supported media playback quality metrics
  7358. *
  7359. * @abstract
  7360. */
  7361. Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  7362. return {};
  7363. };
  7364. /**
  7365. * A method to set a poster from a `Tech`.
  7366. *
  7367. * @abstract
  7368. */
  7369. Tech.prototype.setPoster = function setPoster() {};
  7370. /**
  7371. * A method to check for the presence of the 'playsinine' <video> attribute.
  7372. *
  7373. * @abstract
  7374. */
  7375. Tech.prototype.playsinline = function playsinline() {};
  7376. /**
  7377. * A method to set or unset the 'playsinine' <video> attribute.
  7378. *
  7379. * @abstract
  7380. */
  7381. Tech.prototype.setPlaysinline = function setPlaysinline() {};
  7382. /*
  7383. * Check if the tech can support the given mime-type.
  7384. *
  7385. * The base tech does not support any type, but source handlers might
  7386. * overwrite this.
  7387. *
  7388. * @param {string} type
  7389. * The mimetype to check for support
  7390. *
  7391. * @return {string}
  7392. * 'probably', 'maybe', or empty string
  7393. *
  7394. * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
  7395. *
  7396. * @abstract
  7397. */
  7398. Tech.prototype.canPlayType = function canPlayType() {
  7399. return '';
  7400. };
  7401. /**
  7402. * Check if the type is supported by this tech.
  7403. *
  7404. * The base tech does not support any type, but source handlers might
  7405. * overwrite this.
  7406. *
  7407. * @param {string} type
  7408. * The media type to check
  7409. * @return {string} Returns the native video element's response
  7410. */
  7411. Tech.canPlayType = function canPlayType() {
  7412. return '';
  7413. };
  7414. /**
  7415. * Check if the tech can support the given source
  7416. * @param {Object} srcObj
  7417. * The source object
  7418. * @param {Object} options
  7419. * The options passed to the tech
  7420. * @return {string} 'probably', 'maybe', or '' (empty string)
  7421. */
  7422. Tech.canPlaySource = function canPlaySource(srcObj, options) {
  7423. return Tech.canPlayType(srcObj.type);
  7424. };
  7425. /*
  7426. * Return whether the argument is a Tech or not.
  7427. * Can be passed either a Class like `Html5` or a instance like `player.tech_`
  7428. *
  7429. * @param {Object} component
  7430. * The item to check
  7431. *
  7432. * @return {boolean}
  7433. * Whether it is a tech or not
  7434. * - True if it is a tech
  7435. * - False if it is not
  7436. */
  7437. Tech.isTech = function isTech(component) {
  7438. return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
  7439. };
  7440. /**
  7441. * Registers a `Tech` into a shared list for videojs.
  7442. *
  7443. * @param {string} name
  7444. * Name of the `Tech` to register.
  7445. *
  7446. * @param {Object} tech
  7447. * The `Tech` class to register.
  7448. */
  7449. Tech.registerTech = function registerTech(name, tech) {
  7450. if (!Tech.techs_) {
  7451. Tech.techs_ = {};
  7452. }
  7453. if (!Tech.isTech(tech)) {
  7454. throw new Error('Tech ' + name + ' must be a Tech');
  7455. }
  7456. if (!Tech.canPlayType) {
  7457. throw new Error('Techs must have a static canPlayType method on them');
  7458. }
  7459. if (!Tech.canPlaySource) {
  7460. throw new Error('Techs must have a static canPlaySource method on them');
  7461. }
  7462. name = toTitleCase(name);
  7463. Tech.techs_[name] = tech;
  7464. if (name !== 'Tech') {
  7465. // camel case the techName for use in techOrder
  7466. Tech.defaultTechOrder_.push(name);
  7467. }
  7468. return tech;
  7469. };
  7470. /**
  7471. * Get a `Tech` from the shared list by name.
  7472. *
  7473. * @param {string} name
  7474. * `camelCase` or `TitleCase` name of the Tech to get
  7475. *
  7476. * @return {Tech|undefined}
  7477. * The `Tech` or undefined if there was no tech with the name requsted.
  7478. */
  7479. Tech.getTech = function getTech(name) {
  7480. if (!name) {
  7481. return;
  7482. }
  7483. name = toTitleCase(name);
  7484. if (Tech.techs_ && Tech.techs_[name]) {
  7485. return Tech.techs_[name];
  7486. }
  7487. if (window && window.videojs && window.videojs[name]) {
  7488. log.warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)');
  7489. return window.videojs[name];
  7490. }
  7491. };
  7492. return Tech;
  7493. }(Component);
  7494. /**
  7495. * Get the {@link VideoTrackList}
  7496. *
  7497. * @returns {VideoTrackList}
  7498. * @method Tech.prototype.videoTracks
  7499. */
  7500. /**
  7501. * Get the {@link AudioTrackList}
  7502. *
  7503. * @returns {AudioTrackList}
  7504. * @method Tech.prototype.audioTracks
  7505. */
  7506. /**
  7507. * Get the {@link TextTrackList}
  7508. *
  7509. * @returns {TextTrackList}
  7510. * @method Tech.prototype.textTracks
  7511. */
  7512. /**
  7513. * Get the remote element {@link TextTrackList}
  7514. *
  7515. * @returns {TextTrackList}
  7516. * @method Tech.prototype.remoteTextTracks
  7517. */
  7518. /**
  7519. * Get the remote element {@link HtmlTrackElementList}
  7520. *
  7521. * @returns {HtmlTrackElementList}
  7522. * @method Tech.prototype.remoteTextTrackEls
  7523. */
  7524. ALL.names.forEach(function (name) {
  7525. var props = ALL[name];
  7526. Tech.prototype[props.getterName] = function () {
  7527. this[props.privateName] = this[props.privateName] || new props.ListClass();
  7528. return this[props.privateName];
  7529. };
  7530. });
  7531. /**
  7532. * List of associated text tracks
  7533. *
  7534. * @type {TextTrackList}
  7535. * @private
  7536. * @property Tech#textTracks_
  7537. */
  7538. /**
  7539. * List of associated audio tracks.
  7540. *
  7541. * @type {AudioTrackList}
  7542. * @private
  7543. * @property Tech#audioTracks_
  7544. */
  7545. /**
  7546. * List of associated video tracks.
  7547. *
  7548. * @type {VideoTrackList}
  7549. * @private
  7550. * @property Tech#videoTracks_
  7551. */
  7552. /**
  7553. * Boolean indicating wether the `Tech` supports volume control.
  7554. *
  7555. * @type {boolean}
  7556. * @default
  7557. */
  7558. Tech.prototype.featuresVolumeControl = true;
  7559. /**
  7560. * Boolean indicating whether the `Tech` supports muting volume.
  7561. *
  7562. * @type {bolean}
  7563. * @default
  7564. */
  7565. Tech.prototype.featuresMuteControl = true;
  7566. /**
  7567. * Boolean indicating whether the `Tech` supports fullscreen resize control.
  7568. * Resizing plugins using request fullscreen reloads the plugin
  7569. *
  7570. * @type {boolean}
  7571. * @default
  7572. */
  7573. Tech.prototype.featuresFullscreenResize = false;
  7574. /**
  7575. * Boolean indicating wether the `Tech` supports changing the speed at which the video
  7576. * plays. Examples:
  7577. * - Set player to play 2x (twice) as fast
  7578. * - Set player to play 0.5x (half) as fast
  7579. *
  7580. * @type {boolean}
  7581. * @default
  7582. */
  7583. Tech.prototype.featuresPlaybackRate = false;
  7584. /**
  7585. * Boolean indicating wether the `Tech` supports the `progress` event. This is currently
  7586. * not triggered by video-js-swf. This will be used to determine if
  7587. * {@link Tech#manualProgressOn} should be called.
  7588. *
  7589. * @type {boolean}
  7590. * @default
  7591. */
  7592. Tech.prototype.featuresProgressEvents = false;
  7593. /**
  7594. * Boolean indicating wether the `Tech` supports the `sourceset` event.
  7595. *
  7596. * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
  7597. * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
  7598. * a new source.
  7599. *
  7600. * @type {boolean}
  7601. * @default
  7602. */
  7603. Tech.prototype.featuresSourceset = false;
  7604. /**
  7605. * Boolean indicating wether the `Tech` supports the `timeupdate` event. This is currently
  7606. * not triggered by video-js-swf. This will be used to determine if
  7607. * {@link Tech#manualTimeUpdates} should be called.
  7608. *
  7609. * @type {boolean}
  7610. * @default
  7611. */
  7612. Tech.prototype.featuresTimeupdateEvents = false;
  7613. /**
  7614. * Boolean indicating wether the `Tech` supports the native `TextTrack`s.
  7615. * This will help us integrate with native `TextTrack`s if the browser supports them.
  7616. *
  7617. * @type {boolean}
  7618. * @default
  7619. */
  7620. Tech.prototype.featuresNativeTextTracks = false;
  7621. /**
  7622. * A functional mixin for techs that want to use the Source Handler pattern.
  7623. * Source handlers are scripts for handling specific formats.
  7624. * The source handler pattern is used for adaptive formats (HLS, DASH) that
  7625. * manually load video data and feed it into a Source Buffer (Media Source Extensions)
  7626. * Example: `Tech.withSourceHandlers.call(MyTech);`
  7627. *
  7628. * @param {Tech} _Tech
  7629. * The tech to add source handler functions to.
  7630. *
  7631. * @mixes Tech~SourceHandlerAdditions
  7632. */
  7633. Tech.withSourceHandlers = function (_Tech) {
  7634. /**
  7635. * Register a source handler
  7636. *
  7637. * @param {Function} handler
  7638. * The source handler class
  7639. *
  7640. * @param {number} [index]
  7641. * Register it at the following index
  7642. */
  7643. _Tech.registerSourceHandler = function (handler, index) {
  7644. var handlers = _Tech.sourceHandlers;
  7645. if (!handlers) {
  7646. handlers = _Tech.sourceHandlers = [];
  7647. }
  7648. if (index === undefined) {
  7649. // add to the end of the list
  7650. index = handlers.length;
  7651. }
  7652. handlers.splice(index, 0, handler);
  7653. };
  7654. /**
  7655. * Check if the tech can support the given type. Also checks the
  7656. * Techs sourceHandlers.
  7657. *
  7658. * @param {string} type
  7659. * The mimetype to check.
  7660. *
  7661. * @return {string}
  7662. * 'probably', 'maybe', or '' (empty string)
  7663. */
  7664. _Tech.canPlayType = function (type) {
  7665. var handlers = _Tech.sourceHandlers || [];
  7666. var can = void 0;
  7667. for (var i = 0; i < handlers.length; i++) {
  7668. can = handlers[i].canPlayType(type);
  7669. if (can) {
  7670. return can;
  7671. }
  7672. }
  7673. return '';
  7674. };
  7675. /**
  7676. * Returns the first source handler that supports the source.
  7677. *
  7678. * TODO: Answer question: should 'probably' be prioritized over 'maybe'
  7679. *
  7680. * @param {Tech~SourceObject} source
  7681. * The source object
  7682. *
  7683. * @param {Object} options
  7684. * The options passed to the tech
  7685. *
  7686. * @return {SourceHandler|null}
  7687. * The first source handler that supports the source or null if
  7688. * no SourceHandler supports the source
  7689. */
  7690. _Tech.selectSourceHandler = function (source, options) {
  7691. var handlers = _Tech.sourceHandlers || [];
  7692. var can = void 0;
  7693. for (var i = 0; i < handlers.length; i++) {
  7694. can = handlers[i].canHandleSource(source, options);
  7695. if (can) {
  7696. return handlers[i];
  7697. }
  7698. }
  7699. return null;
  7700. };
  7701. /**
  7702. * Check if the tech can support the given source.
  7703. *
  7704. * @param {Tech~SourceObject} srcObj
  7705. * The source object
  7706. *
  7707. * @param {Object} options
  7708. * The options passed to the tech
  7709. *
  7710. * @return {string}
  7711. * 'probably', 'maybe', or '' (empty string)
  7712. */
  7713. _Tech.canPlaySource = function (srcObj, options) {
  7714. var sh = _Tech.selectSourceHandler(srcObj, options);
  7715. if (sh) {
  7716. return sh.canHandleSource(srcObj, options);
  7717. }
  7718. return '';
  7719. };
  7720. /**
  7721. * When using a source handler, prefer its implementation of
  7722. * any function normally provided by the tech.
  7723. */
  7724. var deferrable = ['seekable', 'seeking', 'duration'];
  7725. /**
  7726. * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
  7727. * function if it exists, with a fallback to the Techs seekable function.
  7728. *
  7729. * @method _Tech.seekable
  7730. */
  7731. /**
  7732. * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
  7733. * function if it exists, otherwise it will fallback to the techs duration function.
  7734. *
  7735. * @method _Tech.duration
  7736. */
  7737. deferrable.forEach(function (fnName) {
  7738. var originalFn = this[fnName];
  7739. if (typeof originalFn !== 'function') {
  7740. return;
  7741. }
  7742. this[fnName] = function () {
  7743. if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
  7744. return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
  7745. }
  7746. return originalFn.apply(this, arguments);
  7747. };
  7748. }, _Tech.prototype);
  7749. /**
  7750. * Create a function for setting the source using a source object
  7751. * and source handlers.
  7752. * Should never be called unless a source handler was found.
  7753. *
  7754. * @param {Tech~SourceObject} source
  7755. * A source object with src and type keys
  7756. */
  7757. _Tech.prototype.setSource = function (source) {
  7758. var sh = _Tech.selectSourceHandler(source, this.options_);
  7759. if (!sh) {
  7760. // Fall back to a native source hander when unsupported sources are
  7761. // deliberately set
  7762. if (_Tech.nativeSourceHandler) {
  7763. sh = _Tech.nativeSourceHandler;
  7764. } else {
  7765. log.error('No source hander found for the current source.');
  7766. }
  7767. }
  7768. // Dispose any existing source handler
  7769. this.disposeSourceHandler();
  7770. this.off('dispose', this.disposeSourceHandler);
  7771. if (sh !== _Tech.nativeSourceHandler) {
  7772. this.currentSource_ = source;
  7773. }
  7774. this.sourceHandler_ = sh.handleSource(source, this, this.options_);
  7775. this.on('dispose', this.disposeSourceHandler);
  7776. };
  7777. /**
  7778. * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
  7779. *
  7780. * @listens Tech#dispose
  7781. */
  7782. _Tech.prototype.disposeSourceHandler = function () {
  7783. // if we have a source and get another one
  7784. // then we are loading something new
  7785. // than clear all of our current tracks
  7786. if (this.currentSource_) {
  7787. this.clearTracks(['audio', 'video']);
  7788. this.currentSource_ = null;
  7789. }
  7790. // always clean up auto-text tracks
  7791. this.cleanupAutoTextTracks();
  7792. if (this.sourceHandler_) {
  7793. if (this.sourceHandler_.dispose) {
  7794. this.sourceHandler_.dispose();
  7795. }
  7796. this.sourceHandler_ = null;
  7797. }
  7798. };
  7799. };
  7800. // The base Tech class needs to be registered as a Component. It is the only
  7801. // Tech that can be registered as a Component.
  7802. Component.registerComponent('Tech', Tech);
  7803. Tech.registerTech('Tech', Tech);
  7804. /**
  7805. * A list of techs that should be added to techOrder on Players
  7806. *
  7807. * @private
  7808. */
  7809. Tech.defaultTechOrder_ = [];
  7810. var middlewares = {};
  7811. var middlewareInstances = {};
  7812. var TERMINATOR = {};
  7813. function use(type, middleware) {
  7814. middlewares[type] = middlewares[type] || [];
  7815. middlewares[type].push(middleware);
  7816. }
  7817. function setSource(player, src, next) {
  7818. player.setTimeout(function () {
  7819. return setSourceHelper(src, middlewares[src.type], next, player);
  7820. }, 1);
  7821. }
  7822. function setTech(middleware, tech) {
  7823. middleware.forEach(function (mw) {
  7824. return mw.setTech && mw.setTech(tech);
  7825. });
  7826. }
  7827. /**
  7828. * Calls a getter on the tech first, through each middleware
  7829. * from right to left to the player.
  7830. */
  7831. function get$1(middleware, tech, method) {
  7832. return middleware.reduceRight(middlewareIterator(method), tech[method]());
  7833. }
  7834. /**
  7835. * Takes the argument given to the player and calls the setter method on each
  7836. * middlware from left to right to the tech.
  7837. */
  7838. function set$1(middleware, tech, method, arg) {
  7839. return tech[method](middleware.reduce(middlewareIterator(method), arg));
  7840. }
  7841. /**
  7842. * Takes the argument given to the player and calls the `call` version of the method
  7843. * on each middleware from left to right.
  7844. * Then, call the passed in method on the tech and return the result unchanged
  7845. * back to the player, through middleware, this time from right to left.
  7846. */
  7847. function mediate(middleware, tech, method) {
  7848. var arg = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
  7849. var callMethod = 'call' + toTitleCase(method);
  7850. var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
  7851. var terminated = middlewareValue === TERMINATOR;
  7852. var returnValue = terminated ? null : tech[method](middlewareValue);
  7853. executeRight(middleware, method, returnValue, terminated);
  7854. return returnValue;
  7855. }
  7856. var allowedGetters = {
  7857. buffered: 1,
  7858. currentTime: 1,
  7859. duration: 1,
  7860. seekable: 1,
  7861. played: 1,
  7862. paused: 1
  7863. };
  7864. var allowedSetters = {
  7865. setCurrentTime: 1
  7866. };
  7867. var allowedMediators = {
  7868. play: 1,
  7869. pause: 1
  7870. };
  7871. function middlewareIterator(method) {
  7872. return function (value, mw) {
  7873. // if the previous middleware terminated, pass along the termination
  7874. if (value === TERMINATOR) {
  7875. return TERMINATOR;
  7876. }
  7877. if (mw[method]) {
  7878. return mw[method](value);
  7879. }
  7880. return value;
  7881. };
  7882. }
  7883. function executeRight(mws, method, value, terminated) {
  7884. for (var i = mws.length - 1; i >= 0; i--) {
  7885. var mw = mws[i];
  7886. if (mw[method]) {
  7887. mw[method](terminated, value);
  7888. }
  7889. }
  7890. }
  7891. function clearCacheForPlayer(player) {
  7892. middlewareInstances[player.id()] = null;
  7893. }
  7894. /**
  7895. * {
  7896. * [playerId]: [[mwFactory, mwInstance], ...]
  7897. * }
  7898. */
  7899. function getOrCreateFactory(player, mwFactory) {
  7900. var mws = middlewareInstances[player.id()];
  7901. var mw = null;
  7902. if (mws === undefined || mws === null) {
  7903. mw = mwFactory(player);
  7904. middlewareInstances[player.id()] = [[mwFactory, mw]];
  7905. return mw;
  7906. }
  7907. for (var i = 0; i < mws.length; i++) {
  7908. var _mws$i = mws[i],
  7909. mwf = _mws$i[0],
  7910. mwi = _mws$i[1];
  7911. if (mwf !== mwFactory) {
  7912. continue;
  7913. }
  7914. mw = mwi;
  7915. }
  7916. if (mw === null) {
  7917. mw = mwFactory(player);
  7918. mws.push([mwFactory, mw]);
  7919. }
  7920. return mw;
  7921. }
  7922. function setSourceHelper() {
  7923. var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  7924. var middleware = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  7925. var next = arguments[2];
  7926. var player = arguments[3];
  7927. var acc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
  7928. var lastRun = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
  7929. var mwFactory = middleware[0],
  7930. mwrest = middleware.slice(1);
  7931. // if mwFactory is a string, then we're at a fork in the road
  7932. if (typeof mwFactory === 'string') {
  7933. setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
  7934. // if we have an mwFactory, call it with the player to get the mw,
  7935. // then call the mw's setSource method
  7936. } else if (mwFactory) {
  7937. var mw = getOrCreateFactory(player, mwFactory);
  7938. // if setSource isn't present, implicitly select this middleware
  7939. if (!mw.setSource) {
  7940. acc.push(mw);
  7941. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  7942. }
  7943. mw.setSource(assign({}, src), function (err, _src) {
  7944. // something happened, try the next middleware on the current level
  7945. // make sure to use the old src
  7946. if (err) {
  7947. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  7948. }
  7949. // we've succeeded, now we need to go deeper
  7950. acc.push(mw);
  7951. // if it's the same type, continue down the current chain
  7952. // otherwise, we want to go down the new chain
  7953. setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
  7954. });
  7955. } else if (mwrest.length) {
  7956. setSourceHelper(src, mwrest, next, player, acc, lastRun);
  7957. } else if (lastRun) {
  7958. next(src, acc);
  7959. } else {
  7960. setSourceHelper(src, middlewares['*'], next, player, acc, true);
  7961. }
  7962. }
  7963. /**
  7964. * Mimetypes
  7965. *
  7966. * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
  7967. * @typedef Mimetypes~Kind
  7968. * @enum
  7969. */
  7970. var MimetypesKind = {
  7971. opus: 'video/ogg',
  7972. ogv: 'video/ogg',
  7973. mp4: 'video/mp4',
  7974. mov: 'video/mp4',
  7975. m4v: 'video/mp4',
  7976. mkv: 'video/x-matroska',
  7977. mp3: 'audio/mpeg',
  7978. aac: 'audio/aac',
  7979. oga: 'audio/ogg',
  7980. m3u8: 'application/x-mpegURL'
  7981. };
  7982. /**
  7983. * Get the mimetype of a given src url if possible
  7984. *
  7985. * @param {string} src
  7986. * The url to the src
  7987. *
  7988. * @return {string}
  7989. * return the mimetype if it was known or empty string otherwise
  7990. */
  7991. var getMimetype = function getMimetype() {
  7992. var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  7993. var ext = getFileExtension(src);
  7994. var mimetype = MimetypesKind[ext.toLowerCase()];
  7995. return mimetype || '';
  7996. };
  7997. /**
  7998. * Find the mime type of a given source string if possible. Uses the player
  7999. * source cache.
  8000. *
  8001. * @param {Player} player
  8002. * The player object
  8003. *
  8004. * @param {string} src
  8005. * The source string
  8006. *
  8007. * @return {string}
  8008. * The type that was found
  8009. */
  8010. var findMimetype = function findMimetype(player, src) {
  8011. if (!src) {
  8012. return '';
  8013. }
  8014. // 1. check for the type in the `source` cache
  8015. if (player.cache_.source.src === src && player.cache_.source.type) {
  8016. return player.cache_.source.type;
  8017. }
  8018. // 2. see if we have this source in our `currentSources` cache
  8019. var matchingSources = player.cache_.sources.filter(function (s) {
  8020. return s.src === src;
  8021. });
  8022. if (matchingSources.length) {
  8023. return matchingSources[0].type;
  8024. }
  8025. // 3. look for the src url in source elements and use the type there
  8026. var sources = player.$$('source');
  8027. for (var i = 0; i < sources.length; i++) {
  8028. var s = sources[i];
  8029. if (s.type && s.src && s.src === src) {
  8030. return s.type;
  8031. }
  8032. }
  8033. // 4. finally fallback to our list of mime types based on src url extension
  8034. return getMimetype(src);
  8035. };
  8036. /**
  8037. * @module filter-source
  8038. */
  8039. /**
  8040. * Filter out single bad source objects or multiple source objects in an
  8041. * array. Also flattens nested source object arrays into a 1 dimensional
  8042. * array of source objects.
  8043. *
  8044. * @param {Tech~SourceObject|Tech~SourceObject[]} src
  8045. * The src object to filter
  8046. *
  8047. * @return {Tech~SourceObject[]}
  8048. * An array of sourceobjects containing only valid sources
  8049. *
  8050. * @private
  8051. */
  8052. var filterSource = function filterSource(src) {
  8053. // traverse array
  8054. if (Array.isArray(src)) {
  8055. var newsrc = [];
  8056. src.forEach(function (srcobj) {
  8057. srcobj = filterSource(srcobj);
  8058. if (Array.isArray(srcobj)) {
  8059. newsrc = newsrc.concat(srcobj);
  8060. } else if (isObject(srcobj)) {
  8061. newsrc.push(srcobj);
  8062. }
  8063. });
  8064. src = newsrc;
  8065. } else if (typeof src === 'string' && src.trim()) {
  8066. // convert string into object
  8067. src = [fixSource({ src: src })];
  8068. } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
  8069. // src is already valid
  8070. src = [fixSource(src)];
  8071. } else {
  8072. // invalid source, turn it into an empty array
  8073. src = [];
  8074. }
  8075. return src;
  8076. };
  8077. /**
  8078. * Checks src mimetype, adding it when possible
  8079. *
  8080. * @param {Tech~SourceObject} src
  8081. * The src object to check
  8082. * @return {Tech~SourceObject}
  8083. * src Object with known type
  8084. */
  8085. function fixSource(src) {
  8086. var mimetype = getMimetype(src.src);
  8087. if (!src.type && mimetype) {
  8088. src.type = mimetype;
  8089. }
  8090. return src;
  8091. }
  8092. /**
  8093. * @file loader.js
  8094. */
  8095. /**
  8096. * The `MediaLoader` is the `Component` that decides which playback technology to load
  8097. * when a player is initialized.
  8098. *
  8099. * @extends Component
  8100. */
  8101. var MediaLoader = function (_Component) {
  8102. inherits(MediaLoader, _Component);
  8103. /**
  8104. * Create an instance of this class.
  8105. *
  8106. * @param {Player} player
  8107. * The `Player` that this class should attach to.
  8108. *
  8109. * @param {Object} [options]
  8110. * The key/value stroe of player options.
  8111. *
  8112. * @param {Component~ReadyCallback} [ready]
  8113. * The function that is run when this component is ready.
  8114. */
  8115. function MediaLoader(player, options, ready) {
  8116. classCallCheck(this, MediaLoader);
  8117. // MediaLoader has no element
  8118. var options_ = mergeOptions({ createEl: false }, options);
  8119. // If there are no sources when the player is initialized,
  8120. // load the first supported playback technology.
  8121. var _this = possibleConstructorReturn(this, _Component.call(this, player, options_, ready));
  8122. if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
  8123. for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
  8124. var techName = toTitleCase(j[i]);
  8125. var tech = Tech.getTech(techName);
  8126. // Support old behavior of techs being registered as components.
  8127. // Remove once that deprecated behavior is removed.
  8128. if (!techName) {
  8129. tech = Component.getComponent(techName);
  8130. }
  8131. // Check if the browser supports this technology
  8132. if (tech && tech.isSupported()) {
  8133. player.loadTech_(techName);
  8134. break;
  8135. }
  8136. }
  8137. } else {
  8138. // Loop through playback technologies (HTML5, Flash) and check for support.
  8139. // Then load the best source.
  8140. // A few assumptions here:
  8141. // All playback technologies respect preload false.
  8142. player.src(options.playerOptions.sources);
  8143. }
  8144. return _this;
  8145. }
  8146. return MediaLoader;
  8147. }(Component);
  8148. Component.registerComponent('MediaLoader', MediaLoader);
  8149. /**
  8150. * @file button.js
  8151. */
  8152. /**
  8153. * Clickable Component which is clickable or keyboard actionable,
  8154. * but is not a native HTML button.
  8155. *
  8156. * @extends Component
  8157. */
  8158. var ClickableComponent = function (_Component) {
  8159. inherits(ClickableComponent, _Component);
  8160. /**
  8161. * Creates an instance of this class.
  8162. *
  8163. * @param {Player} player
  8164. * The `Player` that this class should be attached to.
  8165. *
  8166. * @param {Object} [options]
  8167. * The key/value store of player options.
  8168. */
  8169. function ClickableComponent(player, options) {
  8170. classCallCheck(this, ClickableComponent);
  8171. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  8172. _this.emitTapEvents();
  8173. _this.enable();
  8174. return _this;
  8175. }
  8176. /**
  8177. * Create the `Component`s DOM element.
  8178. *
  8179. * @param {string} [tag=div]
  8180. * The element's node type.
  8181. *
  8182. * @param {Object} [props={}]
  8183. * An object of properties that should be set on the element.
  8184. *
  8185. * @param {Object} [attributes={}]
  8186. * An object of attributes that should be set on the element.
  8187. *
  8188. * @return {Element}
  8189. * The element that gets created.
  8190. */
  8191. ClickableComponent.prototype.createEl = function createEl$$1() {
  8192. var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';
  8193. var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  8194. var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  8195. props = assign({
  8196. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  8197. className: this.buildCSSClass(),
  8198. tabIndex: 0
  8199. }, props);
  8200. if (tag === 'button') {
  8201. log.error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.');
  8202. }
  8203. // Add ARIA attributes for clickable element which is not a native HTML button
  8204. attributes = assign({
  8205. role: 'button'
  8206. }, attributes);
  8207. this.tabIndex_ = props.tabIndex;
  8208. var el = _Component.prototype.createEl.call(this, tag, props, attributes);
  8209. this.createControlTextEl(el);
  8210. return el;
  8211. };
  8212. ClickableComponent.prototype.dispose = function dispose() {
  8213. // remove controlTextEl_ on dipose
  8214. this.controlTextEl_ = null;
  8215. _Component.prototype.dispose.call(this);
  8216. };
  8217. /**
  8218. * Create a control text element on this `Component`
  8219. *
  8220. * @param {Element} [el]
  8221. * Parent element for the control text.
  8222. *
  8223. * @return {Element}
  8224. * The control text element that gets created.
  8225. */
  8226. ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) {
  8227. this.controlTextEl_ = createEl('span', {
  8228. className: 'vjs-control-text'
  8229. }, {
  8230. // let the screen reader user know that the text of the element may change
  8231. 'aria-live': 'polite'
  8232. });
  8233. if (el) {
  8234. el.appendChild(this.controlTextEl_);
  8235. }
  8236. this.controlText(this.controlText_, el);
  8237. return this.controlTextEl_;
  8238. };
  8239. /**
  8240. * Get or set the localize text to use for the controls on the `Component`.
  8241. *
  8242. * @param {string} [text]
  8243. * Control text for element.
  8244. *
  8245. * @param {Element} [el=this.el()]
  8246. * Element to set the title on.
  8247. *
  8248. * @return {string}
  8249. * - The control text when getting
  8250. */
  8251. ClickableComponent.prototype.controlText = function controlText(text) {
  8252. var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.el();
  8253. if (text === undefined) {
  8254. return this.controlText_ || 'Need Text';
  8255. }
  8256. var localizedText = this.localize(text);
  8257. this.controlText_ = text;
  8258. textContent(this.controlTextEl_, localizedText);
  8259. if (!this.nonIconControl) {
  8260. // Set title attribute if only an icon is shown
  8261. el.setAttribute('title', localizedText);
  8262. }
  8263. };
  8264. /**
  8265. * Builds the default DOM `className`.
  8266. *
  8267. * @return {string}
  8268. * The DOM `className` for this object.
  8269. */
  8270. ClickableComponent.prototype.buildCSSClass = function buildCSSClass() {
  8271. return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this);
  8272. };
  8273. /**
  8274. * Enable this `Component`s element.
  8275. */
  8276. ClickableComponent.prototype.enable = function enable() {
  8277. if (!this.enabled_) {
  8278. this.enabled_ = true;
  8279. this.removeClass('vjs-disabled');
  8280. this.el_.setAttribute('aria-disabled', 'false');
  8281. if (typeof this.tabIndex_ !== 'undefined') {
  8282. this.el_.setAttribute('tabIndex', this.tabIndex_);
  8283. }
  8284. this.on(['tap', 'click'], this.handleClick);
  8285. this.on('focus', this.handleFocus);
  8286. this.on('blur', this.handleBlur);
  8287. }
  8288. };
  8289. /**
  8290. * Disable this `Component`s element.
  8291. */
  8292. ClickableComponent.prototype.disable = function disable() {
  8293. this.enabled_ = false;
  8294. this.addClass('vjs-disabled');
  8295. this.el_.setAttribute('aria-disabled', 'true');
  8296. if (typeof this.tabIndex_ !== 'undefined') {
  8297. this.el_.removeAttribute('tabIndex');
  8298. }
  8299. this.off(['tap', 'click'], this.handleClick);
  8300. this.off('focus', this.handleFocus);
  8301. this.off('blur', this.handleBlur);
  8302. };
  8303. /**
  8304. * This gets called when a `ClickableComponent` gets:
  8305. * - Clicked (via the `click` event, listening starts in the constructor)
  8306. * - Tapped (via the `tap` event, listening starts in the constructor)
  8307. * - The following things happen in order:
  8308. * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
  8309. * `ClickableComponent`.
  8310. * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
  8311. * {@link ClickableComponent#handleKeyPress}.
  8312. * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
  8313. * the space or enter key.
  8314. * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
  8315. * event as a parameter.
  8316. *
  8317. * @param {EventTarget~Event} event
  8318. * The `keydown`, `tap`, or `click` event that caused this function to be
  8319. * called.
  8320. *
  8321. * @listens tap
  8322. * @listens click
  8323. * @abstract
  8324. */
  8325. ClickableComponent.prototype.handleClick = function handleClick(event) {};
  8326. /**
  8327. * This gets called when a `ClickableComponent` gains focus via a `focus` event.
  8328. * Turns on listening for `keydown` events. When they happen it
  8329. * calls `this.handleKeyPress`.
  8330. *
  8331. * @param {EventTarget~Event} event
  8332. * The `focus` event that caused this function to be called.
  8333. *
  8334. * @listens focus
  8335. */
  8336. ClickableComponent.prototype.handleFocus = function handleFocus(event) {
  8337. on(document, 'keydown', bind(this, this.handleKeyPress));
  8338. };
  8339. /**
  8340. * Called when this ClickableComponent has focus and a key gets pressed down. By
  8341. * default it will call `this.handleClick` when the key is space or enter.
  8342. *
  8343. * @param {EventTarget~Event} event
  8344. * The `keydown` event that caused this function to be called.
  8345. *
  8346. * @listens keydown
  8347. */
  8348. ClickableComponent.prototype.handleKeyPress = function handleKeyPress(event) {
  8349. // Support Space (32) or Enter (13) key operation to fire a click event
  8350. if (event.which === 32 || event.which === 13) {
  8351. event.preventDefault();
  8352. this.trigger('click');
  8353. } else if (_Component.prototype.handleKeyPress) {
  8354. // Pass keypress handling up for unsupported keys
  8355. _Component.prototype.handleKeyPress.call(this, event);
  8356. }
  8357. };
  8358. /**
  8359. * Called when a `ClickableComponent` loses focus. Turns off the listener for
  8360. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  8361. *
  8362. * @param {EventTarget~Event} event
  8363. * The `blur` event that caused this function to be called.
  8364. *
  8365. * @listens blur
  8366. */
  8367. ClickableComponent.prototype.handleBlur = function handleBlur(event) {
  8368. off(document, 'keydown', bind(this, this.handleKeyPress));
  8369. };
  8370. return ClickableComponent;
  8371. }(Component);
  8372. Component.registerComponent('ClickableComponent', ClickableComponent);
  8373. /**
  8374. * @file poster-image.js
  8375. */
  8376. /**
  8377. * A `ClickableComponent` that handles showing the poster image for the player.
  8378. *
  8379. * @extends ClickableComponent
  8380. */
  8381. var PosterImage = function (_ClickableComponent) {
  8382. inherits(PosterImage, _ClickableComponent);
  8383. /**
  8384. * Create an instance of this class.
  8385. *
  8386. * @param {Player} player
  8387. * The `Player` that this class should attach to.
  8388. *
  8389. * @param {Object} [options]
  8390. * The key/value store of player options.
  8391. */
  8392. function PosterImage(player, options) {
  8393. classCallCheck(this, PosterImage);
  8394. var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options));
  8395. _this.update();
  8396. player.on('posterchange', bind(_this, _this.update));
  8397. return _this;
  8398. }
  8399. /**
  8400. * Clean up and dispose of the `PosterImage`.
  8401. */
  8402. PosterImage.prototype.dispose = function dispose() {
  8403. this.player().off('posterchange', this.update);
  8404. _ClickableComponent.prototype.dispose.call(this);
  8405. };
  8406. /**
  8407. * Create the `PosterImage`s DOM element.
  8408. *
  8409. * @return {Element}
  8410. * The element that gets created.
  8411. */
  8412. PosterImage.prototype.createEl = function createEl$$1() {
  8413. var el = createEl('div', {
  8414. className: 'vjs-poster',
  8415. // Don't want poster to be tabbable.
  8416. tabIndex: -1
  8417. });
  8418. // To ensure the poster image resizes while maintaining its original aspect
  8419. // ratio, use a div with `background-size` when available. For browsers that
  8420. // do not support `background-size` (e.g. IE8), fall back on using a regular
  8421. // img element.
  8422. if (!BACKGROUND_SIZE_SUPPORTED) {
  8423. this.fallbackImg_ = createEl('img');
  8424. el.appendChild(this.fallbackImg_);
  8425. }
  8426. return el;
  8427. };
  8428. /**
  8429. * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
  8430. *
  8431. * @listens Player#posterchange
  8432. *
  8433. * @param {EventTarget~Event} [event]
  8434. * The `Player#posterchange` event that triggered this function.
  8435. */
  8436. PosterImage.prototype.update = function update(event) {
  8437. var url = this.player().poster();
  8438. this.setSrc(url);
  8439. // If there's no poster source we should display:none on this component
  8440. // so it's not still clickable or right-clickable
  8441. if (url) {
  8442. this.show();
  8443. } else {
  8444. this.hide();
  8445. }
  8446. };
  8447. /**
  8448. * Set the source of the `PosterImage` depending on the display method.
  8449. *
  8450. * @param {string} url
  8451. * The URL to the source for the `PosterImage`.
  8452. */
  8453. PosterImage.prototype.setSrc = function setSrc(url) {
  8454. if (this.fallbackImg_) {
  8455. this.fallbackImg_.src = url;
  8456. } else {
  8457. var backgroundImage = '';
  8458. // Any falsey values should stay as an empty string, otherwise
  8459. // this will throw an extra error
  8460. if (url) {
  8461. backgroundImage = 'url("' + url + '")';
  8462. }
  8463. this.el_.style.backgroundImage = backgroundImage;
  8464. }
  8465. };
  8466. /**
  8467. * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
  8468. * {@link ClickableComponent#handleClick} for instances where this will be triggered.
  8469. *
  8470. * @listens tap
  8471. * @listens click
  8472. * @listens keydown
  8473. *
  8474. * @param {EventTarget~Event} event
  8475. + The `click`, `tap` or `keydown` event that caused this function to be called.
  8476. */
  8477. PosterImage.prototype.handleClick = function handleClick(event) {
  8478. // We don't want a click to trigger playback when controls are disabled
  8479. if (!this.player_.controls()) {
  8480. return;
  8481. }
  8482. if (this.player_.paused()) {
  8483. silencePromise(this.player_.play());
  8484. } else {
  8485. this.player_.pause();
  8486. }
  8487. };
  8488. return PosterImage;
  8489. }(ClickableComponent);
  8490. Component.registerComponent('PosterImage', PosterImage);
  8491. /**
  8492. * @file text-track-display.js
  8493. */
  8494. var darkGray = '#222';
  8495. var lightGray = '#ccc';
  8496. var fontMap = {
  8497. monospace: 'monospace',
  8498. sansSerif: 'sans-serif',
  8499. serif: 'serif',
  8500. monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
  8501. monospaceSerif: '"Courier New", monospace',
  8502. proportionalSansSerif: 'sans-serif',
  8503. proportionalSerif: 'serif',
  8504. casual: '"Comic Sans MS", Impact, fantasy',
  8505. script: '"Monotype Corsiva", cursive',
  8506. smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
  8507. };
  8508. /**
  8509. * Construct an rgba color from a given hex color code.
  8510. *
  8511. * @param {number} color
  8512. * Hex number for color, like #f0e or #f604e2.
  8513. *
  8514. * @param {number} opacity
  8515. * Value for opacity, 0.0 - 1.0.
  8516. *
  8517. * @return {string}
  8518. * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
  8519. */
  8520. function constructColor(color, opacity) {
  8521. var hex = void 0;
  8522. if (color.length === 4) {
  8523. // color looks like "#f0e"
  8524. hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
  8525. } else if (color.length === 7) {
  8526. // color looks like "#f604e2"
  8527. hex = color.slice(1);
  8528. } else {
  8529. throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
  8530. }
  8531. return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
  8532. }
  8533. /**
  8534. * Try to update the style of a DOM element. Some style changes will throw an error,
  8535. * particularly in IE8. Those should be noops.
  8536. *
  8537. * @param {Element} el
  8538. * The DOM element to be styled.
  8539. *
  8540. * @param {string} style
  8541. * The CSS property on the element that should be styled.
  8542. *
  8543. * @param {string} rule
  8544. * The style rule that should be applied to the property.
  8545. *
  8546. * @private
  8547. */
  8548. function tryUpdateStyle(el, style, rule) {
  8549. try {
  8550. el.style[style] = rule;
  8551. } catch (e) {
  8552. // Satisfies linter.
  8553. return;
  8554. }
  8555. }
  8556. /**
  8557. * The component for displaying text track cues.
  8558. *
  8559. * @extends Component
  8560. */
  8561. var TextTrackDisplay = function (_Component) {
  8562. inherits(TextTrackDisplay, _Component);
  8563. /**
  8564. * Creates an instance of this class.
  8565. *
  8566. * @param {Player} player
  8567. * The `Player` that this class should be attached to.
  8568. *
  8569. * @param {Object} [options]
  8570. * The key/value store of player options.
  8571. *
  8572. * @param {Component~ReadyCallback} [ready]
  8573. * The function to call when `TextTrackDisplay` is ready.
  8574. */
  8575. function TextTrackDisplay(player, options, ready) {
  8576. classCallCheck(this, TextTrackDisplay);
  8577. var _this = possibleConstructorReturn(this, _Component.call(this, player, options, ready));
  8578. var updateDisplayHandler = bind(_this, _this.updateDisplay);
  8579. player.on('loadstart', bind(_this, _this.toggleDisplay));
  8580. player.on('texttrackchange', updateDisplayHandler);
  8581. player.on('loadstart', bind(_this, _this.preselectTrack));
  8582. // This used to be called during player init, but was causing an error
  8583. // if a track should show by default and the display hadn't loaded yet.
  8584. // Should probably be moved to an external track loader when we support
  8585. // tracks that don't need a display.
  8586. player.ready(bind(_this, function () {
  8587. if (player.tech_ && player.tech_.featuresNativeTextTracks) {
  8588. this.hide();
  8589. return;
  8590. }
  8591. player.on('fullscreenchange', updateDisplayHandler);
  8592. player.on('playerresize', updateDisplayHandler);
  8593. if (window.addEventListener) {
  8594. window.addEventListener('orientationchange', updateDisplayHandler);
  8595. }
  8596. player.on('dispose', function () {
  8597. return window.removeEventListener('orientationchange', updateDisplayHandler);
  8598. });
  8599. var tracks = this.options_.playerOptions.tracks || [];
  8600. for (var i = 0; i < tracks.length; i++) {
  8601. this.player_.addRemoteTextTrack(tracks[i], true);
  8602. }
  8603. this.preselectTrack();
  8604. }));
  8605. return _this;
  8606. }
  8607. /**
  8608. * Preselect a track following this precedence:
  8609. * - matches the previously selected {@link TextTrack}'s language and kind
  8610. * - matches the previously selected {@link TextTrack}'s language only
  8611. * - is the first default captions track
  8612. * - is the first default descriptions track
  8613. *
  8614. * @listens Player#loadstart
  8615. */
  8616. TextTrackDisplay.prototype.preselectTrack = function preselectTrack() {
  8617. var modes = { captions: 1, subtitles: 1 };
  8618. var trackList = this.player_.textTracks();
  8619. var userPref = this.player_.cache_.selectedLanguage;
  8620. var firstDesc = void 0;
  8621. var firstCaptions = void 0;
  8622. var preferredTrack = void 0;
  8623. for (var i = 0; i < trackList.length; i++) {
  8624. var track = trackList[i];
  8625. if (userPref && userPref.enabled && userPref.language === track.language) {
  8626. // Always choose the track that matches both language and kind
  8627. if (track.kind === userPref.kind) {
  8628. preferredTrack = track;
  8629. // or choose the first track that matches language
  8630. } else if (!preferredTrack) {
  8631. preferredTrack = track;
  8632. }
  8633. // clear everything if offTextTrackMenuItem was clicked
  8634. } else if (userPref && !userPref.enabled) {
  8635. preferredTrack = null;
  8636. firstDesc = null;
  8637. firstCaptions = null;
  8638. } else if (track['default']) {
  8639. if (track.kind === 'descriptions' && !firstDesc) {
  8640. firstDesc = track;
  8641. } else if (track.kind in modes && !firstCaptions) {
  8642. firstCaptions = track;
  8643. }
  8644. }
  8645. }
  8646. // The preferredTrack matches the user preference and takes
  8647. // precendence over all the other tracks.
  8648. // So, display the preferredTrack before the first default track
  8649. // and the subtitles/captions track before the descriptions track
  8650. if (preferredTrack) {
  8651. preferredTrack.mode = 'showing';
  8652. } else if (firstCaptions) {
  8653. firstCaptions.mode = 'showing';
  8654. } else if (firstDesc) {
  8655. firstDesc.mode = 'showing';
  8656. }
  8657. };
  8658. /**
  8659. * Turn display of {@link TextTrack}'s from the current state into the other state.
  8660. * There are only two states:
  8661. * - 'shown'
  8662. * - 'hidden'
  8663. *
  8664. * @listens Player#loadstart
  8665. */
  8666. TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() {
  8667. if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
  8668. this.hide();
  8669. } else {
  8670. this.show();
  8671. }
  8672. };
  8673. /**
  8674. * Create the {@link Component}'s DOM element.
  8675. *
  8676. * @return {Element}
  8677. * The element that was created.
  8678. */
  8679. TextTrackDisplay.prototype.createEl = function createEl() {
  8680. return _Component.prototype.createEl.call(this, 'div', {
  8681. className: 'vjs-text-track-display'
  8682. }, {
  8683. 'aria-live': 'off',
  8684. 'aria-atomic': 'true'
  8685. });
  8686. };
  8687. /**
  8688. * Clear all displayed {@link TextTrack}s.
  8689. */
  8690. TextTrackDisplay.prototype.clearDisplay = function clearDisplay() {
  8691. if (typeof window.WebVTT === 'function') {
  8692. window.WebVTT.processCues(window, [], this.el_);
  8693. }
  8694. };
  8695. /**
  8696. * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
  8697. * a {@link Player#fullscreenchange} is fired.
  8698. *
  8699. * @listens Player#texttrackchange
  8700. * @listens Player#fullscreenchange
  8701. */
  8702. TextTrackDisplay.prototype.updateDisplay = function updateDisplay() {
  8703. var tracks = this.player_.textTracks();
  8704. this.clearDisplay();
  8705. // Track display prioritization model: if multiple tracks are 'showing',
  8706. // display the first 'subtitles' or 'captions' track which is 'showing',
  8707. // otherwise display the first 'descriptions' track which is 'showing'
  8708. var descriptionsTrack = null;
  8709. var captionsSubtitlesTrack = null;
  8710. var i = tracks.length;
  8711. while (i--) {
  8712. var track = tracks[i];
  8713. if (track.mode === 'showing') {
  8714. if (track.kind === 'descriptions') {
  8715. descriptionsTrack = track;
  8716. } else {
  8717. captionsSubtitlesTrack = track;
  8718. }
  8719. }
  8720. }
  8721. if (captionsSubtitlesTrack) {
  8722. if (this.getAttribute('aria-live') !== 'off') {
  8723. this.setAttribute('aria-live', 'off');
  8724. }
  8725. this.updateForTrack(captionsSubtitlesTrack);
  8726. } else if (descriptionsTrack) {
  8727. if (this.getAttribute('aria-live') !== 'assertive') {
  8728. this.setAttribute('aria-live', 'assertive');
  8729. }
  8730. this.updateForTrack(descriptionsTrack);
  8731. }
  8732. };
  8733. /**
  8734. * Add an {@link Texttrack} to to the {@link Tech}s {@link TextTrackList}.
  8735. *
  8736. * @param {TextTrack} track
  8737. * Text track object to be added to the list.
  8738. */
  8739. TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) {
  8740. if (typeof window.WebVTT !== 'function' || !track.activeCues) {
  8741. return;
  8742. }
  8743. var cues = [];
  8744. for (var _i = 0; _i < track.activeCues.length; _i++) {
  8745. cues.push(track.activeCues[_i]);
  8746. }
  8747. window.WebVTT.processCues(window, cues, this.el_);
  8748. if (!this.player_.textTrackSettings) {
  8749. return;
  8750. }
  8751. var overrides = this.player_.textTrackSettings.getValues();
  8752. var i = cues.length;
  8753. while (i--) {
  8754. var cue = cues[i];
  8755. if (!cue) {
  8756. continue;
  8757. }
  8758. var cueDiv = cue.displayState;
  8759. if (overrides.color) {
  8760. cueDiv.firstChild.style.color = overrides.color;
  8761. }
  8762. if (overrides.textOpacity) {
  8763. tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
  8764. }
  8765. if (overrides.backgroundColor) {
  8766. cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
  8767. }
  8768. if (overrides.backgroundOpacity) {
  8769. tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
  8770. }
  8771. if (overrides.windowColor) {
  8772. if (overrides.windowOpacity) {
  8773. tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
  8774. } else {
  8775. cueDiv.style.backgroundColor = overrides.windowColor;
  8776. }
  8777. }
  8778. if (overrides.edgeStyle) {
  8779. if (overrides.edgeStyle === 'dropshadow') {
  8780. cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray;
  8781. } else if (overrides.edgeStyle === 'raised') {
  8782. cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray;
  8783. } else if (overrides.edgeStyle === 'depressed') {
  8784. cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray;
  8785. } else if (overrides.edgeStyle === 'uniform') {
  8786. cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray;
  8787. }
  8788. }
  8789. if (overrides.fontPercent && overrides.fontPercent !== 1) {
  8790. var fontSize = window.parseFloat(cueDiv.style.fontSize);
  8791. cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
  8792. cueDiv.style.height = 'auto';
  8793. cueDiv.style.top = 'auto';
  8794. cueDiv.style.bottom = '2px';
  8795. }
  8796. if (overrides.fontFamily && overrides.fontFamily !== 'default') {
  8797. if (overrides.fontFamily === 'small-caps') {
  8798. cueDiv.firstChild.style.fontVariant = 'small-caps';
  8799. } else {
  8800. cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
  8801. }
  8802. }
  8803. }
  8804. };
  8805. return TextTrackDisplay;
  8806. }(Component);
  8807. Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
  8808. /**
  8809. * @file loading-spinner.js
  8810. */
  8811. /**
  8812. * A loading spinner for use during waiting/loading events.
  8813. *
  8814. * @extends Component
  8815. */
  8816. var LoadingSpinner = function (_Component) {
  8817. inherits(LoadingSpinner, _Component);
  8818. function LoadingSpinner() {
  8819. classCallCheck(this, LoadingSpinner);
  8820. return possibleConstructorReturn(this, _Component.apply(this, arguments));
  8821. }
  8822. /**
  8823. * Create the `LoadingSpinner`s DOM element.
  8824. *
  8825. * @return {Element}
  8826. * The dom element that gets created.
  8827. */
  8828. LoadingSpinner.prototype.createEl = function createEl$$1() {
  8829. var isAudio = this.player_.isAudio();
  8830. var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
  8831. var controlText = createEl('span', {
  8832. className: 'vjs-control-text',
  8833. innerHTML: this.localize('{1} is loading.', [playerType])
  8834. });
  8835. var el = _Component.prototype.createEl.call(this, 'div', {
  8836. className: 'vjs-loading-spinner',
  8837. dir: 'ltr'
  8838. });
  8839. el.appendChild(controlText);
  8840. return el;
  8841. };
  8842. return LoadingSpinner;
  8843. }(Component);
  8844. Component.registerComponent('LoadingSpinner', LoadingSpinner);
  8845. /**
  8846. * @file button.js
  8847. */
  8848. /**
  8849. * Base class for all buttons.
  8850. *
  8851. * @extends ClickableComponent
  8852. */
  8853. var Button = function (_ClickableComponent) {
  8854. inherits(Button, _ClickableComponent);
  8855. function Button() {
  8856. classCallCheck(this, Button);
  8857. return possibleConstructorReturn(this, _ClickableComponent.apply(this, arguments));
  8858. }
  8859. /**
  8860. * Create the `Button`s DOM element.
  8861. *
  8862. * @param {string} [tag="button"]
  8863. * The element's node type. This argument is IGNORED: no matter what
  8864. * is passed, it will always create a `button` element.
  8865. *
  8866. * @param {Object} [props={}]
  8867. * An object of properties that should be set on the element.
  8868. *
  8869. * @param {Object} [attributes={}]
  8870. * An object of attributes that should be set on the element.
  8871. *
  8872. * @return {Element}
  8873. * The element that gets created.
  8874. */
  8875. Button.prototype.createEl = function createEl(tag) {
  8876. var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  8877. var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  8878. tag = 'button';
  8879. props = assign({
  8880. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  8881. className: this.buildCSSClass()
  8882. }, props);
  8883. // Add attributes for button element
  8884. attributes = assign({
  8885. // Necessary since the default button type is "submit"
  8886. type: 'button'
  8887. }, attributes);
  8888. var el = Component.prototype.createEl.call(this, tag, props, attributes);
  8889. this.createControlTextEl(el);
  8890. return el;
  8891. };
  8892. /**
  8893. * Add a child `Component` inside of this `Button`.
  8894. *
  8895. * @param {string|Component} child
  8896. * The name or instance of a child to add.
  8897. *
  8898. * @param {Object} [options={}]
  8899. * The key/value store of options that will get passed to children of
  8900. * the child.
  8901. *
  8902. * @return {Component}
  8903. * The `Component` that gets added as a child. When using a string the
  8904. * `Component` will get created by this process.
  8905. *
  8906. * @deprecated since version 5
  8907. */
  8908. Button.prototype.addChild = function addChild(child) {
  8909. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  8910. var className = this.constructor.name;
  8911. log.warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.');
  8912. // Avoid the error message generated by ClickableComponent's addChild method
  8913. return Component.prototype.addChild.call(this, child, options);
  8914. };
  8915. /**
  8916. * Enable the `Button` element so that it can be activated or clicked. Use this with
  8917. * {@link Button#disable}.
  8918. */
  8919. Button.prototype.enable = function enable() {
  8920. _ClickableComponent.prototype.enable.call(this);
  8921. this.el_.removeAttribute('disabled');
  8922. };
  8923. /**
  8924. * Disable the `Button` element so that it cannot be activated or clicked. Use this with
  8925. * {@link Button#enable}.
  8926. */
  8927. Button.prototype.disable = function disable() {
  8928. _ClickableComponent.prototype.disable.call(this);
  8929. this.el_.setAttribute('disabled', 'disabled');
  8930. };
  8931. /**
  8932. * This gets called when a `Button` has focus and `keydown` is triggered via a key
  8933. * press.
  8934. *
  8935. * @param {EventTarget~Event} event
  8936. * The event that caused this function to get called.
  8937. *
  8938. * @listens keydown
  8939. */
  8940. Button.prototype.handleKeyPress = function handleKeyPress(event) {
  8941. // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button.
  8942. if (event.which === 32 || event.which === 13) {
  8943. return;
  8944. }
  8945. // Pass keypress handling up for unsupported keys
  8946. _ClickableComponent.prototype.handleKeyPress.call(this, event);
  8947. };
  8948. return Button;
  8949. }(ClickableComponent);
  8950. Component.registerComponent('Button', Button);
  8951. /**
  8952. * @file big-play-button.js
  8953. */
  8954. /**
  8955. * The initial play button that shows before the video has played. The hiding of the
  8956. * `BigPlayButton` get done via CSS and `Player` states.
  8957. *
  8958. * @extends Button
  8959. */
  8960. var BigPlayButton = function (_Button) {
  8961. inherits(BigPlayButton, _Button);
  8962. function BigPlayButton(player, options) {
  8963. classCallCheck(this, BigPlayButton);
  8964. var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
  8965. _this.mouseused_ = false;
  8966. _this.on('mousedown', _this.handleMouseDown);
  8967. return _this;
  8968. }
  8969. /**
  8970. * Builds the default DOM `className`.
  8971. *
  8972. * @return {string}
  8973. * The DOM `className` for this object. Always returns 'vjs-big-play-button'.
  8974. */
  8975. BigPlayButton.prototype.buildCSSClass = function buildCSSClass() {
  8976. return 'vjs-big-play-button';
  8977. };
  8978. /**
  8979. * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
  8980. * for more detailed information on what a click can be.
  8981. *
  8982. * @param {EventTarget~Event} event
  8983. * The `keydown`, `tap`, or `click` event that caused this function to be
  8984. * called.
  8985. *
  8986. * @listens tap
  8987. * @listens click
  8988. */
  8989. BigPlayButton.prototype.handleClick = function handleClick(event) {
  8990. var playPromise = this.player_.play();
  8991. // exit early if clicked via the mouse
  8992. if (this.mouseused_ && event.clientX && event.clientY) {
  8993. silencePromise(playPromise);
  8994. return;
  8995. }
  8996. var cb = this.player_.getChild('controlBar');
  8997. var playToggle = cb && cb.getChild('playToggle');
  8998. if (!playToggle) {
  8999. this.player_.focus();
  9000. return;
  9001. }
  9002. var playFocus = function playFocus() {
  9003. return playToggle.focus();
  9004. };
  9005. if (isPromise(playPromise)) {
  9006. playPromise.then(playFocus, function () {});
  9007. } else {
  9008. this.setTimeout(playFocus, 1);
  9009. }
  9010. };
  9011. BigPlayButton.prototype.handleKeyPress = function handleKeyPress(event) {
  9012. this.mouseused_ = false;
  9013. _Button.prototype.handleKeyPress.call(this, event);
  9014. };
  9015. BigPlayButton.prototype.handleMouseDown = function handleMouseDown(event) {
  9016. this.mouseused_ = true;
  9017. };
  9018. return BigPlayButton;
  9019. }(Button);
  9020. /**
  9021. * The text that should display over the `BigPlayButton`s controls. Added to for localization.
  9022. *
  9023. * @type {string}
  9024. * @private
  9025. */
  9026. BigPlayButton.prototype.controlText_ = 'Play Video';
  9027. Component.registerComponent('BigPlayButton', BigPlayButton);
  9028. /**
  9029. * @file close-button.js
  9030. */
  9031. /**
  9032. * The `CloseButton` is a `{@link Button}` that fires a `close` event when
  9033. * it gets clicked.
  9034. *
  9035. * @extends Button
  9036. */
  9037. var CloseButton = function (_Button) {
  9038. inherits(CloseButton, _Button);
  9039. /**
  9040. * Creates an instance of the this class.
  9041. *
  9042. * @param {Player} player
  9043. * The `Player` that this class should be attached to.
  9044. *
  9045. * @param {Object} [options]
  9046. * The key/value store of player options.
  9047. */
  9048. function CloseButton(player, options) {
  9049. classCallCheck(this, CloseButton);
  9050. var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
  9051. _this.controlText(options && options.controlText || _this.localize('Close'));
  9052. return _this;
  9053. }
  9054. /**
  9055. * Builds the default DOM `className`.
  9056. *
  9057. * @return {string}
  9058. * The DOM `className` for this object.
  9059. */
  9060. CloseButton.prototype.buildCSSClass = function buildCSSClass() {
  9061. return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this);
  9062. };
  9063. /**
  9064. * This gets called when a `CloseButton` gets clicked. See
  9065. * {@link ClickableComponent#handleClick} for more information on when this will be
  9066. * triggered
  9067. *
  9068. * @param {EventTarget~Event} event
  9069. * The `keydown`, `tap`, or `click` event that caused this function to be
  9070. * called.
  9071. *
  9072. * @listens tap
  9073. * @listens click
  9074. * @fires CloseButton#close
  9075. */
  9076. CloseButton.prototype.handleClick = function handleClick(event) {
  9077. /**
  9078. * Triggered when the a `CloseButton` is clicked.
  9079. *
  9080. * @event CloseButton#close
  9081. * @type {EventTarget~Event}
  9082. *
  9083. * @property {boolean} [bubbles=false]
  9084. * set to false so that the close event does not
  9085. * bubble up to parents if there is no listener
  9086. */
  9087. this.trigger({ type: 'close', bubbles: false });
  9088. };
  9089. return CloseButton;
  9090. }(Button);
  9091. Component.registerComponent('CloseButton', CloseButton);
  9092. /**
  9093. * @file play-toggle.js
  9094. */
  9095. /**
  9096. * Button to toggle between play and pause.
  9097. *
  9098. * @extends Button
  9099. */
  9100. var PlayToggle = function (_Button) {
  9101. inherits(PlayToggle, _Button);
  9102. /**
  9103. * Creates an instance of this class.
  9104. *
  9105. * @param {Player} player
  9106. * The `Player` that this class should be attached to.
  9107. *
  9108. * @param {Object} [options]
  9109. * The key/value store of player options.
  9110. */
  9111. function PlayToggle(player, options) {
  9112. classCallCheck(this, PlayToggle);
  9113. var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
  9114. _this.on(player, 'play', _this.handlePlay);
  9115. _this.on(player, 'pause', _this.handlePause);
  9116. _this.on(player, 'ended', _this.handleEnded);
  9117. return _this;
  9118. }
  9119. /**
  9120. * Builds the default DOM `className`.
  9121. *
  9122. * @return {string}
  9123. * The DOM `className` for this object.
  9124. */
  9125. PlayToggle.prototype.buildCSSClass = function buildCSSClass() {
  9126. return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this);
  9127. };
  9128. /**
  9129. * This gets called when an `PlayToggle` is "clicked". See
  9130. * {@link ClickableComponent} for more detailed information on what a click can be.
  9131. *
  9132. * @param {EventTarget~Event} [event]
  9133. * The `keydown`, `tap`, or `click` event that caused this function to be
  9134. * called.
  9135. *
  9136. * @listens tap
  9137. * @listens click
  9138. */
  9139. PlayToggle.prototype.handleClick = function handleClick(event) {
  9140. if (this.player_.paused()) {
  9141. this.player_.play();
  9142. } else {
  9143. this.player_.pause();
  9144. }
  9145. };
  9146. /**
  9147. * This gets called once after the video has ended and the user seeks so that
  9148. * we can change the replay button back to a play button.
  9149. *
  9150. * @param {EventTarget~Event} [event]
  9151. * The event that caused this function to run.
  9152. *
  9153. * @listens Player#seeked
  9154. */
  9155. PlayToggle.prototype.handleSeeked = function handleSeeked(event) {
  9156. this.removeClass('vjs-ended');
  9157. if (this.player_.paused()) {
  9158. this.handlePause(event);
  9159. } else {
  9160. this.handlePlay(event);
  9161. }
  9162. };
  9163. /**
  9164. * Add the vjs-playing class to the element so it can change appearance.
  9165. *
  9166. * @param {EventTarget~Event} [event]
  9167. * The event that caused this function to run.
  9168. *
  9169. * @listens Player#play
  9170. */
  9171. PlayToggle.prototype.handlePlay = function handlePlay(event) {
  9172. this.removeClass('vjs-ended');
  9173. this.removeClass('vjs-paused');
  9174. this.addClass('vjs-playing');
  9175. // change the button text to "Pause"
  9176. this.controlText('Pause');
  9177. };
  9178. /**
  9179. * Add the vjs-paused class to the element so it can change appearance.
  9180. *
  9181. * @param {EventTarget~Event} [event]
  9182. * The event that caused this function to run.
  9183. *
  9184. * @listens Player#pause
  9185. */
  9186. PlayToggle.prototype.handlePause = function handlePause(event) {
  9187. this.removeClass('vjs-playing');
  9188. this.addClass('vjs-paused');
  9189. // change the button text to "Play"
  9190. this.controlText('Play');
  9191. };
  9192. /**
  9193. * Add the vjs-ended class to the element so it can change appearance
  9194. *
  9195. * @param {EventTarget~Event} [event]
  9196. * The event that caused this function to run.
  9197. *
  9198. * @listens Player#ended
  9199. */
  9200. PlayToggle.prototype.handleEnded = function handleEnded(event) {
  9201. this.removeClass('vjs-playing');
  9202. this.addClass('vjs-ended');
  9203. // change the button text to "Replay"
  9204. this.controlText('Replay');
  9205. // on the next seek remove the replay button
  9206. this.one(this.player_, 'seeked', this.handleSeeked);
  9207. };
  9208. return PlayToggle;
  9209. }(Button);
  9210. /**
  9211. * The text that should display over the `PlayToggle`s controls. Added for localization.
  9212. *
  9213. * @type {string}
  9214. * @private
  9215. */
  9216. PlayToggle.prototype.controlText_ = 'Play';
  9217. Component.registerComponent('PlayToggle', PlayToggle);
  9218. /**
  9219. * @file format-time.js
  9220. * @module format-time
  9221. */
  9222. /**
  9223. * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in seconds)
  9224. * will force a number of leading zeros to cover the length of the guide.
  9225. *
  9226. * @param {number} seconds
  9227. * Number of seconds to be turned into a string
  9228. *
  9229. * @param {number} guide
  9230. * Number (in seconds) to model the string after
  9231. *
  9232. * @return {string}
  9233. * Time formatted as H:MM:SS or M:SS
  9234. */
  9235. var defaultImplementation = function defaultImplementation(seconds, guide) {
  9236. seconds = seconds < 0 ? 0 : seconds;
  9237. var s = Math.floor(seconds % 60);
  9238. var m = Math.floor(seconds / 60 % 60);
  9239. var h = Math.floor(seconds / 3600);
  9240. var gm = Math.floor(guide / 60 % 60);
  9241. var gh = Math.floor(guide / 3600);
  9242. // handle invalid times
  9243. if (isNaN(seconds) || seconds === Infinity) {
  9244. // '-' is false for all relational operators (e.g. <, >=) so this setting
  9245. // will add the minimum number of fields specified by the guide
  9246. h = m = s = '-';
  9247. }
  9248. // Check if we need to show hours
  9249. h = h > 0 || gh > 0 ? h + ':' : '';
  9250. // If hours are showing, we may need to add a leading zero.
  9251. // Always show at least one digit of minutes.
  9252. m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
  9253. // Check if leading zero is need for seconds
  9254. s = s < 10 ? '0' + s : s;
  9255. return h + m + s;
  9256. };
  9257. var implementation = defaultImplementation;
  9258. /**
  9259. * Replaces the default formatTime implementation with a custom implementation.
  9260. *
  9261. * @param {Function} customImplementation
  9262. * A function which will be used in place of the default formatTime implementation.
  9263. * Will receive the current time in seconds and the guide (in seconds) as arguments.
  9264. */
  9265. function setFormatTime(customImplementation) {
  9266. implementation = customImplementation;
  9267. }
  9268. /**
  9269. * Resets formatTime to the default implementation.
  9270. */
  9271. function resetFormatTime() {
  9272. implementation = defaultImplementation;
  9273. }
  9274. var formatTime = function (seconds) {
  9275. var guide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : seconds;
  9276. return implementation(seconds, guide);
  9277. };
  9278. /**
  9279. * @file time-display.js
  9280. */
  9281. /**
  9282. * Displays the time left in the video
  9283. *
  9284. * @extends Component
  9285. */
  9286. var TimeDisplay = function (_Component) {
  9287. inherits(TimeDisplay, _Component);
  9288. /**
  9289. * Creates an instance of this class.
  9290. *
  9291. * @param {Player} player
  9292. * The `Player` that this class should be attached to.
  9293. *
  9294. * @param {Object} [options]
  9295. * The key/value store of player options.
  9296. */
  9297. function TimeDisplay(player, options) {
  9298. classCallCheck(this, TimeDisplay);
  9299. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  9300. _this.throttledUpdateContent = throttle(bind(_this, _this.updateContent), 25);
  9301. _this.on(player, 'timeupdate', _this.throttledUpdateContent);
  9302. return _this;
  9303. }
  9304. /**
  9305. * Create the `Component`'s DOM element
  9306. *
  9307. * @return {Element}
  9308. * The element that was created.
  9309. */
  9310. TimeDisplay.prototype.createEl = function createEl$$1(plainName) {
  9311. var className = this.buildCSSClass();
  9312. var el = _Component.prototype.createEl.call(this, 'div', {
  9313. className: className + ' vjs-time-control vjs-control',
  9314. innerHTML: '<span class="vjs-control-text">' + this.localize(this.labelText_) + '\xA0</span>'
  9315. });
  9316. this.contentEl_ = createEl('span', {
  9317. className: className + '-display'
  9318. }, {
  9319. // tell screen readers not to automatically read the time as it changes
  9320. 'aria-live': 'off'
  9321. });
  9322. this.updateTextNode_();
  9323. el.appendChild(this.contentEl_);
  9324. return el;
  9325. };
  9326. TimeDisplay.prototype.dispose = function dispose() {
  9327. this.contentEl_ = null;
  9328. this.textNode_ = null;
  9329. _Component.prototype.dispose.call(this);
  9330. };
  9331. /**
  9332. * Updates the "remaining time" text node with new content using the
  9333. * contents of the `formattedTime_` property.
  9334. *
  9335. * @private
  9336. */
  9337. TimeDisplay.prototype.updateTextNode_ = function updateTextNode_() {
  9338. if (!this.contentEl_) {
  9339. return;
  9340. }
  9341. while (this.contentEl_.firstChild) {
  9342. this.contentEl_.removeChild(this.contentEl_.firstChild);
  9343. }
  9344. this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0));
  9345. this.contentEl_.appendChild(this.textNode_);
  9346. };
  9347. /**
  9348. * Generates a formatted time for this component to use in display.
  9349. *
  9350. * @param {number} time
  9351. * A numeric time, in seconds.
  9352. *
  9353. * @return {string}
  9354. * A formatted time
  9355. *
  9356. * @private
  9357. */
  9358. TimeDisplay.prototype.formatTime_ = function formatTime_(time) {
  9359. return formatTime(time);
  9360. };
  9361. /**
  9362. * Updates the time display text node if it has what was passed in changed
  9363. * the formatted time.
  9364. *
  9365. * @param {number} time
  9366. * The time to update to
  9367. *
  9368. * @private
  9369. */
  9370. TimeDisplay.prototype.updateFormattedTime_ = function updateFormattedTime_(time) {
  9371. var formattedTime = this.formatTime_(time);
  9372. if (formattedTime === this.formattedTime_) {
  9373. return;
  9374. }
  9375. this.formattedTime_ = formattedTime;
  9376. this.requestAnimationFrame(this.updateTextNode_);
  9377. };
  9378. /**
  9379. * To be filled out in the child class, should update the displayed time
  9380. * in accordance with the fact that the current time has changed.
  9381. *
  9382. * @param {EventTarget~Event} [event]
  9383. * The `timeupdate` event that caused this to run.
  9384. *
  9385. * @listens Player#timeupdate
  9386. */
  9387. TimeDisplay.prototype.updateContent = function updateContent(event) {};
  9388. return TimeDisplay;
  9389. }(Component);
  9390. /**
  9391. * The text that is added to the `TimeDisplay` for screen reader users.
  9392. *
  9393. * @type {string}
  9394. * @private
  9395. */
  9396. TimeDisplay.prototype.labelText_ = 'Time';
  9397. /**
  9398. * The text that should display over the `TimeDisplay`s controls. Added to for localization.
  9399. *
  9400. * @type {string}
  9401. * @private
  9402. *
  9403. * @deprecated in v7; controlText_ is not used in non-active display Components
  9404. */
  9405. TimeDisplay.prototype.controlText_ = 'Time';
  9406. Component.registerComponent('TimeDisplay', TimeDisplay);
  9407. /**
  9408. * @file current-time-display.js
  9409. */
  9410. /**
  9411. * Displays the current time
  9412. *
  9413. * @extends Component
  9414. */
  9415. var CurrentTimeDisplay = function (_TimeDisplay) {
  9416. inherits(CurrentTimeDisplay, _TimeDisplay);
  9417. /**
  9418. * Creates an instance of this class.
  9419. *
  9420. * @param {Player} player
  9421. * The `Player` that this class should be attached to.
  9422. *
  9423. * @param {Object} [options]
  9424. * The key/value store of player options.
  9425. */
  9426. function CurrentTimeDisplay(player, options) {
  9427. classCallCheck(this, CurrentTimeDisplay);
  9428. var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
  9429. _this.on(player, 'ended', _this.handleEnded);
  9430. return _this;
  9431. }
  9432. /**
  9433. * Builds the default DOM `className`.
  9434. *
  9435. * @return {string}
  9436. * The DOM `className` for this object.
  9437. */
  9438. CurrentTimeDisplay.prototype.buildCSSClass = function buildCSSClass() {
  9439. return 'vjs-current-time';
  9440. };
  9441. /**
  9442. * Update current time display
  9443. *
  9444. * @param {EventTarget~Event} [event]
  9445. * The `timeupdate` event that caused this function to run.
  9446. *
  9447. * @listens Player#timeupdate
  9448. */
  9449. CurrentTimeDisplay.prototype.updateContent = function updateContent(event) {
  9450. // Allows for smooth scrubbing, when player can't keep up.
  9451. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  9452. this.updateFormattedTime_(time);
  9453. };
  9454. /**
  9455. * When the player fires ended there should be no time left. Sadly
  9456. * this is not always the case, lets make it seem like that is the case
  9457. * for users.
  9458. *
  9459. * @param {EventTarget~Event} [event]
  9460. * The `ended` event that caused this to run.
  9461. *
  9462. * @listens Player#ended
  9463. */
  9464. CurrentTimeDisplay.prototype.handleEnded = function handleEnded(event) {
  9465. if (!this.player_.duration()) {
  9466. return;
  9467. }
  9468. this.updateFormattedTime_(this.player_.duration());
  9469. };
  9470. return CurrentTimeDisplay;
  9471. }(TimeDisplay);
  9472. /**
  9473. * The text that is added to the `CurrentTimeDisplay` for screen reader users.
  9474. *
  9475. * @type {string}
  9476. * @private
  9477. */
  9478. CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
  9479. /**
  9480. * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
  9481. *
  9482. * @type {string}
  9483. * @private
  9484. *
  9485. * @deprecated in v7; controlText_ is not used in non-active display Components
  9486. */
  9487. CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
  9488. Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
  9489. /**
  9490. * @file duration-display.js
  9491. */
  9492. /**
  9493. * Displays the duration
  9494. *
  9495. * @extends Component
  9496. */
  9497. var DurationDisplay = function (_TimeDisplay) {
  9498. inherits(DurationDisplay, _TimeDisplay);
  9499. /**
  9500. * Creates an instance of this class.
  9501. *
  9502. * @param {Player} player
  9503. * The `Player` that this class should be attached to.
  9504. *
  9505. * @param {Object} [options]
  9506. * The key/value store of player options.
  9507. */
  9508. function DurationDisplay(player, options) {
  9509. classCallCheck(this, DurationDisplay);
  9510. // we do not want to/need to throttle duration changes,
  9511. // as they should always display the changed duration as
  9512. // it has changed
  9513. var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
  9514. _this.on(player, 'durationchange', _this.updateContent);
  9515. // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
  9516. // listeners could have broken dependent applications/libraries. These
  9517. // can likely be removed for 7.0.
  9518. _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
  9519. return _this;
  9520. }
  9521. /**
  9522. * Builds the default DOM `className`.
  9523. *
  9524. * @return {string}
  9525. * The DOM `className` for this object.
  9526. */
  9527. DurationDisplay.prototype.buildCSSClass = function buildCSSClass() {
  9528. return 'vjs-duration';
  9529. };
  9530. /**
  9531. * Update duration time display.
  9532. *
  9533. * @param {EventTarget~Event} [event]
  9534. * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
  9535. * this function to be called.
  9536. *
  9537. * @listens Player#durationchange
  9538. * @listens Player#timeupdate
  9539. * @listens Player#loadedmetadata
  9540. */
  9541. DurationDisplay.prototype.updateContent = function updateContent(event) {
  9542. var duration = this.player_.duration();
  9543. if (duration && this.duration_ !== duration) {
  9544. this.duration_ = duration;
  9545. this.updateFormattedTime_(duration);
  9546. }
  9547. };
  9548. return DurationDisplay;
  9549. }(TimeDisplay);
  9550. /**
  9551. * The text that is added to the `DurationDisplay` for screen reader users.
  9552. *
  9553. * @type {string}
  9554. * @private
  9555. */
  9556. DurationDisplay.prototype.labelText_ = 'Duration';
  9557. /**
  9558. * The text that should display over the `DurationDisplay`s controls. Added to for localization.
  9559. *
  9560. * @type {string}
  9561. * @private
  9562. *
  9563. * @deprecated in v7; controlText_ is not used in non-active display Components
  9564. */
  9565. DurationDisplay.prototype.controlText_ = 'Duration';
  9566. Component.registerComponent('DurationDisplay', DurationDisplay);
  9567. /**
  9568. * @file time-divider.js
  9569. */
  9570. /**
  9571. * The separator between the current time and duration.
  9572. * Can be hidden if it's not needed in the design.
  9573. *
  9574. * @extends Component
  9575. */
  9576. var TimeDivider = function (_Component) {
  9577. inherits(TimeDivider, _Component);
  9578. function TimeDivider() {
  9579. classCallCheck(this, TimeDivider);
  9580. return possibleConstructorReturn(this, _Component.apply(this, arguments));
  9581. }
  9582. /**
  9583. * Create the component's DOM element
  9584. *
  9585. * @return {Element}
  9586. * The element that was created.
  9587. */
  9588. TimeDivider.prototype.createEl = function createEl() {
  9589. return _Component.prototype.createEl.call(this, 'div', {
  9590. className: 'vjs-time-control vjs-time-divider',
  9591. innerHTML: '<div><span>/</span></div>'
  9592. });
  9593. };
  9594. return TimeDivider;
  9595. }(Component);
  9596. Component.registerComponent('TimeDivider', TimeDivider);
  9597. /**
  9598. * @file remaining-time-display.js
  9599. */
  9600. /**
  9601. * Displays the time left in the video
  9602. *
  9603. * @extends Component
  9604. */
  9605. var RemainingTimeDisplay = function (_TimeDisplay) {
  9606. inherits(RemainingTimeDisplay, _TimeDisplay);
  9607. /**
  9608. * Creates an instance of this class.
  9609. *
  9610. * @param {Player} player
  9611. * The `Player` that this class should be attached to.
  9612. *
  9613. * @param {Object} [options]
  9614. * The key/value store of player options.
  9615. */
  9616. function RemainingTimeDisplay(player, options) {
  9617. classCallCheck(this, RemainingTimeDisplay);
  9618. var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
  9619. _this.on(player, 'durationchange', _this.throttledUpdateContent);
  9620. _this.on(player, 'ended', _this.handleEnded);
  9621. return _this;
  9622. }
  9623. /**
  9624. * Builds the default DOM `className`.
  9625. *
  9626. * @return {string}
  9627. * The DOM `className` for this object.
  9628. */
  9629. RemainingTimeDisplay.prototype.buildCSSClass = function buildCSSClass() {
  9630. return 'vjs-remaining-time';
  9631. };
  9632. /**
  9633. * The remaining time display prefixes numbers with a "minus" character.
  9634. *
  9635. * @param {number} time
  9636. * A numeric time, in seconds.
  9637. *
  9638. * @return {string}
  9639. * A formatted time
  9640. *
  9641. * @private
  9642. */
  9643. RemainingTimeDisplay.prototype.formatTime_ = function formatTime_(time) {
  9644. // TODO: The "-" should be decorative, and not announced by a screen reader
  9645. return '-' + _TimeDisplay.prototype.formatTime_.call(this, time);
  9646. };
  9647. /**
  9648. * Update remaining time display.
  9649. *
  9650. * @param {EventTarget~Event} [event]
  9651. * The `timeupdate` or `durationchange` event that caused this to run.
  9652. *
  9653. * @listens Player#timeupdate
  9654. * @listens Player#durationchange
  9655. */
  9656. RemainingTimeDisplay.prototype.updateContent = function updateContent(event) {
  9657. if (!this.player_.duration()) {
  9658. return;
  9659. }
  9660. // @deprecated We should only use remainingTimeDisplay
  9661. // as of video.js 7
  9662. if (this.player_.remainingTimeDisplay) {
  9663. this.updateFormattedTime_(this.player_.remainingTimeDisplay());
  9664. } else {
  9665. this.updateFormattedTime_(this.player_.remainingTime());
  9666. }
  9667. };
  9668. /**
  9669. * When the player fires ended there should be no time left. Sadly
  9670. * this is not always the case, lets make it seem like that is the case
  9671. * for users.
  9672. *
  9673. * @param {EventTarget~Event} [event]
  9674. * The `ended` event that caused this to run.
  9675. *
  9676. * @listens Player#ended
  9677. */
  9678. RemainingTimeDisplay.prototype.handleEnded = function handleEnded(event) {
  9679. if (!this.player_.duration()) {
  9680. return;
  9681. }
  9682. this.updateFormattedTime_(0);
  9683. };
  9684. return RemainingTimeDisplay;
  9685. }(TimeDisplay);
  9686. /**
  9687. * The text that is added to the `RemainingTimeDisplay` for screen reader users.
  9688. *
  9689. * @type {string}
  9690. * @private
  9691. */
  9692. RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
  9693. /**
  9694. * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
  9695. *
  9696. * @type {string}
  9697. * @private
  9698. *
  9699. * @deprecated in v7; controlText_ is not used in non-active display Components
  9700. */
  9701. RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
  9702. Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
  9703. /**
  9704. * @file live-display.js
  9705. */
  9706. // TODO - Future make it click to snap to live
  9707. /**
  9708. * Displays the live indicator when duration is Infinity.
  9709. *
  9710. * @extends Component
  9711. */
  9712. var LiveDisplay = function (_Component) {
  9713. inherits(LiveDisplay, _Component);
  9714. /**
  9715. * Creates an instance of this class.
  9716. *
  9717. * @param {Player} player
  9718. * The `Player` that this class should be attached to.
  9719. *
  9720. * @param {Object} [options]
  9721. * The key/value store of player options.
  9722. */
  9723. function LiveDisplay(player, options) {
  9724. classCallCheck(this, LiveDisplay);
  9725. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  9726. _this.updateShowing();
  9727. _this.on(_this.player(), 'durationchange', _this.updateShowing);
  9728. return _this;
  9729. }
  9730. /**
  9731. * Create the `Component`'s DOM element
  9732. *
  9733. * @return {Element}
  9734. * The element that was created.
  9735. */
  9736. LiveDisplay.prototype.createEl = function createEl$$1() {
  9737. var el = _Component.prototype.createEl.call(this, 'div', {
  9738. className: 'vjs-live-control vjs-control'
  9739. });
  9740. this.contentEl_ = createEl('div', {
  9741. className: 'vjs-live-display',
  9742. innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '\xA0</span>' + this.localize('LIVE')
  9743. }, {
  9744. 'aria-live': 'off'
  9745. });
  9746. el.appendChild(this.contentEl_);
  9747. return el;
  9748. };
  9749. LiveDisplay.prototype.dispose = function dispose() {
  9750. this.contentEl_ = null;
  9751. _Component.prototype.dispose.call(this);
  9752. };
  9753. /**
  9754. * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
  9755. * it accordingly
  9756. *
  9757. * @param {EventTarget~Event} [event]
  9758. * The {@link Player#durationchange} event that caused this function to run.
  9759. *
  9760. * @listens Player#durationchange
  9761. */
  9762. LiveDisplay.prototype.updateShowing = function updateShowing(event) {
  9763. if (this.player().duration() === Infinity) {
  9764. this.show();
  9765. } else {
  9766. this.hide();
  9767. }
  9768. };
  9769. return LiveDisplay;
  9770. }(Component);
  9771. Component.registerComponent('LiveDisplay', LiveDisplay);
  9772. /**
  9773. * @file slider.js
  9774. */
  9775. /**
  9776. * The base functionality for a slider. Can be vertical or horizontal.
  9777. * For instance the volume bar or the seek bar on a video is a slider.
  9778. *
  9779. * @extends Component
  9780. */
  9781. var Slider = function (_Component) {
  9782. inherits(Slider, _Component);
  9783. /**
  9784. * Create an instance of this class
  9785. *
  9786. * @param {Player} player
  9787. * The `Player` that this class should be attached to.
  9788. *
  9789. * @param {Object} [options]
  9790. * The key/value store of player options.
  9791. */
  9792. function Slider(player, options) {
  9793. classCallCheck(this, Slider);
  9794. // Set property names to bar to match with the child Slider class is looking for
  9795. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  9796. _this.bar = _this.getChild(_this.options_.barName);
  9797. // Set a horizontal or vertical class on the slider depending on the slider type
  9798. _this.vertical(!!_this.options_.vertical);
  9799. _this.enable();
  9800. return _this;
  9801. }
  9802. /**
  9803. * Are controls are currently enabled for this slider or not.
  9804. *
  9805. * @return {boolean}
  9806. * true if controls are enabled, false otherwise
  9807. */
  9808. Slider.prototype.enabled = function enabled() {
  9809. return this.enabled_;
  9810. };
  9811. /**
  9812. * Enable controls for this slider if they are disabled
  9813. */
  9814. Slider.prototype.enable = function enable() {
  9815. if (this.enabled()) {
  9816. return;
  9817. }
  9818. this.on('mousedown', this.handleMouseDown);
  9819. this.on('touchstart', this.handleMouseDown);
  9820. this.on('focus', this.handleFocus);
  9821. this.on('blur', this.handleBlur);
  9822. this.on('click', this.handleClick);
  9823. this.on(this.player_, 'controlsvisible', this.update);
  9824. if (this.playerEvent) {
  9825. this.on(this.player_, this.playerEvent, this.update);
  9826. }
  9827. this.removeClass('disabled');
  9828. this.setAttribute('tabindex', 0);
  9829. this.enabled_ = true;
  9830. };
  9831. /**
  9832. * Disable controls for this slider if they are enabled
  9833. */
  9834. Slider.prototype.disable = function disable() {
  9835. if (!this.enabled()) {
  9836. return;
  9837. }
  9838. var doc = this.bar.el_.ownerDocument;
  9839. this.off('mousedown', this.handleMouseDown);
  9840. this.off('touchstart', this.handleMouseDown);
  9841. this.off('focus', this.handleFocus);
  9842. this.off('blur', this.handleBlur);
  9843. this.off('click', this.handleClick);
  9844. this.off(this.player_, 'controlsvisible', this.update);
  9845. this.off(doc, 'mousemove', this.handleMouseMove);
  9846. this.off(doc, 'mouseup', this.handleMouseUp);
  9847. this.off(doc, 'touchmove', this.handleMouseMove);
  9848. this.off(doc, 'touchend', this.handleMouseUp);
  9849. this.removeAttribute('tabindex');
  9850. this.addClass('disabled');
  9851. if (this.playerEvent) {
  9852. this.off(this.player_, this.playerEvent, this.update);
  9853. }
  9854. this.enabled_ = false;
  9855. };
  9856. /**
  9857. * Create the `Button`s DOM element.
  9858. *
  9859. * @param {string} type
  9860. * Type of element to create.
  9861. *
  9862. * @param {Object} [props={}]
  9863. * List of properties in Object form.
  9864. *
  9865. * @param {Object} [attributes={}]
  9866. * list of attributes in Object form.
  9867. *
  9868. * @return {Element}
  9869. * The element that gets created.
  9870. */
  9871. Slider.prototype.createEl = function createEl$$1(type) {
  9872. var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  9873. var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  9874. // Add the slider element class to all sub classes
  9875. props.className = props.className + ' vjs-slider';
  9876. props = assign({
  9877. tabIndex: 0
  9878. }, props);
  9879. attributes = assign({
  9880. 'role': 'slider',
  9881. 'aria-valuenow': 0,
  9882. 'aria-valuemin': 0,
  9883. 'aria-valuemax': 100,
  9884. 'tabIndex': 0
  9885. }, attributes);
  9886. return _Component.prototype.createEl.call(this, type, props, attributes);
  9887. };
  9888. /**
  9889. * Handle `mousedown` or `touchstart` events on the `Slider`.
  9890. *
  9891. * @param {EventTarget~Event} event
  9892. * `mousedown` or `touchstart` event that triggered this function
  9893. *
  9894. * @listens mousedown
  9895. * @listens touchstart
  9896. * @fires Slider#slideractive
  9897. */
  9898. Slider.prototype.handleMouseDown = function handleMouseDown(event) {
  9899. var doc = this.bar.el_.ownerDocument;
  9900. if (event.type === 'mousedown') {
  9901. event.preventDefault();
  9902. }
  9903. // Do not call preventDefault() on touchstart in Chrome
  9904. // to avoid console warnings. Use a 'touch-action: none' style
  9905. // instead to prevent unintented scrolling.
  9906. // https://developers.google.com/web/updates/2017/01/scrolling-intervention
  9907. if (event.type === 'touchstart' && !IS_CHROME) {
  9908. event.preventDefault();
  9909. }
  9910. blockTextSelection();
  9911. this.addClass('vjs-sliding');
  9912. /**
  9913. * Triggered when the slider is in an active state
  9914. *
  9915. * @event Slider#slideractive
  9916. * @type {EventTarget~Event}
  9917. */
  9918. this.trigger('slideractive');
  9919. this.on(doc, 'mousemove', this.handleMouseMove);
  9920. this.on(doc, 'mouseup', this.handleMouseUp);
  9921. this.on(doc, 'touchmove', this.handleMouseMove);
  9922. this.on(doc, 'touchend', this.handleMouseUp);
  9923. this.handleMouseMove(event);
  9924. };
  9925. /**
  9926. * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
  9927. * The `mousemove` and `touchmove` events will only only trigger this function during
  9928. * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
  9929. * {@link Slider#handleMouseUp}.
  9930. *
  9931. * @param {EventTarget~Event} event
  9932. * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
  9933. * this function
  9934. *
  9935. * @listens mousemove
  9936. * @listens touchmove
  9937. */
  9938. Slider.prototype.handleMouseMove = function handleMouseMove(event) {};
  9939. /**
  9940. * Handle `mouseup` or `touchend` events on the `Slider`.
  9941. *
  9942. * @param {EventTarget~Event} event
  9943. * `mouseup` or `touchend` event that triggered this function.
  9944. *
  9945. * @listens touchend
  9946. * @listens mouseup
  9947. * @fires Slider#sliderinactive
  9948. */
  9949. Slider.prototype.handleMouseUp = function handleMouseUp() {
  9950. var doc = this.bar.el_.ownerDocument;
  9951. unblockTextSelection();
  9952. this.removeClass('vjs-sliding');
  9953. /**
  9954. * Triggered when the slider is no longer in an active state.
  9955. *
  9956. * @event Slider#sliderinactive
  9957. * @type {EventTarget~Event}
  9958. */
  9959. this.trigger('sliderinactive');
  9960. this.off(doc, 'mousemove', this.handleMouseMove);
  9961. this.off(doc, 'mouseup', this.handleMouseUp);
  9962. this.off(doc, 'touchmove', this.handleMouseMove);
  9963. this.off(doc, 'touchend', this.handleMouseUp);
  9964. this.update();
  9965. };
  9966. /**
  9967. * Update the progress bar of the `Slider`.
  9968. *
  9969. * @returns {number}
  9970. * The percentage of progress the progress bar represents as a
  9971. * number from 0 to 1.
  9972. */
  9973. Slider.prototype.update = function update() {
  9974. // In VolumeBar init we have a setTimeout for update that pops and update
  9975. // to the end of the execution stack. The player is destroyed before then
  9976. // update will cause an error
  9977. if (!this.el_) {
  9978. return;
  9979. }
  9980. // If scrubbing, we could use a cached value to make the handle keep up
  9981. // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
  9982. // some flash players are slow, so we might want to utilize this later.
  9983. // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
  9984. var progress = this.getPercent();
  9985. var bar = this.bar;
  9986. // If there's no bar...
  9987. if (!bar) {
  9988. return;
  9989. }
  9990. // Protect against no duration and other division issues
  9991. if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
  9992. progress = 0;
  9993. }
  9994. // Convert to a percentage for setting
  9995. var percentage = (progress * 100).toFixed(2) + '%';
  9996. var style = bar.el().style;
  9997. // Set the new bar width or height
  9998. if (this.vertical()) {
  9999. style.height = percentage;
  10000. } else {
  10001. style.width = percentage;
  10002. }
  10003. return progress;
  10004. };
  10005. /**
  10006. * Calculate distance for slider
  10007. *
  10008. * @param {EventTarget~Event} event
  10009. * The event that caused this function to run.
  10010. *
  10011. * @return {number}
  10012. * The current position of the Slider.
  10013. * - postition.x for vertical `Slider`s
  10014. * - postition.y for horizontal `Slider`s
  10015. */
  10016. Slider.prototype.calculateDistance = function calculateDistance(event) {
  10017. var position = getPointerPosition(this.el_, event);
  10018. if (this.vertical()) {
  10019. return position.y;
  10020. }
  10021. return position.x;
  10022. };
  10023. /**
  10024. * Handle a `focus` event on this `Slider`.
  10025. *
  10026. * @param {EventTarget~Event} event
  10027. * The `focus` event that caused this function to run.
  10028. *
  10029. * @listens focus
  10030. */
  10031. Slider.prototype.handleFocus = function handleFocus() {
  10032. this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  10033. };
  10034. /**
  10035. * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
  10036. * arrow keys. This function will only be called when the slider has focus. See
  10037. * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
  10038. *
  10039. * @param {EventTarget~Event} event
  10040. * the `keydown` event that caused this function to run.
  10041. *
  10042. * @listens keydown
  10043. */
  10044. Slider.prototype.handleKeyPress = function handleKeyPress(event) {
  10045. // Left and Down Arrows
  10046. if (event.which === 37 || event.which === 40) {
  10047. event.preventDefault();
  10048. this.stepBack();
  10049. // Up and Right Arrows
  10050. } else if (event.which === 38 || event.which === 39) {
  10051. event.preventDefault();
  10052. this.stepForward();
  10053. }
  10054. };
  10055. /**
  10056. * Handle a `blur` event on this `Slider`.
  10057. *
  10058. * @param {EventTarget~Event} event
  10059. * The `blur` event that caused this function to run.
  10060. *
  10061. * @listens blur
  10062. */
  10063. Slider.prototype.handleBlur = function handleBlur() {
  10064. this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  10065. };
  10066. /**
  10067. * Listener for click events on slider, used to prevent clicks
  10068. * from bubbling up to parent elements like button menus.
  10069. *
  10070. * @param {Object} event
  10071. * Event that caused this object to run
  10072. */
  10073. Slider.prototype.handleClick = function handleClick(event) {
  10074. event.stopImmediatePropagation();
  10075. event.preventDefault();
  10076. };
  10077. /**
  10078. * Get/set if slider is horizontal for vertical
  10079. *
  10080. * @param {boolean} [bool]
  10081. * - true if slider is vertical,
  10082. * - false is horizontal
  10083. *
  10084. * @return {boolean}
  10085. * - true if slider is vertical, and getting
  10086. * - false if the slider is horizontal, and getting
  10087. */
  10088. Slider.prototype.vertical = function vertical(bool) {
  10089. if (bool === undefined) {
  10090. return this.vertical_ || false;
  10091. }
  10092. this.vertical_ = !!bool;
  10093. if (this.vertical_) {
  10094. this.addClass('vjs-slider-vertical');
  10095. } else {
  10096. this.addClass('vjs-slider-horizontal');
  10097. }
  10098. };
  10099. return Slider;
  10100. }(Component);
  10101. Component.registerComponent('Slider', Slider);
  10102. /**
  10103. * @file load-progress-bar.js
  10104. */
  10105. /**
  10106. * Shows loading progress
  10107. *
  10108. * @extends Component
  10109. */
  10110. var LoadProgressBar = function (_Component) {
  10111. inherits(LoadProgressBar, _Component);
  10112. /**
  10113. * Creates an instance of this class.
  10114. *
  10115. * @param {Player} player
  10116. * The `Player` that this class should be attached to.
  10117. *
  10118. * @param {Object} [options]
  10119. * The key/value store of player options.
  10120. */
  10121. function LoadProgressBar(player, options) {
  10122. classCallCheck(this, LoadProgressBar);
  10123. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  10124. _this.partEls_ = [];
  10125. _this.on(player, 'progress', _this.update);
  10126. return _this;
  10127. }
  10128. /**
  10129. * Create the `Component`'s DOM element
  10130. *
  10131. * @return {Element}
  10132. * The element that was created.
  10133. */
  10134. LoadProgressBar.prototype.createEl = function createEl$$1() {
  10135. return _Component.prototype.createEl.call(this, 'div', {
  10136. className: 'vjs-load-progress',
  10137. innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>'
  10138. });
  10139. };
  10140. LoadProgressBar.prototype.dispose = function dispose() {
  10141. this.partEls_ = null;
  10142. _Component.prototype.dispose.call(this);
  10143. };
  10144. /**
  10145. * Update progress bar
  10146. *
  10147. * @param {EventTarget~Event} [event]
  10148. * The `progress` event that caused this function to run.
  10149. *
  10150. * @listens Player#progress
  10151. */
  10152. LoadProgressBar.prototype.update = function update(event) {
  10153. var buffered = this.player_.buffered();
  10154. var duration = this.player_.duration();
  10155. var bufferedEnd = this.player_.bufferedEnd();
  10156. var children = this.partEls_;
  10157. // get the percent width of a time compared to the total end
  10158. var percentify = function percentify(time, end) {
  10159. // no NaN
  10160. var percent = time / end || 0;
  10161. return (percent >= 1 ? 1 : percent) * 100 + '%';
  10162. };
  10163. // update the width of the progress bar
  10164. this.el_.style.width = percentify(bufferedEnd, duration);
  10165. // add child elements to represent the individual buffered time ranges
  10166. for (var i = 0; i < buffered.length; i++) {
  10167. var start = buffered.start(i);
  10168. var end = buffered.end(i);
  10169. var part = children[i];
  10170. if (!part) {
  10171. part = this.el_.appendChild(createEl());
  10172. children[i] = part;
  10173. }
  10174. // set the percent based on the width of the progress bar (bufferedEnd)
  10175. part.style.left = percentify(start, bufferedEnd);
  10176. part.style.width = percentify(end - start, bufferedEnd);
  10177. }
  10178. // remove unused buffered range elements
  10179. for (var _i = children.length; _i > buffered.length; _i--) {
  10180. this.el_.removeChild(children[_i - 1]);
  10181. }
  10182. children.length = buffered.length;
  10183. };
  10184. return LoadProgressBar;
  10185. }(Component);
  10186. Component.registerComponent('LoadProgressBar', LoadProgressBar);
  10187. /**
  10188. * @file time-tooltip.js
  10189. */
  10190. /**
  10191. * Time tooltips display a time above the progress bar.
  10192. *
  10193. * @extends Component
  10194. */
  10195. var TimeTooltip = function (_Component) {
  10196. inherits(TimeTooltip, _Component);
  10197. function TimeTooltip() {
  10198. classCallCheck(this, TimeTooltip);
  10199. return possibleConstructorReturn(this, _Component.apply(this, arguments));
  10200. }
  10201. /**
  10202. * Create the time tooltip DOM element
  10203. *
  10204. * @return {Element}
  10205. * The element that was created.
  10206. */
  10207. TimeTooltip.prototype.createEl = function createEl$$1() {
  10208. return _Component.prototype.createEl.call(this, 'div', {
  10209. className: 'vjs-time-tooltip'
  10210. });
  10211. };
  10212. /**
  10213. * Updates the position of the time tooltip relative to the `SeekBar`.
  10214. *
  10215. * @param {Object} seekBarRect
  10216. * The `ClientRect` for the {@link SeekBar} element.
  10217. *
  10218. * @param {number} seekBarPoint
  10219. * A number from 0 to 1, representing a horizontal reference point
  10220. * from the left edge of the {@link SeekBar}
  10221. */
  10222. TimeTooltip.prototype.update = function update(seekBarRect, seekBarPoint, content) {
  10223. var tooltipRect = getBoundingClientRect(this.el_);
  10224. var playerRect = getBoundingClientRect(this.player_.el());
  10225. var seekBarPointPx = seekBarRect.width * seekBarPoint;
  10226. // do nothing if either rect isn't available
  10227. // for example, if the player isn't in the DOM for testing
  10228. if (!playerRect || !tooltipRect) {
  10229. return;
  10230. }
  10231. // This is the space left of the `seekBarPoint` available within the bounds
  10232. // of the player. We calculate any gap between the left edge of the player
  10233. // and the left edge of the `SeekBar` and add the number of pixels in the
  10234. // `SeekBar` before hitting the `seekBarPoint`
  10235. var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx;
  10236. // This is the space right of the `seekBarPoint` available within the bounds
  10237. // of the player. We calculate the number of pixels from the `seekBarPoint`
  10238. // to the right edge of the `SeekBar` and add to that any gap between the
  10239. // right edge of the `SeekBar` and the player.
  10240. var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right);
  10241. // This is the number of pixels by which the tooltip will need to be pulled
  10242. // further to the right to center it over the `seekBarPoint`.
  10243. var pullTooltipBy = tooltipRect.width / 2;
  10244. // Adjust the `pullTooltipBy` distance to the left or right depending on
  10245. // the results of the space calculations above.
  10246. if (spaceLeftOfPoint < pullTooltipBy) {
  10247. pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
  10248. } else if (spaceRightOfPoint < pullTooltipBy) {
  10249. pullTooltipBy = spaceRightOfPoint;
  10250. }
  10251. // Due to the imprecision of decimal/ratio based calculations and varying
  10252. // rounding behaviors, there are cases where the spacing adjustment is off
  10253. // by a pixel or two. This adds insurance to these calculations.
  10254. if (pullTooltipBy < 0) {
  10255. pullTooltipBy = 0;
  10256. } else if (pullTooltipBy > tooltipRect.width) {
  10257. pullTooltipBy = tooltipRect.width;
  10258. }
  10259. this.el_.style.right = '-' + pullTooltipBy + 'px';
  10260. textContent(this.el_, content);
  10261. };
  10262. return TimeTooltip;
  10263. }(Component);
  10264. Component.registerComponent('TimeTooltip', TimeTooltip);
  10265. /**
  10266. * @file play-progress-bar.js
  10267. */
  10268. /**
  10269. * Used by {@link SeekBar} to display media playback progress as part of the
  10270. * {@link ProgressControl}.
  10271. *
  10272. * @extends Component
  10273. */
  10274. var PlayProgressBar = function (_Component) {
  10275. inherits(PlayProgressBar, _Component);
  10276. function PlayProgressBar() {
  10277. classCallCheck(this, PlayProgressBar);
  10278. return possibleConstructorReturn(this, _Component.apply(this, arguments));
  10279. }
  10280. /**
  10281. * Create the the DOM element for this class.
  10282. *
  10283. * @return {Element}
  10284. * The element that was created.
  10285. */
  10286. PlayProgressBar.prototype.createEl = function createEl() {
  10287. return _Component.prototype.createEl.call(this, 'div', {
  10288. className: 'vjs-play-progress vjs-slider-bar',
  10289. innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>'
  10290. });
  10291. };
  10292. /**
  10293. * Enqueues updates to its own DOM as well as the DOM of its
  10294. * {@link TimeTooltip} child.
  10295. *
  10296. * @param {Object} seekBarRect
  10297. * The `ClientRect` for the {@link SeekBar} element.
  10298. *
  10299. * @param {number} seekBarPoint
  10300. * A number from 0 to 1, representing a horizontal reference point
  10301. * from the left edge of the {@link SeekBar}
  10302. */
  10303. PlayProgressBar.prototype.update = function update(seekBarRect, seekBarPoint) {
  10304. var _this2 = this;
  10305. // If there is an existing rAF ID, cancel it so we don't over-queue.
  10306. if (this.rafId_) {
  10307. this.cancelAnimationFrame(this.rafId_);
  10308. }
  10309. this.rafId_ = this.requestAnimationFrame(function () {
  10310. var time = _this2.player_.scrubbing() ? _this2.player_.getCache().currentTime : _this2.player_.currentTime();
  10311. var content = formatTime(time, _this2.player_.duration());
  10312. var timeTooltip = _this2.getChild('timeTooltip');
  10313. if (timeTooltip) {
  10314. timeTooltip.update(seekBarRect, seekBarPoint, content);
  10315. }
  10316. });
  10317. };
  10318. return PlayProgressBar;
  10319. }(Component);
  10320. /**
  10321. * Default options for {@link PlayProgressBar}.
  10322. *
  10323. * @type {Object}
  10324. * @private
  10325. */
  10326. PlayProgressBar.prototype.options_ = {
  10327. children: []
  10328. };
  10329. // Time tooltips should not be added to a player on mobile devices or IE8
  10330. if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) {
  10331. PlayProgressBar.prototype.options_.children.push('timeTooltip');
  10332. }
  10333. Component.registerComponent('PlayProgressBar', PlayProgressBar);
  10334. /**
  10335. * @file mouse-time-display.js
  10336. */
  10337. /**
  10338. * The {@link MouseTimeDisplay} component tracks mouse movement over the
  10339. * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
  10340. * indicating the time which is represented by a given point in the
  10341. * {@link ProgressControl}.
  10342. *
  10343. * @extends Component
  10344. */
  10345. var MouseTimeDisplay = function (_Component) {
  10346. inherits(MouseTimeDisplay, _Component);
  10347. /**
  10348. * Creates an instance of this class.
  10349. *
  10350. * @param {Player} player
  10351. * The {@link Player} that this class should be attached to.
  10352. *
  10353. * @param {Object} [options]
  10354. * The key/value store of player options.
  10355. */
  10356. function MouseTimeDisplay(player, options) {
  10357. classCallCheck(this, MouseTimeDisplay);
  10358. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  10359. _this.update = throttle(bind(_this, _this.update), 25);
  10360. return _this;
  10361. }
  10362. /**
  10363. * Create the DOM element for this class.
  10364. *
  10365. * @return {Element}
  10366. * The element that was created.
  10367. */
  10368. MouseTimeDisplay.prototype.createEl = function createEl() {
  10369. return _Component.prototype.createEl.call(this, 'div', {
  10370. className: 'vjs-mouse-display'
  10371. });
  10372. };
  10373. /**
  10374. * Enqueues updates to its own DOM as well as the DOM of its
  10375. * {@link TimeTooltip} child.
  10376. *
  10377. * @param {Object} seekBarRect
  10378. * The `ClientRect` for the {@link SeekBar} element.
  10379. *
  10380. * @param {number} seekBarPoint
  10381. * A number from 0 to 1, representing a horizontal reference point
  10382. * from the left edge of the {@link SeekBar}
  10383. */
  10384. MouseTimeDisplay.prototype.update = function update(seekBarRect, seekBarPoint) {
  10385. var _this2 = this;
  10386. // If there is an existing rAF ID, cancel it so we don't over-queue.
  10387. if (this.rafId_) {
  10388. this.cancelAnimationFrame(this.rafId_);
  10389. }
  10390. this.rafId_ = this.requestAnimationFrame(function () {
  10391. var duration = _this2.player_.duration();
  10392. var content = formatTime(seekBarPoint * duration, duration);
  10393. _this2.el_.style.left = seekBarRect.width * seekBarPoint + 'px';
  10394. _this2.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content);
  10395. });
  10396. };
  10397. return MouseTimeDisplay;
  10398. }(Component);
  10399. /**
  10400. * Default options for `MouseTimeDisplay`
  10401. *
  10402. * @type {Object}
  10403. * @private
  10404. */
  10405. MouseTimeDisplay.prototype.options_ = {
  10406. children: ['timeTooltip']
  10407. };
  10408. Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
  10409. /**
  10410. * @file seek-bar.js
  10411. */
  10412. // The number of seconds the `step*` functions move the timeline.
  10413. var STEP_SECONDS = 5;
  10414. // The interval at which the bar should update as it progresses.
  10415. var UPDATE_REFRESH_INTERVAL = 30;
  10416. /**
  10417. * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
  10418. * as its `bar`.
  10419. *
  10420. * @extends Slider
  10421. */
  10422. var SeekBar = function (_Slider) {
  10423. inherits(SeekBar, _Slider);
  10424. /**
  10425. * Creates an instance of this class.
  10426. *
  10427. * @param {Player} player
  10428. * The `Player` that this class should be attached to.
  10429. *
  10430. * @param {Object} [options]
  10431. * The key/value store of player options.
  10432. */
  10433. function SeekBar(player, options) {
  10434. classCallCheck(this, SeekBar);
  10435. var _this = possibleConstructorReturn(this, _Slider.call(this, player, options));
  10436. _this.setEventHandlers_();
  10437. return _this;
  10438. }
  10439. /**
  10440. * Sets the event handlers
  10441. *
  10442. * @private
  10443. */
  10444. SeekBar.prototype.setEventHandlers_ = function setEventHandlers_() {
  10445. var _this2 = this;
  10446. this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
  10447. this.on(this.player_, 'timeupdate', this.update);
  10448. this.on(this.player_, 'ended', this.handleEnded);
  10449. // when playing, let's ensure we smoothly update the play progress bar
  10450. // via an interval
  10451. this.updateInterval = null;
  10452. this.on(this.player_, ['playing'], function () {
  10453. _this2.clearInterval(_this2.updateInterval);
  10454. _this2.updateInterval = _this2.setInterval(function () {
  10455. _this2.requestAnimationFrame(function () {
  10456. _this2.update();
  10457. });
  10458. }, UPDATE_REFRESH_INTERVAL);
  10459. });
  10460. this.on(this.player_, ['ended', 'pause', 'waiting'], function () {
  10461. _this2.clearInterval(_this2.updateInterval);
  10462. });
  10463. this.on(this.player_, ['timeupdate', 'ended'], this.update);
  10464. };
  10465. /**
  10466. * Create the `Component`'s DOM element
  10467. *
  10468. * @return {Element}
  10469. * The element that was created.
  10470. */
  10471. SeekBar.prototype.createEl = function createEl$$1() {
  10472. return _Slider.prototype.createEl.call(this, 'div', {
  10473. className: 'vjs-progress-holder'
  10474. }, {
  10475. 'aria-label': this.localize('Progress Bar')
  10476. });
  10477. };
  10478. /**
  10479. * This function updates the play progress bar and accessiblity
  10480. * attributes to whatever is passed in.
  10481. *
  10482. * @param {number} currentTime
  10483. * The currentTime value that should be used for accessiblity
  10484. *
  10485. * @param {number} percent
  10486. * The percentage as a decimal that the bar should be filled from 0-1.
  10487. *
  10488. * @private
  10489. */
  10490. SeekBar.prototype.update_ = function update_(currentTime, percent) {
  10491. var duration = this.player_.duration();
  10492. // machine readable value of progress bar (percentage complete)
  10493. this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
  10494. // human readable value of progress bar (time complete)
  10495. this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
  10496. // Update the `PlayProgressBar`.
  10497. this.bar.update(getBoundingClientRect(this.el_), percent);
  10498. };
  10499. /**
  10500. * Update the seek bar's UI.
  10501. *
  10502. * @param {EventTarget~Event} [event]
  10503. * The `timeupdate` or `ended` event that caused this to run.
  10504. *
  10505. * @listens Player#timeupdate
  10506. *
  10507. * @returns {number}
  10508. * The current percent at a number from 0-1
  10509. */
  10510. SeekBar.prototype.update = function update(event) {
  10511. var percent = _Slider.prototype.update.call(this);
  10512. this.update_(this.getCurrentTime_(), percent);
  10513. return percent;
  10514. };
  10515. /**
  10516. * Get the value of current time but allows for smooth scrubbing,
  10517. * when player can't keep up.
  10518. *
  10519. * @return {number}
  10520. * The current time value to display
  10521. *
  10522. * @private
  10523. */
  10524. SeekBar.prototype.getCurrentTime_ = function getCurrentTime_() {
  10525. return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  10526. };
  10527. /**
  10528. * We want the seek bar to be full on ended
  10529. * no matter what the actual internal values are. so we force it.
  10530. *
  10531. * @param {EventTarget~Event} [event]
  10532. * The `timeupdate` or `ended` event that caused this to run.
  10533. *
  10534. * @listens Player#ended
  10535. */
  10536. SeekBar.prototype.handleEnded = function handleEnded(event) {
  10537. this.update_(this.player_.duration(), 1);
  10538. };
  10539. /**
  10540. * Get the percentage of media played so far.
  10541. *
  10542. * @return {number}
  10543. * The percentage of media played so far (0 to 1).
  10544. */
  10545. SeekBar.prototype.getPercent = function getPercent() {
  10546. var percent = this.getCurrentTime_() / this.player_.duration();
  10547. return percent >= 1 ? 1 : percent;
  10548. };
  10549. /**
  10550. * Handle mouse down on seek bar
  10551. *
  10552. * @param {EventTarget~Event} event
  10553. * The `mousedown` event that caused this to run.
  10554. *
  10555. * @listens mousedown
  10556. */
  10557. SeekBar.prototype.handleMouseDown = function handleMouseDown(event) {
  10558. if (!isSingleLeftClick(event)) {
  10559. return;
  10560. }
  10561. // Stop event propagation to prevent double fire in progress-control.js
  10562. event.stopPropagation();
  10563. this.player_.scrubbing(true);
  10564. this.videoWasPlaying = !this.player_.paused();
  10565. this.player_.pause();
  10566. _Slider.prototype.handleMouseDown.call(this, event);
  10567. };
  10568. /**
  10569. * Handle mouse move on seek bar
  10570. *
  10571. * @param {EventTarget~Event} event
  10572. * The `mousemove` event that caused this to run.
  10573. *
  10574. * @listens mousemove
  10575. */
  10576. SeekBar.prototype.handleMouseMove = function handleMouseMove(event) {
  10577. if (!isSingleLeftClick(event)) {
  10578. return;
  10579. }
  10580. var newTime = this.calculateDistance(event) * this.player_.duration();
  10581. // Don't let video end while scrubbing.
  10582. if (newTime === this.player_.duration()) {
  10583. newTime = newTime - 0.1;
  10584. }
  10585. // Set new time (tell player to seek to new time)
  10586. this.player_.currentTime(newTime);
  10587. };
  10588. SeekBar.prototype.enable = function enable() {
  10589. _Slider.prototype.enable.call(this);
  10590. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  10591. if (!mouseTimeDisplay) {
  10592. return;
  10593. }
  10594. mouseTimeDisplay.show();
  10595. };
  10596. SeekBar.prototype.disable = function disable() {
  10597. _Slider.prototype.disable.call(this);
  10598. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  10599. if (!mouseTimeDisplay) {
  10600. return;
  10601. }
  10602. mouseTimeDisplay.hide();
  10603. };
  10604. /**
  10605. * Handle mouse up on seek bar
  10606. *
  10607. * @param {EventTarget~Event} event
  10608. * The `mouseup` event that caused this to run.
  10609. *
  10610. * @listens mouseup
  10611. */
  10612. SeekBar.prototype.handleMouseUp = function handleMouseUp(event) {
  10613. _Slider.prototype.handleMouseUp.call(this, event);
  10614. // Stop event propagation to prevent double fire in progress-control.js
  10615. if (event) {
  10616. event.stopPropagation();
  10617. }
  10618. this.player_.scrubbing(false);
  10619. /**
  10620. * Trigger timeupdate because we're done seeking and the time has changed.
  10621. * This is particularly useful for if the player is paused to time the time displays.
  10622. *
  10623. * @event Tech#timeupdate
  10624. * @type {EventTarget~Event}
  10625. */
  10626. this.player_.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
  10627. if (this.videoWasPlaying) {
  10628. silencePromise(this.player_.play());
  10629. }
  10630. };
  10631. /**
  10632. * Move more quickly fast forward for keyboard-only users
  10633. */
  10634. SeekBar.prototype.stepForward = function stepForward() {
  10635. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
  10636. };
  10637. /**
  10638. * Move more quickly rewind for keyboard-only users
  10639. */
  10640. SeekBar.prototype.stepBack = function stepBack() {
  10641. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
  10642. };
  10643. /**
  10644. * Toggles the playback state of the player
  10645. * This gets called when enter or space is used on the seekbar
  10646. *
  10647. * @param {EventTarget~Event} event
  10648. * The `keydown` event that caused this function to be called
  10649. *
  10650. */
  10651. SeekBar.prototype.handleAction = function handleAction(event) {
  10652. if (this.player_.paused()) {
  10653. this.player_.play();
  10654. } else {
  10655. this.player_.pause();
  10656. }
  10657. };
  10658. /**
  10659. * Called when this SeekBar has focus and a key gets pressed down. By
  10660. * default it will call `this.handleAction` when the key is space or enter.
  10661. *
  10662. * @param {EventTarget~Event} event
  10663. * The `keydown` event that caused this function to be called.
  10664. *
  10665. * @listens keydown
  10666. */
  10667. SeekBar.prototype.handleKeyPress = function handleKeyPress(event) {
  10668. // Support Space (32) or Enter (13) key operation to fire a click event
  10669. if (event.which === 32 || event.which === 13) {
  10670. event.preventDefault();
  10671. this.handleAction(event);
  10672. } else if (_Slider.prototype.handleKeyPress) {
  10673. // Pass keypress handling up for unsupported keys
  10674. _Slider.prototype.handleKeyPress.call(this, event);
  10675. }
  10676. };
  10677. return SeekBar;
  10678. }(Slider);
  10679. /**
  10680. * Default options for the `SeekBar`
  10681. *
  10682. * @type {Object}
  10683. * @private
  10684. */
  10685. SeekBar.prototype.options_ = {
  10686. children: ['loadProgressBar', 'playProgressBar'],
  10687. barName: 'playProgressBar'
  10688. };
  10689. // MouseTimeDisplay tooltips should not be added to a player on mobile devices or IE8
  10690. if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) {
  10691. SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
  10692. }
  10693. /**
  10694. * Call the update event for this Slider when this event happens on the player.
  10695. *
  10696. * @type {string}
  10697. */
  10698. SeekBar.prototype.playerEvent = 'timeupdate';
  10699. Component.registerComponent('SeekBar', SeekBar);
  10700. /**
  10701. * @file progress-control.js
  10702. */
  10703. /**
  10704. * The Progress Control component contains the seek bar, load progress,
  10705. * and play progress.
  10706. *
  10707. * @extends Component
  10708. */
  10709. var ProgressControl = function (_Component) {
  10710. inherits(ProgressControl, _Component);
  10711. /**
  10712. * Creates an instance of this class.
  10713. *
  10714. * @param {Player} player
  10715. * The `Player` that this class should be attached to.
  10716. *
  10717. * @param {Object} [options]
  10718. * The key/value store of player options.
  10719. */
  10720. function ProgressControl(player, options) {
  10721. classCallCheck(this, ProgressControl);
  10722. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  10723. _this.handleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25);
  10724. _this.throttledHandleMouseSeek = throttle(bind(_this, _this.handleMouseSeek), 25);
  10725. _this.enable();
  10726. return _this;
  10727. }
  10728. /**
  10729. * Create the `Component`'s DOM element
  10730. *
  10731. * @return {Element}
  10732. * The element that was created.
  10733. */
  10734. ProgressControl.prototype.createEl = function createEl$$1() {
  10735. return _Component.prototype.createEl.call(this, 'div', {
  10736. className: 'vjs-progress-control vjs-control'
  10737. });
  10738. };
  10739. /**
  10740. * When the mouse moves over the `ProgressControl`, the pointer position
  10741. * gets passed down to the `MouseTimeDisplay` component.
  10742. *
  10743. * @param {EventTarget~Event} event
  10744. * The `mousemove` event that caused this function to run.
  10745. *
  10746. * @listen mousemove
  10747. */
  10748. ProgressControl.prototype.handleMouseMove = function handleMouseMove(event) {
  10749. var seekBar = this.getChild('seekBar');
  10750. if (seekBar) {
  10751. var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
  10752. var seekBarEl = seekBar.el();
  10753. var seekBarRect = getBoundingClientRect(seekBarEl);
  10754. var seekBarPoint = getPointerPosition(seekBarEl, event).x;
  10755. // The default skin has a gap on either side of the `SeekBar`. This means
  10756. // that it's possible to trigger this behavior outside the boundaries of
  10757. // the `SeekBar`. This ensures we stay within it at all times.
  10758. if (seekBarPoint > 1) {
  10759. seekBarPoint = 1;
  10760. } else if (seekBarPoint < 0) {
  10761. seekBarPoint = 0;
  10762. }
  10763. if (mouseTimeDisplay) {
  10764. mouseTimeDisplay.update(seekBarRect, seekBarPoint);
  10765. }
  10766. }
  10767. };
  10768. /**
  10769. * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
  10770. *
  10771. * @method ProgressControl#throttledHandleMouseSeek
  10772. * @param {EventTarget~Event} event
  10773. * The `mousemove` event that caused this function to run.
  10774. *
  10775. * @listen mousemove
  10776. * @listen touchmove
  10777. */
  10778. /**
  10779. * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
  10780. *
  10781. * @param {EventTarget~Event} event
  10782. * `mousedown` or `touchstart` event that triggered this function
  10783. *
  10784. * @listens mousemove
  10785. * @listens touchmove
  10786. */
  10787. ProgressControl.prototype.handleMouseSeek = function handleMouseSeek(event) {
  10788. var seekBar = this.getChild('seekBar');
  10789. if (seekBar) {
  10790. seekBar.handleMouseMove(event);
  10791. }
  10792. };
  10793. /**
  10794. * Are controls are currently enabled for this progress control.
  10795. *
  10796. * @return {boolean}
  10797. * true if controls are enabled, false otherwise
  10798. */
  10799. ProgressControl.prototype.enabled = function enabled() {
  10800. return this.enabled_;
  10801. };
  10802. /**
  10803. * Disable all controls on the progress control and its children
  10804. */
  10805. ProgressControl.prototype.disable = function disable() {
  10806. this.children().forEach(function (child) {
  10807. return child.disable && child.disable();
  10808. });
  10809. if (!this.enabled()) {
  10810. return;
  10811. }
  10812. this.off(['mousedown', 'touchstart'], this.handleMouseDown);
  10813. this.off(this.el_, 'mousemove', this.handleMouseMove);
  10814. this.handleMouseUp();
  10815. this.addClass('disabled');
  10816. this.enabled_ = false;
  10817. };
  10818. /**
  10819. * Enable all controls on the progress control and its children
  10820. */
  10821. ProgressControl.prototype.enable = function enable() {
  10822. this.children().forEach(function (child) {
  10823. return child.enable && child.enable();
  10824. });
  10825. if (this.enabled()) {
  10826. return;
  10827. }
  10828. this.on(['mousedown', 'touchstart'], this.handleMouseDown);
  10829. this.on(this.el_, 'mousemove', this.handleMouseMove);
  10830. this.removeClass('disabled');
  10831. this.enabled_ = true;
  10832. };
  10833. /**
  10834. * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
  10835. *
  10836. * @param {EventTarget~Event} event
  10837. * `mousedown` or `touchstart` event that triggered this function
  10838. *
  10839. * @listens mousedown
  10840. * @listens touchstart
  10841. */
  10842. ProgressControl.prototype.handleMouseDown = function handleMouseDown(event) {
  10843. var doc = this.el_.ownerDocument;
  10844. var seekBar = this.getChild('seekBar');
  10845. if (seekBar) {
  10846. seekBar.handleMouseDown(event);
  10847. }
  10848. this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
  10849. this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
  10850. this.on(doc, 'mouseup', this.handleMouseUp);
  10851. this.on(doc, 'touchend', this.handleMouseUp);
  10852. };
  10853. /**
  10854. * Handle `mouseup` or `touchend` events on the `ProgressControl`.
  10855. *
  10856. * @param {EventTarget~Event} event
  10857. * `mouseup` or `touchend` event that triggered this function.
  10858. *
  10859. * @listens touchend
  10860. * @listens mouseup
  10861. */
  10862. ProgressControl.prototype.handleMouseUp = function handleMouseUp(event) {
  10863. var doc = this.el_.ownerDocument;
  10864. var seekBar = this.getChild('seekBar');
  10865. if (seekBar) {
  10866. seekBar.handleMouseUp(event);
  10867. }
  10868. this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
  10869. this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
  10870. this.off(doc, 'mouseup', this.handleMouseUp);
  10871. this.off(doc, 'touchend', this.handleMouseUp);
  10872. };
  10873. return ProgressControl;
  10874. }(Component);
  10875. /**
  10876. * Default options for `ProgressControl`
  10877. *
  10878. * @type {Object}
  10879. * @private
  10880. */
  10881. ProgressControl.prototype.options_ = {
  10882. children: ['seekBar']
  10883. };
  10884. Component.registerComponent('ProgressControl', ProgressControl);
  10885. /**
  10886. * @file fullscreen-toggle.js
  10887. */
  10888. /**
  10889. * Toggle fullscreen video
  10890. *
  10891. * @extends Button
  10892. */
  10893. var FullscreenToggle = function (_Button) {
  10894. inherits(FullscreenToggle, _Button);
  10895. /**
  10896. * Creates an instance of this class.
  10897. *
  10898. * @param {Player} player
  10899. * The `Player` that this class should be attached to.
  10900. *
  10901. * @param {Object} [options]
  10902. * The key/value store of player options.
  10903. */
  10904. function FullscreenToggle(player, options) {
  10905. classCallCheck(this, FullscreenToggle);
  10906. var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
  10907. _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
  10908. if (document[FullscreenApi.fullscreenEnabled] === false) {
  10909. _this.disable();
  10910. }
  10911. return _this;
  10912. }
  10913. /**
  10914. * Builds the default DOM `className`.
  10915. *
  10916. * @return {string}
  10917. * The DOM `className` for this object.
  10918. */
  10919. FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() {
  10920. return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this);
  10921. };
  10922. /**
  10923. * Handles fullscreenchange on the player and change control text accordingly.
  10924. *
  10925. * @param {EventTarget~Event} [event]
  10926. * The {@link Player#fullscreenchange} event that caused this function to be
  10927. * called.
  10928. *
  10929. * @listens Player#fullscreenchange
  10930. */
  10931. FullscreenToggle.prototype.handleFullscreenChange = function handleFullscreenChange(event) {
  10932. if (this.player_.isFullscreen()) {
  10933. this.controlText('Non-Fullscreen');
  10934. } else {
  10935. this.controlText('Fullscreen');
  10936. }
  10937. };
  10938. /**
  10939. * This gets called when an `FullscreenToggle` is "clicked". See
  10940. * {@link ClickableComponent} for more detailed information on what a click can be.
  10941. *
  10942. * @param {EventTarget~Event} [event]
  10943. * The `keydown`, `tap`, or `click` event that caused this function to be
  10944. * called.
  10945. *
  10946. * @listens tap
  10947. * @listens click
  10948. */
  10949. FullscreenToggle.prototype.handleClick = function handleClick(event) {
  10950. if (!this.player_.isFullscreen()) {
  10951. this.player_.requestFullscreen();
  10952. } else {
  10953. this.player_.exitFullscreen();
  10954. }
  10955. };
  10956. return FullscreenToggle;
  10957. }(Button);
  10958. /**
  10959. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  10960. *
  10961. * @type {string}
  10962. * @private
  10963. */
  10964. FullscreenToggle.prototype.controlText_ = 'Fullscreen';
  10965. Component.registerComponent('FullscreenToggle', FullscreenToggle);
  10966. /**
  10967. * Check if volume control is supported and if it isn't hide the
  10968. * `Component` that was passed using the `vjs-hidden` class.
  10969. *
  10970. * @param {Component} self
  10971. * The component that should be hidden if volume is unsupported
  10972. *
  10973. * @param {Player} player
  10974. * A reference to the player
  10975. *
  10976. * @private
  10977. */
  10978. var checkVolumeSupport = function checkVolumeSupport(self, player) {
  10979. // hide volume controls when they're not supported by the current tech
  10980. if (player.tech_ && !player.tech_.featuresVolumeControl) {
  10981. self.addClass('vjs-hidden');
  10982. }
  10983. self.on(player, 'loadstart', function () {
  10984. if (!player.tech_.featuresVolumeControl) {
  10985. self.addClass('vjs-hidden');
  10986. } else {
  10987. self.removeClass('vjs-hidden');
  10988. }
  10989. });
  10990. };
  10991. /**
  10992. * @file volume-level.js
  10993. */
  10994. /**
  10995. * Shows volume level
  10996. *
  10997. * @extends Component
  10998. */
  10999. var VolumeLevel = function (_Component) {
  11000. inherits(VolumeLevel, _Component);
  11001. function VolumeLevel() {
  11002. classCallCheck(this, VolumeLevel);
  11003. return possibleConstructorReturn(this, _Component.apply(this, arguments));
  11004. }
  11005. /**
  11006. * Create the `Component`'s DOM element
  11007. *
  11008. * @return {Element}
  11009. * The element that was created.
  11010. */
  11011. VolumeLevel.prototype.createEl = function createEl() {
  11012. return _Component.prototype.createEl.call(this, 'div', {
  11013. className: 'vjs-volume-level',
  11014. innerHTML: '<span class="vjs-control-text"></span>'
  11015. });
  11016. };
  11017. return VolumeLevel;
  11018. }(Component);
  11019. Component.registerComponent('VolumeLevel', VolumeLevel);
  11020. /**
  11021. * @file volume-bar.js
  11022. */
  11023. // Required children
  11024. /**
  11025. * The bar that contains the volume level and can be clicked on to adjust the level
  11026. *
  11027. * @extends Slider
  11028. */
  11029. var VolumeBar = function (_Slider) {
  11030. inherits(VolumeBar, _Slider);
  11031. /**
  11032. * Creates an instance of this class.
  11033. *
  11034. * @param {Player} player
  11035. * The `Player` that this class should be attached to.
  11036. *
  11037. * @param {Object} [options]
  11038. * The key/value store of player options.
  11039. */
  11040. function VolumeBar(player, options) {
  11041. classCallCheck(this, VolumeBar);
  11042. var _this = possibleConstructorReturn(this, _Slider.call(this, player, options));
  11043. _this.on('slideractive', _this.updateLastVolume_);
  11044. _this.on(player, 'volumechange', _this.updateARIAAttributes);
  11045. player.ready(function () {
  11046. return _this.updateARIAAttributes();
  11047. });
  11048. return _this;
  11049. }
  11050. /**
  11051. * Create the `Component`'s DOM element
  11052. *
  11053. * @return {Element}
  11054. * The element that was created.
  11055. */
  11056. VolumeBar.prototype.createEl = function createEl$$1() {
  11057. return _Slider.prototype.createEl.call(this, 'div', {
  11058. className: 'vjs-volume-bar vjs-slider-bar'
  11059. }, {
  11060. 'aria-label': this.localize('Volume Level'),
  11061. 'aria-live': 'polite'
  11062. });
  11063. };
  11064. /**
  11065. * Handle mouse down on volume bar
  11066. *
  11067. * @param {EventTarget~Event} event
  11068. * The `mousedown` event that caused this to run.
  11069. *
  11070. * @listens mousedown
  11071. */
  11072. VolumeBar.prototype.handleMouseDown = function handleMouseDown(event) {
  11073. if (!isSingleLeftClick(event)) {
  11074. return;
  11075. }
  11076. _Slider.prototype.handleMouseDown.call(this, event);
  11077. };
  11078. /**
  11079. * Handle movement events on the {@link VolumeMenuButton}.
  11080. *
  11081. * @param {EventTarget~Event} event
  11082. * The event that caused this function to run.
  11083. *
  11084. * @listens mousemove
  11085. */
  11086. VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) {
  11087. if (!isSingleLeftClick(event)) {
  11088. return;
  11089. }
  11090. this.checkMuted();
  11091. this.player_.volume(this.calculateDistance(event));
  11092. };
  11093. /**
  11094. * If the player is muted unmute it.
  11095. */
  11096. VolumeBar.prototype.checkMuted = function checkMuted() {
  11097. if (this.player_.muted()) {
  11098. this.player_.muted(false);
  11099. }
  11100. };
  11101. /**
  11102. * Get percent of volume level
  11103. *
  11104. * @return {number}
  11105. * Volume level percent as a decimal number.
  11106. */
  11107. VolumeBar.prototype.getPercent = function getPercent() {
  11108. if (this.player_.muted()) {
  11109. return 0;
  11110. }
  11111. return this.player_.volume();
  11112. };
  11113. /**
  11114. * Increase volume level for keyboard users
  11115. */
  11116. VolumeBar.prototype.stepForward = function stepForward() {
  11117. this.checkMuted();
  11118. this.player_.volume(this.player_.volume() + 0.1);
  11119. };
  11120. /**
  11121. * Decrease volume level for keyboard users
  11122. */
  11123. VolumeBar.prototype.stepBack = function stepBack() {
  11124. this.checkMuted();
  11125. this.player_.volume(this.player_.volume() - 0.1);
  11126. };
  11127. /**
  11128. * Update ARIA accessibility attributes
  11129. *
  11130. * @param {EventTarget~Event} [event]
  11131. * The `volumechange` event that caused this function to run.
  11132. *
  11133. * @listens Player#volumechange
  11134. */
  11135. VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes(event) {
  11136. var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
  11137. this.el_.setAttribute('aria-valuenow', ariaValue);
  11138. this.el_.setAttribute('aria-valuetext', ariaValue + '%');
  11139. };
  11140. /**
  11141. * Returns the current value of the player volume as a percentage
  11142. *
  11143. * @private
  11144. */
  11145. VolumeBar.prototype.volumeAsPercentage_ = function volumeAsPercentage_() {
  11146. return Math.round(this.player_.volume() * 100);
  11147. };
  11148. /**
  11149. * When user starts dragging the VolumeBar, store the volume and listen for
  11150. * the end of the drag. When the drag ends, if the volume was set to zero,
  11151. * set lastVolume to the stored volume.
  11152. *
  11153. * @listens slideractive
  11154. * @private
  11155. */
  11156. VolumeBar.prototype.updateLastVolume_ = function updateLastVolume_() {
  11157. var _this2 = this;
  11158. var volumeBeforeDrag = this.player_.volume();
  11159. this.one('sliderinactive', function () {
  11160. if (_this2.player_.volume() === 0) {
  11161. _this2.player_.lastVolume_(volumeBeforeDrag);
  11162. }
  11163. });
  11164. };
  11165. return VolumeBar;
  11166. }(Slider);
  11167. /**
  11168. * Default options for the `VolumeBar`
  11169. *
  11170. * @type {Object}
  11171. * @private
  11172. */
  11173. VolumeBar.prototype.options_ = {
  11174. children: ['volumeLevel'],
  11175. barName: 'volumeLevel'
  11176. };
  11177. /**
  11178. * Call the update event for this Slider when this event happens on the player.
  11179. *
  11180. * @type {string}
  11181. */
  11182. VolumeBar.prototype.playerEvent = 'volumechange';
  11183. Component.registerComponent('VolumeBar', VolumeBar);
  11184. /**
  11185. * @file volume-control.js
  11186. */
  11187. // Required children
  11188. /**
  11189. * The component for controlling the volume level
  11190. *
  11191. * @extends Component
  11192. */
  11193. var VolumeControl = function (_Component) {
  11194. inherits(VolumeControl, _Component);
  11195. /**
  11196. * Creates an instance of this class.
  11197. *
  11198. * @param {Player} player
  11199. * The `Player` that this class should be attached to.
  11200. *
  11201. * @param {Object} [options={}]
  11202. * The key/value store of player options.
  11203. */
  11204. function VolumeControl(player) {
  11205. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  11206. classCallCheck(this, VolumeControl);
  11207. options.vertical = options.vertical || false;
  11208. // Pass the vertical option down to the VolumeBar if
  11209. // the VolumeBar is turned on.
  11210. if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
  11211. options.volumeBar = options.volumeBar || {};
  11212. options.volumeBar.vertical = options.vertical;
  11213. }
  11214. // hide this control if volume support is missing
  11215. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  11216. checkVolumeSupport(_this, player);
  11217. _this.throttledHandleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25);
  11218. _this.on('mousedown', _this.handleMouseDown);
  11219. _this.on('touchstart', _this.handleMouseDown);
  11220. // while the slider is active (the mouse has been pressed down and
  11221. // is dragging) or in focus we do not want to hide the VolumeBar
  11222. _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
  11223. _this.volumeBar.addClass('vjs-slider-active');
  11224. _this.addClass('vjs-slider-active');
  11225. _this.trigger('slideractive');
  11226. });
  11227. _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
  11228. _this.volumeBar.removeClass('vjs-slider-active');
  11229. _this.removeClass('vjs-slider-active');
  11230. _this.trigger('sliderinactive');
  11231. });
  11232. return _this;
  11233. }
  11234. /**
  11235. * Create the `Component`'s DOM element
  11236. *
  11237. * @return {Element}
  11238. * The element that was created.
  11239. */
  11240. VolumeControl.prototype.createEl = function createEl() {
  11241. var orientationClass = 'vjs-volume-horizontal';
  11242. if (this.options_.vertical) {
  11243. orientationClass = 'vjs-volume-vertical';
  11244. }
  11245. return _Component.prototype.createEl.call(this, 'div', {
  11246. className: 'vjs-volume-control vjs-control ' + orientationClass
  11247. });
  11248. };
  11249. /**
  11250. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  11251. *
  11252. * @param {EventTarget~Event} event
  11253. * `mousedown` or `touchstart` event that triggered this function
  11254. *
  11255. * @listens mousedown
  11256. * @listens touchstart
  11257. */
  11258. VolumeControl.prototype.handleMouseDown = function handleMouseDown(event) {
  11259. var doc = this.el_.ownerDocument;
  11260. this.on(doc, 'mousemove', this.throttledHandleMouseMove);
  11261. this.on(doc, 'touchmove', this.throttledHandleMouseMove);
  11262. this.on(doc, 'mouseup', this.handleMouseUp);
  11263. this.on(doc, 'touchend', this.handleMouseUp);
  11264. };
  11265. /**
  11266. * Handle `mouseup` or `touchend` events on the `VolumeControl`.
  11267. *
  11268. * @param {EventTarget~Event} event
  11269. * `mouseup` or `touchend` event that triggered this function.
  11270. *
  11271. * @listens touchend
  11272. * @listens mouseup
  11273. */
  11274. VolumeControl.prototype.handleMouseUp = function handleMouseUp(event) {
  11275. var doc = this.el_.ownerDocument;
  11276. this.off(doc, 'mousemove', this.throttledHandleMouseMove);
  11277. this.off(doc, 'touchmove', this.throttledHandleMouseMove);
  11278. this.off(doc, 'mouseup', this.handleMouseUp);
  11279. this.off(doc, 'touchend', this.handleMouseUp);
  11280. };
  11281. /**
  11282. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  11283. *
  11284. * @param {EventTarget~Event} event
  11285. * `mousedown` or `touchstart` event that triggered this function
  11286. *
  11287. * @listens mousedown
  11288. * @listens touchstart
  11289. */
  11290. VolumeControl.prototype.handleMouseMove = function handleMouseMove(event) {
  11291. this.volumeBar.handleMouseMove(event);
  11292. };
  11293. return VolumeControl;
  11294. }(Component);
  11295. /**
  11296. * Default options for the `VolumeControl`
  11297. *
  11298. * @type {Object}
  11299. * @private
  11300. */
  11301. VolumeControl.prototype.options_ = {
  11302. children: ['volumeBar']
  11303. };
  11304. Component.registerComponent('VolumeControl', VolumeControl);
  11305. /**
  11306. * Check if muting volume is supported and if it isn't hide the mute toggle
  11307. * button.
  11308. *
  11309. * @param {Component} self
  11310. * A reference to the mute toggle button
  11311. *
  11312. * @param {Player} player
  11313. * A reference to the player
  11314. *
  11315. * @private
  11316. */
  11317. var checkMuteSupport = function checkMuteSupport(self, player) {
  11318. // hide mute toggle button if it's not supported by the current tech
  11319. if (player.tech_ && !player.tech_.featuresMuteControl) {
  11320. self.addClass('vjs-hidden');
  11321. }
  11322. self.on(player, 'loadstart', function () {
  11323. if (!player.tech_.featuresMuteControl) {
  11324. self.addClass('vjs-hidden');
  11325. } else {
  11326. self.removeClass('vjs-hidden');
  11327. }
  11328. });
  11329. };
  11330. /**
  11331. * @file mute-toggle.js
  11332. */
  11333. /**
  11334. * A button component for muting the audio.
  11335. *
  11336. * @extends Button
  11337. */
  11338. var MuteToggle = function (_Button) {
  11339. inherits(MuteToggle, _Button);
  11340. /**
  11341. * Creates an instance of this class.
  11342. *
  11343. * @param {Player} player
  11344. * The `Player` that this class should be attached to.
  11345. *
  11346. * @param {Object} [options]
  11347. * The key/value store of player options.
  11348. */
  11349. function MuteToggle(player, options) {
  11350. classCallCheck(this, MuteToggle);
  11351. // hide this control if volume support is missing
  11352. var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
  11353. checkMuteSupport(_this, player);
  11354. _this.on(player, ['loadstart', 'volumechange'], _this.update);
  11355. return _this;
  11356. }
  11357. /**
  11358. * Builds the default DOM `className`.
  11359. *
  11360. * @return {string}
  11361. * The DOM `className` for this object.
  11362. */
  11363. MuteToggle.prototype.buildCSSClass = function buildCSSClass() {
  11364. return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this);
  11365. };
  11366. /**
  11367. * This gets called when an `MuteToggle` is "clicked". See
  11368. * {@link ClickableComponent} for more detailed information on what a click can be.
  11369. *
  11370. * @param {EventTarget~Event} [event]
  11371. * The `keydown`, `tap`, or `click` event that caused this function to be
  11372. * called.
  11373. *
  11374. * @listens tap
  11375. * @listens click
  11376. */
  11377. MuteToggle.prototype.handleClick = function handleClick(event) {
  11378. var vol = this.player_.volume();
  11379. var lastVolume = this.player_.lastVolume_();
  11380. if (vol === 0) {
  11381. var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
  11382. this.player_.volume(volumeToSet);
  11383. this.player_.muted(false);
  11384. } else {
  11385. this.player_.muted(this.player_.muted() ? false : true);
  11386. }
  11387. };
  11388. /**
  11389. * Update the `MuteToggle` button based on the state of `volume` and `muted`
  11390. * on the player.
  11391. *
  11392. * @param {EventTarget~Event} [event]
  11393. * The {@link Player#loadstart} event if this function was called
  11394. * through an event.
  11395. *
  11396. * @listens Player#loadstart
  11397. * @listens Player#volumechange
  11398. */
  11399. MuteToggle.prototype.update = function update(event) {
  11400. this.updateIcon_();
  11401. this.updateControlText_();
  11402. };
  11403. /**
  11404. * Update the appearance of the `MuteToggle` icon.
  11405. *
  11406. * Possible states (given `level` variable below):
  11407. * - 0: crossed out
  11408. * - 1: zero bars of volume
  11409. * - 2: one bar of volume
  11410. * - 3: two bars of volume
  11411. *
  11412. * @private
  11413. */
  11414. MuteToggle.prototype.updateIcon_ = function updateIcon_() {
  11415. var vol = this.player_.volume();
  11416. var level = 3;
  11417. // in iOS when a player is loaded with muted attribute
  11418. // and volume is changed with a native mute button
  11419. // we want to make sure muted state is updated
  11420. if (IS_IOS) {
  11421. this.player_.muted(this.player_.tech_.el_.muted);
  11422. }
  11423. if (vol === 0 || this.player_.muted()) {
  11424. level = 0;
  11425. } else if (vol < 0.33) {
  11426. level = 1;
  11427. } else if (vol < 0.67) {
  11428. level = 2;
  11429. }
  11430. // TODO improve muted icon classes
  11431. for (var i = 0; i < 4; i++) {
  11432. removeClass(this.el_, 'vjs-vol-' + i);
  11433. }
  11434. addClass(this.el_, 'vjs-vol-' + level);
  11435. };
  11436. /**
  11437. * If `muted` has changed on the player, update the control text
  11438. * (`title` attribute on `vjs-mute-control` element and content of
  11439. * `vjs-control-text` element).
  11440. *
  11441. * @private
  11442. */
  11443. MuteToggle.prototype.updateControlText_ = function updateControlText_() {
  11444. var soundOff = this.player_.muted() || this.player_.volume() === 0;
  11445. var text = soundOff ? 'Unmute' : 'Mute';
  11446. if (this.controlText() !== text) {
  11447. this.controlText(text);
  11448. }
  11449. };
  11450. return MuteToggle;
  11451. }(Button);
  11452. /**
  11453. * The text that should display over the `MuteToggle`s controls. Added for localization.
  11454. *
  11455. * @type {string}
  11456. * @private
  11457. */
  11458. MuteToggle.prototype.controlText_ = 'Mute';
  11459. Component.registerComponent('MuteToggle', MuteToggle);
  11460. /**
  11461. * @file volume-control.js
  11462. */
  11463. // Required children
  11464. /**
  11465. * A Component to contain the MuteToggle and VolumeControl so that
  11466. * they can work together.
  11467. *
  11468. * @extends Component
  11469. */
  11470. var VolumePanel = function (_Component) {
  11471. inherits(VolumePanel, _Component);
  11472. /**
  11473. * Creates an instance of this class.
  11474. *
  11475. * @param {Player} player
  11476. * The `Player` that this class should be attached to.
  11477. *
  11478. * @param {Object} [options={}]
  11479. * The key/value store of player options.
  11480. */
  11481. function VolumePanel(player) {
  11482. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  11483. classCallCheck(this, VolumePanel);
  11484. if (typeof options.inline !== 'undefined') {
  11485. options.inline = options.inline;
  11486. } else {
  11487. options.inline = true;
  11488. }
  11489. // pass the inline option down to the VolumeControl as vertical if
  11490. // the VolumeControl is on.
  11491. if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
  11492. options.volumeControl = options.volumeControl || {};
  11493. options.volumeControl.vertical = !options.inline;
  11494. }
  11495. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  11496. _this.on(player, ['loadstart'], _this.volumePanelState_);
  11497. // while the slider is active (the mouse has been pressed down and
  11498. // is dragging) we do not want to hide the VolumeBar
  11499. _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
  11500. _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
  11501. return _this;
  11502. }
  11503. /**
  11504. * Add vjs-slider-active class to the VolumePanel
  11505. *
  11506. * @listens VolumeControl#slideractive
  11507. * @private
  11508. */
  11509. VolumePanel.prototype.sliderActive_ = function sliderActive_() {
  11510. this.addClass('vjs-slider-active');
  11511. };
  11512. /**
  11513. * Removes vjs-slider-active class to the VolumePanel
  11514. *
  11515. * @listens VolumeControl#sliderinactive
  11516. * @private
  11517. */
  11518. VolumePanel.prototype.sliderInactive_ = function sliderInactive_() {
  11519. this.removeClass('vjs-slider-active');
  11520. };
  11521. /**
  11522. * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
  11523. * depending on MuteToggle and VolumeControl state
  11524. *
  11525. * @listens Player#loadstart
  11526. * @private
  11527. */
  11528. VolumePanel.prototype.volumePanelState_ = function volumePanelState_() {
  11529. // hide volume panel if neither volume control or mute toggle
  11530. // are displayed
  11531. if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
  11532. this.addClass('vjs-hidden');
  11533. }
  11534. // if only mute toggle is visible we don't want
  11535. // volume panel expanding when hovered or active
  11536. if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
  11537. this.addClass('vjs-mute-toggle-only');
  11538. }
  11539. };
  11540. /**
  11541. * Create the `Component`'s DOM element
  11542. *
  11543. * @return {Element}
  11544. * The element that was created.
  11545. */
  11546. VolumePanel.prototype.createEl = function createEl() {
  11547. var orientationClass = 'vjs-volume-panel-horizontal';
  11548. if (!this.options_.inline) {
  11549. orientationClass = 'vjs-volume-panel-vertical';
  11550. }
  11551. return _Component.prototype.createEl.call(this, 'div', {
  11552. className: 'vjs-volume-panel vjs-control ' + orientationClass
  11553. });
  11554. };
  11555. return VolumePanel;
  11556. }(Component);
  11557. /**
  11558. * Default options for the `VolumeControl`
  11559. *
  11560. * @type {Object}
  11561. * @private
  11562. */
  11563. VolumePanel.prototype.options_ = {
  11564. children: ['muteToggle', 'volumeControl']
  11565. };
  11566. Component.registerComponent('VolumePanel', VolumePanel);
  11567. /**
  11568. * @file menu.js
  11569. */
  11570. /**
  11571. * The Menu component is used to build popup menus, including subtitle and
  11572. * captions selection menus.
  11573. *
  11574. * @extends Component
  11575. */
  11576. var Menu = function (_Component) {
  11577. inherits(Menu, _Component);
  11578. /**
  11579. * Create an instance of this class.
  11580. *
  11581. * @param {Player} player
  11582. * the player that this component should attach to
  11583. *
  11584. * @param {Object} [options]
  11585. * Object of option names and values
  11586. *
  11587. */
  11588. function Menu(player, options) {
  11589. classCallCheck(this, Menu);
  11590. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  11591. if (options) {
  11592. _this.menuButton_ = options.menuButton;
  11593. }
  11594. _this.focusedChild_ = -1;
  11595. _this.on('keydown', _this.handleKeyPress);
  11596. return _this;
  11597. }
  11598. /**
  11599. * Add a {@link MenuItem} to the menu.
  11600. *
  11601. * @param {Object|string} component
  11602. * The name or instance of the `MenuItem` to add.
  11603. *
  11604. */
  11605. Menu.prototype.addItem = function addItem(component) {
  11606. this.addChild(component);
  11607. component.on('click', bind(this, function (event) {
  11608. // Unpress the associated MenuButton, and move focus back to it
  11609. if (this.menuButton_) {
  11610. this.menuButton_.unpressButton();
  11611. // don't focus menu button if item is a caption settings item
  11612. // because focus will move elsewhere and it logs an error on IE8
  11613. if (component.name() !== 'CaptionSettingsMenuItem') {
  11614. this.menuButton_.focus();
  11615. }
  11616. }
  11617. }));
  11618. };
  11619. /**
  11620. * Create the `Menu`s DOM element.
  11621. *
  11622. * @return {Element}
  11623. * the element that was created
  11624. */
  11625. Menu.prototype.createEl = function createEl$$1() {
  11626. var contentElType = this.options_.contentElType || 'ul';
  11627. this.contentEl_ = createEl(contentElType, {
  11628. className: 'vjs-menu-content'
  11629. });
  11630. this.contentEl_.setAttribute('role', 'menu');
  11631. var el = _Component.prototype.createEl.call(this, 'div', {
  11632. append: this.contentEl_,
  11633. className: 'vjs-menu'
  11634. });
  11635. el.appendChild(this.contentEl_);
  11636. // Prevent clicks from bubbling up. Needed for Menu Buttons,
  11637. // where a click on the parent is significant
  11638. on(el, 'click', function (event) {
  11639. event.preventDefault();
  11640. event.stopImmediatePropagation();
  11641. });
  11642. return el;
  11643. };
  11644. Menu.prototype.dispose = function dispose() {
  11645. this.contentEl_ = null;
  11646. _Component.prototype.dispose.call(this);
  11647. };
  11648. /**
  11649. * Handle a `keydown` event on this menu. This listener is added in the constructor.
  11650. *
  11651. * @param {EventTarget~Event} event
  11652. * A `keydown` event that happened on the menu.
  11653. *
  11654. * @listens keydown
  11655. */
  11656. Menu.prototype.handleKeyPress = function handleKeyPress(event) {
  11657. // Left and Down Arrows
  11658. if (event.which === 37 || event.which === 40) {
  11659. event.preventDefault();
  11660. this.stepForward();
  11661. // Up and Right Arrows
  11662. } else if (event.which === 38 || event.which === 39) {
  11663. event.preventDefault();
  11664. this.stepBack();
  11665. }
  11666. };
  11667. /**
  11668. * Move to next (lower) menu item for keyboard users.
  11669. */
  11670. Menu.prototype.stepForward = function stepForward() {
  11671. var stepChild = 0;
  11672. if (this.focusedChild_ !== undefined) {
  11673. stepChild = this.focusedChild_ + 1;
  11674. }
  11675. this.focus(stepChild);
  11676. };
  11677. /**
  11678. * Move to previous (higher) menu item for keyboard users.
  11679. */
  11680. Menu.prototype.stepBack = function stepBack() {
  11681. var stepChild = 0;
  11682. if (this.focusedChild_ !== undefined) {
  11683. stepChild = this.focusedChild_ - 1;
  11684. }
  11685. this.focus(stepChild);
  11686. };
  11687. /**
  11688. * Set focus on a {@link MenuItem} in the `Menu`.
  11689. *
  11690. * @param {Object|string} [item=0]
  11691. * Index of child item set focus on.
  11692. */
  11693. Menu.prototype.focus = function focus() {
  11694. var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
  11695. var children = this.children().slice();
  11696. var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
  11697. if (haveTitle) {
  11698. children.shift();
  11699. }
  11700. if (children.length > 0) {
  11701. if (item < 0) {
  11702. item = 0;
  11703. } else if (item >= children.length) {
  11704. item = children.length - 1;
  11705. }
  11706. this.focusedChild_ = item;
  11707. children[item].el_.focus();
  11708. }
  11709. };
  11710. return Menu;
  11711. }(Component);
  11712. Component.registerComponent('Menu', Menu);
  11713. /**
  11714. * @file menu-button.js
  11715. */
  11716. /**
  11717. * A `MenuButton` class for any popup {@link Menu}.
  11718. *
  11719. * @extends Component
  11720. */
  11721. var MenuButton = function (_Component) {
  11722. inherits(MenuButton, _Component);
  11723. /**
  11724. * Creates an instance of this class.
  11725. *
  11726. * @param {Player} player
  11727. * The `Player` that this class should be attached to.
  11728. *
  11729. * @param {Object} [options={}]
  11730. * The key/value store of player options.
  11731. */
  11732. function MenuButton(player) {
  11733. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  11734. classCallCheck(this, MenuButton);
  11735. var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
  11736. _this.menuButton_ = new Button(player, options);
  11737. _this.menuButton_.controlText(_this.controlText_);
  11738. _this.menuButton_.el_.setAttribute('aria-haspopup', 'true');
  11739. // Add buildCSSClass values to the button, not the wrapper
  11740. var buttonClass = Button.prototype.buildCSSClass();
  11741. _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
  11742. _this.menuButton_.removeClass('vjs-control');
  11743. _this.addChild(_this.menuButton_);
  11744. _this.update();
  11745. _this.enabled_ = true;
  11746. _this.on(_this.menuButton_, 'tap', _this.handleClick);
  11747. _this.on(_this.menuButton_, 'click', _this.handleClick);
  11748. _this.on(_this.menuButton_, 'focus', _this.handleFocus);
  11749. _this.on(_this.menuButton_, 'blur', _this.handleBlur);
  11750. _this.on('keydown', _this.handleSubmenuKeyPress);
  11751. return _this;
  11752. }
  11753. /**
  11754. * Update the menu based on the current state of its items.
  11755. */
  11756. MenuButton.prototype.update = function update() {
  11757. var menu = this.createMenu();
  11758. if (this.menu) {
  11759. this.menu.dispose();
  11760. this.removeChild(this.menu);
  11761. }
  11762. this.menu = menu;
  11763. this.addChild(menu);
  11764. /**
  11765. * Track the state of the menu button
  11766. *
  11767. * @type {Boolean}
  11768. * @private
  11769. */
  11770. this.buttonPressed_ = false;
  11771. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  11772. if (this.items && this.items.length <= this.hideThreshold_) {
  11773. this.hide();
  11774. } else {
  11775. this.show();
  11776. }
  11777. };
  11778. /**
  11779. * Create the menu and add all items to it.
  11780. *
  11781. * @return {Menu}
  11782. * The constructed menu
  11783. */
  11784. MenuButton.prototype.createMenu = function createMenu() {
  11785. var menu = new Menu(this.player_, { menuButton: this });
  11786. /**
  11787. * Hide the menu if the number of items is less than or equal to this threshold. This defaults
  11788. * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
  11789. * it here because every time we run `createMenu` we need to reset the value.
  11790. *
  11791. * @protected
  11792. * @type {Number}
  11793. */
  11794. this.hideThreshold_ = 0;
  11795. // Add a title list item to the top
  11796. if (this.options_.title) {
  11797. var title = createEl('li', {
  11798. className: 'vjs-menu-title',
  11799. innerHTML: toTitleCase(this.options_.title),
  11800. tabIndex: -1
  11801. });
  11802. this.hideThreshold_ += 1;
  11803. menu.children_.unshift(title);
  11804. prependTo(title, menu.contentEl());
  11805. }
  11806. this.items = this.createItems();
  11807. if (this.items) {
  11808. // Add menu items to the menu
  11809. for (var i = 0; i < this.items.length; i++) {
  11810. menu.addItem(this.items[i]);
  11811. }
  11812. }
  11813. return menu;
  11814. };
  11815. /**
  11816. * Create the list of menu items. Specific to each subclass.
  11817. *
  11818. * @abstract
  11819. */
  11820. MenuButton.prototype.createItems = function createItems() {};
  11821. /**
  11822. * Create the `MenuButtons`s DOM element.
  11823. *
  11824. * @return {Element}
  11825. * The element that gets created.
  11826. */
  11827. MenuButton.prototype.createEl = function createEl$$1() {
  11828. return _Component.prototype.createEl.call(this, 'div', {
  11829. className: this.buildWrapperCSSClass()
  11830. }, {});
  11831. };
  11832. /**
  11833. * Allow sub components to stack CSS class names for the wrapper element
  11834. *
  11835. * @return {string}
  11836. * The constructed wrapper DOM `className`
  11837. */
  11838. MenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
  11839. var menuButtonClass = 'vjs-menu-button';
  11840. // If the inline option is passed, we want to use different styles altogether.
  11841. if (this.options_.inline === true) {
  11842. menuButtonClass += '-inline';
  11843. } else {
  11844. menuButtonClass += '-popup';
  11845. }
  11846. // TODO: Fix the CSS so that this isn't necessary
  11847. var buttonClass = Button.prototype.buildCSSClass();
  11848. return 'vjs-menu-button ' + menuButtonClass + ' ' + buttonClass + ' ' + _Component.prototype.buildCSSClass.call(this);
  11849. };
  11850. /**
  11851. * Builds the default DOM `className`.
  11852. *
  11853. * @return {string}
  11854. * The DOM `className` for this object.
  11855. */
  11856. MenuButton.prototype.buildCSSClass = function buildCSSClass() {
  11857. var menuButtonClass = 'vjs-menu-button';
  11858. // If the inline option is passed, we want to use different styles altogether.
  11859. if (this.options_.inline === true) {
  11860. menuButtonClass += '-inline';
  11861. } else {
  11862. menuButtonClass += '-popup';
  11863. }
  11864. return 'vjs-menu-button ' + menuButtonClass + ' ' + _Component.prototype.buildCSSClass.call(this);
  11865. };
  11866. /**
  11867. * Get or set the localized control text that will be used for accessibility.
  11868. *
  11869. * > NOTE: This will come from the internal `menuButton_` element.
  11870. *
  11871. * @param {string} [text]
  11872. * Control text for element.
  11873. *
  11874. * @param {Element} [el=this.menuButton_.el()]
  11875. * Element to set the title on.
  11876. *
  11877. * @return {string}
  11878. * - The control text when getting
  11879. */
  11880. MenuButton.prototype.controlText = function controlText(text) {
  11881. var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.menuButton_.el();
  11882. return this.menuButton_.controlText(text, el);
  11883. };
  11884. /**
  11885. * Handle a click on a `MenuButton`.
  11886. * See {@link ClickableComponent#handleClick} for instances where this is called.
  11887. *
  11888. * @param {EventTarget~Event} event
  11889. * The `keydown`, `tap`, or `click` event that caused this function to be
  11890. * called.
  11891. *
  11892. * @listens tap
  11893. * @listens click
  11894. */
  11895. MenuButton.prototype.handleClick = function handleClick(event) {
  11896. // When you click the button it adds focus, which will show the menu.
  11897. // So we'll remove focus when the mouse leaves the button. Focus is needed
  11898. // for tab navigation.
  11899. this.one(this.menu.contentEl(), 'mouseleave', bind(this, function (e) {
  11900. this.unpressButton();
  11901. this.el_.blur();
  11902. }));
  11903. if (this.buttonPressed_) {
  11904. this.unpressButton();
  11905. } else {
  11906. this.pressButton();
  11907. }
  11908. };
  11909. /**
  11910. * Set the focus to the actual button, not to this element
  11911. */
  11912. MenuButton.prototype.focus = function focus() {
  11913. this.menuButton_.focus();
  11914. };
  11915. /**
  11916. * Remove the focus from the actual button, not this element
  11917. */
  11918. MenuButton.prototype.blur = function blur() {
  11919. this.menuButton_.blur();
  11920. };
  11921. /**
  11922. * This gets called when a `MenuButton` gains focus via a `focus` event.
  11923. * Turns on listening for `keydown` events. When they happen it
  11924. * calls `this.handleKeyPress`.
  11925. *
  11926. * @param {EventTarget~Event} event
  11927. * The `focus` event that caused this function to be called.
  11928. *
  11929. * @listens focus
  11930. */
  11931. MenuButton.prototype.handleFocus = function handleFocus() {
  11932. on(document, 'keydown', bind(this, this.handleKeyPress));
  11933. };
  11934. /**
  11935. * Called when a `MenuButton` loses focus. Turns off the listener for
  11936. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  11937. *
  11938. * @param {EventTarget~Event} event
  11939. * The `blur` event that caused this function to be called.
  11940. *
  11941. * @listens blur
  11942. */
  11943. MenuButton.prototype.handleBlur = function handleBlur() {
  11944. off(document, 'keydown', bind(this, this.handleKeyPress));
  11945. };
  11946. /**
  11947. * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
  11948. * {@link ClickableComponent#handleKeyPress} for instances where this is called.
  11949. *
  11950. * @param {EventTarget~Event} event
  11951. * The `keydown` event that caused this function to be called.
  11952. *
  11953. * @listens keydown
  11954. */
  11955. MenuButton.prototype.handleKeyPress = function handleKeyPress(event) {
  11956. // Escape (27) key or Tab (9) key unpress the 'button'
  11957. if (event.which === 27 || event.which === 9) {
  11958. if (this.buttonPressed_) {
  11959. this.unpressButton();
  11960. }
  11961. // Don't preventDefault for Tab key - we still want to lose focus
  11962. if (event.which !== 9) {
  11963. event.preventDefault();
  11964. // Set focus back to the menu button's button
  11965. this.menuButton_.el_.focus();
  11966. }
  11967. // Up (38) key or Down (40) key press the 'button'
  11968. } else if (event.which === 38 || event.which === 40) {
  11969. if (!this.buttonPressed_) {
  11970. this.pressButton();
  11971. event.preventDefault();
  11972. }
  11973. }
  11974. };
  11975. /**
  11976. * Handle a `keydown` event on a sub-menu. The listener for this is added in
  11977. * the constructor.
  11978. *
  11979. * @param {EventTarget~Event} event
  11980. * Key press event
  11981. *
  11982. * @listens keydown
  11983. */
  11984. MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
  11985. // Escape (27) key or Tab (9) key unpress the 'button'
  11986. if (event.which === 27 || event.which === 9) {
  11987. if (this.buttonPressed_) {
  11988. this.unpressButton();
  11989. }
  11990. // Don't preventDefault for Tab key - we still want to lose focus
  11991. if (event.which !== 9) {
  11992. event.preventDefault();
  11993. // Set focus back to the menu button's button
  11994. this.menuButton_.el_.focus();
  11995. }
  11996. }
  11997. };
  11998. /**
  11999. * Put the current `MenuButton` into a pressed state.
  12000. */
  12001. MenuButton.prototype.pressButton = function pressButton() {
  12002. if (this.enabled_) {
  12003. this.buttonPressed_ = true;
  12004. this.menu.lockShowing();
  12005. this.menuButton_.el_.setAttribute('aria-expanded', 'true');
  12006. // set the focus into the submenu, except on iOS where it is resulting in
  12007. // undesired scrolling behavior when the player is in an iframe
  12008. if (IS_IOS && isInFrame()) {
  12009. // Return early so that the menu isn't focused
  12010. return;
  12011. }
  12012. this.menu.focus();
  12013. }
  12014. };
  12015. /**
  12016. * Take the current `MenuButton` out of a pressed state.
  12017. */
  12018. MenuButton.prototype.unpressButton = function unpressButton() {
  12019. if (this.enabled_) {
  12020. this.buttonPressed_ = false;
  12021. this.menu.unlockShowing();
  12022. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  12023. }
  12024. };
  12025. /**
  12026. * Disable the `MenuButton`. Don't allow it to be clicked.
  12027. */
  12028. MenuButton.prototype.disable = function disable() {
  12029. this.unpressButton();
  12030. this.enabled_ = false;
  12031. this.addClass('vjs-disabled');
  12032. this.menuButton_.disable();
  12033. };
  12034. /**
  12035. * Enable the `MenuButton`. Allow it to be clicked.
  12036. */
  12037. MenuButton.prototype.enable = function enable() {
  12038. this.enabled_ = true;
  12039. this.removeClass('vjs-disabled');
  12040. this.menuButton_.enable();
  12041. };
  12042. return MenuButton;
  12043. }(Component);
  12044. Component.registerComponent('MenuButton', MenuButton);
  12045. /**
  12046. * @file track-button.js
  12047. */
  12048. /**
  12049. * The base class for buttons that toggle specific track types (e.g. subtitles).
  12050. *
  12051. * @extends MenuButton
  12052. */
  12053. var TrackButton = function (_MenuButton) {
  12054. inherits(TrackButton, _MenuButton);
  12055. /**
  12056. * Creates an instance of this class.
  12057. *
  12058. * @param {Player} player
  12059. * The `Player` that this class should be attached to.
  12060. *
  12061. * @param {Object} [options]
  12062. * The key/value store of player options.
  12063. */
  12064. function TrackButton(player, options) {
  12065. classCallCheck(this, TrackButton);
  12066. var tracks = options.tracks;
  12067. var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options));
  12068. if (_this.items.length <= 1) {
  12069. _this.hide();
  12070. }
  12071. if (!tracks) {
  12072. return possibleConstructorReturn(_this);
  12073. }
  12074. var updateHandler = bind(_this, _this.update);
  12075. tracks.addEventListener('removetrack', updateHandler);
  12076. tracks.addEventListener('addtrack', updateHandler);
  12077. _this.player_.on('ready', updateHandler);
  12078. _this.player_.on('dispose', function () {
  12079. tracks.removeEventListener('removetrack', updateHandler);
  12080. tracks.removeEventListener('addtrack', updateHandler);
  12081. });
  12082. return _this;
  12083. }
  12084. return TrackButton;
  12085. }(MenuButton);
  12086. Component.registerComponent('TrackButton', TrackButton);
  12087. /**
  12088. * @file menu-item.js
  12089. */
  12090. /**
  12091. * The component for a menu item. `<li>`
  12092. *
  12093. * @extends ClickableComponent
  12094. */
  12095. var MenuItem = function (_ClickableComponent) {
  12096. inherits(MenuItem, _ClickableComponent);
  12097. /**
  12098. * Creates an instance of the this class.
  12099. *
  12100. * @param {Player} player
  12101. * The `Player` that this class should be attached to.
  12102. *
  12103. * @param {Object} [options={}]
  12104. * The key/value store of player options.
  12105. *
  12106. */
  12107. function MenuItem(player, options) {
  12108. classCallCheck(this, MenuItem);
  12109. var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options));
  12110. _this.selectable = options.selectable;
  12111. _this.isSelected_ = options.selected || false;
  12112. _this.multiSelectable = options.multiSelectable;
  12113. _this.selected(_this.isSelected_);
  12114. if (_this.selectable) {
  12115. if (_this.multiSelectable) {
  12116. _this.el_.setAttribute('role', 'menuitemcheckbox');
  12117. } else {
  12118. _this.el_.setAttribute('role', 'menuitemradio');
  12119. }
  12120. } else {
  12121. _this.el_.setAttribute('role', 'menuitem');
  12122. }
  12123. return _this;
  12124. }
  12125. /**
  12126. * Create the `MenuItem's DOM element
  12127. *
  12128. * @param {string} [type=li]
  12129. * Element's node type, not actually used, always set to `li`.
  12130. *
  12131. * @param {Object} [props={}]
  12132. * An object of properties that should be set on the element
  12133. *
  12134. * @param {Object} [attrs={}]
  12135. * An object of attributes that should be set on the element
  12136. *
  12137. * @return {Element}
  12138. * The element that gets created.
  12139. */
  12140. MenuItem.prototype.createEl = function createEl(type, props, attrs) {
  12141. // The control is textual, not just an icon
  12142. this.nonIconControl = true;
  12143. return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
  12144. className: 'vjs-menu-item',
  12145. innerHTML: '<span class="vjs-menu-item-text">' + this.localize(this.options_.label) + '</span>',
  12146. tabIndex: -1
  12147. }, props), attrs);
  12148. };
  12149. /**
  12150. * Any click on a `MenuItem` puts it into the selected state.
  12151. * See {@link ClickableComponent#handleClick} for instances where this is called.
  12152. *
  12153. * @param {EventTarget~Event} event
  12154. * The `keydown`, `tap`, or `click` event that caused this function to be
  12155. * called.
  12156. *
  12157. * @listens tap
  12158. * @listens click
  12159. */
  12160. MenuItem.prototype.handleClick = function handleClick(event) {
  12161. this.selected(true);
  12162. };
  12163. /**
  12164. * Set the state for this menu item as selected or not.
  12165. *
  12166. * @param {boolean} selected
  12167. * if the menu item is selected or not
  12168. */
  12169. MenuItem.prototype.selected = function selected(_selected) {
  12170. if (this.selectable) {
  12171. if (_selected) {
  12172. this.addClass('vjs-selected');
  12173. this.el_.setAttribute('aria-checked', 'true');
  12174. // aria-checked isn't fully supported by browsers/screen readers,
  12175. // so indicate selected state to screen reader in the control text.
  12176. this.controlText(', selected');
  12177. this.isSelected_ = true;
  12178. } else {
  12179. this.removeClass('vjs-selected');
  12180. this.el_.setAttribute('aria-checked', 'false');
  12181. // Indicate un-selected state to screen reader
  12182. this.controlText('');
  12183. this.isSelected_ = false;
  12184. }
  12185. }
  12186. };
  12187. return MenuItem;
  12188. }(ClickableComponent);
  12189. Component.registerComponent('MenuItem', MenuItem);
  12190. /**
  12191. * @file text-track-menu-item.js
  12192. */
  12193. /**
  12194. * The specific menu item type for selecting a language within a text track kind
  12195. *
  12196. * @extends MenuItem
  12197. */
  12198. var TextTrackMenuItem = function (_MenuItem) {
  12199. inherits(TextTrackMenuItem, _MenuItem);
  12200. /**
  12201. * Creates an instance of this class.
  12202. *
  12203. * @param {Player} player
  12204. * The `Player` that this class should be attached to.
  12205. *
  12206. * @param {Object} [options]
  12207. * The key/value store of player options.
  12208. */
  12209. function TextTrackMenuItem(player, options) {
  12210. classCallCheck(this, TextTrackMenuItem);
  12211. var track = options.track;
  12212. var tracks = player.textTracks();
  12213. // Modify options for parent MenuItem class's init.
  12214. options.label = track.label || track.language || 'Unknown';
  12215. options.selected = track.mode === 'showing';
  12216. var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
  12217. _this.track = track;
  12218. var changeHandler = function changeHandler() {
  12219. for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
  12220. args[_key] = arguments[_key];
  12221. }
  12222. _this.handleTracksChange.apply(_this, args);
  12223. };
  12224. var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
  12225. for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  12226. args[_key2] = arguments[_key2];
  12227. }
  12228. _this.handleSelectedLanguageChange.apply(_this, args);
  12229. };
  12230. player.on(['loadstart', 'texttrackchange'], changeHandler);
  12231. tracks.addEventListener('change', changeHandler);
  12232. tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  12233. _this.on('dispose', function () {
  12234. player.off(['loadstart', 'texttrackchange'], changeHandler);
  12235. tracks.removeEventListener('change', changeHandler);
  12236. tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  12237. });
  12238. // iOS7 doesn't dispatch change events to TextTrackLists when an
  12239. // associated track's mode changes. Without something like
  12240. // Object.observe() (also not present on iOS7), it's not
  12241. // possible to detect changes to the mode attribute and polyfill
  12242. // the change event. As a poor substitute, we manually dispatch
  12243. // change events whenever the controls modify the mode.
  12244. if (tracks.onchange === undefined) {
  12245. var event = void 0;
  12246. _this.on(['tap', 'click'], function () {
  12247. if (_typeof(window.Event) !== 'object') {
  12248. // Android 2.3 throws an Illegal Constructor error for window.Event
  12249. try {
  12250. event = new window.Event('change');
  12251. } catch (err) {
  12252. // continue regardless of error
  12253. }
  12254. }
  12255. if (!event) {
  12256. event = document.createEvent('Event');
  12257. event.initEvent('change', true, true);
  12258. }
  12259. tracks.dispatchEvent(event);
  12260. });
  12261. }
  12262. // set the default state based on current tracks
  12263. _this.handleTracksChange();
  12264. return _this;
  12265. }
  12266. /**
  12267. * This gets called when an `TextTrackMenuItem` is "clicked". See
  12268. * {@link ClickableComponent} for more detailed information on what a click can be.
  12269. *
  12270. * @param {EventTarget~Event} event
  12271. * The `keydown`, `tap`, or `click` event that caused this function to be
  12272. * called.
  12273. *
  12274. * @listens tap
  12275. * @listens click
  12276. */
  12277. TextTrackMenuItem.prototype.handleClick = function handleClick(event) {
  12278. var kind = this.track.kind;
  12279. var kinds = this.track.kinds;
  12280. var tracks = this.player_.textTracks();
  12281. if (!kinds) {
  12282. kinds = [kind];
  12283. }
  12284. _MenuItem.prototype.handleClick.call(this, event);
  12285. if (!tracks) {
  12286. return;
  12287. }
  12288. for (var i = 0; i < tracks.length; i++) {
  12289. var track = tracks[i];
  12290. if (track === this.track && kinds.indexOf(track.kind) > -1) {
  12291. if (track.mode !== 'showing') {
  12292. track.mode = 'showing';
  12293. }
  12294. } else if (track.mode !== 'disabled') {
  12295. track.mode = 'disabled';
  12296. }
  12297. }
  12298. };
  12299. /**
  12300. * Handle text track list change
  12301. *
  12302. * @param {EventTarget~Event} event
  12303. * The `change` event that caused this function to be called.
  12304. *
  12305. * @listens TextTrackList#change
  12306. */
  12307. TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
  12308. var shouldBeSelected = this.track.mode === 'showing';
  12309. // Prevent redundant selected() calls because they may cause
  12310. // screen readers to read the appended control text unnecessarily
  12311. if (shouldBeSelected !== this.isSelected_) {
  12312. this.selected(shouldBeSelected);
  12313. }
  12314. };
  12315. TextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  12316. if (this.track.mode === 'showing') {
  12317. var selectedLanguage = this.player_.cache_.selectedLanguage;
  12318. // Don't replace the kind of track across the same language
  12319. if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
  12320. return;
  12321. }
  12322. this.player_.cache_.selectedLanguage = {
  12323. enabled: true,
  12324. language: this.track.language,
  12325. kind: this.track.kind
  12326. };
  12327. }
  12328. };
  12329. TextTrackMenuItem.prototype.dispose = function dispose() {
  12330. // remove reference to track object on dispose
  12331. this.track = null;
  12332. _MenuItem.prototype.dispose.call(this);
  12333. };
  12334. return TextTrackMenuItem;
  12335. }(MenuItem);
  12336. Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
  12337. /**
  12338. * @file off-text-track-menu-item.js
  12339. */
  12340. /**
  12341. * A special menu item for turning of a specific type of text track
  12342. *
  12343. * @extends TextTrackMenuItem
  12344. */
  12345. var OffTextTrackMenuItem = function (_TextTrackMenuItem) {
  12346. inherits(OffTextTrackMenuItem, _TextTrackMenuItem);
  12347. /**
  12348. * Creates an instance of this class.
  12349. *
  12350. * @param {Player} player
  12351. * The `Player` that this class should be attached to.
  12352. *
  12353. * @param {Object} [options]
  12354. * The key/value store of player options.
  12355. */
  12356. function OffTextTrackMenuItem(player, options) {
  12357. classCallCheck(this, OffTextTrackMenuItem);
  12358. // Create pseudo track info
  12359. // Requires options['kind']
  12360. options.track = {
  12361. player: player,
  12362. kind: options.kind,
  12363. kinds: options.kinds,
  12364. 'default': false,
  12365. mode: 'disabled'
  12366. };
  12367. if (!options.kinds) {
  12368. options.kinds = [options.kind];
  12369. }
  12370. if (options.label) {
  12371. options.track.label = options.label;
  12372. } else {
  12373. options.track.label = options.kinds.join(' and ') + ' off';
  12374. }
  12375. // MenuItem is selectable
  12376. options.selectable = true;
  12377. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  12378. options.multiSelectable = false;
  12379. return possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options));
  12380. }
  12381. /**
  12382. * Handle text track change
  12383. *
  12384. * @param {EventTarget~Event} event
  12385. * The event that caused this function to run
  12386. */
  12387. OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
  12388. var tracks = this.player().textTracks();
  12389. var shouldBeSelected = true;
  12390. for (var i = 0, l = tracks.length; i < l; i++) {
  12391. var track = tracks[i];
  12392. if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
  12393. shouldBeSelected = false;
  12394. break;
  12395. }
  12396. }
  12397. // Prevent redundant selected() calls because they may cause
  12398. // screen readers to read the appended control text unnecessarily
  12399. if (shouldBeSelected !== this.isSelected_) {
  12400. this.selected(shouldBeSelected);
  12401. }
  12402. };
  12403. OffTextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  12404. var tracks = this.player().textTracks();
  12405. var allHidden = true;
  12406. for (var i = 0, l = tracks.length; i < l; i++) {
  12407. var track = tracks[i];
  12408. if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
  12409. allHidden = false;
  12410. break;
  12411. }
  12412. }
  12413. if (allHidden) {
  12414. this.player_.cache_.selectedLanguage = {
  12415. enabled: false
  12416. };
  12417. }
  12418. };
  12419. return OffTextTrackMenuItem;
  12420. }(TextTrackMenuItem);
  12421. Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
  12422. /**
  12423. * @file text-track-button.js
  12424. */
  12425. /**
  12426. * The base class for buttons that toggle specific text track types (e.g. subtitles)
  12427. *
  12428. * @extends MenuButton
  12429. */
  12430. var TextTrackButton = function (_TrackButton) {
  12431. inherits(TextTrackButton, _TrackButton);
  12432. /**
  12433. * Creates an instance of this class.
  12434. *
  12435. * @param {Player} player
  12436. * The `Player` that this class should be attached to.
  12437. *
  12438. * @param {Object} [options={}]
  12439. * The key/value store of player options.
  12440. */
  12441. function TextTrackButton(player) {
  12442. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  12443. classCallCheck(this, TextTrackButton);
  12444. options.tracks = player.textTracks();
  12445. return possibleConstructorReturn(this, _TrackButton.call(this, player, options));
  12446. }
  12447. /**
  12448. * Create a menu item for each text track
  12449. *
  12450. * @param {TextTrackMenuItem[]} [items=[]]
  12451. * Existing array of items to use during creation
  12452. *
  12453. * @return {TextTrackMenuItem[]}
  12454. * Array of menu items that were created
  12455. */
  12456. TextTrackButton.prototype.createItems = function createItems() {
  12457. var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  12458. var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TextTrackMenuItem;
  12459. // Label is an overide for the [track] off label
  12460. // USed to localise captions/subtitles
  12461. var label = void 0;
  12462. if (this.label_) {
  12463. label = this.label_ + ' off';
  12464. }
  12465. // Add an OFF menu item to turn all tracks off
  12466. items.push(new OffTextTrackMenuItem(this.player_, {
  12467. kinds: this.kinds_,
  12468. kind: this.kind_,
  12469. label: label
  12470. }));
  12471. this.hideThreshold_ += 1;
  12472. var tracks = this.player_.textTracks();
  12473. if (!Array.isArray(this.kinds_)) {
  12474. this.kinds_ = [this.kind_];
  12475. }
  12476. for (var i = 0; i < tracks.length; i++) {
  12477. var track = tracks[i];
  12478. // only add tracks that are of an appropriate kind and have a label
  12479. if (this.kinds_.indexOf(track.kind) > -1) {
  12480. var item = new TrackMenuItem(this.player_, {
  12481. track: track,
  12482. // MenuItem is selectable
  12483. selectable: true,
  12484. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  12485. multiSelectable: false
  12486. });
  12487. item.addClass('vjs-' + track.kind + '-menu-item');
  12488. items.push(item);
  12489. }
  12490. }
  12491. return items;
  12492. };
  12493. return TextTrackButton;
  12494. }(TrackButton);
  12495. Component.registerComponent('TextTrackButton', TextTrackButton);
  12496. /**
  12497. * @file chapters-track-menu-item.js
  12498. */
  12499. /**
  12500. * The chapter track menu item
  12501. *
  12502. * @extends MenuItem
  12503. */
  12504. var ChaptersTrackMenuItem = function (_MenuItem) {
  12505. inherits(ChaptersTrackMenuItem, _MenuItem);
  12506. /**
  12507. * Creates an instance of this class.
  12508. *
  12509. * @param {Player} player
  12510. * The `Player` that this class should be attached to.
  12511. *
  12512. * @param {Object} [options]
  12513. * The key/value store of player options.
  12514. */
  12515. function ChaptersTrackMenuItem(player, options) {
  12516. classCallCheck(this, ChaptersTrackMenuItem);
  12517. var track = options.track;
  12518. var cue = options.cue;
  12519. var currentTime = player.currentTime();
  12520. // Modify options for parent MenuItem class's init.
  12521. options.selectable = true;
  12522. options.multiSelectable = false;
  12523. options.label = cue.text;
  12524. options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
  12525. var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
  12526. _this.track = track;
  12527. _this.cue = cue;
  12528. track.addEventListener('cuechange', bind(_this, _this.update));
  12529. return _this;
  12530. }
  12531. /**
  12532. * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
  12533. * {@link ClickableComponent} for more detailed information on what a click can be.
  12534. *
  12535. * @param {EventTarget~Event} [event]
  12536. * The `keydown`, `tap`, or `click` event that caused this function to be
  12537. * called.
  12538. *
  12539. * @listens tap
  12540. * @listens click
  12541. */
  12542. ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) {
  12543. _MenuItem.prototype.handleClick.call(this);
  12544. this.player_.currentTime(this.cue.startTime);
  12545. this.update(this.cue.startTime);
  12546. };
  12547. /**
  12548. * Update chapter menu item
  12549. *
  12550. * @param {EventTarget~Event} [event]
  12551. * The `cuechange` event that caused this function to run.
  12552. *
  12553. * @listens TextTrack#cuechange
  12554. */
  12555. ChaptersTrackMenuItem.prototype.update = function update(event) {
  12556. var cue = this.cue;
  12557. var currentTime = this.player_.currentTime();
  12558. // vjs.log(currentTime, cue.startTime);
  12559. this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
  12560. };
  12561. return ChaptersTrackMenuItem;
  12562. }(MenuItem);
  12563. Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
  12564. /**
  12565. * @file chapters-button.js
  12566. */
  12567. /**
  12568. * The button component for toggling and selecting chapters
  12569. * Chapters act much differently than other text tracks
  12570. * Cues are navigation vs. other tracks of alternative languages
  12571. *
  12572. * @extends TextTrackButton
  12573. */
  12574. var ChaptersButton = function (_TextTrackButton) {
  12575. inherits(ChaptersButton, _TextTrackButton);
  12576. /**
  12577. * Creates an instance of this class.
  12578. *
  12579. * @param {Player} player
  12580. * The `Player` that this class should be attached to.
  12581. *
  12582. * @param {Object} [options]
  12583. * The key/value store of player options.
  12584. *
  12585. * @param {Component~ReadyCallback} [ready]
  12586. * The function to call when this function is ready.
  12587. */
  12588. function ChaptersButton(player, options, ready) {
  12589. classCallCheck(this, ChaptersButton);
  12590. return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
  12591. }
  12592. /**
  12593. * Builds the default DOM `className`.
  12594. *
  12595. * @return {string}
  12596. * The DOM `className` for this object.
  12597. */
  12598. ChaptersButton.prototype.buildCSSClass = function buildCSSClass() {
  12599. return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
  12600. };
  12601. ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
  12602. return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  12603. };
  12604. /**
  12605. * Update the menu based on the current state of its items.
  12606. *
  12607. * @param {EventTarget~Event} [event]
  12608. * An event that triggered this function to run.
  12609. *
  12610. * @listens TextTrackList#addtrack
  12611. * @listens TextTrackList#removetrack
  12612. * @listens TextTrackList#change
  12613. */
  12614. ChaptersButton.prototype.update = function update(event) {
  12615. if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
  12616. this.setTrack(this.findChaptersTrack());
  12617. }
  12618. _TextTrackButton.prototype.update.call(this);
  12619. };
  12620. /**
  12621. * Set the currently selected track for the chapters button.
  12622. *
  12623. * @param {TextTrack} track
  12624. * The new track to select. Nothing will change if this is the currently selected
  12625. * track.
  12626. */
  12627. ChaptersButton.prototype.setTrack = function setTrack(track) {
  12628. if (this.track_ === track) {
  12629. return;
  12630. }
  12631. if (!this.updateHandler_) {
  12632. this.updateHandler_ = this.update.bind(this);
  12633. }
  12634. // here this.track_ refers to the old track instance
  12635. if (this.track_) {
  12636. var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  12637. if (remoteTextTrackEl) {
  12638. remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
  12639. }
  12640. this.track_ = null;
  12641. }
  12642. this.track_ = track;
  12643. // here this.track_ refers to the new track instance
  12644. if (this.track_) {
  12645. this.track_.mode = 'hidden';
  12646. var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  12647. if (_remoteTextTrackEl) {
  12648. _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
  12649. }
  12650. }
  12651. };
  12652. /**
  12653. * Find the track object that is currently in use by this ChaptersButton
  12654. *
  12655. * @return {TextTrack|undefined}
  12656. * The current track or undefined if none was found.
  12657. */
  12658. ChaptersButton.prototype.findChaptersTrack = function findChaptersTrack() {
  12659. var tracks = this.player_.textTracks() || [];
  12660. for (var i = tracks.length - 1; i >= 0; i--) {
  12661. // We will always choose the last track as our chaptersTrack
  12662. var track = tracks[i];
  12663. if (track.kind === this.kind_) {
  12664. return track;
  12665. }
  12666. }
  12667. };
  12668. /**
  12669. * Get the caption for the ChaptersButton based on the track label. This will also
  12670. * use the current tracks localized kind as a fallback if a label does not exist.
  12671. *
  12672. * @return {string}
  12673. * The tracks current label or the localized track kind.
  12674. */
  12675. ChaptersButton.prototype.getMenuCaption = function getMenuCaption() {
  12676. if (this.track_ && this.track_.label) {
  12677. return this.track_.label;
  12678. }
  12679. return this.localize(toTitleCase(this.kind_));
  12680. };
  12681. /**
  12682. * Create menu from chapter track
  12683. *
  12684. * @return {Menu}
  12685. * New menu for the chapter buttons
  12686. */
  12687. ChaptersButton.prototype.createMenu = function createMenu() {
  12688. this.options_.title = this.getMenuCaption();
  12689. return _TextTrackButton.prototype.createMenu.call(this);
  12690. };
  12691. /**
  12692. * Create a menu item for each text track
  12693. *
  12694. * @return {TextTrackMenuItem[]}
  12695. * Array of menu items
  12696. */
  12697. ChaptersButton.prototype.createItems = function createItems() {
  12698. var items = [];
  12699. if (!this.track_) {
  12700. return items;
  12701. }
  12702. var cues = this.track_.cues;
  12703. if (!cues) {
  12704. return items;
  12705. }
  12706. for (var i = 0, l = cues.length; i < l; i++) {
  12707. var cue = cues[i];
  12708. var mi = new ChaptersTrackMenuItem(this.player_, { track: this.track_, cue: cue });
  12709. items.push(mi);
  12710. }
  12711. return items;
  12712. };
  12713. return ChaptersButton;
  12714. }(TextTrackButton);
  12715. /**
  12716. * `kind` of TextTrack to look for to associate it with this menu.
  12717. *
  12718. * @type {string}
  12719. * @private
  12720. */
  12721. ChaptersButton.prototype.kind_ = 'chapters';
  12722. /**
  12723. * The text that should display over the `ChaptersButton`s controls. Added for localization.
  12724. *
  12725. * @type {string}
  12726. * @private
  12727. */
  12728. ChaptersButton.prototype.controlText_ = 'Chapters';
  12729. Component.registerComponent('ChaptersButton', ChaptersButton);
  12730. /**
  12731. * @file descriptions-button.js
  12732. */
  12733. /**
  12734. * The button component for toggling and selecting descriptions
  12735. *
  12736. * @extends TextTrackButton
  12737. */
  12738. var DescriptionsButton = function (_TextTrackButton) {
  12739. inherits(DescriptionsButton, _TextTrackButton);
  12740. /**
  12741. * Creates an instance of this class.
  12742. *
  12743. * @param {Player} player
  12744. * The `Player` that this class should be attached to.
  12745. *
  12746. * @param {Object} [options]
  12747. * The key/value store of player options.
  12748. *
  12749. * @param {Component~ReadyCallback} [ready]
  12750. * The function to call when this component is ready.
  12751. */
  12752. function DescriptionsButton(player, options, ready) {
  12753. classCallCheck(this, DescriptionsButton);
  12754. var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
  12755. var tracks = player.textTracks();
  12756. var changeHandler = bind(_this, _this.handleTracksChange);
  12757. tracks.addEventListener('change', changeHandler);
  12758. _this.on('dispose', function () {
  12759. tracks.removeEventListener('change', changeHandler);
  12760. });
  12761. return _this;
  12762. }
  12763. /**
  12764. * Handle text track change
  12765. *
  12766. * @param {EventTarget~Event} event
  12767. * The event that caused this function to run
  12768. *
  12769. * @listens TextTrackList#change
  12770. */
  12771. DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) {
  12772. var tracks = this.player().textTracks();
  12773. var disabled = false;
  12774. // Check whether a track of a different kind is showing
  12775. for (var i = 0, l = tracks.length; i < l; i++) {
  12776. var track = tracks[i];
  12777. if (track.kind !== this.kind_ && track.mode === 'showing') {
  12778. disabled = true;
  12779. break;
  12780. }
  12781. }
  12782. // If another track is showing, disable this menu button
  12783. if (disabled) {
  12784. this.disable();
  12785. } else {
  12786. this.enable();
  12787. }
  12788. };
  12789. /**
  12790. * Builds the default DOM `className`.
  12791. *
  12792. * @return {string}
  12793. * The DOM `className` for this object.
  12794. */
  12795. DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() {
  12796. return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
  12797. };
  12798. DescriptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
  12799. return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  12800. };
  12801. return DescriptionsButton;
  12802. }(TextTrackButton);
  12803. /**
  12804. * `kind` of TextTrack to look for to associate it with this menu.
  12805. *
  12806. * @type {string}
  12807. * @private
  12808. */
  12809. DescriptionsButton.prototype.kind_ = 'descriptions';
  12810. /**
  12811. * The text that should display over the `DescriptionsButton`s controls. Added for localization.
  12812. *
  12813. * @type {string}
  12814. * @private
  12815. */
  12816. DescriptionsButton.prototype.controlText_ = 'Descriptions';
  12817. Component.registerComponent('DescriptionsButton', DescriptionsButton);
  12818. /**
  12819. * @file subtitles-button.js
  12820. */
  12821. /**
  12822. * The button component for toggling and selecting subtitles
  12823. *
  12824. * @extends TextTrackButton
  12825. */
  12826. var SubtitlesButton = function (_TextTrackButton) {
  12827. inherits(SubtitlesButton, _TextTrackButton);
  12828. /**
  12829. * Creates an instance of this class.
  12830. *
  12831. * @param {Player} player
  12832. * The `Player` that this class should be attached to.
  12833. *
  12834. * @param {Object} [options]
  12835. * The key/value store of player options.
  12836. *
  12837. * @param {Component~ReadyCallback} [ready]
  12838. * The function to call when this component is ready.
  12839. */
  12840. function SubtitlesButton(player, options, ready) {
  12841. classCallCheck(this, SubtitlesButton);
  12842. return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
  12843. }
  12844. /**
  12845. * Builds the default DOM `className`.
  12846. *
  12847. * @return {string}
  12848. * The DOM `className` for this object.
  12849. */
  12850. SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() {
  12851. return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
  12852. };
  12853. SubtitlesButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
  12854. return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  12855. };
  12856. return SubtitlesButton;
  12857. }(TextTrackButton);
  12858. /**
  12859. * `kind` of TextTrack to look for to associate it with this menu.
  12860. *
  12861. * @type {string}
  12862. * @private
  12863. */
  12864. SubtitlesButton.prototype.kind_ = 'subtitles';
  12865. /**
  12866. * The text that should display over the `SubtitlesButton`s controls. Added for localization.
  12867. *
  12868. * @type {string}
  12869. * @private
  12870. */
  12871. SubtitlesButton.prototype.controlText_ = 'Subtitles';
  12872. Component.registerComponent('SubtitlesButton', SubtitlesButton);
  12873. /**
  12874. * @file caption-settings-menu-item.js
  12875. */
  12876. /**
  12877. * The menu item for caption track settings menu
  12878. *
  12879. * @extends TextTrackMenuItem
  12880. */
  12881. var CaptionSettingsMenuItem = function (_TextTrackMenuItem) {
  12882. inherits(CaptionSettingsMenuItem, _TextTrackMenuItem);
  12883. /**
  12884. * Creates an instance of this class.
  12885. *
  12886. * @param {Player} player
  12887. * The `Player` that this class should be attached to.
  12888. *
  12889. * @param {Object} [options]
  12890. * The key/value store of player options.
  12891. */
  12892. function CaptionSettingsMenuItem(player, options) {
  12893. classCallCheck(this, CaptionSettingsMenuItem);
  12894. options.track = {
  12895. player: player,
  12896. kind: options.kind,
  12897. label: options.kind + ' settings',
  12898. selectable: false,
  12899. 'default': false,
  12900. mode: 'disabled'
  12901. };
  12902. // CaptionSettingsMenuItem has no concept of 'selected'
  12903. options.selectable = false;
  12904. options.name = 'CaptionSettingsMenuItem';
  12905. var _this = possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options));
  12906. _this.addClass('vjs-texttrack-settings');
  12907. _this.controlText(', opens ' + options.kind + ' settings dialog');
  12908. return _this;
  12909. }
  12910. /**
  12911. * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
  12912. * {@link ClickableComponent} for more detailed information on what a click can be.
  12913. *
  12914. * @param {EventTarget~Event} [event]
  12915. * The `keydown`, `tap`, or `click` event that caused this function to be
  12916. * called.
  12917. *
  12918. * @listens tap
  12919. * @listens click
  12920. */
  12921. CaptionSettingsMenuItem.prototype.handleClick = function handleClick(event) {
  12922. this.player().getChild('textTrackSettings').open();
  12923. };
  12924. return CaptionSettingsMenuItem;
  12925. }(TextTrackMenuItem);
  12926. Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
  12927. /**
  12928. * @file captions-button.js
  12929. */
  12930. /**
  12931. * The button component for toggling and selecting captions
  12932. *
  12933. * @extends TextTrackButton
  12934. */
  12935. var CaptionsButton = function (_TextTrackButton) {
  12936. inherits(CaptionsButton, _TextTrackButton);
  12937. /**
  12938. * Creates an instance of this class.
  12939. *
  12940. * @param {Player} player
  12941. * The `Player` that this class should be attached to.
  12942. *
  12943. * @param {Object} [options]
  12944. * The key/value store of player options.
  12945. *
  12946. * @param {Component~ReadyCallback} [ready]
  12947. * The function to call when this component is ready.
  12948. */
  12949. function CaptionsButton(player, options, ready) {
  12950. classCallCheck(this, CaptionsButton);
  12951. return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
  12952. }
  12953. /**
  12954. * Builds the default DOM `className`.
  12955. *
  12956. * @return {string}
  12957. * The DOM `className` for this object.
  12958. */
  12959. CaptionsButton.prototype.buildCSSClass = function buildCSSClass() {
  12960. return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
  12961. };
  12962. CaptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
  12963. return 'vjs-captions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  12964. };
  12965. /**
  12966. * Create caption menu items
  12967. *
  12968. * @return {CaptionSettingsMenuItem[]}
  12969. * The array of current menu items.
  12970. */
  12971. CaptionsButton.prototype.createItems = function createItems() {
  12972. var items = [];
  12973. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  12974. items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.kind_ }));
  12975. this.hideThreshold_ += 1;
  12976. }
  12977. return _TextTrackButton.prototype.createItems.call(this, items);
  12978. };
  12979. return CaptionsButton;
  12980. }(TextTrackButton);
  12981. /**
  12982. * `kind` of TextTrack to look for to associate it with this menu.
  12983. *
  12984. * @type {string}
  12985. * @private
  12986. */
  12987. CaptionsButton.prototype.kind_ = 'captions';
  12988. /**
  12989. * The text that should display over the `CaptionsButton`s controls. Added for localization.
  12990. *
  12991. * @type {string}
  12992. * @private
  12993. */
  12994. CaptionsButton.prototype.controlText_ = 'Captions';
  12995. Component.registerComponent('CaptionsButton', CaptionsButton);
  12996. /**
  12997. * @file subs-caps-menu-item.js
  12998. */
  12999. /**
  13000. * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
  13001. * in the SubsCapsMenu.
  13002. *
  13003. * @extends TextTrackMenuItem
  13004. */
  13005. var SubsCapsMenuItem = function (_TextTrackMenuItem) {
  13006. inherits(SubsCapsMenuItem, _TextTrackMenuItem);
  13007. function SubsCapsMenuItem() {
  13008. classCallCheck(this, SubsCapsMenuItem);
  13009. return possibleConstructorReturn(this, _TextTrackMenuItem.apply(this, arguments));
  13010. }
  13011. SubsCapsMenuItem.prototype.createEl = function createEl(type, props, attrs) {
  13012. var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label);
  13013. if (this.options_.track.kind === 'captions') {
  13014. innerHTML += '\n <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n <span class="vjs-control-text"> ' + this.localize('Captions') + '</span>\n ';
  13015. }
  13016. innerHTML += '</span>';
  13017. var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
  13018. innerHTML: innerHTML
  13019. }, props), attrs);
  13020. return el;
  13021. };
  13022. return SubsCapsMenuItem;
  13023. }(TextTrackMenuItem);
  13024. Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
  13025. /**
  13026. * @file sub-caps-button.js
  13027. */
  13028. /**
  13029. * The button component for toggling and selecting captions and/or subtitles
  13030. *
  13031. * @extends TextTrackButton
  13032. */
  13033. var SubsCapsButton = function (_TextTrackButton) {
  13034. inherits(SubsCapsButton, _TextTrackButton);
  13035. function SubsCapsButton(player) {
  13036. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  13037. classCallCheck(this, SubsCapsButton);
  13038. // Although North America uses "captions" in most cases for
  13039. // "captions and subtitles" other locales use "subtitles"
  13040. var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options));
  13041. _this.label_ = 'subtitles';
  13042. if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
  13043. _this.label_ = 'captions';
  13044. }
  13045. _this.menuButton_.controlText(toTitleCase(_this.label_));
  13046. return _this;
  13047. }
  13048. /**
  13049. * Builds the default DOM `className`.
  13050. *
  13051. * @return {string}
  13052. * The DOM `className` for this object.
  13053. */
  13054. SubsCapsButton.prototype.buildCSSClass = function buildCSSClass() {
  13055. return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
  13056. };
  13057. SubsCapsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13058. return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13059. };
  13060. /**
  13061. * Create caption/subtitles menu items
  13062. *
  13063. * @return {CaptionSettingsMenuItem[]}
  13064. * The array of current menu items.
  13065. */
  13066. SubsCapsButton.prototype.createItems = function createItems() {
  13067. var items = [];
  13068. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  13069. items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.label_ }));
  13070. this.hideThreshold_ += 1;
  13071. }
  13072. items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
  13073. return items;
  13074. };
  13075. return SubsCapsButton;
  13076. }(TextTrackButton);
  13077. /**
  13078. * `kind`s of TextTrack to look for to associate it with this menu.
  13079. *
  13080. * @type {array}
  13081. * @private
  13082. */
  13083. SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
  13084. /**
  13085. * The text that should display over the `SubsCapsButton`s controls.
  13086. *
  13087. *
  13088. * @type {string}
  13089. * @private
  13090. */
  13091. SubsCapsButton.prototype.controlText_ = 'Subtitles';
  13092. Component.registerComponent('SubsCapsButton', SubsCapsButton);
  13093. /**
  13094. * @file audio-track-menu-item.js
  13095. */
  13096. /**
  13097. * An {@link AudioTrack} {@link MenuItem}
  13098. *
  13099. * @extends MenuItem
  13100. */
  13101. var AudioTrackMenuItem = function (_MenuItem) {
  13102. inherits(AudioTrackMenuItem, _MenuItem);
  13103. /**
  13104. * Creates an instance of this class.
  13105. *
  13106. * @param {Player} player
  13107. * The `Player` that this class should be attached to.
  13108. *
  13109. * @param {Object} [options]
  13110. * The key/value store of player options.
  13111. */
  13112. function AudioTrackMenuItem(player, options) {
  13113. classCallCheck(this, AudioTrackMenuItem);
  13114. var track = options.track;
  13115. var tracks = player.audioTracks();
  13116. // Modify options for parent MenuItem class's init.
  13117. options.label = track.label || track.language || 'Unknown';
  13118. options.selected = track.enabled;
  13119. var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
  13120. _this.track = track;
  13121. _this.addClass('vjs-' + track.kind + '-menu-item');
  13122. var changeHandler = function changeHandler() {
  13123. for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
  13124. args[_key] = arguments[_key];
  13125. }
  13126. _this.handleTracksChange.apply(_this, args);
  13127. };
  13128. tracks.addEventListener('change', changeHandler);
  13129. _this.on('dispose', function () {
  13130. tracks.removeEventListener('change', changeHandler);
  13131. });
  13132. return _this;
  13133. }
  13134. AudioTrackMenuItem.prototype.createEl = function createEl(type, props, attrs) {
  13135. var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label);
  13136. if (this.options_.track.kind === 'main-desc') {
  13137. innerHTML += '\n <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n <span class="vjs-control-text"> ' + this.localize('Descriptions') + '</span>\n ';
  13138. }
  13139. innerHTML += '</span>';
  13140. var el = _MenuItem.prototype.createEl.call(this, type, assign({
  13141. innerHTML: innerHTML
  13142. }, props), attrs);
  13143. return el;
  13144. };
  13145. /**
  13146. * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
  13147. * for more detailed information on what a click can be.
  13148. *
  13149. * @param {EventTarget~Event} [event]
  13150. * The `keydown`, `tap`, or `click` event that caused this function to be
  13151. * called.
  13152. *
  13153. * @listens tap
  13154. * @listens click
  13155. */
  13156. AudioTrackMenuItem.prototype.handleClick = function handleClick(event) {
  13157. var tracks = this.player_.audioTracks();
  13158. _MenuItem.prototype.handleClick.call(this, event);
  13159. for (var i = 0; i < tracks.length; i++) {
  13160. var track = tracks[i];
  13161. track.enabled = track === this.track;
  13162. }
  13163. };
  13164. /**
  13165. * Handle any {@link AudioTrack} change.
  13166. *
  13167. * @param {EventTarget~Event} [event]
  13168. * The {@link AudioTrackList#change} event that caused this to run.
  13169. *
  13170. * @listens AudioTrackList#change
  13171. */
  13172. AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
  13173. this.selected(this.track.enabled);
  13174. };
  13175. return AudioTrackMenuItem;
  13176. }(MenuItem);
  13177. Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
  13178. /**
  13179. * @file audio-track-button.js
  13180. */
  13181. /**
  13182. * The base class for buttons that toggle specific {@link AudioTrack} types.
  13183. *
  13184. * @extends TrackButton
  13185. */
  13186. var AudioTrackButton = function (_TrackButton) {
  13187. inherits(AudioTrackButton, _TrackButton);
  13188. /**
  13189. * Creates an instance of this class.
  13190. *
  13191. * @param {Player} player
  13192. * The `Player` that this class should be attached to.
  13193. *
  13194. * @param {Object} [options={}]
  13195. * The key/value store of player options.
  13196. */
  13197. function AudioTrackButton(player) {
  13198. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  13199. classCallCheck(this, AudioTrackButton);
  13200. options.tracks = player.audioTracks();
  13201. return possibleConstructorReturn(this, _TrackButton.call(this, player, options));
  13202. }
  13203. /**
  13204. * Builds the default DOM `className`.
  13205. *
  13206. * @return {string}
  13207. * The DOM `className` for this object.
  13208. */
  13209. AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() {
  13210. return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this);
  13211. };
  13212. AudioTrackButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13213. return 'vjs-audio-button ' + _TrackButton.prototype.buildWrapperCSSClass.call(this);
  13214. };
  13215. /**
  13216. * Create a menu item for each audio track
  13217. *
  13218. * @param {AudioTrackMenuItem[]} [items=[]]
  13219. * An array of existing menu items to use.
  13220. *
  13221. * @return {AudioTrackMenuItem[]}
  13222. * An array of menu items
  13223. */
  13224. AudioTrackButton.prototype.createItems = function createItems() {
  13225. var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  13226. // if there's only one audio track, there no point in showing it
  13227. this.hideThreshold_ = 1;
  13228. var tracks = this.player_.audioTracks();
  13229. for (var i = 0; i < tracks.length; i++) {
  13230. var track = tracks[i];
  13231. items.push(new AudioTrackMenuItem(this.player_, {
  13232. track: track,
  13233. // MenuItem is selectable
  13234. selectable: true,
  13235. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  13236. multiSelectable: false
  13237. }));
  13238. }
  13239. return items;
  13240. };
  13241. return AudioTrackButton;
  13242. }(TrackButton);
  13243. /**
  13244. * The text that should display over the `AudioTrackButton`s controls. Added for localization.
  13245. *
  13246. * @type {string}
  13247. * @private
  13248. */
  13249. AudioTrackButton.prototype.controlText_ = 'Audio Track';
  13250. Component.registerComponent('AudioTrackButton', AudioTrackButton);
  13251. /**
  13252. * @file playback-rate-menu-item.js
  13253. */
  13254. /**
  13255. * The specific menu item type for selecting a playback rate.
  13256. *
  13257. * @extends MenuItem
  13258. */
  13259. var PlaybackRateMenuItem = function (_MenuItem) {
  13260. inherits(PlaybackRateMenuItem, _MenuItem);
  13261. /**
  13262. * Creates an instance of this class.
  13263. *
  13264. * @param {Player} player
  13265. * The `Player` that this class should be attached to.
  13266. *
  13267. * @param {Object} [options]
  13268. * The key/value store of player options.
  13269. */
  13270. function PlaybackRateMenuItem(player, options) {
  13271. classCallCheck(this, PlaybackRateMenuItem);
  13272. var label = options.rate;
  13273. var rate = parseFloat(label, 10);
  13274. // Modify options for parent MenuItem class's init.
  13275. options.label = label;
  13276. options.selected = rate === 1;
  13277. options.selectable = true;
  13278. options.multiSelectable = false;
  13279. var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
  13280. _this.label = label;
  13281. _this.rate = rate;
  13282. _this.on(player, 'ratechange', _this.update);
  13283. return _this;
  13284. }
  13285. /**
  13286. * This gets called when an `PlaybackRateMenuItem` is "clicked". See
  13287. * {@link ClickableComponent} for more detailed information on what a click can be.
  13288. *
  13289. * @param {EventTarget~Event} [event]
  13290. * The `keydown`, `tap`, or `click` event that caused this function to be
  13291. * called.
  13292. *
  13293. * @listens tap
  13294. * @listens click
  13295. */
  13296. PlaybackRateMenuItem.prototype.handleClick = function handleClick(event) {
  13297. _MenuItem.prototype.handleClick.call(this);
  13298. this.player().playbackRate(this.rate);
  13299. };
  13300. /**
  13301. * Update the PlaybackRateMenuItem when the playbackrate changes.
  13302. *
  13303. * @param {EventTarget~Event} [event]
  13304. * The `ratechange` event that caused this function to run.
  13305. *
  13306. * @listens Player#ratechange
  13307. */
  13308. PlaybackRateMenuItem.prototype.update = function update(event) {
  13309. this.selected(this.player().playbackRate() === this.rate);
  13310. };
  13311. return PlaybackRateMenuItem;
  13312. }(MenuItem);
  13313. /**
  13314. * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
  13315. *
  13316. * @type {string}
  13317. * @private
  13318. */
  13319. PlaybackRateMenuItem.prototype.contentElType = 'button';
  13320. Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
  13321. /**
  13322. * @file playback-rate-menu-button.js
  13323. */
  13324. /**
  13325. * The component for controlling the playback rate.
  13326. *
  13327. * @extends MenuButton
  13328. */
  13329. var PlaybackRateMenuButton = function (_MenuButton) {
  13330. inherits(PlaybackRateMenuButton, _MenuButton);
  13331. /**
  13332. * Creates an instance of this class.
  13333. *
  13334. * @param {Player} player
  13335. * The `Player` that this class should be attached to.
  13336. *
  13337. * @param {Object} [options]
  13338. * The key/value store of player options.
  13339. */
  13340. function PlaybackRateMenuButton(player, options) {
  13341. classCallCheck(this, PlaybackRateMenuButton);
  13342. var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options));
  13343. _this.updateVisibility();
  13344. _this.updateLabel();
  13345. _this.on(player, 'loadstart', _this.updateVisibility);
  13346. _this.on(player, 'ratechange', _this.updateLabel);
  13347. return _this;
  13348. }
  13349. /**
  13350. * Create the `Component`'s DOM element
  13351. *
  13352. * @return {Element}
  13353. * The element that was created.
  13354. */
  13355. PlaybackRateMenuButton.prototype.createEl = function createEl$$1() {
  13356. var el = _MenuButton.prototype.createEl.call(this);
  13357. this.labelEl_ = createEl('div', {
  13358. className: 'vjs-playback-rate-value',
  13359. innerHTML: '1x'
  13360. });
  13361. el.appendChild(this.labelEl_);
  13362. return el;
  13363. };
  13364. PlaybackRateMenuButton.prototype.dispose = function dispose() {
  13365. this.labelEl_ = null;
  13366. _MenuButton.prototype.dispose.call(this);
  13367. };
  13368. /**
  13369. * Builds the default DOM `className`.
  13370. *
  13371. * @return {string}
  13372. * The DOM `className` for this object.
  13373. */
  13374. PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() {
  13375. return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this);
  13376. };
  13377. PlaybackRateMenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13378. return 'vjs-playback-rate ' + _MenuButton.prototype.buildWrapperCSSClass.call(this);
  13379. };
  13380. /**
  13381. * Create the playback rate menu
  13382. *
  13383. * @return {Menu}
  13384. * Menu object populated with {@link PlaybackRateMenuItem}s
  13385. */
  13386. PlaybackRateMenuButton.prototype.createMenu = function createMenu() {
  13387. var menu = new Menu(this.player());
  13388. var rates = this.playbackRates();
  13389. if (rates) {
  13390. for (var i = rates.length - 1; i >= 0; i--) {
  13391. menu.addChild(new PlaybackRateMenuItem(this.player(), { rate: rates[i] + 'x' }));
  13392. }
  13393. }
  13394. return menu;
  13395. };
  13396. /**
  13397. * Updates ARIA accessibility attributes
  13398. */
  13399. PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() {
  13400. // Current playback rate
  13401. this.el().setAttribute('aria-valuenow', this.player().playbackRate());
  13402. };
  13403. /**
  13404. * This gets called when an `PlaybackRateMenuButton` is "clicked". See
  13405. * {@link ClickableComponent} for more detailed information on what a click can be.
  13406. *
  13407. * @param {EventTarget~Event} [event]
  13408. * The `keydown`, `tap`, or `click` event that caused this function to be
  13409. * called.
  13410. *
  13411. * @listens tap
  13412. * @listens click
  13413. */
  13414. PlaybackRateMenuButton.prototype.handleClick = function handleClick(event) {
  13415. // select next rate option
  13416. var currentRate = this.player().playbackRate();
  13417. var rates = this.playbackRates();
  13418. // this will select first one if the last one currently selected
  13419. var newRate = rates[0];
  13420. for (var i = 0; i < rates.length; i++) {
  13421. if (rates[i] > currentRate) {
  13422. newRate = rates[i];
  13423. break;
  13424. }
  13425. }
  13426. this.player().playbackRate(newRate);
  13427. };
  13428. /**
  13429. * Get possible playback rates
  13430. *
  13431. * @return {Array}
  13432. * All possible playback rates
  13433. */
  13434. PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() {
  13435. return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
  13436. };
  13437. /**
  13438. * Get whether playback rates is supported by the tech
  13439. * and an array of playback rates exists
  13440. *
  13441. * @return {boolean}
  13442. * Whether changing playback rate is supported
  13443. */
  13444. PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() {
  13445. return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
  13446. };
  13447. /**
  13448. * Hide playback rate controls when they're no playback rate options to select
  13449. *
  13450. * @param {EventTarget~Event} [event]
  13451. * The event that caused this function to run.
  13452. *
  13453. * @listens Player#loadstart
  13454. */
  13455. PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility(event) {
  13456. if (this.playbackRateSupported()) {
  13457. this.removeClass('vjs-hidden');
  13458. } else {
  13459. this.addClass('vjs-hidden');
  13460. }
  13461. };
  13462. /**
  13463. * Update button label when rate changed
  13464. *
  13465. * @param {EventTarget~Event} [event]
  13466. * The event that caused this function to run.
  13467. *
  13468. * @listens Player#ratechange
  13469. */
  13470. PlaybackRateMenuButton.prototype.updateLabel = function updateLabel(event) {
  13471. if (this.playbackRateSupported()) {
  13472. this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
  13473. }
  13474. };
  13475. return PlaybackRateMenuButton;
  13476. }(MenuButton);
  13477. /**
  13478. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  13479. *
  13480. * @type {string}
  13481. * @private
  13482. */
  13483. PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
  13484. Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
  13485. /**
  13486. * @file spacer.js
  13487. */
  13488. /**
  13489. * Just an empty spacer element that can be used as an append point for plugins, etc.
  13490. * Also can be used to create space between elements when necessary.
  13491. *
  13492. * @extends Component
  13493. */
  13494. var Spacer = function (_Component) {
  13495. inherits(Spacer, _Component);
  13496. function Spacer() {
  13497. classCallCheck(this, Spacer);
  13498. return possibleConstructorReturn(this, _Component.apply(this, arguments));
  13499. }
  13500. /**
  13501. * Builds the default DOM `className`.
  13502. *
  13503. * @return {string}
  13504. * The DOM `className` for this object.
  13505. */
  13506. Spacer.prototype.buildCSSClass = function buildCSSClass() {
  13507. return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this);
  13508. };
  13509. /**
  13510. * Create the `Component`'s DOM element
  13511. *
  13512. * @return {Element}
  13513. * The element that was created.
  13514. */
  13515. Spacer.prototype.createEl = function createEl() {
  13516. return _Component.prototype.createEl.call(this, 'div', {
  13517. className: this.buildCSSClass()
  13518. });
  13519. };
  13520. return Spacer;
  13521. }(Component);
  13522. Component.registerComponent('Spacer', Spacer);
  13523. /**
  13524. * @file custom-control-spacer.js
  13525. */
  13526. /**
  13527. * Spacer specifically meant to be used as an insertion point for new plugins, etc.
  13528. *
  13529. * @extends Spacer
  13530. */
  13531. var CustomControlSpacer = function (_Spacer) {
  13532. inherits(CustomControlSpacer, _Spacer);
  13533. function CustomControlSpacer() {
  13534. classCallCheck(this, CustomControlSpacer);
  13535. return possibleConstructorReturn(this, _Spacer.apply(this, arguments));
  13536. }
  13537. /**
  13538. * Builds the default DOM `className`.
  13539. *
  13540. * @return {string}
  13541. * The DOM `className` for this object.
  13542. */
  13543. CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() {
  13544. return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this);
  13545. };
  13546. /**
  13547. * Create the `Component`'s DOM element
  13548. *
  13549. * @return {Element}
  13550. * The element that was created.
  13551. */
  13552. CustomControlSpacer.prototype.createEl = function createEl() {
  13553. var el = _Spacer.prototype.createEl.call(this, {
  13554. className: this.buildCSSClass()
  13555. });
  13556. // No-flex/table-cell mode requires there be some content
  13557. // in the cell to fill the remaining space of the table.
  13558. el.innerHTML = '\xA0';
  13559. return el;
  13560. };
  13561. return CustomControlSpacer;
  13562. }(Spacer);
  13563. Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
  13564. /**
  13565. * @file control-bar.js
  13566. */
  13567. // Required children
  13568. /**
  13569. * Container of main controls.
  13570. *
  13571. * @extends Component
  13572. */
  13573. var ControlBar = function (_Component) {
  13574. inherits(ControlBar, _Component);
  13575. function ControlBar() {
  13576. classCallCheck(this, ControlBar);
  13577. return possibleConstructorReturn(this, _Component.apply(this, arguments));
  13578. }
  13579. /**
  13580. * Create the `Component`'s DOM element
  13581. *
  13582. * @return {Element}
  13583. * The element that was created.
  13584. */
  13585. ControlBar.prototype.createEl = function createEl() {
  13586. return _Component.prototype.createEl.call(this, 'div', {
  13587. className: 'vjs-control-bar',
  13588. dir: 'ltr'
  13589. });
  13590. };
  13591. return ControlBar;
  13592. }(Component);
  13593. /**
  13594. * Default options for `ControlBar`
  13595. *
  13596. * @type {Object}
  13597. * @private
  13598. */
  13599. ControlBar.prototype.options_ = {
  13600. children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
  13601. };
  13602. Component.registerComponent('ControlBar', ControlBar);
  13603. /**
  13604. * @file error-display.js
  13605. */
  13606. /**
  13607. * A display that indicates an error has occurred. This means that the video
  13608. * is unplayable.
  13609. *
  13610. * @extends ModalDialog
  13611. */
  13612. var ErrorDisplay = function (_ModalDialog) {
  13613. inherits(ErrorDisplay, _ModalDialog);
  13614. /**
  13615. * Creates an instance of this class.
  13616. *
  13617. * @param {Player} player
  13618. * The `Player` that this class should be attached to.
  13619. *
  13620. * @param {Object} [options]
  13621. * The key/value store of player options.
  13622. */
  13623. function ErrorDisplay(player, options) {
  13624. classCallCheck(this, ErrorDisplay);
  13625. var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options));
  13626. _this.on(player, 'error', _this.open);
  13627. return _this;
  13628. }
  13629. /**
  13630. * Builds the default DOM `className`.
  13631. *
  13632. * @return {string}
  13633. * The DOM `className` for this object.
  13634. *
  13635. * @deprecated Since version 5.
  13636. */
  13637. ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() {
  13638. return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this);
  13639. };
  13640. /**
  13641. * Gets the localized error message based on the `Player`s error.
  13642. *
  13643. * @return {string}
  13644. * The `Player`s error message localized or an empty string.
  13645. */
  13646. ErrorDisplay.prototype.content = function content() {
  13647. var error = this.player().error();
  13648. return error ? this.localize(error.message) : '';
  13649. };
  13650. return ErrorDisplay;
  13651. }(ModalDialog);
  13652. /**
  13653. * The default options for an `ErrorDisplay`.
  13654. *
  13655. * @private
  13656. */
  13657. ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
  13658. pauseOnOpen: false,
  13659. fillAlways: true,
  13660. temporary: false,
  13661. uncloseable: true
  13662. });
  13663. Component.registerComponent('ErrorDisplay', ErrorDisplay);
  13664. /**
  13665. * @file text-track-settings.js
  13666. */
  13667. var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
  13668. var COLOR_BLACK = ['#000', 'Black'];
  13669. var COLOR_BLUE = ['#00F', 'Blue'];
  13670. var COLOR_CYAN = ['#0FF', 'Cyan'];
  13671. var COLOR_GREEN = ['#0F0', 'Green'];
  13672. var COLOR_MAGENTA = ['#F0F', 'Magenta'];
  13673. var COLOR_RED = ['#F00', 'Red'];
  13674. var COLOR_WHITE = ['#FFF', 'White'];
  13675. var COLOR_YELLOW = ['#FF0', 'Yellow'];
  13676. var OPACITY_OPAQUE = ['1', 'Opaque'];
  13677. var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
  13678. var OPACITY_TRANS = ['0', 'Transparent'];
  13679. // Configuration for the various <select> elements in the DOM of this component.
  13680. //
  13681. // Possible keys include:
  13682. //
  13683. // `default`:
  13684. // The default option index. Only needs to be provided if not zero.
  13685. // `parser`:
  13686. // A function which is used to parse the value from the selected option in
  13687. // a customized way.
  13688. // `selector`:
  13689. // The selector used to find the associated <select> element.
  13690. var selectConfigs = {
  13691. backgroundColor: {
  13692. selector: '.vjs-bg-color > select',
  13693. id: 'captions-background-color-%s',
  13694. label: 'Color',
  13695. options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  13696. },
  13697. backgroundOpacity: {
  13698. selector: '.vjs-bg-opacity > select',
  13699. id: 'captions-background-opacity-%s',
  13700. label: 'Transparency',
  13701. options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
  13702. },
  13703. color: {
  13704. selector: '.vjs-fg-color > select',
  13705. id: 'captions-foreground-color-%s',
  13706. label: 'Color',
  13707. options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  13708. },
  13709. edgeStyle: {
  13710. selector: '.vjs-edge-style > select',
  13711. id: '%s',
  13712. label: 'Text Edge Style',
  13713. options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
  13714. },
  13715. fontFamily: {
  13716. selector: '.vjs-font-family > select',
  13717. id: 'captions-font-family-%s',
  13718. label: 'Font Family',
  13719. options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
  13720. },
  13721. fontPercent: {
  13722. selector: '.vjs-font-percent > select',
  13723. id: 'captions-font-size-%s',
  13724. label: 'Font Size',
  13725. options: [['0.50', '50%'], ['0.75', '75%'], ['1.00', '100%'], ['1.25', '125%'], ['1.50', '150%'], ['1.75', '175%'], ['2.00', '200%'], ['3.00', '300%'], ['4.00', '400%']],
  13726. 'default': 2,
  13727. parser: function parser(v) {
  13728. return v === '1.00' ? null : Number(v);
  13729. }
  13730. },
  13731. textOpacity: {
  13732. selector: '.vjs-text-opacity > select',
  13733. id: 'captions-foreground-opacity-%s',
  13734. label: 'Transparency',
  13735. options: [OPACITY_OPAQUE, OPACITY_SEMI]
  13736. },
  13737. // Options for this object are defined below.
  13738. windowColor: {
  13739. selector: '.vjs-window-color > select',
  13740. id: 'captions-window-color-%s',
  13741. label: 'Color'
  13742. },
  13743. // Options for this object are defined below.
  13744. windowOpacity: {
  13745. selector: '.vjs-window-opacity > select',
  13746. id: 'captions-window-opacity-%s',
  13747. label: 'Transparency',
  13748. options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
  13749. }
  13750. };
  13751. selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
  13752. /**
  13753. * Get the actual value of an option.
  13754. *
  13755. * @param {string} value
  13756. * The value to get
  13757. *
  13758. * @param {Function} [parser]
  13759. * Optional function to adjust the value.
  13760. *
  13761. * @return {Mixed}
  13762. * - Will be `undefined` if no value exists
  13763. * - Will be `undefined` if the given value is "none".
  13764. * - Will be the actual value otherwise.
  13765. *
  13766. * @private
  13767. */
  13768. function parseOptionValue(value, parser) {
  13769. if (parser) {
  13770. value = parser(value);
  13771. }
  13772. if (value && value !== 'none') {
  13773. return value;
  13774. }
  13775. }
  13776. /**
  13777. * Gets the value of the selected <option> element within a <select> element.
  13778. *
  13779. * @param {Element} el
  13780. * the element to look in
  13781. *
  13782. * @param {Function} [parser]
  13783. * Optional function to adjust the value.
  13784. *
  13785. * @return {Mixed}
  13786. * - Will be `undefined` if no value exists
  13787. * - Will be `undefined` if the given value is "none".
  13788. * - Will be the actual value otherwise.
  13789. *
  13790. * @private
  13791. */
  13792. function getSelectedOptionValue(el, parser) {
  13793. var value = el.options[el.options.selectedIndex].value;
  13794. return parseOptionValue(value, parser);
  13795. }
  13796. /**
  13797. * Sets the selected <option> element within a <select> element based on a
  13798. * given value.
  13799. *
  13800. * @param {Element} el
  13801. * The element to look in.
  13802. *
  13803. * @param {string} value
  13804. * the property to look on.
  13805. *
  13806. * @param {Function} [parser]
  13807. * Optional function to adjust the value before comparing.
  13808. *
  13809. * @private
  13810. */
  13811. function setSelectedOption(el, value, parser) {
  13812. if (!value) {
  13813. return;
  13814. }
  13815. for (var i = 0; i < el.options.length; i++) {
  13816. if (parseOptionValue(el.options[i].value, parser) === value) {
  13817. el.selectedIndex = i;
  13818. break;
  13819. }
  13820. }
  13821. }
  13822. /**
  13823. * Manipulate Text Tracks settings.
  13824. *
  13825. * @extends ModalDialog
  13826. */
  13827. var TextTrackSettings = function (_ModalDialog) {
  13828. inherits(TextTrackSettings, _ModalDialog);
  13829. /**
  13830. * Creates an instance of this class.
  13831. *
  13832. * @param {Player} player
  13833. * The `Player` that this class should be attached to.
  13834. *
  13835. * @param {Object} [options]
  13836. * The key/value store of player options.
  13837. */
  13838. function TextTrackSettings(player, options) {
  13839. classCallCheck(this, TextTrackSettings);
  13840. options.temporary = false;
  13841. var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options));
  13842. _this.updateDisplay = bind(_this, _this.updateDisplay);
  13843. // fill the modal and pretend we have opened it
  13844. _this.fill();
  13845. _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
  13846. _this.endDialog = createEl('p', {
  13847. className: 'vjs-control-text',
  13848. textContent: _this.localize('End of dialog window.')
  13849. });
  13850. _this.el().appendChild(_this.endDialog);
  13851. _this.setDefaults();
  13852. // Grab `persistTextTrackSettings` from the player options if not passed in child options
  13853. if (options.persistTextTrackSettings === undefined) {
  13854. _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
  13855. }
  13856. _this.on(_this.$('.vjs-done-button'), 'click', function () {
  13857. _this.saveSettings();
  13858. _this.close();
  13859. });
  13860. _this.on(_this.$('.vjs-default-button'), 'click', function () {
  13861. _this.setDefaults();
  13862. _this.updateDisplay();
  13863. });
  13864. each(selectConfigs, function (config) {
  13865. _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
  13866. });
  13867. if (_this.options_.persistTextTrackSettings) {
  13868. _this.restoreSettings();
  13869. }
  13870. return _this;
  13871. }
  13872. TextTrackSettings.prototype.dispose = function dispose() {
  13873. this.endDialog = null;
  13874. _ModalDialog.prototype.dispose.call(this);
  13875. };
  13876. /**
  13877. * Create a <select> element with configured options.
  13878. *
  13879. * @param {string} key
  13880. * Configuration key to use during creation.
  13881. *
  13882. * @return {string}
  13883. * An HTML string.
  13884. *
  13885. * @private
  13886. */
  13887. TextTrackSettings.prototype.createElSelect_ = function createElSelect_(key) {
  13888. var _this2 = this;
  13889. var legendId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
  13890. var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'label';
  13891. var config = selectConfigs[key];
  13892. var id = config.id.replace('%s', this.id_);
  13893. var selectLabelledbyIds = [legendId, id].join(' ').trim();
  13894. return ['<' + type + ' id="' + id + '" class="' + (type === 'label' ? 'vjs-label' : '') + '">', this.localize(config.label), '</' + type + '>', '<select aria-labelledby="' + selectLabelledbyIds + '">'].concat(config.options.map(function (o) {
  13895. var optionId = id + '-' + o[1].replace(/\W+/g, '');
  13896. return ['<option id="' + optionId + '" value="' + o[0] + '" ', 'aria-labelledby="' + selectLabelledbyIds + ' ' + optionId + '">', _this2.localize(o[1]), '</option>'].join('');
  13897. })).concat('</select>').join('');
  13898. };
  13899. /**
  13900. * Create foreground color element for the component
  13901. *
  13902. * @return {string}
  13903. * An HTML string.
  13904. *
  13905. * @private
  13906. */
  13907. TextTrackSettings.prototype.createElFgColor_ = function createElFgColor_() {
  13908. var legendId = 'captions-text-legend-' + this.id_;
  13909. return ['<fieldset class="vjs-fg-color vjs-track-setting">', '<legend id="' + legendId + '">', this.localize('Text'), '</legend>', this.createElSelect_('color', legendId), '<span class="vjs-text-opacity vjs-opacity">', this.createElSelect_('textOpacity', legendId), '</span>', '</fieldset>'].join('');
  13910. };
  13911. /**
  13912. * Create background color element for the component
  13913. *
  13914. * @return {string}
  13915. * An HTML string.
  13916. *
  13917. * @private
  13918. */
  13919. TextTrackSettings.prototype.createElBgColor_ = function createElBgColor_() {
  13920. var legendId = 'captions-background-' + this.id_;
  13921. return ['<fieldset class="vjs-bg-color vjs-track-setting">', '<legend id="' + legendId + '">', this.localize('Background'), '</legend>', this.createElSelect_('backgroundColor', legendId), '<span class="vjs-bg-opacity vjs-opacity">', this.createElSelect_('backgroundOpacity', legendId), '</span>', '</fieldset>'].join('');
  13922. };
  13923. /**
  13924. * Create window color element for the component
  13925. *
  13926. * @return {string}
  13927. * An HTML string.
  13928. *
  13929. * @private
  13930. */
  13931. TextTrackSettings.prototype.createElWinColor_ = function createElWinColor_() {
  13932. var legendId = 'captions-window-' + this.id_;
  13933. return ['<fieldset class="vjs-window-color vjs-track-setting">', '<legend id="' + legendId + '">', this.localize('Window'), '</legend>', this.createElSelect_('windowColor', legendId), '<span class="vjs-window-opacity vjs-opacity">', this.createElSelect_('windowOpacity', legendId), '</span>', '</fieldset>'].join('');
  13934. };
  13935. /**
  13936. * Create color elements for the component
  13937. *
  13938. * @return {Element}
  13939. * The element that was created
  13940. *
  13941. * @private
  13942. */
  13943. TextTrackSettings.prototype.createElColors_ = function createElColors_() {
  13944. return createEl('div', {
  13945. className: 'vjs-track-settings-colors',
  13946. innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
  13947. });
  13948. };
  13949. /**
  13950. * Create font elements for the component
  13951. *
  13952. * @return {Element}
  13953. * The element that was created.
  13954. *
  13955. * @private
  13956. */
  13957. TextTrackSettings.prototype.createElFont_ = function createElFont_() {
  13958. return createEl('div', {
  13959. className: 'vjs-track-settings-font',
  13960. innerHTML: ['<fieldset class="vjs-font-percent vjs-track-setting">', this.createElSelect_('fontPercent', '', 'legend'), '</fieldset>', '<fieldset class="vjs-edge-style vjs-track-setting">', this.createElSelect_('edgeStyle', '', 'legend'), '</fieldset>', '<fieldset class="vjs-font-family vjs-track-setting">', this.createElSelect_('fontFamily', '', 'legend'), '</fieldset>'].join('')
  13961. });
  13962. };
  13963. /**
  13964. * Create controls for the component
  13965. *
  13966. * @return {Element}
  13967. * The element that was created.
  13968. *
  13969. * @private
  13970. */
  13971. TextTrackSettings.prototype.createElControls_ = function createElControls_() {
  13972. var defaultsDescription = this.localize('restore all settings to the default values');
  13973. return createEl('div', {
  13974. className: 'vjs-track-settings-controls',
  13975. innerHTML: ['<button type="button" class="vjs-default-button" title="' + defaultsDescription + '">', this.localize('Reset'), '<span class="vjs-control-text"> ' + defaultsDescription + '</span>', '</button>', '<button type="button" class="vjs-done-button">' + this.localize('Done') + '</button>'].join('')
  13976. });
  13977. };
  13978. TextTrackSettings.prototype.content = function content() {
  13979. return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
  13980. };
  13981. TextTrackSettings.prototype.label = function label() {
  13982. return this.localize('Caption Settings Dialog');
  13983. };
  13984. TextTrackSettings.prototype.description = function description() {
  13985. return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
  13986. };
  13987. TextTrackSettings.prototype.buildCSSClass = function buildCSSClass() {
  13988. return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
  13989. };
  13990. /**
  13991. * Gets an object of text track settings (or null).
  13992. *
  13993. * @return {Object}
  13994. * An object with config values parsed from the DOM or localStorage.
  13995. */
  13996. TextTrackSettings.prototype.getValues = function getValues() {
  13997. var _this3 = this;
  13998. return reduce(selectConfigs, function (accum, config, key) {
  13999. var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
  14000. if (value !== undefined) {
  14001. accum[key] = value;
  14002. }
  14003. return accum;
  14004. }, {});
  14005. };
  14006. /**
  14007. * Sets text track settings from an object of values.
  14008. *
  14009. * @param {Object} values
  14010. * An object with config values parsed from the DOM or localStorage.
  14011. */
  14012. TextTrackSettings.prototype.setValues = function setValues(values) {
  14013. var _this4 = this;
  14014. each(selectConfigs, function (config, key) {
  14015. setSelectedOption(_this4.$(config.selector), values[key], config.parser);
  14016. });
  14017. };
  14018. /**
  14019. * Sets all `<select>` elements to their default values.
  14020. */
  14021. TextTrackSettings.prototype.setDefaults = function setDefaults() {
  14022. var _this5 = this;
  14023. each(selectConfigs, function (config) {
  14024. var index = config.hasOwnProperty('default') ? config['default'] : 0;
  14025. _this5.$(config.selector).selectedIndex = index;
  14026. });
  14027. };
  14028. /**
  14029. * Restore texttrack settings from localStorage
  14030. */
  14031. TextTrackSettings.prototype.restoreSettings = function restoreSettings() {
  14032. var values = void 0;
  14033. try {
  14034. values = JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_KEY));
  14035. } catch (err) {
  14036. log.warn(err);
  14037. }
  14038. if (values) {
  14039. this.setValues(values);
  14040. }
  14041. };
  14042. /**
  14043. * Save text track settings to localStorage
  14044. */
  14045. TextTrackSettings.prototype.saveSettings = function saveSettings() {
  14046. if (!this.options_.persistTextTrackSettings) {
  14047. return;
  14048. }
  14049. var values = this.getValues();
  14050. try {
  14051. if (Object.keys(values).length) {
  14052. window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
  14053. } else {
  14054. window.localStorage.removeItem(LOCAL_STORAGE_KEY);
  14055. }
  14056. } catch (err) {
  14057. log.warn(err);
  14058. }
  14059. };
  14060. /**
  14061. * Update display of text track settings
  14062. */
  14063. TextTrackSettings.prototype.updateDisplay = function updateDisplay() {
  14064. var ttDisplay = this.player_.getChild('textTrackDisplay');
  14065. if (ttDisplay) {
  14066. ttDisplay.updateDisplay();
  14067. }
  14068. };
  14069. /**
  14070. * conditionally blur the element and refocus the captions button
  14071. *
  14072. * @private
  14073. */
  14074. TextTrackSettings.prototype.conditionalBlur_ = function conditionalBlur_() {
  14075. this.previouslyActiveEl_ = null;
  14076. this.off(document, 'keydown', this.handleKeyDown);
  14077. var cb = this.player_.controlBar;
  14078. var subsCapsBtn = cb && cb.subsCapsButton;
  14079. var ccBtn = cb && cb.captionsButton;
  14080. if (subsCapsBtn) {
  14081. subsCapsBtn.focus();
  14082. } else if (ccBtn) {
  14083. ccBtn.focus();
  14084. }
  14085. };
  14086. return TextTrackSettings;
  14087. }(ModalDialog);
  14088. Component.registerComponent('TextTrackSettings', TextTrackSettings);
  14089. /**
  14090. * @file resize-manager.js
  14091. */
  14092. /**
  14093. * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
  14094. *
  14095. * It'll either create an iframe and use a debounced resize handler on it or use the new {@link https://wicg.github.io/ResizeObserver/|ResizeObserver}.
  14096. *
  14097. * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
  14098. * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
  14099. * @example <caption>How to disable the resize manager</caption>
  14100. * const player = videojs('#vid', {
  14101. * resizeManager: false
  14102. * });
  14103. *
  14104. * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
  14105. *
  14106. * @extends Component
  14107. */
  14108. var ResizeManager = function (_Component) {
  14109. inherits(ResizeManager, _Component);
  14110. /**
  14111. * Create the ResizeManager.
  14112. *
  14113. * @param {Object} player
  14114. * The `Player` that this class should be attached to.
  14115. *
  14116. * @param {Object} [options]
  14117. * The key/value store of ResizeManager options.
  14118. *
  14119. * @param {Object} [options.ResizeObserver]
  14120. * A polyfill for ResizeObserver can be passed in here.
  14121. * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
  14122. */
  14123. function ResizeManager(player, options) {
  14124. classCallCheck(this, ResizeManager);
  14125. var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window.ResizeObserver;
  14126. // if `null` was passed, we want to disable the ResizeObserver
  14127. if (options.ResizeObserver === null) {
  14128. RESIZE_OBSERVER_AVAILABLE = false;
  14129. }
  14130. // Only create an element when ResizeObserver isn't available
  14131. var options_ = mergeOptions({ createEl: !RESIZE_OBSERVER_AVAILABLE }, options);
  14132. var _this = possibleConstructorReturn(this, _Component.call(this, player, options_));
  14133. _this.ResizeObserver = options.ResizeObserver || window.ResizeObserver;
  14134. _this.loadListener_ = null;
  14135. _this.resizeObserver_ = null;
  14136. _this.debouncedHandler_ = debounce(function () {
  14137. _this.resizeHandler();
  14138. }, 100, false, _this);
  14139. if (RESIZE_OBSERVER_AVAILABLE) {
  14140. _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
  14141. _this.resizeObserver_.observe(player.el());
  14142. } else {
  14143. _this.loadListener_ = function () {
  14144. if (!_this.el_ || !_this.el_.contentWindow) {
  14145. return;
  14146. }
  14147. on(_this.el_.contentWindow, 'resize', _this.debouncedHandler_);
  14148. };
  14149. _this.one('load', _this.loadListener_);
  14150. }
  14151. return _this;
  14152. }
  14153. ResizeManager.prototype.createEl = function createEl() {
  14154. return _Component.prototype.createEl.call(this, 'iframe', {
  14155. className: 'vjs-resize-manager'
  14156. });
  14157. };
  14158. /**
  14159. * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
  14160. *
  14161. * @fires Player#playerresize
  14162. */
  14163. ResizeManager.prototype.resizeHandler = function resizeHandler() {
  14164. /**
  14165. * Called when the player size has changed
  14166. *
  14167. * @event Player#playerresize
  14168. * @type {EventTarget~Event}
  14169. */
  14170. // make sure player is still around to trigger
  14171. // prevents this from causing an error after dispose
  14172. if (!this.player_ || !this.player_.trigger) {
  14173. return;
  14174. }
  14175. this.player_.trigger('playerresize');
  14176. };
  14177. ResizeManager.prototype.dispose = function dispose() {
  14178. if (this.debouncedHandler_) {
  14179. this.debouncedHandler_.cancel();
  14180. }
  14181. if (this.resizeObserver_) {
  14182. if (this.player_.el()) {
  14183. this.resizeObserver_.unobserve(this.player_.el());
  14184. }
  14185. this.resizeObserver_.disconnect();
  14186. }
  14187. if (this.el_ && this.el_.contentWindow) {
  14188. off(this.el_.contentWindow, 'resize', this.debouncedHandler_);
  14189. }
  14190. if (this.loadListener_) {
  14191. this.off('load', this.loadListener_);
  14192. }
  14193. this.ResizeObserver = null;
  14194. this.resizeObserver = null;
  14195. this.debouncedHandler_ = null;
  14196. this.loadListener_ = null;
  14197. };
  14198. return ResizeManager;
  14199. }(Component);
  14200. Component.registerComponent('ResizeManager', ResizeManager);
  14201. /**
  14202. * This function is used to fire a sourceset when there is something
  14203. * similar to `mediaEl.load()` being called. It will try to find the source via
  14204. * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
  14205. * with the source that was found or empty string if we cannot know. If it cannot
  14206. * find a source then `sourceset` will not be fired.
  14207. *
  14208. * @param {Html5} tech
  14209. * The tech object that sourceset was setup on
  14210. *
  14211. * @return {boolean}
  14212. * returns false if the sourceset was not fired and true otherwise.
  14213. */
  14214. var sourcesetLoad = function sourcesetLoad(tech) {
  14215. var el = tech.el();
  14216. // if `el.src` is set, that source will be loaded.
  14217. if (el.hasAttribute('src')) {
  14218. tech.triggerSourceset(el.src);
  14219. return true;
  14220. }
  14221. /**
  14222. * Since there isn't a src property on the media element, source elements will be used for
  14223. * implementing the source selection algorithm. This happens asynchronously and
  14224. * for most cases were there is more than one source we cannot tell what source will
  14225. * be loaded, without re-implementing the source selection algorithm. At this time we are not
  14226. * going to do that. There are three special cases that we do handle here though:
  14227. *
  14228. * 1. If there are no sources, do not fire `sourceset`.
  14229. * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
  14230. * 3. If there is more than one `<source>` but all of them have the same `src` url.
  14231. * That will be our src.
  14232. */
  14233. var sources = tech.$$('source');
  14234. var srcUrls = [];
  14235. var src = '';
  14236. // if there are no sources, do not fire sourceset
  14237. if (!sources.length) {
  14238. return false;
  14239. }
  14240. // only count valid/non-duplicate source elements
  14241. for (var i = 0; i < sources.length; i++) {
  14242. var url = sources[i].src;
  14243. if (url && srcUrls.indexOf(url) === -1) {
  14244. srcUrls.push(url);
  14245. }
  14246. }
  14247. // there were no valid sources
  14248. if (!srcUrls.length) {
  14249. return false;
  14250. }
  14251. // there is only one valid source element url
  14252. // use that
  14253. if (srcUrls.length === 1) {
  14254. src = srcUrls[0];
  14255. }
  14256. tech.triggerSourceset(src);
  14257. return true;
  14258. };
  14259. /**
  14260. * our implementation of an `innerHTML` descriptor for browsers
  14261. * that do not have one.
  14262. */
  14263. var innerHTMLDescriptorPolyfill = {};
  14264. if (!IS_IE8) {
  14265. innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
  14266. get: function get() {
  14267. return this.cloneNode(true).innerHTML;
  14268. },
  14269. set: function set(v) {
  14270. // make a dummy node to use innerHTML on
  14271. var dummy = document.createElement(this.nodeName.toLowerCase());
  14272. // set innerHTML to the value provided
  14273. dummy.innerHTML = v;
  14274. // make a document fragment to hold the nodes from dummy
  14275. var docFrag = document.createDocumentFragment();
  14276. // copy all of the nodes created by the innerHTML on dummy
  14277. // to the document fragment
  14278. while (dummy.childNodes.length) {
  14279. docFrag.appendChild(dummy.childNodes[0]);
  14280. }
  14281. // remove content
  14282. this.innerText = '';
  14283. // now we add all of that html in one by appending the
  14284. // document fragment. This is how innerHTML does it.
  14285. window.Element.prototype.appendChild.call(this, docFrag);
  14286. // then return the result that innerHTML's setter would
  14287. return this.innerHTML;
  14288. }
  14289. });
  14290. }
  14291. /**
  14292. * Get a property descriptor given a list of priorities and the
  14293. * property to get.
  14294. */
  14295. var getDescriptor = function getDescriptor(priority, prop) {
  14296. var descriptor = {};
  14297. for (var i = 0; i < priority.length; i++) {
  14298. descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
  14299. if (descriptor && descriptor.set && descriptor.get) {
  14300. break;
  14301. }
  14302. }
  14303. descriptor.enumerable = true;
  14304. descriptor.configurable = true;
  14305. return descriptor;
  14306. };
  14307. var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
  14308. return getDescriptor([tech.el(), window.HTMLMediaElement.prototype, window.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
  14309. };
  14310. /**
  14311. * Patches browser internal functions so that we can tell syncronously
  14312. * if a `<source>` was appended to the media element. For some reason this
  14313. * causes a `sourceset` if the the media element is ready and has no source.
  14314. * This happens when:
  14315. * - The page has just loaded and the media element does not have a source.
  14316. * - The media element was emptied of all sources, then `load()` was called.
  14317. *
  14318. * It does this by patching the following functions/properties when they are supported:
  14319. *
  14320. * - `append()` - can be used to add a `<source>` element to the media element
  14321. * - `appendChild()` - can be used to add a `<source>` element to the media element
  14322. * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element
  14323. * - `innerHTML` - can be used to add a `<source>` element to the media element
  14324. *
  14325. * @param {Html5} tech
  14326. * The tech object that sourceset is being setup on.
  14327. */
  14328. var firstSourceWatch = function firstSourceWatch(tech) {
  14329. var el = tech.el();
  14330. // make sure firstSourceWatch isn't setup twice.
  14331. if (el.resetSourceWatch_) {
  14332. return;
  14333. }
  14334. var old = {};
  14335. var innerDescriptor = getInnerHTMLDescriptor(tech);
  14336. var appendWrapper = function appendWrapper(appendFn) {
  14337. return function () {
  14338. for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
  14339. args[_key] = arguments[_key];
  14340. }
  14341. var retval = appendFn.apply(el, args);
  14342. sourcesetLoad(tech);
  14343. return retval;
  14344. };
  14345. };
  14346. ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
  14347. if (!el[k]) {
  14348. return;
  14349. }
  14350. // store the old function
  14351. old[k] = el[k];
  14352. // call the old function with a sourceset if a source
  14353. // was loaded
  14354. el[k] = appendWrapper(old[k]);
  14355. });
  14356. Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
  14357. set: appendWrapper(innerDescriptor.set)
  14358. }));
  14359. el.resetSourceWatch_ = function () {
  14360. el.resetSourceWatch_ = null;
  14361. Object.keys(old).forEach(function (k) {
  14362. el[k] = old[k];
  14363. });
  14364. Object.defineProperty(el, 'innerHTML', innerDescriptor);
  14365. };
  14366. // on the first sourceset, we need to revert our changes
  14367. tech.one('sourceset', el.resetSourceWatch_);
  14368. };
  14369. /**
  14370. * our implementation of a `src` descriptor for browsers
  14371. * that do not have one.
  14372. */
  14373. var srcDescriptorPolyfill = {};
  14374. if (!IS_IE8) {
  14375. srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
  14376. get: function get() {
  14377. if (this.hasAttribute('src')) {
  14378. return getAbsoluteURL(window.Element.prototype.getAttribute.call(this, 'src'));
  14379. }
  14380. return '';
  14381. },
  14382. set: function set(v) {
  14383. window.Element.prototype.setAttribute.call(this, 'src', v);
  14384. return v;
  14385. }
  14386. });
  14387. }
  14388. var getSrcDescriptor = function getSrcDescriptor(tech) {
  14389. return getDescriptor([tech.el(), window.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
  14390. };
  14391. /**
  14392. * setup `sourceset` handling on the `Html5` tech. This function
  14393. * patches the following element properties/functions:
  14394. *
  14395. * - `src` - to determine when `src` is set
  14396. * - `setAttribute()` - to determine when `src` is set
  14397. * - `load()` - this re-triggers the source selection algorithm, and can
  14398. * cause a sourceset.
  14399. *
  14400. * If there is no source when we are adding `sourceset` support or during a `load()`
  14401. * we also patch the functions listed in `firstSourceWatch`.
  14402. *
  14403. * @param {Html5} tech
  14404. * The tech to patch
  14405. */
  14406. var setupSourceset = function setupSourceset(tech) {
  14407. if (!tech.featuresSourceset) {
  14408. return;
  14409. }
  14410. var el = tech.el();
  14411. // make sure sourceset isn't setup twice.
  14412. if (el.resetSourceset_) {
  14413. return;
  14414. }
  14415. var srcDescriptor = getSrcDescriptor(tech);
  14416. var oldSetAttribute = el.setAttribute;
  14417. var oldLoad = el.load;
  14418. Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
  14419. set: function set(v) {
  14420. var retval = srcDescriptor.set.call(el, v);
  14421. // we use the getter here to get the actual value set on src
  14422. tech.triggerSourceset(el.src);
  14423. return retval;
  14424. }
  14425. }));
  14426. el.setAttribute = function (n, v) {
  14427. var retval = oldSetAttribute.call(el, n, v);
  14428. if (/src/i.test(n)) {
  14429. tech.triggerSourceset(el.src);
  14430. }
  14431. return retval;
  14432. };
  14433. el.load = function () {
  14434. var retval = oldLoad.call(el);
  14435. // if load was called, but there was no source to fire
  14436. // sourceset on. We have to watch for a source append
  14437. // as that can trigger a `sourceset` when the media element
  14438. // has no source
  14439. if (!sourcesetLoad(tech)) {
  14440. tech.triggerSourceset('');
  14441. firstSourceWatch(tech);
  14442. }
  14443. return retval;
  14444. };
  14445. if (el.currentSrc) {
  14446. tech.triggerSourceset(el.currentSrc);
  14447. } else if (!sourcesetLoad(tech)) {
  14448. firstSourceWatch(tech);
  14449. }
  14450. el.resetSourceset_ = function () {
  14451. el.resetSourceset_ = null;
  14452. el.load = oldLoad;
  14453. el.setAttribute = oldSetAttribute;
  14454. Object.defineProperty(el, 'src', srcDescriptor);
  14455. if (el.resetSourceWatch_) {
  14456. el.resetSourceWatch_();
  14457. }
  14458. };
  14459. };
  14460. var _templateObject$2 = taggedTemplateLiteralLoose(['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n This may prevent text tracks from loading.'], ['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n This may prevent text tracks from loading.']);
  14461. /**
  14462. * @file html5.js
  14463. */
  14464. /**
  14465. * HTML5 Media Controller - Wrapper for HTML5 Media API
  14466. *
  14467. * @mixes Tech~SouceHandlerAdditions
  14468. * @extends Tech
  14469. */
  14470. var Html5 = function (_Tech) {
  14471. inherits(Html5, _Tech);
  14472. /**
  14473. * Create an instance of this Tech.
  14474. *
  14475. * @param {Object} [options]
  14476. * The key/value store of player options.
  14477. *
  14478. * @param {Component~ReadyCallback} ready
  14479. * Callback function to call when the `HTML5` Tech is ready.
  14480. */
  14481. function Html5(options, ready) {
  14482. classCallCheck(this, Html5);
  14483. var _this = possibleConstructorReturn(this, _Tech.call(this, options, ready));
  14484. var source = options.source;
  14485. var crossoriginTracks = false;
  14486. // Set the source if one is provided
  14487. // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
  14488. // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
  14489. // anyway so the error gets fired.
  14490. if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
  14491. _this.setSource(source);
  14492. } else {
  14493. _this.handleLateInit_(_this.el_);
  14494. }
  14495. // setup sourceset after late sourceset/init
  14496. if (options.enableSourceset) {
  14497. _this.setupSourcesetHandling_();
  14498. }
  14499. if (_this.el_.hasChildNodes()) {
  14500. var nodes = _this.el_.childNodes;
  14501. var nodesLength = nodes.length;
  14502. var removeNodes = [];
  14503. while (nodesLength--) {
  14504. var node = nodes[nodesLength];
  14505. var nodeName = node.nodeName.toLowerCase();
  14506. if (nodeName === 'track') {
  14507. if (!_this.featuresNativeTextTracks) {
  14508. // Empty video tag tracks so the built-in player doesn't use them also.
  14509. // This may not be fast enough to stop HTML5 browsers from reading the tags
  14510. // so we'll need to turn off any default tracks if we're manually doing
  14511. // captions and subtitles. videoElement.textTracks
  14512. removeNodes.push(node);
  14513. } else {
  14514. // store HTMLTrackElement and TextTrack to remote list
  14515. _this.remoteTextTrackEls().addTrackElement_(node);
  14516. _this.remoteTextTracks().addTrack(node.track);
  14517. _this.textTracks().addTrack(node.track);
  14518. if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
  14519. crossoriginTracks = true;
  14520. }
  14521. }
  14522. }
  14523. }
  14524. for (var i = 0; i < removeNodes.length; i++) {
  14525. _this.el_.removeChild(removeNodes[i]);
  14526. }
  14527. }
  14528. _this.proxyNativeTracks_();
  14529. if (_this.featuresNativeTextTracks && crossoriginTracks) {
  14530. log.warn(tsml(_templateObject$2));
  14531. }
  14532. // prevent iOS Safari from disabling metadata text tracks during native playback
  14533. _this.restoreMetadataTracksInIOSNativePlayer_();
  14534. // Determine if native controls should be used
  14535. // Our goal should be to get the custom controls on mobile solid everywhere
  14536. // so we can remove this all together. Right now this will block custom
  14537. // controls on touch enabled laptops like the Chrome Pixel
  14538. if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
  14539. _this.setControls(true);
  14540. }
  14541. // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
  14542. // into a `fullscreenchange` event
  14543. _this.proxyWebkitFullscreen_();
  14544. _this.triggerReady();
  14545. return _this;
  14546. }
  14547. /**
  14548. * Dispose of `HTML5` media element and remove all tracks.
  14549. */
  14550. Html5.prototype.dispose = function dispose() {
  14551. if (this.el_ && this.el_.resetSourceset_) {
  14552. this.el_.resetSourceset_();
  14553. }
  14554. Html5.disposeMediaElement(this.el_);
  14555. this.options_ = null;
  14556. // tech will handle clearing of the emulated track list
  14557. _Tech.prototype.dispose.call(this);
  14558. };
  14559. /**
  14560. * Modify the media element so that we can detect when
  14561. * the source is changed. Fires `sourceset` just after the source has changed
  14562. */
  14563. Html5.prototype.setupSourcesetHandling_ = function setupSourcesetHandling_() {
  14564. setupSourceset(this);
  14565. };
  14566. /**
  14567. * When a captions track is enabled in the iOS Safari native player, all other
  14568. * tracks are disabled (including metadata tracks), which nulls all of their
  14569. * associated cue points. This will restore metadata tracks to their pre-fullscreen
  14570. * state in those cases so that cue points are not needlessly lost.
  14571. *
  14572. * @private
  14573. */
  14574. Html5.prototype.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
  14575. var textTracks = this.textTracks();
  14576. var metadataTracksPreFullscreenState = void 0;
  14577. // captures a snapshot of every metadata track's current state
  14578. var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
  14579. metadataTracksPreFullscreenState = [];
  14580. for (var i = 0; i < textTracks.length; i++) {
  14581. var track = textTracks[i];
  14582. if (track.kind === 'metadata') {
  14583. metadataTracksPreFullscreenState.push({
  14584. track: track,
  14585. storedMode: track.mode
  14586. });
  14587. }
  14588. }
  14589. };
  14590. // snapshot each metadata track's initial state, and update the snapshot
  14591. // each time there is a track 'change' event
  14592. takeMetadataTrackSnapshot();
  14593. textTracks.addEventListener('change', takeMetadataTrackSnapshot);
  14594. this.on('dispose', function () {
  14595. return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  14596. });
  14597. var restoreTrackMode = function restoreTrackMode() {
  14598. for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
  14599. var storedTrack = metadataTracksPreFullscreenState[i];
  14600. if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
  14601. storedTrack.track.mode = storedTrack.storedMode;
  14602. }
  14603. }
  14604. // we only want this handler to be executed on the first 'change' event
  14605. textTracks.removeEventListener('change', restoreTrackMode);
  14606. };
  14607. // when we enter fullscreen playback, stop updating the snapshot and
  14608. // restore all track modes to their pre-fullscreen state
  14609. this.on('webkitbeginfullscreen', function () {
  14610. textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  14611. // remove the listener before adding it just in case it wasn't previously removed
  14612. textTracks.removeEventListener('change', restoreTrackMode);
  14613. textTracks.addEventListener('change', restoreTrackMode);
  14614. });
  14615. // start updating the snapshot again after leaving fullscreen
  14616. this.on('webkitendfullscreen', function () {
  14617. // remove the listener before adding it just in case it wasn't previously removed
  14618. textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  14619. textTracks.addEventListener('change', takeMetadataTrackSnapshot);
  14620. // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
  14621. textTracks.removeEventListener('change', restoreTrackMode);
  14622. });
  14623. };
  14624. /**
  14625. * Proxy all native track list events to our track lists if the browser we are playing
  14626. * in supports that type of track list.
  14627. *
  14628. * @private
  14629. */
  14630. Html5.prototype.proxyNativeTracks_ = function proxyNativeTracks_() {
  14631. var _this2 = this;
  14632. NORMAL.names.forEach(function (name) {
  14633. var props = NORMAL[name];
  14634. var elTracks = _this2.el()[props.getterName];
  14635. var techTracks = _this2[props.getterName]();
  14636. if (!_this2['featuresNative' + props.capitalName + 'Tracks'] || !elTracks || !elTracks.addEventListener) {
  14637. return;
  14638. }
  14639. var listeners = {
  14640. change: function change(e) {
  14641. techTracks.trigger({
  14642. type: 'change',
  14643. target: techTracks,
  14644. currentTarget: techTracks,
  14645. srcElement: techTracks
  14646. });
  14647. },
  14648. addtrack: function addtrack(e) {
  14649. techTracks.addTrack(e.track);
  14650. },
  14651. removetrack: function removetrack(e) {
  14652. techTracks.removeTrack(e.track);
  14653. }
  14654. };
  14655. var removeOldTracks = function removeOldTracks() {
  14656. var removeTracks = [];
  14657. for (var i = 0; i < techTracks.length; i++) {
  14658. var found = false;
  14659. for (var j = 0; j < elTracks.length; j++) {
  14660. if (elTracks[j] === techTracks[i]) {
  14661. found = true;
  14662. break;
  14663. }
  14664. }
  14665. if (!found) {
  14666. removeTracks.push(techTracks[i]);
  14667. }
  14668. }
  14669. while (removeTracks.length) {
  14670. techTracks.removeTrack(removeTracks.shift());
  14671. }
  14672. };
  14673. Object.keys(listeners).forEach(function (eventName) {
  14674. var listener = listeners[eventName];
  14675. elTracks.addEventListener(eventName, listener);
  14676. _this2.on('dispose', function (e) {
  14677. return elTracks.removeEventListener(eventName, listener);
  14678. });
  14679. });
  14680. // Remove (native) tracks that are not used anymore
  14681. _this2.on('loadstart', removeOldTracks);
  14682. _this2.on('dispose', function (e) {
  14683. return _this2.off('loadstart', removeOldTracks);
  14684. });
  14685. });
  14686. };
  14687. /**
  14688. * Create the `Html5` Tech's DOM element.
  14689. *
  14690. * @return {Element}
  14691. * The element that gets created.
  14692. */
  14693. Html5.prototype.createEl = function createEl$$1() {
  14694. var el = this.options_.tag;
  14695. // Check if this browser supports moving the element into the box.
  14696. // On the iPhone video will break if you move the element,
  14697. // So we have to create a brand new element.
  14698. // If we ingested the player div, we do not need to move the media element.
  14699. if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
  14700. // If the original tag is still there, clone and remove it.
  14701. if (el) {
  14702. var clone = el.cloneNode(true);
  14703. if (el.parentNode) {
  14704. el.parentNode.insertBefore(clone, el);
  14705. }
  14706. Html5.disposeMediaElement(el);
  14707. el = clone;
  14708. } else {
  14709. el = document.createElement('video');
  14710. // determine if native controls should be used
  14711. var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
  14712. var attributes = mergeOptions({}, tagAttributes);
  14713. if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
  14714. delete attributes.controls;
  14715. }
  14716. setAttributes(el, assign(attributes, {
  14717. id: this.options_.techId,
  14718. 'class': 'vjs-tech'
  14719. }));
  14720. }
  14721. el.playerId = this.options_.playerId;
  14722. }
  14723. if (typeof this.options_.preload !== 'undefined') {
  14724. setAttribute(el, 'preload', this.options_.preload);
  14725. }
  14726. // Update specific tag settings, in case they were overridden
  14727. // `autoplay` has to be *last* so that `muted` and `playsinline` are present
  14728. // when iOS/Safari or other browsers attempt to autoplay.
  14729. var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
  14730. for (var i = 0; i < settingsAttrs.length; i++) {
  14731. var attr = settingsAttrs[i];
  14732. var value = this.options_[attr];
  14733. if (typeof value !== 'undefined') {
  14734. if (value) {
  14735. setAttribute(el, attr, attr);
  14736. } else {
  14737. removeAttribute(el, attr);
  14738. }
  14739. el[attr] = value;
  14740. }
  14741. }
  14742. return el;
  14743. };
  14744. /**
  14745. * This will be triggered if the loadstart event has already fired, before videojs was
  14746. * ready. Two known examples of when this can happen are:
  14747. * 1. If we're loading the playback object after it has started loading
  14748. * 2. The media is already playing the (often with autoplay on) then
  14749. *
  14750. * This function will fire another loadstart so that videojs can catchup.
  14751. *
  14752. * @fires Tech#loadstart
  14753. *
  14754. * @return {undefined}
  14755. * returns nothing.
  14756. */
  14757. Html5.prototype.handleLateInit_ = function handleLateInit_(el) {
  14758. if (el.networkState === 0 || el.networkState === 3) {
  14759. // The video element hasn't started loading the source yet
  14760. // or didn't find a source
  14761. return;
  14762. }
  14763. if (el.readyState === 0) {
  14764. // NetworkState is set synchronously BUT loadstart is fired at the
  14765. // end of the current stack, usually before setInterval(fn, 0).
  14766. // So at this point we know loadstart may have already fired or is
  14767. // about to fire, and either way the player hasn't seen it yet.
  14768. // We don't want to fire loadstart prematurely here and cause a
  14769. // double loadstart so we'll wait and see if it happens between now
  14770. // and the next loop, and fire it if not.
  14771. // HOWEVER, we also want to make sure it fires before loadedmetadata
  14772. // which could also happen between now and the next loop, so we'll
  14773. // watch for that also.
  14774. var loadstartFired = false;
  14775. var setLoadstartFired = function setLoadstartFired() {
  14776. loadstartFired = true;
  14777. };
  14778. this.on('loadstart', setLoadstartFired);
  14779. var triggerLoadstart = function triggerLoadstart() {
  14780. // We did miss the original loadstart. Make sure the player
  14781. // sees loadstart before loadedmetadata
  14782. if (!loadstartFired) {
  14783. this.trigger('loadstart');
  14784. }
  14785. };
  14786. this.on('loadedmetadata', triggerLoadstart);
  14787. this.ready(function () {
  14788. this.off('loadstart', setLoadstartFired);
  14789. this.off('loadedmetadata', triggerLoadstart);
  14790. if (!loadstartFired) {
  14791. // We did miss the original native loadstart. Fire it now.
  14792. this.trigger('loadstart');
  14793. }
  14794. });
  14795. return;
  14796. }
  14797. // From here on we know that loadstart already fired and we missed it.
  14798. // The other readyState events aren't as much of a problem if we double
  14799. // them, so not going to go to as much trouble as loadstart to prevent
  14800. // that unless we find reason to.
  14801. var eventsToTrigger = ['loadstart'];
  14802. // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
  14803. eventsToTrigger.push('loadedmetadata');
  14804. // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
  14805. if (el.readyState >= 2) {
  14806. eventsToTrigger.push('loadeddata');
  14807. }
  14808. // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
  14809. if (el.readyState >= 3) {
  14810. eventsToTrigger.push('canplay');
  14811. }
  14812. // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
  14813. if (el.readyState >= 4) {
  14814. eventsToTrigger.push('canplaythrough');
  14815. }
  14816. // We still need to give the player time to add event listeners
  14817. this.ready(function () {
  14818. eventsToTrigger.forEach(function (type) {
  14819. this.trigger(type);
  14820. }, this);
  14821. });
  14822. };
  14823. /**
  14824. * Set current time for the `HTML5` tech.
  14825. *
  14826. * @param {number} seconds
  14827. * Set the current time of the media to this.
  14828. */
  14829. Html5.prototype.setCurrentTime = function setCurrentTime(seconds) {
  14830. try {
  14831. this.el_.currentTime = seconds;
  14832. } catch (e) {
  14833. log(e, 'Video is not ready. (Video.js)');
  14834. // this.warning(VideoJS.warnings.videoNotReady);
  14835. }
  14836. };
  14837. /**
  14838. * Get the current duration of the HTML5 media element.
  14839. *
  14840. * @return {number}
  14841. * The duration of the media or 0 if there is no duration.
  14842. */
  14843. Html5.prototype.duration = function duration() {
  14844. var _this3 = this;
  14845. // Android Chrome will report duration as Infinity for VOD HLS until after
  14846. // playback has started, which triggers the live display erroneously.
  14847. // Return NaN if playback has not started and trigger a durationupdate once
  14848. // the duration can be reliably known.
  14849. if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
  14850. // Wait for the first `timeupdate` with currentTime > 0 - there may be
  14851. // several with 0
  14852. var checkProgress = function checkProgress() {
  14853. if (_this3.el_.currentTime > 0) {
  14854. // Trigger durationchange for genuinely live video
  14855. if (_this3.el_.duration === Infinity) {
  14856. _this3.trigger('durationchange');
  14857. }
  14858. _this3.off('timeupdate', checkProgress);
  14859. }
  14860. };
  14861. this.on('timeupdate', checkProgress);
  14862. return NaN;
  14863. }
  14864. return this.el_.duration || NaN;
  14865. };
  14866. /**
  14867. * Get the current width of the HTML5 media element.
  14868. *
  14869. * @return {number}
  14870. * The width of the HTML5 media element.
  14871. */
  14872. Html5.prototype.width = function width() {
  14873. return this.el_.offsetWidth;
  14874. };
  14875. /**
  14876. * Get the current height of the HTML5 media element.
  14877. *
  14878. * @return {number}
  14879. * The heigth of the HTML5 media element.
  14880. */
  14881. Html5.prototype.height = function height() {
  14882. return this.el_.offsetHeight;
  14883. };
  14884. /**
  14885. * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
  14886. * `fullscreenchange` event.
  14887. *
  14888. * @private
  14889. * @fires fullscreenchange
  14890. * @listens webkitendfullscreen
  14891. * @listens webkitbeginfullscreen
  14892. * @listens webkitbeginfullscreen
  14893. */
  14894. Html5.prototype.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
  14895. var _this4 = this;
  14896. if (!('webkitDisplayingFullscreen' in this.el_)) {
  14897. return;
  14898. }
  14899. var endFn = function endFn() {
  14900. this.trigger('fullscreenchange', { isFullscreen: false });
  14901. };
  14902. var beginFn = function beginFn() {
  14903. if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
  14904. this.one('webkitendfullscreen', endFn);
  14905. this.trigger('fullscreenchange', { isFullscreen: true });
  14906. }
  14907. };
  14908. this.on('webkitbeginfullscreen', beginFn);
  14909. this.on('dispose', function () {
  14910. _this4.off('webkitbeginfullscreen', beginFn);
  14911. _this4.off('webkitendfullscreen', endFn);
  14912. });
  14913. };
  14914. /**
  14915. * Check if fullscreen is supported on the current playback device.
  14916. *
  14917. * @return {boolean}
  14918. * - True if fullscreen is supported.
  14919. * - False if fullscreen is not supported.
  14920. */
  14921. Html5.prototype.supportsFullScreen = function supportsFullScreen() {
  14922. if (typeof this.el_.webkitEnterFullScreen === 'function') {
  14923. var userAgent = window.navigator && window.navigator.userAgent || '';
  14924. // Seems to be broken in Chromium/Chrome && Safari in Leopard
  14925. if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
  14926. return true;
  14927. }
  14928. }
  14929. return false;
  14930. };
  14931. /**
  14932. * Request that the `HTML5` Tech enter fullscreen.
  14933. */
  14934. Html5.prototype.enterFullScreen = function enterFullScreen() {
  14935. var video = this.el_;
  14936. if (video.paused && video.networkState <= video.HAVE_METADATA) {
  14937. // attempt to prime the video element for programmatic access
  14938. // this isn't necessary on the desktop but shouldn't hurt
  14939. this.el_.play();
  14940. // playing and pausing synchronously during the transition to fullscreen
  14941. // can get iOS ~6.1 devices into a play/pause loop
  14942. this.setTimeout(function () {
  14943. video.pause();
  14944. video.webkitEnterFullScreen();
  14945. }, 0);
  14946. } else {
  14947. video.webkitEnterFullScreen();
  14948. }
  14949. };
  14950. /**
  14951. * Request that the `HTML5` Tech exit fullscreen.
  14952. */
  14953. Html5.prototype.exitFullScreen = function exitFullScreen() {
  14954. this.el_.webkitExitFullScreen();
  14955. };
  14956. /**
  14957. * A getter/setter for the `Html5` Tech's source object.
  14958. * > Note: Please use {@link Html5#setSource}
  14959. *
  14960. * @param {Tech~SourceObject} [src]
  14961. * The source object you want to set on the `HTML5` techs element.
  14962. *
  14963. * @return {Tech~SourceObject|undefined}
  14964. * - The current source object when a source is not passed in.
  14965. * - undefined when setting
  14966. *
  14967. * @deprecated Since version 5.
  14968. */
  14969. Html5.prototype.src = function src(_src) {
  14970. if (_src === undefined) {
  14971. return this.el_.src;
  14972. }
  14973. // Setting src through `src` instead of `setSrc` will be deprecated
  14974. this.setSrc(_src);
  14975. };
  14976. /**
  14977. * Reset the tech by removing all sources and then calling
  14978. * {@link Html5.resetMediaElement}.
  14979. */
  14980. Html5.prototype.reset = function reset() {
  14981. Html5.resetMediaElement(this.el_);
  14982. };
  14983. /**
  14984. * Get the current source on the `HTML5` Tech. Falls back to returning the source from
  14985. * the HTML5 media element.
  14986. *
  14987. * @return {Tech~SourceObject}
  14988. * The current source object from the HTML5 tech. With a fallback to the
  14989. * elements source.
  14990. */
  14991. Html5.prototype.currentSrc = function currentSrc() {
  14992. if (this.currentSource_) {
  14993. return this.currentSource_.src;
  14994. }
  14995. return this.el_.currentSrc;
  14996. };
  14997. /**
  14998. * Set controls attribute for the HTML5 media Element.
  14999. *
  15000. * @param {string} val
  15001. * Value to set the controls attribute to
  15002. */
  15003. Html5.prototype.setControls = function setControls(val) {
  15004. this.el_.controls = !!val;
  15005. };
  15006. /**
  15007. * Create and returns a remote {@link TextTrack} object.
  15008. *
  15009. * @param {string} kind
  15010. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  15011. *
  15012. * @param {string} [label]
  15013. * Label to identify the text track
  15014. *
  15015. * @param {string} [language]
  15016. * Two letter language abbreviation
  15017. *
  15018. * @return {TextTrack}
  15019. * The TextTrack that gets created.
  15020. */
  15021. Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) {
  15022. if (!this.featuresNativeTextTracks) {
  15023. return _Tech.prototype.addTextTrack.call(this, kind, label, language);
  15024. }
  15025. return this.el_.addTextTrack(kind, label, language);
  15026. };
  15027. /**
  15028. * Creates either native TextTrack or an emulated TextTrack depending
  15029. * on the value of `featuresNativeTextTracks`
  15030. *
  15031. * @param {Object} options
  15032. * The object should contain the options to intialize the TextTrack with.
  15033. *
  15034. * @param {string} [options.kind]
  15035. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  15036. *
  15037. * @param {string} [options.label].
  15038. * Label to identify the text track
  15039. *
  15040. * @param {string} [options.language]
  15041. * Two letter language abbreviation.
  15042. *
  15043. * @param {boolean} [options.default]
  15044. * Default this track to on.
  15045. *
  15046. * @param {string} [options.id]
  15047. * The internal id to assign this track.
  15048. *
  15049. * @param {string} [options.src]
  15050. * A source url for the track.
  15051. *
  15052. * @return {HTMLTrackElement}
  15053. * The track element that gets created.
  15054. */
  15055. Html5.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) {
  15056. if (!this.featuresNativeTextTracks) {
  15057. return _Tech.prototype.createRemoteTextTrack.call(this, options);
  15058. }
  15059. var htmlTrackElement = document.createElement('track');
  15060. if (options.kind) {
  15061. htmlTrackElement.kind = options.kind;
  15062. }
  15063. if (options.label) {
  15064. htmlTrackElement.label = options.label;
  15065. }
  15066. if (options.language || options.srclang) {
  15067. htmlTrackElement.srclang = options.language || options.srclang;
  15068. }
  15069. if (options['default']) {
  15070. htmlTrackElement['default'] = options['default'];
  15071. }
  15072. if (options.id) {
  15073. htmlTrackElement.id = options.id;
  15074. }
  15075. if (options.src) {
  15076. htmlTrackElement.src = options.src;
  15077. }
  15078. return htmlTrackElement;
  15079. };
  15080. /**
  15081. * Creates a remote text track object and returns an html track element.
  15082. *
  15083. * @param {Object} options The object should contain values for
  15084. * kind, language, label, and src (location of the WebVTT file)
  15085. * @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be
  15086. * automatically removed from the video element whenever the source changes
  15087. * @return {HTMLTrackElement} An Html Track Element.
  15088. * This can be an emulated {@link HTMLTrackElement} or a native one.
  15089. * @deprecated The default value of the "manualCleanup" parameter will default
  15090. * to "false" in upcoming versions of Video.js
  15091. */
  15092. Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  15093. var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
  15094. if (this.featuresNativeTextTracks) {
  15095. this.el().appendChild(htmlTrackElement);
  15096. }
  15097. return htmlTrackElement;
  15098. };
  15099. /**
  15100. * Remove remote `TextTrack` from `TextTrackList` object
  15101. *
  15102. * @param {TextTrack} track
  15103. * `TextTrack` object to remove
  15104. */
  15105. Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  15106. _Tech.prototype.removeRemoteTextTrack.call(this, track);
  15107. if (this.featuresNativeTextTracks) {
  15108. var tracks = this.$$('track');
  15109. var i = tracks.length;
  15110. while (i--) {
  15111. if (track === tracks[i] || track === tracks[i].track) {
  15112. this.el().removeChild(tracks[i]);
  15113. }
  15114. }
  15115. }
  15116. };
  15117. /**
  15118. * Gets available media playback quality metrics as specified by the W3C's Media
  15119. * Playback Quality API.
  15120. *
  15121. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  15122. *
  15123. * @return {Object}
  15124. * An object with supported media playback quality metrics
  15125. */
  15126. Html5.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  15127. if (typeof this.el().getVideoPlaybackQuality === 'function') {
  15128. return this.el().getVideoPlaybackQuality();
  15129. }
  15130. var videoPlaybackQuality = {};
  15131. if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
  15132. videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
  15133. videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
  15134. }
  15135. if (window.performance && typeof window.performance.now === 'function') {
  15136. videoPlaybackQuality.creationTime = window.performance.now();
  15137. } else if (window.performance && window.performance.timing && typeof window.performance.timing.navigationStart === 'number') {
  15138. videoPlaybackQuality.creationTime = window.Date.now() - window.performance.timing.navigationStart;
  15139. }
  15140. return videoPlaybackQuality;
  15141. };
  15142. return Html5;
  15143. }(Tech);
  15144. /* HTML5 Support Testing ---------------------------------------------------- */
  15145. if (isReal()) {
  15146. /**
  15147. * Element for testing browser HTML5 media capabilities
  15148. *
  15149. * @type {Element}
  15150. * @constant
  15151. * @private
  15152. */
  15153. Html5.TEST_VID = document.createElement('video');
  15154. var track = document.createElement('track');
  15155. track.kind = 'captions';
  15156. track.srclang = 'en';
  15157. track.label = 'English';
  15158. Html5.TEST_VID.appendChild(track);
  15159. }
  15160. /**
  15161. * Check if HTML5 media is supported by this browser/device.
  15162. *
  15163. * @return {boolean}
  15164. * - True if HTML5 media is supported.
  15165. * - False if HTML5 media is not supported.
  15166. */
  15167. Html5.isSupported = function () {
  15168. // IE9 with no Media Player is a LIAR! (#984)
  15169. try {
  15170. Html5.TEST_VID.volume = 0.5;
  15171. } catch (e) {
  15172. return false;
  15173. }
  15174. return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
  15175. };
  15176. /**
  15177. * Check if the tech can support the given type
  15178. *
  15179. * @param {string} type
  15180. * The mimetype to check
  15181. * @return {string} 'probably', 'maybe', or '' (empty string)
  15182. */
  15183. Html5.canPlayType = function (type) {
  15184. return Html5.TEST_VID.canPlayType(type);
  15185. };
  15186. /**
  15187. * Check if the tech can support the given source
  15188. * @param {Object} srcObj
  15189. * The source object
  15190. * @param {Object} options
  15191. * The options passed to the tech
  15192. * @return {string} 'probably', 'maybe', or '' (empty string)
  15193. */
  15194. Html5.canPlaySource = function (srcObj, options) {
  15195. return Html5.canPlayType(srcObj.type);
  15196. };
  15197. /**
  15198. * Check if the volume can be changed in this browser/device.
  15199. * Volume cannot be changed in a lot of mobile devices.
  15200. * Specifically, it can't be changed from 1 on iOS.
  15201. *
  15202. * @return {boolean}
  15203. * - True if volume can be controlled
  15204. * - False otherwise
  15205. */
  15206. Html5.canControlVolume = function () {
  15207. // IE will error if Windows Media Player not installed #3315
  15208. try {
  15209. var volume = Html5.TEST_VID.volume;
  15210. Html5.TEST_VID.volume = volume / 2 + 0.1;
  15211. return volume !== Html5.TEST_VID.volume;
  15212. } catch (e) {
  15213. return false;
  15214. }
  15215. };
  15216. /**
  15217. * Check if the volume can be muted in this browser/device.
  15218. * Some devices, e.g. iOS, don't allow changing volume
  15219. * but permits muting/unmuting.
  15220. *
  15221. * @return {bolean}
  15222. * - True if volume can be muted
  15223. * - False otherwise
  15224. */
  15225. Html5.canMuteVolume = function () {
  15226. try {
  15227. var muted = Html5.TEST_VID.muted;
  15228. // in some versions of iOS muted property doesn't always
  15229. // work, so we want to set both property and attribute
  15230. Html5.TEST_VID.muted = !muted;
  15231. if (Html5.TEST_VID.muted) {
  15232. setAttribute(Html5.TEST_VID, 'muted', 'muted');
  15233. } else {
  15234. removeAttribute(Html5.TEST_VID, 'muted', 'muted');
  15235. }
  15236. return muted !== Html5.TEST_VID.muted;
  15237. } catch (e) {
  15238. return false;
  15239. }
  15240. };
  15241. /**
  15242. * Check if the playback rate can be changed in this browser/device.
  15243. *
  15244. * @return {boolean}
  15245. * - True if playback rate can be controlled
  15246. * - False otherwise
  15247. */
  15248. Html5.canControlPlaybackRate = function () {
  15249. // Playback rate API is implemented in Android Chrome, but doesn't do anything
  15250. // https://github.com/videojs/video.js/issues/3180
  15251. if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
  15252. return false;
  15253. }
  15254. // IE will error if Windows Media Player not installed #3315
  15255. try {
  15256. var playbackRate = Html5.TEST_VID.playbackRate;
  15257. Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
  15258. return playbackRate !== Html5.TEST_VID.playbackRate;
  15259. } catch (e) {
  15260. return false;
  15261. }
  15262. };
  15263. /**
  15264. * Check if we can override a video/audio elements attributes, with
  15265. * Object.defineProperty.
  15266. *
  15267. * @return {boolean}
  15268. * - True if builtin attributes can be overriden
  15269. * - False otherwise
  15270. */
  15271. Html5.canOverrideAttributes = function () {
  15272. if (IS_IE8) {
  15273. return false;
  15274. }
  15275. // if we cannot overwrite the src/innerHTML property, there is no support
  15276. // iOS 7 safari for instance cannot do this.
  15277. try {
  15278. var noop = function noop() {};
  15279. Object.defineProperty(document.createElement('video'), 'src', { get: noop, set: noop });
  15280. Object.defineProperty(document.createElement('audio'), 'src', { get: noop, set: noop });
  15281. Object.defineProperty(document.createElement('video'), 'innerHTML', { get: noop, set: noop });
  15282. Object.defineProperty(document.createElement('audio'), 'innerHTML', { get: noop, set: noop });
  15283. } catch (e) {
  15284. return false;
  15285. }
  15286. return true;
  15287. };
  15288. /**
  15289. * Check to see if native `TextTrack`s are supported by this browser/device.
  15290. *
  15291. * @return {boolean}
  15292. * - True if native `TextTrack`s are supported.
  15293. * - False otherwise
  15294. */
  15295. Html5.supportsNativeTextTracks = function () {
  15296. return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
  15297. };
  15298. /**
  15299. * Check to see if native `VideoTrack`s are supported by this browser/device
  15300. *
  15301. * @return {boolean}
  15302. * - True if native `VideoTrack`s are supported.
  15303. * - False otherwise
  15304. */
  15305. Html5.supportsNativeVideoTracks = function () {
  15306. return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
  15307. };
  15308. /**
  15309. * Check to see if native `AudioTrack`s are supported by this browser/device
  15310. *
  15311. * @return {boolean}
  15312. * - True if native `AudioTrack`s are supported.
  15313. * - False otherwise
  15314. */
  15315. Html5.supportsNativeAudioTracks = function () {
  15316. return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
  15317. };
  15318. /**
  15319. * An array of events available on the Html5 tech.
  15320. *
  15321. * @private
  15322. * @type {Array}
  15323. */
  15324. Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'resize', 'volumechange'];
  15325. /**
  15326. * Boolean indicating whether the `Tech` supports volume control.
  15327. *
  15328. * @type {boolean}
  15329. * @default {@link Html5.canControlVolume}
  15330. */
  15331. Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
  15332. /**
  15333. * Boolean indicating whether the `Tech` supports muting volume.
  15334. *
  15335. * @type {bolean}
  15336. * @default {@link Html5.canMuteVolume}
  15337. */
  15338. Html5.prototype.featuresMuteControl = Html5.canMuteVolume();
  15339. /**
  15340. * Boolean indicating whether the `Tech` supports changing the speed at which the media
  15341. * plays. Examples:
  15342. * - Set player to play 2x (twice) as fast
  15343. * - Set player to play 0.5x (half) as fast
  15344. *
  15345. * @type {boolean}
  15346. * @default {@link Html5.canControlPlaybackRate}
  15347. */
  15348. Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
  15349. /**
  15350. * Boolean indicating wether the `Tech` supports the `sourceset` event.
  15351. *
  15352. * @type {boolean}
  15353. * @default
  15354. */
  15355. Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
  15356. /**
  15357. * Boolean indicating whether the `HTML5` tech currently supports the media element
  15358. * moving in the DOM. iOS breaks if you move the media element, so this is set this to
  15359. * false there. Everywhere else this should be true.
  15360. *
  15361. * @type {boolean}
  15362. * @default
  15363. */
  15364. Html5.prototype.movingMediaElementInDOM = !IS_IOS;
  15365. // TODO: Previous comment: No longer appears to be used. Can probably be removed.
  15366. // Is this true?
  15367. /**
  15368. * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
  15369. * when going into fullscreen.
  15370. *
  15371. * @type {boolean}
  15372. * @default
  15373. */
  15374. Html5.prototype.featuresFullscreenResize = true;
  15375. /**
  15376. * Boolean indicating whether the `HTML5` tech currently supports the progress event.
  15377. * If this is false, manual `progress` events will be triggred instead.
  15378. *
  15379. * @type {boolean}
  15380. * @default
  15381. */
  15382. Html5.prototype.featuresProgressEvents = true;
  15383. /**
  15384. * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
  15385. * If this is false, manual `timeupdate` events will be triggred instead.
  15386. *
  15387. * @default
  15388. */
  15389. Html5.prototype.featuresTimeupdateEvents = true;
  15390. /**
  15391. * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
  15392. *
  15393. * @type {boolean}
  15394. * @default {@link Html5.supportsNativeTextTracks}
  15395. */
  15396. Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
  15397. /**
  15398. * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
  15399. *
  15400. * @type {boolean}
  15401. * @default {@link Html5.supportsNativeVideoTracks}
  15402. */
  15403. Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
  15404. /**
  15405. * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
  15406. *
  15407. * @type {boolean}
  15408. * @default {@link Html5.supportsNativeAudioTracks}
  15409. */
  15410. Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks();
  15411. // HTML5 Feature detection and Device Fixes --------------------------------- //
  15412. var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
  15413. var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
  15414. var mp4RE = /^video\/mp4/i;
  15415. Html5.patchCanPlayType = function () {
  15416. // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
  15417. // Firefox and Chrome report correctly
  15418. if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
  15419. Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
  15420. if (type && mpegurlRE.test(type)) {
  15421. return 'maybe';
  15422. }
  15423. return canPlayType.call(this, type);
  15424. };
  15425. // Override Android 2.2 and less canPlayType method which is broken
  15426. } else if (IS_OLD_ANDROID) {
  15427. Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
  15428. if (type && mp4RE.test(type)) {
  15429. return 'maybe';
  15430. }
  15431. return canPlayType.call(this, type);
  15432. };
  15433. }
  15434. };
  15435. Html5.unpatchCanPlayType = function () {
  15436. var r = Html5.TEST_VID.constructor.prototype.canPlayType;
  15437. Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
  15438. return r;
  15439. };
  15440. // by default, patch the media element
  15441. Html5.patchCanPlayType();
  15442. Html5.disposeMediaElement = function (el) {
  15443. if (!el) {
  15444. return;
  15445. }
  15446. if (el.parentNode) {
  15447. el.parentNode.removeChild(el);
  15448. }
  15449. // remove any child track or source nodes to prevent their loading
  15450. while (el.hasChildNodes()) {
  15451. el.removeChild(el.firstChild);
  15452. }
  15453. // remove any src reference. not setting `src=''` because that causes a warning
  15454. // in firefox
  15455. el.removeAttribute('src');
  15456. // force the media element to update its loading state by calling load()
  15457. // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
  15458. if (typeof el.load === 'function') {
  15459. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  15460. (function () {
  15461. try {
  15462. el.load();
  15463. } catch (e) {
  15464. // not supported
  15465. }
  15466. })();
  15467. }
  15468. };
  15469. Html5.resetMediaElement = function (el) {
  15470. if (!el) {
  15471. return;
  15472. }
  15473. var sources = el.querySelectorAll('source');
  15474. var i = sources.length;
  15475. while (i--) {
  15476. el.removeChild(sources[i]);
  15477. }
  15478. // remove any src reference.
  15479. // not setting `src=''` because that throws an error
  15480. el.removeAttribute('src');
  15481. if (typeof el.load === 'function') {
  15482. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  15483. (function () {
  15484. try {
  15485. el.load();
  15486. } catch (e) {
  15487. // satisfy linter
  15488. }
  15489. })();
  15490. }
  15491. };
  15492. /* Native HTML5 element property wrapping ----------------------------------- */
  15493. // Wrap native boolean attributes with getters that check both property and attribute
  15494. // The list is as followed:
  15495. // muted, defaultMuted, autoplay, controls, loop, playsinline
  15496. [
  15497. /**
  15498. * Get the value of `muted` from the media element. `muted` indicates
  15499. * that the volume for the media should be set to silent. This does not actually change
  15500. * the `volume` attribute.
  15501. *
  15502. * @method Html5#muted
  15503. * @return {boolean}
  15504. * - True if the value of `volume` should be ignored and the audio set to silent.
  15505. * - False if the value of `volume` should be used.
  15506. *
  15507. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  15508. */
  15509. 'muted',
  15510. /**
  15511. * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
  15512. * whether the media should start muted or not. Only changes the default state of the
  15513. * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
  15514. * current state.
  15515. *
  15516. * @method Html5#defaultMuted
  15517. * @return {boolean}
  15518. * - The value of `defaultMuted` from the media element.
  15519. * - True indicates that the media should start muted.
  15520. * - False indicates that the media should not start muted
  15521. *
  15522. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  15523. */
  15524. 'defaultMuted',
  15525. /**
  15526. * Get the value of `autoplay` from the media element. `autoplay` indicates
  15527. * that the media should start to play as soon as the page is ready.
  15528. *
  15529. * @method Html5#autoplay
  15530. * @return {boolean}
  15531. * - The value of `autoplay` from the media element.
  15532. * - True indicates that the media should start as soon as the page loads.
  15533. * - False indicates that the media should not start as soon as the page loads.
  15534. *
  15535. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  15536. */
  15537. 'autoplay',
  15538. /**
  15539. * Get the value of `controls` from the media element. `controls` indicates
  15540. * whether the native media controls should be shown or hidden.
  15541. *
  15542. * @method Html5#controls
  15543. * @return {boolean}
  15544. * - The value of `controls` from the media element.
  15545. * - True indicates that native controls should be showing.
  15546. * - False indicates that native controls should be hidden.
  15547. *
  15548. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
  15549. */
  15550. 'controls',
  15551. /**
  15552. * Get the value of `loop` from the media element. `loop` indicates
  15553. * that the media should return to the start of the media and continue playing once
  15554. * it reaches the end.
  15555. *
  15556. * @method Html5#loop
  15557. * @return {boolean}
  15558. * - The value of `loop` from the media element.
  15559. * - True indicates that playback should seek back to start once
  15560. * the end of a media is reached.
  15561. * - False indicates that playback should not loop back to the start when the
  15562. * end of the media is reached.
  15563. *
  15564. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  15565. */
  15566. 'loop',
  15567. /**
  15568. * Get the value of `playsinline` from the media element. `playsinline` indicates
  15569. * to the browser that non-fullscreen playback is preferred when fullscreen
  15570. * playback is the native default, such as in iOS Safari.
  15571. *
  15572. * @method Html5#playsinline
  15573. * @return {boolean}
  15574. * - The value of `playsinline` from the media element.
  15575. * - True indicates that the media should play inline.
  15576. * - False indicates that the media should not play inline.
  15577. *
  15578. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  15579. */
  15580. 'playsinline'].forEach(function (prop) {
  15581. Html5.prototype[prop] = function () {
  15582. return this.el_[prop] || this.el_.hasAttribute(prop);
  15583. };
  15584. });
  15585. // Wrap native boolean attributes with setters that set both property and attribute
  15586. // The list is as followed:
  15587. // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
  15588. // setControls is special-cased above
  15589. [
  15590. /**
  15591. * Set the value of `muted` on the media element. `muted` indicates that the current
  15592. * audio level should be silent.
  15593. *
  15594. * @method Html5#setMuted
  15595. * @param {boolean} muted
  15596. * - True if the audio should be set to silent
  15597. * - False otherwise
  15598. *
  15599. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  15600. */
  15601. 'muted',
  15602. /**
  15603. * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
  15604. * audio level should be silent, but will only effect the muted level on intial playback..
  15605. *
  15606. * @method Html5.prototype.setDefaultMuted
  15607. * @param {boolean} defaultMuted
  15608. * - True if the audio should be set to silent
  15609. * - False otherwise
  15610. *
  15611. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  15612. */
  15613. 'defaultMuted',
  15614. /**
  15615. * Set the value of `autoplay` on the media element. `autoplay` indicates
  15616. * that the media should start to play as soon as the page is ready.
  15617. *
  15618. * @method Html5#setAutoplay
  15619. * @param {boolean} autoplay
  15620. * - True indicates that the media should start as soon as the page loads.
  15621. * - False indicates that the media should not start as soon as the page loads.
  15622. *
  15623. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  15624. */
  15625. 'autoplay',
  15626. /**
  15627. * Set the value of `loop` on the media element. `loop` indicates
  15628. * that the media should return to the start of the media and continue playing once
  15629. * it reaches the end.
  15630. *
  15631. * @method Html5#setLoop
  15632. * @param {boolean} loop
  15633. * - True indicates that playback should seek back to start once
  15634. * the end of a media is reached.
  15635. * - False indicates that playback should not loop back to the start when the
  15636. * end of the media is reached.
  15637. *
  15638. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  15639. */
  15640. 'loop',
  15641. /**
  15642. * Set the value of `playsinline` from the media element. `playsinline` indicates
  15643. * to the browser that non-fullscreen playback is preferred when fullscreen
  15644. * playback is the native default, such as in iOS Safari.
  15645. *
  15646. * @method Html5#setPlaysinline
  15647. * @param {boolean} playsinline
  15648. * - True indicates that the media should play inline.
  15649. * - False indicates that the media should not play inline.
  15650. *
  15651. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  15652. */
  15653. 'playsinline'].forEach(function (prop) {
  15654. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  15655. this.el_[prop] = v;
  15656. if (v) {
  15657. this.el_.setAttribute(prop, prop);
  15658. } else {
  15659. this.el_.removeAttribute(prop);
  15660. }
  15661. };
  15662. });
  15663. // Wrap native properties with a getter
  15664. // The list is as followed
  15665. // paused, currentTime, buffered, volume, poster, preload, error, seeking
  15666. // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
  15667. // readyState, videoWidth, videoHeight
  15668. [
  15669. /**
  15670. * Get the value of `paused` from the media element. `paused` indicates whether the media element
  15671. * is currently paused or not.
  15672. *
  15673. * @method Html5#paused
  15674. * @return {boolean}
  15675. * The value of `paused` from the media element.
  15676. *
  15677. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
  15678. */
  15679. 'paused',
  15680. /**
  15681. * Get the value of `currentTime` from the media element. `currentTime` indicates
  15682. * the current second that the media is at in playback.
  15683. *
  15684. * @method Html5#currentTime
  15685. * @return {number}
  15686. * The value of `currentTime` from the media element.
  15687. *
  15688. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
  15689. */
  15690. 'currentTime',
  15691. /**
  15692. * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
  15693. * object that represents the parts of the media that are already downloaded and
  15694. * available for playback.
  15695. *
  15696. * @method Html5#buffered
  15697. * @return {TimeRange}
  15698. * The value of `buffered` from the media element.
  15699. *
  15700. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
  15701. */
  15702. 'buffered',
  15703. /**
  15704. * Get the value of `volume` from the media element. `volume` indicates
  15705. * the current playback volume of audio for a media. `volume` will be a value from 0
  15706. * (silent) to 1 (loudest and default).
  15707. *
  15708. * @method Html5#volume
  15709. * @return {number}
  15710. * The value of `volume` from the media element. Value will be between 0-1.
  15711. *
  15712. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  15713. */
  15714. 'volume',
  15715. /**
  15716. * Get the value of `poster` from the media element. `poster` indicates
  15717. * that the url of an image file that can/will be shown when no media data is available.
  15718. *
  15719. * @method Html5#poster
  15720. * @return {string}
  15721. * The value of `poster` from the media element. Value will be a url to an
  15722. * image.
  15723. *
  15724. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
  15725. */
  15726. 'poster',
  15727. /**
  15728. * Get the value of `preload` from the media element. `preload` indicates
  15729. * what should download before the media is interacted with. It can have the following
  15730. * values:
  15731. * - none: nothing should be downloaded
  15732. * - metadata: poster and the first few frames of the media may be downloaded to get
  15733. * media dimensions and other metadata
  15734. * - auto: allow the media and metadata for the media to be downloaded before
  15735. * interaction
  15736. *
  15737. * @method Html5#preload
  15738. * @return {string}
  15739. * The value of `preload` from the media element. Will be 'none', 'metadata',
  15740. * or 'auto'.
  15741. *
  15742. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  15743. */
  15744. 'preload',
  15745. /**
  15746. * Get the value of the `error` from the media element. `error` indicates any
  15747. * MediaError that may have occured during playback. If error returns null there is no
  15748. * current error.
  15749. *
  15750. * @method Html5#error
  15751. * @return {MediaError|null}
  15752. * The value of `error` from the media element. Will be `MediaError` if there
  15753. * is a current error and null otherwise.
  15754. *
  15755. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
  15756. */
  15757. 'error',
  15758. /**
  15759. * Get the value of `seeking` from the media element. `seeking` indicates whether the
  15760. * media is currently seeking to a new position or not.
  15761. *
  15762. * @method Html5#seeking
  15763. * @return {boolean}
  15764. * - The value of `seeking` from the media element.
  15765. * - True indicates that the media is currently seeking to a new position.
  15766. * - Flase indicates that the media is not seeking to a new position at this time.
  15767. *
  15768. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
  15769. */
  15770. 'seeking',
  15771. /**
  15772. * Get the value of `seekable` from the media element. `seekable` returns a
  15773. * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
  15774. *
  15775. * @method Html5#seekable
  15776. * @return {TimeRange}
  15777. * The value of `seekable` from the media element. A `TimeRange` object
  15778. * indicating the current ranges of time that can be seeked to.
  15779. *
  15780. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
  15781. */
  15782. 'seekable',
  15783. /**
  15784. * Get the value of `ended` from the media element. `ended` indicates whether
  15785. * the media has reached the end or not.
  15786. *
  15787. * @method Html5#ended
  15788. * @return {boolean}
  15789. * - The value of `ended` from the media element.
  15790. * - True indicates that the media has ended.
  15791. * - False indicates that the media has not ended.
  15792. *
  15793. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
  15794. */
  15795. 'ended',
  15796. /**
  15797. * Get the value of `playbackRate` from the media element. `playbackRate` indicates
  15798. * the rate at which the media is currently playing back. Examples:
  15799. * - if playbackRate is set to 2, media will play twice as fast.
  15800. * - if playbackRate is set to 0.5, media will play half as fast.
  15801. *
  15802. * @method Html5#playbackRate
  15803. * @return {number}
  15804. * The value of `playbackRate` from the media element. A number indicating
  15805. * the current playback speed of the media, where 1 is normal speed.
  15806. *
  15807. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  15808. */
  15809. 'playbackRate',
  15810. /**
  15811. * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
  15812. * the rate at which the media is currently playing back. This value will not indicate the current
  15813. * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
  15814. *
  15815. * Examples:
  15816. * - if defaultPlaybackRate is set to 2, media will play twice as fast.
  15817. * - if defaultPlaybackRate is set to 0.5, media will play half as fast.
  15818. *
  15819. * @method Html5.prototype.defaultPlaybackRate
  15820. * @return {number}
  15821. * The value of `defaultPlaybackRate` from the media element. A number indicating
  15822. * the current playback speed of the media, where 1 is normal speed.
  15823. *
  15824. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  15825. */
  15826. 'defaultPlaybackRate',
  15827. /**
  15828. * Get the value of `played` from the media element. `played` returns a `TimeRange`
  15829. * object representing points in the media timeline that have been played.
  15830. *
  15831. * @method Html5#played
  15832. * @return {TimeRange}
  15833. * The value of `played` from the media element. A `TimeRange` object indicating
  15834. * the ranges of time that have been played.
  15835. *
  15836. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
  15837. */
  15838. 'played',
  15839. /**
  15840. * Get the value of `networkState` from the media element. `networkState` indicates
  15841. * the current network state. It returns an enumeration from the following list:
  15842. * - 0: NETWORK_EMPTY
  15843. * - 1: NEWORK_IDLE
  15844. * - 2: NETWORK_LOADING
  15845. * - 3: NETWORK_NO_SOURCE
  15846. *
  15847. * @method Html5#networkState
  15848. * @return {number}
  15849. * The value of `networkState` from the media element. This will be a number
  15850. * from the list in the description.
  15851. *
  15852. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
  15853. */
  15854. 'networkState',
  15855. /**
  15856. * Get the value of `readyState` from the media element. `readyState` indicates
  15857. * the current state of the media element. It returns an enumeration from the
  15858. * following list:
  15859. * - 0: HAVE_NOTHING
  15860. * - 1: HAVE_METADATA
  15861. * - 2: HAVE_CURRENT_DATA
  15862. * - 3: HAVE_FUTURE_DATA
  15863. * - 4: HAVE_ENOUGH_DATA
  15864. *
  15865. * @method Html5#readyState
  15866. * @return {number}
  15867. * The value of `readyState` from the media element. This will be a number
  15868. * from the list in the description.
  15869. *
  15870. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
  15871. */
  15872. 'readyState',
  15873. /**
  15874. * Get the value of `videoWidth` from the video element. `videoWidth` indicates
  15875. * the current width of the video in css pixels.
  15876. *
  15877. * @method Html5#videoWidth
  15878. * @return {number}
  15879. * The value of `videoWidth` from the video element. This will be a number
  15880. * in css pixels.
  15881. *
  15882. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  15883. */
  15884. 'videoWidth',
  15885. /**
  15886. * Get the value of `videoHeight` from the video element. `videoHeigth` indicates
  15887. * the current height of the video in css pixels.
  15888. *
  15889. * @method Html5#videoHeight
  15890. * @return {number}
  15891. * The value of `videoHeight` from the video element. This will be a number
  15892. * in css pixels.
  15893. *
  15894. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  15895. */
  15896. 'videoHeight'].forEach(function (prop) {
  15897. Html5.prototype[prop] = function () {
  15898. return this.el_[prop];
  15899. };
  15900. });
  15901. // Wrap native properties with a setter in this format:
  15902. // set + toTitleCase(name)
  15903. // The list is as follows:
  15904. // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
  15905. [
  15906. /**
  15907. * Set the value of `volume` on the media element. `volume` indicates the current
  15908. * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
  15909. * so on.
  15910. *
  15911. * @method Html5#setVolume
  15912. * @param {number} percentAsDecimal
  15913. * The volume percent as a decimal. Valid range is from 0-1.
  15914. *
  15915. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  15916. */
  15917. 'volume',
  15918. /**
  15919. * Set the value of `src` on the media element. `src` indicates the current
  15920. * {@link Tech~SourceObject} for the media.
  15921. *
  15922. * @method Html5#setSrc
  15923. * @param {Tech~SourceObject} src
  15924. * The source object to set as the current source.
  15925. *
  15926. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
  15927. */
  15928. 'src',
  15929. /**
  15930. * Set the value of `poster` on the media element. `poster` is the url to
  15931. * an image file that can/will be shown when no media data is available.
  15932. *
  15933. * @method Html5#setPoster
  15934. * @param {string} poster
  15935. * The url to an image that should be used as the `poster` for the media
  15936. * element.
  15937. *
  15938. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
  15939. */
  15940. 'poster',
  15941. /**
  15942. * Set the value of `preload` on the media element. `preload` indicates
  15943. * what should download before the media is interacted with. It can have the following
  15944. * values:
  15945. * - none: nothing should be downloaded
  15946. * - metadata: poster and the first few frames of the media may be downloaded to get
  15947. * media dimensions and other metadata
  15948. * - auto: allow the media and metadata for the media to be downloaded before
  15949. * interaction
  15950. *
  15951. * @method Html5#setPreload
  15952. * @param {string} preload
  15953. * The value of `preload` to set on the media element. Must be 'none', 'metadata',
  15954. * or 'auto'.
  15955. *
  15956. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  15957. */
  15958. 'preload',
  15959. /**
  15960. * Set the value of `playbackRate` on the media element. `playbackRate` indicates
  15961. * the rate at which the media should play back. Examples:
  15962. * - if playbackRate is set to 2, media will play twice as fast.
  15963. * - if playbackRate is set to 0.5, media will play half as fast.
  15964. *
  15965. * @method Html5#setPlaybackRate
  15966. * @return {number}
  15967. * The value of `playbackRate` from the media element. A number indicating
  15968. * the current playback speed of the media, where 1 is normal speed.
  15969. *
  15970. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  15971. */
  15972. 'playbackRate',
  15973. /**
  15974. * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
  15975. * the rate at which the media should play back upon initial startup. Changing this value
  15976. * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
  15977. *
  15978. * Example Values:
  15979. * - if playbackRate is set to 2, media will play twice as fast.
  15980. * - if playbackRate is set to 0.5, media will play half as fast.
  15981. *
  15982. * @method Html5.prototype.setDefaultPlaybackRate
  15983. * @return {number}
  15984. * The value of `defaultPlaybackRate` from the media element. A number indicating
  15985. * the current playback speed of the media, where 1 is normal speed.
  15986. *
  15987. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
  15988. */
  15989. 'defaultPlaybackRate'].forEach(function (prop) {
  15990. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  15991. this.el_[prop] = v;
  15992. };
  15993. });
  15994. // wrap native functions with a function
  15995. // The list is as follows:
  15996. // pause, load play
  15997. [
  15998. /**
  15999. * A wrapper around the media elements `pause` function. This will call the `HTML5`
  16000. * media elements `pause` function.
  16001. *
  16002. * @method Html5#pause
  16003. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
  16004. */
  16005. 'pause',
  16006. /**
  16007. * A wrapper around the media elements `load` function. This will call the `HTML5`s
  16008. * media element `load` function.
  16009. *
  16010. * @method Html5#load
  16011. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
  16012. */
  16013. 'load',
  16014. /**
  16015. * A wrapper around the media elements `play` function. This will call the `HTML5`s
  16016. * media element `play` function.
  16017. *
  16018. * @method Html5#play
  16019. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
  16020. */
  16021. 'play'].forEach(function (prop) {
  16022. Html5.prototype[prop] = function () {
  16023. return this.el_[prop]();
  16024. };
  16025. });
  16026. Tech.withSourceHandlers(Html5);
  16027. /**
  16028. * Native source handler for Html5, simply passes the source to the media element.
  16029. *
  16030. * @proprety {Tech~SourceObject} source
  16031. * The source object
  16032. *
  16033. * @proprety {Html5} tech
  16034. * The instance of the HTML5 tech.
  16035. */
  16036. Html5.nativeSourceHandler = {};
  16037. /**
  16038. * Check if the media element can play the given mime type.
  16039. *
  16040. * @param {string} type
  16041. * The mimetype to check
  16042. *
  16043. * @return {string}
  16044. * 'probably', 'maybe', or '' (empty string)
  16045. */
  16046. Html5.nativeSourceHandler.canPlayType = function (type) {
  16047. // IE9 on Windows 7 without MediaPlayer throws an error here
  16048. // https://github.com/videojs/video.js/issues/519
  16049. try {
  16050. return Html5.TEST_VID.canPlayType(type);
  16051. } catch (e) {
  16052. return '';
  16053. }
  16054. };
  16055. /**
  16056. * Check if the media element can handle a source natively.
  16057. *
  16058. * @param {Tech~SourceObject} source
  16059. * The source object
  16060. *
  16061. * @param {Object} [options]
  16062. * Options to be passed to the tech.
  16063. *
  16064. * @return {string}
  16065. * 'probably', 'maybe', or '' (empty string).
  16066. */
  16067. Html5.nativeSourceHandler.canHandleSource = function (source, options) {
  16068. // If a type was provided we should rely on that
  16069. if (source.type) {
  16070. return Html5.nativeSourceHandler.canPlayType(source.type);
  16071. // If no type, fall back to checking 'video/[EXTENSION]'
  16072. } else if (source.src) {
  16073. var ext = getFileExtension(source.src);
  16074. return Html5.nativeSourceHandler.canPlayType('video/' + ext);
  16075. }
  16076. return '';
  16077. };
  16078. /**
  16079. * Pass the source to the native media element.
  16080. *
  16081. * @param {Tech~SourceObject} source
  16082. * The source object
  16083. *
  16084. * @param {Html5} tech
  16085. * The instance of the Html5 tech
  16086. *
  16087. * @param {Object} [options]
  16088. * The options to pass to the source
  16089. */
  16090. Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
  16091. tech.setSrc(source.src);
  16092. };
  16093. /**
  16094. * A noop for the native dispose function, as cleanup is not needed.
  16095. */
  16096. Html5.nativeSourceHandler.dispose = function () {};
  16097. // Register the native source handler
  16098. Html5.registerSourceHandler(Html5.nativeSourceHandler);
  16099. Tech.registerTech('Html5', Html5);
  16100. var _templateObject$1 = taggedTemplateLiteralLoose(['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n '], ['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n ']);
  16101. /**
  16102. * @file player.js
  16103. */
  16104. // Subclasses Component
  16105. // The following imports are used only to ensure that the corresponding modules
  16106. // are always included in the video.js package. Importing the modules will
  16107. // execute them and they will register themselves with video.js.
  16108. // Import Html5 tech, at least for disposing the original video tag.
  16109. // The following tech events are simply re-triggered
  16110. // on the player when they happen
  16111. var TECH_EVENTS_RETRIGGER = [
  16112. /**
  16113. * Fired while the user agent is downloading media data.
  16114. *
  16115. * @event Player#progress
  16116. * @type {EventTarget~Event}
  16117. */
  16118. /**
  16119. * Retrigger the `progress` event that was triggered by the {@link Tech}.
  16120. *
  16121. * @private
  16122. * @method Player#handleTechProgress_
  16123. * @fires Player#progress
  16124. * @listens Tech#progress
  16125. */
  16126. 'progress',
  16127. /**
  16128. * Fires when the loading of an audio/video is aborted.
  16129. *
  16130. * @event Player#abort
  16131. * @type {EventTarget~Event}
  16132. */
  16133. /**
  16134. * Retrigger the `abort` event that was triggered by the {@link Tech}.
  16135. *
  16136. * @private
  16137. * @method Player#handleTechAbort_
  16138. * @fires Player#abort
  16139. * @listens Tech#abort
  16140. */
  16141. 'abort',
  16142. /**
  16143. * Fires when the browser is intentionally not getting media data.
  16144. *
  16145. * @event Player#suspend
  16146. * @type {EventTarget~Event}
  16147. */
  16148. /**
  16149. * Retrigger the `suspend` event that was triggered by the {@link Tech}.
  16150. *
  16151. * @private
  16152. * @method Player#handleTechSuspend_
  16153. * @fires Player#suspend
  16154. * @listens Tech#suspend
  16155. */
  16156. 'suspend',
  16157. /**
  16158. * Fires when the current playlist is empty.
  16159. *
  16160. * @event Player#emptied
  16161. * @type {EventTarget~Event}
  16162. */
  16163. /**
  16164. * Retrigger the `emptied` event that was triggered by the {@link Tech}.
  16165. *
  16166. * @private
  16167. * @method Player#handleTechEmptied_
  16168. * @fires Player#emptied
  16169. * @listens Tech#emptied
  16170. */
  16171. 'emptied',
  16172. /**
  16173. * Fires when the browser is trying to get media data, but data is not available.
  16174. *
  16175. * @event Player#stalled
  16176. * @type {EventTarget~Event}
  16177. */
  16178. /**
  16179. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  16180. *
  16181. * @private
  16182. * @method Player#handleTechStalled_
  16183. * @fires Player#stalled
  16184. * @listens Tech#stalled
  16185. */
  16186. 'stalled',
  16187. /**
  16188. * Fires when the browser has loaded meta data for the audio/video.
  16189. *
  16190. * @event Player#loadedmetadata
  16191. * @type {EventTarget~Event}
  16192. */
  16193. /**
  16194. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  16195. *
  16196. * @private
  16197. * @method Player#handleTechLoadedmetadata_
  16198. * @fires Player#loadedmetadata
  16199. * @listens Tech#loadedmetadata
  16200. */
  16201. 'loadedmetadata',
  16202. /**
  16203. * Fires when the browser has loaded the current frame of the audio/video.
  16204. *
  16205. * @event Player#loadeddata
  16206. * @type {event}
  16207. */
  16208. /**
  16209. * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
  16210. *
  16211. * @private
  16212. * @method Player#handleTechLoaddeddata_
  16213. * @fires Player#loadeddata
  16214. * @listens Tech#loadeddata
  16215. */
  16216. 'loadeddata',
  16217. /**
  16218. * Fires when the current playback position has changed.
  16219. *
  16220. * @event Player#timeupdate
  16221. * @type {event}
  16222. */
  16223. /**
  16224. * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
  16225. *
  16226. * @private
  16227. * @method Player#handleTechTimeUpdate_
  16228. * @fires Player#timeupdate
  16229. * @listens Tech#timeupdate
  16230. */
  16231. 'timeupdate',
  16232. /**
  16233. * Fires when the video's intrinsic dimensions change
  16234. *
  16235. * @event Player#resize
  16236. * @type {event}
  16237. */
  16238. /**
  16239. * Retrigger the `resize` event that was triggered by the {@link Tech}.
  16240. *
  16241. * @private
  16242. * @method Player#handleTechResize_
  16243. * @fires Player#resize
  16244. * @listens Tech#resize
  16245. */
  16246. 'resize',
  16247. /**
  16248. * Fires when the volume has been changed
  16249. *
  16250. * @event Player#volumechange
  16251. * @type {event}
  16252. */
  16253. /**
  16254. * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
  16255. *
  16256. * @private
  16257. * @method Player#handleTechVolumechange_
  16258. * @fires Player#volumechange
  16259. * @listens Tech#volumechange
  16260. */
  16261. 'volumechange',
  16262. /**
  16263. * Fires when the text track has been changed
  16264. *
  16265. * @event Player#texttrackchange
  16266. * @type {event}
  16267. */
  16268. /**
  16269. * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
  16270. *
  16271. * @private
  16272. * @method Player#handleTechTexttrackchange_
  16273. * @fires Player#texttrackchange
  16274. * @listens Tech#texttrackchange
  16275. */
  16276. 'texttrackchange'];
  16277. // events to queue when playback rate is zero
  16278. // this is a hash for the sole purpose of mapping non-camel-cased event names
  16279. // to camel-cased function names
  16280. var TECH_EVENTS_QUEUE = {
  16281. canplay: 'CanPlay',
  16282. canplaythrough: 'CanPlayThrough',
  16283. playing: 'Playing',
  16284. seeked: 'Seeked'
  16285. };
  16286. var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
  16287. var BREAKPOINT_CLASSES = {};
  16288. // grep: vjs-layout-tiny
  16289. // grep: vjs-layout-x-small
  16290. // grep: vjs-layout-small
  16291. // grep: vjs-layout-medium
  16292. // grep: vjs-layout-large
  16293. // grep: vjs-layout-x-large
  16294. // grep: vjs-layout-huge
  16295. BREAKPOINT_ORDER.forEach(function (k) {
  16296. var v = k.charAt(0) === 'x' ? 'x-' + k.substring(1) : k;
  16297. BREAKPOINT_CLASSES[k] = 'vjs-layout-' + v;
  16298. });
  16299. var DEFAULT_BREAKPOINTS = {
  16300. tiny: 210,
  16301. xsmall: 320,
  16302. small: 425,
  16303. medium: 768,
  16304. large: 1440,
  16305. xlarge: 2560,
  16306. huge: Infinity
  16307. };
  16308. /**
  16309. * An instance of the `Player` class is created when any of the Video.js setup methods
  16310. * are used to initialize a video.
  16311. *
  16312. * After an instance has been created it can be accessed globally in two ways:
  16313. * 1. By calling `videojs('example_video_1');`
  16314. * 2. By using it directly via `videojs.players.example_video_1;`
  16315. *
  16316. * @extends Component
  16317. */
  16318. var Player = function (_Component) {
  16319. inherits(Player, _Component);
  16320. /**
  16321. * Create an instance of this class.
  16322. *
  16323. * @param {Element} tag
  16324. * The original video DOM element used for configuring options.
  16325. *
  16326. * @param {Object} [options]
  16327. * Object of option names and values.
  16328. *
  16329. * @param {Component~ReadyCallback} [ready]
  16330. * Ready callback function.
  16331. */
  16332. function Player(tag, options, ready) {
  16333. classCallCheck(this, Player);
  16334. // Make sure tag ID exists
  16335. tag.id = tag.id || options.id || 'vjs_video_' + newGUID();
  16336. // Set Options
  16337. // The options argument overrides options set in the video tag
  16338. // which overrides globally set options.
  16339. // This latter part coincides with the load order
  16340. // (tag must exist before Player)
  16341. options = assign(Player.getTagSettings(tag), options);
  16342. // Delay the initialization of children because we need to set up
  16343. // player properties first, and can't use `this` before `super()`
  16344. options.initChildren = false;
  16345. // Same with creating the element
  16346. options.createEl = false;
  16347. // don't auto mixin the evented mixin
  16348. options.evented = false;
  16349. // we don't want the player to report touch activity on itself
  16350. // see enableTouchActivity in Component
  16351. options.reportTouchActivity = false;
  16352. // If language is not set, get the closest lang attribute
  16353. if (!options.language) {
  16354. if (typeof tag.closest === 'function') {
  16355. var closest = tag.closest('[lang]');
  16356. if (closest && closest.getAttribute) {
  16357. options.language = closest.getAttribute('lang');
  16358. }
  16359. } else {
  16360. var element = tag;
  16361. while (element && element.nodeType === 1) {
  16362. if (getAttributes(element).hasOwnProperty('lang')) {
  16363. options.language = element.getAttribute('lang');
  16364. break;
  16365. }
  16366. element = element.parentNode;
  16367. }
  16368. }
  16369. }
  16370. // Run base component initializing with new options
  16371. // create logger
  16372. var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready));
  16373. _this.log = createLogger(_this.id_);
  16374. // Tracks when a tech changes the poster
  16375. _this.isPosterFromTech_ = false;
  16376. // Holds callback info that gets queued when playback rate is zero
  16377. // and a seek is happening
  16378. _this.queuedCallbacks_ = [];
  16379. // Turn off API access because we're loading a new tech that might load asynchronously
  16380. _this.isReady_ = false;
  16381. // Init state hasStarted_
  16382. _this.hasStarted_ = false;
  16383. // Init state userActive_
  16384. _this.userActive_ = false;
  16385. // if the global option object was accidentally blown away by
  16386. // someone, bail early with an informative error
  16387. if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
  16388. throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
  16389. }
  16390. // Store the original tag used to set options
  16391. _this.tag = tag;
  16392. // Store the tag attributes used to restore html5 element
  16393. _this.tagAttributes = tag && getAttributes(tag);
  16394. // Update current language
  16395. _this.language(_this.options_.language);
  16396. // Update Supported Languages
  16397. if (options.languages) {
  16398. // Normalise player option languages to lowercase
  16399. var languagesToLower = {};
  16400. Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) {
  16401. languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1];
  16402. });
  16403. _this.languages_ = languagesToLower;
  16404. } else {
  16405. _this.languages_ = Player.prototype.options_.languages;
  16406. }
  16407. // Cache for video property values.
  16408. _this.cache_ = {};
  16409. // Set poster
  16410. _this.poster_ = options.poster || '';
  16411. // Set controls
  16412. _this.controls_ = !!options.controls;
  16413. // Set default values for lastVolume
  16414. _this.cache_.lastVolume = 1;
  16415. // Original tag settings stored in options
  16416. // now remove immediately so native controls don't flash.
  16417. // May be turned back on by HTML5 tech if nativeControlsForTouch is true
  16418. tag.controls = false;
  16419. tag.removeAttribute('controls');
  16420. // the attribute overrides the option
  16421. if (tag.hasAttribute('autoplay')) {
  16422. _this.options_.autoplay = true;
  16423. } else {
  16424. // otherwise use the setter to validate and
  16425. // set the correct value.
  16426. _this.autoplay(_this.options_.autoplay);
  16427. }
  16428. /*
  16429. * Store the internal state of scrubbing
  16430. *
  16431. * @private
  16432. * @return {Boolean} True if the user is scrubbing
  16433. */
  16434. _this.scrubbing_ = false;
  16435. _this.el_ = _this.createEl();
  16436. // Set default value for lastPlaybackRate
  16437. _this.cache_.lastPlaybackRate = _this.defaultPlaybackRate();
  16438. // Make this an evented object and use `el_` as its event bus.
  16439. evented(_this, { eventBusKey: 'el_' });
  16440. // We also want to pass the original player options to each component and plugin
  16441. // as well so they don't need to reach back into the player for options later.
  16442. // We also need to do another copy of this.options_ so we don't end up with
  16443. // an infinite loop.
  16444. var playerOptionsCopy = mergeOptions(_this.options_);
  16445. // Load plugins
  16446. if (options.plugins) {
  16447. var plugins = options.plugins;
  16448. Object.keys(plugins).forEach(function (name$$1) {
  16449. if (typeof this[name$$1] === 'function') {
  16450. this[name$$1](plugins[name$$1]);
  16451. } else {
  16452. throw new Error('plugin "' + name$$1 + '" does not exist');
  16453. }
  16454. }, _this);
  16455. }
  16456. _this.options_.playerOptions = playerOptionsCopy;
  16457. _this.middleware_ = [];
  16458. _this.initChildren();
  16459. // Set isAudio based on whether or not an audio tag was used
  16460. _this.isAudio(tag.nodeName.toLowerCase() === 'audio');
  16461. // Update controls className. Can't do this when the controls are initially
  16462. // set because the element doesn't exist yet.
  16463. if (_this.controls()) {
  16464. _this.addClass('vjs-controls-enabled');
  16465. } else {
  16466. _this.addClass('vjs-controls-disabled');
  16467. }
  16468. // Set ARIA label and region role depending on player type
  16469. _this.el_.setAttribute('role', 'region');
  16470. if (_this.isAudio()) {
  16471. _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
  16472. } else {
  16473. _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
  16474. }
  16475. if (_this.isAudio()) {
  16476. _this.addClass('vjs-audio');
  16477. }
  16478. if (_this.flexNotSupported_()) {
  16479. _this.addClass('vjs-no-flex');
  16480. }
  16481. // TODO: Make this smarter. Toggle user state between touching/mousing
  16482. // using events, since devices can have both touch and mouse events.
  16483. // if (browser.TOUCH_ENABLED) {
  16484. // this.addClass('vjs-touch-enabled');
  16485. // }
  16486. // iOS Safari has broken hover handling
  16487. if (!IS_IOS) {
  16488. _this.addClass('vjs-workinghover');
  16489. }
  16490. // Make player easily findable by ID
  16491. Player.players[_this.id_] = _this;
  16492. // Add a major version class to aid css in plugins
  16493. var majorVersion = version.split('.')[0];
  16494. _this.addClass('vjs-v' + majorVersion);
  16495. // When the player is first initialized, trigger activity so components
  16496. // like the control bar show themselves if needed
  16497. _this.userActive(true);
  16498. _this.reportUserActivity();
  16499. _this.one('play', _this.listenForUserActivity_);
  16500. _this.on('fullscreenchange', _this.handleFullscreenChange_);
  16501. _this.on('stageclick', _this.handleStageClick_);
  16502. _this.breakpoints(_this.options_.breakpoints);
  16503. _this.responsive(_this.options_.responsive);
  16504. _this.changingSrc_ = false;
  16505. _this.playWaitingForReady_ = false;
  16506. _this.playOnLoadstart_ = null;
  16507. return _this;
  16508. }
  16509. /**
  16510. * Destroys the video player and does any necessary cleanup.
  16511. *
  16512. * This is especially helpful if you are dynamically adding and removing videos
  16513. * to/from the DOM.
  16514. *
  16515. * @fires Player#dispose
  16516. */
  16517. Player.prototype.dispose = function dispose() {
  16518. /**
  16519. * Called when the player is being disposed of.
  16520. *
  16521. * @event Player#dispose
  16522. * @type {EventTarget~Event}
  16523. */
  16524. this.trigger('dispose');
  16525. // prevent dispose from being called twice
  16526. this.off('dispose');
  16527. if (this.styleEl_ && this.styleEl_.parentNode) {
  16528. this.styleEl_.parentNode.removeChild(this.styleEl_);
  16529. this.styleEl_ = null;
  16530. }
  16531. // Kill reference to this player
  16532. Player.players[this.id_] = null;
  16533. if (this.tag && this.tag.player) {
  16534. this.tag.player = null;
  16535. }
  16536. if (this.el_ && this.el_.player) {
  16537. this.el_.player = null;
  16538. }
  16539. if (this.tech_) {
  16540. this.tech_.dispose();
  16541. this.isPosterFromTech_ = false;
  16542. this.poster_ = '';
  16543. }
  16544. if (this.playerElIngest_) {
  16545. this.playerElIngest_ = null;
  16546. }
  16547. if (this.tag) {
  16548. this.tag = null;
  16549. }
  16550. clearCacheForPlayer(this);
  16551. // the actual .el_ is removed here
  16552. _Component.prototype.dispose.call(this);
  16553. };
  16554. /**
  16555. * Create the `Player`'s DOM element.
  16556. *
  16557. * @return {Element}
  16558. * The DOM element that gets created.
  16559. */
  16560. Player.prototype.createEl = function createEl$$1() {
  16561. var tag = this.tag;
  16562. var el = void 0;
  16563. var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
  16564. var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
  16565. if (playerElIngest) {
  16566. el = this.el_ = tag.parentNode;
  16567. } else if (!divEmbed) {
  16568. el = this.el_ = _Component.prototype.createEl.call(this, 'div');
  16569. }
  16570. // Copy over all the attributes from the tag, including ID and class
  16571. // ID will now reference player box, not the video tag
  16572. var attrs = getAttributes(tag);
  16573. if (divEmbed) {
  16574. el = this.el_ = tag;
  16575. tag = this.tag = document.createElement('video');
  16576. while (el.children.length) {
  16577. tag.appendChild(el.firstChild);
  16578. }
  16579. if (!hasClass(el, 'video-js')) {
  16580. addClass(el, 'video-js');
  16581. }
  16582. el.appendChild(tag);
  16583. playerElIngest = this.playerElIngest_ = el;
  16584. // copy over properties from the video-js element
  16585. // ie8 doesn't support Object.keys nor hasOwnProperty
  16586. // on dom elements so we have to specify properties individually
  16587. ['autoplay', 'controls', 'crossOrigin', 'defaultMuted', 'defaultPlaybackRate', 'loop', 'muted', 'playbackRate', 'src', 'volume'].forEach(function (prop) {
  16588. if (typeof el[prop] !== 'undefined') {
  16589. tag[prop] = el[prop];
  16590. }
  16591. });
  16592. }
  16593. // set tabindex to -1 to remove the video element from the focus order
  16594. tag.setAttribute('tabindex', '-1');
  16595. attrs.tabindex = '-1';
  16596. // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button)
  16597. // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
  16598. // Note that we can't detect if JAWS is being used, but this ARIA attribute
  16599. // doesn't change behavior of IE11 if JAWS is not being used
  16600. if (IE_VERSION) {
  16601. tag.setAttribute('role', 'application');
  16602. attrs.role = 'application';
  16603. }
  16604. // Remove width/height attrs from tag so CSS can make it 100% width/height
  16605. tag.removeAttribute('width');
  16606. tag.removeAttribute('height');
  16607. if ('width' in attrs) {
  16608. delete attrs.width;
  16609. }
  16610. if ('height' in attrs) {
  16611. delete attrs.height;
  16612. }
  16613. Object.getOwnPropertyNames(attrs).forEach(function (attr) {
  16614. // workaround so we don't totally break IE7
  16615. // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
  16616. if (attr === 'class') {
  16617. el.className += ' ' + attrs[attr];
  16618. if (divEmbed) {
  16619. tag.className += ' ' + attrs[attr];
  16620. }
  16621. } else {
  16622. el.setAttribute(attr, attrs[attr]);
  16623. if (divEmbed) {
  16624. tag.setAttribute(attr, attrs[attr]);
  16625. }
  16626. }
  16627. });
  16628. // Update tag id/class for use as HTML5 playback tech
  16629. // Might think we should do this after embedding in container so .vjs-tech class
  16630. // doesn't flash 100% width/height, but class only applies with .video-js parent
  16631. tag.playerId = tag.id;
  16632. tag.id += '_html5_api';
  16633. tag.className = 'vjs-tech';
  16634. // Make player findable on elements
  16635. tag.player = el.player = this;
  16636. // Default state of video is paused
  16637. this.addClass('vjs-paused');
  16638. // Add a style element in the player that we'll use to set the width/height
  16639. // of the player in a way that's still overrideable by CSS, just like the
  16640. // video element
  16641. if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
  16642. this.styleEl_ = createStyleElement('vjs-styles-dimensions');
  16643. var defaultsStyleEl = $('.vjs-styles-defaults');
  16644. var head = $('head');
  16645. head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
  16646. }
  16647. this.fill_ = false;
  16648. this.fluid_ = false;
  16649. // Pass in the width/height/aspectRatio options which will update the style el
  16650. this.width(this.options_.width);
  16651. this.height(this.options_.height);
  16652. this.fill(this.options_.fill);
  16653. this.fluid(this.options_.fluid);
  16654. this.aspectRatio(this.options_.aspectRatio);
  16655. // Hide any links within the video/audio tag, because IE doesn't hide them completely.
  16656. var links = tag.getElementsByTagName('a');
  16657. for (var i = 0; i < links.length; i++) {
  16658. var linkEl = links.item(i);
  16659. addClass(linkEl, 'vjs-hidden');
  16660. linkEl.setAttribute('hidden', 'hidden');
  16661. }
  16662. // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
  16663. // keep track of the original for later so we can know if the source originally failed
  16664. tag.initNetworkState_ = tag.networkState;
  16665. // Wrap video tag in div (el/box) container
  16666. if (tag.parentNode && !playerElIngest) {
  16667. tag.parentNode.insertBefore(el, tag);
  16668. }
  16669. // insert the tag as the first child of the player element
  16670. // then manually add it to the children array so that this.addChild
  16671. // will work properly for other components
  16672. //
  16673. // Breaks iPhone, fixed in HTML5 setup.
  16674. prependTo(tag, el);
  16675. this.children_.unshift(tag);
  16676. // Set lang attr on player to ensure CSS :lang() in consistent with player
  16677. // if it's been set to something different to the doc
  16678. this.el_.setAttribute('lang', this.language_);
  16679. this.el_ = el;
  16680. return el;
  16681. };
  16682. /**
  16683. * A getter/setter for the `Player`'s width. Returns the player's configured value.
  16684. * To get the current width use `currentWidth()`.
  16685. *
  16686. * @param {number} [value]
  16687. * The value to set the `Player`'s width to.
  16688. *
  16689. * @return {number}
  16690. * The current width of the `Player` when getting.
  16691. */
  16692. Player.prototype.width = function width(value) {
  16693. return this.dimension('width', value);
  16694. };
  16695. /**
  16696. * A getter/setter for the `Player`'s height. Returns the player's configured value.
  16697. * To get the current height use `currentheight()`.
  16698. *
  16699. * @param {number} [value]
  16700. * The value to set the `Player`'s heigth to.
  16701. *
  16702. * @return {number}
  16703. * The current height of the `Player` when getting.
  16704. */
  16705. Player.prototype.height = function height(value) {
  16706. return this.dimension('height', value);
  16707. };
  16708. /**
  16709. * A getter/setter for the `Player`'s width & height.
  16710. *
  16711. * @param {string} dimension
  16712. * This string can be:
  16713. * - 'width'
  16714. * - 'height'
  16715. *
  16716. * @param {number} [value]
  16717. * Value for dimension specified in the first argument.
  16718. *
  16719. * @return {number}
  16720. * The dimension arguments value when getting (width/height).
  16721. */
  16722. Player.prototype.dimension = function dimension(_dimension, value) {
  16723. var privDimension = _dimension + '_';
  16724. if (value === undefined) {
  16725. return this[privDimension] || 0;
  16726. }
  16727. if (value === '') {
  16728. // If an empty string is given, reset the dimension to be automatic
  16729. this[privDimension] = undefined;
  16730. this.updateStyleEl_();
  16731. return;
  16732. }
  16733. var parsedVal = parseFloat(value);
  16734. if (isNaN(parsedVal)) {
  16735. log.error('Improper value "' + value + '" supplied for for ' + _dimension);
  16736. return;
  16737. }
  16738. this[privDimension] = parsedVal;
  16739. this.updateStyleEl_();
  16740. };
  16741. /**
  16742. * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
  16743. *
  16744. * Turning this on will turn off fill mode.
  16745. *
  16746. * @param {boolean} [bool]
  16747. * - A value of true adds the class.
  16748. * - A value of false removes the class.
  16749. * - No value will be a getter.
  16750. *
  16751. * @return {boolean|undefined}
  16752. * - The value of fluid when getting.
  16753. * - `undefined` when setting.
  16754. */
  16755. Player.prototype.fluid = function fluid(bool) {
  16756. if (bool === undefined) {
  16757. return !!this.fluid_;
  16758. }
  16759. this.fluid_ = !!bool;
  16760. if (bool) {
  16761. this.addClass('vjs-fluid');
  16762. this.fill(false);
  16763. } else {
  16764. this.removeClass('vjs-fluid');
  16765. }
  16766. this.updateStyleEl_();
  16767. };
  16768. /**
  16769. * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
  16770. *
  16771. * Turning this on will turn off fluid mode.
  16772. *
  16773. * @param {boolean} [bool]
  16774. * - A value of true adds the class.
  16775. * - A value of false removes the class.
  16776. * - No value will be a getter.
  16777. *
  16778. * @return {boolean|undefined}
  16779. * - The value of fluid when getting.
  16780. * - `undefined` when setting.
  16781. */
  16782. Player.prototype.fill = function fill(bool) {
  16783. if (bool === undefined) {
  16784. return !!this.fill_;
  16785. }
  16786. this.fill_ = !!bool;
  16787. if (bool) {
  16788. this.addClass('vjs-fill');
  16789. this.fluid(false);
  16790. } else {
  16791. this.removeClass('vjs-fill');
  16792. }
  16793. };
  16794. /**
  16795. * Get/Set the aspect ratio
  16796. *
  16797. * @param {string} [ratio]
  16798. * Aspect ratio for player
  16799. *
  16800. * @return {string|undefined}
  16801. * returns the current aspect ratio when getting
  16802. */
  16803. /**
  16804. * A getter/setter for the `Player`'s aspect ratio.
  16805. *
  16806. * @param {string} [ratio]
  16807. * The value to set the `Player's aspect ratio to.
  16808. *
  16809. * @return {string|undefined}
  16810. * - The current aspect ratio of the `Player` when getting.
  16811. * - undefined when setting
  16812. */
  16813. Player.prototype.aspectRatio = function aspectRatio(ratio) {
  16814. if (ratio === undefined) {
  16815. return this.aspectRatio_;
  16816. }
  16817. // Check for width:height format
  16818. if (!/^\d+\:\d+$/.test(ratio)) {
  16819. throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
  16820. }
  16821. this.aspectRatio_ = ratio;
  16822. // We're assuming if you set an aspect ratio you want fluid mode,
  16823. // because in fixed mode you could calculate width and height yourself.
  16824. this.fluid(true);
  16825. this.updateStyleEl_();
  16826. };
  16827. /**
  16828. * Update styles of the `Player` element (height, width and aspect ratio).
  16829. *
  16830. * @private
  16831. * @listens Tech#loadedmetadata
  16832. */
  16833. Player.prototype.updateStyleEl_ = function updateStyleEl_() {
  16834. if (window.VIDEOJS_NO_DYNAMIC_STYLE === true) {
  16835. var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
  16836. var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
  16837. var techEl = this.tech_ && this.tech_.el();
  16838. if (techEl) {
  16839. if (_width >= 0) {
  16840. techEl.width = _width;
  16841. }
  16842. if (_height >= 0) {
  16843. techEl.height = _height;
  16844. }
  16845. }
  16846. return;
  16847. }
  16848. var width = void 0;
  16849. var height = void 0;
  16850. var aspectRatio = void 0;
  16851. var idClass = void 0;
  16852. // The aspect ratio is either used directly or to calculate width and height.
  16853. if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
  16854. // Use any aspectRatio that's been specifically set
  16855. aspectRatio = this.aspectRatio_;
  16856. } else if (this.videoWidth() > 0) {
  16857. // Otherwise try to get the aspect ratio from the video metadata
  16858. aspectRatio = this.videoWidth() + ':' + this.videoHeight();
  16859. } else {
  16860. // Or use a default. The video element's is 2:1, but 16:9 is more common.
  16861. aspectRatio = '16:9';
  16862. }
  16863. // Get the ratio as a decimal we can use to calculate dimensions
  16864. var ratioParts = aspectRatio.split(':');
  16865. var ratioMultiplier = ratioParts[1] / ratioParts[0];
  16866. if (this.width_ !== undefined) {
  16867. // Use any width that's been specifically set
  16868. width = this.width_;
  16869. } else if (this.height_ !== undefined) {
  16870. // Or calulate the width from the aspect ratio if a height has been set
  16871. width = this.height_ / ratioMultiplier;
  16872. } else {
  16873. // Or use the video's metadata, or use the video el's default of 300
  16874. width = this.videoWidth() || 300;
  16875. }
  16876. if (this.height_ !== undefined) {
  16877. // Use any height that's been specifically set
  16878. height = this.height_;
  16879. } else {
  16880. // Otherwise calculate the height from the ratio and the width
  16881. height = width * ratioMultiplier;
  16882. }
  16883. // Ensure the CSS class is valid by starting with an alpha character
  16884. if (/^[^a-zA-Z]/.test(this.id())) {
  16885. idClass = 'dimensions-' + this.id();
  16886. } else {
  16887. idClass = this.id() + '-dimensions';
  16888. }
  16889. // Ensure the right class is still on the player for the style element
  16890. this.addClass(idClass);
  16891. setTextContent(this.styleEl_, '\n .' + idClass + ' {\n width: ' + width + 'px;\n height: ' + height + 'px;\n }\n\n .' + idClass + '.vjs-fluid {\n padding-top: ' + ratioMultiplier * 100 + '%;\n }\n ');
  16892. };
  16893. /**
  16894. * Load/Create an instance of playback {@link Tech} including element
  16895. * and API methods. Then append the `Tech` element in `Player` as a child.
  16896. *
  16897. * @param {string} techName
  16898. * name of the playback technology
  16899. *
  16900. * @param {string} source
  16901. * video source
  16902. *
  16903. * @private
  16904. */
  16905. Player.prototype.loadTech_ = function loadTech_(techName, source) {
  16906. var _this2 = this;
  16907. // Pause and remove current playback technology
  16908. if (this.tech_) {
  16909. this.unloadTech_();
  16910. }
  16911. var titleTechName = toTitleCase(techName);
  16912. var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
  16913. // get rid of the HTML5 video tag as soon as we are using another tech
  16914. if (titleTechName !== 'Html5' && this.tag) {
  16915. Tech.getTech('Html5').disposeMediaElement(this.tag);
  16916. this.tag.player = null;
  16917. this.tag = null;
  16918. }
  16919. this.techName_ = titleTechName;
  16920. // Turn off API access because we're loading a new tech that might load asynchronously
  16921. this.isReady_ = false;
  16922. // if autoplay is a string we pass false to the tech
  16923. // because the player is going to handle autoplay on `loadstart`
  16924. var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay();
  16925. // Grab tech-specific options from player options and add source and parent element to use.
  16926. var techOptions = {
  16927. source: source,
  16928. autoplay: autoplay,
  16929. 'nativeControlsForTouch': this.options_.nativeControlsForTouch,
  16930. 'playerId': this.id(),
  16931. 'techId': this.id() + '_' + camelTechName + '_api',
  16932. 'playsinline': this.options_.playsinline,
  16933. 'preload': this.options_.preload,
  16934. 'loop': this.options_.loop,
  16935. 'muted': this.options_.muted,
  16936. 'poster': this.poster(),
  16937. 'language': this.language(),
  16938. 'playerElIngest': this.playerElIngest_ || false,
  16939. 'vtt.js': this.options_['vtt.js'],
  16940. 'canOverridePoster': !!this.options_.techCanOverridePoster,
  16941. 'enableSourceset': this.options_.enableSourceset
  16942. };
  16943. ALL.names.forEach(function (name$$1) {
  16944. var props = ALL[name$$1];
  16945. techOptions[props.getterName] = _this2[props.privateName];
  16946. });
  16947. assign(techOptions, this.options_[titleTechName]);
  16948. assign(techOptions, this.options_[camelTechName]);
  16949. assign(techOptions, this.options_[techName.toLowerCase()]);
  16950. if (this.tag) {
  16951. techOptions.tag = this.tag;
  16952. }
  16953. if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
  16954. techOptions.startTime = this.cache_.currentTime;
  16955. }
  16956. // Initialize tech instance
  16957. var TechClass = Tech.getTech(techName);
  16958. if (!TechClass) {
  16959. throw new Error('No Tech named \'' + titleTechName + '\' exists! \'' + titleTechName + '\' should be registered using videojs.registerTech()\'');
  16960. }
  16961. this.tech_ = new TechClass(techOptions);
  16962. // player.triggerReady is always async, so don't need this to be async
  16963. this.tech_.ready(bind(this, this.handleTechReady_), true);
  16964. textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
  16965. // Listen to all HTML5-defined events and trigger them on the player
  16966. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  16967. _this2.on(_this2.tech_, event, _this2['handleTech' + toTitleCase(event) + '_']);
  16968. });
  16969. Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
  16970. _this2.on(_this2.tech_, event, function (eventObj) {
  16971. if (_this2.tech_.playbackRate() === 0 && _this2.tech_.seeking()) {
  16972. _this2.queuedCallbacks_.push({
  16973. callback: _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'].bind(_this2),
  16974. event: eventObj
  16975. });
  16976. return;
  16977. }
  16978. _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'](eventObj);
  16979. });
  16980. });
  16981. this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
  16982. this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
  16983. this.on(this.tech_, 'waiting', this.handleTechWaiting_);
  16984. this.on(this.tech_, 'ended', this.handleTechEnded_);
  16985. this.on(this.tech_, 'seeking', this.handleTechSeeking_);
  16986. this.on(this.tech_, 'play', this.handleTechPlay_);
  16987. this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
  16988. this.on(this.tech_, 'pause', this.handleTechPause_);
  16989. this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
  16990. this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
  16991. this.on(this.tech_, 'error', this.handleTechError_);
  16992. this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
  16993. this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
  16994. this.on(this.tech_, 'textdata', this.handleTechTextData_);
  16995. this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
  16996. this.usingNativeControls(this.techGet_('controls'));
  16997. if (this.controls() && !this.usingNativeControls()) {
  16998. this.addTechControlsListeners_();
  16999. }
  17000. // Add the tech element in the DOM if it was not already there
  17001. // Make sure to not insert the original video element if using Html5
  17002. if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
  17003. prependTo(this.tech_.el(), this.el());
  17004. }
  17005. // Get rid of the original video tag reference after the first tech is loaded
  17006. if (this.tag) {
  17007. this.tag.player = null;
  17008. this.tag = null;
  17009. }
  17010. };
  17011. /**
  17012. * Unload and dispose of the current playback {@link Tech}.
  17013. *
  17014. * @private
  17015. */
  17016. Player.prototype.unloadTech_ = function unloadTech_() {
  17017. var _this3 = this;
  17018. // Save the current text tracks so that we can reuse the same text tracks with the next tech
  17019. ALL.names.forEach(function (name$$1) {
  17020. var props = ALL[name$$1];
  17021. _this3[props.privateName] = _this3[props.getterName]();
  17022. });
  17023. this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
  17024. this.isReady_ = false;
  17025. this.tech_.dispose();
  17026. this.tech_ = false;
  17027. if (this.isPosterFromTech_) {
  17028. this.poster_ = '';
  17029. this.trigger('posterchange');
  17030. }
  17031. this.isPosterFromTech_ = false;
  17032. };
  17033. /**
  17034. * Return a reference to the current {@link Tech}.
  17035. * It will print a warning by default about the danger of using the tech directly
  17036. * but any argument that is passed in will silence the warning.
  17037. *
  17038. * @param {*} [safety]
  17039. * Anything passed in to silence the warning
  17040. *
  17041. * @return {Tech}
  17042. * The Tech
  17043. */
  17044. Player.prototype.tech = function tech(safety) {
  17045. if (safety === undefined) {
  17046. log.warn(tsml(_templateObject$1));
  17047. }
  17048. return this.tech_;
  17049. };
  17050. /**
  17051. * Set up click and touch listeners for the playback element
  17052. *
  17053. * - On desktops: a click on the video itself will toggle playback
  17054. * - On mobile devices: a click on the video toggles controls
  17055. * which is done by toggling the user state between active and
  17056. * inactive
  17057. * - A tap can signal that a user has become active or has become inactive
  17058. * e.g. a quick tap on an iPhone movie should reveal the controls. Another
  17059. * quick tap should hide them again (signaling the user is in an inactive
  17060. * viewing state)
  17061. * - In addition to this, we still want the user to be considered inactive after
  17062. * a few seconds of inactivity.
  17063. *
  17064. * > Note: the only part of iOS interaction we can't mimic with this setup
  17065. * is a touch and hold on the video element counting as activity in order to
  17066. * keep the controls showing, but that shouldn't be an issue. A touch and hold
  17067. * on any controls will still keep the user active
  17068. *
  17069. * @private
  17070. */
  17071. Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() {
  17072. // Make sure to remove all the previous listeners in case we are called multiple times.
  17073. this.removeTechControlsListeners_();
  17074. // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
  17075. // trigger mousedown/up.
  17076. // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
  17077. // Any touch events are set to block the mousedown event from happening
  17078. this.on(this.tech_, 'mousedown', this.handleTechClick_);
  17079. // If the controls were hidden we don't want that to change without a tap event
  17080. // so we'll check if the controls were already showing before reporting user
  17081. // activity
  17082. this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
  17083. this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
  17084. this.on(this.tech_, 'touchend', this.handleTechTouchEnd_);
  17085. // The tap listener needs to come after the touchend listener because the tap
  17086. // listener cancels out any reportedUserActivity when setting userActive(false)
  17087. this.on(this.tech_, 'tap', this.handleTechTap_);
  17088. };
  17089. /**
  17090. * Remove the listeners used for click and tap controls. This is needed for
  17091. * toggling to controls disabled, where a tap/touch should do nothing.
  17092. *
  17093. * @private
  17094. */
  17095. Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() {
  17096. // We don't want to just use `this.off()` because there might be other needed
  17097. // listeners added by techs that extend this.
  17098. this.off(this.tech_, 'tap', this.handleTechTap_);
  17099. this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
  17100. this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
  17101. this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
  17102. this.off(this.tech_, 'mousedown', this.handleTechClick_);
  17103. };
  17104. /**
  17105. * Player waits for the tech to be ready
  17106. *
  17107. * @private
  17108. */
  17109. Player.prototype.handleTechReady_ = function handleTechReady_() {
  17110. this.triggerReady();
  17111. // Keep the same volume as before
  17112. if (this.cache_.volume) {
  17113. this.techCall_('setVolume', this.cache_.volume);
  17114. }
  17115. // Look if the tech found a higher resolution poster while loading
  17116. this.handleTechPosterChange_();
  17117. // Update the duration if available
  17118. this.handleTechDurationChange_();
  17119. // Chrome and Safari both have issues with autoplay.
  17120. // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
  17121. // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
  17122. // This fixes both issues. Need to wait for API, so it updates displays correctly
  17123. if ((this.src() || this.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) {
  17124. try {
  17125. // Chrome Fix. Fixed in Chrome v16.
  17126. delete this.tag.poster;
  17127. } catch (e) {
  17128. log('deleting tag.poster throws in some browsers', e);
  17129. }
  17130. }
  17131. };
  17132. /**
  17133. * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
  17134. * function will also trigger {@link Player#firstplay} if it is the first loadstart
  17135. * for a video.
  17136. *
  17137. * @fires Player#loadstart
  17138. * @fires Player#firstplay
  17139. * @listens Tech#loadstart
  17140. * @private
  17141. */
  17142. Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() {
  17143. // TODO: Update to use `emptied` event instead. See #1277.
  17144. this.removeClass('vjs-ended');
  17145. this.removeClass('vjs-seeking');
  17146. // reset the error state
  17147. this.error(null);
  17148. // If it's already playing we want to trigger a firstplay event now.
  17149. // The firstplay event relies on both the play and loadstart events
  17150. // which can happen in any order for a new source
  17151. if (!this.paused()) {
  17152. /**
  17153. * Fired when the user agent begins looking for media data
  17154. *
  17155. * @event Player#loadstart
  17156. * @type {EventTarget~Event}
  17157. */
  17158. this.trigger('loadstart');
  17159. this.trigger('firstplay');
  17160. } else {
  17161. // reset the hasStarted state
  17162. this.hasStarted(false);
  17163. this.trigger('loadstart');
  17164. }
  17165. // autoplay happens after loadstart for the browser,
  17166. // so we mimic that behavior
  17167. this.manualAutoplay_(this.autoplay());
  17168. };
  17169. /**
  17170. * Handle autoplay string values, rather than the typical boolean
  17171. * values that should be handled by the tech. Note that this is not
  17172. * part of any specification. Valid values and what they do can be
  17173. * found on the autoplay getter at Player#autoplay()
  17174. */
  17175. Player.prototype.manualAutoplay_ = function manualAutoplay_(type) {
  17176. var _this4 = this;
  17177. if (!this.tech_ || typeof type !== 'string') {
  17178. return;
  17179. }
  17180. var muted = function muted() {
  17181. var previouslyMuted = _this4.muted();
  17182. _this4.muted(true);
  17183. var playPromise = _this4.play();
  17184. if (!playPromise || !playPromise.then || !playPromise['catch']) {
  17185. return;
  17186. }
  17187. return playPromise['catch'](function (e) {
  17188. // restore old value of muted on failure
  17189. _this4.muted(previouslyMuted);
  17190. });
  17191. };
  17192. var promise = void 0;
  17193. if (type === 'any') {
  17194. promise = this.play();
  17195. if (promise && promise.then && promise['catch']) {
  17196. promise['catch'](function () {
  17197. return muted();
  17198. });
  17199. }
  17200. } else if (type === 'muted') {
  17201. promise = muted();
  17202. } else {
  17203. promise = this.play();
  17204. }
  17205. if (!promise || !promise.then || !promise['catch']) {
  17206. return;
  17207. }
  17208. return promise.then(function () {
  17209. _this4.trigger({ type: 'autoplay-success', autoplay: type });
  17210. })['catch'](function (e) {
  17211. _this4.trigger({ type: 'autoplay-failure', autoplay: type });
  17212. });
  17213. };
  17214. /**
  17215. * Update the internal source caches so that we return the correct source from
  17216. * `src()`, `currentSource()`, and `currentSources()`.
  17217. *
  17218. * > Note: `currentSources` will not be updated if the source that is passed in exists
  17219. * in the current `currentSources` cache.
  17220. *
  17221. *
  17222. * @param {Tech~SourceObject} srcObj
  17223. * A string or object source to update our caches to.
  17224. */
  17225. Player.prototype.updateSourceCaches_ = function updateSourceCaches_() {
  17226. var srcObj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  17227. var src = srcObj;
  17228. var type = '';
  17229. if (typeof src !== 'string') {
  17230. src = srcObj.src;
  17231. type = srcObj.type;
  17232. }
  17233. // make sure all the caches are set to default values
  17234. // to prevent null checking
  17235. this.cache_.source = this.cache_.source || {};
  17236. this.cache_.sources = this.cache_.sources || [];
  17237. // try to get the type of the src that was passed in
  17238. if (src && !type) {
  17239. type = findMimetype(this, src);
  17240. }
  17241. // update `currentSource` cache always
  17242. this.cache_.source = mergeOptions({}, srcObj, { src: src, type: type });
  17243. var matchingSources = this.cache_.sources.filter(function (s) {
  17244. return s.src && s.src === src;
  17245. });
  17246. var sourceElSources = [];
  17247. var sourceEls = this.$$('source');
  17248. var matchingSourceEls = [];
  17249. for (var i = 0; i < sourceEls.length; i++) {
  17250. var sourceObj = getAttributes(sourceEls[i]);
  17251. sourceElSources.push(sourceObj);
  17252. if (sourceObj.src && sourceObj.src === src) {
  17253. matchingSourceEls.push(sourceObj.src);
  17254. }
  17255. }
  17256. // if we have matching source els but not matching sources
  17257. // the current source cache is not up to date
  17258. if (matchingSourceEls.length && !matchingSources.length) {
  17259. this.cache_.sources = sourceElSources;
  17260. // if we don't have matching source or source els set the
  17261. // sources cache to the `currentSource` cache
  17262. } else if (!matchingSources.length) {
  17263. this.cache_.sources = [this.cache_.source];
  17264. }
  17265. // update the tech `src` cache
  17266. this.cache_.src = src;
  17267. };
  17268. /**
  17269. * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
  17270. * causing the media element to reload.
  17271. *
  17272. * It will fire for the initial source and each subsequent source.
  17273. * This event is a custom event from Video.js and is triggered by the {@link Tech}.
  17274. *
  17275. * The event object for this event contains a `src` property that will contain the source
  17276. * that was available when the event was triggered. This is generally only necessary if Video.js
  17277. * is switching techs while the source was being changed.
  17278. *
  17279. * It is also fired when `load` is called on the player (or media element)
  17280. * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
  17281. * says that the resource selection algorithm needs to be aborted and restarted.
  17282. * In this case, it is very likely that the `src` property will be set to the
  17283. * empty string `""` to indicate we do not know what the source will be but
  17284. * that it is changing.
  17285. *
  17286. * *This event is currently still experimental and may change in minor releases.*
  17287. * __To use this, pass `enableSourceset` option to the player.__
  17288. *
  17289. * @event Player#sourceset
  17290. * @type {EventTarget~Event}
  17291. * @prop {string} src
  17292. * The source url available when the `sourceset` was triggered.
  17293. * It will be an empty string if we cannot know what the source is
  17294. * but know that the source will change.
  17295. */
  17296. /**
  17297. * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
  17298. *
  17299. * @fires Player#sourceset
  17300. * @listens Tech#sourceset
  17301. * @private
  17302. */
  17303. Player.prototype.handleTechSourceset_ = function handleTechSourceset_(event) {
  17304. var _this5 = this;
  17305. // only update the source cache when the source
  17306. // was not updated using the player api
  17307. if (!this.changingSrc_) {
  17308. var updateSourceCaches = function updateSourceCaches(src) {
  17309. return _this5.updateSourceCaches_(src);
  17310. };
  17311. var playerSrc = this.currentSource().src;
  17312. var eventSrc = event.src;
  17313. // if we have a playerSrc that is not a blob, and a tech src that is a blob
  17314. if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
  17315. // if both the tech source and the player source were updated we assume
  17316. // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
  17317. if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
  17318. updateSourceCaches = function updateSourceCaches() {};
  17319. }
  17320. }
  17321. // update the source to the intial source right away
  17322. // in some cases this will be empty string
  17323. updateSourceCaches(eventSrc);
  17324. // if the `sourceset` `src` was an empty string
  17325. // wait for a `loadstart` to update the cache to `currentSrc`.
  17326. // If a sourceset happens before a `loadstart`, we reset the state
  17327. // as this function will be called again.
  17328. if (!event.src) {
  17329. var updateCache = function updateCache(e) {
  17330. if (e.type !== 'sourceset') {
  17331. var techSrc = _this5.techGet('currentSrc');
  17332. _this5.lastSource_.tech = techSrc;
  17333. _this5.updateSourceCaches_(techSrc);
  17334. }
  17335. _this5.tech_.off(['sourceset', 'loadstart'], updateCache);
  17336. };
  17337. this.tech_.one(['sourceset', 'loadstart'], updateCache);
  17338. }
  17339. }
  17340. this.lastSource_ = { player: this.currentSource().src, tech: event.src };
  17341. this.trigger({
  17342. src: event.src,
  17343. type: 'sourceset'
  17344. });
  17345. };
  17346. /**
  17347. * Add/remove the vjs-has-started class
  17348. *
  17349. * @fires Player#firstplay
  17350. *
  17351. * @param {boolean} request
  17352. * - true: adds the class
  17353. * - false: remove the class
  17354. *
  17355. * @return {boolean}
  17356. * the boolean value of hasStarted_
  17357. */
  17358. Player.prototype.hasStarted = function hasStarted(request) {
  17359. if (request === undefined) {
  17360. // act as getter, if we have no request to change
  17361. return this.hasStarted_;
  17362. }
  17363. if (request === this.hasStarted_) {
  17364. return;
  17365. }
  17366. this.hasStarted_ = request;
  17367. if (this.hasStarted_) {
  17368. this.addClass('vjs-has-started');
  17369. this.trigger('firstplay');
  17370. } else {
  17371. this.removeClass('vjs-has-started');
  17372. }
  17373. };
  17374. /**
  17375. * Fired whenever the media begins or resumes playback
  17376. *
  17377. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
  17378. * @fires Player#play
  17379. * @listens Tech#play
  17380. * @private
  17381. */
  17382. Player.prototype.handleTechPlay_ = function handleTechPlay_() {
  17383. this.removeClass('vjs-ended');
  17384. this.removeClass('vjs-paused');
  17385. this.addClass('vjs-playing');
  17386. // hide the poster when the user hits play
  17387. this.hasStarted(true);
  17388. /**
  17389. * Triggered whenever an {@link Tech#play} event happens. Indicates that
  17390. * playback has started or resumed.
  17391. *
  17392. * @event Player#play
  17393. * @type {EventTarget~Event}
  17394. */
  17395. this.trigger('play');
  17396. };
  17397. /**
  17398. * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
  17399. *
  17400. * If there were any events queued while the playback rate was zero, fire
  17401. * those events now.
  17402. *
  17403. * @private
  17404. * @method Player#handleTechRateChange_
  17405. * @fires Player#ratechange
  17406. * @listens Tech#ratechange
  17407. */
  17408. Player.prototype.handleTechRateChange_ = function handleTechRateChange_() {
  17409. if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
  17410. this.queuedCallbacks_.forEach(function (queued) {
  17411. return queued.callback(queued.event);
  17412. });
  17413. this.queuedCallbacks_ = [];
  17414. }
  17415. this.cache_.lastPlaybackRate = this.tech_.playbackRate();
  17416. /**
  17417. * Fires when the playing speed of the audio/video is changed
  17418. *
  17419. * @event Player#ratechange
  17420. * @type {event}
  17421. */
  17422. this.trigger('ratechange');
  17423. };
  17424. /**
  17425. * Retrigger the `waiting` event that was triggered by the {@link Tech}.
  17426. *
  17427. * @fires Player#waiting
  17428. * @listens Tech#waiting
  17429. * @private
  17430. */
  17431. Player.prototype.handleTechWaiting_ = function handleTechWaiting_() {
  17432. var _this6 = this;
  17433. this.addClass('vjs-waiting');
  17434. /**
  17435. * A readyState change on the DOM element has caused playback to stop.
  17436. *
  17437. * @event Player#waiting
  17438. * @type {EventTarget~Event}
  17439. */
  17440. this.trigger('waiting');
  17441. this.one('timeupdate', function () {
  17442. return _this6.removeClass('vjs-waiting');
  17443. });
  17444. };
  17445. /**
  17446. * Retrigger the `canplay` event that was triggered by the {@link Tech}.
  17447. * > Note: This is not consistent between browsers. See #1351
  17448. *
  17449. * @fires Player#canplay
  17450. * @listens Tech#canplay
  17451. * @private
  17452. */
  17453. Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() {
  17454. this.removeClass('vjs-waiting');
  17455. /**
  17456. * The media has a readyState of HAVE_FUTURE_DATA or greater.
  17457. *
  17458. * @event Player#canplay
  17459. * @type {EventTarget~Event}
  17460. */
  17461. this.trigger('canplay');
  17462. };
  17463. /**
  17464. * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
  17465. *
  17466. * @fires Player#canplaythrough
  17467. * @listens Tech#canplaythrough
  17468. * @private
  17469. */
  17470. Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
  17471. this.removeClass('vjs-waiting');
  17472. /**
  17473. * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
  17474. * entire media file can be played without buffering.
  17475. *
  17476. * @event Player#canplaythrough
  17477. * @type {EventTarget~Event}
  17478. */
  17479. this.trigger('canplaythrough');
  17480. };
  17481. /**
  17482. * Retrigger the `playing` event that was triggered by the {@link Tech}.
  17483. *
  17484. * @fires Player#playing
  17485. * @listens Tech#playing
  17486. * @private
  17487. */
  17488. Player.prototype.handleTechPlaying_ = function handleTechPlaying_() {
  17489. this.removeClass('vjs-waiting');
  17490. /**
  17491. * The media is no longer blocked from playback, and has started playing.
  17492. *
  17493. * @event Player#playing
  17494. * @type {EventTarget~Event}
  17495. */
  17496. this.trigger('playing');
  17497. };
  17498. /**
  17499. * Retrigger the `seeking` event that was triggered by the {@link Tech}.
  17500. *
  17501. * @fires Player#seeking
  17502. * @listens Tech#seeking
  17503. * @private
  17504. */
  17505. Player.prototype.handleTechSeeking_ = function handleTechSeeking_() {
  17506. this.addClass('vjs-seeking');
  17507. /**
  17508. * Fired whenever the player is jumping to a new time
  17509. *
  17510. * @event Player#seeking
  17511. * @type {EventTarget~Event}
  17512. */
  17513. this.trigger('seeking');
  17514. };
  17515. /**
  17516. * Retrigger the `seeked` event that was triggered by the {@link Tech}.
  17517. *
  17518. * @fires Player#seeked
  17519. * @listens Tech#seeked
  17520. * @private
  17521. */
  17522. Player.prototype.handleTechSeeked_ = function handleTechSeeked_() {
  17523. this.removeClass('vjs-seeking');
  17524. /**
  17525. * Fired when the player has finished jumping to a new time
  17526. *
  17527. * @event Player#seeked
  17528. * @type {EventTarget~Event}
  17529. */
  17530. this.trigger('seeked');
  17531. };
  17532. /**
  17533. * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
  17534. *
  17535. * @fires Player#firstplay
  17536. * @listens Tech#firstplay
  17537. * @deprecated As of 6.0 firstplay event is deprecated.
  17538. * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
  17539. * @private
  17540. */
  17541. Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() {
  17542. // If the first starttime attribute is specified
  17543. // then we will start at the given offset in seconds
  17544. if (this.options_.starttime) {
  17545. log.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
  17546. this.currentTime(this.options_.starttime);
  17547. }
  17548. this.addClass('vjs-has-started');
  17549. /**
  17550. * Fired the first time a video is played. Not part of the HLS spec, and this is
  17551. * probably not the best implementation yet, so use sparingly. If you don't have a
  17552. * reason to prevent playback, use `myPlayer.one('play');` instead.
  17553. *
  17554. * @event Player#firstplay
  17555. * @deprecated As of 6.0 firstplay event is deprecated.
  17556. * @type {EventTarget~Event}
  17557. */
  17558. this.trigger('firstplay');
  17559. };
  17560. /**
  17561. * Retrigger the `pause` event that was triggered by the {@link Tech}.
  17562. *
  17563. * @fires Player#pause
  17564. * @listens Tech#pause
  17565. * @private
  17566. */
  17567. Player.prototype.handleTechPause_ = function handleTechPause_() {
  17568. this.removeClass('vjs-playing');
  17569. this.addClass('vjs-paused');
  17570. /**
  17571. * Fired whenever the media has been paused
  17572. *
  17573. * @event Player#pause
  17574. * @type {EventTarget~Event}
  17575. */
  17576. this.trigger('pause');
  17577. };
  17578. /**
  17579. * Retrigger the `ended` event that was triggered by the {@link Tech}.
  17580. *
  17581. * @fires Player#ended
  17582. * @listens Tech#ended
  17583. * @private
  17584. */
  17585. Player.prototype.handleTechEnded_ = function handleTechEnded_() {
  17586. this.addClass('vjs-ended');
  17587. if (this.options_.loop) {
  17588. this.currentTime(0);
  17589. this.play();
  17590. } else if (!this.paused()) {
  17591. this.pause();
  17592. }
  17593. /**
  17594. * Fired when the end of the media resource is reached (currentTime == duration)
  17595. *
  17596. * @event Player#ended
  17597. * @type {EventTarget~Event}
  17598. */
  17599. this.trigger('ended');
  17600. };
  17601. /**
  17602. * Fired when the duration of the media resource is first known or changed
  17603. *
  17604. * @listens Tech#durationchange
  17605. * @private
  17606. */
  17607. Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() {
  17608. this.duration(this.techGet_('duration'));
  17609. };
  17610. /**
  17611. * Handle a click on the media element to play/pause
  17612. *
  17613. * @param {EventTarget~Event} event
  17614. * the event that caused this function to trigger
  17615. *
  17616. * @listens Tech#mousedown
  17617. * @private
  17618. */
  17619. Player.prototype.handleTechClick_ = function handleTechClick_(event) {
  17620. if (!isSingleLeftClick(event)) {
  17621. return;
  17622. }
  17623. // When controls are disabled a click should not toggle playback because
  17624. // the click is considered a control
  17625. if (!this.controls_) {
  17626. return;
  17627. }
  17628. if (this.paused()) {
  17629. silencePromise(this.play());
  17630. } else {
  17631. this.pause();
  17632. }
  17633. };
  17634. /**
  17635. * Handle a tap on the media element. It will toggle the user
  17636. * activity state, which hides and shows the controls.
  17637. *
  17638. * @listens Tech#tap
  17639. * @private
  17640. */
  17641. Player.prototype.handleTechTap_ = function handleTechTap_() {
  17642. this.userActive(!this.userActive());
  17643. };
  17644. /**
  17645. * Handle touch to start
  17646. *
  17647. * @listens Tech#touchstart
  17648. * @private
  17649. */
  17650. Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() {
  17651. this.userWasActive = this.userActive();
  17652. };
  17653. /**
  17654. * Handle touch to move
  17655. *
  17656. * @listens Tech#touchmove
  17657. * @private
  17658. */
  17659. Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() {
  17660. if (this.userWasActive) {
  17661. this.reportUserActivity();
  17662. }
  17663. };
  17664. /**
  17665. * Handle touch to end
  17666. *
  17667. * @param {EventTarget~Event} event
  17668. * the touchend event that triggered
  17669. * this function
  17670. *
  17671. * @listens Tech#touchend
  17672. * @private
  17673. */
  17674. Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
  17675. // Stop the mouse events from also happening
  17676. event.preventDefault();
  17677. };
  17678. /**
  17679. * Fired when the player switches in or out of fullscreen mode
  17680. *
  17681. * @private
  17682. * @listens Player#fullscreenchange
  17683. */
  17684. Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() {
  17685. if (this.isFullscreen()) {
  17686. this.addClass('vjs-fullscreen');
  17687. } else {
  17688. this.removeClass('vjs-fullscreen');
  17689. }
  17690. };
  17691. /**
  17692. * native click events on the SWF aren't triggered on IE11, Win8.1RT
  17693. * use stageclick events triggered from inside the SWF instead
  17694. *
  17695. * @private
  17696. * @listens stageclick
  17697. */
  17698. Player.prototype.handleStageClick_ = function handleStageClick_() {
  17699. this.reportUserActivity();
  17700. };
  17701. /**
  17702. * Handle Tech Fullscreen Change
  17703. *
  17704. * @param {EventTarget~Event} event
  17705. * the fullscreenchange event that triggered this function
  17706. *
  17707. * @param {Object} data
  17708. * the data that was sent with the event
  17709. *
  17710. * @private
  17711. * @listens Tech#fullscreenchange
  17712. * @fires Player#fullscreenchange
  17713. */
  17714. Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
  17715. if (data) {
  17716. this.isFullscreen(data.isFullscreen);
  17717. }
  17718. /**
  17719. * Fired when going in and out of fullscreen.
  17720. *
  17721. * @event Player#fullscreenchange
  17722. * @type {EventTarget~Event}
  17723. */
  17724. this.trigger('fullscreenchange');
  17725. };
  17726. /**
  17727. * Fires when an error occurred during the loading of an audio/video.
  17728. *
  17729. * @private
  17730. * @listens Tech#error
  17731. */
  17732. Player.prototype.handleTechError_ = function handleTechError_() {
  17733. var error = this.tech_.error();
  17734. this.error(error);
  17735. };
  17736. /**
  17737. * Retrigger the `textdata` event that was triggered by the {@link Tech}.
  17738. *
  17739. * @fires Player#textdata
  17740. * @listens Tech#textdata
  17741. * @private
  17742. */
  17743. Player.prototype.handleTechTextData_ = function handleTechTextData_() {
  17744. var data = null;
  17745. if (arguments.length > 1) {
  17746. data = arguments[1];
  17747. }
  17748. /**
  17749. * Fires when we get a textdata event from tech
  17750. *
  17751. * @event Player#textdata
  17752. * @type {EventTarget~Event}
  17753. */
  17754. this.trigger('textdata', data);
  17755. };
  17756. /**
  17757. * Get object for cached values.
  17758. *
  17759. * @return {Object}
  17760. * get the current object cache
  17761. */
  17762. Player.prototype.getCache = function getCache() {
  17763. return this.cache_;
  17764. };
  17765. /**
  17766. * Pass values to the playback tech
  17767. *
  17768. * @param {string} [method]
  17769. * the method to call
  17770. *
  17771. * @param {Object} arg
  17772. * the argument to pass
  17773. *
  17774. * @private
  17775. */
  17776. Player.prototype.techCall_ = function techCall_(method, arg) {
  17777. // If it's not ready yet, call method when it is
  17778. this.ready(function () {
  17779. if (method in allowedSetters) {
  17780. return set$1(this.middleware_, this.tech_, method, arg);
  17781. } else if (method in allowedMediators) {
  17782. return mediate(this.middleware_, this.tech_, method, arg);
  17783. }
  17784. try {
  17785. if (this.tech_) {
  17786. this.tech_[method](arg);
  17787. }
  17788. } catch (e) {
  17789. log(e);
  17790. throw e;
  17791. }
  17792. }, true);
  17793. };
  17794. /**
  17795. * Get calls can't wait for the tech, and sometimes don't need to.
  17796. *
  17797. * @param {string} method
  17798. * Tech method
  17799. *
  17800. * @return {Function|undefined}
  17801. * the method or undefined
  17802. *
  17803. * @private
  17804. */
  17805. Player.prototype.techGet_ = function techGet_(method) {
  17806. if (!this.tech_ || !this.tech_.isReady_) {
  17807. return;
  17808. }
  17809. if (method in allowedGetters) {
  17810. return get$1(this.middleware_, this.tech_, method);
  17811. } else if (method in allowedMediators) {
  17812. return mediate(this.middleware_, this.tech_, method);
  17813. }
  17814. // Flash likes to die and reload when you hide or reposition it.
  17815. // In these cases the object methods go away and we get errors.
  17816. // When that happens we'll catch the errors and inform tech that it's not ready any more.
  17817. try {
  17818. return this.tech_[method]();
  17819. } catch (e) {
  17820. // When building additional tech libs, an expected method may not be defined yet
  17821. if (this.tech_[method] === undefined) {
  17822. log('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e);
  17823. throw e;
  17824. }
  17825. // When a method isn't available on the object it throws a TypeError
  17826. if (e.name === 'TypeError') {
  17827. log('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e);
  17828. this.tech_.isReady_ = false;
  17829. throw e;
  17830. }
  17831. // If error unknown, just log and throw
  17832. log(e);
  17833. throw e;
  17834. }
  17835. };
  17836. /**
  17837. * Attempt to begin playback at the first opportunity.
  17838. *
  17839. * @return {Promise|undefined}
  17840. * Returns a promise if the browser supports Promises (or one
  17841. * was passed in as an option). This promise will be resolved on
  17842. * the return value of play. If this is undefined it will fulfill the
  17843. * promise chain otherwise the promise chain will be fulfilled when
  17844. * the promise from play is fulfilled.
  17845. */
  17846. Player.prototype.play = function play() {
  17847. var _this7 = this;
  17848. var PromiseClass = this.options_.Promise || window.Promise;
  17849. if (PromiseClass) {
  17850. return new PromiseClass(function (resolve) {
  17851. _this7.play_(resolve);
  17852. });
  17853. }
  17854. return this.play_();
  17855. };
  17856. /**
  17857. * The actual logic for play, takes a callback that will be resolved on the
  17858. * return value of play. This allows us to resolve to the play promise if there
  17859. * is one on modern browsers.
  17860. *
  17861. * @private
  17862. * @param {Function} [callback]
  17863. * The callback that should be called when the techs play is actually called
  17864. */
  17865. Player.prototype.play_ = function play_() {
  17866. var _this8 = this;
  17867. var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : silencePromise;
  17868. // If this is called while we have a play queued up on a loadstart, remove
  17869. // that listener to avoid getting in a potentially bad state.
  17870. if (this.playOnLoadstart_) {
  17871. this.off('loadstart', this.playOnLoadstart_);
  17872. }
  17873. // If the player/tech is not ready, queue up another call to `play()` for
  17874. // when it is. This will loop back into this method for another attempt at
  17875. // playback when the tech is ready.
  17876. if (!this.isReady_) {
  17877. // Bail out if we're already waiting for `ready`!
  17878. if (this.playWaitingForReady_) {
  17879. return;
  17880. }
  17881. this.playWaitingForReady_ = true;
  17882. this.ready(function () {
  17883. _this8.playWaitingForReady_ = false;
  17884. callback(_this8.play());
  17885. });
  17886. // If the player/tech is ready and we have a source, we can attempt playback.
  17887. } else if (!this.changingSrc_ && (this.src() || this.currentSrc())) {
  17888. callback(this.techGet_('play'));
  17889. return;
  17890. // If the tech is ready, but we do not have a source, we'll need to wait
  17891. // for both the `ready` and a `loadstart` when the source is finally
  17892. // resolved by middleware and set on the player.
  17893. //
  17894. // This can happen if `play()` is called while changing sources or before
  17895. // one has been set on the player.
  17896. } else {
  17897. this.playOnLoadstart_ = function () {
  17898. _this8.playOnLoadstart_ = null;
  17899. callback(_this8.play());
  17900. };
  17901. this.one('loadstart', this.playOnLoadstart_);
  17902. }
  17903. };
  17904. /**
  17905. * Pause the video playback
  17906. *
  17907. * @return {Player}
  17908. * A reference to the player object this function was called on
  17909. */
  17910. Player.prototype.pause = function pause() {
  17911. this.techCall_('pause');
  17912. };
  17913. /**
  17914. * Check if the player is paused or has yet to play
  17915. *
  17916. * @return {boolean}
  17917. * - false: if the media is currently playing
  17918. * - true: if media is not currently playing
  17919. */
  17920. Player.prototype.paused = function paused() {
  17921. // The initial state of paused should be true (in Safari it's actually false)
  17922. return this.techGet_('paused') === false ? false : true;
  17923. };
  17924. /**
  17925. * Get a TimeRange object representing the current ranges of time that the user
  17926. * has played.
  17927. *
  17928. * @return {TimeRange}
  17929. * A time range object that represents all the increments of time that have
  17930. * been played.
  17931. */
  17932. Player.prototype.played = function played() {
  17933. return this.techGet_('played') || createTimeRanges(0, 0);
  17934. };
  17935. /**
  17936. * Returns whether or not the user is "scrubbing". Scrubbing is
  17937. * when the user has clicked the progress bar handle and is
  17938. * dragging it along the progress bar.
  17939. *
  17940. * @param {boolean} [isScrubbing]
  17941. * wether the user is or is not scrubbing
  17942. *
  17943. * @return {boolean}
  17944. * The value of scrubbing when getting
  17945. */
  17946. Player.prototype.scrubbing = function scrubbing(isScrubbing) {
  17947. if (typeof isScrubbing === 'undefined') {
  17948. return this.scrubbing_;
  17949. }
  17950. this.scrubbing_ = !!isScrubbing;
  17951. if (isScrubbing) {
  17952. this.addClass('vjs-scrubbing');
  17953. } else {
  17954. this.removeClass('vjs-scrubbing');
  17955. }
  17956. };
  17957. /**
  17958. * Get or set the current time (in seconds)
  17959. *
  17960. * @param {number|string} [seconds]
  17961. * The time to seek to in seconds
  17962. *
  17963. * @return {number}
  17964. * - the current time in seconds when getting
  17965. */
  17966. Player.prototype.currentTime = function currentTime(seconds) {
  17967. if (typeof seconds !== 'undefined') {
  17968. if (seconds < 0) {
  17969. seconds = 0;
  17970. }
  17971. this.techCall_('setCurrentTime', seconds);
  17972. return;
  17973. }
  17974. // cache last currentTime and return. default to 0 seconds
  17975. //
  17976. // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
  17977. // currentTime when scrubbing, but may not provide much performance benefit afterall.
  17978. // Should be tested. Also something has to read the actual current time or the cache will
  17979. // never get updated.
  17980. this.cache_.currentTime = this.techGet_('currentTime') || 0;
  17981. return this.cache_.currentTime;
  17982. };
  17983. /**
  17984. * Normally gets the length in time of the video in seconds;
  17985. * in all but the rarest use cases an argument will NOT be passed to the method
  17986. *
  17987. * > **NOTE**: The video must have started loading before the duration can be
  17988. * known, and in the case of Flash, may not be known until the video starts
  17989. * playing.
  17990. *
  17991. * @fires Player#durationchange
  17992. *
  17993. * @param {number} [seconds]
  17994. * The duration of the video to set in seconds
  17995. *
  17996. * @return {number}
  17997. * - The duration of the video in seconds when getting
  17998. */
  17999. Player.prototype.duration = function duration(seconds) {
  18000. if (seconds === undefined) {
  18001. // return NaN if the duration is not known
  18002. return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
  18003. }
  18004. seconds = parseFloat(seconds);
  18005. // Standardize on Inifity for signaling video is live
  18006. if (seconds < 0) {
  18007. seconds = Infinity;
  18008. }
  18009. if (seconds !== this.cache_.duration) {
  18010. // Cache the last set value for optimized scrubbing (esp. Flash)
  18011. this.cache_.duration = seconds;
  18012. if (seconds === Infinity) {
  18013. this.addClass('vjs-live');
  18014. } else {
  18015. this.removeClass('vjs-live');
  18016. }
  18017. /**
  18018. * @event Player#durationchange
  18019. * @type {EventTarget~Event}
  18020. */
  18021. this.trigger('durationchange');
  18022. }
  18023. };
  18024. /**
  18025. * Calculates how much time is left in the video. Not part
  18026. * of the native video API.
  18027. *
  18028. * @return {number}
  18029. * The time remaining in seconds
  18030. */
  18031. Player.prototype.remainingTime = function remainingTime() {
  18032. return this.duration() - this.currentTime();
  18033. };
  18034. /**
  18035. * A remaining time function that is intented to be used when
  18036. * the time is to be displayed directly to the user.
  18037. *
  18038. * @return {number}
  18039. * The rounded time remaining in seconds
  18040. */
  18041. Player.prototype.remainingTimeDisplay = function remainingTimeDisplay() {
  18042. return Math.floor(this.duration()) - Math.floor(this.currentTime());
  18043. };
  18044. //
  18045. // Kind of like an array of portions of the video that have been downloaded.
  18046. /**
  18047. * Get a TimeRange object with an array of the times of the video
  18048. * that have been downloaded. If you just want the percent of the
  18049. * video that's been downloaded, use bufferedPercent.
  18050. *
  18051. * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
  18052. *
  18053. * @return {TimeRange}
  18054. * A mock TimeRange object (following HTML spec)
  18055. */
  18056. Player.prototype.buffered = function buffered() {
  18057. var buffered = this.techGet_('buffered');
  18058. if (!buffered || !buffered.length) {
  18059. buffered = createTimeRanges(0, 0);
  18060. }
  18061. return buffered;
  18062. };
  18063. /**
  18064. * Get the percent (as a decimal) of the video that's been downloaded.
  18065. * This method is not a part of the native HTML video API.
  18066. *
  18067. * @return {number}
  18068. * A decimal between 0 and 1 representing the percent
  18069. * that is bufferred 0 being 0% and 1 being 100%
  18070. */
  18071. Player.prototype.bufferedPercent = function bufferedPercent$$1() {
  18072. return bufferedPercent(this.buffered(), this.duration());
  18073. };
  18074. /**
  18075. * Get the ending time of the last buffered time range
  18076. * This is used in the progress bar to encapsulate all time ranges.
  18077. *
  18078. * @return {number}
  18079. * The end of the last buffered time range
  18080. */
  18081. Player.prototype.bufferedEnd = function bufferedEnd() {
  18082. var buffered = this.buffered();
  18083. var duration = this.duration();
  18084. var end = buffered.end(buffered.length - 1);
  18085. if (end > duration) {
  18086. end = duration;
  18087. }
  18088. return end;
  18089. };
  18090. /**
  18091. * Get or set the current volume of the media
  18092. *
  18093. * @param {number} [percentAsDecimal]
  18094. * The new volume as a decimal percent:
  18095. * - 0 is muted/0%/off
  18096. * - 1.0 is 100%/full
  18097. * - 0.5 is half volume or 50%
  18098. *
  18099. * @return {number}
  18100. * The current volume as a percent when getting
  18101. */
  18102. Player.prototype.volume = function volume(percentAsDecimal) {
  18103. var vol = void 0;
  18104. if (percentAsDecimal !== undefined) {
  18105. // Force value to between 0 and 1
  18106. vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
  18107. this.cache_.volume = vol;
  18108. this.techCall_('setVolume', vol);
  18109. if (vol > 0) {
  18110. this.lastVolume_(vol);
  18111. }
  18112. return;
  18113. }
  18114. // Default to 1 when returning current volume.
  18115. vol = parseFloat(this.techGet_('volume'));
  18116. return isNaN(vol) ? 1 : vol;
  18117. };
  18118. /**
  18119. * Get the current muted state, or turn mute on or off
  18120. *
  18121. * @param {boolean} [muted]
  18122. * - true to mute
  18123. * - false to unmute
  18124. *
  18125. * @return {boolean}
  18126. * - true if mute is on and getting
  18127. * - false if mute is off and getting
  18128. */
  18129. Player.prototype.muted = function muted(_muted) {
  18130. if (_muted !== undefined) {
  18131. this.techCall_('setMuted', _muted);
  18132. return;
  18133. }
  18134. return this.techGet_('muted') || false;
  18135. };
  18136. /**
  18137. * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
  18138. * indicates the state of muted on intial playback.
  18139. *
  18140. * ```js
  18141. * var myPlayer = videojs('some-player-id');
  18142. *
  18143. * myPlayer.src("http://www.example.com/path/to/video.mp4");
  18144. *
  18145. * // get, should be false
  18146. * console.log(myPlayer.defaultMuted());
  18147. * // set to true
  18148. * myPlayer.defaultMuted(true);
  18149. * // get should be true
  18150. * console.log(myPlayer.defaultMuted());
  18151. * ```
  18152. *
  18153. * @param {boolean} [defaultMuted]
  18154. * - true to mute
  18155. * - false to unmute
  18156. *
  18157. * @return {boolean|Player}
  18158. * - true if defaultMuted is on and getting
  18159. * - false if defaultMuted is off and getting
  18160. * - A reference to the current player when setting
  18161. */
  18162. Player.prototype.defaultMuted = function defaultMuted(_defaultMuted) {
  18163. if (_defaultMuted !== undefined) {
  18164. return this.techCall_('setDefaultMuted', _defaultMuted);
  18165. }
  18166. return this.techGet_('defaultMuted') || false;
  18167. };
  18168. /**
  18169. * Get the last volume, or set it
  18170. *
  18171. * @param {number} [percentAsDecimal]
  18172. * The new last volume as a decimal percent:
  18173. * - 0 is muted/0%/off
  18174. * - 1.0 is 100%/full
  18175. * - 0.5 is half volume or 50%
  18176. *
  18177. * @return {number}
  18178. * the current value of lastVolume as a percent when getting
  18179. *
  18180. * @private
  18181. */
  18182. Player.prototype.lastVolume_ = function lastVolume_(percentAsDecimal) {
  18183. if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
  18184. this.cache_.lastVolume = percentAsDecimal;
  18185. return;
  18186. }
  18187. return this.cache_.lastVolume;
  18188. };
  18189. /**
  18190. * Check if current tech can support native fullscreen
  18191. * (e.g. with built in controls like iOS, so not our flash swf)
  18192. *
  18193. * @return {boolean}
  18194. * if native fullscreen is supported
  18195. */
  18196. Player.prototype.supportsFullScreen = function supportsFullScreen() {
  18197. return this.techGet_('supportsFullScreen') || false;
  18198. };
  18199. /**
  18200. * Check if the player is in fullscreen mode or tell the player that it
  18201. * is or is not in fullscreen mode.
  18202. *
  18203. * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
  18204. * property and instead document.fullscreenElement is used. But isFullscreen is
  18205. * still a valuable property for internal player workings.
  18206. *
  18207. * @param {boolean} [isFS]
  18208. * Set the players current fullscreen state
  18209. *
  18210. * @return {boolean}
  18211. * - true if fullscreen is on and getting
  18212. * - false if fullscreen is off and getting
  18213. */
  18214. Player.prototype.isFullscreen = function isFullscreen(isFS) {
  18215. if (isFS !== undefined) {
  18216. this.isFullscreen_ = !!isFS;
  18217. return;
  18218. }
  18219. return !!this.isFullscreen_;
  18220. };
  18221. /**
  18222. * Increase the size of the video to full screen
  18223. * In some browsers, full screen is not supported natively, so it enters
  18224. * "full window mode", where the video fills the browser window.
  18225. * In browsers and devices that support native full screen, sometimes the
  18226. * browser's default controls will be shown, and not the Video.js custom skin.
  18227. * This includes most mobile devices (iOS, Android) and older versions of
  18228. * Safari.
  18229. *
  18230. * @fires Player#fullscreenchange
  18231. */
  18232. Player.prototype.requestFullscreen = function requestFullscreen() {
  18233. var fsApi = FullscreenApi;
  18234. this.isFullscreen(true);
  18235. if (fsApi.requestFullscreen) {
  18236. // the browser supports going fullscreen at the element level so we can
  18237. // take the controls fullscreen as well as the video
  18238. // Trigger fullscreenchange event after change
  18239. // We have to specifically add this each time, and remove
  18240. // when canceling fullscreen. Otherwise if there's multiple
  18241. // players on a page, they would all be reacting to the same fullscreen
  18242. // events
  18243. on(document, fsApi.fullscreenchange, bind(this, function documentFullscreenChange(e) {
  18244. this.isFullscreen(document[fsApi.fullscreenElement]);
  18245. // If cancelling fullscreen, remove event listener.
  18246. if (this.isFullscreen() === false) {
  18247. off(document, fsApi.fullscreenchange, documentFullscreenChange);
  18248. }
  18249. /**
  18250. * @event Player#fullscreenchange
  18251. * @type {EventTarget~Event}
  18252. */
  18253. this.trigger('fullscreenchange');
  18254. }));
  18255. this.el_[fsApi.requestFullscreen]();
  18256. } else if (this.tech_.supportsFullScreen()) {
  18257. // we can't take the video.js controls fullscreen but we can go fullscreen
  18258. // with native controls
  18259. this.techCall_('enterFullScreen');
  18260. } else {
  18261. // fullscreen isn't supported so we'll just stretch the video element to
  18262. // fill the viewport
  18263. this.enterFullWindow();
  18264. /**
  18265. * @event Player#fullscreenchange
  18266. * @type {EventTarget~Event}
  18267. */
  18268. this.trigger('fullscreenchange');
  18269. }
  18270. };
  18271. /**
  18272. * Return the video to its normal size after having been in full screen mode
  18273. *
  18274. * @fires Player#fullscreenchange
  18275. */
  18276. Player.prototype.exitFullscreen = function exitFullscreen() {
  18277. var fsApi = FullscreenApi;
  18278. this.isFullscreen(false);
  18279. // Check for browser element fullscreen support
  18280. if (fsApi.requestFullscreen) {
  18281. document[fsApi.exitFullscreen]();
  18282. } else if (this.tech_.supportsFullScreen()) {
  18283. this.techCall_('exitFullScreen');
  18284. } else {
  18285. this.exitFullWindow();
  18286. /**
  18287. * @event Player#fullscreenchange
  18288. * @type {EventTarget~Event}
  18289. */
  18290. this.trigger('fullscreenchange');
  18291. }
  18292. };
  18293. /**
  18294. * When fullscreen isn't supported we can stretch the
  18295. * video container to as wide as the browser will let us.
  18296. *
  18297. * @fires Player#enterFullWindow
  18298. */
  18299. Player.prototype.enterFullWindow = function enterFullWindow() {
  18300. this.isFullWindow = true;
  18301. // Storing original doc overflow value to return to when fullscreen is off
  18302. this.docOrigOverflow = document.documentElement.style.overflow;
  18303. // Add listener for esc key to exit fullscreen
  18304. on(document, 'keydown', bind(this, this.fullWindowOnEscKey));
  18305. // Hide any scroll bars
  18306. document.documentElement.style.overflow = 'hidden';
  18307. // Apply fullscreen styles
  18308. addClass(document.body, 'vjs-full-window');
  18309. /**
  18310. * @event Player#enterFullWindow
  18311. * @type {EventTarget~Event}
  18312. */
  18313. this.trigger('enterFullWindow');
  18314. };
  18315. /**
  18316. * Check for call to either exit full window or
  18317. * full screen on ESC key
  18318. *
  18319. * @param {string} event
  18320. * Event to check for key press
  18321. */
  18322. Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
  18323. if (event.keyCode === 27) {
  18324. if (this.isFullscreen() === true) {
  18325. this.exitFullscreen();
  18326. } else {
  18327. this.exitFullWindow();
  18328. }
  18329. }
  18330. };
  18331. /**
  18332. * Exit full window
  18333. *
  18334. * @fires Player#exitFullWindow
  18335. */
  18336. Player.prototype.exitFullWindow = function exitFullWindow() {
  18337. this.isFullWindow = false;
  18338. off(document, 'keydown', this.fullWindowOnEscKey);
  18339. // Unhide scroll bars.
  18340. document.documentElement.style.overflow = this.docOrigOverflow;
  18341. // Remove fullscreen styles
  18342. removeClass(document.body, 'vjs-full-window');
  18343. // Resize the box, controller, and poster to original sizes
  18344. // this.positionAll();
  18345. /**
  18346. * @event Player#exitFullWindow
  18347. * @type {EventTarget~Event}
  18348. */
  18349. this.trigger('exitFullWindow');
  18350. };
  18351. /**
  18352. * Check whether the player can play a given mimetype
  18353. *
  18354. * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
  18355. *
  18356. * @param {string} type
  18357. * The mimetype to check
  18358. *
  18359. * @return {string}
  18360. * 'probably', 'maybe', or '' (empty string)
  18361. */
  18362. Player.prototype.canPlayType = function canPlayType(type) {
  18363. var can = void 0;
  18364. // Loop through each playback technology in the options order
  18365. for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
  18366. var techName = j[i];
  18367. var tech = Tech.getTech(techName);
  18368. // Support old behavior of techs being registered as components.
  18369. // Remove once that deprecated behavior is removed.
  18370. if (!tech) {
  18371. tech = Component.getComponent(techName);
  18372. }
  18373. // Check if the current tech is defined before continuing
  18374. if (!tech) {
  18375. log.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
  18376. continue;
  18377. }
  18378. // Check if the browser supports this technology
  18379. if (tech.isSupported()) {
  18380. can = tech.canPlayType(type);
  18381. if (can) {
  18382. return can;
  18383. }
  18384. }
  18385. }
  18386. return '';
  18387. };
  18388. /**
  18389. * Select source based on tech-order or source-order
  18390. * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
  18391. * defaults to tech-order selection
  18392. *
  18393. * @param {Array} sources
  18394. * The sources for a media asset
  18395. *
  18396. * @return {Object|boolean}
  18397. * Object of source and tech order or false
  18398. */
  18399. Player.prototype.selectSource = function selectSource(sources) {
  18400. var _this9 = this;
  18401. // Get only the techs specified in `techOrder` that exist and are supported by the
  18402. // current platform
  18403. var techs = this.options_.techOrder.map(function (techName) {
  18404. return [techName, Tech.getTech(techName)];
  18405. }).filter(function (_ref) {
  18406. var techName = _ref[0],
  18407. tech = _ref[1];
  18408. // Check if the current tech is defined before continuing
  18409. if (tech) {
  18410. // Check if the browser supports this technology
  18411. return tech.isSupported();
  18412. }
  18413. log.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
  18414. return false;
  18415. });
  18416. // Iterate over each `innerArray` element once per `outerArray` element and execute
  18417. // `tester` with both. If `tester` returns a non-falsy value, exit early and return
  18418. // that value.
  18419. var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
  18420. var found = void 0;
  18421. outerArray.some(function (outerChoice) {
  18422. return innerArray.some(function (innerChoice) {
  18423. found = tester(outerChoice, innerChoice);
  18424. if (found) {
  18425. return true;
  18426. }
  18427. });
  18428. });
  18429. return found;
  18430. };
  18431. var foundSourceAndTech = void 0;
  18432. var flip = function flip(fn) {
  18433. return function (a, b) {
  18434. return fn(b, a);
  18435. };
  18436. };
  18437. var finder = function finder(_ref2, source) {
  18438. var techName = _ref2[0],
  18439. tech = _ref2[1];
  18440. if (tech.canPlaySource(source, _this9.options_[techName.toLowerCase()])) {
  18441. return { source: source, tech: techName };
  18442. }
  18443. };
  18444. // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
  18445. // to select from them based on their priority.
  18446. if (this.options_.sourceOrder) {
  18447. // Source-first ordering
  18448. foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
  18449. } else {
  18450. // Tech-first ordering
  18451. foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
  18452. }
  18453. return foundSourceAndTech || false;
  18454. };
  18455. /**
  18456. * Get or set the video source.
  18457. *
  18458. * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
  18459. * A SourceObject, an array of SourceObjects, or a string referencing
  18460. * a URL to a media source. It is _highly recommended_ that an object
  18461. * or array of objects is used here, so that source selection
  18462. * algorithms can take the `type` into account.
  18463. *
  18464. * If not provided, this method acts as a getter.
  18465. *
  18466. * @return {string|undefined}
  18467. * If the `source` argument is missing, returns the current source
  18468. * URL. Otherwise, returns nothing/undefined.
  18469. */
  18470. Player.prototype.src = function src(source) {
  18471. var _this10 = this;
  18472. // getter usage
  18473. if (typeof source === 'undefined') {
  18474. return this.cache_.src || '';
  18475. }
  18476. // filter out invalid sources and turn our source into
  18477. // an array of source objects
  18478. var sources = filterSource(source);
  18479. // if a source was passed in then it is invalid because
  18480. // it was filtered to a zero length Array. So we have to
  18481. // show an error
  18482. if (!sources.length) {
  18483. this.setTimeout(function () {
  18484. this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
  18485. }, 0);
  18486. return;
  18487. }
  18488. // intial sources
  18489. this.changingSrc_ = true;
  18490. this.cache_.sources = sources;
  18491. this.updateSourceCaches_(sources[0]);
  18492. // middlewareSource is the source after it has been changed by middleware
  18493. setSource(this, sources[0], function (middlewareSource, mws) {
  18494. _this10.middleware_ = mws;
  18495. // since sourceSet is async we have to update the cache again after we select a source since
  18496. // the source that is selected could be out of order from the cache update above this callback.
  18497. _this10.cache_.sources = sources;
  18498. _this10.updateSourceCaches_(middlewareSource);
  18499. var err = _this10.src_(middlewareSource);
  18500. if (err) {
  18501. if (sources.length > 1) {
  18502. return _this10.src(sources.slice(1));
  18503. }
  18504. _this10.changingSrc_ = false;
  18505. // We need to wrap this in a timeout to give folks a chance to add error event handlers
  18506. _this10.setTimeout(function () {
  18507. this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
  18508. }, 0);
  18509. // we could not find an appropriate tech, but let's still notify the delegate that this is it
  18510. // this needs a better comment about why this is needed
  18511. _this10.triggerReady();
  18512. return;
  18513. }
  18514. setTech(mws, _this10.tech_);
  18515. });
  18516. };
  18517. /**
  18518. * Set the source object on the tech, returns a boolean that indicates wether
  18519. * there is a tech that can play the source or not
  18520. *
  18521. * @param {Tech~SourceObject} source
  18522. * The source object to set on the Tech
  18523. *
  18524. * @return {Boolean}
  18525. * - True if there is no Tech to playback this source
  18526. * - False otherwise
  18527. *
  18528. * @private
  18529. */
  18530. Player.prototype.src_ = function src_(source) {
  18531. var _this11 = this;
  18532. var sourceTech = this.selectSource([source]);
  18533. if (!sourceTech) {
  18534. return true;
  18535. }
  18536. if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
  18537. this.changingSrc_ = true;
  18538. // load this technology with the chosen source
  18539. this.loadTech_(sourceTech.tech, sourceTech.source);
  18540. this.tech_.ready(function () {
  18541. _this11.changingSrc_ = false;
  18542. });
  18543. return false;
  18544. }
  18545. // wait until the tech is ready to set the source
  18546. // and set it synchronously if possible (#2326)
  18547. this.ready(function () {
  18548. // The setSource tech method was added with source handlers
  18549. // so older techs won't support it
  18550. // We need to check the direct prototype for the case where subclasses
  18551. // of the tech do not support source handlers
  18552. if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
  18553. this.techCall_('setSource', source);
  18554. } else {
  18555. this.techCall_('src', source.src);
  18556. }
  18557. this.changingSrc_ = false;
  18558. }, true);
  18559. return false;
  18560. };
  18561. /**
  18562. * Begin loading the src data.
  18563. */
  18564. Player.prototype.load = function load() {
  18565. this.techCall_('load');
  18566. };
  18567. /**
  18568. * Reset the player. Loads the first tech in the techOrder,
  18569. * removes all the text tracks in the existing `tech`,
  18570. * and calls `reset` on the `tech`.
  18571. */
  18572. Player.prototype.reset = function reset() {
  18573. if (this.tech_) {
  18574. this.tech_.clearTracks('text');
  18575. }
  18576. this.loadTech_(this.options_.techOrder[0], null);
  18577. this.techCall_('reset');
  18578. };
  18579. /**
  18580. * Returns all of the current source objects.
  18581. *
  18582. * @return {Tech~SourceObject[]}
  18583. * The current source objects
  18584. */
  18585. Player.prototype.currentSources = function currentSources() {
  18586. var source = this.currentSource();
  18587. var sources = [];
  18588. // assume `{}` or `{ src }`
  18589. if (Object.keys(source).length !== 0) {
  18590. sources.push(source);
  18591. }
  18592. return this.cache_.sources || sources;
  18593. };
  18594. /**
  18595. * Returns the current source object.
  18596. *
  18597. * @return {Tech~SourceObject}
  18598. * The current source object
  18599. */
  18600. Player.prototype.currentSource = function currentSource() {
  18601. return this.cache_.source || {};
  18602. };
  18603. /**
  18604. * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
  18605. * Can be used in conjuction with `currentType` to assist in rebuilding the current source object.
  18606. *
  18607. * @return {string}
  18608. * The current source
  18609. */
  18610. Player.prototype.currentSrc = function currentSrc() {
  18611. return this.currentSource() && this.currentSource().src || '';
  18612. };
  18613. /**
  18614. * Get the current source type e.g. video/mp4
  18615. * This can allow you rebuild the current source object so that you could load the same
  18616. * source and tech later
  18617. *
  18618. * @return {string}
  18619. * The source MIME type
  18620. */
  18621. Player.prototype.currentType = function currentType() {
  18622. return this.currentSource() && this.currentSource().type || '';
  18623. };
  18624. /**
  18625. * Get or set the preload attribute
  18626. *
  18627. * @param {boolean} [value]
  18628. * - true means that we should preload
  18629. * - false maens that we should not preload
  18630. *
  18631. * @return {string}
  18632. * The preload attribute value when getting
  18633. */
  18634. Player.prototype.preload = function preload(value) {
  18635. if (value !== undefined) {
  18636. this.techCall_('setPreload', value);
  18637. this.options_.preload = value;
  18638. return;
  18639. }
  18640. return this.techGet_('preload');
  18641. };
  18642. /**
  18643. * Get or set the autoplay option. When this is a boolean it will
  18644. * modify the attribute on the tech. When this is a string the attribute on
  18645. * the tech will be removed and `Player` will handle autoplay on loadstarts.
  18646. *
  18647. * @param {boolean|string} [value]
  18648. * - true: autoplay using the browser behavior
  18649. * - false: do not autoplay
  18650. * - 'play': call play() on every loadstart
  18651. * - 'muted': call muted() then play() on every loadstart
  18652. * - 'any': call play() on every loadstart. if that fails call muted() then play().
  18653. * - *: values other than those listed here will be set `autoplay` to true
  18654. *
  18655. * @return {boolean|string}
  18656. * The current value of autoplay when getting
  18657. */
  18658. Player.prototype.autoplay = function autoplay(value) {
  18659. // getter usage
  18660. if (value === undefined) {
  18661. return this.options_.autoplay || false;
  18662. }
  18663. var techAutoplay = void 0;
  18664. // if the value is a valid string set it to that
  18665. if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
  18666. this.options_.autoplay = value;
  18667. this.manualAutoplay_(value);
  18668. techAutoplay = false;
  18669. // any falsy value sets autoplay to false in the browser,
  18670. // lets do the same
  18671. } else if (!value) {
  18672. this.options_.autoplay = false;
  18673. // any other value (ie truthy) sets autoplay to true
  18674. } else {
  18675. this.options_.autoplay = true;
  18676. }
  18677. techAutoplay = techAutoplay || this.options_.autoplay;
  18678. // if we don't have a tech then we do not queue up
  18679. // a setAutoplay call on tech ready. We do this because the
  18680. // autoplay option will be passed in the constructor and we
  18681. // do not need to set it twice
  18682. if (this.tech_) {
  18683. this.techCall_('setAutoplay', techAutoplay);
  18684. }
  18685. };
  18686. /**
  18687. * Set or unset the playsinline attribute.
  18688. * Playsinline tells the browser that non-fullscreen playback is preferred.
  18689. *
  18690. * @param {boolean} [value]
  18691. * - true means that we should try to play inline by default
  18692. * - false means that we should use the browser's default playback mode,
  18693. * which in most cases is inline. iOS Safari is a notable exception
  18694. * and plays fullscreen by default.
  18695. *
  18696. * @return {string|Player}
  18697. * - the current value of playsinline
  18698. * - the player when setting
  18699. *
  18700. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  18701. */
  18702. Player.prototype.playsinline = function playsinline(value) {
  18703. if (value !== undefined) {
  18704. this.techCall_('setPlaysinline', value);
  18705. this.options_.playsinline = value;
  18706. return this;
  18707. }
  18708. return this.techGet_('playsinline');
  18709. };
  18710. /**
  18711. * Get or set the loop attribute on the video element.
  18712. *
  18713. * @param {boolean} [value]
  18714. * - true means that we should loop the video
  18715. * - false means that we should not loop the video
  18716. *
  18717. * @return {string}
  18718. * The current value of loop when getting
  18719. */
  18720. Player.prototype.loop = function loop(value) {
  18721. if (value !== undefined) {
  18722. this.techCall_('setLoop', value);
  18723. this.options_.loop = value;
  18724. return;
  18725. }
  18726. return this.techGet_('loop');
  18727. };
  18728. /**
  18729. * Get or set the poster image source url
  18730. *
  18731. * @fires Player#posterchange
  18732. *
  18733. * @param {string} [src]
  18734. * Poster image source URL
  18735. *
  18736. * @return {string}
  18737. * The current value of poster when getting
  18738. */
  18739. Player.prototype.poster = function poster(src) {
  18740. if (src === undefined) {
  18741. return this.poster_;
  18742. }
  18743. // The correct way to remove a poster is to set as an empty string
  18744. // other falsey values will throw errors
  18745. if (!src) {
  18746. src = '';
  18747. }
  18748. if (src === this.poster_) {
  18749. return;
  18750. }
  18751. // update the internal poster variable
  18752. this.poster_ = src;
  18753. // update the tech's poster
  18754. this.techCall_('setPoster', src);
  18755. this.isPosterFromTech_ = false;
  18756. // alert components that the poster has been set
  18757. /**
  18758. * This event fires when the poster image is changed on the player.
  18759. *
  18760. * @event Player#posterchange
  18761. * @type {EventTarget~Event}
  18762. */
  18763. this.trigger('posterchange');
  18764. };
  18765. /**
  18766. * Some techs (e.g. YouTube) can provide a poster source in an
  18767. * asynchronous way. We want the poster component to use this
  18768. * poster source so that it covers up the tech's controls.
  18769. * (YouTube's play button). However we only want to use this
  18770. * source if the player user hasn't set a poster through
  18771. * the normal APIs.
  18772. *
  18773. * @fires Player#posterchange
  18774. * @listens Tech#posterchange
  18775. * @private
  18776. */
  18777. Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() {
  18778. if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
  18779. var newPoster = this.tech_.poster() || '';
  18780. if (newPoster !== this.poster_) {
  18781. this.poster_ = newPoster;
  18782. this.isPosterFromTech_ = true;
  18783. // Let components know the poster has changed
  18784. this.trigger('posterchange');
  18785. }
  18786. }
  18787. };
  18788. /**
  18789. * Get or set whether or not the controls are showing.
  18790. *
  18791. * @fires Player#controlsenabled
  18792. *
  18793. * @param {boolean} [bool]
  18794. * - true to turn controls on
  18795. * - false to turn controls off
  18796. *
  18797. * @return {boolean}
  18798. * The current value of controls when getting
  18799. */
  18800. Player.prototype.controls = function controls(bool) {
  18801. if (bool === undefined) {
  18802. return !!this.controls_;
  18803. }
  18804. bool = !!bool;
  18805. // Don't trigger a change event unless it actually changed
  18806. if (this.controls_ === bool) {
  18807. return;
  18808. }
  18809. this.controls_ = bool;
  18810. if (this.usingNativeControls()) {
  18811. this.techCall_('setControls', bool);
  18812. }
  18813. if (this.controls_) {
  18814. this.removeClass('vjs-controls-disabled');
  18815. this.addClass('vjs-controls-enabled');
  18816. /**
  18817. * @event Player#controlsenabled
  18818. * @type {EventTarget~Event}
  18819. */
  18820. this.trigger('controlsenabled');
  18821. if (!this.usingNativeControls()) {
  18822. this.addTechControlsListeners_();
  18823. }
  18824. } else {
  18825. this.removeClass('vjs-controls-enabled');
  18826. this.addClass('vjs-controls-disabled');
  18827. /**
  18828. * @event Player#controlsdisabled
  18829. * @type {EventTarget~Event}
  18830. */
  18831. this.trigger('controlsdisabled');
  18832. if (!this.usingNativeControls()) {
  18833. this.removeTechControlsListeners_();
  18834. }
  18835. }
  18836. };
  18837. /**
  18838. * Toggle native controls on/off. Native controls are the controls built into
  18839. * devices (e.g. default iPhone controls), Flash, or other techs
  18840. * (e.g. Vimeo Controls)
  18841. * **This should only be set by the current tech, because only the tech knows
  18842. * if it can support native controls**
  18843. *
  18844. * @fires Player#usingnativecontrols
  18845. * @fires Player#usingcustomcontrols
  18846. *
  18847. * @param {boolean} [bool]
  18848. * - true to turn native controls on
  18849. * - false to turn native controls off
  18850. *
  18851. * @return {boolean}
  18852. * The current value of native controls when getting
  18853. */
  18854. Player.prototype.usingNativeControls = function usingNativeControls(bool) {
  18855. if (bool === undefined) {
  18856. return !!this.usingNativeControls_;
  18857. }
  18858. bool = !!bool;
  18859. // Don't trigger a change event unless it actually changed
  18860. if (this.usingNativeControls_ === bool) {
  18861. return;
  18862. }
  18863. this.usingNativeControls_ = bool;
  18864. if (this.usingNativeControls_) {
  18865. this.addClass('vjs-using-native-controls');
  18866. /**
  18867. * player is using the native device controls
  18868. *
  18869. * @event Player#usingnativecontrols
  18870. * @type {EventTarget~Event}
  18871. */
  18872. this.trigger('usingnativecontrols');
  18873. } else {
  18874. this.removeClass('vjs-using-native-controls');
  18875. /**
  18876. * player is using the custom HTML controls
  18877. *
  18878. * @event Player#usingcustomcontrols
  18879. * @type {EventTarget~Event}
  18880. */
  18881. this.trigger('usingcustomcontrols');
  18882. }
  18883. };
  18884. /**
  18885. * Set or get the current MediaError
  18886. *
  18887. * @fires Player#error
  18888. *
  18889. * @param {MediaError|string|number} [err]
  18890. * A MediaError or a string/number to be turned
  18891. * into a MediaError
  18892. *
  18893. * @return {MediaError|null}
  18894. * The current MediaError when getting (or null)
  18895. */
  18896. Player.prototype.error = function error(err) {
  18897. if (err === undefined) {
  18898. return this.error_ || null;
  18899. }
  18900. // restoring to default
  18901. if (err === null) {
  18902. this.error_ = err;
  18903. this.removeClass('vjs-error');
  18904. if (this.errorDisplay) {
  18905. this.errorDisplay.close();
  18906. }
  18907. return;
  18908. }
  18909. this.error_ = new MediaError(err);
  18910. // add the vjs-error classname to the player
  18911. this.addClass('vjs-error');
  18912. // log the name of the error type and any message
  18913. // ie8 just logs "[object object]" if you just log the error object
  18914. log.error('(CODE:' + this.error_.code + ' ' + MediaError.errorTypes[this.error_.code] + ')', this.error_.message, this.error_);
  18915. /**
  18916. * @event Player#error
  18917. * @type {EventTarget~Event}
  18918. */
  18919. this.trigger('error');
  18920. return;
  18921. };
  18922. /**
  18923. * Report user activity
  18924. *
  18925. * @param {Object} event
  18926. * Event object
  18927. */
  18928. Player.prototype.reportUserActivity = function reportUserActivity(event) {
  18929. this.userActivity_ = true;
  18930. };
  18931. /**
  18932. * Get/set if user is active
  18933. *
  18934. * @fires Player#useractive
  18935. * @fires Player#userinactive
  18936. *
  18937. * @param {boolean} [bool]
  18938. * - true if the user is active
  18939. * - false if the user is inactive
  18940. *
  18941. * @return {boolean}
  18942. * The current value of userActive when getting
  18943. */
  18944. Player.prototype.userActive = function userActive(bool) {
  18945. if (bool === undefined) {
  18946. return this.userActive_;
  18947. }
  18948. bool = !!bool;
  18949. if (bool === this.userActive_) {
  18950. return;
  18951. }
  18952. this.userActive_ = bool;
  18953. if (this.userActive_) {
  18954. this.userActivity_ = true;
  18955. this.removeClass('vjs-user-inactive');
  18956. this.addClass('vjs-user-active');
  18957. /**
  18958. * @event Player#useractive
  18959. * @type {EventTarget~Event}
  18960. */
  18961. this.trigger('useractive');
  18962. return;
  18963. }
  18964. // Chrome/Safari/IE have bugs where when you change the cursor it can
  18965. // trigger a mousemove event. This causes an issue when you're hiding
  18966. // the cursor when the user is inactive, and a mousemove signals user
  18967. // activity. Making it impossible to go into inactive mode. Specifically
  18968. // this happens in fullscreen when we really need to hide the cursor.
  18969. //
  18970. // When this gets resolved in ALL browsers it can be removed
  18971. // https://code.google.com/p/chromium/issues/detail?id=103041
  18972. if (this.tech_) {
  18973. this.tech_.one('mousemove', function (e) {
  18974. e.stopPropagation();
  18975. e.preventDefault();
  18976. });
  18977. }
  18978. this.userActivity_ = false;
  18979. this.removeClass('vjs-user-active');
  18980. this.addClass('vjs-user-inactive');
  18981. /**
  18982. * @event Player#userinactive
  18983. * @type {EventTarget~Event}
  18984. */
  18985. this.trigger('userinactive');
  18986. };
  18987. /**
  18988. * Listen for user activity based on timeout value
  18989. *
  18990. * @private
  18991. */
  18992. Player.prototype.listenForUserActivity_ = function listenForUserActivity_() {
  18993. var mouseInProgress = void 0;
  18994. var lastMoveX = void 0;
  18995. var lastMoveY = void 0;
  18996. var handleActivity = bind(this, this.reportUserActivity);
  18997. var handleMouseMove = function handleMouseMove(e) {
  18998. // #1068 - Prevent mousemove spamming
  18999. // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
  19000. if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
  19001. lastMoveX = e.screenX;
  19002. lastMoveY = e.screenY;
  19003. handleActivity();
  19004. }
  19005. };
  19006. var handleMouseDown = function handleMouseDown() {
  19007. handleActivity();
  19008. // For as long as the they are touching the device or have their mouse down,
  19009. // we consider them active even if they're not moving their finger or mouse.
  19010. // So we want to continue to update that they are active
  19011. this.clearInterval(mouseInProgress);
  19012. // Setting userActivity=true now and setting the interval to the same time
  19013. // as the activityCheck interval (250) should ensure we never miss the
  19014. // next activityCheck
  19015. mouseInProgress = this.setInterval(handleActivity, 250);
  19016. };
  19017. var handleMouseUp = function handleMouseUp(event) {
  19018. handleActivity();
  19019. // Stop the interval that maintains activity if the mouse/touch is down
  19020. this.clearInterval(mouseInProgress);
  19021. };
  19022. // Any mouse movement will be considered user activity
  19023. this.on('mousedown', handleMouseDown);
  19024. this.on('mousemove', handleMouseMove);
  19025. this.on('mouseup', handleMouseUp);
  19026. // Listen for keyboard navigation
  19027. // Shouldn't need to use inProgress interval because of key repeat
  19028. this.on('keydown', handleActivity);
  19029. this.on('keyup', handleActivity);
  19030. // Run an interval every 250 milliseconds instead of stuffing everything into
  19031. // the mousemove/touchmove function itself, to prevent performance degradation.
  19032. // `this.reportUserActivity` simply sets this.userActivity_ to true, which
  19033. // then gets picked up by this loop
  19034. // http://ejohn.org/blog/learning-from-twitter/
  19035. var inactivityTimeout = void 0;
  19036. this.setInterval(function () {
  19037. // Check to see if mouse/touch activity has happened
  19038. if (!this.userActivity_) {
  19039. return;
  19040. }
  19041. // Reset the activity tracker
  19042. this.userActivity_ = false;
  19043. // If the user state was inactive, set the state to active
  19044. this.userActive(true);
  19045. // Clear any existing inactivity timeout to start the timer over
  19046. this.clearTimeout(inactivityTimeout);
  19047. var timeout = this.options_.inactivityTimeout;
  19048. if (timeout <= 0) {
  19049. return;
  19050. }
  19051. // In <timeout> milliseconds, if no more activity has occurred the
  19052. // user will be considered inactive
  19053. inactivityTimeout = this.setTimeout(function () {
  19054. // Protect against the case where the inactivityTimeout can trigger just
  19055. // before the next user activity is picked up by the activity check loop
  19056. // causing a flicker
  19057. if (!this.userActivity_) {
  19058. this.userActive(false);
  19059. }
  19060. }, timeout);
  19061. }, 250);
  19062. };
  19063. /**
  19064. * Gets or sets the current playback rate. A playback rate of
  19065. * 1.0 represents normal speed and 0.5 would indicate half-speed
  19066. * playback, for instance.
  19067. *
  19068. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
  19069. *
  19070. * @param {number} [rate]
  19071. * New playback rate to set.
  19072. *
  19073. * @return {number}
  19074. * The current playback rate when getting or 1.0
  19075. */
  19076. Player.prototype.playbackRate = function playbackRate(rate) {
  19077. if (rate !== undefined) {
  19078. // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
  19079. // that is registered above
  19080. this.techCall_('setPlaybackRate', rate);
  19081. return;
  19082. }
  19083. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  19084. return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
  19085. }
  19086. return 1.0;
  19087. };
  19088. /**
  19089. * Gets or sets the current default playback rate. A default playback rate of
  19090. * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
  19091. * defaultPlaybackRate will only represent what the intial playbackRate of a video was, not
  19092. * not the current playbackRate.
  19093. *
  19094. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
  19095. *
  19096. * @param {number} [rate]
  19097. * New default playback rate to set.
  19098. *
  19099. * @return {number|Player}
  19100. * - The default playback rate when getting or 1.0
  19101. * - the player when setting
  19102. */
  19103. Player.prototype.defaultPlaybackRate = function defaultPlaybackRate(rate) {
  19104. if (rate !== undefined) {
  19105. return this.techCall_('setDefaultPlaybackRate', rate);
  19106. }
  19107. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  19108. return this.techGet_('defaultPlaybackRate');
  19109. }
  19110. return 1.0;
  19111. };
  19112. /**
  19113. * Gets or sets the audio flag
  19114. *
  19115. * @param {boolean} bool
  19116. * - true signals that this is an audio player
  19117. * - false signals that this is not an audio player
  19118. *
  19119. * @return {boolean}
  19120. * The current value of isAudio when getting
  19121. */
  19122. Player.prototype.isAudio = function isAudio(bool) {
  19123. if (bool !== undefined) {
  19124. this.isAudio_ = !!bool;
  19125. return;
  19126. }
  19127. return !!this.isAudio_;
  19128. };
  19129. /**
  19130. * A helper method for adding a {@link TextTrack} to our
  19131. * {@link TextTrackList}.
  19132. *
  19133. * In addition to the W3C settings we allow adding additional info through options.
  19134. *
  19135. * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
  19136. *
  19137. * @param {string} [kind]
  19138. * the kind of TextTrack you are adding
  19139. *
  19140. * @param {string} [label]
  19141. * the label to give the TextTrack label
  19142. *
  19143. * @param {string} [language]
  19144. * the language to set on the TextTrack
  19145. *
  19146. * @return {TextTrack|undefined}
  19147. * the TextTrack that was added or undefined
  19148. * if there is no tech
  19149. */
  19150. Player.prototype.addTextTrack = function addTextTrack(kind, label, language) {
  19151. if (this.tech_) {
  19152. return this.tech_.addTextTrack(kind, label, language);
  19153. }
  19154. };
  19155. /**
  19156. * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
  19157. * automatically removed from the video element whenever the source changes, unless
  19158. * manualCleanup is set to false.
  19159. *
  19160. * @param {Object} options
  19161. * Options to pass to {@link HTMLTrackElement} during creation. See
  19162. * {@link HTMLTrackElement} for object properties that you should use.
  19163. *
  19164. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  19165. *
  19166. * @return {HtmlTrackElement}
  19167. * the HTMLTrackElement that was created and added
  19168. * to the HtmlTrackElementList and the remote
  19169. * TextTrackList
  19170. *
  19171. * @deprecated The default value of the "manualCleanup" parameter will default
  19172. * to "false" in upcoming versions of Video.js
  19173. */
  19174. Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  19175. if (this.tech_) {
  19176. return this.tech_.addRemoteTextTrack(options, manualCleanup);
  19177. }
  19178. };
  19179. /**
  19180. * Remove a remote {@link TextTrack} from the respective
  19181. * {@link TextTrackList} and {@link HtmlTrackElementList}.
  19182. *
  19183. * @param {Object} track
  19184. * Remote {@link TextTrack} to remove
  19185. *
  19186. * @return {undefined}
  19187. * does not return anything
  19188. */
  19189. Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() {
  19190. var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
  19191. _ref3$track = _ref3.track,
  19192. track = _ref3$track === undefined ? arguments[0] : _ref3$track;
  19193. // destructure the input into an object with a track argument, defaulting to arguments[0]
  19194. // default the whole argument to an empty object if nothing was passed in
  19195. if (this.tech_) {
  19196. return this.tech_.removeRemoteTextTrack(track);
  19197. }
  19198. };
  19199. /**
  19200. * Gets available media playback quality metrics as specified by the W3C's Media
  19201. * Playback Quality API.
  19202. *
  19203. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  19204. *
  19205. * @return {Object|undefined}
  19206. * An object with supported media playback quality metrics or undefined if there
  19207. * is no tech or the tech does not support it.
  19208. */
  19209. Player.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  19210. return this.techGet_('getVideoPlaybackQuality');
  19211. };
  19212. /**
  19213. * Get video width
  19214. *
  19215. * @return {number}
  19216. * current video width
  19217. */
  19218. Player.prototype.videoWidth = function videoWidth() {
  19219. return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
  19220. };
  19221. /**
  19222. * Get video height
  19223. *
  19224. * @return {number}
  19225. * current video height
  19226. */
  19227. Player.prototype.videoHeight = function videoHeight() {
  19228. return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
  19229. };
  19230. /**
  19231. * The player's language code
  19232. * NOTE: The language should be set in the player options if you want the
  19233. * the controls to be built with a specific language. Changing the lanugage
  19234. * later will not update controls text.
  19235. *
  19236. * @param {string} [code]
  19237. * the language code to set the player to
  19238. *
  19239. * @return {string}
  19240. * The current language code when getting
  19241. */
  19242. Player.prototype.language = function language(code) {
  19243. if (code === undefined) {
  19244. return this.language_;
  19245. }
  19246. this.language_ = String(code).toLowerCase();
  19247. };
  19248. /**
  19249. * Get the player's language dictionary
  19250. * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
  19251. * Languages specified directly in the player options have precedence
  19252. *
  19253. * @return {Array}
  19254. * An array of of supported languages
  19255. */
  19256. Player.prototype.languages = function languages() {
  19257. return mergeOptions(Player.prototype.options_.languages, this.languages_);
  19258. };
  19259. /**
  19260. * returns a JavaScript object reperesenting the current track
  19261. * information. **DOES not return it as JSON**
  19262. *
  19263. * @return {Object}
  19264. * Object representing the current of track info
  19265. */
  19266. Player.prototype.toJSON = function toJSON() {
  19267. var options = mergeOptions(this.options_);
  19268. var tracks = options.tracks;
  19269. options.tracks = [];
  19270. for (var i = 0; i < tracks.length; i++) {
  19271. var track = tracks[i];
  19272. // deep merge tracks and null out player so no circular references
  19273. track = mergeOptions(track);
  19274. track.player = undefined;
  19275. options.tracks[i] = track;
  19276. }
  19277. return options;
  19278. };
  19279. /**
  19280. * Creates a simple modal dialog (an instance of the {@link ModalDialog}
  19281. * component) that immediately overlays the player with arbitrary
  19282. * content and removes itself when closed.
  19283. *
  19284. * @param {string|Function|Element|Array|null} content
  19285. * Same as {@link ModalDialog#content}'s param of the same name.
  19286. * The most straight-forward usage is to provide a string or DOM
  19287. * element.
  19288. *
  19289. * @param {Object} [options]
  19290. * Extra options which will be passed on to the {@link ModalDialog}.
  19291. *
  19292. * @return {ModalDialog}
  19293. * the {@link ModalDialog} that was created
  19294. */
  19295. Player.prototype.createModal = function createModal(content, options) {
  19296. var _this12 = this;
  19297. options = options || {};
  19298. options.content = content || '';
  19299. var modal = new ModalDialog(this, options);
  19300. this.addChild(modal);
  19301. modal.on('dispose', function () {
  19302. _this12.removeChild(modal);
  19303. });
  19304. modal.open();
  19305. return modal;
  19306. };
  19307. /**
  19308. * Change breakpoint classes when the player resizes.
  19309. *
  19310. * @private
  19311. */
  19312. Player.prototype.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
  19313. if (!this.responsive()) {
  19314. return;
  19315. }
  19316. var currentBreakpoint = this.currentBreakpoint();
  19317. var currentWidth = this.currentWidth();
  19318. for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
  19319. var candidateBreakpoint = BREAKPOINT_ORDER[i];
  19320. var maxWidth = this.breakpoints_[candidateBreakpoint];
  19321. if (currentWidth <= maxWidth) {
  19322. // The current breakpoint did not change, nothing to do.
  19323. if (currentBreakpoint === candidateBreakpoint) {
  19324. return;
  19325. }
  19326. // Only remove a class if there is a current breakpoint.
  19327. if (currentBreakpoint) {
  19328. this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
  19329. }
  19330. this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
  19331. this.breakpoint_ = candidateBreakpoint;
  19332. break;
  19333. }
  19334. }
  19335. };
  19336. /**
  19337. * Removes the current breakpoint.
  19338. *
  19339. * @private
  19340. */
  19341. Player.prototype.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
  19342. var className = this.currentBreakpointClass();
  19343. this.breakpoint_ = '';
  19344. if (className) {
  19345. this.removeClass(className);
  19346. }
  19347. };
  19348. /**
  19349. * Get or set breakpoints on the player.
  19350. *
  19351. * Calling this method with an object or `true` will remove any previous
  19352. * custom breakpoints and start from the defaults again.
  19353. *
  19354. * @param {Object|boolean} [breakpoints]
  19355. * If an object is given, it can be used to provide custom
  19356. * breakpoints. If `true` is given, will set default breakpoints.
  19357. * If this argument is not given, will simply return the current
  19358. * breakpoints.
  19359. *
  19360. * @param {number} [breakpoints.tiny]
  19361. * The maximum width for the "vjs-layout-tiny" class.
  19362. *
  19363. * @param {number} [breakpoints.xsmall]
  19364. * The maximum width for the "vjs-layout-x-small" class.
  19365. *
  19366. * @param {number} [breakpoints.small]
  19367. * The maximum width for the "vjs-layout-small" class.
  19368. *
  19369. * @param {number} [breakpoints.medium]
  19370. * The maximum width for the "vjs-layout-medium" class.
  19371. *
  19372. * @param {number} [breakpoints.large]
  19373. * The maximum width for the "vjs-layout-large" class.
  19374. *
  19375. * @param {number} [breakpoints.xlarge]
  19376. * The maximum width for the "vjs-layout-x-large" class.
  19377. *
  19378. * @param {number} [breakpoints.huge]
  19379. * The maximum width for the "vjs-layout-huge" class.
  19380. *
  19381. * @return {Object}
  19382. * An object mapping breakpoint names to maximum width values.
  19383. */
  19384. Player.prototype.breakpoints = function breakpoints(_breakpoints) {
  19385. // Used as a getter.
  19386. if (_breakpoints === undefined) {
  19387. return assign(this.breakpoints_);
  19388. }
  19389. this.breakpoint_ = '';
  19390. this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints);
  19391. // When breakpoint definitions change, we need to update the currently
  19392. // selected breakpoint.
  19393. this.updateCurrentBreakpoint_();
  19394. // Clone the breakpoints before returning.
  19395. return assign(this.breakpoints_);
  19396. };
  19397. /**
  19398. * Get or set a flag indicating whether or not this player should adjust
  19399. * its UI based on its dimensions.
  19400. *
  19401. * @param {boolean} value
  19402. * Should be `true` if the player should adjust its UI based on its
  19403. * dimensions; otherwise, should be `false`.
  19404. *
  19405. * @return {boolean}
  19406. * Will be `true` if this player should adjust its UI based on its
  19407. * dimensions; otherwise, will be `false`.
  19408. */
  19409. Player.prototype.responsive = function responsive(value) {
  19410. // Used as a getter.
  19411. if (value === undefined) {
  19412. return this.responsive_;
  19413. }
  19414. value = Boolean(value);
  19415. var current = this.responsive_;
  19416. // Nothing changed.
  19417. if (value === current) {
  19418. return;
  19419. }
  19420. // The value actually changed, set it.
  19421. this.responsive_ = value;
  19422. // Start listening for breakpoints and set the initial breakpoint if the
  19423. // player is now responsive.
  19424. if (value) {
  19425. this.on('playerresize', this.updateCurrentBreakpoint_);
  19426. this.updateCurrentBreakpoint_();
  19427. // Stop listening for breakpoints if the player is no longer responsive.
  19428. } else {
  19429. this.off('playerresize', this.updateCurrentBreakpoint_);
  19430. this.removeCurrentBreakpoint_();
  19431. }
  19432. return value;
  19433. };
  19434. /**
  19435. * Get current breakpoint name, if any.
  19436. *
  19437. * @return {string}
  19438. * If there is currently a breakpoint set, returns a the key from the
  19439. * breakpoints object matching it. Otherwise, returns an empty string.
  19440. */
  19441. Player.prototype.currentBreakpoint = function currentBreakpoint() {
  19442. return this.breakpoint_;
  19443. };
  19444. /**
  19445. * Get the current breakpoint class name.
  19446. *
  19447. * @return {string}
  19448. * The matching class name (e.g. `"vjs-layout-tiny"` or
  19449. * `"vjs-layout-large"`) for the current breakpoint. Empty string if
  19450. * there is no current breakpoint.
  19451. */
  19452. Player.prototype.currentBreakpointClass = function currentBreakpointClass() {
  19453. return BREAKPOINT_CLASSES[this.breakpoint_] || '';
  19454. };
  19455. /**
  19456. * Gets tag settings
  19457. *
  19458. * @param {Element} tag
  19459. * The player tag
  19460. *
  19461. * @return {Object}
  19462. * An object containing all of the settings
  19463. * for a player tag
  19464. */
  19465. Player.getTagSettings = function getTagSettings(tag) {
  19466. var baseOptions = {
  19467. sources: [],
  19468. tracks: []
  19469. };
  19470. var tagOptions = getAttributes(tag);
  19471. var dataSetup = tagOptions['data-setup'];
  19472. if (hasClass(tag, 'vjs-fill')) {
  19473. tagOptions.fill = true;
  19474. }
  19475. if (hasClass(tag, 'vjs-fluid')) {
  19476. tagOptions.fluid = true;
  19477. }
  19478. // Check if data-setup attr exists.
  19479. if (dataSetup !== null) {
  19480. // Parse options JSON
  19481. // If empty string, make it a parsable json object.
  19482. var _safeParseTuple = safeParseTuple(dataSetup || '{}'),
  19483. err = _safeParseTuple[0],
  19484. data = _safeParseTuple[1];
  19485. if (err) {
  19486. log.error(err);
  19487. }
  19488. assign(tagOptions, data);
  19489. }
  19490. assign(baseOptions, tagOptions);
  19491. // Get tag children settings
  19492. if (tag.hasChildNodes()) {
  19493. var children = tag.childNodes;
  19494. for (var i = 0, j = children.length; i < j; i++) {
  19495. var child = children[i];
  19496. // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
  19497. var childName = child.nodeName.toLowerCase();
  19498. if (childName === 'source') {
  19499. baseOptions.sources.push(getAttributes(child));
  19500. } else if (childName === 'track') {
  19501. baseOptions.tracks.push(getAttributes(child));
  19502. }
  19503. }
  19504. }
  19505. return baseOptions;
  19506. };
  19507. /**
  19508. * Determine wether or not flexbox is supported
  19509. *
  19510. * @return {boolean}
  19511. * - true if flexbox is supported
  19512. * - false if flexbox is not supported
  19513. */
  19514. Player.prototype.flexNotSupported_ = function flexNotSupported_() {
  19515. var elem = document.createElement('i');
  19516. // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
  19517. // common flex features that we can rely on when checking for flex support.
  19518. return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style ||
  19519. // IE10-specific (2012 flex spec)
  19520. 'msFlexOrder' in elem.style);
  19521. };
  19522. return Player;
  19523. }(Component);
  19524. /**
  19525. * Get the {@link VideoTrackList}
  19526. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
  19527. *
  19528. * @return {VideoTrackList}
  19529. * the current video track list
  19530. *
  19531. * @method Player.prototype.videoTracks
  19532. */
  19533. /**
  19534. * Get the {@link AudioTrackList}
  19535. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
  19536. *
  19537. * @return {AudioTrackList}
  19538. * the current audio track list
  19539. *
  19540. * @method Player.prototype.audioTracks
  19541. */
  19542. /**
  19543. * Get the {@link TextTrackList}
  19544. *
  19545. * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
  19546. *
  19547. * @return {TextTrackList}
  19548. * the current text track list
  19549. *
  19550. * @method Player.prototype.textTracks
  19551. */
  19552. /**
  19553. * Get the remote {@link TextTrackList}
  19554. *
  19555. * @return {TextTrackList}
  19556. * The current remote text track list
  19557. *
  19558. * @method Player.prototype.remoteTextTracks
  19559. */
  19560. /**
  19561. * Get the remote {@link HtmlTrackElementList} tracks.
  19562. *
  19563. * @return {HtmlTrackElementList}
  19564. * The current remote text track element list
  19565. *
  19566. * @method Player.prototype.remoteTextTrackEls
  19567. */
  19568. ALL.names.forEach(function (name$$1) {
  19569. var props = ALL[name$$1];
  19570. Player.prototype[props.getterName] = function () {
  19571. if (this.tech_) {
  19572. return this.tech_[props.getterName]();
  19573. }
  19574. // if we have not yet loadTech_, we create {video,audio,text}Tracks_
  19575. // these will be passed to the tech during loading
  19576. this[props.privateName] = this[props.privateName] || new props.ListClass();
  19577. return this[props.privateName];
  19578. };
  19579. });
  19580. /**
  19581. * Global player list
  19582. *
  19583. * @type {Object}
  19584. */
  19585. Player.players = {};
  19586. var navigator = window.navigator;
  19587. /*
  19588. * Player instance options, surfaced using options
  19589. * options = Player.prototype.options_
  19590. * Make changes in options, not here.
  19591. *
  19592. * @type {Object}
  19593. * @private
  19594. */
  19595. Player.prototype.options_ = {
  19596. // Default order of fallback technology
  19597. techOrder: Tech.defaultTechOrder_,
  19598. html5: {},
  19599. flash: {},
  19600. // default inactivity timeout
  19601. inactivityTimeout: 2000,
  19602. // default playback rates
  19603. playbackRates: [],
  19604. // Add playback rate selection by adding rates
  19605. // 'playbackRates': [0.5, 1, 1.5, 2],
  19606. // Included control sets
  19607. children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings'],
  19608. language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
  19609. // locales and their language translations
  19610. languages: {},
  19611. // Default message to show when a video cannot be played.
  19612. notSupportedMessage: 'No compatible source was found for this media.',
  19613. breakpoints: {},
  19614. responsive: false
  19615. };
  19616. if (!IS_IE8) {
  19617. Player.prototype.options_.children.push('resizeManager');
  19618. }
  19619. [
  19620. /**
  19621. * Returns whether or not the player is in the "ended" state.
  19622. *
  19623. * @return {Boolean} True if the player is in the ended state, false if not.
  19624. * @method Player#ended
  19625. */
  19626. 'ended',
  19627. /**
  19628. * Returns whether or not the player is in the "seeking" state.
  19629. *
  19630. * @return {Boolean} True if the player is in the seeking state, false if not.
  19631. * @method Player#seeking
  19632. */
  19633. 'seeking',
  19634. /**
  19635. * Returns the TimeRanges of the media that are currently available
  19636. * for seeking to.
  19637. *
  19638. * @return {TimeRanges} the seekable intervals of the media timeline
  19639. * @method Player#seekable
  19640. */
  19641. 'seekable',
  19642. /**
  19643. * Returns the current state of network activity for the element, from
  19644. * the codes in the list below.
  19645. * - NETWORK_EMPTY (numeric value 0)
  19646. * The element has not yet been initialised. All attributes are in
  19647. * their initial states.
  19648. * - NETWORK_IDLE (numeric value 1)
  19649. * The element's resource selection algorithm is active and has
  19650. * selected a resource, but it is not actually using the network at
  19651. * this time.
  19652. * - NETWORK_LOADING (numeric value 2)
  19653. * The user agent is actively trying to download data.
  19654. * - NETWORK_NO_SOURCE (numeric value 3)
  19655. * The element's resource selection algorithm is active, but it has
  19656. * not yet found a resource to use.
  19657. *
  19658. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
  19659. * @return {number} the current network activity state
  19660. * @method Player#networkState
  19661. */
  19662. 'networkState',
  19663. /**
  19664. * Returns a value that expresses the current state of the element
  19665. * with respect to rendering the current playback position, from the
  19666. * codes in the list below.
  19667. * - HAVE_NOTHING (numeric value 0)
  19668. * No information regarding the media resource is available.
  19669. * - HAVE_METADATA (numeric value 1)
  19670. * Enough of the resource has been obtained that the duration of the
  19671. * resource is available.
  19672. * - HAVE_CURRENT_DATA (numeric value 2)
  19673. * Data for the immediate current playback position is available.
  19674. * - HAVE_FUTURE_DATA (numeric value 3)
  19675. * Data for the immediate current playback position is available, as
  19676. * well as enough data for the user agent to advance the current
  19677. * playback position in the direction of playback.
  19678. * - HAVE_ENOUGH_DATA (numeric value 4)
  19679. * The user agent estimates that enough data is available for
  19680. * playback to proceed uninterrupted.
  19681. *
  19682. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
  19683. * @return {number} the current playback rendering state
  19684. * @method Player#readyState
  19685. */
  19686. 'readyState'].forEach(function (fn) {
  19687. Player.prototype[fn] = function () {
  19688. return this.techGet_(fn);
  19689. };
  19690. });
  19691. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  19692. Player.prototype['handleTech' + toTitleCase(event) + '_'] = function () {
  19693. return this.trigger(event);
  19694. };
  19695. });
  19696. /**
  19697. * Fired when the player has initial duration and dimension information
  19698. *
  19699. * @event Player#loadedmetadata
  19700. * @type {EventTarget~Event}
  19701. */
  19702. /**
  19703. * Fired when the player has downloaded data at the current playback position
  19704. *
  19705. * @event Player#loadeddata
  19706. * @type {EventTarget~Event}
  19707. */
  19708. /**
  19709. * Fired when the current playback position has changed *
  19710. * During playback this is fired every 15-250 milliseconds, depending on the
  19711. * playback technology in use.
  19712. *
  19713. * @event Player#timeupdate
  19714. * @type {EventTarget~Event}
  19715. */
  19716. /**
  19717. * Fired when the volume changes
  19718. *
  19719. * @event Player#volumechange
  19720. * @type {EventTarget~Event}
  19721. */
  19722. /**
  19723. * Reports whether or not a player has a plugin available.
  19724. *
  19725. * This does not report whether or not the plugin has ever been initialized
  19726. * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
  19727. *
  19728. * @method Player#hasPlugin
  19729. * @param {string} name
  19730. * The name of a plugin.
  19731. *
  19732. * @return {boolean}
  19733. * Whether or not this player has the requested plugin available.
  19734. */
  19735. /**
  19736. * Reports whether or not a player is using a plugin by name.
  19737. *
  19738. * For basic plugins, this only reports whether the plugin has _ever_ been
  19739. * initialized on this player.
  19740. *
  19741. * @method Player#usingPlugin
  19742. * @param {string} name
  19743. * The name of a plugin.
  19744. *
  19745. * @return {boolean}
  19746. * Whether or not this player is using the requested plugin.
  19747. */
  19748. Component.registerComponent('Player', Player);
  19749. /**
  19750. * @file plugin.js
  19751. */
  19752. /**
  19753. * The base plugin name.
  19754. *
  19755. * @private
  19756. * @constant
  19757. * @type {string}
  19758. */
  19759. var BASE_PLUGIN_NAME = 'plugin';
  19760. /**
  19761. * The key on which a player's active plugins cache is stored.
  19762. *
  19763. * @private
  19764. * @constant
  19765. * @type {string}
  19766. */
  19767. var PLUGIN_CACHE_KEY = 'activePlugins_';
  19768. /**
  19769. * Stores registered plugins in a private space.
  19770. *
  19771. * @private
  19772. * @type {Object}
  19773. */
  19774. var pluginStorage = {};
  19775. /**
  19776. * Reports whether or not a plugin has been registered.
  19777. *
  19778. * @private
  19779. * @param {string} name
  19780. * The name of a plugin.
  19781. *
  19782. * @returns {boolean}
  19783. * Whether or not the plugin has been registered.
  19784. */
  19785. var pluginExists = function pluginExists(name) {
  19786. return pluginStorage.hasOwnProperty(name);
  19787. };
  19788. /**
  19789. * Get a single registered plugin by name.
  19790. *
  19791. * @private
  19792. * @param {string} name
  19793. * The name of a plugin.
  19794. *
  19795. * @returns {Function|undefined}
  19796. * The plugin (or undefined).
  19797. */
  19798. var getPlugin = function getPlugin(name) {
  19799. return pluginExists(name) ? pluginStorage[name] : undefined;
  19800. };
  19801. /**
  19802. * Marks a plugin as "active" on a player.
  19803. *
  19804. * Also, ensures that the player has an object for tracking active plugins.
  19805. *
  19806. * @private
  19807. * @param {Player} player
  19808. * A Video.js player instance.
  19809. *
  19810. * @param {string} name
  19811. * The name of a plugin.
  19812. */
  19813. var markPluginAsActive = function markPluginAsActive(player, name) {
  19814. player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
  19815. player[PLUGIN_CACHE_KEY][name] = true;
  19816. };
  19817. /**
  19818. * Triggers a pair of plugin setup events.
  19819. *
  19820. * @private
  19821. * @param {Player} player
  19822. * A Video.js player instance.
  19823. *
  19824. * @param {Plugin~PluginEventHash} hash
  19825. * A plugin event hash.
  19826. *
  19827. * @param {Boolean} [before]
  19828. * If true, prefixes the event name with "before". In other words,
  19829. * use this to trigger "beforepluginsetup" instead of "pluginsetup".
  19830. */
  19831. var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
  19832. var eventName = (before ? 'before' : '') + 'pluginsetup';
  19833. player.trigger(eventName, hash);
  19834. player.trigger(eventName + ':' + hash.name, hash);
  19835. };
  19836. /**
  19837. * Takes a basic plugin function and returns a wrapper function which marks
  19838. * on the player that the plugin has been activated.
  19839. *
  19840. * @private
  19841. * @param {string} name
  19842. * The name of the plugin.
  19843. *
  19844. * @param {Function} plugin
  19845. * The basic plugin.
  19846. *
  19847. * @returns {Function}
  19848. * A wrapper function for the given plugin.
  19849. */
  19850. var createBasicPlugin = function createBasicPlugin(name, plugin) {
  19851. var basicPluginWrapper = function basicPluginWrapper() {
  19852. // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
  19853. // regardless, but we want the hash to be consistent with the hash provided
  19854. // for advanced plugins.
  19855. //
  19856. // The only potentially counter-intuitive thing here is the `instance` in
  19857. // the "pluginsetup" event is the value returned by the `plugin` function.
  19858. triggerSetupEvent(this, { name: name, plugin: plugin, instance: null }, true);
  19859. var instance = plugin.apply(this, arguments);
  19860. markPluginAsActive(this, name);
  19861. triggerSetupEvent(this, { name: name, plugin: plugin, instance: instance });
  19862. return instance;
  19863. };
  19864. Object.keys(plugin).forEach(function (prop) {
  19865. basicPluginWrapper[prop] = plugin[prop];
  19866. });
  19867. return basicPluginWrapper;
  19868. };
  19869. /**
  19870. * Takes a plugin sub-class and returns a factory function for generating
  19871. * instances of it.
  19872. *
  19873. * This factory function will replace itself with an instance of the requested
  19874. * sub-class of Plugin.
  19875. *
  19876. * @private
  19877. * @param {string} name
  19878. * The name of the plugin.
  19879. *
  19880. * @param {Plugin} PluginSubClass
  19881. * The advanced plugin.
  19882. *
  19883. * @returns {Function}
  19884. */
  19885. var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
  19886. // Add a `name` property to the plugin prototype so that each plugin can
  19887. // refer to itself by name.
  19888. PluginSubClass.prototype.name = name;
  19889. return function () {
  19890. triggerSetupEvent(this, { name: name, plugin: PluginSubClass, instance: null }, true);
  19891. for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
  19892. args[_key] = arguments[_key];
  19893. }
  19894. var instance = new (Function.prototype.bind.apply(PluginSubClass, [null].concat([this].concat(args))))();
  19895. // The plugin is replaced by a function that returns the current instance.
  19896. this[name] = function () {
  19897. return instance;
  19898. };
  19899. triggerSetupEvent(this, instance.getEventHash());
  19900. return instance;
  19901. };
  19902. };
  19903. /**
  19904. * Parent class for all advanced plugins.
  19905. *
  19906. * @mixes module:evented~EventedMixin
  19907. * @mixes module:stateful~StatefulMixin
  19908. * @fires Player#beforepluginsetup
  19909. * @fires Player#beforepluginsetup:$name
  19910. * @fires Player#pluginsetup
  19911. * @fires Player#pluginsetup:$name
  19912. * @listens Player#dispose
  19913. * @throws {Error}
  19914. * If attempting to instantiate the base {@link Plugin} class
  19915. * directly instead of via a sub-class.
  19916. */
  19917. var Plugin = function () {
  19918. /**
  19919. * Creates an instance of this class.
  19920. *
  19921. * Sub-classes should call `super` to ensure plugins are properly initialized.
  19922. *
  19923. * @param {Player} player
  19924. * A Video.js player instance.
  19925. */
  19926. function Plugin(player) {
  19927. classCallCheck(this, Plugin);
  19928. if (this.constructor === Plugin) {
  19929. throw new Error('Plugin must be sub-classed; not directly instantiated.');
  19930. }
  19931. this.player = player;
  19932. // Make this object evented, but remove the added `trigger` method so we
  19933. // use the prototype version instead.
  19934. evented(this);
  19935. delete this.trigger;
  19936. stateful(this, this.constructor.defaultState);
  19937. markPluginAsActive(player, this.name);
  19938. // Auto-bind the dispose method so we can use it as a listener and unbind
  19939. // it later easily.
  19940. this.dispose = bind(this, this.dispose);
  19941. // If the player is disposed, dispose the plugin.
  19942. player.on('dispose', this.dispose);
  19943. }
  19944. /**
  19945. * Get the version of the plugin that was set on <pluginName>.VERSION
  19946. */
  19947. Plugin.prototype.version = function version() {
  19948. return this.constructor.VERSION;
  19949. };
  19950. /**
  19951. * Each event triggered by plugins includes a hash of additional data with
  19952. * conventional properties.
  19953. *
  19954. * This returns that object or mutates an existing hash.
  19955. *
  19956. * @param {Object} [hash={}]
  19957. * An object to be used as event an event hash.
  19958. *
  19959. * @returns {Plugin~PluginEventHash}
  19960. * An event hash object with provided properties mixed-in.
  19961. */
  19962. Plugin.prototype.getEventHash = function getEventHash() {
  19963. var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  19964. hash.name = this.name;
  19965. hash.plugin = this.constructor;
  19966. hash.instance = this;
  19967. return hash;
  19968. };
  19969. /**
  19970. * Triggers an event on the plugin object and overrides
  19971. * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
  19972. *
  19973. * @param {string|Object} event
  19974. * An event type or an object with a type property.
  19975. *
  19976. * @param {Object} [hash={}]
  19977. * Additional data hash to merge with a
  19978. * {@link Plugin~PluginEventHash|PluginEventHash}.
  19979. *
  19980. * @returns {boolean}
  19981. * Whether or not default was prevented.
  19982. */
  19983. Plugin.prototype.trigger = function trigger$$1(event) {
  19984. var hash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  19985. return trigger(this.eventBusEl_, event, this.getEventHash(hash));
  19986. };
  19987. /**
  19988. * Handles "statechanged" events on the plugin. No-op by default, override by
  19989. * subclassing.
  19990. *
  19991. * @abstract
  19992. * @param {Event} e
  19993. * An event object provided by a "statechanged" event.
  19994. *
  19995. * @param {Object} e.changes
  19996. * An object describing changes that occurred with the "statechanged"
  19997. * event.
  19998. */
  19999. Plugin.prototype.handleStateChanged = function handleStateChanged(e) {};
  20000. /**
  20001. * Disposes a plugin.
  20002. *
  20003. * Subclasses can override this if they want, but for the sake of safety,
  20004. * it's probably best to subscribe the "dispose" event.
  20005. *
  20006. * @fires Plugin#dispose
  20007. */
  20008. Plugin.prototype.dispose = function dispose() {
  20009. var name = this.name,
  20010. player = this.player;
  20011. /**
  20012. * Signals that a advanced plugin is about to be disposed.
  20013. *
  20014. * @event Plugin#dispose
  20015. * @type {EventTarget~Event}
  20016. */
  20017. this.trigger('dispose');
  20018. this.off();
  20019. player.off('dispose', this.dispose);
  20020. // Eliminate any possible sources of leaking memory by clearing up
  20021. // references between the player and the plugin instance and nulling out
  20022. // the plugin's state and replacing methods with a function that throws.
  20023. player[PLUGIN_CACHE_KEY][name] = false;
  20024. this.player = this.state = null;
  20025. // Finally, replace the plugin name on the player with a new factory
  20026. // function, so that the plugin is ready to be set up again.
  20027. player[name] = createPluginFactory(name, pluginStorage[name]);
  20028. };
  20029. /**
  20030. * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
  20031. *
  20032. * @param {string|Function} plugin
  20033. * If a string, matches the name of a plugin. If a function, will be
  20034. * tested directly.
  20035. *
  20036. * @returns {boolean}
  20037. * Whether or not a plugin is a basic plugin.
  20038. */
  20039. Plugin.isBasic = function isBasic(plugin) {
  20040. var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
  20041. return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
  20042. };
  20043. /**
  20044. * Register a Video.js plugin.
  20045. *
  20046. * @param {string} name
  20047. * The name of the plugin to be registered. Must be a string and
  20048. * must not match an existing plugin or a method on the `Player`
  20049. * prototype.
  20050. *
  20051. * @param {Function} plugin
  20052. * A sub-class of `Plugin` or a function for basic plugins.
  20053. *
  20054. * @returns {Function}
  20055. * For advanced plugins, a factory function for that plugin. For
  20056. * basic plugins, a wrapper function that initializes the plugin.
  20057. */
  20058. Plugin.registerPlugin = function registerPlugin(name, plugin) {
  20059. if (typeof name !== 'string') {
  20060. throw new Error('Illegal plugin name, "' + name + '", must be a string, was ' + (typeof name === 'undefined' ? 'undefined' : _typeof(name)) + '.');
  20061. }
  20062. if (pluginExists(name)) {
  20063. log.warn('A plugin named "' + name + '" already exists. You may want to avoid re-registering plugins!');
  20064. } else if (Player.prototype.hasOwnProperty(name)) {
  20065. throw new Error('Illegal plugin name, "' + name + '", cannot share a name with an existing player method!');
  20066. }
  20067. if (typeof plugin !== 'function') {
  20068. throw new Error('Illegal plugin for "' + name + '", must be a function, was ' + (typeof plugin === 'undefined' ? 'undefined' : _typeof(plugin)) + '.');
  20069. }
  20070. pluginStorage[name] = plugin;
  20071. // Add a player prototype method for all sub-classed plugins (but not for
  20072. // the base Plugin class).
  20073. if (name !== BASE_PLUGIN_NAME) {
  20074. if (Plugin.isBasic(plugin)) {
  20075. Player.prototype[name] = createBasicPlugin(name, plugin);
  20076. } else {
  20077. Player.prototype[name] = createPluginFactory(name, plugin);
  20078. }
  20079. }
  20080. return plugin;
  20081. };
  20082. /**
  20083. * De-register a Video.js plugin.
  20084. *
  20085. * @param {string} name
  20086. * The name of the plugin to be deregistered.
  20087. */
  20088. Plugin.deregisterPlugin = function deregisterPlugin(name) {
  20089. if (name === BASE_PLUGIN_NAME) {
  20090. throw new Error('Cannot de-register base plugin.');
  20091. }
  20092. if (pluginExists(name)) {
  20093. delete pluginStorage[name];
  20094. delete Player.prototype[name];
  20095. }
  20096. };
  20097. /**
  20098. * Gets an object containing multiple Video.js plugins.
  20099. *
  20100. * @param {Array} [names]
  20101. * If provided, should be an array of plugin names. Defaults to _all_
  20102. * plugin names.
  20103. *
  20104. * @returns {Object|undefined}
  20105. * An object containing plugin(s) associated with their name(s) or
  20106. * `undefined` if no matching plugins exist).
  20107. */
  20108. Plugin.getPlugins = function getPlugins() {
  20109. var names = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Object.keys(pluginStorage);
  20110. var result = void 0;
  20111. names.forEach(function (name) {
  20112. var plugin = getPlugin(name);
  20113. if (plugin) {
  20114. result = result || {};
  20115. result[name] = plugin;
  20116. }
  20117. });
  20118. return result;
  20119. };
  20120. /**
  20121. * Gets a plugin's version, if available
  20122. *
  20123. * @param {string} name
  20124. * The name of a plugin.
  20125. *
  20126. * @returns {string}
  20127. * The plugin's version or an empty string.
  20128. */
  20129. Plugin.getPluginVersion = function getPluginVersion(name) {
  20130. var plugin = getPlugin(name);
  20131. return plugin && plugin.VERSION || '';
  20132. };
  20133. return Plugin;
  20134. }();
  20135. /**
  20136. * Gets a plugin by name if it exists.
  20137. *
  20138. * @static
  20139. * @method getPlugin
  20140. * @memberOf Plugin
  20141. * @param {string} name
  20142. * The name of a plugin.
  20143. *
  20144. * @returns {Function|undefined}
  20145. * The plugin (or `undefined`).
  20146. */
  20147. Plugin.getPlugin = getPlugin;
  20148. /**
  20149. * The name of the base plugin class as it is registered.
  20150. *
  20151. * @type {string}
  20152. */
  20153. Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
  20154. Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
  20155. /**
  20156. * Documented in player.js
  20157. *
  20158. * @ignore
  20159. */
  20160. Player.prototype.usingPlugin = function (name) {
  20161. return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
  20162. };
  20163. /**
  20164. * Documented in player.js
  20165. *
  20166. * @ignore
  20167. */
  20168. Player.prototype.hasPlugin = function (name) {
  20169. return !!pluginExists(name);
  20170. };
  20171. /**
  20172. * Signals that a plugin is about to be set up on a player.
  20173. *
  20174. * @event Player#beforepluginsetup
  20175. * @type {Plugin~PluginEventHash}
  20176. */
  20177. /**
  20178. * Signals that a plugin is about to be set up on a player - by name. The name
  20179. * is the name of the plugin.
  20180. *
  20181. * @event Player#beforepluginsetup:$name
  20182. * @type {Plugin~PluginEventHash}
  20183. */
  20184. /**
  20185. * Signals that a plugin has just been set up on a player.
  20186. *
  20187. * @event Player#pluginsetup
  20188. * @type {Plugin~PluginEventHash}
  20189. */
  20190. /**
  20191. * Signals that a plugin has just been set up on a player - by name. The name
  20192. * is the name of the plugin.
  20193. *
  20194. * @event Player#pluginsetup:$name
  20195. * @type {Plugin~PluginEventHash}
  20196. */
  20197. /**
  20198. * @typedef {Object} Plugin~PluginEventHash
  20199. *
  20200. * @property {string} instance
  20201. * For basic plugins, the return value of the plugin function. For
  20202. * advanced plugins, the plugin instance on which the event is fired.
  20203. *
  20204. * @property {string} name
  20205. * The name of the plugin.
  20206. *
  20207. * @property {string} plugin
  20208. * For basic plugins, the plugin function. For advanced plugins, the
  20209. * plugin class/constructor.
  20210. */
  20211. /**
  20212. * @file extend.js
  20213. * @module extend
  20214. */
  20215. /**
  20216. * A combination of node inherits and babel's inherits (after transpile).
  20217. * Both work the same but node adds `super_` to the subClass
  20218. * and Bable adds the superClass as __proto__. Both seem useful.
  20219. *
  20220. * @param {Object} subClass
  20221. * The class to inherit to
  20222. *
  20223. * @param {Object} superClass
  20224. * The class to inherit from
  20225. *
  20226. * @private
  20227. */
  20228. var _inherits = function _inherits(subClass, superClass) {
  20229. if (typeof superClass !== 'function' && superClass !== null) {
  20230. throw new TypeError('Super expression must either be null or a function, not ' + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)));
  20231. }
  20232. subClass.prototype = Object.create(superClass && superClass.prototype, {
  20233. constructor: {
  20234. value: subClass,
  20235. enumerable: false,
  20236. writable: true,
  20237. configurable: true
  20238. }
  20239. });
  20240. if (superClass) {
  20241. // node
  20242. subClass.super_ = superClass;
  20243. }
  20244. };
  20245. /**
  20246. * Function for subclassing using the same inheritance that
  20247. * videojs uses internally
  20248. *
  20249. * @static
  20250. * @const
  20251. *
  20252. * @param {Object} superClass
  20253. * The class to inherit from
  20254. *
  20255. * @param {Object} [subClassMethods={}]
  20256. * The class to inherit to
  20257. *
  20258. * @return {Object}
  20259. * The new object with subClassMethods that inherited superClass.
  20260. */
  20261. var extendFn = function extendFn(superClass) {
  20262. var subClassMethods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  20263. var subClass = function subClass() {
  20264. superClass.apply(this, arguments);
  20265. };
  20266. var methods = {};
  20267. if ((typeof subClassMethods === 'undefined' ? 'undefined' : _typeof(subClassMethods)) === 'object') {
  20268. if (subClassMethods.constructor !== Object.prototype.constructor) {
  20269. subClass = subClassMethods.constructor;
  20270. }
  20271. methods = subClassMethods;
  20272. } else if (typeof subClassMethods === 'function') {
  20273. subClass = subClassMethods;
  20274. }
  20275. _inherits(subClass, superClass);
  20276. // Extend subObj's prototype with functions and other properties from props
  20277. for (var name in methods) {
  20278. if (methods.hasOwnProperty(name)) {
  20279. subClass.prototype[name] = methods[name];
  20280. }
  20281. }
  20282. return subClass;
  20283. };
  20284. /**
  20285. * @file video.js
  20286. * @module videojs
  20287. */
  20288. // Include the built-in techs
  20289. // HTML5 Element Shim for IE8
  20290. if (typeof HTMLVideoElement === 'undefined' && isReal()) {
  20291. document.createElement('video');
  20292. document.createElement('audio');
  20293. document.createElement('track');
  20294. document.createElement('video-js');
  20295. }
  20296. /**
  20297. * Normalize an `id` value by trimming off a leading `#`
  20298. *
  20299. * @param {string} id
  20300. * A string, maybe with a leading `#`.
  20301. *
  20302. * @returns {string}
  20303. * The string, without any leading `#`.
  20304. */
  20305. var normalizeId = function normalizeId(id) {
  20306. return id.indexOf('#') === 0 ? id.slice(1) : id;
  20307. };
  20308. /**
  20309. * Doubles as the main function for users to create a player instance and also
  20310. * the main library object.
  20311. * The `videojs` function can be used to initialize or retrieve a player.
  20312. *
  20313. * @param {string|Element} id
  20314. * Video element or video element ID
  20315. *
  20316. * @param {Object} [options]
  20317. * Optional options object for config/settings
  20318. *
  20319. * @param {Component~ReadyCallback} [ready]
  20320. * Optional ready callback
  20321. *
  20322. * @return {Player}
  20323. * A player instance
  20324. */
  20325. function videojs(id, options, ready) {
  20326. var player = videojs.getPlayer(id);
  20327. if (player) {
  20328. if (options) {
  20329. log.warn('Player "' + id + '" is already initialised. Options will not be applied.');
  20330. }
  20331. if (ready) {
  20332. player.ready(ready);
  20333. }
  20334. return player;
  20335. }
  20336. var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
  20337. if (!isEl(el)) {
  20338. throw new TypeError('The element or ID supplied is not valid. (videojs)');
  20339. }
  20340. if (!document.body.contains(el)) {
  20341. log.warn('The element supplied is not included in the DOM');
  20342. }
  20343. options = options || {};
  20344. videojs.hooks('beforesetup').forEach(function (hookFunction) {
  20345. var opts = hookFunction(el, mergeOptions(options));
  20346. if (!isObject(opts) || Array.isArray(opts)) {
  20347. log.error('please return an object in beforesetup hooks');
  20348. return;
  20349. }
  20350. options = mergeOptions(options, opts);
  20351. });
  20352. // We get the current "Player" component here in case an integration has
  20353. // replaced it with a custom player.
  20354. var PlayerComponent = Component.getComponent('Player');
  20355. player = new PlayerComponent(el, options, ready);
  20356. videojs.hooks('setup').forEach(function (hookFunction) {
  20357. return hookFunction(player);
  20358. });
  20359. return player;
  20360. }
  20361. /**
  20362. * An Object that contains lifecycle hooks as keys which point to an array
  20363. * of functions that are run when a lifecycle is triggered
  20364. */
  20365. videojs.hooks_ = {};
  20366. /**
  20367. * Get a list of hooks for a specific lifecycle
  20368. * @function videojs.hooks
  20369. *
  20370. * @param {string} type
  20371. * the lifecyle to get hooks from
  20372. *
  20373. * @param {Function|Function[]} [fn]
  20374. * Optionally add a hook (or hooks) to the lifecycle that your are getting.
  20375. *
  20376. * @return {Array}
  20377. * an array of hooks, or an empty array if there are none.
  20378. */
  20379. videojs.hooks = function (type, fn) {
  20380. videojs.hooks_[type] = videojs.hooks_[type] || [];
  20381. if (fn) {
  20382. videojs.hooks_[type] = videojs.hooks_[type].concat(fn);
  20383. }
  20384. return videojs.hooks_[type];
  20385. };
  20386. /**
  20387. * Add a function hook to a specific videojs lifecycle.
  20388. *
  20389. * @param {string} type
  20390. * the lifecycle to hook the function to.
  20391. *
  20392. * @param {Function|Function[]}
  20393. * The function or array of functions to attach.
  20394. */
  20395. videojs.hook = function (type, fn) {
  20396. videojs.hooks(type, fn);
  20397. };
  20398. /**
  20399. * Add a function hook that will only run once to a specific videojs lifecycle.
  20400. *
  20401. * @param {string} type
  20402. * the lifecycle to hook the function to.
  20403. *
  20404. * @param {Function|Function[]}
  20405. * The function or array of functions to attach.
  20406. */
  20407. videojs.hookOnce = function (type, fn) {
  20408. videojs.hooks(type, [].concat(fn).map(function (original) {
  20409. var wrapper = function wrapper() {
  20410. videojs.removeHook(type, wrapper);
  20411. return original.apply(undefined, arguments);
  20412. };
  20413. return wrapper;
  20414. }));
  20415. };
  20416. /**
  20417. * Remove a hook from a specific videojs lifecycle.
  20418. *
  20419. * @param {string} type
  20420. * the lifecycle that the function hooked to
  20421. *
  20422. * @param {Function} fn
  20423. * The hooked function to remove
  20424. *
  20425. * @return {boolean}
  20426. * The function that was removed or undef
  20427. */
  20428. videojs.removeHook = function (type, fn) {
  20429. var index = videojs.hooks(type).indexOf(fn);
  20430. if (index <= -1) {
  20431. return false;
  20432. }
  20433. videojs.hooks_[type] = videojs.hooks_[type].slice();
  20434. videojs.hooks_[type].splice(index, 1);
  20435. return true;
  20436. };
  20437. // Add default styles
  20438. if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
  20439. var style = $('.vjs-styles-defaults');
  20440. if (!style) {
  20441. style = createStyleElement('vjs-styles-defaults');
  20442. var head = $('head');
  20443. if (head) {
  20444. head.insertBefore(style, head.firstChild);
  20445. }
  20446. setTextContent(style, '\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n ');
  20447. }
  20448. }
  20449. // Run Auto-load players
  20450. // You have to wait at least once in case this script is loaded after your
  20451. // video in the DOM (weird behavior only with minified version)
  20452. autoSetupTimeout(1, videojs);
  20453. /**
  20454. * Current software version. Follows semver.
  20455. *
  20456. * @type {string}
  20457. */
  20458. videojs.VERSION = version;
  20459. /**
  20460. * The global options object. These are the settings that take effect
  20461. * if no overrides are specified when the player is created.
  20462. *
  20463. * @type {Object}
  20464. */
  20465. videojs.options = Player.prototype.options_;
  20466. /**
  20467. * Get an object with the currently created players, keyed by player ID
  20468. *
  20469. * @return {Object}
  20470. * The created players
  20471. */
  20472. videojs.getPlayers = function () {
  20473. return Player.players;
  20474. };
  20475. /**
  20476. * Get a single player based on an ID or DOM element.
  20477. *
  20478. * This is useful if you want to check if an element or ID has an associated
  20479. * Video.js player, but not create one if it doesn't.
  20480. *
  20481. * @param {string|Element} id
  20482. * An HTML element - `<video>`, `<audio>`, or `<video-js>` -
  20483. * or a string matching the `id` of such an element.
  20484. *
  20485. * @returns {Player|undefined}
  20486. * A player instance or `undefined` if there is no player instance
  20487. * matching the argument.
  20488. */
  20489. videojs.getPlayer = function (id) {
  20490. var players = Player.players;
  20491. var tag = void 0;
  20492. if (typeof id === 'string') {
  20493. var nId = normalizeId(id);
  20494. var player = players[nId];
  20495. if (player) {
  20496. return player;
  20497. }
  20498. tag = $('#' + nId);
  20499. } else {
  20500. tag = id;
  20501. }
  20502. if (isEl(tag)) {
  20503. var _tag = tag,
  20504. _player = _tag.player,
  20505. playerId = _tag.playerId;
  20506. // Element may have a `player` property referring to an already created
  20507. // player instance. If so, return that.
  20508. if (_player || players[playerId]) {
  20509. return _player || players[playerId];
  20510. }
  20511. }
  20512. };
  20513. /**
  20514. * Returns an array of all current players.
  20515. *
  20516. * @return {Array}
  20517. * An array of all players. The array will be in the order that
  20518. * `Object.keys` provides, which could potentially vary between
  20519. * JavaScript engines.
  20520. *
  20521. */
  20522. videojs.getAllPlayers = function () {
  20523. return (
  20524. // Disposed players leave a key with a `null` value, so we need to make sure
  20525. // we filter those out.
  20526. Object.keys(Player.players).map(function (k) {
  20527. return Player.players[k];
  20528. }).filter(Boolean)
  20529. );
  20530. };
  20531. /**
  20532. * Expose players object.
  20533. *
  20534. * @memberOf videojs
  20535. * @property {Object} players
  20536. */
  20537. videojs.players = Player.players;
  20538. /**
  20539. * Get a component class object by name
  20540. *
  20541. * @borrows Component.getComponent as videojs.getComponent
  20542. */
  20543. videojs.getComponent = Component.getComponent;
  20544. /**
  20545. * Register a component so it can referred to by name. Used when adding to other
  20546. * components, either through addChild `component.addChild('myComponent')` or through
  20547. * default children options `{ children: ['myComponent'] }`.
  20548. *
  20549. * > NOTE: You could also just initialize the component before adding.
  20550. * `component.addChild(new MyComponent());`
  20551. *
  20552. * @param {string} name
  20553. * The class name of the component
  20554. *
  20555. * @param {Component} comp
  20556. * The component class
  20557. *
  20558. * @return {Component}
  20559. * The newly registered component
  20560. */
  20561. videojs.registerComponent = function (name$$1, comp) {
  20562. if (Tech.isTech(comp)) {
  20563. log.warn('The ' + name$$1 + ' tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)');
  20564. }
  20565. Component.registerComponent.call(Component, name$$1, comp);
  20566. };
  20567. /**
  20568. * Get a Tech class object by name
  20569. *
  20570. * @borrows Tech.getTech as videojs.getTech
  20571. */
  20572. videojs.getTech = Tech.getTech;
  20573. /**
  20574. * Register a Tech so it can referred to by name.
  20575. * This is used in the tech order for the player.
  20576. *
  20577. * @borrows Tech.registerTech as videojs.registerTech
  20578. */
  20579. videojs.registerTech = Tech.registerTech;
  20580. /**
  20581. * Register a middleware to a source type.
  20582. *
  20583. * @param {String} type A string representing a MIME type.
  20584. * @param {function(player):object} middleware A middleware factory that takes a player.
  20585. */
  20586. videojs.use = use;
  20587. /**
  20588. * An object that can be returned by a middleware to signify
  20589. * that the middleware is being terminated.
  20590. *
  20591. * @type {object}
  20592. * @memberOf {videojs}
  20593. * @property {object} middleware.TERMINATOR
  20594. */
  20595. // Object.defineProperty is not available in IE8
  20596. if (!IS_IE8 && Object.defineProperty) {
  20597. Object.defineProperty(videojs, 'middleware', {
  20598. value: {},
  20599. writeable: false,
  20600. enumerable: true
  20601. });
  20602. Object.defineProperty(videojs.middleware, 'TERMINATOR', {
  20603. value: TERMINATOR,
  20604. writeable: false,
  20605. enumerable: true
  20606. });
  20607. } else {
  20608. videojs.middleware = { TERMINATOR: TERMINATOR };
  20609. }
  20610. /**
  20611. * A suite of browser and device tests from {@link browser}.
  20612. *
  20613. * @type {Object}
  20614. * @private
  20615. */
  20616. videojs.browser = browser;
  20617. /**
  20618. * Whether or not the browser supports touch events. Included for backward
  20619. * compatibility with 4.x, but deprecated. Use `videojs.browser.TOUCH_ENABLED`
  20620. * instead going forward.
  20621. *
  20622. * @deprecated since version 5.0
  20623. * @type {boolean}
  20624. */
  20625. videojs.TOUCH_ENABLED = TOUCH_ENABLED;
  20626. /**
  20627. * Subclass an existing class
  20628. * Mimics ES6 subclassing with the `extend` keyword
  20629. *
  20630. * @borrows extend:extendFn as videojs.extend
  20631. */
  20632. videojs.extend = extendFn;
  20633. /**
  20634. * Merge two options objects recursively
  20635. * Performs a deep merge like lodash.merge but **only merges plain objects**
  20636. * (not arrays, elements, anything else)
  20637. * Other values will be copied directly from the second object.
  20638. *
  20639. * @borrows merge-options:mergeOptions as videojs.mergeOptions
  20640. */
  20641. videojs.mergeOptions = mergeOptions;
  20642. /**
  20643. * Change the context (this) of a function
  20644. *
  20645. * > NOTE: as of v5.0 we require an ES5 shim, so you should use the native
  20646. * `function() {}.bind(newContext);` instead of this.
  20647. *
  20648. * @borrows fn:bind as videojs.bind
  20649. */
  20650. videojs.bind = bind;
  20651. /**
  20652. * Register a Video.js plugin.
  20653. *
  20654. * @borrows plugin:registerPlugin as videojs.registerPlugin
  20655. * @method registerPlugin
  20656. *
  20657. * @param {string} name
  20658. * The name of the plugin to be registered. Must be a string and
  20659. * must not match an existing plugin or a method on the `Player`
  20660. * prototype.
  20661. *
  20662. * @param {Function} plugin
  20663. * A sub-class of `Plugin` or a function for basic plugins.
  20664. *
  20665. * @return {Function}
  20666. * For advanced plugins, a factory function for that plugin. For
  20667. * basic plugins, a wrapper function that initializes the plugin.
  20668. */
  20669. videojs.registerPlugin = Plugin.registerPlugin;
  20670. /**
  20671. * Deregister a Video.js plugin.
  20672. *
  20673. * @borrows plugin:deregisterPlugin as videojs.deregisterPlugin
  20674. * @method deregisterPlugin
  20675. *
  20676. * @param {string} name
  20677. * The name of the plugin to be deregistered. Must be a string and
  20678. * must match an existing plugin or a method on the `Player`
  20679. * prototype.
  20680. *
  20681. */
  20682. videojs.deregisterPlugin = Plugin.deregisterPlugin;
  20683. /**
  20684. * Deprecated method to register a plugin with Video.js
  20685. *
  20686. * @deprecated
  20687. * videojs.plugin() is deprecated; use videojs.registerPlugin() instead
  20688. *
  20689. * @param {string} name
  20690. * The plugin name
  20691. *
  20692. * @param {Plugin|Function} plugin
  20693. * The plugin sub-class or function
  20694. */
  20695. videojs.plugin = function (name$$1, plugin) {
  20696. log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
  20697. return Plugin.registerPlugin(name$$1, plugin);
  20698. };
  20699. /**
  20700. * Gets an object containing multiple Video.js plugins.
  20701. *
  20702. * @param {Array} [names]
  20703. * If provided, should be an array of plugin names. Defaults to _all_
  20704. * plugin names.
  20705. *
  20706. * @return {Object|undefined}
  20707. * An object containing plugin(s) associated with their name(s) or
  20708. * `undefined` if no matching plugins exist).
  20709. */
  20710. videojs.getPlugins = Plugin.getPlugins;
  20711. /**
  20712. * Gets a plugin by name if it exists.
  20713. *
  20714. * @param {string} name
  20715. * The name of a plugin.
  20716. *
  20717. * @return {Function|undefined}
  20718. * The plugin (or `undefined`).
  20719. */
  20720. videojs.getPlugin = Plugin.getPlugin;
  20721. /**
  20722. * Gets a plugin's version, if available
  20723. *
  20724. * @param {string} name
  20725. * The name of a plugin.
  20726. *
  20727. * @return {string}
  20728. * The plugin's version or an empty string.
  20729. */
  20730. videojs.getPluginVersion = Plugin.getPluginVersion;
  20731. /**
  20732. * Adding languages so that they're available to all players.
  20733. * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
  20734. *
  20735. * @param {string} code
  20736. * The language code or dictionary property
  20737. *
  20738. * @param {Object} data
  20739. * The data values to be translated
  20740. *
  20741. * @return {Object}
  20742. * The resulting language dictionary object
  20743. */
  20744. videojs.addLanguage = function (code, data) {
  20745. var _mergeOptions;
  20746. code = ('' + code).toLowerCase();
  20747. videojs.options.languages = mergeOptions(videojs.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
  20748. return videojs.options.languages[code];
  20749. };
  20750. /**
  20751. * Log messages
  20752. *
  20753. * @borrows log:log as videojs.log
  20754. */
  20755. videojs.log = log;
  20756. videojs.createLogger = createLogger;
  20757. /**
  20758. * Creates an emulated TimeRange object.
  20759. *
  20760. * @borrows time-ranges:createTimeRanges as videojs.createTimeRange
  20761. */
  20762. /**
  20763. * @borrows time-ranges:createTimeRanges as videojs.createTimeRanges
  20764. */
  20765. videojs.createTimeRange = videojs.createTimeRanges = createTimeRanges;
  20766. /**
  20767. * Format seconds as a time string, H:MM:SS or M:SS
  20768. * Supplying a guide (in seconds) will force a number of leading zeros
  20769. * to cover the length of the guide
  20770. *
  20771. * @borrows format-time:formatTime as videojs.formatTime
  20772. */
  20773. videojs.formatTime = formatTime;
  20774. /**
  20775. * Replaces format-time with a custom implementation, to be used in place of the default.
  20776. *
  20777. * @borrows format-time:setFormatTime as videojs.setFormatTime
  20778. *
  20779. * @method setFormatTime
  20780. *
  20781. * @param {Function} customFn
  20782. * A custom format-time function which will be called with the current time and guide (in seconds) as arguments.
  20783. * Passed fn should return a string.
  20784. */
  20785. videojs.setFormatTime = setFormatTime;
  20786. /**
  20787. * Resets format-time to the default implementation.
  20788. *
  20789. * @borrows format-time:resetFormatTime as videojs.resetFormatTime
  20790. *
  20791. * @method resetFormatTime
  20792. */
  20793. videojs.resetFormatTime = resetFormatTime;
  20794. /**
  20795. * Resolve and parse the elements of a URL
  20796. *
  20797. * @borrows url:parseUrl as videojs.parseUrl
  20798. *
  20799. */
  20800. videojs.parseUrl = parseUrl;
  20801. /**
  20802. * Returns whether the url passed is a cross domain request or not.
  20803. *
  20804. * @borrows url:isCrossOrigin as videojs.isCrossOrigin
  20805. */
  20806. videojs.isCrossOrigin = isCrossOrigin;
  20807. /**
  20808. * Event target class.
  20809. *
  20810. * @borrows EventTarget as videojs.EventTarget
  20811. */
  20812. videojs.EventTarget = EventTarget;
  20813. /**
  20814. * Add an event listener to element
  20815. * It stores the handler function in a separate cache object
  20816. * and adds a generic handler to the element's event,
  20817. * along with a unique id (guid) to the element.
  20818. *
  20819. * @borrows events:on as videojs.on
  20820. */
  20821. videojs.on = on;
  20822. /**
  20823. * Trigger a listener only once for an event
  20824. *
  20825. * @borrows events:one as videojs.one
  20826. */
  20827. videojs.one = one;
  20828. /**
  20829. * Removes event listeners from an element
  20830. *
  20831. * @borrows events:off as videojs.off
  20832. */
  20833. videojs.off = off;
  20834. /**
  20835. * Trigger an event for an element
  20836. *
  20837. * @borrows events:trigger as videojs.trigger
  20838. */
  20839. videojs.trigger = trigger;
  20840. /**
  20841. * A cross-browser XMLHttpRequest wrapper. Here's a simple example:
  20842. *
  20843. * @param {Object} options
  20844. * settings for the request.
  20845. *
  20846. * @return {XMLHttpRequest|XDomainRequest}
  20847. * The request object.
  20848. *
  20849. * @see https://github.com/Raynos/xhr
  20850. */
  20851. videojs.xhr = xhr;
  20852. /**
  20853. * TextTrack class
  20854. *
  20855. * @borrows TextTrack as videojs.TextTrack
  20856. */
  20857. videojs.TextTrack = TextTrack;
  20858. /**
  20859. * export the AudioTrack class so that source handlers can create
  20860. * AudioTracks and then add them to the players AudioTrackList
  20861. *
  20862. * @borrows AudioTrack as videojs.AudioTrack
  20863. */
  20864. videojs.AudioTrack = AudioTrack;
  20865. /**
  20866. * export the VideoTrack class so that source handlers can create
  20867. * VideoTracks and then add them to the players VideoTrackList
  20868. *
  20869. * @borrows VideoTrack as videojs.VideoTrack
  20870. */
  20871. videojs.VideoTrack = VideoTrack;
  20872. /**
  20873. * Determines, via duck typing, whether or not a value is a DOM element.
  20874. *
  20875. * @borrows dom:isEl as videojs.isEl
  20876. * @deprecated Use videojs.dom.isEl() instead
  20877. */
  20878. /**
  20879. * Determines, via duck typing, whether or not a value is a text node.
  20880. *
  20881. * @borrows dom:isTextNode as videojs.isTextNode
  20882. * @deprecated Use videojs.dom.isTextNode() instead
  20883. */
  20884. /**
  20885. * Creates an element and applies properties.
  20886. *
  20887. * @borrows dom:createEl as videojs.createEl
  20888. * @deprecated Use videojs.dom.createEl() instead
  20889. */
  20890. /**
  20891. * Check if an element has a CSS class
  20892. *
  20893. * @borrows dom:hasElClass as videojs.hasClass
  20894. * @deprecated Use videojs.dom.hasClass() instead
  20895. */
  20896. /**
  20897. * Add a CSS class name to an element
  20898. *
  20899. * @borrows dom:addElClass as videojs.addClass
  20900. * @deprecated Use videojs.dom.addClass() instead
  20901. */
  20902. /**
  20903. * Remove a CSS class name from an element
  20904. *
  20905. * @borrows dom:removeElClass as videojs.removeClass
  20906. * @deprecated Use videojs.dom.removeClass() instead
  20907. */
  20908. /**
  20909. * Adds or removes a CSS class name on an element depending on an optional
  20910. * condition or the presence/absence of the class name.
  20911. *
  20912. * @borrows dom:toggleElClass as videojs.toggleClass
  20913. * @deprecated Use videojs.dom.toggleClass() instead
  20914. */
  20915. /**
  20916. * Apply attributes to an HTML element.
  20917. *
  20918. * @borrows dom:setElAttributes as videojs.setAttribute
  20919. * @deprecated Use videojs.dom.setAttributes() instead
  20920. */
  20921. /**
  20922. * Get an element's attribute values, as defined on the HTML tag
  20923. * Attributes are not the same as properties. They're defined on the tag
  20924. * or with setAttribute (which shouldn't be used with HTML)
  20925. * This will return true or false for boolean attributes.
  20926. *
  20927. * @borrows dom:getElAttributes as videojs.getAttributes
  20928. * @deprecated Use videojs.dom.getAttributes() instead
  20929. */
  20930. /**
  20931. * Empties the contents of an element.
  20932. *
  20933. * @borrows dom:emptyEl as videojs.emptyEl
  20934. * @deprecated Use videojs.dom.emptyEl() instead
  20935. */
  20936. /**
  20937. * Normalizes and appends content to an element.
  20938. *
  20939. * The content for an element can be passed in multiple types and
  20940. * combinations, whose behavior is as follows:
  20941. *
  20942. * - String
  20943. * Normalized into a text node.
  20944. *
  20945. * - Element, TextNode
  20946. * Passed through.
  20947. *
  20948. * - Array
  20949. * A one-dimensional array of strings, elements, nodes, or functions (which
  20950. * return single strings, elements, or nodes).
  20951. *
  20952. * - Function
  20953. * If the sole argument, is expected to produce a string, element,
  20954. * node, or array.
  20955. *
  20956. * @borrows dom:appendContents as videojs.appendContet
  20957. * @deprecated Use videojs.dom.appendContent() instead
  20958. */
  20959. /**
  20960. * Normalizes and inserts content into an element; this is identical to
  20961. * `appendContent()`, except it empties the element first.
  20962. *
  20963. * The content for an element can be passed in multiple types and
  20964. * combinations, whose behavior is as follows:
  20965. *
  20966. * - String
  20967. * Normalized into a text node.
  20968. *
  20969. * - Element, TextNode
  20970. * Passed through.
  20971. *
  20972. * - Array
  20973. * A one-dimensional array of strings, elements, nodes, or functions (which
  20974. * return single strings, elements, or nodes).
  20975. *
  20976. * - Function
  20977. * If the sole argument, is expected to produce a string, element,
  20978. * node, or array.
  20979. *
  20980. * @borrows dom:insertContent as videojs.insertContent
  20981. * @deprecated Use videojs.dom.insertContent() instead
  20982. */
  20983. ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
  20984. videojs[k] = function () {
  20985. log.warn('videojs.' + k + '() is deprecated; use videojs.dom.' + k + '() instead');
  20986. return Dom[k].apply(null, arguments);
  20987. };
  20988. });
  20989. /**
  20990. * A safe getComputedStyle with an IE8 fallback.
  20991. *
  20992. * This is because in Firefox, if the player is loaded in an iframe with `display:none`,
  20993. * then `getComputedStyle` returns `null`, so, we do a null-check to make sure
  20994. * that the player doesn't break in these cases.
  20995. * See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 for more details.
  20996. *
  20997. * @borrows computed-style:computedStyle as videojs.computedStyle
  20998. */
  20999. videojs.computedStyle = computedStyle;
  21000. /**
  21001. * Export the Dom utilities for use in external plugins
  21002. * and Tech's
  21003. */
  21004. videojs.dom = Dom;
  21005. /**
  21006. * Export the Url utilities for use in external plugins
  21007. * and Tech's
  21008. */
  21009. videojs.url = Url;
  21010. export default videojs;