説明なし

video.es.js 1.3MB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680386813868238683386843868538686386873868838689386903869138692386933869438695386963869738698386993870038701387023870338704387053870638707387083870938710387113871238713387143871538716387173871838719387203872138722387233872438725387263872738728387293873038731387323873338734387353873638737387383873938740387413874238743387443874538746387473874838749387503875138752387533875438755387563875738758387593876038761387623876338764387653876638767387683876938770387713877238773387743877538776387773877838779387803878138782387833878438785387863878738788387893879038791387923879338794387953879638797387983879938800388013880238803388043880538806388073880838809388103881138812388133881438815388163881738818388193882038821388223882338824388253882638827388283882938830388313883238833388343883538836388373883838839388403884138842388433884438845388463884738848388493885038851388523885338854388553885638857388583885938860388613886238863388643886538866388673886838869388703887138872388733887438875388763887738878388793888038881388823888338884388853888638887388883888938890388913889238893388943889538896388973889838899389003890138902389033890438905389063890738908389093891038911389123891338914389153891638917389183891938920389213892238923389243892538926389273892838929389303893138932389333893438935389363893738938389393894038941389423894338944389453894638947389483894938950389513895238953389543895538956389573895838959389603896138962389633896438965389663896738968389693897038971389723897338974389753897638977389783897938980389813898238983389843898538986389873898838989389903899138992389933899438995389963899738998389993900039001390023900339004390053900639007390083900939010390113901239013390143901539016390173901839019390203902139022390233902439025390263902739028390293903039031390323903339034390353903639037390383903939040390413904239043390443904539046390473904839049390503905139052390533905439055390563905739058390593906039061390623906339064390653906639067390683906939070390713907239073390743907539076390773907839079390803908139082390833908439085390863908739088390893909039091390923909339094390953909639097390983909939100391013910239103391043910539106391073910839109391103911139112391133911439115391163911739118391193912039121391223912339124391253912639127391283912939130391313913239133391343913539136391373913839139391403914139142391433914439145391463914739148391493915039151391523915339154391553915639157391583915939160391613916239163391643916539166391673916839169391703917139172391733917439175391763917739178391793918039181391823918339184391853918639187391883918939190391913919239193391943919539196391973919839199392003920139202392033920439205392063920739208392093921039211392123921339214392153921639217392183921939220392213922239223392243922539226392273922839229392303923139232392333923439235392363923739238392393924039241392423924339244392453924639247392483924939250392513925239253392543925539256392573925839259392603926139262392633926439265392663926739268392693927039271392723927339274392753927639277392783927939280392813928239283392843928539286392873928839289392903929139292392933929439295392963929739298392993930039301393023930339304393053930639307393083930939310393113931239313393143931539316393173931839319393203932139322393233932439325393263932739328393293933039331393323933339334393353933639337393383933939340393413934239343393443934539346393473934839349393503935139352393533935439355393563935739358393593936039361393623936339364393653936639367393683936939370393713937239373393743937539376393773937839379393803938139382393833938439385393863938739388393893939039391393923939339394393953939639397393983939939400394013940239403394043940539406394073940839409394103941139412394133941439415394163941739418394193942039421394223942339424394253942639427394283942939430394313943239433394343943539436394373943839439394403944139442394433944439445394463944739448394493945039451394523945339454394553945639457394583945939460394613946239463394643946539466394673946839469394703947139472394733947439475394763947739478394793948039481394823948339484394853948639487394883948939490394913949239493394943949539496394973949839499395003950139502395033950439505395063950739508395093951039511395123951339514395153951639517395183951939520395213952239523395243952539526395273952839529395303953139532395333953439535395363953739538395393954039541395423954339544395453954639547395483954939550395513955239553395543955539556395573955839559395603956139562395633956439565395663956739568395693957039571395723957339574395753957639577395783957939580395813958239583395843958539586395873958839589395903959139592395933959439595395963959739598395993960039601396023960339604396053960639607396083960939610396113961239613396143961539616396173961839619396203962139622396233962439625396263962739628396293963039631396323963339634396353963639637396383963939640396413964239643396443964539646396473964839649396503965139652396533965439655396563965739658396593966039661396623966339664396653966639667396683966939670396713967239673396743967539676396773967839679396803968139682396833968439685396863968739688396893969039691396923969339694396953969639697396983969939700397013970239703397043970539706397073970839709397103971139712397133971439715397163971739718397193972039721397223972339724397253972639727397283972939730397313973239733397343973539736397373973839739397403974139742397433974439745397463974739748397493975039751397523975339754397553975639757397583975939760397613976239763397643976539766397673976839769397703977139772397733977439775397763977739778397793978039781397823978339784397853978639787397883978939790397913979239793397943979539796397973979839799398003980139802398033980439805398063980739808398093981039811398123981339814398153981639817398183981939820398213982239823398243982539826398273982839829398303983139832398333983439835398363983739838398393984039841398423984339844398453984639847398483984939850398513985239853398543985539856398573985839859398603986139862398633986439865398663986739868398693987039871398723987339874398753987639877398783987939880398813988239883398843988539886398873988839889398903989139892398933989439895398963989739898398993990039901399023990339904399053990639907399083990939910399113991239913399143991539916399173991839919399203992139922399233992439925399263992739928399293993039931399323993339934399353993639937399383993939940399413994239943399443994539946399473994839949399503995139952399533995439955399563995739958399593996039961399623996339964399653996639967399683996939970399713997239973399743997539976399773997839979399803998139982399833998439985399863998739988399893999039991399923999339994399953999639997399983999940000400014000240003400044000540006400074000840009400104001140012400134001440015400164001740018400194002040021400224002340024400254002640027400284002940030400314003240033400344003540036400374003840039400404004140042400434004440045400464004740048400494005040051400524005340054400554005640057400584005940060400614006240063400644006540066400674006840069400704007140072400734007440075400764007740078400794008040081400824008340084400854008640087400884008940090400914009240093400944009540096400974009840099401004010140102401034010440105401064010740108401094011040111401124011340114401154011640117401184011940120401214012240123401244012540126401274012840129401304013140132401334013440135401364013740138401394014040141401424014340144401454014640147401484014940150401514015240153401544015540156401574015840159401604016140162401634016440165401664016740168401694017040171401724017340174401754017640177401784017940180401814018240183401844018540186401874018840189401904019140192401934019440195401964019740198401994020040201402024020340204402054020640207402084020940210402114021240213402144021540216402174021840219402204022140222402234022440225402264022740228402294023040231402324023340234402354023640237402384023940240402414024240243402444024540246402474024840249402504025140252402534025440255402564025740258402594026040261402624026340264402654026640267402684026940270402714027240273402744027540276402774027840279402804028140282402834028440285402864028740288402894029040291402924029340294402954029640297402984029940300403014030240303403044030540306403074030840309403104031140312403134031440315403164031740318403194032040321403224032340324403254032640327403284032940330403314033240333403344033540336403374033840339403404034140342403434034440345403464034740348403494035040351403524035340354403554035640357403584035940360403614036240363403644036540366403674036840369403704037140372403734037440375403764037740378403794038040381403824038340384403854038640387403884038940390403914039240393403944039540396403974039840399404004040140402404034040440405404064040740408404094041040411404124041340414404154041640417404184041940420404214042240423404244042540426404274042840429404304043140432404334043440435404364043740438404394044040441404424044340444404454044640447404484044940450404514045240453404544045540456404574045840459404604046140462404634046440465404664046740468404694047040471404724047340474404754047640477404784047940480404814048240483404844048540486404874048840489404904049140492404934049440495404964049740498404994050040501405024050340504405054050640507405084050940510405114051240513405144051540516405174051840519405204052140522405234052440525405264052740528405294053040531405324053340534405354053640537405384053940540405414054240543405444054540546405474054840549405504055140552405534055440555405564055740558405594056040561405624056340564405654056640567405684056940570405714057240573405744057540576405774057840579405804058140582405834058440585405864058740588405894059040591405924059340594405954059640597405984059940600406014060240603406044060540606406074060840609406104061140612406134061440615406164061740618406194062040621406224062340624406254062640627406284062940630406314063240633406344063540636406374063840639406404064140642406434064440645406464064740648406494065040651406524065340654406554065640657406584065940660406614066240663406644066540666406674066840669406704067140672406734067440675406764067740678406794068040681406824068340684406854068640687406884068940690406914069240693406944069540696406974069840699407004070140702407034070440705407064070740708407094071040711407124071340714407154071640717407184071940720407214072240723407244072540726407274072840729407304073140732407334073440735407364073740738407394074040741407424074340744407454074640747407484074940750407514075240753407544075540756407574075840759407604076140762407634076440765407664076740768407694077040771407724077340774407754077640777407784077940780407814078240783407844078540786407874078840789407904079140792407934079440795407964079740798407994080040801408024080340804408054080640807408084080940810408114081240813408144081540816408174081840819408204082140822408234082440825408264082740828408294083040831408324083340834408354083640837408384083940840408414084240843408444084540846408474084840849408504085140852408534085440855408564085740858408594086040861408624086340864408654086640867408684086940870408714087240873408744087540876408774087840879408804088140882408834088440885408864088740888408894089040891408924089340894408954089640897408984089940900409014090240903409044090540906409074090840909409104091140912409134091440915409164091740918409194092040921409224092340924409254092640927409284092940930409314093240933409344093540936409374093840939409404094140942409434094440945409464094740948409494095040951409524095340954409554095640957409584095940960409614096240963409644096540966409674096840969409704097140972409734097440975409764097740978409794098040981409824098340984409854098640987409884098940990409914099240993409944099540996409974099840999410004100141002410034100441005410064100741008410094101041011410124101341014410154101641017410184101941020410214102241023410244102541026410274102841029410304103141032410334103441035410364103741038410394104041041410424104341044410454104641047410484104941050410514105241053410544105541056410574105841059410604106141062410634106441065410664106741068410694107041071410724107341074410754107641077410784107941080410814108241083410844108541086410874108841089410904109141092410934109441095410964109741098410994110041101411024110341104411054110641107411084110941110411114111241113411144111541116411174111841119411204112141122411234112441125411264112741128411294113041131411324113341134411354113641137411384113941140411414114241143411444114541146411474114841149411504115141152411534115441155411564115741158411594116041161411624116341164411654116641167411684116941170411714117241173411744117541176411774117841179411804118141182411834118441185411864118741188411894119041191411924119341194411954119641197411984119941200412014120241203412044120541206412074120841209412104121141212412134121441215412164121741218412194122041221412224122341224412254122641227412284122941230412314123241233412344123541236412374123841239412404124141242412434124441245412464124741248412494125041251412524125341254412554125641257412584125941260412614126241263412644126541266412674126841269412704127141272412734127441275412764127741278412794128041281412824128341284412854128641287412884128941290412914129241293412944129541296412974129841299413004130141302413034130441305413064130741308413094131041311413124131341314413154131641317413184131941320413214132241323413244132541326413274132841329413304133141332413334133441335413364133741338413394134041341413424134341344413454134641347413484134941350413514135241353413544135541356413574135841359413604136141362413634136441365413664136741368413694137041371413724137341374413754137641377413784137941380413814138241383413844138541386413874138841389413904139141392413934139441395413964139741398413994140041401414024140341404414054140641407414084140941410414114141241413414144141541416414174141841419414204142141422414234142441425414264142741428414294143041431414324143341434414354143641437414384143941440414414144241443414444144541446414474144841449414504145141452414534145441455414564145741458414594146041461414624146341464414654146641467414684146941470414714147241473414744147541476414774147841479414804148141482414834148441485414864148741488414894149041491414924149341494414954149641497414984149941500415014150241503415044150541506415074150841509415104151141512415134151441515415164151741518415194152041521415224152341524415254152641527415284152941530415314153241533415344153541536415374153841539415404154141542415434154441545415464154741548415494155041551415524155341554415554155641557415584155941560415614156241563415644156541566415674156841569415704157141572415734157441575415764157741578415794158041581415824158341584415854158641587415884158941590415914159241593415944159541596415974159841599416004160141602416034160441605416064160741608416094161041611416124161341614416154161641617416184161941620416214162241623416244162541626416274162841629416304163141632416334163441635416364163741638416394164041641416424164341644416454164641647416484164941650416514165241653416544165541656416574165841659416604166141662416634166441665416664166741668416694167041671416724167341674416754167641677416784167941680416814168241683416844168541686416874168841689416904169141692416934169441695416964169741698416994170041701417024170341704417054170641707417084170941710417114171241713417144171541716417174171841719417204172141722417234172441725417264172741728417294173041731417324173341734417354173641737417384173941740417414174241743417444174541746417474174841749417504175141752417534175441755417564175741758417594176041761417624176341764417654176641767417684176941770417714177241773417744177541776417774177841779417804178141782417834178441785417864178741788417894179041791417924179341794417954179641797417984179941800418014180241803418044180541806418074180841809418104181141812418134181441815418164181741818418194182041821418224182341824418254182641827418284182941830418314183241833418344183541836418374183841839418404184141842418434184441845418464184741848418494185041851418524185341854418554185641857418584185941860418614186241863418644186541866418674186841869418704187141872418734187441875418764187741878418794188041881418824188341884418854188641887418884188941890418914189241893418944189541896418974189841899419004190141902419034190441905419064190741908419094191041911419124191341914419154191641917419184191941920419214192241923419244192541926419274192841929419304193141932419334193441935419364193741938419394194041941419424194341944419454194641947419484194941950419514195241953419544195541956419574195841959419604196141962419634196441965419664196741968419694197041971419724197341974419754197641977419784197941980419814198241983419844198541986419874198841989419904199141992419934199441995419964199741998419994200042001420024200342004420054200642007420084200942010420114201242013420144201542016420174201842019420204202142022420234202442025420264202742028420294203042031420324203342034420354203642037420384203942040420414204242043420444204542046420474204842049420504205142052420534205442055420564205742058420594206042061420624206342064420654206642067420684206942070420714207242073420744207542076420774207842079420804208142082420834208442085420864208742088420894209042091420924209342094420954209642097420984209942100421014210242103421044210542106421074210842109421104211142112421134211442115421164211742118421194212042121421224212342124421254212642127421284212942130421314213242133421344213542136421374213842139421404214142142421434214442145421464214742148421494215042151421524215342154421554215642157421584215942160421614216242163421644216542166421674216842169421704217142172421734217442175421764217742178421794218042181421824218342184421854218642187421884218942190421914219242193421944219542196421974219842199422004220142202422034220442205422064220742208422094221042211422124221342214422154221642217422184221942220422214222242223422244222542226422274222842229422304223142232422334223442235422364223742238422394224042241422424224342244422454224642247422484224942250422514225242253422544225542256422574225842259422604226142262422634226442265422664226742268422694227042271422724227342274422754227642277422784227942280422814228242283422844228542286422874228842289422904229142292422934229442295422964229742298422994230042301423024230342304423054230642307423084230942310423114231242313423144231542316423174231842319423204232142322423234232442325423264232742328423294233042331423324233342334423354233642337423384233942340423414234242343423444234542346423474234842349423504235142352423534235442355423564235742358423594236042361423624236342364423654236642367423684236942370423714237242373423744237542376423774237842379423804238142382423834238442385423864238742388423894239042391423924239342394423954239642397423984239942400424014240242403424044240542406424074240842409424104241142412424134241442415424164241742418424194242042421424224242342424424254242642427424284242942430424314243242433424344243542436424374243842439424404244142442424434244442445424464244742448424494245042451424524245342454424554245642457424584245942460424614246242463424644246542466424674246842469424704247142472424734247442475424764247742478424794248042481424824248342484424854248642487424884248942490424914249242493424944249542496424974249842499425004250142502425034250442505425064250742508425094251042511425124251342514425154251642517425184251942520425214252242523425244252542526425274252842529425304253142532425334253442535425364253742538425394254042541425424254342544425454254642547425484254942550425514255242553425544255542556425574255842559425604256142562425634256442565425664256742568425694257042571425724257342574425754257642577425784257942580425814258242583425844258542586425874258842589425904259142592425934259442595425964259742598425994260042601426024260342604426054260642607426084260942610426114261242613426144261542616426174261842619426204262142622426234262442625426264262742628426294263042631426324263342634426354263642637426384263942640426414264242643426444264542646426474264842649426504265142652426534265442655426564265742658426594266042661426624266342664426654266642667426684266942670426714267242673426744267542676426774267842679426804268142682426834268442685426864268742688426894269042691426924269342694426954269642697426984269942700427014270242703427044270542706427074270842709427104271142712427134271442715427164271742718427194272042721427224272342724427254272642727427284272942730427314273242733427344273542736427374273842739427404274142742427434274442745427464274742748427494275042751427524275342754427554275642757427584275942760427614276242763427644276542766427674276842769427704277142772427734277442775427764277742778427794278042781427824278342784427854278642787427884278942790427914279242793427944279542796427974279842799428004280142802428034280442805428064280742808428094281042811428124281342814428154281642817428184281942820428214282242823428244282542826428274282842829428304283142832428334283442835428364283742838428394284042841428424284342844428454284642847428484284942850428514285242853428544285542856428574285842859428604286142862428634286442865428664286742868428694287042871428724287342874428754287642877428784287942880428814288242883428844288542886428874288842889428904289142892428934289442895428964289742898428994290042901429024290342904429054290642907429084290942910429114291242913429144291542916429174291842919429204292142922429234292442925429264292742928429294293042931429324293342934429354293642937429384293942940429414294242943429444294542946429474294842949429504295142952429534295442955429564295742958429594296042961429624296342964429654296642967429684296942970429714297242973429744297542976429774297842979429804298142982429834298442985429864298742988429894299042991429924299342994429954299642997429984299943000430014300243003430044300543006430074300843009430104301143012430134301443015430164301743018430194302043021430224302343024430254302643027430284302943030430314303243033430344303543036430374303843039430404304143042430434304443045430464304743048430494305043051430524305343054430554305643057430584305943060430614306243063430644306543066430674306843069430704307143072430734307443075430764307743078430794308043081430824308343084430854308643087430884308943090430914309243093430944309543096430974309843099431004310143102431034310443105431064310743108431094311043111431124311343114431154311643117431184311943120431214312243123431244312543126431274312843129431304313143132431334313443135431364313743138431394314043141431424314343144431454314643147431484314943150431514315243153431544315543156431574315843159431604316143162431634316443165431664316743168431694317043171431724317343174431754317643177431784317943180431814318243183431844318543186431874318843189431904319143192431934319443195431964319743198431994320043201432024320343204432054320643207432084320943210432114321243213432144321543216432174321843219432204322143222432234322443225432264322743228432294323043231432324323343234432354323643237432384323943240432414324243243432444324543246432474324843249432504325143252432534325443255432564325743258432594326043261432624326343264432654326643267432684326943270432714327243273432744327543276432774327843279432804328143282432834328443285432864328743288432894329043291432924329343294432954329643297432984329943300433014330243303433044330543306433074330843309433104331143312433134331443315433164331743318433194332043321433224332343324433254332643327433284332943330433314333243333433344333543336433374333843339433404334143342433434334443345433464334743348433494335043351433524335343354433554335643357433584335943360433614336243363433644336543366433674336843369433704337143372433734337443375433764337743378433794338043381433824338343384433854338643387433884338943390433914339243393433944339543396433974339843399434004340143402434034340443405434064340743408434094341043411434124341343414434154341643417434184341943420434214342243423434244342543426434274342843429434304343143432434334343443435434364343743438434394344043441434424344343444434454344643447434484344943450434514345243453434544345543456434574345843459434604346143462434634346443465434664346743468434694347043471434724347343474434754347643477434784347943480434814348243483434844348543486434874348843489434904349143492434934349443495434964349743498434994350043501435024350343504435054350643507435084350943510435114351243513435144351543516435174351843519435204352143522435234352443525435264352743528435294353043531435324353343534435354353643537435384353943540435414354243543435444354543546435474354843549435504355143552435534355443555435564355743558435594356043561435624356343564435654356643567435684356943570435714357243573435744357543576435774357843579435804358143582435834358443585435864358743588435894359043591435924359343594435954359643597435984359943600436014360243603436044360543606436074360843609436104361143612436134361443615436164361743618436194362043621436224362343624436254362643627436284362943630436314363243633436344363543636436374363843639436404364143642436434364443645436464364743648436494365043651436524365343654436554365643657436584365943660436614366243663436644366543666436674366843669436704367143672436734367443675436764367743678436794368043681436824368343684436854368643687436884368943690436914369243693436944369543696436974369843699437004370143702437034370443705437064370743708437094371043711437124371343714437154371643717437184371943720437214372243723437244372543726437274372843729437304373143732437334373443735437364373743738437394374043741437424374343744437454374643747437484374943750437514375243753437544375543756437574375843759437604376143762437634376443765437664376743768437694377043771437724377343774437754377643777437784377943780437814378243783437844378543786437874378843789437904379143792437934379443795437964379743798437994380043801438024380343804438054380643807438084380943810438114381243813438144381543816438174381843819438204382143822438234382443825438264382743828438294383043831438324383343834438354383643837438384383943840438414384243843438444384543846438474384843849438504385143852438534385443855438564385743858438594386043861438624386343864438654386643867438684386943870438714387243873438744387543876438774387843879438804388143882438834388443885438864388743888438894389043891438924389343894438954389643897438984389943900439014390243903439044390543906439074390843909439104391143912439134391443915439164391743918439194392043921439224392343924439254392643927439284392943930439314393243933439344393543936439374393843939439404394143942439434394443945439464394743948439494395043951439524395343954439554395643957439584395943960439614396243963439644396543966439674396843969439704397143972439734397443975439764397743978439794398043981439824398343984439854398643987439884398943990439914399243993439944399543996439974399843999440004400144002440034400444005440064400744008440094401044011440124401344014440154401644017440184401944020440214402244023440244402544026440274402844029440304403144032440334403444035440364403744038440394404044041440424404344044440454404644047440484404944050440514405244053440544405544056440574405844059440604406144062440634406444065440664406744068440694407044071440724407344074440754407644077440784407944080440814408244083440844408544086440874408844089440904409144092440934409444095440964409744098440994410044101441024410344104441054410644107441084410944110441114411244113441144411544116441174411844119441204412144122441234412444125441264412744128441294413044131441324413344134441354413644137441384413944140441414414244143441444414544146441474414844149441504415144152441534415444155441564415744158441594416044161441624416344164441654416644167441684416944170441714417244173441744417544176441774417844179441804418144182441834418444185441864418744188441894419044191441924419344194441954419644197441984419944200442014420244203442044420544206442074420844209442104421144212442134421444215442164421744218442194422044221442224422344224442254422644227442284422944230442314423244233442344423544236442374423844239442404424144242442434424444245442464424744248442494425044251442524425344254442554425644257442584425944260442614426244263442644426544266442674426844269442704427144272442734427444275442764427744278442794428044281442824428344284442854428644287442884428944290442914429244293442944429544296442974429844299443004430144302443034430444305443064430744308443094431044311443124431344314443154431644317443184431944320443214432244323443244432544326443274432844329443304433144332443334433444335443364433744338443394434044341443424434344344443454434644347443484434944350443514435244353443544435544356443574435844359443604436144362443634436444365443664436744368443694437044371443724437344374443754437644377443784437944380443814438244383443844438544386443874438844389443904439144392443934439444395443964439744398443994440044401444024440344404444054440644407444084440944410444114441244413444144441544416444174441844419444204442144422444234442444425444264442744428444294443044431444324443344434444354443644437444384443944440444414444244443444444444544446444474444844449444504445144452444534445444455444564445744458444594446044461444624446344464444654446644467444684446944470444714447244473444744447544476444774447844479444804448144482444834448444485444864448744488444894449044491444924449344494444954449644497444984449944500445014450244503445044450544506445074450844509445104451144512445134451444515445164451744518445194452044521445224452344524445254452644527445284452944530445314453244533445344453544536445374453844539445404454144542445434454444545445464454744548445494455044551445524455344554445554455644557445584455944560445614456244563445644456544566445674456844569445704457144572445734457444575445764457744578445794458044581445824458344584445854458644587445884458944590445914459244593445944459544596445974459844599446004460144602446034460444605446064460744608446094461044611446124461344614446154461644617446184461944620446214462244623446244462544626446274462844629446304463144632446334463444635446364463744638446394464044641446424464344644446454464644647446484464944650446514465244653446544465544656446574465844659446604466144662446634466444665446664466744668446694467044671446724467344674446754467644677446784467944680446814468244683446844468544686446874468844689446904469144692446934469444695446964469744698446994470044701447024470344704447054470644707447084470944710447114471244713447144471544716447174471844719447204472144722447234472444725447264472744728447294473044731447324473344734447354473644737447384473944740447414474244743447444474544746447474474844749447504475144752447534475444755447564475744758447594476044761447624476344764447654476644767447684476944770447714477244773447744477544776447774477844779447804478144782447834478444785447864478744788447894479044791447924479344794447954479644797447984479944800448014480244803448044480544806448074480844809448104481144812448134481444815448164481744818448194482044821448224482344824448254482644827448284482944830448314483244833448344483544836448374483844839448404484144842448434484444845448464484744848448494485044851448524485344854448554485644857448584485944860448614486244863448644486544866448674486844869448704487144872448734487444875448764487744878448794488044881448824488344884448854488644887448884488944890448914489244893448944489544896448974489844899449004490144902449034490444905449064490744908449094491044911449124491344914449154491644917449184491944920449214492244923449244492544926449274492844929449304493144932449334493444935449364493744938449394494044941449424494344944449454494644947449484494944950449514495244953449544495544956449574495844959449604496144962449634496444965449664496744968449694497044971449724497344974449754497644977449784497944980449814498244983449844498544986449874498844989449904499144992449934499444995449964499744998449994500045001450024500345004450054500645007450084500945010450114501245013450144501545016450174501845019450204502145022450234502445025450264502745028450294503045031450324503345034450354503645037450384503945040450414504245043450444504545046450474504845049450504505145052450534505445055450564505745058450594506045061450624506345064450654506645067450684506945070450714507245073450744507545076450774507845079450804508145082450834508445085450864508745088450894509045091450924509345094450954509645097450984509945100451014510245103451044510545106451074510845109451104511145112451134511445115451164511745118451194512045121451224512345124451254512645127451284512945130451314513245133451344513545136451374513845139451404514145142451434514445145451464514745148451494515045151451524515345154451554515645157451584515945160451614516245163451644516545166451674516845169451704517145172451734517445175451764517745178451794518045181451824518345184451854518645187451884518945190451914519245193451944519545196451974519845199452004520145202452034520445205452064520745208452094521045211452124521345214452154521645217452184521945220452214522245223452244522545226452274522845229452304523145232452334523445235452364523745238452394524045241452424524345244452454524645247452484524945250452514525245253452544525545256452574525845259452604526145262452634526445265452664526745268452694527045271452724527345274452754527645277452784527945280452814528245283452844528545286452874528845289452904529145292452934529445295452964529745298452994530045301453024530345304453054530645307453084530945310453114531245313453144531545316453174531845319453204532145322453234532445325453264532745328453294533045331453324533345334453354533645337453384533945340453414534245343453444534545346453474534845349453504535145352453534535445355453564535745358453594536045361453624536345364453654536645367453684536945370453714537245373453744537545376453774537845379
  1. /**
  2. * @license
  3. * Video.js 7.5.4 <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$1 from 'global/window';
  13. import document from 'global/document';
  14. import tsml from 'tsml';
  15. import safeParseTuple from 'safe-json-parse/tuple';
  16. import keycode from 'keycode';
  17. import xhr from 'xhr';
  18. import vtt from 'videojs-vtt.js';
  19. import URLToolkit from 'url-toolkit';
  20. import { Parser } from 'm3u8-parser';
  21. import { parse, parseUTCTiming } from 'mpd-parser';
  22. import mp4Inspector from 'mux.js/lib/tools/mp4-inspector';
  23. import mp4probe from 'mux.js/lib/mp4/probe';
  24. import { CaptionParser } from 'mux.js/lib/mp4';
  25. import tsInspector from 'mux.js/lib/tools/ts-inspector.js';
  26. import { Decrypter, AsyncStream, decrypt } from 'aes-decrypter';
  27. var version = "7.5.4";
  28. function _inheritsLoose(subClass, superClass) {
  29. subClass.prototype = Object.create(superClass.prototype);
  30. subClass.prototype.constructor = subClass;
  31. subClass.__proto__ = superClass;
  32. }
  33. function _setPrototypeOf(o, p) {
  34. _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
  35. o.__proto__ = p;
  36. return o;
  37. };
  38. return _setPrototypeOf(o, p);
  39. }
  40. function isNativeReflectConstruct() {
  41. if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  42. if (Reflect.construct.sham) return false;
  43. if (typeof Proxy === "function") return true;
  44. try {
  45. Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
  46. return true;
  47. } catch (e) {
  48. return false;
  49. }
  50. }
  51. function _construct(Parent, args, Class) {
  52. if (isNativeReflectConstruct()) {
  53. _construct = Reflect.construct;
  54. } else {
  55. _construct = function _construct(Parent, args, Class) {
  56. var a = [null];
  57. a.push.apply(a, args);
  58. var Constructor = Function.bind.apply(Parent, a);
  59. var instance = new Constructor();
  60. if (Class) _setPrototypeOf(instance, Class.prototype);
  61. return instance;
  62. };
  63. }
  64. return _construct.apply(null, arguments);
  65. }
  66. function _assertThisInitialized(self) {
  67. if (self === void 0) {
  68. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  69. }
  70. return self;
  71. }
  72. function _taggedTemplateLiteralLoose(strings, raw) {
  73. if (!raw) {
  74. raw = strings.slice(0);
  75. }
  76. strings.raw = raw;
  77. return strings;
  78. }
  79. /**
  80. * @file create-logger.js
  81. * @module create-logger
  82. */
  83. var history = [];
  84. /**
  85. * Log messages to the console and history based on the type of message
  86. *
  87. * @private
  88. * @param {string} type
  89. * The name of the console method to use.
  90. *
  91. * @param {Array} args
  92. * The arguments to be passed to the matching console method.
  93. */
  94. var LogByTypeFactory = function LogByTypeFactory(name, log) {
  95. return function (type, level, args) {
  96. var lvl = log.levels[level];
  97. var lvlRegExp = new RegExp("^(" + lvl + ")$");
  98. if (type !== 'log') {
  99. // Add the type to the front of the message when it's not "log".
  100. args.unshift(type.toUpperCase() + ':');
  101. } // Add console prefix after adding to history.
  102. args.unshift(name + ':'); // Add a clone of the args at this point to history.
  103. if (history) {
  104. history.push([].concat(args));
  105. } // If there's no console then don't try to output messages, but they will
  106. // still be stored in history.
  107. if (!window$1.console) {
  108. return;
  109. } // Was setting these once outside of this function, but containing them
  110. // in the function makes it easier to test cases where console doesn't exist
  111. // when the module is executed.
  112. var fn = window$1.console[type];
  113. if (!fn && type === 'debug') {
  114. // Certain browsers don't have support for console.debug. For those, we
  115. // should default to the closest comparable log.
  116. fn = window$1.console.info || window$1.console.log;
  117. } // Bail out if there's no console or if this type is not allowed by the
  118. // current logging level.
  119. if (!fn || !lvl || !lvlRegExp.test(type)) {
  120. return;
  121. }
  122. fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args);
  123. };
  124. };
  125. function createLogger(name) {
  126. // This is the private tracking variable for logging level.
  127. var level = 'info'; // the curried logByType bound to the specific log and history
  128. var logByType;
  129. /**
  130. * Logs plain debug messages. Similar to `console.log`.
  131. *
  132. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  133. * of our JSDoc template, we cannot properly document this as both a function
  134. * and a namespace, so its function signature is documented here.
  135. *
  136. * #### Arguments
  137. * ##### *args
  138. * Mixed[]
  139. *
  140. * Any combination of values that could be passed to `console.log()`.
  141. *
  142. * #### Return Value
  143. *
  144. * `undefined`
  145. *
  146. * @namespace
  147. * @param {Mixed[]} args
  148. * One or more messages or objects that should be logged.
  149. */
  150. var log = function log() {
  151. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  152. args[_key] = arguments[_key];
  153. }
  154. logByType('log', level, args);
  155. }; // This is the logByType helper that the logging methods below use
  156. logByType = LogByTypeFactory(name, log);
  157. /**
  158. * Create a new sublogger which chains the old name to the new name.
  159. *
  160. * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
  161. * ```js
  162. * mylogger('foo');
  163. * // > VIDEOJS: player: foo
  164. * ```
  165. *
  166. * @param {string} name
  167. * The name to add call the new logger
  168. * @return {Object}
  169. */
  170. log.createLogger = function (subname) {
  171. return createLogger(name + ': ' + subname);
  172. };
  173. /**
  174. * Enumeration of available logging levels, where the keys are the level names
  175. * and the values are `|`-separated strings containing logging methods allowed
  176. * in that logging level. These strings are used to create a regular expression
  177. * matching the function name being called.
  178. *
  179. * Levels provided by Video.js are:
  180. *
  181. * - `off`: Matches no calls. Any value that can be cast to `false` will have
  182. * this effect. The most restrictive.
  183. * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
  184. * `log.warn`, and `log.error`).
  185. * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
  186. * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
  187. * - `warn`: Matches `log.warn` and `log.error` calls.
  188. * - `error`: Matches only `log.error` calls.
  189. *
  190. * @type {Object}
  191. */
  192. log.levels = {
  193. all: 'debug|log|warn|error',
  194. off: '',
  195. debug: 'debug|log|warn|error',
  196. info: 'log|warn|error',
  197. warn: 'warn|error',
  198. error: 'error',
  199. DEFAULT: level
  200. };
  201. /**
  202. * Get or set the current logging level.
  203. *
  204. * If a string matching a key from {@link module:log.levels} is provided, acts
  205. * as a setter.
  206. *
  207. * @param {string} [lvl]
  208. * Pass a valid level to set a new logging level.
  209. *
  210. * @return {string}
  211. * The current logging level.
  212. */
  213. log.level = function (lvl) {
  214. if (typeof lvl === 'string') {
  215. if (!log.levels.hasOwnProperty(lvl)) {
  216. throw new Error("\"" + lvl + "\" in not a valid log level");
  217. }
  218. level = lvl;
  219. }
  220. return level;
  221. };
  222. /**
  223. * Returns an array containing everything that has been logged to the history.
  224. *
  225. * This array is a shallow clone of the internal history record. However, its
  226. * contents are _not_ cloned; so, mutating objects inside this array will
  227. * mutate them in history.
  228. *
  229. * @return {Array}
  230. */
  231. log.history = function () {
  232. return history ? [].concat(history) : [];
  233. };
  234. /**
  235. * Allows you to filter the history by the given logger name
  236. *
  237. * @param {string} fname
  238. * The name to filter by
  239. *
  240. * @return {Array}
  241. * The filtered list to return
  242. */
  243. log.history.filter = function (fname) {
  244. return (history || []).filter(function (historyItem) {
  245. // if the first item in each historyItem includes `fname`, then it's a match
  246. return new RegExp(".*" + fname + ".*").test(historyItem[0]);
  247. });
  248. };
  249. /**
  250. * Clears the internal history tracking, but does not prevent further history
  251. * tracking.
  252. */
  253. log.history.clear = function () {
  254. if (history) {
  255. history.length = 0;
  256. }
  257. };
  258. /**
  259. * Disable history tracking if it is currently enabled.
  260. */
  261. log.history.disable = function () {
  262. if (history !== null) {
  263. history.length = 0;
  264. history = null;
  265. }
  266. };
  267. /**
  268. * Enable history tracking if it is currently disabled.
  269. */
  270. log.history.enable = function () {
  271. if (history === null) {
  272. history = [];
  273. }
  274. };
  275. /**
  276. * Logs error messages. Similar to `console.error`.
  277. *
  278. * @param {Mixed[]} args
  279. * One or more messages or objects that should be logged as an error
  280. */
  281. log.error = function () {
  282. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  283. args[_key2] = arguments[_key2];
  284. }
  285. return logByType('error', level, args);
  286. };
  287. /**
  288. * Logs warning messages. Similar to `console.warn`.
  289. *
  290. * @param {Mixed[]} args
  291. * One or more messages or objects that should be logged as a warning.
  292. */
  293. log.warn = function () {
  294. for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  295. args[_key3] = arguments[_key3];
  296. }
  297. return logByType('warn', level, args);
  298. };
  299. /**
  300. * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
  301. * log if `console.debug` is not available
  302. *
  303. * @param {Mixed[]} args
  304. * One or more messages or objects that should be logged as debug.
  305. */
  306. log.debug = function () {
  307. for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
  308. args[_key4] = arguments[_key4];
  309. }
  310. return logByType('debug', level, args);
  311. };
  312. return log;
  313. }
  314. /**
  315. * @file log.js
  316. * @module log
  317. */
  318. var log = createLogger('VIDEOJS');
  319. var createLogger$1 = log.createLogger;
  320. /**
  321. * @file obj.js
  322. * @module obj
  323. */
  324. /**
  325. * @callback obj:EachCallback
  326. *
  327. * @param {Mixed} value
  328. * The current key for the object that is being iterated over.
  329. *
  330. * @param {string} key
  331. * The current key-value for object that is being iterated over
  332. */
  333. /**
  334. * @callback obj:ReduceCallback
  335. *
  336. * @param {Mixed} accum
  337. * The value that is accumulating over the reduce loop.
  338. *
  339. * @param {Mixed} value
  340. * The current key for the object that is being iterated over.
  341. *
  342. * @param {string} key
  343. * The current key-value for object that is being iterated over
  344. *
  345. * @return {Mixed}
  346. * The new accumulated value.
  347. */
  348. var toString = Object.prototype.toString;
  349. /**
  350. * Get the keys of an Object
  351. *
  352. * @param {Object}
  353. * The Object to get the keys from
  354. *
  355. * @return {string[]}
  356. * An array of the keys from the object. Returns an empty array if the
  357. * object passed in was invalid or had no keys.
  358. *
  359. * @private
  360. */
  361. var keys = function keys(object) {
  362. return isObject(object) ? Object.keys(object) : [];
  363. };
  364. /**
  365. * Array-like iteration for objects.
  366. *
  367. * @param {Object} object
  368. * The object to iterate over
  369. *
  370. * @param {obj:EachCallback} fn
  371. * The callback function which is called for each key in the object.
  372. */
  373. function each(object, fn) {
  374. keys(object).forEach(function (key) {
  375. return fn(object[key], key);
  376. });
  377. }
  378. /**
  379. * Array-like reduce for objects.
  380. *
  381. * @param {Object} object
  382. * The Object that you want to reduce.
  383. *
  384. * @param {Function} fn
  385. * A callback function which is called for each key in the object. It
  386. * receives the accumulated value and the per-iteration value and key
  387. * as arguments.
  388. *
  389. * @param {Mixed} [initial = 0]
  390. * Starting value
  391. *
  392. * @return {Mixed}
  393. * The final accumulated value.
  394. */
  395. function reduce(object, fn, initial) {
  396. if (initial === void 0) {
  397. initial = 0;
  398. }
  399. return keys(object).reduce(function (accum, key) {
  400. return fn(accum, object[key], key);
  401. }, initial);
  402. }
  403. /**
  404. * Object.assign-style object shallow merge/extend.
  405. *
  406. * @param {Object} target
  407. * @param {Object} ...sources
  408. * @return {Object}
  409. */
  410. function assign(target) {
  411. for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  412. sources[_key - 1] = arguments[_key];
  413. }
  414. if (Object.assign) {
  415. return Object.assign.apply(Object, [target].concat(sources));
  416. }
  417. sources.forEach(function (source) {
  418. if (!source) {
  419. return;
  420. }
  421. each(source, function (value, key) {
  422. target[key] = value;
  423. });
  424. });
  425. return target;
  426. }
  427. /**
  428. * Returns whether a value is an object of any kind - including DOM nodes,
  429. * arrays, regular expressions, etc. Not functions, though.
  430. *
  431. * This avoids the gotcha where using `typeof` on a `null` value
  432. * results in `'object'`.
  433. *
  434. * @param {Object} value
  435. * @return {boolean}
  436. */
  437. function isObject(value) {
  438. return !!value && typeof value === 'object';
  439. }
  440. /**
  441. * Returns whether an object appears to be a "plain" object - that is, a
  442. * direct instance of `Object`.
  443. *
  444. * @param {Object} value
  445. * @return {boolean}
  446. */
  447. function isPlain(value) {
  448. return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
  449. }
  450. /**
  451. * @file computed-style.js
  452. * @module computed-style
  453. */
  454. /**
  455. * A safe getComputedStyle.
  456. *
  457. * This is needed because in Firefox, if the player is loaded in an iframe with
  458. * `display:none`, then `getComputedStyle` returns `null`, so, we do a
  459. * null-check to make sure that the player doesn't break in these cases.
  460. *
  461. * @function
  462. * @param {Element} el
  463. * The element you want the computed style of
  464. *
  465. * @param {string} prop
  466. * The property name you want
  467. *
  468. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  469. */
  470. function computedStyle(el, prop) {
  471. if (!el || !prop) {
  472. return '';
  473. }
  474. if (typeof window$1.getComputedStyle === 'function') {
  475. var cs = window$1.getComputedStyle(el);
  476. return cs ? cs[prop] : '';
  477. }
  478. return '';
  479. }
  480. function _templateObject() {
  481. var data = _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 ", "."]);
  482. _templateObject = function _templateObject() {
  483. return data;
  484. };
  485. return data;
  486. }
  487. /**
  488. * Detect if a value is a string with any non-whitespace characters.
  489. *
  490. * @private
  491. * @param {string} str
  492. * The string to check
  493. *
  494. * @return {boolean}
  495. * Will be `true` if the string is non-blank, `false` otherwise.
  496. *
  497. */
  498. function isNonBlankString(str) {
  499. return typeof str === 'string' && /\S/.test(str);
  500. }
  501. /**
  502. * Throws an error if the passed string has whitespace. This is used by
  503. * class methods to be relatively consistent with the classList API.
  504. *
  505. * @private
  506. * @param {string} str
  507. * The string to check for whitespace.
  508. *
  509. * @throws {Error}
  510. * Throws an error if there is whitespace in the string.
  511. */
  512. function throwIfWhitespace(str) {
  513. if (/\s/.test(str)) {
  514. throw new Error('class has illegal whitespace characters');
  515. }
  516. }
  517. /**
  518. * Produce a regular expression for matching a className within an elements className.
  519. *
  520. * @private
  521. * @param {string} className
  522. * The className to generate the RegExp for.
  523. *
  524. * @return {RegExp}
  525. * The RegExp that will check for a specific `className` in an elements
  526. * className.
  527. */
  528. function classRegExp(className) {
  529. return new RegExp('(^|\\s)' + className + '($|\\s)');
  530. }
  531. /**
  532. * Whether the current DOM interface appears to be real (i.e. not simulated).
  533. *
  534. * @return {boolean}
  535. * Will be `true` if the DOM appears to be real, `false` otherwise.
  536. */
  537. function isReal() {
  538. // Both document and window will never be undefined thanks to `global`.
  539. return document === window$1.document;
  540. }
  541. /**
  542. * Determines, via duck typing, whether or not a value is a DOM element.
  543. *
  544. * @param {Mixed} value
  545. * The value to check.
  546. *
  547. * @return {boolean}
  548. * Will be `true` if the value is a DOM element, `false` otherwise.
  549. */
  550. function isEl(value) {
  551. return isObject(value) && value.nodeType === 1;
  552. }
  553. /**
  554. * Determines if the current DOM is embedded in an iframe.
  555. *
  556. * @return {boolean}
  557. * Will be `true` if the DOM is embedded in an iframe, `false`
  558. * otherwise.
  559. */
  560. function isInFrame() {
  561. // We need a try/catch here because Safari will throw errors when attempting
  562. // to get either `parent` or `self`
  563. try {
  564. return window$1.parent !== window$1.self;
  565. } catch (x) {
  566. return true;
  567. }
  568. }
  569. /**
  570. * Creates functions to query the DOM using a given method.
  571. *
  572. * @private
  573. * @param {string} method
  574. * The method to create the query with.
  575. *
  576. * @return {Function}
  577. * The query method
  578. */
  579. function createQuerier(method) {
  580. return function (selector, context) {
  581. if (!isNonBlankString(selector)) {
  582. return document[method](null);
  583. }
  584. if (isNonBlankString(context)) {
  585. context = document.querySelector(context);
  586. }
  587. var ctx = isEl(context) ? context : document;
  588. return ctx[method] && ctx[method](selector);
  589. };
  590. }
  591. /**
  592. * Creates an element and applies properties, attributes, and inserts content.
  593. *
  594. * @param {string} [tagName='div']
  595. * Name of tag to be created.
  596. *
  597. * @param {Object} [properties={}]
  598. * Element properties to be applied.
  599. *
  600. * @param {Object} [attributes={}]
  601. * Element attributes to be applied.
  602. *
  603. * @param {module:dom~ContentDescriptor} content
  604. * A content descriptor object.
  605. *
  606. * @return {Element}
  607. * The element that was created.
  608. */
  609. function createEl(tagName, properties, attributes, content) {
  610. if (tagName === void 0) {
  611. tagName = 'div';
  612. }
  613. if (properties === void 0) {
  614. properties = {};
  615. }
  616. if (attributes === void 0) {
  617. attributes = {};
  618. }
  619. var el = document.createElement(tagName);
  620. Object.getOwnPropertyNames(properties).forEach(function (propName) {
  621. var val = properties[propName]; // See #2176
  622. // We originally were accepting both properties and attributes in the
  623. // same object, but that doesn't work so well.
  624. if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
  625. log.warn(tsml(_templateObject(), propName, val));
  626. el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a
  627. // method for it.
  628. } else if (propName === 'textContent') {
  629. textContent(el, val);
  630. } else {
  631. el[propName] = val;
  632. }
  633. });
  634. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  635. el.setAttribute(attrName, attributes[attrName]);
  636. });
  637. if (content) {
  638. appendContent(el, content);
  639. }
  640. return el;
  641. }
  642. /**
  643. * Injects text into an element, replacing any existing contents entirely.
  644. *
  645. * @param {Element} el
  646. * The element to add text content into
  647. *
  648. * @param {string} text
  649. * The text content to add.
  650. *
  651. * @return {Element}
  652. * The element with added text content.
  653. */
  654. function textContent(el, text) {
  655. if (typeof el.textContent === 'undefined') {
  656. el.innerText = text;
  657. } else {
  658. el.textContent = text;
  659. }
  660. return el;
  661. }
  662. /**
  663. * Insert an element as the first child node of another
  664. *
  665. * @param {Element} child
  666. * Element to insert
  667. *
  668. * @param {Element} parent
  669. * Element to insert child into
  670. */
  671. function prependTo(child, parent) {
  672. if (parent.firstChild) {
  673. parent.insertBefore(child, parent.firstChild);
  674. } else {
  675. parent.appendChild(child);
  676. }
  677. }
  678. /**
  679. * Check if an element has a class name.
  680. *
  681. * @param {Element} element
  682. * Element to check
  683. *
  684. * @param {string} classToCheck
  685. * Class name to check for
  686. *
  687. * @return {boolean}
  688. * Will be `true` if the element has a class, `false` otherwise.
  689. *
  690. * @throws {Error}
  691. * Throws an error if `classToCheck` has white space.
  692. */
  693. function hasClass(element, classToCheck) {
  694. throwIfWhitespace(classToCheck);
  695. if (element.classList) {
  696. return element.classList.contains(classToCheck);
  697. }
  698. return classRegExp(classToCheck).test(element.className);
  699. }
  700. /**
  701. * Add a class name to an element.
  702. *
  703. * @param {Element} element
  704. * Element to add class name to.
  705. *
  706. * @param {string} classToAdd
  707. * Class name to add.
  708. *
  709. * @return {Element}
  710. * The DOM element with the added class name.
  711. */
  712. function addClass(element, classToAdd) {
  713. if (element.classList) {
  714. element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
  715. // in the case of classList not being supported.
  716. } else if (!hasClass(element, classToAdd)) {
  717. element.className = (element.className + ' ' + classToAdd).trim();
  718. }
  719. return element;
  720. }
  721. /**
  722. * Remove a class name from an element.
  723. *
  724. * @param {Element} element
  725. * Element to remove a class name from.
  726. *
  727. * @param {string} classToRemove
  728. * Class name to remove
  729. *
  730. * @return {Element}
  731. * The DOM element with class name removed.
  732. */
  733. function removeClass(element, classToRemove) {
  734. if (element.classList) {
  735. element.classList.remove(classToRemove);
  736. } else {
  737. throwIfWhitespace(classToRemove);
  738. element.className = element.className.split(/\s+/).filter(function (c) {
  739. return c !== classToRemove;
  740. }).join(' ');
  741. }
  742. return element;
  743. }
  744. /**
  745. * The callback definition for toggleClass.
  746. *
  747. * @callback module:dom~PredicateCallback
  748. * @param {Element} element
  749. * The DOM element of the Component.
  750. *
  751. * @param {string} classToToggle
  752. * The `className` that wants to be toggled
  753. *
  754. * @return {boolean|undefined}
  755. * If `true` is returned, the `classToToggle` will be added to the
  756. * `element`. If `false`, the `classToToggle` will be removed from
  757. * the `element`. If `undefined`, the callback will be ignored.
  758. */
  759. /**
  760. * Adds or removes a class name to/from an element depending on an optional
  761. * condition or the presence/absence of the class name.
  762. *
  763. * @param {Element} element
  764. * The element to toggle a class name on.
  765. *
  766. * @param {string} classToToggle
  767. * The class that should be toggled.
  768. *
  769. * @param {boolean|module:dom~PredicateCallback} [predicate]
  770. * See the return value for {@link module:dom~PredicateCallback}
  771. *
  772. * @return {Element}
  773. * The element with a class that has been toggled.
  774. */
  775. function toggleClass(element, classToToggle, predicate) {
  776. // This CANNOT use `classList` internally because IE11 does not support the
  777. // second parameter to the `classList.toggle()` method! Which is fine because
  778. // `classList` will be used by the add/remove functions.
  779. var has = hasClass(element, classToToggle);
  780. if (typeof predicate === 'function') {
  781. predicate = predicate(element, classToToggle);
  782. }
  783. if (typeof predicate !== 'boolean') {
  784. predicate = !has;
  785. } // If the necessary class operation matches the current state of the
  786. // element, no action is required.
  787. if (predicate === has) {
  788. return;
  789. }
  790. if (predicate) {
  791. addClass(element, classToToggle);
  792. } else {
  793. removeClass(element, classToToggle);
  794. }
  795. return element;
  796. }
  797. /**
  798. * Apply attributes to an HTML element.
  799. *
  800. * @param {Element} el
  801. * Element to add attributes to.
  802. *
  803. * @param {Object} [attributes]
  804. * Attributes to be applied.
  805. */
  806. function setAttributes(el, attributes) {
  807. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  808. var attrValue = attributes[attrName];
  809. if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
  810. el.removeAttribute(attrName);
  811. } else {
  812. el.setAttribute(attrName, attrValue === true ? '' : attrValue);
  813. }
  814. });
  815. }
  816. /**
  817. * Get an element's attribute values, as defined on the HTML tag.
  818. *
  819. * Attributes are not the same as properties. They're defined on the tag
  820. * or with setAttribute.
  821. *
  822. * @param {Element} tag
  823. * Element from which to get tag attributes.
  824. *
  825. * @return {Object}
  826. * All attributes of the element. Boolean attributes will be `true` or
  827. * `false`, others will be strings.
  828. */
  829. function getAttributes(tag) {
  830. var obj = {}; // known boolean attributes
  831. // we can check for matching boolean properties, but not all browsers
  832. // and not all tags know about these attributes, so, we still want to check them manually
  833. var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
  834. if (tag && tag.attributes && tag.attributes.length > 0) {
  835. var attrs = tag.attributes;
  836. for (var i = attrs.length - 1; i >= 0; i--) {
  837. var attrName = attrs[i].name;
  838. var attrVal = attrs[i].value; // check for known booleans
  839. // the matching element property will return a value for typeof
  840. if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
  841. // the value of an included boolean attribute is typically an empty
  842. // string ('') which would equal false if we just check for a false value.
  843. // we also don't want support bad code like autoplay='false'
  844. attrVal = attrVal !== null ? true : false;
  845. }
  846. obj[attrName] = attrVal;
  847. }
  848. }
  849. return obj;
  850. }
  851. /**
  852. * Get the value of an element's attribute.
  853. *
  854. * @param {Element} el
  855. * A DOM element.
  856. *
  857. * @param {string} attribute
  858. * Attribute to get the value of.
  859. *
  860. * @return {string}
  861. * The value of the attribute.
  862. */
  863. function getAttribute(el, attribute) {
  864. return el.getAttribute(attribute);
  865. }
  866. /**
  867. * Set the value of an element's attribute.
  868. *
  869. * @param {Element} el
  870. * A DOM element.
  871. *
  872. * @param {string} attribute
  873. * Attribute to set.
  874. *
  875. * @param {string} value
  876. * Value to set the attribute to.
  877. */
  878. function setAttribute(el, attribute, value) {
  879. el.setAttribute(attribute, value);
  880. }
  881. /**
  882. * Remove an element's attribute.
  883. *
  884. * @param {Element} el
  885. * A DOM element.
  886. *
  887. * @param {string} attribute
  888. * Attribute to remove.
  889. */
  890. function removeAttribute(el, attribute) {
  891. el.removeAttribute(attribute);
  892. }
  893. /**
  894. * Attempt to block the ability to select text.
  895. */
  896. function blockTextSelection() {
  897. document.body.focus();
  898. document.onselectstart = function () {
  899. return false;
  900. };
  901. }
  902. /**
  903. * Turn off text selection blocking.
  904. */
  905. function unblockTextSelection() {
  906. document.onselectstart = function () {
  907. return true;
  908. };
  909. }
  910. /**
  911. * Identical to the native `getBoundingClientRect` function, but ensures that
  912. * the method is supported at all (it is in all browsers we claim to support)
  913. * and that the element is in the DOM before continuing.
  914. *
  915. * This wrapper function also shims properties which are not provided by some
  916. * older browsers (namely, IE8).
  917. *
  918. * Additionally, some browsers do not support adding properties to a
  919. * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
  920. * properties (except `x` and `y` which are not widely supported). This helps
  921. * avoid implementations where keys are non-enumerable.
  922. *
  923. * @param {Element} el
  924. * Element whose `ClientRect` we want to calculate.
  925. *
  926. * @return {Object|undefined}
  927. * Always returns a plain object - or `undefined` if it cannot.
  928. */
  929. function getBoundingClientRect(el) {
  930. if (el && el.getBoundingClientRect && el.parentNode) {
  931. var rect = el.getBoundingClientRect();
  932. var result = {};
  933. ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
  934. if (rect[k] !== undefined) {
  935. result[k] = rect[k];
  936. }
  937. });
  938. if (!result.height) {
  939. result.height = parseFloat(computedStyle(el, 'height'));
  940. }
  941. if (!result.width) {
  942. result.width = parseFloat(computedStyle(el, 'width'));
  943. }
  944. return result;
  945. }
  946. }
  947. /**
  948. * Represents the position of a DOM element on the page.
  949. *
  950. * @typedef {Object} module:dom~Position
  951. *
  952. * @property {number} left
  953. * Pixels to the left.
  954. *
  955. * @property {number} top
  956. * Pixels from the top.
  957. */
  958. /**
  959. * Get the position of an element in the DOM.
  960. *
  961. * Uses `getBoundingClientRect` technique from John Resig.
  962. *
  963. * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
  964. *
  965. * @param {Element} el
  966. * Element from which to get offset.
  967. *
  968. * @return {module:dom~Position}
  969. * The position of the element that was passed in.
  970. */
  971. function findPosition(el) {
  972. var box;
  973. if (el.getBoundingClientRect && el.parentNode) {
  974. box = el.getBoundingClientRect();
  975. }
  976. if (!box) {
  977. return {
  978. left: 0,
  979. top: 0
  980. };
  981. }
  982. var docEl = document.documentElement;
  983. var body = document.body;
  984. var clientLeft = docEl.clientLeft || body.clientLeft || 0;
  985. var scrollLeft = window$1.pageXOffset || body.scrollLeft;
  986. var left = box.left + scrollLeft - clientLeft;
  987. var clientTop = docEl.clientTop || body.clientTop || 0;
  988. var scrollTop = window$1.pageYOffset || body.scrollTop;
  989. var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round
  990. return {
  991. left: Math.round(left),
  992. top: Math.round(top)
  993. };
  994. }
  995. /**
  996. * Represents x and y coordinates for a DOM element or mouse pointer.
  997. *
  998. * @typedef {Object} module:dom~Coordinates
  999. *
  1000. * @property {number} x
  1001. * x coordinate in pixels
  1002. *
  1003. * @property {number} y
  1004. * y coordinate in pixels
  1005. */
  1006. /**
  1007. * Get the pointer position within an element.
  1008. *
  1009. * The base on the coordinates are the bottom left of the element.
  1010. *
  1011. * @param {Element} el
  1012. * Element on which to get the pointer position on.
  1013. *
  1014. * @param {EventTarget~Event} event
  1015. * Event object.
  1016. *
  1017. * @return {module:dom~Coordinates}
  1018. * A coordinates object corresponding to the mouse position.
  1019. *
  1020. */
  1021. function getPointerPosition(el, event) {
  1022. var position = {};
  1023. var box = findPosition(el);
  1024. var boxW = el.offsetWidth;
  1025. var boxH = el.offsetHeight;
  1026. var boxY = box.top;
  1027. var boxX = box.left;
  1028. var pageY = event.pageY;
  1029. var pageX = event.pageX;
  1030. if (event.changedTouches) {
  1031. pageX = event.changedTouches[0].pageX;
  1032. pageY = event.changedTouches[0].pageY;
  1033. }
  1034. position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
  1035. position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
  1036. return position;
  1037. }
  1038. /**
  1039. * Determines, via duck typing, whether or not a value is a text node.
  1040. *
  1041. * @param {Mixed} value
  1042. * Check if this value is a text node.
  1043. *
  1044. * @return {boolean}
  1045. * Will be `true` if the value is a text node, `false` otherwise.
  1046. */
  1047. function isTextNode(value) {
  1048. return isObject(value) && value.nodeType === 3;
  1049. }
  1050. /**
  1051. * Empties the contents of an element.
  1052. *
  1053. * @param {Element} el
  1054. * The element to empty children from
  1055. *
  1056. * @return {Element}
  1057. * The element with no children
  1058. */
  1059. function emptyEl(el) {
  1060. while (el.firstChild) {
  1061. el.removeChild(el.firstChild);
  1062. }
  1063. return el;
  1064. }
  1065. /**
  1066. * This is a mixed value that describes content to be injected into the DOM
  1067. * via some method. It can be of the following types:
  1068. *
  1069. * Type | Description
  1070. * -----------|-------------
  1071. * `string` | The value will be normalized into a text node.
  1072. * `Element` | The value will be accepted as-is.
  1073. * `TextNode` | The value will be accepted as-is.
  1074. * `Array` | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored).
  1075. * `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes.
  1076. *
  1077. * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor
  1078. */
  1079. /**
  1080. * Normalizes content for eventual insertion into the DOM.
  1081. *
  1082. * This allows a wide range of content definition methods, but helps protect
  1083. * from falling into the trap of simply writing to `innerHTML`, which could
  1084. * be an XSS concern.
  1085. *
  1086. * The content for an element can be passed in multiple types and
  1087. * combinations, whose behavior is as follows:
  1088. *
  1089. * @param {module:dom~ContentDescriptor} content
  1090. * A content descriptor value.
  1091. *
  1092. * @return {Array}
  1093. * All of the content that was passed in, normalized to an array of
  1094. * elements or text nodes.
  1095. */
  1096. function normalizeContent(content) {
  1097. // First, invoke content if it is a function. If it produces an array,
  1098. // that needs to happen before normalization.
  1099. if (typeof content === 'function') {
  1100. content = content();
  1101. } // Next up, normalize to an array, so one or many items can be normalized,
  1102. // filtered, and returned.
  1103. return (Array.isArray(content) ? content : [content]).map(function (value) {
  1104. // First, invoke value if it is a function to produce a new value,
  1105. // which will be subsequently normalized to a Node of some kind.
  1106. if (typeof value === 'function') {
  1107. value = value();
  1108. }
  1109. if (isEl(value) || isTextNode(value)) {
  1110. return value;
  1111. }
  1112. if (typeof value === 'string' && /\S/.test(value)) {
  1113. return document.createTextNode(value);
  1114. }
  1115. }).filter(function (value) {
  1116. return value;
  1117. });
  1118. }
  1119. /**
  1120. * Normalizes and appends content to an element.
  1121. *
  1122. * @param {Element} el
  1123. * Element to append normalized content to.
  1124. *
  1125. * @param {module:dom~ContentDescriptor} content
  1126. * A content descriptor value.
  1127. *
  1128. * @return {Element}
  1129. * The element with appended normalized content.
  1130. */
  1131. function appendContent(el, content) {
  1132. normalizeContent(content).forEach(function (node) {
  1133. return el.appendChild(node);
  1134. });
  1135. return el;
  1136. }
  1137. /**
  1138. * Normalizes and inserts content into an element; this is identical to
  1139. * `appendContent()`, except it empties the element first.
  1140. *
  1141. * @param {Element} el
  1142. * Element to insert normalized content into.
  1143. *
  1144. * @param {module:dom~ContentDescriptor} content
  1145. * A content descriptor value.
  1146. *
  1147. * @return {Element}
  1148. * The element with inserted normalized content.
  1149. */
  1150. function insertContent(el, content) {
  1151. return appendContent(emptyEl(el), content);
  1152. }
  1153. /**
  1154. * Check if an event was a single left click.
  1155. *
  1156. * @param {EventTarget~Event} event
  1157. * Event object.
  1158. *
  1159. * @return {boolean}
  1160. * Will be `true` if a single left click, `false` otherwise.
  1161. */
  1162. function isSingleLeftClick(event) {
  1163. // Note: if you create something draggable, be sure to
  1164. // call it on both `mousedown` and `mousemove` event,
  1165. // otherwise `mousedown` should be enough for a button
  1166. if (event.button === undefined && event.buttons === undefined) {
  1167. // Why do we need `buttons` ?
  1168. // Because, middle mouse sometimes have this:
  1169. // e.button === 0 and e.buttons === 4
  1170. // Furthermore, we want to prevent combination click, something like
  1171. // HOLD middlemouse then left click, that would be
  1172. // e.button === 0, e.buttons === 5
  1173. // just `button` is not gonna work
  1174. // Alright, then what this block does ?
  1175. // this is for chrome `simulate mobile devices`
  1176. // I want to support this as well
  1177. return true;
  1178. }
  1179. if (event.button === 0 && event.buttons === undefined) {
  1180. // Touch screen, sometimes on some specific device, `buttons`
  1181. // doesn't have anything (safari on ios, blackberry...)
  1182. return true;
  1183. }
  1184. if (event.button !== 0 || event.buttons !== 1) {
  1185. // This is the reason we have those if else block above
  1186. // if any special case we can catch and let it slide
  1187. // we do it above, when get to here, this definitely
  1188. // is-not-left-click
  1189. return false;
  1190. }
  1191. return true;
  1192. }
  1193. /**
  1194. * Finds a single DOM element matching `selector` within the optional
  1195. * `context` of another DOM element (defaulting to `document`).
  1196. *
  1197. * @param {string} selector
  1198. * A valid CSS selector, which will be passed to `querySelector`.
  1199. *
  1200. * @param {Element|String} [context=document]
  1201. * A DOM element within which to query. Can also be a selector
  1202. * string in which case the first matching element will be used
  1203. * as context. If missing (or no element matches selector), falls
  1204. * back to `document`.
  1205. *
  1206. * @return {Element|null}
  1207. * The element that was found or null.
  1208. */
  1209. var $ = createQuerier('querySelector');
  1210. /**
  1211. * Finds a all DOM elements matching `selector` within the optional
  1212. * `context` of another DOM element (defaulting to `document`).
  1213. *
  1214. * @param {string} selector
  1215. * A valid CSS selector, which will be passed to `querySelectorAll`.
  1216. *
  1217. * @param {Element|String} [context=document]
  1218. * A DOM element within which to query. Can also be a selector
  1219. * string in which case the first matching element will be used
  1220. * as context. If missing (or no element matches selector), falls
  1221. * back to `document`.
  1222. *
  1223. * @return {NodeList}
  1224. * A element list of elements that were found. Will be empty if none
  1225. * were found.
  1226. *
  1227. */
  1228. var $$ = createQuerier('querySelectorAll');
  1229. var Dom = /*#__PURE__*/Object.freeze({
  1230. isReal: isReal,
  1231. isEl: isEl,
  1232. isInFrame: isInFrame,
  1233. createEl: createEl,
  1234. textContent: textContent,
  1235. prependTo: prependTo,
  1236. hasClass: hasClass,
  1237. addClass: addClass,
  1238. removeClass: removeClass,
  1239. toggleClass: toggleClass,
  1240. setAttributes: setAttributes,
  1241. getAttributes: getAttributes,
  1242. getAttribute: getAttribute,
  1243. setAttribute: setAttribute,
  1244. removeAttribute: removeAttribute,
  1245. blockTextSelection: blockTextSelection,
  1246. unblockTextSelection: unblockTextSelection,
  1247. getBoundingClientRect: getBoundingClientRect,
  1248. findPosition: findPosition,
  1249. getPointerPosition: getPointerPosition,
  1250. isTextNode: isTextNode,
  1251. emptyEl: emptyEl,
  1252. normalizeContent: normalizeContent,
  1253. appendContent: appendContent,
  1254. insertContent: insertContent,
  1255. isSingleLeftClick: isSingleLeftClick,
  1256. $: $,
  1257. $$: $$
  1258. });
  1259. /**
  1260. * @file guid.js
  1261. * @module guid
  1262. */
  1263. /**
  1264. * Unique ID for an element or function
  1265. * @type {Number}
  1266. */
  1267. var _guid = 1;
  1268. /**
  1269. * Get a unique auto-incrementing ID by number that has not been returned before.
  1270. *
  1271. * @return {number}
  1272. * A new unique ID.
  1273. */
  1274. function newGUID() {
  1275. return _guid++;
  1276. }
  1277. /**
  1278. * @file dom-data.js
  1279. * @module dom-data
  1280. */
  1281. /**
  1282. * Element Data Store.
  1283. *
  1284. * Allows for binding data to an element without putting it directly on the
  1285. * element. Ex. Event listeners are stored here.
  1286. * (also from jsninja.com, slightly modified and updated for closure compiler)
  1287. *
  1288. * @type {Object}
  1289. * @private
  1290. */
  1291. var elData = {};
  1292. /*
  1293. * Unique attribute name to store an element's guid in
  1294. *
  1295. * @type {String}
  1296. * @constant
  1297. * @private
  1298. */
  1299. var elIdAttr = 'vdata' + Math.floor(window$1.performance && window$1.performance.now() || Date.now());
  1300. /**
  1301. * Returns the cache object where data for an element is stored
  1302. *
  1303. * @param {Element} el
  1304. * Element to store data for.
  1305. *
  1306. * @return {Object}
  1307. * The cache object for that el that was passed in.
  1308. */
  1309. function getData(el) {
  1310. var id = el[elIdAttr];
  1311. if (!id) {
  1312. id = el[elIdAttr] = newGUID();
  1313. }
  1314. if (!elData[id]) {
  1315. elData[id] = {};
  1316. }
  1317. return elData[id];
  1318. }
  1319. /**
  1320. * Returns whether or not an element has cached data
  1321. *
  1322. * @param {Element} el
  1323. * Check if this element has cached data.
  1324. *
  1325. * @return {boolean}
  1326. * - True if the DOM element has cached data.
  1327. * - False otherwise.
  1328. */
  1329. function hasData(el) {
  1330. var id = el[elIdAttr];
  1331. if (!id) {
  1332. return false;
  1333. }
  1334. return !!Object.getOwnPropertyNames(elData[id]).length;
  1335. }
  1336. /**
  1337. * Delete data for the element from the cache and the guid attr from getElementById
  1338. *
  1339. * @param {Element} el
  1340. * Remove cached data for this element.
  1341. */
  1342. function removeData(el) {
  1343. var id = el[elIdAttr];
  1344. if (!id) {
  1345. return;
  1346. } // Remove all stored data
  1347. delete elData[id]; // Remove the elIdAttr property from the DOM node
  1348. try {
  1349. delete el[elIdAttr];
  1350. } catch (e) {
  1351. if (el.removeAttribute) {
  1352. el.removeAttribute(elIdAttr);
  1353. } else {
  1354. // IE doesn't appear to support removeAttribute on the document element
  1355. el[elIdAttr] = null;
  1356. }
  1357. }
  1358. }
  1359. /**
  1360. * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
  1361. * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
  1362. * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
  1363. * robust as jquery's, so there's probably some differences.
  1364. *
  1365. * @file events.js
  1366. * @module events
  1367. */
  1368. /**
  1369. * Clean up the listener cache and dispatchers
  1370. *
  1371. * @param {Element|Object} elem
  1372. * Element to clean up
  1373. *
  1374. * @param {string} type
  1375. * Type of event to clean up
  1376. */
  1377. function _cleanUpEvents(elem, type) {
  1378. var data = getData(elem); // Remove the events of a particular type if there are none left
  1379. if (data.handlers[type].length === 0) {
  1380. delete data.handlers[type]; // data.handlers[type] = null;
  1381. // Setting to null was causing an error with data.handlers
  1382. // Remove the meta-handler from the element
  1383. if (elem.removeEventListener) {
  1384. elem.removeEventListener(type, data.dispatcher, false);
  1385. } else if (elem.detachEvent) {
  1386. elem.detachEvent('on' + type, data.dispatcher);
  1387. }
  1388. } // Remove the events object if there are no types left
  1389. if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
  1390. delete data.handlers;
  1391. delete data.dispatcher;
  1392. delete data.disabled;
  1393. } // Finally remove the element data if there is no data left
  1394. if (Object.getOwnPropertyNames(data).length === 0) {
  1395. removeData(elem);
  1396. }
  1397. }
  1398. /**
  1399. * Loops through an array of event types and calls the requested method for each type.
  1400. *
  1401. * @param {Function} fn
  1402. * The event method we want to use.
  1403. *
  1404. * @param {Element|Object} elem
  1405. * Element or object to bind listeners to
  1406. *
  1407. * @param {string} type
  1408. * Type of event to bind to.
  1409. *
  1410. * @param {EventTarget~EventListener} callback
  1411. * Event listener.
  1412. */
  1413. function _handleMultipleEvents(fn, elem, types, callback) {
  1414. types.forEach(function (type) {
  1415. // Call the event method for each one of the types
  1416. fn(elem, type, callback);
  1417. });
  1418. }
  1419. /**
  1420. * Fix a native event to have standard property values
  1421. *
  1422. * @param {Object} event
  1423. * Event object to fix.
  1424. *
  1425. * @return {Object}
  1426. * Fixed event object.
  1427. */
  1428. function fixEvent(event) {
  1429. function returnTrue() {
  1430. return true;
  1431. }
  1432. function returnFalse() {
  1433. return false;
  1434. } // Test if fixing up is needed
  1435. // Used to check if !event.stopPropagation instead of isPropagationStopped
  1436. // But native events return true for stopPropagation, but don't have
  1437. // other expected methods like isPropagationStopped. Seems to be a problem
  1438. // with the Javascript Ninja code. So we're just overriding all events now.
  1439. if (!event || !event.isPropagationStopped) {
  1440. var old = event || window$1.event;
  1441. event = {}; // Clone the old object so that we can modify the values event = {};
  1442. // IE8 Doesn't like when you mess with native event properties
  1443. // Firefox returns false for event.hasOwnProperty('type') and other props
  1444. // which makes copying more difficult.
  1445. // TODO: Probably best to create a whitelist of event props
  1446. for (var key in old) {
  1447. // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
  1448. // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
  1449. // and webkitMovementX/Y
  1450. if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
  1451. // Chrome 32+ warns if you try to copy deprecated returnValue, but
  1452. // we still want to if preventDefault isn't supported (IE8).
  1453. if (!(key === 'returnValue' && old.preventDefault)) {
  1454. event[key] = old[key];
  1455. }
  1456. }
  1457. } // The event occurred on this element
  1458. if (!event.target) {
  1459. event.target = event.srcElement || document;
  1460. } // Handle which other element the event is related to
  1461. if (!event.relatedTarget) {
  1462. event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
  1463. } // Stop the default browser action
  1464. event.preventDefault = function () {
  1465. if (old.preventDefault) {
  1466. old.preventDefault();
  1467. }
  1468. event.returnValue = false;
  1469. old.returnValue = false;
  1470. event.defaultPrevented = true;
  1471. };
  1472. event.defaultPrevented = false; // Stop the event from bubbling
  1473. event.stopPropagation = function () {
  1474. if (old.stopPropagation) {
  1475. old.stopPropagation();
  1476. }
  1477. event.cancelBubble = true;
  1478. old.cancelBubble = true;
  1479. event.isPropagationStopped = returnTrue;
  1480. };
  1481. event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
  1482. event.stopImmediatePropagation = function () {
  1483. if (old.stopImmediatePropagation) {
  1484. old.stopImmediatePropagation();
  1485. }
  1486. event.isImmediatePropagationStopped = returnTrue;
  1487. event.stopPropagation();
  1488. };
  1489. event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
  1490. if (event.clientX !== null && event.clientX !== undefined) {
  1491. var doc = document.documentElement;
  1492. var body = document.body;
  1493. event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
  1494. event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
  1495. } // Handle key presses
  1496. event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
  1497. // 0 == left; 1 == middle; 2 == right
  1498. if (event.button !== null && event.button !== undefined) {
  1499. // The following is disabled because it does not pass videojs-standard
  1500. // and... yikes.
  1501. /* eslint-disable */
  1502. event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
  1503. /* eslint-enable */
  1504. }
  1505. } // Returns fixed-up instance
  1506. return event;
  1507. }
  1508. /**
  1509. * Whether passive event listeners are supported
  1510. */
  1511. var _supportsPassive = false;
  1512. (function () {
  1513. try {
  1514. var opts = Object.defineProperty({}, 'passive', {
  1515. get: function get() {
  1516. _supportsPassive = true;
  1517. }
  1518. });
  1519. window$1.addEventListener('test', null, opts);
  1520. window$1.removeEventListener('test', null, opts);
  1521. } catch (e) {// disregard
  1522. }
  1523. })();
  1524. /**
  1525. * Touch events Chrome expects to be passive
  1526. */
  1527. var passiveEvents = ['touchstart', 'touchmove'];
  1528. /**
  1529. * Add an event listener to element
  1530. * It stores the handler function in a separate cache object
  1531. * and adds a generic handler to the element's event,
  1532. * along with a unique id (guid) to the element.
  1533. *
  1534. * @param {Element|Object} elem
  1535. * Element or object to bind listeners to
  1536. *
  1537. * @param {string|string[]} type
  1538. * Type of event to bind to.
  1539. *
  1540. * @param {EventTarget~EventListener} fn
  1541. * Event listener.
  1542. */
  1543. function on(elem, type, fn) {
  1544. if (Array.isArray(type)) {
  1545. return _handleMultipleEvents(on, elem, type, fn);
  1546. }
  1547. var data = getData(elem); // We need a place to store all our handler data
  1548. if (!data.handlers) {
  1549. data.handlers = {};
  1550. }
  1551. if (!data.handlers[type]) {
  1552. data.handlers[type] = [];
  1553. }
  1554. if (!fn.guid) {
  1555. fn.guid = newGUID();
  1556. }
  1557. data.handlers[type].push(fn);
  1558. if (!data.dispatcher) {
  1559. data.disabled = false;
  1560. data.dispatcher = function (event, hash) {
  1561. if (data.disabled) {
  1562. return;
  1563. }
  1564. event = fixEvent(event);
  1565. var handlers = data.handlers[event.type];
  1566. if (handlers) {
  1567. // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
  1568. var handlersCopy = handlers.slice(0);
  1569. for (var m = 0, n = handlersCopy.length; m < n; m++) {
  1570. if (event.isImmediatePropagationStopped()) {
  1571. break;
  1572. } else {
  1573. try {
  1574. handlersCopy[m].call(elem, event, hash);
  1575. } catch (e) {
  1576. log.error(e);
  1577. }
  1578. }
  1579. }
  1580. }
  1581. };
  1582. }
  1583. if (data.handlers[type].length === 1) {
  1584. if (elem.addEventListener) {
  1585. var options = false;
  1586. if (_supportsPassive && passiveEvents.indexOf(type) > -1) {
  1587. options = {
  1588. passive: true
  1589. };
  1590. }
  1591. elem.addEventListener(type, data.dispatcher, options);
  1592. } else if (elem.attachEvent) {
  1593. elem.attachEvent('on' + type, data.dispatcher);
  1594. }
  1595. }
  1596. }
  1597. /**
  1598. * Removes event listeners from an element
  1599. *
  1600. * @param {Element|Object} elem
  1601. * Object to remove listeners from.
  1602. *
  1603. * @param {string|string[]} [type]
  1604. * Type of listener to remove. Don't include to remove all events from element.
  1605. *
  1606. * @param {EventTarget~EventListener} [fn]
  1607. * Specific listener to remove. Don't include to remove listeners for an event
  1608. * type.
  1609. */
  1610. function off(elem, type, fn) {
  1611. // Don't want to add a cache object through getElData if not needed
  1612. if (!hasData(elem)) {
  1613. return;
  1614. }
  1615. var data = getData(elem); // If no events exist, nothing to unbind
  1616. if (!data.handlers) {
  1617. return;
  1618. }
  1619. if (Array.isArray(type)) {
  1620. return _handleMultipleEvents(off, elem, type, fn);
  1621. } // Utility function
  1622. var removeType = function removeType(el, t) {
  1623. data.handlers[t] = [];
  1624. _cleanUpEvents(el, t);
  1625. }; // Are we removing all bound events?
  1626. if (type === undefined) {
  1627. for (var t in data.handlers) {
  1628. if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
  1629. removeType(elem, t);
  1630. }
  1631. }
  1632. return;
  1633. }
  1634. var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind
  1635. if (!handlers) {
  1636. return;
  1637. } // If no listener was provided, remove all listeners for type
  1638. if (!fn) {
  1639. removeType(elem, type);
  1640. return;
  1641. } // We're only removing a single handler
  1642. if (fn.guid) {
  1643. for (var n = 0; n < handlers.length; n++) {
  1644. if (handlers[n].guid === fn.guid) {
  1645. handlers.splice(n--, 1);
  1646. }
  1647. }
  1648. }
  1649. _cleanUpEvents(elem, type);
  1650. }
  1651. /**
  1652. * Trigger an event for an element
  1653. *
  1654. * @param {Element|Object} elem
  1655. * Element to trigger an event on
  1656. *
  1657. * @param {EventTarget~Event|string} event
  1658. * A string (the type) or an event object with a type attribute
  1659. *
  1660. * @param {Object} [hash]
  1661. * data hash to pass along with the event
  1662. *
  1663. * @return {boolean|undefined}
  1664. * Returns the opposite of `defaultPrevented` if default was
  1665. * prevented. Otherwise, returns `undefined`
  1666. */
  1667. function trigger(elem, event, hash) {
  1668. // Fetches element data and a reference to the parent (for bubbling).
  1669. // Don't want to add a data object to cache for every parent,
  1670. // so checking hasElData first.
  1671. var elemData = hasData(elem) ? getData(elem) : {};
  1672. var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event,
  1673. // handler;
  1674. // If an event name was passed as a string, creates an event out of it
  1675. if (typeof event === 'string') {
  1676. event = {
  1677. type: event,
  1678. target: elem
  1679. };
  1680. } else if (!event.target) {
  1681. event.target = elem;
  1682. } // Normalizes the event properties.
  1683. event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers.
  1684. if (elemData.dispatcher) {
  1685. elemData.dispatcher.call(elem, event, hash);
  1686. } // Unless explicitly stopped or the event does not bubble (e.g. media events)
  1687. // recursively calls this function to bubble the event up the DOM.
  1688. if (parent && !event.isPropagationStopped() && event.bubbles === true) {
  1689. trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled.
  1690. } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
  1691. var targetData = getData(event.target); // Checks if the target has a default action for this event.
  1692. if (event.target[event.type]) {
  1693. // Temporarily disables event dispatching on the target as we have already executed the handler.
  1694. targetData.disabled = true; // Executes the default action.
  1695. if (typeof event.target[event.type] === 'function') {
  1696. event.target[event.type]();
  1697. } // Re-enables event dispatching.
  1698. targetData.disabled = false;
  1699. }
  1700. } // Inform the triggerer if the default was prevented by returning false
  1701. return !event.defaultPrevented;
  1702. }
  1703. /**
  1704. * Trigger a listener only once for an event.
  1705. *
  1706. * @param {Element|Object} elem
  1707. * Element or object to bind to.
  1708. *
  1709. * @param {string|string[]} type
  1710. * Name/type of event
  1711. *
  1712. * @param {Event~EventListener} fn
  1713. * Event listener function
  1714. */
  1715. function one(elem, type, fn) {
  1716. if (Array.isArray(type)) {
  1717. return _handleMultipleEvents(one, elem, type, fn);
  1718. }
  1719. var func = function func() {
  1720. off(elem, type, func);
  1721. fn.apply(this, arguments);
  1722. }; // copy the guid to the new function so it can removed using the original function's ID
  1723. func.guid = fn.guid = fn.guid || newGUID();
  1724. on(elem, type, func);
  1725. }
  1726. var Events = /*#__PURE__*/Object.freeze({
  1727. fixEvent: fixEvent,
  1728. on: on,
  1729. off: off,
  1730. trigger: trigger,
  1731. one: one
  1732. });
  1733. /**
  1734. * @file setup.js - Functions for setting up a player without
  1735. * user interaction based on the data-setup `attribute` of the video tag.
  1736. *
  1737. * @module setup
  1738. */
  1739. var _windowLoaded = false;
  1740. var videojs;
  1741. /**
  1742. * Set up any tags that have a data-setup `attribute` when the player is started.
  1743. */
  1744. var autoSetup = function autoSetup() {
  1745. // Protect against breakage in non-browser environments and check global autoSetup option.
  1746. if (!isReal() || videojs.options.autoSetup === false) {
  1747. return;
  1748. }
  1749. var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
  1750. var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
  1751. var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
  1752. var mediaEls = vids.concat(audios, divs); // Check if any media elements exist
  1753. if (mediaEls && mediaEls.length > 0) {
  1754. for (var i = 0, e = mediaEls.length; i < e; i++) {
  1755. var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func.
  1756. if (mediaEl && mediaEl.getAttribute) {
  1757. // Make sure this player hasn't already been set up.
  1758. if (mediaEl.player === undefined) {
  1759. var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists.
  1760. // We only auto-setup if they've added the data-setup attr.
  1761. if (options !== null) {
  1762. // Create new video.js instance.
  1763. videojs(mediaEl);
  1764. }
  1765. } // If getAttribute isn't defined, we need to wait for the DOM.
  1766. } else {
  1767. autoSetupTimeout(1);
  1768. break;
  1769. }
  1770. } // No videos were found, so keep looping unless page is finished loading.
  1771. } else if (!_windowLoaded) {
  1772. autoSetupTimeout(1);
  1773. }
  1774. };
  1775. /**
  1776. * Wait until the page is loaded before running autoSetup. This will be called in
  1777. * autoSetup if `hasLoaded` returns false.
  1778. *
  1779. * @param {number} wait
  1780. * How long to wait in ms
  1781. *
  1782. * @param {module:videojs} [vjs]
  1783. * The videojs library function
  1784. */
  1785. function autoSetupTimeout(wait, vjs) {
  1786. if (vjs) {
  1787. videojs = vjs;
  1788. }
  1789. window$1.setTimeout(autoSetup, wait);
  1790. }
  1791. if (isReal() && document.readyState === 'complete') {
  1792. _windowLoaded = true;
  1793. } else {
  1794. /**
  1795. * Listen for the load event on window, and set _windowLoaded to true.
  1796. *
  1797. * @listens load
  1798. */
  1799. one(window$1, 'load', function () {
  1800. _windowLoaded = true;
  1801. });
  1802. }
  1803. /**
  1804. * @file stylesheet.js
  1805. * @module stylesheet
  1806. */
  1807. /**
  1808. * Create a DOM syle element given a className for it.
  1809. *
  1810. * @param {string} className
  1811. * The className to add to the created style element.
  1812. *
  1813. * @return {Element}
  1814. * The element that was created.
  1815. */
  1816. var createStyleElement = function createStyleElement(className) {
  1817. var style = document.createElement('style');
  1818. style.className = className;
  1819. return style;
  1820. };
  1821. /**
  1822. * Add text to a DOM element.
  1823. *
  1824. * @param {Element} el
  1825. * The Element to add text content to.
  1826. *
  1827. * @param {string} content
  1828. * The text to add to the element.
  1829. */
  1830. var setTextContent = function setTextContent(el, content) {
  1831. if (el.styleSheet) {
  1832. el.styleSheet.cssText = content;
  1833. } else {
  1834. el.textContent = content;
  1835. }
  1836. };
  1837. /**
  1838. * @file fn.js
  1839. * @module fn
  1840. */
  1841. /**
  1842. * Bind (a.k.a proxy or context). A simple method for changing the context of
  1843. * a function.
  1844. *
  1845. * It also stores a unique id on the function so it can be easily removed from
  1846. * events.
  1847. *
  1848. * @function
  1849. * @param {Mixed} context
  1850. * The object to bind as scope.
  1851. *
  1852. * @param {Function} fn
  1853. * The function to be bound to a scope.
  1854. *
  1855. * @param {number} [uid]
  1856. * An optional unique ID for the function to be set
  1857. *
  1858. * @return {Function}
  1859. * The new function that will be bound into the context given
  1860. */
  1861. var bind = function bind(context, fn, uid) {
  1862. // Make sure the function has a unique ID
  1863. if (!fn.guid) {
  1864. fn.guid = newGUID();
  1865. } // Create the new function that changes the context
  1866. var bound = function bound() {
  1867. return fn.apply(context, arguments);
  1868. }; // Allow for the ability to individualize this function
  1869. // Needed in the case where multiple objects might share the same prototype
  1870. // IF both items add an event listener with the same function, then you try to remove just one
  1871. // it will remove both because they both have the same guid.
  1872. // when using this, you need to use the bind method when you remove the listener as well.
  1873. // currently used in text tracks
  1874. bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
  1875. return bound;
  1876. };
  1877. /**
  1878. * Wraps the given function, `fn`, with a new function that only invokes `fn`
  1879. * at most once per every `wait` milliseconds.
  1880. *
  1881. * @function
  1882. * @param {Function} fn
  1883. * The function to be throttled.
  1884. *
  1885. * @param {number} wait
  1886. * The number of milliseconds by which to throttle.
  1887. *
  1888. * @return {Function}
  1889. */
  1890. var throttle = function throttle(fn, wait) {
  1891. var last = window$1.performance.now();
  1892. var throttled = function throttled() {
  1893. var now = window$1.performance.now();
  1894. if (now - last >= wait) {
  1895. fn.apply(void 0, arguments);
  1896. last = now;
  1897. }
  1898. };
  1899. return throttled;
  1900. };
  1901. /**
  1902. * Creates a debounced function that delays invoking `func` until after `wait`
  1903. * milliseconds have elapsed since the last time the debounced function was
  1904. * invoked.
  1905. *
  1906. * Inspired by lodash and underscore implementations.
  1907. *
  1908. * @function
  1909. * @param {Function} func
  1910. * The function to wrap with debounce behavior.
  1911. *
  1912. * @param {number} wait
  1913. * The number of milliseconds to wait after the last invocation.
  1914. *
  1915. * @param {boolean} [immediate]
  1916. * Whether or not to invoke the function immediately upon creation.
  1917. *
  1918. * @param {Object} [context=window]
  1919. * The "context" in which the debounced function should debounce. For
  1920. * example, if this function should be tied to a Video.js player,
  1921. * the player can be passed here. Alternatively, defaults to the
  1922. * global `window` object.
  1923. *
  1924. * @return {Function}
  1925. * A debounced function.
  1926. */
  1927. var debounce = function debounce(func, wait, immediate, context) {
  1928. if (context === void 0) {
  1929. context = window$1;
  1930. }
  1931. var timeout;
  1932. var cancel = function cancel() {
  1933. context.clearTimeout(timeout);
  1934. timeout = null;
  1935. };
  1936. /* eslint-disable consistent-this */
  1937. var debounced = function debounced() {
  1938. var self = this;
  1939. var args = arguments;
  1940. var _later = function later() {
  1941. timeout = null;
  1942. _later = null;
  1943. if (!immediate) {
  1944. func.apply(self, args);
  1945. }
  1946. };
  1947. if (!timeout && immediate) {
  1948. func.apply(self, args);
  1949. }
  1950. context.clearTimeout(timeout);
  1951. timeout = context.setTimeout(_later, wait);
  1952. };
  1953. /* eslint-enable consistent-this */
  1954. debounced.cancel = cancel;
  1955. return debounced;
  1956. };
  1957. /**
  1958. * @file src/js/event-target.js
  1959. */
  1960. /**
  1961. * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
  1962. * adds shorthand functions that wrap around lengthy functions. For example:
  1963. * the `on` function is a wrapper around `addEventListener`.
  1964. *
  1965. * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
  1966. * @class EventTarget
  1967. */
  1968. var EventTarget = function EventTarget() {};
  1969. /**
  1970. * A Custom DOM event.
  1971. *
  1972. * @typedef {Object} EventTarget~Event
  1973. * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
  1974. */
  1975. /**
  1976. * All event listeners should follow the following format.
  1977. *
  1978. * @callback EventTarget~EventListener
  1979. * @this {EventTarget}
  1980. *
  1981. * @param {EventTarget~Event} event
  1982. * the event that triggered this function
  1983. *
  1984. * @param {Object} [hash]
  1985. * hash of data sent during the event
  1986. */
  1987. /**
  1988. * An object containing event names as keys and booleans as values.
  1989. *
  1990. * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
  1991. * will have extra functionality. See that function for more information.
  1992. *
  1993. * @property EventTarget.prototype.allowedEvents_
  1994. * @private
  1995. */
  1996. EventTarget.prototype.allowedEvents_ = {};
  1997. /**
  1998. * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
  1999. * function that will get called when an event with a certain name gets triggered.
  2000. *
  2001. * @param {string|string[]} type
  2002. * An event name or an array of event names.
  2003. *
  2004. * @param {EventTarget~EventListener} fn
  2005. * The function to call with `EventTarget`s
  2006. */
  2007. EventTarget.prototype.on = function (type, fn) {
  2008. // Remove the addEventListener alias before calling Events.on
  2009. // so we don't get into an infinite type loop
  2010. var ael = this.addEventListener;
  2011. this.addEventListener = function () {};
  2012. on(this, type, fn);
  2013. this.addEventListener = ael;
  2014. };
  2015. /**
  2016. * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
  2017. * the standard DOM API.
  2018. *
  2019. * @function
  2020. * @see {@link EventTarget#on}
  2021. */
  2022. EventTarget.prototype.addEventListener = EventTarget.prototype.on;
  2023. /**
  2024. * Removes an `event listener` for a specific event from an instance of `EventTarget`.
  2025. * This makes it so that the `event listener` will no longer get called when the
  2026. * named event happens.
  2027. *
  2028. * @param {string|string[]} type
  2029. * An event name or an array of event names.
  2030. *
  2031. * @param {EventTarget~EventListener} fn
  2032. * The function to remove.
  2033. */
  2034. EventTarget.prototype.off = function (type, fn) {
  2035. off(this, type, fn);
  2036. };
  2037. /**
  2038. * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
  2039. * the standard DOM API.
  2040. *
  2041. * @function
  2042. * @see {@link EventTarget#off}
  2043. */
  2044. EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
  2045. /**
  2046. * This function will add an `event listener` that gets triggered only once. After the
  2047. * first trigger it will get removed. This is like adding an `event listener`
  2048. * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
  2049. *
  2050. * @param {string|string[]} type
  2051. * An event name or an array of event names.
  2052. *
  2053. * @param {EventTarget~EventListener} fn
  2054. * The function to be called once for each event name.
  2055. */
  2056. EventTarget.prototype.one = function (type, fn) {
  2057. // Remove the addEventListener alialing Events.on
  2058. // so we don't get into an infinite type loop
  2059. var ael = this.addEventListener;
  2060. this.addEventListener = function () {};
  2061. one(this, type, fn);
  2062. this.addEventListener = ael;
  2063. };
  2064. /**
  2065. * This function causes an event to happen. This will then cause any `event listeners`
  2066. * that are waiting for that event, to get called. If there are no `event listeners`
  2067. * for an event then nothing will happen.
  2068. *
  2069. * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
  2070. * Trigger will also call the `on` + `uppercaseEventName` function.
  2071. *
  2072. * Example:
  2073. * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
  2074. * `onClick` if it exists.
  2075. *
  2076. * @param {string|EventTarget~Event|Object} event
  2077. * The name of the event, an `Event`, or an object with a key of type set to
  2078. * an event name.
  2079. */
  2080. EventTarget.prototype.trigger = function (event) {
  2081. var type = event.type || event; // deprecation
  2082. // In a future version we should default target to `this`
  2083. // similar to how we default the target to `elem` in
  2084. // `Events.trigger`. Right now the default `target` will be
  2085. // `document` due to the `Event.fixEvent` call.
  2086. if (typeof event === 'string') {
  2087. event = {
  2088. type: type
  2089. };
  2090. }
  2091. event = fixEvent(event);
  2092. if (this.allowedEvents_[type] && this['on' + type]) {
  2093. this['on' + type](event);
  2094. }
  2095. trigger(this, event);
  2096. };
  2097. /**
  2098. * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
  2099. * the standard DOM API.
  2100. *
  2101. * @function
  2102. * @see {@link EventTarget#trigger}
  2103. */
  2104. EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
  2105. var EVENT_MAP;
  2106. EventTarget.prototype.queueTrigger = function (event) {
  2107. var _this = this;
  2108. // only set up EVENT_MAP if it'll be used
  2109. if (!EVENT_MAP) {
  2110. EVENT_MAP = new Map();
  2111. }
  2112. var type = event.type || event;
  2113. var map = EVENT_MAP.get(this);
  2114. if (!map) {
  2115. map = new Map();
  2116. EVENT_MAP.set(this, map);
  2117. }
  2118. var oldTimeout = map.get(type);
  2119. map.delete(type);
  2120. window$1.clearTimeout(oldTimeout);
  2121. var timeout = window$1.setTimeout(function () {
  2122. // if we cleared out all timeouts for the current target, delete its map
  2123. if (map.size === 0) {
  2124. map = null;
  2125. EVENT_MAP.delete(_this);
  2126. }
  2127. _this.trigger(event);
  2128. }, 0);
  2129. map.set(type, timeout);
  2130. };
  2131. /**
  2132. * @file mixins/evented.js
  2133. * @module evented
  2134. */
  2135. /**
  2136. * Returns whether or not an object has had the evented mixin applied.
  2137. *
  2138. * @param {Object} object
  2139. * An object to test.
  2140. *
  2141. * @return {boolean}
  2142. * Whether or not the object appears to be evented.
  2143. */
  2144. var isEvented = function isEvented(object) {
  2145. return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
  2146. return typeof object[k] === 'function';
  2147. });
  2148. };
  2149. /**
  2150. * Adds a callback to run after the evented mixin applied.
  2151. *
  2152. * @param {Object} object
  2153. * An object to Add
  2154. * @param {Function} callback
  2155. * The callback to run.
  2156. */
  2157. var addEventedCallback = function addEventedCallback(target, callback) {
  2158. if (isEvented(target)) {
  2159. callback();
  2160. } else {
  2161. if (!target.eventedCallbacks) {
  2162. target.eventedCallbacks = [];
  2163. }
  2164. target.eventedCallbacks.push(callback);
  2165. }
  2166. };
  2167. /**
  2168. * Whether a value is a valid event type - non-empty string or array.
  2169. *
  2170. * @private
  2171. * @param {string|Array} type
  2172. * The type value to test.
  2173. *
  2174. * @return {boolean}
  2175. * Whether or not the type is a valid event type.
  2176. */
  2177. var isValidEventType = function isValidEventType(type) {
  2178. return (// The regex here verifies that the `type` contains at least one non-
  2179. // whitespace character.
  2180. typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
  2181. );
  2182. };
  2183. /**
  2184. * Validates a value to determine if it is a valid event target. Throws if not.
  2185. *
  2186. * @private
  2187. * @throws {Error}
  2188. * If the target does not appear to be a valid event target.
  2189. *
  2190. * @param {Object} target
  2191. * The object to test.
  2192. */
  2193. var validateTarget = function validateTarget(target) {
  2194. if (!target.nodeName && !isEvented(target)) {
  2195. throw new Error('Invalid target; must be a DOM node or evented object.');
  2196. }
  2197. };
  2198. /**
  2199. * Validates a value to determine if it is a valid event target. Throws if not.
  2200. *
  2201. * @private
  2202. * @throws {Error}
  2203. * If the type does not appear to be a valid event type.
  2204. *
  2205. * @param {string|Array} type
  2206. * The type to test.
  2207. */
  2208. var validateEventType = function validateEventType(type) {
  2209. if (!isValidEventType(type)) {
  2210. throw new Error('Invalid event type; must be a non-empty string or array.');
  2211. }
  2212. };
  2213. /**
  2214. * Validates a value to determine if it is a valid listener. Throws if not.
  2215. *
  2216. * @private
  2217. * @throws {Error}
  2218. * If the listener is not a function.
  2219. *
  2220. * @param {Function} listener
  2221. * The listener to test.
  2222. */
  2223. var validateListener = function validateListener(listener) {
  2224. if (typeof listener !== 'function') {
  2225. throw new Error('Invalid listener; must be a function.');
  2226. }
  2227. };
  2228. /**
  2229. * Takes an array of arguments given to `on()` or `one()`, validates them, and
  2230. * normalizes them into an object.
  2231. *
  2232. * @private
  2233. * @param {Object} self
  2234. * The evented object on which `on()` or `one()` was called. This
  2235. * object will be bound as the `this` value for the listener.
  2236. *
  2237. * @param {Array} args
  2238. * An array of arguments passed to `on()` or `one()`.
  2239. *
  2240. * @return {Object}
  2241. * An object containing useful values for `on()` or `one()` calls.
  2242. */
  2243. var normalizeListenArgs = function normalizeListenArgs(self, args) {
  2244. // If the number of arguments is less than 3, the target is always the
  2245. // evented object itself.
  2246. var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
  2247. var target;
  2248. var type;
  2249. var listener;
  2250. if (isTargetingSelf) {
  2251. target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to
  2252. // the evented object itself.
  2253. if (args.length >= 3) {
  2254. args.shift();
  2255. }
  2256. type = args[0];
  2257. listener = args[1];
  2258. } else {
  2259. target = args[0];
  2260. type = args[1];
  2261. listener = args[2];
  2262. }
  2263. validateTarget(target);
  2264. validateEventType(type);
  2265. validateListener(listener);
  2266. listener = bind(self, listener);
  2267. return {
  2268. isTargetingSelf: isTargetingSelf,
  2269. target: target,
  2270. type: type,
  2271. listener: listener
  2272. };
  2273. };
  2274. /**
  2275. * Adds the listener to the event type(s) on the target, normalizing for
  2276. * the type of target.
  2277. *
  2278. * @private
  2279. * @param {Element|Object} target
  2280. * A DOM node or evented object.
  2281. *
  2282. * @param {string} method
  2283. * The event binding method to use ("on" or "one").
  2284. *
  2285. * @param {string|Array} type
  2286. * One or more event type(s).
  2287. *
  2288. * @param {Function} listener
  2289. * A listener function.
  2290. */
  2291. var listen = function listen(target, method, type, listener) {
  2292. validateTarget(target);
  2293. if (target.nodeName) {
  2294. Events[method](target, type, listener);
  2295. } else {
  2296. target[method](type, listener);
  2297. }
  2298. };
  2299. /**
  2300. * Contains methods that provide event capabilities to an object which is passed
  2301. * to {@link module:evented|evented}.
  2302. *
  2303. * @mixin EventedMixin
  2304. */
  2305. var EventedMixin = {
  2306. /**
  2307. * Add a listener to an event (or events) on this object or another evented
  2308. * object.
  2309. *
  2310. * @param {string|Array|Element|Object} targetOrType
  2311. * If this is a string or array, it represents the event type(s)
  2312. * that will trigger the listener.
  2313. *
  2314. * Another evented object can be passed here instead, which will
  2315. * cause the listener to listen for events on _that_ object.
  2316. *
  2317. * In either case, the listener's `this` value will be bound to
  2318. * this object.
  2319. *
  2320. * @param {string|Array|Function} typeOrListener
  2321. * If the first argument was a string or array, this should be the
  2322. * listener function. Otherwise, this is a string or array of event
  2323. * type(s).
  2324. *
  2325. * @param {Function} [listener]
  2326. * If the first argument was another evented object, this will be
  2327. * the listener function.
  2328. */
  2329. on: function on$$1() {
  2330. var _this = this;
  2331. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  2332. args[_key] = arguments[_key];
  2333. }
  2334. var _normalizeListenArgs = normalizeListenArgs(this, args),
  2335. isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
  2336. target = _normalizeListenArgs.target,
  2337. type = _normalizeListenArgs.type,
  2338. listener = _normalizeListenArgs.listener;
  2339. listen(target, 'on', type, listener); // If this object is listening to another evented object.
  2340. if (!isTargetingSelf) {
  2341. // If this object is disposed, remove the listener.
  2342. var removeListenerOnDispose = function removeListenerOnDispose() {
  2343. return _this.off(target, type, listener);
  2344. }; // Use the same function ID as the listener so we can remove it later it
  2345. // using the ID of the original listener.
  2346. removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures
  2347. // that if the target is disposed BEFORE this object, we remove the
  2348. // removal listener that was just added. Otherwise, we create a memory leak.
  2349. var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
  2350. return _this.off('dispose', removeListenerOnDispose);
  2351. }; // Use the same function ID as the listener so we can remove it later
  2352. // it using the ID of the original listener.
  2353. removeRemoverOnTargetDispose.guid = listener.guid;
  2354. listen(this, 'on', 'dispose', removeListenerOnDispose);
  2355. listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
  2356. }
  2357. },
  2358. /**
  2359. * Add a listener to an event (or events) on this object or another evented
  2360. * object. The listener will only be called once and then removed.
  2361. *
  2362. * @param {string|Array|Element|Object} targetOrType
  2363. * If this is a string or array, it represents the event type(s)
  2364. * that will trigger the listener.
  2365. *
  2366. * Another evented object can be passed here instead, which will
  2367. * cause the listener to listen for events on _that_ object.
  2368. *
  2369. * In either case, the listener's `this` value will be bound to
  2370. * this object.
  2371. *
  2372. * @param {string|Array|Function} typeOrListener
  2373. * If the first argument was a string or array, this should be the
  2374. * listener function. Otherwise, this is a string or array of event
  2375. * type(s).
  2376. *
  2377. * @param {Function} [listener]
  2378. * If the first argument was another evented object, this will be
  2379. * the listener function.
  2380. */
  2381. one: function one$$1() {
  2382. var _this2 = this;
  2383. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  2384. args[_key2] = arguments[_key2];
  2385. }
  2386. var _normalizeListenArgs2 = normalizeListenArgs(this, args),
  2387. isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
  2388. target = _normalizeListenArgs2.target,
  2389. type = _normalizeListenArgs2.type,
  2390. listener = _normalizeListenArgs2.listener; // Targeting this evented object.
  2391. if (isTargetingSelf) {
  2392. listen(target, 'one', type, listener); // Targeting another evented object.
  2393. } else {
  2394. var wrapper = function wrapper() {
  2395. _this2.off(target, type, wrapper);
  2396. for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  2397. largs[_key3] = arguments[_key3];
  2398. }
  2399. listener.apply(null, largs);
  2400. }; // Use the same function ID as the listener so we can remove it later
  2401. // it using the ID of the original listener.
  2402. wrapper.guid = listener.guid;
  2403. listen(target, 'one', type, wrapper);
  2404. }
  2405. },
  2406. /**
  2407. * Removes listener(s) from event(s) on an evented object.
  2408. *
  2409. * @param {string|Array|Element|Object} [targetOrType]
  2410. * If this is a string or array, it represents the event type(s).
  2411. *
  2412. * Another evented object can be passed here instead, in which case
  2413. * ALL 3 arguments are _required_.
  2414. *
  2415. * @param {string|Array|Function} [typeOrListener]
  2416. * If the first argument was a string or array, this may be the
  2417. * listener function. Otherwise, this is a string or array of event
  2418. * type(s).
  2419. *
  2420. * @param {Function} [listener]
  2421. * If the first argument was another evented object, this will be
  2422. * the listener function; otherwise, _all_ listeners bound to the
  2423. * event type(s) will be removed.
  2424. */
  2425. off: function off$$1(targetOrType, typeOrListener, listener) {
  2426. // Targeting this evented object.
  2427. if (!targetOrType || isValidEventType(targetOrType)) {
  2428. off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object.
  2429. } else {
  2430. var target = targetOrType;
  2431. var type = typeOrListener; // Fail fast and in a meaningful way!
  2432. validateTarget(target);
  2433. validateEventType(type);
  2434. validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used
  2435. listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given
  2436. // the same guid as the event listener in on().
  2437. this.off('dispose', listener);
  2438. if (target.nodeName) {
  2439. off(target, type, listener);
  2440. off(target, 'dispose', listener);
  2441. } else if (isEvented(target)) {
  2442. target.off(type, listener);
  2443. target.off('dispose', listener);
  2444. }
  2445. }
  2446. },
  2447. /**
  2448. * Fire an event on this evented object, causing its listeners to be called.
  2449. *
  2450. * @param {string|Object} event
  2451. * An event type or an object with a type property.
  2452. *
  2453. * @param {Object} [hash]
  2454. * An additional object to pass along to listeners.
  2455. *
  2456. * @return {boolean}
  2457. * Whether or not the default behavior was prevented.
  2458. */
  2459. trigger: function trigger$$1(event, hash) {
  2460. return trigger(this.eventBusEl_, event, hash);
  2461. }
  2462. };
  2463. /**
  2464. * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
  2465. *
  2466. * @param {Object} target
  2467. * The object to which to add event methods.
  2468. *
  2469. * @param {Object} [options={}]
  2470. * Options for customizing the mixin behavior.
  2471. *
  2472. * @param {string} [options.eventBusKey]
  2473. * By default, adds a `eventBusEl_` DOM element to the target object,
  2474. * which is used as an event bus. If the target object already has a
  2475. * DOM element that should be used, pass its key here.
  2476. *
  2477. * @return {Object}
  2478. * The target object.
  2479. */
  2480. function evented(target, options) {
  2481. if (options === void 0) {
  2482. options = {};
  2483. }
  2484. var _options = options,
  2485. eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_.
  2486. if (eventBusKey) {
  2487. if (!target[eventBusKey].nodeName) {
  2488. throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element.");
  2489. }
  2490. target.eventBusEl_ = target[eventBusKey];
  2491. } else {
  2492. target.eventBusEl_ = createEl('span', {
  2493. className: 'vjs-event-bus'
  2494. });
  2495. }
  2496. assign(target, EventedMixin);
  2497. if (target.eventedCallbacks) {
  2498. target.eventedCallbacks.forEach(function (callback) {
  2499. callback();
  2500. });
  2501. } // When any evented object is disposed, it removes all its listeners.
  2502. target.on('dispose', function () {
  2503. target.off();
  2504. window$1.setTimeout(function () {
  2505. target.eventBusEl_ = null;
  2506. }, 0);
  2507. });
  2508. return target;
  2509. }
  2510. /**
  2511. * @file mixins/stateful.js
  2512. * @module stateful
  2513. */
  2514. /**
  2515. * Contains methods that provide statefulness to an object which is passed
  2516. * to {@link module:stateful}.
  2517. *
  2518. * @mixin StatefulMixin
  2519. */
  2520. var StatefulMixin = {
  2521. /**
  2522. * A hash containing arbitrary keys and values representing the state of
  2523. * the object.
  2524. *
  2525. * @type {Object}
  2526. */
  2527. state: {},
  2528. /**
  2529. * Set the state of an object by mutating its
  2530. * {@link module:stateful~StatefulMixin.state|state} object in place.
  2531. *
  2532. * @fires module:stateful~StatefulMixin#statechanged
  2533. * @param {Object|Function} stateUpdates
  2534. * A new set of properties to shallow-merge into the plugin state.
  2535. * Can be a plain object or a function returning a plain object.
  2536. *
  2537. * @return {Object|undefined}
  2538. * An object containing changes that occurred. If no changes
  2539. * occurred, returns `undefined`.
  2540. */
  2541. setState: function setState(stateUpdates) {
  2542. var _this = this;
  2543. // Support providing the `stateUpdates` state as a function.
  2544. if (typeof stateUpdates === 'function') {
  2545. stateUpdates = stateUpdates();
  2546. }
  2547. var changes;
  2548. each(stateUpdates, function (value, key) {
  2549. // Record the change if the value is different from what's in the
  2550. // current state.
  2551. if (_this.state[key] !== value) {
  2552. changes = changes || {};
  2553. changes[key] = {
  2554. from: _this.state[key],
  2555. to: value
  2556. };
  2557. }
  2558. _this.state[key] = value;
  2559. }); // Only trigger "statechange" if there were changes AND we have a trigger
  2560. // function. This allows us to not require that the target object be an
  2561. // evented object.
  2562. if (changes && isEvented(this)) {
  2563. /**
  2564. * An event triggered on an object that is both
  2565. * {@link module:stateful|stateful} and {@link module:evented|evented}
  2566. * indicating that its state has changed.
  2567. *
  2568. * @event module:stateful~StatefulMixin#statechanged
  2569. * @type {Object}
  2570. * @property {Object} changes
  2571. * A hash containing the properties that were changed and
  2572. * the values they were changed `from` and `to`.
  2573. */
  2574. this.trigger({
  2575. changes: changes,
  2576. type: 'statechanged'
  2577. });
  2578. }
  2579. return changes;
  2580. }
  2581. };
  2582. /**
  2583. * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
  2584. * object.
  2585. *
  2586. * If the target object is {@link module:evented|evented} and has a
  2587. * `handleStateChanged` method, that method will be automatically bound to the
  2588. * `statechanged` event on itself.
  2589. *
  2590. * @param {Object} target
  2591. * The object to be made stateful.
  2592. *
  2593. * @param {Object} [defaultState]
  2594. * A default set of properties to populate the newly-stateful object's
  2595. * `state` property.
  2596. *
  2597. * @return {Object}
  2598. * Returns the `target`.
  2599. */
  2600. function stateful(target, defaultState) {
  2601. assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state`
  2602. // added in that step.
  2603. target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists.
  2604. if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
  2605. target.on('statechanged', target.handleStateChanged);
  2606. }
  2607. return target;
  2608. }
  2609. /**
  2610. * @file to-title-case.js
  2611. * @module to-title-case
  2612. */
  2613. /**
  2614. * Uppercase the first letter of a string.
  2615. *
  2616. * @param {string} string
  2617. * String to be uppercased
  2618. *
  2619. * @return {string}
  2620. * The string with an uppercased first letter
  2621. */
  2622. function toTitleCase(string) {
  2623. if (typeof string !== 'string') {
  2624. return string;
  2625. }
  2626. return string.charAt(0).toUpperCase() + string.slice(1);
  2627. }
  2628. /**
  2629. * Compares the TitleCase versions of the two strings for equality.
  2630. *
  2631. * @param {string} str1
  2632. * The first string to compare
  2633. *
  2634. * @param {string} str2
  2635. * The second string to compare
  2636. *
  2637. * @return {boolean}
  2638. * Whether the TitleCase versions of the strings are equal
  2639. */
  2640. function titleCaseEquals(str1, str2) {
  2641. return toTitleCase(str1) === toTitleCase(str2);
  2642. }
  2643. /**
  2644. * @file merge-options.js
  2645. * @module merge-options
  2646. */
  2647. /**
  2648. * Merge two objects recursively.
  2649. *
  2650. * Performs a deep merge like
  2651. * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
  2652. * plain objects (not arrays, elements, or anything else).
  2653. *
  2654. * Non-plain object values will be copied directly from the right-most
  2655. * argument.
  2656. *
  2657. * @static
  2658. * @param {Object[]} sources
  2659. * One or more objects to merge into a new object.
  2660. *
  2661. * @return {Object}
  2662. * A new object that is the merged result of all sources.
  2663. */
  2664. function mergeOptions() {
  2665. var result = {};
  2666. for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
  2667. sources[_key] = arguments[_key];
  2668. }
  2669. sources.forEach(function (source) {
  2670. if (!source) {
  2671. return;
  2672. }
  2673. each(source, function (value, key) {
  2674. if (!isPlain(value)) {
  2675. result[key] = value;
  2676. return;
  2677. }
  2678. if (!isPlain(result[key])) {
  2679. result[key] = {};
  2680. }
  2681. result[key] = mergeOptions(result[key], value);
  2682. });
  2683. });
  2684. return result;
  2685. }
  2686. /**
  2687. * Player Component - Base class for all UI objects
  2688. *
  2689. * @file component.js
  2690. */
  2691. /**
  2692. * Base class for all UI Components.
  2693. * Components are UI objects which represent both a javascript object and an element
  2694. * in the DOM. They can be children of other components, and can have
  2695. * children themselves.
  2696. *
  2697. * Components can also use methods from {@link EventTarget}
  2698. */
  2699. var Component =
  2700. /*#__PURE__*/
  2701. function () {
  2702. /**
  2703. * A callback that is called when a component is ready. Does not have any
  2704. * paramters and any callback value will be ignored.
  2705. *
  2706. * @callback Component~ReadyCallback
  2707. * @this Component
  2708. */
  2709. /**
  2710. * Creates an instance of this class.
  2711. *
  2712. * @param {Player} player
  2713. * The `Player` that this class should be attached to.
  2714. *
  2715. * @param {Object} [options]
  2716. * The key/value store of player options.
  2717. *
  2718. * @param {Object[]} [options.children]
  2719. * An array of children objects to intialize this component with. Children objects have
  2720. * a name property that will be used if more than one component of the same type needs to be
  2721. * added.
  2722. *
  2723. * @param {Component~ReadyCallback} [ready]
  2724. * Function that gets called when the `Component` is ready.
  2725. */
  2726. function Component(player, options, ready) {
  2727. // The component might be the player itself and we can't pass `this` to super
  2728. if (!player && this.play) {
  2729. this.player_ = player = this; // eslint-disable-line
  2730. } else {
  2731. this.player_ = player;
  2732. } // Hold the reference to the parent component via `addChild` method
  2733. this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
  2734. this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options
  2735. options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied
  2736. this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one
  2737. if (!this.id_) {
  2738. // Don't require the player ID function in the case of mock players
  2739. var id = player && player.id && player.id() || 'no_player';
  2740. this.id_ = id + "_component_" + newGUID();
  2741. }
  2742. this.name_ = options.name || null; // Create element if one wasn't provided in options
  2743. if (options.el) {
  2744. this.el_ = options.el;
  2745. } else if (options.createEl !== false) {
  2746. this.el_ = this.createEl();
  2747. } // if evented is anything except false, we want to mixin in evented
  2748. if (options.evented !== false) {
  2749. // Make this an evented object and use `el_`, if available, as its event bus
  2750. evented(this, {
  2751. eventBusKey: this.el_ ? 'el_' : null
  2752. });
  2753. }
  2754. stateful(this, this.constructor.defaultState);
  2755. this.children_ = [];
  2756. this.childIndex_ = {};
  2757. this.childNameIndex_ = {}; // Add any child components in options
  2758. if (options.initChildren !== false) {
  2759. this.initChildren();
  2760. }
  2761. this.ready(ready); // Don't want to trigger ready here or it will before init is actually
  2762. // finished for all children that run this constructor
  2763. if (options.reportTouchActivity !== false) {
  2764. this.enableTouchActivity();
  2765. }
  2766. }
  2767. /**
  2768. * Dispose of the `Component` and all child components.
  2769. *
  2770. * @fires Component#dispose
  2771. */
  2772. var _proto = Component.prototype;
  2773. _proto.dispose = function dispose() {
  2774. /**
  2775. * Triggered when a `Component` is disposed.
  2776. *
  2777. * @event Component#dispose
  2778. * @type {EventTarget~Event}
  2779. *
  2780. * @property {boolean} [bubbles=false]
  2781. * set to false so that the close event does not
  2782. * bubble up
  2783. */
  2784. this.trigger({
  2785. type: 'dispose',
  2786. bubbles: false
  2787. }); // Dispose all children.
  2788. if (this.children_) {
  2789. for (var i = this.children_.length - 1; i >= 0; i--) {
  2790. if (this.children_[i].dispose) {
  2791. this.children_[i].dispose();
  2792. }
  2793. }
  2794. } // Delete child references
  2795. this.children_ = null;
  2796. this.childIndex_ = null;
  2797. this.childNameIndex_ = null;
  2798. this.parentComponent_ = null;
  2799. if (this.el_) {
  2800. // Remove element from DOM
  2801. if (this.el_.parentNode) {
  2802. this.el_.parentNode.removeChild(this.el_);
  2803. }
  2804. removeData(this.el_);
  2805. this.el_ = null;
  2806. } // remove reference to the player after disposing of the element
  2807. this.player_ = null;
  2808. }
  2809. /**
  2810. * Return the {@link Player} that the `Component` has attached to.
  2811. *
  2812. * @return {Player}
  2813. * The player that this `Component` has attached to.
  2814. */
  2815. ;
  2816. _proto.player = function player() {
  2817. return this.player_;
  2818. }
  2819. /**
  2820. * Deep merge of options objects with new options.
  2821. * > Note: When both `obj` and `options` contain properties whose values are objects.
  2822. * The two properties get merged using {@link module:mergeOptions}
  2823. *
  2824. * @param {Object} obj
  2825. * The object that contains new options.
  2826. *
  2827. * @return {Object}
  2828. * A new object of `this.options_` and `obj` merged together.
  2829. *
  2830. * @deprecated since version 5
  2831. */
  2832. ;
  2833. _proto.options = function options(obj) {
  2834. log.warn('this.options() has been deprecated and will be moved to the constructor in 6.0');
  2835. if (!obj) {
  2836. return this.options_;
  2837. }
  2838. this.options_ = mergeOptions(this.options_, obj);
  2839. return this.options_;
  2840. }
  2841. /**
  2842. * Get the `Component`s DOM element
  2843. *
  2844. * @return {Element}
  2845. * The DOM element for this `Component`.
  2846. */
  2847. ;
  2848. _proto.el = function el() {
  2849. return this.el_;
  2850. }
  2851. /**
  2852. * Create the `Component`s DOM element.
  2853. *
  2854. * @param {string} [tagName]
  2855. * Element's DOM node type. e.g. 'div'
  2856. *
  2857. * @param {Object} [properties]
  2858. * An object of properties that should be set.
  2859. *
  2860. * @param {Object} [attributes]
  2861. * An object of attributes that should be set.
  2862. *
  2863. * @return {Element}
  2864. * The element that gets created.
  2865. */
  2866. ;
  2867. _proto.createEl = function createEl$$1(tagName, properties, attributes) {
  2868. return createEl(tagName, properties, attributes);
  2869. }
  2870. /**
  2871. * Localize a string given the string in english.
  2872. *
  2873. * If tokens are provided, it'll try and run a simple token replacement on the provided string.
  2874. * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
  2875. *
  2876. * If a `defaultValue` is provided, it'll use that over `string`,
  2877. * if a value isn't found in provided language files.
  2878. * This is useful if you want to have a descriptive key for token replacement
  2879. * but have a succinct localized string and not require `en.json` to be included.
  2880. *
  2881. * Currently, it is used for the progress bar timing.
  2882. * ```js
  2883. * {
  2884. * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
  2885. * }
  2886. * ```
  2887. * It is then used like so:
  2888. * ```js
  2889. * this.localize('progress bar timing: currentTime={1} duration{2}',
  2890. * [this.player_.currentTime(), this.player_.duration()],
  2891. * '{1} of {2}');
  2892. * ```
  2893. *
  2894. * Which outputs something like: `01:23 of 24:56`.
  2895. *
  2896. *
  2897. * @param {string} string
  2898. * The string to localize and the key to lookup in the language files.
  2899. * @param {string[]} [tokens]
  2900. * If the current item has token replacements, provide the tokens here.
  2901. * @param {string} [defaultValue]
  2902. * Defaults to `string`. Can be a default value to use for token replacement
  2903. * if the lookup key is needed to be separate.
  2904. *
  2905. * @return {string}
  2906. * The localized string or if no localization exists the english string.
  2907. */
  2908. ;
  2909. _proto.localize = function localize(string, tokens, defaultValue) {
  2910. if (defaultValue === void 0) {
  2911. defaultValue = string;
  2912. }
  2913. var code = this.player_.language && this.player_.language();
  2914. var languages = this.player_.languages && this.player_.languages();
  2915. var language = languages && languages[code];
  2916. var primaryCode = code && code.split('-')[0];
  2917. var primaryLang = languages && languages[primaryCode];
  2918. var localizedString = defaultValue;
  2919. if (language && language[string]) {
  2920. localizedString = language[string];
  2921. } else if (primaryLang && primaryLang[string]) {
  2922. localizedString = primaryLang[string];
  2923. }
  2924. if (tokens) {
  2925. localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
  2926. var value = tokens[index - 1];
  2927. var ret = value;
  2928. if (typeof value === 'undefined') {
  2929. ret = match;
  2930. }
  2931. return ret;
  2932. });
  2933. }
  2934. return localizedString;
  2935. }
  2936. /**
  2937. * Return the `Component`s DOM element. This is where children get inserted.
  2938. * This will usually be the the same as the element returned in {@link Component#el}.
  2939. *
  2940. * @return {Element}
  2941. * The content element for this `Component`.
  2942. */
  2943. ;
  2944. _proto.contentEl = function contentEl() {
  2945. return this.contentEl_ || this.el_;
  2946. }
  2947. /**
  2948. * Get this `Component`s ID
  2949. *
  2950. * @return {string}
  2951. * The id of this `Component`
  2952. */
  2953. ;
  2954. _proto.id = function id() {
  2955. return this.id_;
  2956. }
  2957. /**
  2958. * Get the `Component`s name. The name gets used to reference the `Component`
  2959. * and is set during registration.
  2960. *
  2961. * @return {string}
  2962. * The name of this `Component`.
  2963. */
  2964. ;
  2965. _proto.name = function name() {
  2966. return this.name_;
  2967. }
  2968. /**
  2969. * Get an array of all child components
  2970. *
  2971. * @return {Array}
  2972. * The children
  2973. */
  2974. ;
  2975. _proto.children = function children() {
  2976. return this.children_;
  2977. }
  2978. /**
  2979. * Returns the child `Component` with the given `id`.
  2980. *
  2981. * @param {string} id
  2982. * The id of the child `Component` to get.
  2983. *
  2984. * @return {Component|undefined}
  2985. * The child `Component` with the given `id` or undefined.
  2986. */
  2987. ;
  2988. _proto.getChildById = function getChildById(id) {
  2989. return this.childIndex_[id];
  2990. }
  2991. /**
  2992. * Returns the child `Component` with the given `name`.
  2993. *
  2994. * @param {string} name
  2995. * The name of the child `Component` to get.
  2996. *
  2997. * @return {Component|undefined}
  2998. * The child `Component` with the given `name` or undefined.
  2999. */
  3000. ;
  3001. _proto.getChild = function getChild(name) {
  3002. if (!name) {
  3003. return;
  3004. }
  3005. name = toTitleCase(name);
  3006. return this.childNameIndex_[name];
  3007. }
  3008. /**
  3009. * Add a child `Component` inside the current `Component`.
  3010. *
  3011. *
  3012. * @param {string|Component} child
  3013. * The name or instance of a child to add.
  3014. *
  3015. * @param {Object} [options={}]
  3016. * The key/value store of options that will get passed to children of
  3017. * the child.
  3018. *
  3019. * @param {number} [index=this.children_.length]
  3020. * The index to attempt to add a child into.
  3021. *
  3022. * @return {Component}
  3023. * The `Component` that gets added as a child. When using a string the
  3024. * `Component` will get created by this process.
  3025. */
  3026. ;
  3027. _proto.addChild = function addChild(child, options, index) {
  3028. if (options === void 0) {
  3029. options = {};
  3030. }
  3031. if (index === void 0) {
  3032. index = this.children_.length;
  3033. }
  3034. var component;
  3035. var componentName; // If child is a string, create component with options
  3036. if (typeof child === 'string') {
  3037. componentName = toTitleCase(child);
  3038. var componentClassName = options.componentClass || componentName; // Set name through options
  3039. options.name = componentName; // Create a new object & element for this controls set
  3040. // If there's no .player_, this is a player
  3041. var ComponentClass = Component.getComponent(componentClassName);
  3042. if (!ComponentClass) {
  3043. throw new Error("Component " + componentClassName + " does not exist");
  3044. } // data stored directly on the videojs object may be
  3045. // misidentified as a component to retain
  3046. // backwards-compatibility with 4.x. check to make sure the
  3047. // component class can be instantiated.
  3048. if (typeof ComponentClass !== 'function') {
  3049. return null;
  3050. }
  3051. component = new ComponentClass(this.player_ || this, options); // child is a component instance
  3052. } else {
  3053. component = child;
  3054. }
  3055. if (component.parentComponent_) {
  3056. component.parentComponent_.removeChild(component);
  3057. }
  3058. this.children_.splice(index, 0, component);
  3059. component.parentComponent_ = this;
  3060. if (typeof component.id === 'function') {
  3061. this.childIndex_[component.id()] = component;
  3062. } // If a name wasn't used to create the component, check if we can use the
  3063. // name function of the component
  3064. componentName = componentName || component.name && toTitleCase(component.name());
  3065. if (componentName) {
  3066. this.childNameIndex_[componentName] = component;
  3067. } // Add the UI object's element to the container div (box)
  3068. // Having an element is not required
  3069. if (typeof component.el === 'function' && component.el()) {
  3070. var childNodes = this.contentEl().children;
  3071. var refNode = childNodes[index] || null;
  3072. this.contentEl().insertBefore(component.el(), refNode);
  3073. } // Return so it can stored on parent object if desired.
  3074. return component;
  3075. }
  3076. /**
  3077. * Remove a child `Component` from this `Component`s list of children. Also removes
  3078. * the child `Component`s element from this `Component`s element.
  3079. *
  3080. * @param {Component} component
  3081. * The child `Component` to remove.
  3082. */
  3083. ;
  3084. _proto.removeChild = function removeChild(component) {
  3085. if (typeof component === 'string') {
  3086. component = this.getChild(component);
  3087. }
  3088. if (!component || !this.children_) {
  3089. return;
  3090. }
  3091. var childFound = false;
  3092. for (var i = this.children_.length - 1; i >= 0; i--) {
  3093. if (this.children_[i] === component) {
  3094. childFound = true;
  3095. this.children_.splice(i, 1);
  3096. break;
  3097. }
  3098. }
  3099. if (!childFound) {
  3100. return;
  3101. }
  3102. component.parentComponent_ = null;
  3103. this.childIndex_[component.id()] = null;
  3104. this.childNameIndex_[component.name()] = null;
  3105. var compEl = component.el();
  3106. if (compEl && compEl.parentNode === this.contentEl()) {
  3107. this.contentEl().removeChild(component.el());
  3108. }
  3109. }
  3110. /**
  3111. * Add and initialize default child `Component`s based upon options.
  3112. */
  3113. ;
  3114. _proto.initChildren = function initChildren() {
  3115. var _this = this;
  3116. var children = this.options_.children;
  3117. if (children) {
  3118. // `this` is `parent`
  3119. var parentOptions = this.options_;
  3120. var handleAdd = function handleAdd(child) {
  3121. var name = child.name;
  3122. var opts = child.opts; // Allow options for children to be set at the parent options
  3123. // e.g. videojs(id, { controlBar: false });
  3124. // instead of videojs(id, { children: { controlBar: false });
  3125. if (parentOptions[name] !== undefined) {
  3126. opts = parentOptions[name];
  3127. } // Allow for disabling default components
  3128. // e.g. options['children']['posterImage'] = false
  3129. if (opts === false) {
  3130. return;
  3131. } // Allow options to be passed as a simple boolean if no configuration
  3132. // is necessary.
  3133. if (opts === true) {
  3134. opts = {};
  3135. } // We also want to pass the original player options
  3136. // to each component as well so they don't need to
  3137. // reach back into the player for options later.
  3138. opts.playerOptions = _this.options_.playerOptions; // Create and add the child component.
  3139. // Add a direct reference to the child by name on the parent instance.
  3140. // If two of the same component are used, different names should be supplied
  3141. // for each
  3142. var newChild = _this.addChild(name, opts);
  3143. if (newChild) {
  3144. _this[name] = newChild;
  3145. }
  3146. }; // Allow for an array of children details to passed in the options
  3147. var workingChildren;
  3148. var Tech = Component.getComponent('Tech');
  3149. if (Array.isArray(children)) {
  3150. workingChildren = children;
  3151. } else {
  3152. workingChildren = Object.keys(children);
  3153. }
  3154. workingChildren // children that are in this.options_ but also in workingChildren would
  3155. // give us extra children we do not want. So, we want to filter them out.
  3156. .concat(Object.keys(this.options_).filter(function (child) {
  3157. return !workingChildren.some(function (wchild) {
  3158. if (typeof wchild === 'string') {
  3159. return child === wchild;
  3160. }
  3161. return child === wchild.name;
  3162. });
  3163. })).map(function (child) {
  3164. var name;
  3165. var opts;
  3166. if (typeof child === 'string') {
  3167. name = child;
  3168. opts = children[name] || _this.options_[name] || {};
  3169. } else {
  3170. name = child.name;
  3171. opts = child;
  3172. }
  3173. return {
  3174. name: name,
  3175. opts: opts
  3176. };
  3177. }).filter(function (child) {
  3178. // we have to make sure that child.name isn't in the techOrder since
  3179. // techs are registerd as Components but can't aren't compatible
  3180. // See https://github.com/videojs/video.js/issues/2772
  3181. var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
  3182. return c && !Tech.isTech(c);
  3183. }).forEach(handleAdd);
  3184. }
  3185. }
  3186. /**
  3187. * Builds the default DOM class name. Should be overriden by sub-components.
  3188. *
  3189. * @return {string}
  3190. * The DOM class name for this object.
  3191. *
  3192. * @abstract
  3193. */
  3194. ;
  3195. _proto.buildCSSClass = function buildCSSClass() {
  3196. // Child classes can include a function that does:
  3197. // return 'CLASS NAME' + this._super();
  3198. return '';
  3199. }
  3200. /**
  3201. * Bind a listener to the component's ready state.
  3202. * Different from event listeners in that if the ready event has already happened
  3203. * it will trigger the function immediately.
  3204. *
  3205. * @return {Component}
  3206. * Returns itself; method can be chained.
  3207. */
  3208. ;
  3209. _proto.ready = function ready(fn, sync) {
  3210. if (sync === void 0) {
  3211. sync = false;
  3212. }
  3213. if (!fn) {
  3214. return;
  3215. }
  3216. if (!this.isReady_) {
  3217. this.readyQueue_ = this.readyQueue_ || [];
  3218. this.readyQueue_.push(fn);
  3219. return;
  3220. }
  3221. if (sync) {
  3222. fn.call(this);
  3223. } else {
  3224. // Call the function asynchronously by default for consistency
  3225. this.setTimeout(fn, 1);
  3226. }
  3227. }
  3228. /**
  3229. * Trigger all the ready listeners for this `Component`.
  3230. *
  3231. * @fires Component#ready
  3232. */
  3233. ;
  3234. _proto.triggerReady = function triggerReady() {
  3235. this.isReady_ = true; // Ensure ready is triggered asynchronously
  3236. this.setTimeout(function () {
  3237. var readyQueue = this.readyQueue_; // Reset Ready Queue
  3238. this.readyQueue_ = [];
  3239. if (readyQueue && readyQueue.length > 0) {
  3240. readyQueue.forEach(function (fn) {
  3241. fn.call(this);
  3242. }, this);
  3243. } // Allow for using event listeners also
  3244. /**
  3245. * Triggered when a `Component` is ready.
  3246. *
  3247. * @event Component#ready
  3248. * @type {EventTarget~Event}
  3249. */
  3250. this.trigger('ready');
  3251. }, 1);
  3252. }
  3253. /**
  3254. * Find a single DOM element matching a `selector`. This can be within the `Component`s
  3255. * `contentEl()` or another custom context.
  3256. *
  3257. * @param {string} selector
  3258. * A valid CSS selector, which will be passed to `querySelector`.
  3259. *
  3260. * @param {Element|string} [context=this.contentEl()]
  3261. * A DOM element within which to query. Can also be a selector string in
  3262. * which case the first matching element will get used as context. If
  3263. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3264. * nothing it falls back to `document`.
  3265. *
  3266. * @return {Element|null}
  3267. * the dom element that was found, or null
  3268. *
  3269. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3270. */
  3271. ;
  3272. _proto.$ = function $$$1(selector, context) {
  3273. return $(selector, context || this.contentEl());
  3274. }
  3275. /**
  3276. * Finds all DOM element matching a `selector`. This can be within the `Component`s
  3277. * `contentEl()` or another custom context.
  3278. *
  3279. * @param {string} selector
  3280. * A valid CSS selector, which will be passed to `querySelectorAll`.
  3281. *
  3282. * @param {Element|string} [context=this.contentEl()]
  3283. * A DOM element within which to query. Can also be a selector string in
  3284. * which case the first matching element will get used as context. If
  3285. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3286. * nothing it falls back to `document`.
  3287. *
  3288. * @return {NodeList}
  3289. * a list of dom elements that were found
  3290. *
  3291. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3292. */
  3293. ;
  3294. _proto.$$ = function $$$$1(selector, context) {
  3295. return $$(selector, context || this.contentEl());
  3296. }
  3297. /**
  3298. * Check if a component's element has a CSS class name.
  3299. *
  3300. * @param {string} classToCheck
  3301. * CSS class name to check.
  3302. *
  3303. * @return {boolean}
  3304. * - True if the `Component` has the class.
  3305. * - False if the `Component` does not have the class`
  3306. */
  3307. ;
  3308. _proto.hasClass = function hasClass$$1(classToCheck) {
  3309. return hasClass(this.el_, classToCheck);
  3310. }
  3311. /**
  3312. * Add a CSS class name to the `Component`s element.
  3313. *
  3314. * @param {string} classToAdd
  3315. * CSS class name to add
  3316. */
  3317. ;
  3318. _proto.addClass = function addClass$$1(classToAdd) {
  3319. addClass(this.el_, classToAdd);
  3320. }
  3321. /**
  3322. * Remove a CSS class name from the `Component`s element.
  3323. *
  3324. * @param {string} classToRemove
  3325. * CSS class name to remove
  3326. */
  3327. ;
  3328. _proto.removeClass = function removeClass$$1(classToRemove) {
  3329. removeClass(this.el_, classToRemove);
  3330. }
  3331. /**
  3332. * Add or remove a CSS class name from the component's element.
  3333. * - `classToToggle` gets added when {@link Component#hasClass} would return false.
  3334. * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
  3335. *
  3336. * @param {string} classToToggle
  3337. * The class to add or remove based on (@link Component#hasClass}
  3338. *
  3339. * @param {boolean|Dom~predicate} [predicate]
  3340. * An {@link Dom~predicate} function or a boolean
  3341. */
  3342. ;
  3343. _proto.toggleClass = function toggleClass$$1(classToToggle, predicate) {
  3344. toggleClass(this.el_, classToToggle, predicate);
  3345. }
  3346. /**
  3347. * Show the `Component`s element if it is hidden by removing the
  3348. * 'vjs-hidden' class name from it.
  3349. */
  3350. ;
  3351. _proto.show = function show() {
  3352. this.removeClass('vjs-hidden');
  3353. }
  3354. /**
  3355. * Hide the `Component`s element if it is currently showing by adding the
  3356. * 'vjs-hidden` class name to it.
  3357. */
  3358. ;
  3359. _proto.hide = function hide() {
  3360. this.addClass('vjs-hidden');
  3361. }
  3362. /**
  3363. * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
  3364. * class name to it. Used during fadeIn/fadeOut.
  3365. *
  3366. * @private
  3367. */
  3368. ;
  3369. _proto.lockShowing = function lockShowing() {
  3370. this.addClass('vjs-lock-showing');
  3371. }
  3372. /**
  3373. * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
  3374. * class name from it. Used during fadeIn/fadeOut.
  3375. *
  3376. * @private
  3377. */
  3378. ;
  3379. _proto.unlockShowing = function unlockShowing() {
  3380. this.removeClass('vjs-lock-showing');
  3381. }
  3382. /**
  3383. * Get the value of an attribute on the `Component`s element.
  3384. *
  3385. * @param {string} attribute
  3386. * Name of the attribute to get the value from.
  3387. *
  3388. * @return {string|null}
  3389. * - The value of the attribute that was asked for.
  3390. * - Can be an empty string on some browsers if the attribute does not exist
  3391. * or has no value
  3392. * - Most browsers will return null if the attibute does not exist or has
  3393. * no value.
  3394. *
  3395. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
  3396. */
  3397. ;
  3398. _proto.getAttribute = function getAttribute$$1(attribute) {
  3399. return getAttribute(this.el_, attribute);
  3400. }
  3401. /**
  3402. * Set the value of an attribute on the `Component`'s element
  3403. *
  3404. * @param {string} attribute
  3405. * Name of the attribute to set.
  3406. *
  3407. * @param {string} value
  3408. * Value to set the attribute to.
  3409. *
  3410. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
  3411. */
  3412. ;
  3413. _proto.setAttribute = function setAttribute$$1(attribute, value) {
  3414. setAttribute(this.el_, attribute, value);
  3415. }
  3416. /**
  3417. * Remove an attribute from the `Component`s element.
  3418. *
  3419. * @param {string} attribute
  3420. * Name of the attribute to remove.
  3421. *
  3422. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
  3423. */
  3424. ;
  3425. _proto.removeAttribute = function removeAttribute$$1(attribute) {
  3426. removeAttribute(this.el_, attribute);
  3427. }
  3428. /**
  3429. * Get or set the width of the component based upon the CSS styles.
  3430. * See {@link Component#dimension} for more detailed information.
  3431. *
  3432. * @param {number|string} [num]
  3433. * The width that you want to set postfixed with '%', 'px' or nothing.
  3434. *
  3435. * @param {boolean} [skipListeners]
  3436. * Skip the componentresize event trigger
  3437. *
  3438. * @return {number|string}
  3439. * The width when getting, zero if there is no width. Can be a string
  3440. * postpixed with '%' or 'px'.
  3441. */
  3442. ;
  3443. _proto.width = function width(num, skipListeners) {
  3444. return this.dimension('width', num, skipListeners);
  3445. }
  3446. /**
  3447. * Get or set the height of the component based upon the CSS styles.
  3448. * See {@link Component#dimension} for more detailed information.
  3449. *
  3450. * @param {number|string} [num]
  3451. * The height that you want to set postfixed with '%', 'px' or nothing.
  3452. *
  3453. * @param {boolean} [skipListeners]
  3454. * Skip the componentresize event trigger
  3455. *
  3456. * @return {number|string}
  3457. * The width when getting, zero if there is no width. Can be a string
  3458. * postpixed with '%' or 'px'.
  3459. */
  3460. ;
  3461. _proto.height = function height(num, skipListeners) {
  3462. return this.dimension('height', num, skipListeners);
  3463. }
  3464. /**
  3465. * Set both the width and height of the `Component` element at the same time.
  3466. *
  3467. * @param {number|string} width
  3468. * Width to set the `Component`s element to.
  3469. *
  3470. * @param {number|string} height
  3471. * Height to set the `Component`s element to.
  3472. */
  3473. ;
  3474. _proto.dimensions = function dimensions(width, height) {
  3475. // Skip componentresize listeners on width for optimization
  3476. this.width(width, true);
  3477. this.height(height);
  3478. }
  3479. /**
  3480. * Get or set width or height of the `Component` element. This is the shared code
  3481. * for the {@link Component#width} and {@link Component#height}.
  3482. *
  3483. * Things to know:
  3484. * - If the width or height in an number this will return the number postfixed with 'px'.
  3485. * - If the width/height is a percent this will return the percent postfixed with '%'
  3486. * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
  3487. * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
  3488. * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
  3489. * for more information
  3490. * - If you want the computed style of the component, use {@link Component#currentWidth}
  3491. * and {@link {Component#currentHeight}
  3492. *
  3493. * @fires Component#componentresize
  3494. *
  3495. * @param {string} widthOrHeight
  3496. 8 'width' or 'height'
  3497. *
  3498. * @param {number|string} [num]
  3499. 8 New dimension
  3500. *
  3501. * @param {boolean} [skipListeners]
  3502. * Skip componentresize event trigger
  3503. *
  3504. * @return {number}
  3505. * The dimension when getting or 0 if unset
  3506. */
  3507. ;
  3508. _proto.dimension = function dimension(widthOrHeight, num, skipListeners) {
  3509. if (num !== undefined) {
  3510. // Set to zero if null or literally NaN (NaN !== NaN)
  3511. if (num === null || num !== num) {
  3512. num = 0;
  3513. } // Check if using css width/height (% or px) and adjust
  3514. if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
  3515. this.el_.style[widthOrHeight] = num;
  3516. } else if (num === 'auto') {
  3517. this.el_.style[widthOrHeight] = '';
  3518. } else {
  3519. this.el_.style[widthOrHeight] = num + 'px';
  3520. } // skipListeners allows us to avoid triggering the resize event when setting both width and height
  3521. if (!skipListeners) {
  3522. /**
  3523. * Triggered when a component is resized.
  3524. *
  3525. * @event Component#componentresize
  3526. * @type {EventTarget~Event}
  3527. */
  3528. this.trigger('componentresize');
  3529. }
  3530. return;
  3531. } // Not setting a value, so getting it
  3532. // Make sure element exists
  3533. if (!this.el_) {
  3534. return 0;
  3535. } // Get dimension value from style
  3536. var val = this.el_.style[widthOrHeight];
  3537. var pxIndex = val.indexOf('px');
  3538. if (pxIndex !== -1) {
  3539. // Return the pixel value with no 'px'
  3540. return parseInt(val.slice(0, pxIndex), 10);
  3541. } // No px so using % or no style was set, so falling back to offsetWidth/height
  3542. // If component has display:none, offset will return 0
  3543. // TODO: handle display:none and no dimension style using px
  3544. return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
  3545. }
  3546. /**
  3547. * Get the computed width or the height of the component's element.
  3548. *
  3549. * Uses `window.getComputedStyle`.
  3550. *
  3551. * @param {string} widthOrHeight
  3552. * A string containing 'width' or 'height'. Whichever one you want to get.
  3553. *
  3554. * @return {number}
  3555. * The dimension that gets asked for or 0 if nothing was set
  3556. * for that dimension.
  3557. */
  3558. ;
  3559. _proto.currentDimension = function currentDimension(widthOrHeight) {
  3560. var computedWidthOrHeight = 0;
  3561. if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
  3562. throw new Error('currentDimension only accepts width or height value');
  3563. }
  3564. if (typeof window$1.getComputedStyle === 'function') {
  3565. var computedStyle = window$1.getComputedStyle(this.el_);
  3566. computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
  3567. } // remove 'px' from variable and parse as integer
  3568. computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying
  3569. // and we want to check the offset values.
  3570. // This code also runs wherever getComputedStyle doesn't exist.
  3571. if (computedWidthOrHeight === 0) {
  3572. var rule = "offset" + toTitleCase(widthOrHeight);
  3573. computedWidthOrHeight = this.el_[rule];
  3574. }
  3575. return computedWidthOrHeight;
  3576. }
  3577. /**
  3578. * An object that contains width and height values of the `Component`s
  3579. * computed style. Uses `window.getComputedStyle`.
  3580. *
  3581. * @typedef {Object} Component~DimensionObject
  3582. *
  3583. * @property {number} width
  3584. * The width of the `Component`s computed style.
  3585. *
  3586. * @property {number} height
  3587. * The height of the `Component`s computed style.
  3588. */
  3589. /**
  3590. * Get an object that contains computed width and height values of the
  3591. * component's element.
  3592. *
  3593. * Uses `window.getComputedStyle`.
  3594. *
  3595. * @return {Component~DimensionObject}
  3596. * The computed dimensions of the component's element.
  3597. */
  3598. ;
  3599. _proto.currentDimensions = function currentDimensions() {
  3600. return {
  3601. width: this.currentDimension('width'),
  3602. height: this.currentDimension('height')
  3603. };
  3604. }
  3605. /**
  3606. * Get the computed width of the component's element.
  3607. *
  3608. * Uses `window.getComputedStyle`.
  3609. *
  3610. * @return {number}
  3611. * The computed width of the component's element.
  3612. */
  3613. ;
  3614. _proto.currentWidth = function currentWidth() {
  3615. return this.currentDimension('width');
  3616. }
  3617. /**
  3618. * Get the computed height of the component's element.
  3619. *
  3620. * Uses `window.getComputedStyle`.
  3621. *
  3622. * @return {number}
  3623. * The computed height of the component's element.
  3624. */
  3625. ;
  3626. _proto.currentHeight = function currentHeight() {
  3627. return this.currentDimension('height');
  3628. }
  3629. /**
  3630. * Set the focus to this component
  3631. */
  3632. ;
  3633. _proto.focus = function focus() {
  3634. this.el_.focus();
  3635. }
  3636. /**
  3637. * Remove the focus from this component
  3638. */
  3639. ;
  3640. _proto.blur = function blur() {
  3641. this.el_.blur();
  3642. }
  3643. /**
  3644. * When this Component receives a keydown event which it does not process,
  3645. * it passes the event to the Player for handling.
  3646. *
  3647. * @param {EventTarget~Event} event
  3648. * The `keydown` event that caused this function to be called.
  3649. */
  3650. ;
  3651. _proto.handleKeyPress = function handleKeyPress(event) {
  3652. if (this.player_) {
  3653. this.player_.handleKeyPress(event);
  3654. }
  3655. }
  3656. /**
  3657. * Emit a 'tap' events when touch event support gets detected. This gets used to
  3658. * support toggling the controls through a tap on the video. They get enabled
  3659. * because every sub-component would have extra overhead otherwise.
  3660. *
  3661. * @private
  3662. * @fires Component#tap
  3663. * @listens Component#touchstart
  3664. * @listens Component#touchmove
  3665. * @listens Component#touchleave
  3666. * @listens Component#touchcancel
  3667. * @listens Component#touchend
  3668. */
  3669. ;
  3670. _proto.emitTapEvents = function emitTapEvents() {
  3671. // Track the start time so we can determine how long the touch lasted
  3672. var touchStart = 0;
  3673. var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap
  3674. // Other popular libs use anywhere from 2 (hammer.js) to 15,
  3675. // so 10 seems like a nice, round number.
  3676. var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap
  3677. var touchTimeThreshold = 200;
  3678. var couldBeTap;
  3679. this.on('touchstart', function (event) {
  3680. // If more than one finger, don't consider treating this as a click
  3681. if (event.touches.length === 1) {
  3682. // Copy pageX/pageY from the object
  3683. firstTouch = {
  3684. pageX: event.touches[0].pageX,
  3685. pageY: event.touches[0].pageY
  3686. }; // Record start time so we can detect a tap vs. "touch and hold"
  3687. touchStart = window$1.performance.now(); // Reset couldBeTap tracking
  3688. couldBeTap = true;
  3689. }
  3690. });
  3691. this.on('touchmove', function (event) {
  3692. // If more than one finger, don't consider treating this as a click
  3693. if (event.touches.length > 1) {
  3694. couldBeTap = false;
  3695. } else if (firstTouch) {
  3696. // Some devices will throw touchmoves for all but the slightest of taps.
  3697. // So, if we moved only a small distance, this could still be a tap
  3698. var xdiff = event.touches[0].pageX - firstTouch.pageX;
  3699. var ydiff = event.touches[0].pageY - firstTouch.pageY;
  3700. var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
  3701. if (touchDistance > tapMovementThreshold) {
  3702. couldBeTap = false;
  3703. }
  3704. }
  3705. });
  3706. var noTap = function noTap() {
  3707. couldBeTap = false;
  3708. }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
  3709. this.on('touchleave', noTap);
  3710. this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate
  3711. // event
  3712. this.on('touchend', function (event) {
  3713. firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen
  3714. if (couldBeTap === true) {
  3715. // Measure how long the touch lasted
  3716. var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
  3717. if (touchTime < touchTimeThreshold) {
  3718. // Don't let browser turn this into a click
  3719. event.preventDefault();
  3720. /**
  3721. * Triggered when a `Component` is tapped.
  3722. *
  3723. * @event Component#tap
  3724. * @type {EventTarget~Event}
  3725. */
  3726. this.trigger('tap'); // It may be good to copy the touchend event object and change the
  3727. // type to tap, if the other event properties aren't exact after
  3728. // Events.fixEvent runs (e.g. event.target)
  3729. }
  3730. }
  3731. });
  3732. }
  3733. /**
  3734. * This function reports user activity whenever touch events happen. This can get
  3735. * turned off by any sub-components that wants touch events to act another way.
  3736. *
  3737. * Report user touch activity when touch events occur. User activity gets used to
  3738. * determine when controls should show/hide. It is simple when it comes to mouse
  3739. * events, because any mouse event should show the controls. So we capture mouse
  3740. * events that bubble up to the player and report activity when that happens.
  3741. * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
  3742. * controls. So touch events can't help us at the player level either.
  3743. *
  3744. * User activity gets checked asynchronously. So what could happen is a tap event
  3745. * on the video turns the controls off. Then the `touchend` event bubbles up to
  3746. * the player. Which, if it reported user activity, would turn the controls right
  3747. * back on. We also don't want to completely block touch events from bubbling up.
  3748. * Furthermore a `touchmove` event and anything other than a tap, should not turn
  3749. * controls back on.
  3750. *
  3751. * @listens Component#touchstart
  3752. * @listens Component#touchmove
  3753. * @listens Component#touchend
  3754. * @listens Component#touchcancel
  3755. */
  3756. ;
  3757. _proto.enableTouchActivity = function enableTouchActivity() {
  3758. // Don't continue if the root player doesn't support reporting user activity
  3759. if (!this.player() || !this.player().reportUserActivity) {
  3760. return;
  3761. } // listener for reporting that the user is active
  3762. var report = bind(this.player(), this.player().reportUserActivity);
  3763. var touchHolding;
  3764. this.on('touchstart', function () {
  3765. report(); // For as long as the they are touching the device or have their mouse down,
  3766. // we consider them active even if they're not moving their finger or mouse.
  3767. // So we want to continue to update that they are active
  3768. this.clearInterval(touchHolding); // report at the same interval as activityCheck
  3769. touchHolding = this.setInterval(report, 250);
  3770. });
  3771. var touchEnd = function touchEnd(event) {
  3772. report(); // stop the interval that maintains activity if the touch is holding
  3773. this.clearInterval(touchHolding);
  3774. };
  3775. this.on('touchmove', report);
  3776. this.on('touchend', touchEnd);
  3777. this.on('touchcancel', touchEnd);
  3778. }
  3779. /**
  3780. * A callback that has no parameters and is bound into `Component`s context.
  3781. *
  3782. * @callback Component~GenericCallback
  3783. * @this Component
  3784. */
  3785. /**
  3786. * Creates a function that runs after an `x` millisecond timeout. This function is a
  3787. * wrapper around `window.setTimeout`. There are a few reasons to use this one
  3788. * instead though:
  3789. * 1. It gets cleared via {@link Component#clearTimeout} when
  3790. * {@link Component#dispose} gets called.
  3791. * 2. The function callback will gets turned into a {@link Component~GenericCallback}
  3792. *
  3793. * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
  3794. * will cause its dispose listener not to get cleaned up! Please use
  3795. * {@link Component#clearTimeout} or {@link Component#dispose} instead.
  3796. *
  3797. * @param {Component~GenericCallback} fn
  3798. * The function that will be run after `timeout`.
  3799. *
  3800. * @param {number} timeout
  3801. * Timeout in milliseconds to delay before executing the specified function.
  3802. *
  3803. * @return {number}
  3804. * Returns a timeout ID that gets used to identify the timeout. It can also
  3805. * get used in {@link Component#clearTimeout} to clear the timeout that
  3806. * was set.
  3807. *
  3808. * @listens Component#dispose
  3809. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
  3810. */
  3811. ;
  3812. _proto.setTimeout = function setTimeout(fn, timeout) {
  3813. var _this2 = this;
  3814. // declare as variables so they are properly available in timeout function
  3815. // eslint-disable-next-line
  3816. var timeoutId, disposeFn;
  3817. fn = bind(this, fn);
  3818. timeoutId = window$1.setTimeout(function () {
  3819. _this2.off('dispose', disposeFn);
  3820. fn();
  3821. }, timeout);
  3822. disposeFn = function disposeFn() {
  3823. return _this2.clearTimeout(timeoutId);
  3824. };
  3825. disposeFn.guid = "vjs-timeout-" + timeoutId;
  3826. this.on('dispose', disposeFn);
  3827. return timeoutId;
  3828. }
  3829. /**
  3830. * Clears a timeout that gets created via `window.setTimeout` or
  3831. * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
  3832. * use this function instead of `window.clearTimout`. If you don't your dispose
  3833. * listener will not get cleaned up until {@link Component#dispose}!
  3834. *
  3835. * @param {number} timeoutId
  3836. * The id of the timeout to clear. The return value of
  3837. * {@link Component#setTimeout} or `window.setTimeout`.
  3838. *
  3839. * @return {number}
  3840. * Returns the timeout id that was cleared.
  3841. *
  3842. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
  3843. */
  3844. ;
  3845. _proto.clearTimeout = function clearTimeout(timeoutId) {
  3846. window$1.clearTimeout(timeoutId);
  3847. var disposeFn = function disposeFn() {};
  3848. disposeFn.guid = "vjs-timeout-" + timeoutId;
  3849. this.off('dispose', disposeFn);
  3850. return timeoutId;
  3851. }
  3852. /**
  3853. * Creates a function that gets run every `x` milliseconds. This function is a wrapper
  3854. * around `window.setInterval`. There are a few reasons to use this one instead though.
  3855. * 1. It gets cleared via {@link Component#clearInterval} when
  3856. * {@link Component#dispose} gets called.
  3857. * 2. The function callback will be a {@link Component~GenericCallback}
  3858. *
  3859. * @param {Component~GenericCallback} fn
  3860. * The function to run every `x` seconds.
  3861. *
  3862. * @param {number} interval
  3863. * Execute the specified function every `x` milliseconds.
  3864. *
  3865. * @return {number}
  3866. * Returns an id that can be used to identify the interval. It can also be be used in
  3867. * {@link Component#clearInterval} to clear the interval.
  3868. *
  3869. * @listens Component#dispose
  3870. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
  3871. */
  3872. ;
  3873. _proto.setInterval = function setInterval(fn, interval) {
  3874. var _this3 = this;
  3875. fn = bind(this, fn);
  3876. var intervalId = window$1.setInterval(fn, interval);
  3877. var disposeFn = function disposeFn() {
  3878. return _this3.clearInterval(intervalId);
  3879. };
  3880. disposeFn.guid = "vjs-interval-" + intervalId;
  3881. this.on('dispose', disposeFn);
  3882. return intervalId;
  3883. }
  3884. /**
  3885. * Clears an interval that gets created via `window.setInterval` or
  3886. * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
  3887. * use this function instead of `window.clearInterval`. If you don't your dispose
  3888. * listener will not get cleaned up until {@link Component#dispose}!
  3889. *
  3890. * @param {number} intervalId
  3891. * The id of the interval to clear. The return value of
  3892. * {@link Component#setInterval} or `window.setInterval`.
  3893. *
  3894. * @return {number}
  3895. * Returns the interval id that was cleared.
  3896. *
  3897. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
  3898. */
  3899. ;
  3900. _proto.clearInterval = function clearInterval(intervalId) {
  3901. window$1.clearInterval(intervalId);
  3902. var disposeFn = function disposeFn() {};
  3903. disposeFn.guid = "vjs-interval-" + intervalId;
  3904. this.off('dispose', disposeFn);
  3905. return intervalId;
  3906. }
  3907. /**
  3908. * Queues up a callback to be passed to requestAnimationFrame (rAF), but
  3909. * with a few extra bonuses:
  3910. *
  3911. * - Supports browsers that do not support rAF by falling back to
  3912. * {@link Component#setTimeout}.
  3913. *
  3914. * - The callback is turned into a {@link Component~GenericCallback} (i.e.
  3915. * bound to the component).
  3916. *
  3917. * - Automatic cancellation of the rAF callback is handled if the component
  3918. * is disposed before it is called.
  3919. *
  3920. * @param {Component~GenericCallback} fn
  3921. * A function that will be bound to this component and executed just
  3922. * before the browser's next repaint.
  3923. *
  3924. * @return {number}
  3925. * Returns an rAF ID that gets used to identify the timeout. It can
  3926. * also be used in {@link Component#cancelAnimationFrame} to cancel
  3927. * the animation frame callback.
  3928. *
  3929. * @listens Component#dispose
  3930. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
  3931. */
  3932. ;
  3933. _proto.requestAnimationFrame = function requestAnimationFrame(fn) {
  3934. var _this4 = this;
  3935. // declare as variables so they are properly available in rAF function
  3936. // eslint-disable-next-line
  3937. var id, disposeFn;
  3938. if (this.supportsRaf_) {
  3939. fn = bind(this, fn);
  3940. id = window$1.requestAnimationFrame(function () {
  3941. _this4.off('dispose', disposeFn);
  3942. fn();
  3943. });
  3944. disposeFn = function disposeFn() {
  3945. return _this4.cancelAnimationFrame(id);
  3946. };
  3947. disposeFn.guid = "vjs-raf-" + id;
  3948. this.on('dispose', disposeFn);
  3949. return id;
  3950. } // Fall back to using a timer.
  3951. return this.setTimeout(fn, 1000 / 60);
  3952. }
  3953. /**
  3954. * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
  3955. * (rAF).
  3956. *
  3957. * If you queue an rAF callback via {@link Component#requestAnimationFrame},
  3958. * use this function instead of `window.cancelAnimationFrame`. If you don't,
  3959. * your dispose listener will not get cleaned up until {@link Component#dispose}!
  3960. *
  3961. * @param {number} id
  3962. * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
  3963. *
  3964. * @return {number}
  3965. * Returns the rAF ID that was cleared.
  3966. *
  3967. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
  3968. */
  3969. ;
  3970. _proto.cancelAnimationFrame = function cancelAnimationFrame(id) {
  3971. if (this.supportsRaf_) {
  3972. window$1.cancelAnimationFrame(id);
  3973. var disposeFn = function disposeFn() {};
  3974. disposeFn.guid = "vjs-raf-" + id;
  3975. this.off('dispose', disposeFn);
  3976. return id;
  3977. } // Fall back to using a timer.
  3978. return this.clearTimeout(id);
  3979. }
  3980. /**
  3981. * Register a `Component` with `videojs` given the name and the component.
  3982. *
  3983. * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
  3984. * should be registered using {@link Tech.registerTech} or
  3985. * {@link videojs:videojs.registerTech}.
  3986. *
  3987. * > NOTE: This function can also be seen on videojs as
  3988. * {@link videojs:videojs.registerComponent}.
  3989. *
  3990. * @param {string} name
  3991. * The name of the `Component` to register.
  3992. *
  3993. * @param {Component} ComponentToRegister
  3994. * The `Component` class to register.
  3995. *
  3996. * @return {Component}
  3997. * The `Component` that was registered.
  3998. */
  3999. ;
  4000. Component.registerComponent = function registerComponent(name, ComponentToRegister) {
  4001. if (typeof name !== 'string' || !name) {
  4002. throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string.");
  4003. }
  4004. var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered.
  4005. var isTech = Tech && Tech.isTech(ComponentToRegister);
  4006. var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
  4007. if (isTech || !isComp) {
  4008. var reason;
  4009. if (isTech) {
  4010. reason = 'techs must be registered using Tech.registerTech()';
  4011. } else {
  4012. reason = 'must be a Component subclass';
  4013. }
  4014. throw new Error("Illegal component, \"" + name + "\"; " + reason + ".");
  4015. }
  4016. name = toTitleCase(name);
  4017. if (!Component.components_) {
  4018. Component.components_ = {};
  4019. }
  4020. var Player = Component.getComponent('Player');
  4021. if (name === 'Player' && Player && Player.players) {
  4022. var players = Player.players;
  4023. var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be
  4024. // in Players.players. So, we must loop through and verify that the value
  4025. // for each item is not null. This allows registration of the Player component
  4026. // after all players have been disposed or before any were created.
  4027. if (players && playerNames.length > 0 && playerNames.map(function (pname) {
  4028. return players[pname];
  4029. }).every(Boolean)) {
  4030. throw new Error('Can not register Player component after player has been created.');
  4031. }
  4032. }
  4033. Component.components_[name] = ComponentToRegister;
  4034. return ComponentToRegister;
  4035. }
  4036. /**
  4037. * Get a `Component` based on the name it was registered with.
  4038. *
  4039. * @param {string} name
  4040. * The Name of the component to get.
  4041. *
  4042. * @return {Component}
  4043. * The `Component` that got registered under the given name.
  4044. *
  4045. * @deprecated In `videojs` 6 this will not return `Component`s that were not
  4046. * registered using {@link Component.registerComponent}. Currently we
  4047. * check the global `videojs` object for a `Component` name and
  4048. * return that if it exists.
  4049. */
  4050. ;
  4051. Component.getComponent = function getComponent(name) {
  4052. if (!name) {
  4053. return;
  4054. }
  4055. name = toTitleCase(name);
  4056. if (Component.components_ && Component.components_[name]) {
  4057. return Component.components_[name];
  4058. }
  4059. };
  4060. return Component;
  4061. }();
  4062. /**
  4063. * Whether or not this component supports `requestAnimationFrame`.
  4064. *
  4065. * This is exposed primarily for testing purposes.
  4066. *
  4067. * @private
  4068. * @type {Boolean}
  4069. */
  4070. Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function';
  4071. Component.registerComponent('Component', Component);
  4072. /**
  4073. * @file browser.js
  4074. * @module browser
  4075. */
  4076. var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
  4077. var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
  4078. var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
  4079. /**
  4080. * Whether or not this device is an iPad.
  4081. *
  4082. * @static
  4083. * @const
  4084. * @type {Boolean}
  4085. */
  4086. var IS_IPAD = /iPad/i.test(USER_AGENT);
  4087. /**
  4088. * Whether or not this device is an iPhone.
  4089. *
  4090. * @static
  4091. * @const
  4092. * @type {Boolean}
  4093. */
  4094. // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
  4095. // to identify iPhones, we need to exclude iPads.
  4096. // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
  4097. var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
  4098. /**
  4099. * Whether or not this device is an iPod.
  4100. *
  4101. * @static
  4102. * @const
  4103. * @type {Boolean}
  4104. */
  4105. var IS_IPOD = /iPod/i.test(USER_AGENT);
  4106. /**
  4107. * Whether or not this is an iOS device.
  4108. *
  4109. * @static
  4110. * @const
  4111. * @type {Boolean}
  4112. */
  4113. var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
  4114. /**
  4115. * The detected iOS version - or `null`.
  4116. *
  4117. * @static
  4118. * @const
  4119. * @type {string|null}
  4120. */
  4121. var IOS_VERSION = function () {
  4122. var match = USER_AGENT.match(/OS (\d+)_/i);
  4123. if (match && match[1]) {
  4124. return match[1];
  4125. }
  4126. return null;
  4127. }();
  4128. /**
  4129. * Whether or not this is an Android device.
  4130. *
  4131. * @static
  4132. * @const
  4133. * @type {Boolean}
  4134. */
  4135. var IS_ANDROID = /Android/i.test(USER_AGENT);
  4136. /**
  4137. * The detected Android version - or `null`.
  4138. *
  4139. * @static
  4140. * @const
  4141. * @type {number|string|null}
  4142. */
  4143. var ANDROID_VERSION = function () {
  4144. // This matches Android Major.Minor.Patch versions
  4145. // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  4146. var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
  4147. if (!match) {
  4148. return null;
  4149. }
  4150. var major = match[1] && parseFloat(match[1]);
  4151. var minor = match[2] && parseFloat(match[2]);
  4152. if (major && minor) {
  4153. return parseFloat(match[1] + '.' + match[2]);
  4154. } else if (major) {
  4155. return major;
  4156. }
  4157. return null;
  4158. }();
  4159. /**
  4160. * Whether or not this is a native Android browser.
  4161. *
  4162. * @static
  4163. * @const
  4164. * @type {Boolean}
  4165. */
  4166. var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
  4167. /**
  4168. * Whether or not this is Mozilla Firefox.
  4169. *
  4170. * @static
  4171. * @const
  4172. * @type {Boolean}
  4173. */
  4174. var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
  4175. /**
  4176. * Whether or not this is Microsoft Edge.
  4177. *
  4178. * @static
  4179. * @const
  4180. * @type {Boolean}
  4181. */
  4182. var IS_EDGE = /Edge/i.test(USER_AGENT);
  4183. /**
  4184. * Whether or not this is Google Chrome.
  4185. *
  4186. * This will also be `true` for Chrome on iOS, which will have different support
  4187. * as it is actually Safari under the hood.
  4188. *
  4189. * @static
  4190. * @const
  4191. * @type {Boolean}
  4192. */
  4193. var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
  4194. /**
  4195. * The detected Google Chrome version - or `null`.
  4196. *
  4197. * @static
  4198. * @const
  4199. * @type {number|null}
  4200. */
  4201. var CHROME_VERSION = function () {
  4202. var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
  4203. if (match && match[2]) {
  4204. return parseFloat(match[2]);
  4205. }
  4206. return null;
  4207. }();
  4208. /**
  4209. * The detected Internet Explorer version - or `null`.
  4210. *
  4211. * @static
  4212. * @const
  4213. * @type {number|null}
  4214. */
  4215. var IE_VERSION = function () {
  4216. var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
  4217. var version = result && parseFloat(result[1]);
  4218. if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
  4219. // IE 11 has a different user agent string than other IE versions
  4220. version = 11.0;
  4221. }
  4222. return version;
  4223. }();
  4224. /**
  4225. * Whether or not this is desktop Safari.
  4226. *
  4227. * @static
  4228. * @const
  4229. * @type {Boolean}
  4230. */
  4231. var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
  4232. /**
  4233. * Whether or not this is any flavor of Safari - including iOS.
  4234. *
  4235. * @static
  4236. * @const
  4237. * @type {Boolean}
  4238. */
  4239. var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
  4240. /**
  4241. * Whether or not this is a Windows machine.
  4242. *
  4243. * @static
  4244. * @const
  4245. * @type {Boolean}
  4246. */
  4247. var IS_WINDOWS = /Windows/i.test(USER_AGENT);
  4248. /**
  4249. * Whether or not this device is touch-enabled.
  4250. *
  4251. * @static
  4252. * @const
  4253. * @type {Boolean}
  4254. */
  4255. var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch);
  4256. var browser = /*#__PURE__*/Object.freeze({
  4257. IS_IPAD: IS_IPAD,
  4258. IS_IPHONE: IS_IPHONE,
  4259. IS_IPOD: IS_IPOD,
  4260. IS_IOS: IS_IOS,
  4261. IOS_VERSION: IOS_VERSION,
  4262. IS_ANDROID: IS_ANDROID,
  4263. ANDROID_VERSION: ANDROID_VERSION,
  4264. IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
  4265. IS_FIREFOX: IS_FIREFOX,
  4266. IS_EDGE: IS_EDGE,
  4267. IS_CHROME: IS_CHROME,
  4268. CHROME_VERSION: CHROME_VERSION,
  4269. IE_VERSION: IE_VERSION,
  4270. IS_SAFARI: IS_SAFARI,
  4271. IS_ANY_SAFARI: IS_ANY_SAFARI,
  4272. IS_WINDOWS: IS_WINDOWS,
  4273. TOUCH_ENABLED: TOUCH_ENABLED
  4274. });
  4275. /**
  4276. * @file time-ranges.js
  4277. * @module time-ranges
  4278. */
  4279. /**
  4280. * Returns the time for the specified index at the start or end
  4281. * of a TimeRange object.
  4282. *
  4283. * @typedef {Function} TimeRangeIndex
  4284. *
  4285. * @param {number} [index=0]
  4286. * The range number to return the time for.
  4287. *
  4288. * @return {number}
  4289. * The time offset at the specified index.
  4290. *
  4291. * @deprecated The index argument must be provided.
  4292. * In the future, leaving it out will throw an error.
  4293. */
  4294. /**
  4295. * An object that contains ranges of time.
  4296. *
  4297. * @typedef {Object} TimeRange
  4298. *
  4299. * @property {number} length
  4300. * The number of time ranges represented by this object.
  4301. *
  4302. * @property {module:time-ranges~TimeRangeIndex} start
  4303. * Returns the time offset at which a specified time range begins.
  4304. *
  4305. * @property {module:time-ranges~TimeRangeIndex} end
  4306. * Returns the time offset at which a specified time range ends.
  4307. *
  4308. * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
  4309. */
  4310. /**
  4311. * Check if any of the time ranges are over the maximum index.
  4312. *
  4313. * @private
  4314. * @param {string} fnName
  4315. * The function name to use for logging
  4316. *
  4317. * @param {number} index
  4318. * The index to check
  4319. *
  4320. * @param {number} maxIndex
  4321. * The maximum possible index
  4322. *
  4323. * @throws {Error} if the timeRanges provided are over the maxIndex
  4324. */
  4325. function rangeCheck(fnName, index, maxIndex) {
  4326. if (typeof index !== 'number' || index < 0 || index > maxIndex) {
  4327. throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
  4328. }
  4329. }
  4330. /**
  4331. * Get the time for the specified index at the start or end
  4332. * of a TimeRange object.
  4333. *
  4334. * @private
  4335. * @param {string} fnName
  4336. * The function name to use for logging
  4337. *
  4338. * @param {string} valueIndex
  4339. * The property that should be used to get the time. should be
  4340. * 'start' or 'end'
  4341. *
  4342. * @param {Array} ranges
  4343. * An array of time ranges
  4344. *
  4345. * @param {Array} [rangeIndex=0]
  4346. * The index to start the search at
  4347. *
  4348. * @return {number}
  4349. * The time that offset at the specified index.
  4350. *
  4351. * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
  4352. * @throws {Error} if rangeIndex is more than the length of ranges
  4353. */
  4354. function getRange(fnName, valueIndex, ranges, rangeIndex) {
  4355. rangeCheck(fnName, rangeIndex, ranges.length - 1);
  4356. return ranges[rangeIndex][valueIndex];
  4357. }
  4358. /**
  4359. * Create a time range object given ranges of time.
  4360. *
  4361. * @private
  4362. * @param {Array} [ranges]
  4363. * An array of time ranges.
  4364. */
  4365. function createTimeRangesObj(ranges) {
  4366. if (ranges === undefined || ranges.length === 0) {
  4367. return {
  4368. length: 0,
  4369. start: function start() {
  4370. throw new Error('This TimeRanges object is empty');
  4371. },
  4372. end: function end() {
  4373. throw new Error('This TimeRanges object is empty');
  4374. }
  4375. };
  4376. }
  4377. return {
  4378. length: ranges.length,
  4379. start: getRange.bind(null, 'start', 0, ranges),
  4380. end: getRange.bind(null, 'end', 1, ranges)
  4381. };
  4382. }
  4383. /**
  4384. * Create a `TimeRange` object which mimics an
  4385. * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
  4386. *
  4387. * @param {number|Array[]} start
  4388. * The start of a single range (a number) or an array of ranges (an
  4389. * array of arrays of two numbers each).
  4390. *
  4391. * @param {number} end
  4392. * The end of a single range. Cannot be used with the array form of
  4393. * the `start` argument.
  4394. */
  4395. function createTimeRanges(start, end) {
  4396. if (Array.isArray(start)) {
  4397. return createTimeRangesObj(start);
  4398. } else if (start === undefined || end === undefined) {
  4399. return createTimeRangesObj();
  4400. }
  4401. return createTimeRangesObj([[start, end]]);
  4402. }
  4403. /**
  4404. * @file buffer.js
  4405. * @module buffer
  4406. */
  4407. /**
  4408. * Compute the percentage of the media that has been buffered.
  4409. *
  4410. * @param {TimeRange} buffered
  4411. * The current `TimeRange` object representing buffered time ranges
  4412. *
  4413. * @param {number} duration
  4414. * Total duration of the media
  4415. *
  4416. * @return {number}
  4417. * Percent buffered of the total duration in decimal form.
  4418. */
  4419. function bufferedPercent(buffered, duration) {
  4420. var bufferedDuration = 0;
  4421. var start;
  4422. var end;
  4423. if (!duration) {
  4424. return 0;
  4425. }
  4426. if (!buffered || !buffered.length) {
  4427. buffered = createTimeRanges(0, 0);
  4428. }
  4429. for (var i = 0; i < buffered.length; i++) {
  4430. start = buffered.start(i);
  4431. end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
  4432. if (end > duration) {
  4433. end = duration;
  4434. }
  4435. bufferedDuration += end - start;
  4436. }
  4437. return bufferedDuration / duration;
  4438. }
  4439. /**
  4440. * @file fullscreen-api.js
  4441. * @module fullscreen-api
  4442. * @private
  4443. */
  4444. /**
  4445. * Store the browser-specific methods for the fullscreen API.
  4446. *
  4447. * @type {Object}
  4448. * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
  4449. * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
  4450. */
  4451. var FullscreenApi = {}; // browser API methods
  4452. var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
  4453. ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
  4454. ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
  4455. ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
  4456. var specApi = apiMap[0];
  4457. var browserApi;
  4458. var prefixedAPI = false; // determine the supported set of functions
  4459. for (var i = 0; i < apiMap.length; i++) {
  4460. // check for exitFullscreen function
  4461. if (apiMap[i][1] in document) {
  4462. browserApi = apiMap[i];
  4463. break;
  4464. }
  4465. } // map the browser API names to the spec API names
  4466. if (browserApi) {
  4467. for (var _i = 0; _i < browserApi.length; _i++) {
  4468. FullscreenApi[specApi[_i]] = browserApi[_i];
  4469. }
  4470. prefixedAPI = browserApi[0] === specApi[0];
  4471. }
  4472. /**
  4473. * @file media-error.js
  4474. */
  4475. /**
  4476. * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
  4477. *
  4478. * @param {number|string|Object|MediaError} value
  4479. * This can be of multiple types:
  4480. * - number: should be a standard error code
  4481. * - string: an error message (the code will be 0)
  4482. * - Object: arbitrary properties
  4483. * - `MediaError` (native): used to populate a video.js `MediaError` object
  4484. * - `MediaError` (video.js): will return itself if it's already a
  4485. * video.js `MediaError` object.
  4486. *
  4487. * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
  4488. * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
  4489. *
  4490. * @class MediaError
  4491. */
  4492. function MediaError(value) {
  4493. // Allow redundant calls to this constructor to avoid having `instanceof`
  4494. // checks peppered around the code.
  4495. if (value instanceof MediaError) {
  4496. return value;
  4497. }
  4498. if (typeof value === 'number') {
  4499. this.code = value;
  4500. } else if (typeof value === 'string') {
  4501. // default code is zero, so this is a custom error
  4502. this.message = value;
  4503. } else if (isObject(value)) {
  4504. // We assign the `code` property manually because native `MediaError` objects
  4505. // do not expose it as an own/enumerable property of the object.
  4506. if (typeof value.code === 'number') {
  4507. this.code = value.code;
  4508. }
  4509. assign(this, value);
  4510. }
  4511. if (!this.message) {
  4512. this.message = MediaError.defaultMessages[this.code] || '';
  4513. }
  4514. }
  4515. /**
  4516. * The error code that refers two one of the defined `MediaError` types
  4517. *
  4518. * @type {Number}
  4519. */
  4520. MediaError.prototype.code = 0;
  4521. /**
  4522. * An optional message that to show with the error. Message is not part of the HTML5
  4523. * video spec but allows for more informative custom errors.
  4524. *
  4525. * @type {String}
  4526. */
  4527. MediaError.prototype.message = '';
  4528. /**
  4529. * An optional status code that can be set by plugins to allow even more detail about
  4530. * the error. For example a plugin might provide a specific HTTP status code and an
  4531. * error message for that code. Then when the plugin gets that error this class will
  4532. * know how to display an error message for it. This allows a custom message to show
  4533. * up on the `Player` error overlay.
  4534. *
  4535. * @type {Array}
  4536. */
  4537. MediaError.prototype.status = null;
  4538. /**
  4539. * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
  4540. * specification listed under {@link MediaError} for more information.
  4541. *
  4542. * @enum {array}
  4543. * @readonly
  4544. * @property {string} 0 - MEDIA_ERR_CUSTOM
  4545. * @property {string} 1 - MEDIA_ERR_ABORTED
  4546. * @property {string} 2 - MEDIA_ERR_NETWORK
  4547. * @property {string} 3 - MEDIA_ERR_DECODE
  4548. * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
  4549. * @property {string} 5 - MEDIA_ERR_ENCRYPTED
  4550. */
  4551. MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
  4552. /**
  4553. * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
  4554. *
  4555. * @type {Array}
  4556. * @constant
  4557. */
  4558. MediaError.defaultMessages = {
  4559. 1: 'You aborted the media playback',
  4560. 2: 'A network error caused the media download to fail part-way.',
  4561. 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
  4562. 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
  4563. 5: 'The media is encrypted and we do not have the keys to decrypt it.'
  4564. }; // Add types as properties on MediaError
  4565. // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
  4566. for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
  4567. MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
  4568. MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
  4569. } // jsdocs for instance/static members added above
  4570. /**
  4571. * Returns whether an object is `Promise`-like (i.e. has a `then` method).
  4572. *
  4573. * @param {Object} value
  4574. * An object that may or may not be `Promise`-like.
  4575. *
  4576. * @return {boolean}
  4577. * Whether or not the object is `Promise`-like.
  4578. */
  4579. function isPromise(value) {
  4580. return value !== undefined && value !== null && typeof value.then === 'function';
  4581. }
  4582. /**
  4583. * Silence a Promise-like object.
  4584. *
  4585. * This is useful for avoiding non-harmful, but potentially confusing "uncaught
  4586. * play promise" rejection error messages.
  4587. *
  4588. * @param {Object} value
  4589. * An object that may or may not be `Promise`-like.
  4590. */
  4591. function silencePromise(value) {
  4592. if (isPromise(value)) {
  4593. value.then(null, function (e) {});
  4594. }
  4595. }
  4596. /**
  4597. * @file text-track-list-converter.js Utilities for capturing text track state and
  4598. * re-creating tracks based on a capture.
  4599. *
  4600. * @module text-track-list-converter
  4601. */
  4602. /**
  4603. * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
  4604. * represents the {@link TextTrack}'s state.
  4605. *
  4606. * @param {TextTrack} track
  4607. * The text track to query.
  4608. *
  4609. * @return {Object}
  4610. * A serializable javascript representation of the TextTrack.
  4611. * @private
  4612. */
  4613. var trackToJson_ = function trackToJson_(track) {
  4614. var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
  4615. if (track[prop]) {
  4616. acc[prop] = track[prop];
  4617. }
  4618. return acc;
  4619. }, {
  4620. cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
  4621. return {
  4622. startTime: cue.startTime,
  4623. endTime: cue.endTime,
  4624. text: cue.text,
  4625. id: cue.id
  4626. };
  4627. })
  4628. });
  4629. return ret;
  4630. };
  4631. /**
  4632. * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
  4633. * state of all {@link TextTrack}s currently configured. The return array is compatible with
  4634. * {@link text-track-list-converter:jsonToTextTracks}.
  4635. *
  4636. * @param {Tech} tech
  4637. * The tech object to query
  4638. *
  4639. * @return {Array}
  4640. * A serializable javascript representation of the {@link Tech}s
  4641. * {@link TextTrackList}.
  4642. */
  4643. var textTracksToJson = function textTracksToJson(tech) {
  4644. var trackEls = tech.$$('track');
  4645. var trackObjs = Array.prototype.map.call(trackEls, function (t) {
  4646. return t.track;
  4647. });
  4648. var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
  4649. var json = trackToJson_(trackEl.track);
  4650. if (trackEl.src) {
  4651. json.src = trackEl.src;
  4652. }
  4653. return json;
  4654. });
  4655. return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
  4656. return trackObjs.indexOf(track) === -1;
  4657. }).map(trackToJson_));
  4658. };
  4659. /**
  4660. * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
  4661. * object {@link TextTrack} representations.
  4662. *
  4663. * @param {Array} json
  4664. * An array of `TextTrack` representation objects, like those that would be
  4665. * produced by `textTracksToJson`.
  4666. *
  4667. * @param {Tech} tech
  4668. * The `Tech` to create the `TextTrack`s on.
  4669. */
  4670. var jsonToTextTracks = function jsonToTextTracks(json, tech) {
  4671. json.forEach(function (track) {
  4672. var addedTrack = tech.addRemoteTextTrack(track).track;
  4673. if (!track.src && track.cues) {
  4674. track.cues.forEach(function (cue) {
  4675. return addedTrack.addCue(cue);
  4676. });
  4677. }
  4678. });
  4679. return tech.textTracks();
  4680. };
  4681. var textTrackConverter = {
  4682. textTracksToJson: textTracksToJson,
  4683. jsonToTextTracks: jsonToTextTracks,
  4684. trackToJson_: trackToJson_
  4685. };
  4686. var MODAL_CLASS_NAME = 'vjs-modal-dialog';
  4687. /**
  4688. * The `ModalDialog` displays over the video and its controls, which blocks
  4689. * interaction with the player until it is closed.
  4690. *
  4691. * Modal dialogs include a "Close" button and will close when that button
  4692. * is activated - or when ESC is pressed anywhere.
  4693. *
  4694. * @extends Component
  4695. */
  4696. var ModalDialog =
  4697. /*#__PURE__*/
  4698. function (_Component) {
  4699. _inheritsLoose(ModalDialog, _Component);
  4700. /**
  4701. * Create an instance of this class.
  4702. *
  4703. * @param {Player} player
  4704. * The `Player` that this class should be attached to.
  4705. *
  4706. * @param {Object} [options]
  4707. * The key/value store of player options.
  4708. *
  4709. * @param {Mixed} [options.content=undefined]
  4710. * Provide customized content for this modal.
  4711. *
  4712. * @param {string} [options.description]
  4713. * A text description for the modal, primarily for accessibility.
  4714. *
  4715. * @param {boolean} [options.fillAlways=false]
  4716. * Normally, modals are automatically filled only the first time
  4717. * they open. This tells the modal to refresh its content
  4718. * every time it opens.
  4719. *
  4720. * @param {string} [options.label]
  4721. * A text label for the modal, primarily for accessibility.
  4722. *
  4723. * @param {boolean} [options.pauseOnOpen=true]
  4724. * If `true`, playback will will be paused if playing when
  4725. * the modal opens, and resumed when it closes.
  4726. *
  4727. * @param {boolean} [options.temporary=true]
  4728. * If `true`, the modal can only be opened once; it will be
  4729. * disposed as soon as it's closed.
  4730. *
  4731. * @param {boolean} [options.uncloseable=false]
  4732. * If `true`, the user will not be able to close the modal
  4733. * through the UI in the normal ways. Programmatic closing is
  4734. * still possible.
  4735. */
  4736. function ModalDialog(player, options) {
  4737. var _this;
  4738. _this = _Component.call(this, player, options) || this;
  4739. _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
  4740. _this.closeable(!_this.options_.uncloseable);
  4741. _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
  4742. // because we only want the contents of the modal in the contentEl
  4743. // (not the UI elements like the close button).
  4744. _this.contentEl_ = createEl('div', {
  4745. className: MODAL_CLASS_NAME + "-content"
  4746. }, {
  4747. role: 'document'
  4748. });
  4749. _this.descEl_ = createEl('p', {
  4750. className: MODAL_CLASS_NAME + "-description vjs-control-text",
  4751. id: _this.el().getAttribute('aria-describedby')
  4752. });
  4753. textContent(_this.descEl_, _this.description());
  4754. _this.el_.appendChild(_this.descEl_);
  4755. _this.el_.appendChild(_this.contentEl_);
  4756. return _this;
  4757. }
  4758. /**
  4759. * Create the `ModalDialog`'s DOM element
  4760. *
  4761. * @return {Element}
  4762. * The DOM element that gets created.
  4763. */
  4764. var _proto = ModalDialog.prototype;
  4765. _proto.createEl = function createEl$$1() {
  4766. return _Component.prototype.createEl.call(this, 'div', {
  4767. className: this.buildCSSClass(),
  4768. tabIndex: -1
  4769. }, {
  4770. 'aria-describedby': this.id() + "_description",
  4771. 'aria-hidden': 'true',
  4772. 'aria-label': this.label(),
  4773. 'role': 'dialog'
  4774. });
  4775. };
  4776. _proto.dispose = function dispose() {
  4777. this.contentEl_ = null;
  4778. this.descEl_ = null;
  4779. this.previouslyActiveEl_ = null;
  4780. _Component.prototype.dispose.call(this);
  4781. }
  4782. /**
  4783. * Builds the default DOM `className`.
  4784. *
  4785. * @return {string}
  4786. * The DOM `className` for this object.
  4787. */
  4788. ;
  4789. _proto.buildCSSClass = function buildCSSClass() {
  4790. return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
  4791. }
  4792. /**
  4793. * Handles `keydown` events on the document, looking for ESC, which closes
  4794. * the modal.
  4795. *
  4796. * @param {EventTarget~Event} event
  4797. * The keypress that triggered this event.
  4798. *
  4799. * @listens keydown
  4800. */
  4801. ;
  4802. _proto.handleKeyPress = function handleKeyPress(event) {
  4803. if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
  4804. this.close();
  4805. }
  4806. }
  4807. /**
  4808. * Returns the label string for this modal. Primarily used for accessibility.
  4809. *
  4810. * @return {string}
  4811. * the localized or raw label of this modal.
  4812. */
  4813. ;
  4814. _proto.label = function label() {
  4815. return this.localize(this.options_.label || 'Modal Window');
  4816. }
  4817. /**
  4818. * Returns the description string for this modal. Primarily used for
  4819. * accessibility.
  4820. *
  4821. * @return {string}
  4822. * The localized or raw description of this modal.
  4823. */
  4824. ;
  4825. _proto.description = function description() {
  4826. var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
  4827. if (this.closeable()) {
  4828. desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
  4829. }
  4830. return desc;
  4831. }
  4832. /**
  4833. * Opens the modal.
  4834. *
  4835. * @fires ModalDialog#beforemodalopen
  4836. * @fires ModalDialog#modalopen
  4837. */
  4838. ;
  4839. _proto.open = function open() {
  4840. if (!this.opened_) {
  4841. var player = this.player();
  4842. /**
  4843. * Fired just before a `ModalDialog` is opened.
  4844. *
  4845. * @event ModalDialog#beforemodalopen
  4846. * @type {EventTarget~Event}
  4847. */
  4848. this.trigger('beforemodalopen');
  4849. this.opened_ = true; // Fill content if the modal has never opened before and
  4850. // never been filled.
  4851. if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
  4852. this.fill();
  4853. } // If the player was playing, pause it and take note of its previously
  4854. // playing state.
  4855. this.wasPlaying_ = !player.paused();
  4856. if (this.options_.pauseOnOpen && this.wasPlaying_) {
  4857. player.pause();
  4858. }
  4859. if (this.closeable()) {
  4860. this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  4861. } // Hide controls and note if they were enabled.
  4862. this.hadControls_ = player.controls();
  4863. player.controls(false);
  4864. this.show();
  4865. this.conditionalFocus_();
  4866. this.el().setAttribute('aria-hidden', 'false');
  4867. /**
  4868. * Fired just after a `ModalDialog` is opened.
  4869. *
  4870. * @event ModalDialog#modalopen
  4871. * @type {EventTarget~Event}
  4872. */
  4873. this.trigger('modalopen');
  4874. this.hasBeenOpened_ = true;
  4875. }
  4876. }
  4877. /**
  4878. * If the `ModalDialog` is currently open or closed.
  4879. *
  4880. * @param {boolean} [value]
  4881. * If given, it will open (`true`) or close (`false`) the modal.
  4882. *
  4883. * @return {boolean}
  4884. * the current open state of the modaldialog
  4885. */
  4886. ;
  4887. _proto.opened = function opened(value) {
  4888. if (typeof value === 'boolean') {
  4889. this[value ? 'open' : 'close']();
  4890. }
  4891. return this.opened_;
  4892. }
  4893. /**
  4894. * Closes the modal, does nothing if the `ModalDialog` is
  4895. * not open.
  4896. *
  4897. * @fires ModalDialog#beforemodalclose
  4898. * @fires ModalDialog#modalclose
  4899. */
  4900. ;
  4901. _proto.close = function close() {
  4902. if (!this.opened_) {
  4903. return;
  4904. }
  4905. var player = this.player();
  4906. /**
  4907. * Fired just before a `ModalDialog` is closed.
  4908. *
  4909. * @event ModalDialog#beforemodalclose
  4910. * @type {EventTarget~Event}
  4911. */
  4912. this.trigger('beforemodalclose');
  4913. this.opened_ = false;
  4914. if (this.wasPlaying_ && this.options_.pauseOnOpen) {
  4915. player.play();
  4916. }
  4917. if (this.closeable()) {
  4918. this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  4919. }
  4920. if (this.hadControls_) {
  4921. player.controls(true);
  4922. }
  4923. this.hide();
  4924. this.el().setAttribute('aria-hidden', 'true');
  4925. /**
  4926. * Fired just after a `ModalDialog` is closed.
  4927. *
  4928. * @event ModalDialog#modalclose
  4929. * @type {EventTarget~Event}
  4930. */
  4931. this.trigger('modalclose');
  4932. this.conditionalBlur_();
  4933. if (this.options_.temporary) {
  4934. this.dispose();
  4935. }
  4936. }
  4937. /**
  4938. * Check to see if the `ModalDialog` is closeable via the UI.
  4939. *
  4940. * @param {boolean} [value]
  4941. * If given as a boolean, it will set the `closeable` option.
  4942. *
  4943. * @return {boolean}
  4944. * Returns the final value of the closable option.
  4945. */
  4946. ;
  4947. _proto.closeable = function closeable(value) {
  4948. if (typeof value === 'boolean') {
  4949. var closeable = this.closeable_ = !!value;
  4950. var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
  4951. if (closeable && !close) {
  4952. // The close button should be a child of the modal - not its
  4953. // content element, so temporarily change the content element.
  4954. var temp = this.contentEl_;
  4955. this.contentEl_ = this.el_;
  4956. close = this.addChild('closeButton', {
  4957. controlText: 'Close Modal Dialog'
  4958. });
  4959. this.contentEl_ = temp;
  4960. this.on(close, 'close', this.close);
  4961. } // If this is being made uncloseable and has a close button, remove it.
  4962. if (!closeable && close) {
  4963. this.off(close, 'close', this.close);
  4964. this.removeChild(close);
  4965. close.dispose();
  4966. }
  4967. }
  4968. return this.closeable_;
  4969. }
  4970. /**
  4971. * Fill the modal's content element with the modal's "content" option.
  4972. * The content element will be emptied before this change takes place.
  4973. */
  4974. ;
  4975. _proto.fill = function fill() {
  4976. this.fillWith(this.content());
  4977. }
  4978. /**
  4979. * Fill the modal's content element with arbitrary content.
  4980. * The content element will be emptied before this change takes place.
  4981. *
  4982. * @fires ModalDialog#beforemodalfill
  4983. * @fires ModalDialog#modalfill
  4984. *
  4985. * @param {Mixed} [content]
  4986. * The same rules apply to this as apply to the `content` option.
  4987. */
  4988. ;
  4989. _proto.fillWith = function fillWith(content) {
  4990. var contentEl = this.contentEl();
  4991. var parentEl = contentEl.parentNode;
  4992. var nextSiblingEl = contentEl.nextSibling;
  4993. /**
  4994. * Fired just before a `ModalDialog` is filled with content.
  4995. *
  4996. * @event ModalDialog#beforemodalfill
  4997. * @type {EventTarget~Event}
  4998. */
  4999. this.trigger('beforemodalfill');
  5000. this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
  5001. // manipulation to avoid modifying the live DOM multiple times.
  5002. parentEl.removeChild(contentEl);
  5003. this.empty();
  5004. insertContent(contentEl, content);
  5005. /**
  5006. * Fired just after a `ModalDialog` is filled with content.
  5007. *
  5008. * @event ModalDialog#modalfill
  5009. * @type {EventTarget~Event}
  5010. */
  5011. this.trigger('modalfill'); // Re-inject the re-filled content element.
  5012. if (nextSiblingEl) {
  5013. parentEl.insertBefore(contentEl, nextSiblingEl);
  5014. } else {
  5015. parentEl.appendChild(contentEl);
  5016. } // make sure that the close button is last in the dialog DOM
  5017. var closeButton = this.getChild('closeButton');
  5018. if (closeButton) {
  5019. parentEl.appendChild(closeButton.el_);
  5020. }
  5021. }
  5022. /**
  5023. * Empties the content element. This happens anytime the modal is filled.
  5024. *
  5025. * @fires ModalDialog#beforemodalempty
  5026. * @fires ModalDialog#modalempty
  5027. */
  5028. ;
  5029. _proto.empty = function empty() {
  5030. /**
  5031. * Fired just before a `ModalDialog` is emptied.
  5032. *
  5033. * @event ModalDialog#beforemodalempty
  5034. * @type {EventTarget~Event}
  5035. */
  5036. this.trigger('beforemodalempty');
  5037. emptyEl(this.contentEl());
  5038. /**
  5039. * Fired just after a `ModalDialog` is emptied.
  5040. *
  5041. * @event ModalDialog#modalempty
  5042. * @type {EventTarget~Event}
  5043. */
  5044. this.trigger('modalempty');
  5045. }
  5046. /**
  5047. * Gets or sets the modal content, which gets normalized before being
  5048. * rendered into the DOM.
  5049. *
  5050. * This does not update the DOM or fill the modal, but it is called during
  5051. * that process.
  5052. *
  5053. * @param {Mixed} [value]
  5054. * If defined, sets the internal content value to be used on the
  5055. * next call(s) to `fill`. This value is normalized before being
  5056. * inserted. To "clear" the internal content value, pass `null`.
  5057. *
  5058. * @return {Mixed}
  5059. * The current content of the modal dialog
  5060. */
  5061. ;
  5062. _proto.content = function content(value) {
  5063. if (typeof value !== 'undefined') {
  5064. this.content_ = value;
  5065. }
  5066. return this.content_;
  5067. }
  5068. /**
  5069. * conditionally focus the modal dialog if focus was previously on the player.
  5070. *
  5071. * @private
  5072. */
  5073. ;
  5074. _proto.conditionalFocus_ = function conditionalFocus_() {
  5075. var activeEl = document.activeElement;
  5076. var playerEl = this.player_.el_;
  5077. this.previouslyActiveEl_ = null;
  5078. if (playerEl.contains(activeEl) || playerEl === activeEl) {
  5079. this.previouslyActiveEl_ = activeEl;
  5080. this.focus();
  5081. this.on(document, 'keydown', this.handleKeyDown);
  5082. }
  5083. }
  5084. /**
  5085. * conditionally blur the element and refocus the last focused element
  5086. *
  5087. * @private
  5088. */
  5089. ;
  5090. _proto.conditionalBlur_ = function conditionalBlur_() {
  5091. if (this.previouslyActiveEl_) {
  5092. this.previouslyActiveEl_.focus();
  5093. this.previouslyActiveEl_ = null;
  5094. }
  5095. this.off(document, 'keydown', this.handleKeyDown);
  5096. }
  5097. /**
  5098. * Keydown handler. Attached when modal is focused.
  5099. *
  5100. * @listens keydown
  5101. */
  5102. ;
  5103. _proto.handleKeyDown = function handleKeyDown(event) {
  5104. // exit early if it isn't a tab key
  5105. if (!keycode.isEventKey(event, 'Tab')) {
  5106. return;
  5107. }
  5108. var focusableEls = this.focusableEls_();
  5109. var activeEl = this.el_.querySelector(':focus');
  5110. var focusIndex;
  5111. for (var i = 0; i < focusableEls.length; i++) {
  5112. if (activeEl === focusableEls[i]) {
  5113. focusIndex = i;
  5114. break;
  5115. }
  5116. }
  5117. if (document.activeElement === this.el_) {
  5118. focusIndex = 0;
  5119. }
  5120. if (event.shiftKey && focusIndex === 0) {
  5121. focusableEls[focusableEls.length - 1].focus();
  5122. event.preventDefault();
  5123. } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
  5124. focusableEls[0].focus();
  5125. event.preventDefault();
  5126. }
  5127. }
  5128. /**
  5129. * get all focusable elements
  5130. *
  5131. * @private
  5132. */
  5133. ;
  5134. _proto.focusableEls_ = function focusableEls_() {
  5135. var allChildren = this.el_.querySelectorAll('*');
  5136. return Array.prototype.filter.call(allChildren, function (child) {
  5137. return (child instanceof window$1.HTMLAnchorElement || child instanceof window$1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window$1.HTMLInputElement || child instanceof window$1.HTMLSelectElement || child instanceof window$1.HTMLTextAreaElement || child instanceof window$1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window$1.HTMLIFrameElement || child instanceof window$1.HTMLObjectElement || child instanceof window$1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
  5138. });
  5139. };
  5140. return ModalDialog;
  5141. }(Component);
  5142. /**
  5143. * Default options for `ModalDialog` default options.
  5144. *
  5145. * @type {Object}
  5146. * @private
  5147. */
  5148. ModalDialog.prototype.options_ = {
  5149. pauseOnOpen: true,
  5150. temporary: true
  5151. };
  5152. Component.registerComponent('ModalDialog', ModalDialog);
  5153. /**
  5154. * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
  5155. * {@link VideoTrackList}
  5156. *
  5157. * @extends EventTarget
  5158. */
  5159. var TrackList =
  5160. /*#__PURE__*/
  5161. function (_EventTarget) {
  5162. _inheritsLoose(TrackList, _EventTarget);
  5163. /**
  5164. * Create an instance of this class
  5165. *
  5166. * @param {Track[]} tracks
  5167. * A list of tracks to initialize the list with.
  5168. *
  5169. * @abstract
  5170. */
  5171. function TrackList(tracks) {
  5172. var _this;
  5173. if (tracks === void 0) {
  5174. tracks = [];
  5175. }
  5176. _this = _EventTarget.call(this) || this;
  5177. _this.tracks_ = [];
  5178. /**
  5179. * @memberof TrackList
  5180. * @member {number} length
  5181. * The current number of `Track`s in the this Trackist.
  5182. * @instance
  5183. */
  5184. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'length', {
  5185. get: function get() {
  5186. return this.tracks_.length;
  5187. }
  5188. });
  5189. for (var i = 0; i < tracks.length; i++) {
  5190. _this.addTrack(tracks[i]);
  5191. }
  5192. return _this;
  5193. }
  5194. /**
  5195. * Add a {@link Track} to the `TrackList`
  5196. *
  5197. * @param {Track} track
  5198. * The audio, video, or text track to add to the list.
  5199. *
  5200. * @fires TrackList#addtrack
  5201. */
  5202. var _proto = TrackList.prototype;
  5203. _proto.addTrack = function addTrack(track) {
  5204. var index = this.tracks_.length;
  5205. if (!('' + index in this)) {
  5206. Object.defineProperty(this, index, {
  5207. get: function get() {
  5208. return this.tracks_[index];
  5209. }
  5210. });
  5211. } // Do not add duplicate tracks
  5212. if (this.tracks_.indexOf(track) === -1) {
  5213. this.tracks_.push(track);
  5214. /**
  5215. * Triggered when a track is added to a track list.
  5216. *
  5217. * @event TrackList#addtrack
  5218. * @type {EventTarget~Event}
  5219. * @property {Track} track
  5220. * A reference to track that was added.
  5221. */
  5222. this.trigger({
  5223. track: track,
  5224. type: 'addtrack',
  5225. target: this
  5226. });
  5227. }
  5228. }
  5229. /**
  5230. * Remove a {@link Track} from the `TrackList`
  5231. *
  5232. * @param {Track} rtrack
  5233. * The audio, video, or text track to remove from the list.
  5234. *
  5235. * @fires TrackList#removetrack
  5236. */
  5237. ;
  5238. _proto.removeTrack = function removeTrack(rtrack) {
  5239. var track;
  5240. for (var i = 0, l = this.length; i < l; i++) {
  5241. if (this[i] === rtrack) {
  5242. track = this[i];
  5243. if (track.off) {
  5244. track.off();
  5245. }
  5246. this.tracks_.splice(i, 1);
  5247. break;
  5248. }
  5249. }
  5250. if (!track) {
  5251. return;
  5252. }
  5253. /**
  5254. * Triggered when a track is removed from track list.
  5255. *
  5256. * @event TrackList#removetrack
  5257. * @type {EventTarget~Event}
  5258. * @property {Track} track
  5259. * A reference to track that was removed.
  5260. */
  5261. this.trigger({
  5262. track: track,
  5263. type: 'removetrack',
  5264. target: this
  5265. });
  5266. }
  5267. /**
  5268. * Get a Track from the TrackList by a tracks id
  5269. *
  5270. * @param {string} id - the id of the track to get
  5271. * @method getTrackById
  5272. * @return {Track}
  5273. * @private
  5274. */
  5275. ;
  5276. _proto.getTrackById = function getTrackById(id) {
  5277. var result = null;
  5278. for (var i = 0, l = this.length; i < l; i++) {
  5279. var track = this[i];
  5280. if (track.id === id) {
  5281. result = track;
  5282. break;
  5283. }
  5284. }
  5285. return result;
  5286. };
  5287. return TrackList;
  5288. }(EventTarget);
  5289. /**
  5290. * Triggered when a different track is selected/enabled.
  5291. *
  5292. * @event TrackList#change
  5293. * @type {EventTarget~Event}
  5294. */
  5295. /**
  5296. * Events that can be called with on + eventName. See {@link EventHandler}.
  5297. *
  5298. * @property {Object} TrackList#allowedEvents_
  5299. * @private
  5300. */
  5301. TrackList.prototype.allowedEvents_ = {
  5302. change: 'change',
  5303. addtrack: 'addtrack',
  5304. removetrack: 'removetrack'
  5305. }; // emulate attribute EventHandler support to allow for feature detection
  5306. for (var event in TrackList.prototype.allowedEvents_) {
  5307. TrackList.prototype['on' + event] = null;
  5308. }
  5309. /**
  5310. * Anywhere we call this function we diverge from the spec
  5311. * as we only support one enabled audiotrack at a time
  5312. *
  5313. * @param {AudioTrackList} list
  5314. * list to work on
  5315. *
  5316. * @param {AudioTrack} track
  5317. * The track to skip
  5318. *
  5319. * @private
  5320. */
  5321. var disableOthers = function disableOthers(list, track) {
  5322. for (var i = 0; i < list.length; i++) {
  5323. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5324. continue;
  5325. } // another audio track is enabled, disable it
  5326. list[i].enabled = false;
  5327. }
  5328. };
  5329. /**
  5330. * The current list of {@link AudioTrack} for a media file.
  5331. *
  5332. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
  5333. * @extends TrackList
  5334. */
  5335. var AudioTrackList =
  5336. /*#__PURE__*/
  5337. function (_TrackList) {
  5338. _inheritsLoose(AudioTrackList, _TrackList);
  5339. /**
  5340. * Create an instance of this class.
  5341. *
  5342. * @param {AudioTrack[]} [tracks=[]]
  5343. * A list of `AudioTrack` to instantiate the list with.
  5344. */
  5345. function AudioTrackList(tracks) {
  5346. var _this;
  5347. if (tracks === void 0) {
  5348. tracks = [];
  5349. }
  5350. // make sure only 1 track is enabled
  5351. // sorted from last index to first index
  5352. for (var i = tracks.length - 1; i >= 0; i--) {
  5353. if (tracks[i].enabled) {
  5354. disableOthers(tracks, tracks[i]);
  5355. break;
  5356. }
  5357. }
  5358. _this = _TrackList.call(this, tracks) || this;
  5359. _this.changing_ = false;
  5360. return _this;
  5361. }
  5362. /**
  5363. * Add an {@link AudioTrack} to the `AudioTrackList`.
  5364. *
  5365. * @param {AudioTrack} track
  5366. * The AudioTrack to add to the list
  5367. *
  5368. * @fires TrackList#addtrack
  5369. */
  5370. var _proto = AudioTrackList.prototype;
  5371. _proto.addTrack = function addTrack(track) {
  5372. var _this2 = this;
  5373. if (track.enabled) {
  5374. disableOthers(this, track);
  5375. }
  5376. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5377. if (!track.addEventListener) {
  5378. return;
  5379. }
  5380. track.enabledChange_ = function () {
  5381. // when we are disabling other tracks (since we don't support
  5382. // more than one track at a time) we will set changing_
  5383. // to true so that we don't trigger additional change events
  5384. if (_this2.changing_) {
  5385. return;
  5386. }
  5387. _this2.changing_ = true;
  5388. disableOthers(_this2, track);
  5389. _this2.changing_ = false;
  5390. _this2.trigger('change');
  5391. };
  5392. /**
  5393. * @listens AudioTrack#enabledchange
  5394. * @fires TrackList#change
  5395. */
  5396. track.addEventListener('enabledchange', track.enabledChange_);
  5397. };
  5398. _proto.removeTrack = function removeTrack(rtrack) {
  5399. _TrackList.prototype.removeTrack.call(this, rtrack);
  5400. if (rtrack.removeEventListener && rtrack.enabledChange_) {
  5401. rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
  5402. rtrack.enabledChange_ = null;
  5403. }
  5404. };
  5405. return AudioTrackList;
  5406. }(TrackList);
  5407. /**
  5408. * Un-select all other {@link VideoTrack}s that are selected.
  5409. *
  5410. * @param {VideoTrackList} list
  5411. * list to work on
  5412. *
  5413. * @param {VideoTrack} track
  5414. * The track to skip
  5415. *
  5416. * @private
  5417. */
  5418. var disableOthers$1 = function disableOthers(list, track) {
  5419. for (var i = 0; i < list.length; i++) {
  5420. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5421. continue;
  5422. } // another video track is enabled, disable it
  5423. list[i].selected = false;
  5424. }
  5425. };
  5426. /**
  5427. * The current list of {@link VideoTrack} for a video.
  5428. *
  5429. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
  5430. * @extends TrackList
  5431. */
  5432. var VideoTrackList =
  5433. /*#__PURE__*/
  5434. function (_TrackList) {
  5435. _inheritsLoose(VideoTrackList, _TrackList);
  5436. /**
  5437. * Create an instance of this class.
  5438. *
  5439. * @param {VideoTrack[]} [tracks=[]]
  5440. * A list of `VideoTrack` to instantiate the list with.
  5441. */
  5442. function VideoTrackList(tracks) {
  5443. var _this;
  5444. if (tracks === void 0) {
  5445. tracks = [];
  5446. }
  5447. // make sure only 1 track is enabled
  5448. // sorted from last index to first index
  5449. for (var i = tracks.length - 1; i >= 0; i--) {
  5450. if (tracks[i].selected) {
  5451. disableOthers$1(tracks, tracks[i]);
  5452. break;
  5453. }
  5454. }
  5455. _this = _TrackList.call(this, tracks) || this;
  5456. _this.changing_ = false;
  5457. /**
  5458. * @member {number} VideoTrackList#selectedIndex
  5459. * The current index of the selected {@link VideoTrack`}.
  5460. */
  5461. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selectedIndex', {
  5462. get: function get() {
  5463. for (var _i = 0; _i < this.length; _i++) {
  5464. if (this[_i].selected) {
  5465. return _i;
  5466. }
  5467. }
  5468. return -1;
  5469. },
  5470. set: function set() {}
  5471. });
  5472. return _this;
  5473. }
  5474. /**
  5475. * Add a {@link VideoTrack} to the `VideoTrackList`.
  5476. *
  5477. * @param {VideoTrack} track
  5478. * The VideoTrack to add to the list
  5479. *
  5480. * @fires TrackList#addtrack
  5481. */
  5482. var _proto = VideoTrackList.prototype;
  5483. _proto.addTrack = function addTrack(track) {
  5484. var _this2 = this;
  5485. if (track.selected) {
  5486. disableOthers$1(this, track);
  5487. }
  5488. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5489. if (!track.addEventListener) {
  5490. return;
  5491. }
  5492. track.selectedChange_ = function () {
  5493. if (_this2.changing_) {
  5494. return;
  5495. }
  5496. _this2.changing_ = true;
  5497. disableOthers$1(_this2, track);
  5498. _this2.changing_ = false;
  5499. _this2.trigger('change');
  5500. };
  5501. /**
  5502. * @listens VideoTrack#selectedchange
  5503. * @fires TrackList#change
  5504. */
  5505. track.addEventListener('selectedchange', track.selectedChange_);
  5506. };
  5507. _proto.removeTrack = function removeTrack(rtrack) {
  5508. _TrackList.prototype.removeTrack.call(this, rtrack);
  5509. if (rtrack.removeEventListener && rtrack.selectedChange_) {
  5510. rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
  5511. rtrack.selectedChange_ = null;
  5512. }
  5513. };
  5514. return VideoTrackList;
  5515. }(TrackList);
  5516. /**
  5517. * The current list of {@link TextTrack} for a media file.
  5518. *
  5519. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
  5520. * @extends TrackList
  5521. */
  5522. var TextTrackList =
  5523. /*#__PURE__*/
  5524. function (_TrackList) {
  5525. _inheritsLoose(TextTrackList, _TrackList);
  5526. function TextTrackList() {
  5527. return _TrackList.apply(this, arguments) || this;
  5528. }
  5529. var _proto = TextTrackList.prototype;
  5530. /**
  5531. * Add a {@link TextTrack} to the `TextTrackList`
  5532. *
  5533. * @param {TextTrack} track
  5534. * The text track to add to the list.
  5535. *
  5536. * @fires TrackList#addtrack
  5537. */
  5538. _proto.addTrack = function addTrack(track) {
  5539. var _this = this;
  5540. _TrackList.prototype.addTrack.call(this, track);
  5541. if (!this.queueChange_) {
  5542. this.queueChange_ = function () {
  5543. return _this.queueTrigger('change');
  5544. };
  5545. }
  5546. if (!this.triggerSelectedlanguagechange) {
  5547. this.triggerSelectedlanguagechange_ = function () {
  5548. return _this.trigger('selectedlanguagechange');
  5549. };
  5550. }
  5551. /**
  5552. * @listens TextTrack#modechange
  5553. * @fires TrackList#change
  5554. */
  5555. track.addEventListener('modechange', this.queueChange_);
  5556. var nonLanguageTextTrackKind = ['metadata', 'chapters'];
  5557. if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
  5558. track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
  5559. }
  5560. };
  5561. _proto.removeTrack = function removeTrack(rtrack) {
  5562. _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
  5563. if (rtrack.removeEventListener) {
  5564. if (this.queueChange_) {
  5565. rtrack.removeEventListener('modechange', this.queueChange_);
  5566. }
  5567. if (this.selectedlanguagechange_) {
  5568. rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
  5569. }
  5570. }
  5571. };
  5572. return TextTrackList;
  5573. }(TrackList);
  5574. /**
  5575. * @file html-track-element-list.js
  5576. */
  5577. /**
  5578. * The current list of {@link HtmlTrackElement}s.
  5579. */
  5580. var HtmlTrackElementList =
  5581. /*#__PURE__*/
  5582. function () {
  5583. /**
  5584. * Create an instance of this class.
  5585. *
  5586. * @param {HtmlTrackElement[]} [tracks=[]]
  5587. * A list of `HtmlTrackElement` to instantiate the list with.
  5588. */
  5589. function HtmlTrackElementList(trackElements) {
  5590. if (trackElements === void 0) {
  5591. trackElements = [];
  5592. }
  5593. this.trackElements_ = [];
  5594. /**
  5595. * @memberof HtmlTrackElementList
  5596. * @member {number} length
  5597. * The current number of `Track`s in the this Trackist.
  5598. * @instance
  5599. */
  5600. Object.defineProperty(this, 'length', {
  5601. get: function get() {
  5602. return this.trackElements_.length;
  5603. }
  5604. });
  5605. for (var i = 0, length = trackElements.length; i < length; i++) {
  5606. this.addTrackElement_(trackElements[i]);
  5607. }
  5608. }
  5609. /**
  5610. * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
  5611. *
  5612. * @param {HtmlTrackElement} trackElement
  5613. * The track element to add to the list.
  5614. *
  5615. * @private
  5616. */
  5617. var _proto = HtmlTrackElementList.prototype;
  5618. _proto.addTrackElement_ = function addTrackElement_(trackElement) {
  5619. var index = this.trackElements_.length;
  5620. if (!('' + index in this)) {
  5621. Object.defineProperty(this, index, {
  5622. get: function get() {
  5623. return this.trackElements_[index];
  5624. }
  5625. });
  5626. } // Do not add duplicate elements
  5627. if (this.trackElements_.indexOf(trackElement) === -1) {
  5628. this.trackElements_.push(trackElement);
  5629. }
  5630. }
  5631. /**
  5632. * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
  5633. * {@link TextTrack}.
  5634. *
  5635. * @param {TextTrack} track
  5636. * The track associated with a track element.
  5637. *
  5638. * @return {HtmlTrackElement|undefined}
  5639. * The track element that was found or undefined.
  5640. *
  5641. * @private
  5642. */
  5643. ;
  5644. _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
  5645. var trackElement_;
  5646. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5647. if (track === this.trackElements_[i].track) {
  5648. trackElement_ = this.trackElements_[i];
  5649. break;
  5650. }
  5651. }
  5652. return trackElement_;
  5653. }
  5654. /**
  5655. * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
  5656. *
  5657. * @param {HtmlTrackElement} trackElement
  5658. * The track element to remove from the list.
  5659. *
  5660. * @private
  5661. */
  5662. ;
  5663. _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
  5664. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5665. if (trackElement === this.trackElements_[i]) {
  5666. if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
  5667. this.trackElements_[i].track.off();
  5668. }
  5669. if (typeof this.trackElements_[i].off === 'function') {
  5670. this.trackElements_[i].off();
  5671. }
  5672. this.trackElements_.splice(i, 1);
  5673. break;
  5674. }
  5675. }
  5676. };
  5677. return HtmlTrackElementList;
  5678. }();
  5679. /**
  5680. * @file text-track-cue-list.js
  5681. */
  5682. /**
  5683. * @typedef {Object} TextTrackCueList~TextTrackCue
  5684. *
  5685. * @property {string} id
  5686. * The unique id for this text track cue
  5687. *
  5688. * @property {number} startTime
  5689. * The start time for this text track cue
  5690. *
  5691. * @property {number} endTime
  5692. * The end time for this text track cue
  5693. *
  5694. * @property {boolean} pauseOnExit
  5695. * Pause when the end time is reached if true.
  5696. *
  5697. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
  5698. */
  5699. /**
  5700. * A List of TextTrackCues.
  5701. *
  5702. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
  5703. */
  5704. var TextTrackCueList =
  5705. /*#__PURE__*/
  5706. function () {
  5707. /**
  5708. * Create an instance of this class..
  5709. *
  5710. * @param {Array} cues
  5711. * A list of cues to be initialized with
  5712. */
  5713. function TextTrackCueList(cues) {
  5714. TextTrackCueList.prototype.setCues_.call(this, cues);
  5715. /**
  5716. * @memberof TextTrackCueList
  5717. * @member {number} length
  5718. * The current number of `TextTrackCue`s in the TextTrackCueList.
  5719. * @instance
  5720. */
  5721. Object.defineProperty(this, 'length', {
  5722. get: function get() {
  5723. return this.length_;
  5724. }
  5725. });
  5726. }
  5727. /**
  5728. * A setter for cues in this list. Creates getters
  5729. * an an index for the cues.
  5730. *
  5731. * @param {Array} cues
  5732. * An array of cues to set
  5733. *
  5734. * @private
  5735. */
  5736. var _proto = TextTrackCueList.prototype;
  5737. _proto.setCues_ = function setCues_(cues) {
  5738. var oldLength = this.length || 0;
  5739. var i = 0;
  5740. var l = cues.length;
  5741. this.cues_ = cues;
  5742. this.length_ = cues.length;
  5743. var defineProp = function defineProp(index) {
  5744. if (!('' + index in this)) {
  5745. Object.defineProperty(this, '' + index, {
  5746. get: function get() {
  5747. return this.cues_[index];
  5748. }
  5749. });
  5750. }
  5751. };
  5752. if (oldLength < l) {
  5753. i = oldLength;
  5754. for (; i < l; i++) {
  5755. defineProp.call(this, i);
  5756. }
  5757. }
  5758. }
  5759. /**
  5760. * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
  5761. *
  5762. * @param {string} id
  5763. * The id of the cue that should be searched for.
  5764. *
  5765. * @return {TextTrackCueList~TextTrackCue|null}
  5766. * A single cue or null if none was found.
  5767. */
  5768. ;
  5769. _proto.getCueById = function getCueById(id) {
  5770. var result = null;
  5771. for (var i = 0, l = this.length; i < l; i++) {
  5772. var cue = this[i];
  5773. if (cue.id === id) {
  5774. result = cue;
  5775. break;
  5776. }
  5777. }
  5778. return result;
  5779. };
  5780. return TextTrackCueList;
  5781. }();
  5782. /**
  5783. * @file track-kinds.js
  5784. */
  5785. /**
  5786. * All possible `VideoTrackKind`s
  5787. *
  5788. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
  5789. * @typedef VideoTrack~Kind
  5790. * @enum
  5791. */
  5792. var VideoTrackKind = {
  5793. alternative: 'alternative',
  5794. captions: 'captions',
  5795. main: 'main',
  5796. sign: 'sign',
  5797. subtitles: 'subtitles',
  5798. commentary: 'commentary'
  5799. };
  5800. /**
  5801. * All possible `AudioTrackKind`s
  5802. *
  5803. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
  5804. * @typedef AudioTrack~Kind
  5805. * @enum
  5806. */
  5807. var AudioTrackKind = {
  5808. 'alternative': 'alternative',
  5809. 'descriptions': 'descriptions',
  5810. 'main': 'main',
  5811. 'main-desc': 'main-desc',
  5812. 'translation': 'translation',
  5813. 'commentary': 'commentary'
  5814. };
  5815. /**
  5816. * All possible `TextTrackKind`s
  5817. *
  5818. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
  5819. * @typedef TextTrack~Kind
  5820. * @enum
  5821. */
  5822. var TextTrackKind = {
  5823. subtitles: 'subtitles',
  5824. captions: 'captions',
  5825. descriptions: 'descriptions',
  5826. chapters: 'chapters',
  5827. metadata: 'metadata'
  5828. };
  5829. /**
  5830. * All possible `TextTrackMode`s
  5831. *
  5832. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
  5833. * @typedef TextTrack~Mode
  5834. * @enum
  5835. */
  5836. var TextTrackMode = {
  5837. disabled: 'disabled',
  5838. hidden: 'hidden',
  5839. showing: 'showing'
  5840. };
  5841. /**
  5842. * A Track class that contains all of the common functionality for {@link AudioTrack},
  5843. * {@link VideoTrack}, and {@link TextTrack}.
  5844. *
  5845. * > Note: This class should not be used directly
  5846. *
  5847. * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
  5848. * @extends EventTarget
  5849. * @abstract
  5850. */
  5851. var Track =
  5852. /*#__PURE__*/
  5853. function (_EventTarget) {
  5854. _inheritsLoose(Track, _EventTarget);
  5855. /**
  5856. * Create an instance of this class.
  5857. *
  5858. * @param {Object} [options={}]
  5859. * Object of option names and values
  5860. *
  5861. * @param {string} [options.kind='']
  5862. * A valid kind for the track type you are creating.
  5863. *
  5864. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  5865. * A unique id for this AudioTrack.
  5866. *
  5867. * @param {string} [options.label='']
  5868. * The menu label for this track.
  5869. *
  5870. * @param {string} [options.language='']
  5871. * A valid two character language code.
  5872. *
  5873. * @abstract
  5874. */
  5875. function Track(options) {
  5876. var _this;
  5877. if (options === void 0) {
  5878. options = {};
  5879. }
  5880. _this = _EventTarget.call(this) || this;
  5881. var trackProps = {
  5882. id: options.id || 'vjs_track_' + newGUID(),
  5883. kind: options.kind || '',
  5884. label: options.label || '',
  5885. language: options.language || ''
  5886. };
  5887. /**
  5888. * @memberof Track
  5889. * @member {string} id
  5890. * The id of this track. Cannot be changed after creation.
  5891. * @instance
  5892. *
  5893. * @readonly
  5894. */
  5895. /**
  5896. * @memberof Track
  5897. * @member {string} kind
  5898. * The kind of track that this is. Cannot be changed after creation.
  5899. * @instance
  5900. *
  5901. * @readonly
  5902. */
  5903. /**
  5904. * @memberof Track
  5905. * @member {string} label
  5906. * The label of this track. Cannot be changed after creation.
  5907. * @instance
  5908. *
  5909. * @readonly
  5910. */
  5911. /**
  5912. * @memberof Track
  5913. * @member {string} language
  5914. * The two letter language code for this track. Cannot be changed after
  5915. * creation.
  5916. * @instance
  5917. *
  5918. * @readonly
  5919. */
  5920. var _loop = function _loop(key) {
  5921. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), key, {
  5922. get: function get() {
  5923. return trackProps[key];
  5924. },
  5925. set: function set() {}
  5926. });
  5927. };
  5928. for (var key in trackProps) {
  5929. _loop(key);
  5930. }
  5931. return _this;
  5932. }
  5933. return Track;
  5934. }(EventTarget);
  5935. /**
  5936. * @file url.js
  5937. * @module url
  5938. */
  5939. /**
  5940. * @typedef {Object} url:URLObject
  5941. *
  5942. * @property {string} protocol
  5943. * The protocol of the url that was parsed.
  5944. *
  5945. * @property {string} hostname
  5946. * The hostname of the url that was parsed.
  5947. *
  5948. * @property {string} port
  5949. * The port of the url that was parsed.
  5950. *
  5951. * @property {string} pathname
  5952. * The pathname of the url that was parsed.
  5953. *
  5954. * @property {string} search
  5955. * The search query of the url that was parsed.
  5956. *
  5957. * @property {string} hash
  5958. * The hash of the url that was parsed.
  5959. *
  5960. * @property {string} host
  5961. * The host of the url that was parsed.
  5962. */
  5963. /**
  5964. * Resolve and parse the elements of a URL.
  5965. *
  5966. * @function
  5967. * @param {String} url
  5968. * The url to parse
  5969. *
  5970. * @return {url:URLObject}
  5971. * An object of url details
  5972. */
  5973. var parseUrl = function parseUrl(url) {
  5974. var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
  5975. var a = document.createElement('a');
  5976. a.href = url; // IE8 (and 9?) Fix
  5977. // ie8 doesn't parse the URL correctly until the anchor is actually
  5978. // added to the body, and an innerHTML is needed to trigger the parsing
  5979. var addToBody = a.host === '' && a.protocol !== 'file:';
  5980. var div;
  5981. if (addToBody) {
  5982. div = document.createElement('div');
  5983. div.innerHTML = "<a href=\"" + url + "\"></a>";
  5984. a = div.firstChild; // prevent the div from affecting layout
  5985. div.setAttribute('style', 'display:none; position:absolute;');
  5986. document.body.appendChild(div);
  5987. } // Copy the specific URL properties to a new object
  5988. // This is also needed for IE8 because the anchor loses its
  5989. // properties when it's removed from the dom
  5990. var details = {};
  5991. for (var i = 0; i < props.length; i++) {
  5992. details[props[i]] = a[props[i]];
  5993. } // IE9 adds the port to the host property unlike everyone else. If
  5994. // a port identifier is added for standard ports, strip it.
  5995. if (details.protocol === 'http:') {
  5996. details.host = details.host.replace(/:80$/, '');
  5997. }
  5998. if (details.protocol === 'https:') {
  5999. details.host = details.host.replace(/:443$/, '');
  6000. }
  6001. if (!details.protocol) {
  6002. details.protocol = window$1.location.protocol;
  6003. }
  6004. if (addToBody) {
  6005. document.body.removeChild(div);
  6006. }
  6007. return details;
  6008. };
  6009. /**
  6010. * Get absolute version of relative URL. Used to tell Flash the correct URL.
  6011. *
  6012. * @function
  6013. * @param {string} url
  6014. * URL to make absolute
  6015. *
  6016. * @return {string}
  6017. * Absolute URL
  6018. *
  6019. * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
  6020. */
  6021. var getAbsoluteURL = function getAbsoluteURL(url) {
  6022. // Check if absolute URL
  6023. if (!url.match(/^https?:\/\//)) {
  6024. // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
  6025. var div = document.createElement('div');
  6026. div.innerHTML = "<a href=\"" + url + "\">x</a>";
  6027. url = div.firstChild.href;
  6028. }
  6029. return url;
  6030. };
  6031. /**
  6032. * Returns the extension of the passed file name. It will return an empty string
  6033. * if passed an invalid path.
  6034. *
  6035. * @function
  6036. * @param {string} path
  6037. * The fileName path like '/path/to/file.mp4'
  6038. *
  6039. * @return {string}
  6040. * The extension in lower case or an empty string if no
  6041. * extension could be found.
  6042. */
  6043. var getFileExtension = function getFileExtension(path) {
  6044. if (typeof path === 'string') {
  6045. var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
  6046. var pathParts = splitPathRe.exec(path);
  6047. if (pathParts) {
  6048. return pathParts.pop().toLowerCase();
  6049. }
  6050. }
  6051. return '';
  6052. };
  6053. /**
  6054. * Returns whether the url passed is a cross domain request or not.
  6055. *
  6056. * @function
  6057. * @param {string} url
  6058. * The url to check.
  6059. *
  6060. * @return {boolean}
  6061. * Whether it is a cross domain request or not.
  6062. */
  6063. var isCrossOrigin = function isCrossOrigin(url) {
  6064. var winLoc = window$1.location;
  6065. var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
  6066. var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
  6067. // IE8 doesn't know location.origin, so we won't rely on it here
  6068. var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
  6069. return crossOrigin;
  6070. };
  6071. var Url = /*#__PURE__*/Object.freeze({
  6072. parseUrl: parseUrl,
  6073. getAbsoluteURL: getAbsoluteURL,
  6074. getFileExtension: getFileExtension,
  6075. isCrossOrigin: isCrossOrigin
  6076. });
  6077. /**
  6078. * Takes a webvtt file contents and parses it into cues
  6079. *
  6080. * @param {string} srcContent
  6081. * webVTT file contents
  6082. *
  6083. * @param {TextTrack} track
  6084. * TextTrack to add cues to. Cues come from the srcContent.
  6085. *
  6086. * @private
  6087. */
  6088. var parseCues = function parseCues(srcContent, track) {
  6089. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
  6090. var errors = [];
  6091. parser.oncue = function (cue) {
  6092. track.addCue(cue);
  6093. };
  6094. parser.onparsingerror = function (error) {
  6095. errors.push(error);
  6096. };
  6097. parser.onflush = function () {
  6098. track.trigger({
  6099. type: 'loadeddata',
  6100. target: track
  6101. });
  6102. };
  6103. parser.parse(srcContent);
  6104. if (errors.length > 0) {
  6105. if (window$1.console && window$1.console.groupCollapsed) {
  6106. window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
  6107. }
  6108. errors.forEach(function (error) {
  6109. return log.error(error);
  6110. });
  6111. if (window$1.console && window$1.console.groupEnd) {
  6112. window$1.console.groupEnd();
  6113. }
  6114. }
  6115. parser.flush();
  6116. };
  6117. /**
  6118. * Load a `TextTrack` from a specified url.
  6119. *
  6120. * @param {string} src
  6121. * Url to load track from.
  6122. *
  6123. * @param {TextTrack} track
  6124. * Track to add cues to. Comes from the content at the end of `url`.
  6125. *
  6126. * @private
  6127. */
  6128. var loadTrack = function loadTrack(src, track) {
  6129. var opts = {
  6130. uri: src
  6131. };
  6132. var crossOrigin = isCrossOrigin(src);
  6133. if (crossOrigin) {
  6134. opts.cors = crossOrigin;
  6135. }
  6136. xhr(opts, bind(this, function (err, response, responseBody) {
  6137. if (err) {
  6138. return log.error(err, response);
  6139. }
  6140. track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  6141. // NOTE: this is only used for the alt/video.novtt.js build
  6142. if (typeof window$1.WebVTT !== 'function') {
  6143. if (track.tech_) {
  6144. // to prevent use before define eslint error, we define loadHandler
  6145. // as a let here
  6146. var loadHandler;
  6147. var errorHandler = function errorHandler() {
  6148. log.error("vttjs failed to load, stopping trying to process " + track.src);
  6149. track.tech_.off('vttjsloaded', loadHandler);
  6150. };
  6151. loadHandler = function loadHandler() {
  6152. track.tech_.off('vttjserror', errorHandler);
  6153. return parseCues(responseBody, track);
  6154. };
  6155. track.tech_.one('vttjsloaded', loadHandler);
  6156. track.tech_.one('vttjserror', errorHandler);
  6157. }
  6158. } else {
  6159. parseCues(responseBody, track);
  6160. }
  6161. }));
  6162. };
  6163. /**
  6164. * A representation of a single `TextTrack`.
  6165. *
  6166. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
  6167. * @extends Track
  6168. */
  6169. var TextTrack =
  6170. /*#__PURE__*/
  6171. function (_Track) {
  6172. _inheritsLoose(TextTrack, _Track);
  6173. /**
  6174. * Create an instance of this class.
  6175. *
  6176. * @param {Object} options={}
  6177. * Object of option names and values
  6178. *
  6179. * @param {Tech} options.tech
  6180. * A reference to the tech that owns this TextTrack.
  6181. *
  6182. * @param {TextTrack~Kind} [options.kind='subtitles']
  6183. * A valid text track kind.
  6184. *
  6185. * @param {TextTrack~Mode} [options.mode='disabled']
  6186. * A valid text track mode.
  6187. *
  6188. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6189. * A unique id for this TextTrack.
  6190. *
  6191. * @param {string} [options.label='']
  6192. * The menu label for this track.
  6193. *
  6194. * @param {string} [options.language='']
  6195. * A valid two character language code.
  6196. *
  6197. * @param {string} [options.srclang='']
  6198. * A valid two character language code. An alternative, but deprioritized
  6199. * version of `options.language`
  6200. *
  6201. * @param {string} [options.src]
  6202. * A url to TextTrack cues.
  6203. *
  6204. * @param {boolean} [options.default]
  6205. * If this track should default to on or off.
  6206. */
  6207. function TextTrack(options) {
  6208. var _this;
  6209. if (options === void 0) {
  6210. options = {};
  6211. }
  6212. if (!options.tech) {
  6213. throw new Error('A tech was not provided.');
  6214. }
  6215. var settings = mergeOptions(options, {
  6216. kind: TextTrackKind[options.kind] || 'subtitles',
  6217. language: options.language || options.srclang || ''
  6218. });
  6219. var mode = TextTrackMode[settings.mode] || 'disabled';
  6220. var default_ = settings.default;
  6221. if (settings.kind === 'metadata' || settings.kind === 'chapters') {
  6222. mode = 'hidden';
  6223. }
  6224. _this = _Track.call(this, settings) || this;
  6225. _this.tech_ = settings.tech;
  6226. _this.cues_ = [];
  6227. _this.activeCues_ = [];
  6228. var cues = new TextTrackCueList(_this.cues_);
  6229. var activeCues = new TextTrackCueList(_this.activeCues_);
  6230. var changed = false;
  6231. var timeupdateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
  6232. // Accessing this.activeCues for the side-effects of updating itself
  6233. // due to its nature as a getter function. Do not remove or cues will
  6234. // stop updating!
  6235. // Use the setter to prevent deletion from uglify (pure_getters rule)
  6236. this.activeCues = this.activeCues;
  6237. if (changed) {
  6238. this.trigger('cuechange');
  6239. changed = false;
  6240. }
  6241. });
  6242. if (mode !== 'disabled') {
  6243. _this.tech_.ready(function () {
  6244. _this.tech_.on('timeupdate', timeupdateHandler);
  6245. }, true);
  6246. }
  6247. Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
  6248. /**
  6249. * @memberof TextTrack
  6250. * @member {boolean} default
  6251. * If this track was set to be on or off by default. Cannot be changed after
  6252. * creation.
  6253. * @instance
  6254. *
  6255. * @readonly
  6256. */
  6257. default: {
  6258. get: function get() {
  6259. return default_;
  6260. },
  6261. set: function set() {}
  6262. },
  6263. /**
  6264. * @memberof TextTrack
  6265. * @member {string} mode
  6266. * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
  6267. * not be set if setting to an invalid mode.
  6268. * @instance
  6269. *
  6270. * @fires TextTrack#modechange
  6271. */
  6272. mode: {
  6273. get: function get() {
  6274. return mode;
  6275. },
  6276. set: function set(newMode) {
  6277. var _this2 = this;
  6278. if (!TextTrackMode[newMode]) {
  6279. return;
  6280. }
  6281. mode = newMode;
  6282. if (mode !== 'disabled') {
  6283. this.tech_.ready(function () {
  6284. _this2.tech_.on('timeupdate', timeupdateHandler);
  6285. }, true);
  6286. } else {
  6287. this.tech_.off('timeupdate', timeupdateHandler);
  6288. }
  6289. /**
  6290. * An event that fires when mode changes on this track. This allows
  6291. * the TextTrackList that holds this track to act accordingly.
  6292. *
  6293. * > Note: This is not part of the spec!
  6294. *
  6295. * @event TextTrack#modechange
  6296. * @type {EventTarget~Event}
  6297. */
  6298. this.trigger('modechange');
  6299. }
  6300. },
  6301. /**
  6302. * @memberof TextTrack
  6303. * @member {TextTrackCueList} cues
  6304. * The text track cue list for this TextTrack.
  6305. * @instance
  6306. */
  6307. cues: {
  6308. get: function get() {
  6309. if (!this.loaded_) {
  6310. return null;
  6311. }
  6312. return cues;
  6313. },
  6314. set: function set() {}
  6315. },
  6316. /**
  6317. * @memberof TextTrack
  6318. * @member {TextTrackCueList} activeCues
  6319. * The list text track cues that are currently active for this TextTrack.
  6320. * @instance
  6321. */
  6322. activeCues: {
  6323. get: function get() {
  6324. if (!this.loaded_) {
  6325. return null;
  6326. } // nothing to do
  6327. if (this.cues.length === 0) {
  6328. return activeCues;
  6329. }
  6330. var ct = this.tech_.currentTime();
  6331. var active = [];
  6332. for (var i = 0, l = this.cues.length; i < l; i++) {
  6333. var cue = this.cues[i];
  6334. if (cue.startTime <= ct && cue.endTime >= ct) {
  6335. active.push(cue);
  6336. } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
  6337. active.push(cue);
  6338. }
  6339. }
  6340. changed = false;
  6341. if (active.length !== this.activeCues_.length) {
  6342. changed = true;
  6343. } else {
  6344. for (var _i = 0; _i < active.length; _i++) {
  6345. if (this.activeCues_.indexOf(active[_i]) === -1) {
  6346. changed = true;
  6347. }
  6348. }
  6349. }
  6350. this.activeCues_ = active;
  6351. activeCues.setCues_(this.activeCues_);
  6352. return activeCues;
  6353. },
  6354. // /!\ Keep this setter empty (see the timeupdate handler above)
  6355. set: function set() {}
  6356. }
  6357. });
  6358. if (settings.src) {
  6359. _this.src = settings.src;
  6360. loadTrack(settings.src, _assertThisInitialized(_assertThisInitialized(_this)));
  6361. } else {
  6362. _this.loaded_ = true;
  6363. }
  6364. return _this;
  6365. }
  6366. /**
  6367. * Add a cue to the internal list of cues.
  6368. *
  6369. * @param {TextTrack~Cue} cue
  6370. * The cue to add to our internal list
  6371. */
  6372. var _proto = TextTrack.prototype;
  6373. _proto.addCue = function addCue(originalCue) {
  6374. var cue = originalCue;
  6375. if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
  6376. cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
  6377. for (var prop in originalCue) {
  6378. if (!(prop in cue)) {
  6379. cue[prop] = originalCue[prop];
  6380. }
  6381. } // make sure that `id` is copied over
  6382. cue.id = originalCue.id;
  6383. cue.originalCue_ = originalCue;
  6384. }
  6385. var tracks = this.tech_.textTracks();
  6386. for (var i = 0; i < tracks.length; i++) {
  6387. if (tracks[i] !== this) {
  6388. tracks[i].removeCue(cue);
  6389. }
  6390. }
  6391. this.cues_.push(cue);
  6392. this.cues.setCues_(this.cues_);
  6393. }
  6394. /**
  6395. * Remove a cue from our internal list
  6396. *
  6397. * @param {TextTrack~Cue} removeCue
  6398. * The cue to remove from our internal list
  6399. */
  6400. ;
  6401. _proto.removeCue = function removeCue(_removeCue) {
  6402. var i = this.cues_.length;
  6403. while (i--) {
  6404. var cue = this.cues_[i];
  6405. if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
  6406. this.cues_.splice(i, 1);
  6407. this.cues.setCues_(this.cues_);
  6408. break;
  6409. }
  6410. }
  6411. };
  6412. return TextTrack;
  6413. }(Track);
  6414. /**
  6415. * cuechange - One or more cues in the track have become active or stopped being active.
  6416. */
  6417. TextTrack.prototype.allowedEvents_ = {
  6418. cuechange: 'cuechange'
  6419. };
  6420. /**
  6421. * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
  6422. * only one `AudioTrack` in the list will be enabled at a time.
  6423. *
  6424. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
  6425. * @extends Track
  6426. */
  6427. var AudioTrack =
  6428. /*#__PURE__*/
  6429. function (_Track) {
  6430. _inheritsLoose(AudioTrack, _Track);
  6431. /**
  6432. * Create an instance of this class.
  6433. *
  6434. * @param {Object} [options={}]
  6435. * Object of option names and values
  6436. *
  6437. * @param {AudioTrack~Kind} [options.kind='']
  6438. * A valid audio track kind
  6439. *
  6440. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6441. * A unique id for this AudioTrack.
  6442. *
  6443. * @param {string} [options.label='']
  6444. * The menu label for this track.
  6445. *
  6446. * @param {string} [options.language='']
  6447. * A valid two character language code.
  6448. *
  6449. * @param {boolean} [options.enabled]
  6450. * If this track is the one that is currently playing. If this track is part of
  6451. * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
  6452. */
  6453. function AudioTrack(options) {
  6454. var _this;
  6455. if (options === void 0) {
  6456. options = {};
  6457. }
  6458. var settings = mergeOptions(options, {
  6459. kind: AudioTrackKind[options.kind] || ''
  6460. });
  6461. _this = _Track.call(this, settings) || this;
  6462. var enabled = false;
  6463. /**
  6464. * @memberof AudioTrack
  6465. * @member {boolean} enabled
  6466. * If this `AudioTrack` is enabled or not. When setting this will
  6467. * fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
  6468. * @instance
  6469. *
  6470. * @fires VideoTrack#selectedchange
  6471. */
  6472. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'enabled', {
  6473. get: function get() {
  6474. return enabled;
  6475. },
  6476. set: function set(newEnabled) {
  6477. // an invalid or unchanged value
  6478. if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
  6479. return;
  6480. }
  6481. enabled = newEnabled;
  6482. /**
  6483. * An event that fires when enabled changes on this track. This allows
  6484. * the AudioTrackList that holds this track to act accordingly.
  6485. *
  6486. * > Note: This is not part of the spec! Native tracks will do
  6487. * this internally without an event.
  6488. *
  6489. * @event AudioTrack#enabledchange
  6490. * @type {EventTarget~Event}
  6491. */
  6492. this.trigger('enabledchange');
  6493. }
  6494. }); // if the user sets this track to selected then
  6495. // set selected to that true value otherwise
  6496. // we keep it false
  6497. if (settings.enabled) {
  6498. _this.enabled = settings.enabled;
  6499. }
  6500. _this.loaded_ = true;
  6501. return _this;
  6502. }
  6503. return AudioTrack;
  6504. }(Track);
  6505. /**
  6506. * A representation of a single `VideoTrack`.
  6507. *
  6508. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
  6509. * @extends Track
  6510. */
  6511. var VideoTrack =
  6512. /*#__PURE__*/
  6513. function (_Track) {
  6514. _inheritsLoose(VideoTrack, _Track);
  6515. /**
  6516. * Create an instance of this class.
  6517. *
  6518. * @param {Object} [options={}]
  6519. * Object of option names and values
  6520. *
  6521. * @param {string} [options.kind='']
  6522. * A valid {@link VideoTrack~Kind}
  6523. *
  6524. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6525. * A unique id for this AudioTrack.
  6526. *
  6527. * @param {string} [options.label='']
  6528. * The menu label for this track.
  6529. *
  6530. * @param {string} [options.language='']
  6531. * A valid two character language code.
  6532. *
  6533. * @param {boolean} [options.selected]
  6534. * If this track is the one that is currently playing.
  6535. */
  6536. function VideoTrack(options) {
  6537. var _this;
  6538. if (options === void 0) {
  6539. options = {};
  6540. }
  6541. var settings = mergeOptions(options, {
  6542. kind: VideoTrackKind[options.kind] || ''
  6543. });
  6544. _this = _Track.call(this, settings) || this;
  6545. var selected = false;
  6546. /**
  6547. * @memberof VideoTrack
  6548. * @member {boolean} selected
  6549. * If this `VideoTrack` is selected or not. When setting this will
  6550. * fire {@link VideoTrack#selectedchange} if the state of selected changed.
  6551. * @instance
  6552. *
  6553. * @fires VideoTrack#selectedchange
  6554. */
  6555. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selected', {
  6556. get: function get() {
  6557. return selected;
  6558. },
  6559. set: function set(newSelected) {
  6560. // an invalid or unchanged value
  6561. if (typeof newSelected !== 'boolean' || newSelected === selected) {
  6562. return;
  6563. }
  6564. selected = newSelected;
  6565. /**
  6566. * An event that fires when selected changes on this track. This allows
  6567. * the VideoTrackList that holds this track to act accordingly.
  6568. *
  6569. * > Note: This is not part of the spec! Native tracks will do
  6570. * this internally without an event.
  6571. *
  6572. * @event VideoTrack#selectedchange
  6573. * @type {EventTarget~Event}
  6574. */
  6575. this.trigger('selectedchange');
  6576. }
  6577. }); // if the user sets this track to selected then
  6578. // set selected to that true value otherwise
  6579. // we keep it false
  6580. if (settings.selected) {
  6581. _this.selected = settings.selected;
  6582. }
  6583. return _this;
  6584. }
  6585. return VideoTrack;
  6586. }(Track);
  6587. /**
  6588. * @memberof HTMLTrackElement
  6589. * @typedef {HTMLTrackElement~ReadyState}
  6590. * @enum {number}
  6591. */
  6592. var NONE = 0;
  6593. var LOADING = 1;
  6594. var LOADED = 2;
  6595. var ERROR = 3;
  6596. /**
  6597. * A single track represented in the DOM.
  6598. *
  6599. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
  6600. * @extends EventTarget
  6601. */
  6602. var HTMLTrackElement =
  6603. /*#__PURE__*/
  6604. function (_EventTarget) {
  6605. _inheritsLoose(HTMLTrackElement, _EventTarget);
  6606. /**
  6607. * Create an instance of this class.
  6608. *
  6609. * @param {Object} options={}
  6610. * Object of option names and values
  6611. *
  6612. * @param {Tech} options.tech
  6613. * A reference to the tech that owns this HTMLTrackElement.
  6614. *
  6615. * @param {TextTrack~Kind} [options.kind='subtitles']
  6616. * A valid text track kind.
  6617. *
  6618. * @param {TextTrack~Mode} [options.mode='disabled']
  6619. * A valid text track mode.
  6620. *
  6621. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6622. * A unique id for this TextTrack.
  6623. *
  6624. * @param {string} [options.label='']
  6625. * The menu label for this track.
  6626. *
  6627. * @param {string} [options.language='']
  6628. * A valid two character language code.
  6629. *
  6630. * @param {string} [options.srclang='']
  6631. * A valid two character language code. An alternative, but deprioritized
  6632. * vesion of `options.language`
  6633. *
  6634. * @param {string} [options.src]
  6635. * A url to TextTrack cues.
  6636. *
  6637. * @param {boolean} [options.default]
  6638. * If this track should default to on or off.
  6639. */
  6640. function HTMLTrackElement(options) {
  6641. var _this;
  6642. if (options === void 0) {
  6643. options = {};
  6644. }
  6645. _this = _EventTarget.call(this) || this;
  6646. var readyState;
  6647. var track = new TextTrack(options);
  6648. _this.kind = track.kind;
  6649. _this.src = track.src;
  6650. _this.srclang = track.language;
  6651. _this.label = track.label;
  6652. _this.default = track.default;
  6653. Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
  6654. /**
  6655. * @memberof HTMLTrackElement
  6656. * @member {HTMLTrackElement~ReadyState} readyState
  6657. * The current ready state of the track element.
  6658. * @instance
  6659. */
  6660. readyState: {
  6661. get: function get() {
  6662. return readyState;
  6663. }
  6664. },
  6665. /**
  6666. * @memberof HTMLTrackElement
  6667. * @member {TextTrack} track
  6668. * The underlying TextTrack object.
  6669. * @instance
  6670. *
  6671. */
  6672. track: {
  6673. get: function get() {
  6674. return track;
  6675. }
  6676. }
  6677. });
  6678. readyState = NONE;
  6679. /**
  6680. * @listens TextTrack#loadeddata
  6681. * @fires HTMLTrackElement#load
  6682. */
  6683. track.addEventListener('loadeddata', function () {
  6684. readyState = LOADED;
  6685. _this.trigger({
  6686. type: 'load',
  6687. target: _assertThisInitialized(_assertThisInitialized(_this))
  6688. });
  6689. });
  6690. return _this;
  6691. }
  6692. return HTMLTrackElement;
  6693. }(EventTarget);
  6694. HTMLTrackElement.prototype.allowedEvents_ = {
  6695. load: 'load'
  6696. };
  6697. HTMLTrackElement.NONE = NONE;
  6698. HTMLTrackElement.LOADING = LOADING;
  6699. HTMLTrackElement.LOADED = LOADED;
  6700. HTMLTrackElement.ERROR = ERROR;
  6701. /*
  6702. * This file contains all track properties that are used in
  6703. * player.js, tech.js, html5.js and possibly other techs in the future.
  6704. */
  6705. var NORMAL = {
  6706. audio: {
  6707. ListClass: AudioTrackList,
  6708. TrackClass: AudioTrack,
  6709. capitalName: 'Audio'
  6710. },
  6711. video: {
  6712. ListClass: VideoTrackList,
  6713. TrackClass: VideoTrack,
  6714. capitalName: 'Video'
  6715. },
  6716. text: {
  6717. ListClass: TextTrackList,
  6718. TrackClass: TextTrack,
  6719. capitalName: 'Text'
  6720. }
  6721. };
  6722. Object.keys(NORMAL).forEach(function (type) {
  6723. NORMAL[type].getterName = type + "Tracks";
  6724. NORMAL[type].privateName = type + "Tracks_";
  6725. });
  6726. var REMOTE = {
  6727. remoteText: {
  6728. ListClass: TextTrackList,
  6729. TrackClass: TextTrack,
  6730. capitalName: 'RemoteText',
  6731. getterName: 'remoteTextTracks',
  6732. privateName: 'remoteTextTracks_'
  6733. },
  6734. remoteTextEl: {
  6735. ListClass: HtmlTrackElementList,
  6736. TrackClass: HTMLTrackElement,
  6737. capitalName: 'RemoteTextTrackEls',
  6738. getterName: 'remoteTextTrackEls',
  6739. privateName: 'remoteTextTrackEls_'
  6740. }
  6741. };
  6742. var ALL = mergeOptions(NORMAL, REMOTE);
  6743. REMOTE.names = Object.keys(REMOTE);
  6744. NORMAL.names = Object.keys(NORMAL);
  6745. ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
  6746. /**
  6747. * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
  6748. * that just contains the src url alone.
  6749. * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
  6750. * `var SourceString = 'http://example.com/some-video.mp4';`
  6751. *
  6752. * @typedef {Object|string} Tech~SourceObject
  6753. *
  6754. * @property {string} src
  6755. * The url to the source
  6756. *
  6757. * @property {string} type
  6758. * The mime type of the source
  6759. */
  6760. /**
  6761. * A function used by {@link Tech} to create a new {@link TextTrack}.
  6762. *
  6763. * @private
  6764. *
  6765. * @param {Tech} self
  6766. * An instance of the Tech class.
  6767. *
  6768. * @param {string} kind
  6769. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  6770. *
  6771. * @param {string} [label]
  6772. * Label to identify the text track
  6773. *
  6774. * @param {string} [language]
  6775. * Two letter language abbreviation
  6776. *
  6777. * @param {Object} [options={}]
  6778. * An object with additional text track options
  6779. *
  6780. * @return {TextTrack}
  6781. * The text track that was created.
  6782. */
  6783. function createTrackHelper(self, kind, label, language, options) {
  6784. if (options === void 0) {
  6785. options = {};
  6786. }
  6787. var tracks = self.textTracks();
  6788. options.kind = kind;
  6789. if (label) {
  6790. options.label = label;
  6791. }
  6792. if (language) {
  6793. options.language = language;
  6794. }
  6795. options.tech = self;
  6796. var track = new ALL.text.TrackClass(options);
  6797. tracks.addTrack(track);
  6798. return track;
  6799. }
  6800. /**
  6801. * This is the base class for media playback technology controllers, such as
  6802. * {@link Flash} and {@link HTML5}
  6803. *
  6804. * @extends Component
  6805. */
  6806. var Tech =
  6807. /*#__PURE__*/
  6808. function (_Component) {
  6809. _inheritsLoose(Tech, _Component);
  6810. /**
  6811. * Create an instance of this Tech.
  6812. *
  6813. * @param {Object} [options]
  6814. * The key/value store of player options.
  6815. *
  6816. * @param {Component~ReadyCallback} ready
  6817. * Callback function to call when the `HTML5` Tech is ready.
  6818. */
  6819. function Tech(options, ready) {
  6820. var _this;
  6821. if (options === void 0) {
  6822. options = {};
  6823. }
  6824. if (ready === void 0) {
  6825. ready = function ready() {};
  6826. }
  6827. // we don't want the tech to report user activity automatically.
  6828. // This is done manually in addControlsListeners
  6829. options.reportTouchActivity = false;
  6830. _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to
  6831. // implement a very limited played()
  6832. _this.hasStarted_ = false;
  6833. _this.on('playing', function () {
  6834. this.hasStarted_ = true;
  6835. });
  6836. _this.on('loadstart', function () {
  6837. this.hasStarted_ = false;
  6838. });
  6839. ALL.names.forEach(function (name) {
  6840. var props = ALL[name];
  6841. if (options && options[props.getterName]) {
  6842. _this[props.privateName] = options[props.getterName];
  6843. }
  6844. }); // Manually track progress in cases where the browser/flash player doesn't report it.
  6845. if (!_this.featuresProgressEvents) {
  6846. _this.manualProgressOn();
  6847. } // Manually track timeupdates in cases where the browser/flash player doesn't report it.
  6848. if (!_this.featuresTimeupdateEvents) {
  6849. _this.manualTimeUpdatesOn();
  6850. }
  6851. ['Text', 'Audio', 'Video'].forEach(function (track) {
  6852. if (options["native" + track + "Tracks"] === false) {
  6853. _this["featuresNative" + track + "Tracks"] = false;
  6854. }
  6855. });
  6856. if (options.nativeCaptions === false || options.nativeTextTracks === false) {
  6857. _this.featuresNativeTextTracks = false;
  6858. } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
  6859. _this.featuresNativeTextTracks = true;
  6860. }
  6861. if (!_this.featuresNativeTextTracks) {
  6862. _this.emulateTextTracks();
  6863. }
  6864. _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
  6865. _this.initTrackListeners(); // Turn on component tap events only if not using native controls
  6866. if (!options.nativeControlsForTouch) {
  6867. _this.emitTapEvents();
  6868. }
  6869. if (_this.constructor) {
  6870. _this.name_ = _this.constructor.name || 'Unknown Tech';
  6871. }
  6872. return _this;
  6873. }
  6874. /**
  6875. * A special function to trigger source set in a way that will allow player
  6876. * to re-trigger if the player or tech are not ready yet.
  6877. *
  6878. * @fires Tech#sourceset
  6879. * @param {string} src The source string at the time of the source changing.
  6880. */
  6881. var _proto = Tech.prototype;
  6882. _proto.triggerSourceset = function triggerSourceset(src) {
  6883. var _this2 = this;
  6884. if (!this.isReady_) {
  6885. // on initial ready we have to trigger source set
  6886. // 1ms after ready so that player can watch for it.
  6887. this.one('ready', function () {
  6888. return _this2.setTimeout(function () {
  6889. return _this2.triggerSourceset(src);
  6890. }, 1);
  6891. });
  6892. }
  6893. /**
  6894. * Fired when the source is set on the tech causing the media element
  6895. * to reload.
  6896. *
  6897. * @see {@link Player#event:sourceset}
  6898. * @event Tech#sourceset
  6899. * @type {EventTarget~Event}
  6900. */
  6901. this.trigger({
  6902. src: src,
  6903. type: 'sourceset'
  6904. });
  6905. }
  6906. /* Fallbacks for unsupported event types
  6907. ================================================================================ */
  6908. /**
  6909. * Polyfill the `progress` event for browsers that don't support it natively.
  6910. *
  6911. * @see {@link Tech#trackProgress}
  6912. */
  6913. ;
  6914. _proto.manualProgressOn = function manualProgressOn() {
  6915. this.on('durationchange', this.onDurationChange);
  6916. this.manualProgress = true; // Trigger progress watching when a source begins loading
  6917. this.one('ready', this.trackProgress);
  6918. }
  6919. /**
  6920. * Turn off the polyfill for `progress` events that was created in
  6921. * {@link Tech#manualProgressOn}
  6922. */
  6923. ;
  6924. _proto.manualProgressOff = function manualProgressOff() {
  6925. this.manualProgress = false;
  6926. this.stopTrackingProgress();
  6927. this.off('durationchange', this.onDurationChange);
  6928. }
  6929. /**
  6930. * This is used to trigger a `progress` event when the buffered percent changes. It
  6931. * sets an interval function that will be called every 500 milliseconds to check if the
  6932. * buffer end percent has changed.
  6933. *
  6934. * > This function is called by {@link Tech#manualProgressOn}
  6935. *
  6936. * @param {EventTarget~Event} event
  6937. * The `ready` event that caused this to run.
  6938. *
  6939. * @listens Tech#ready
  6940. * @fires Tech#progress
  6941. */
  6942. ;
  6943. _proto.trackProgress = function trackProgress(event) {
  6944. this.stopTrackingProgress();
  6945. this.progressInterval = this.setInterval(bind(this, function () {
  6946. // Don't trigger unless buffered amount is greater than last time
  6947. var numBufferedPercent = this.bufferedPercent();
  6948. if (this.bufferedPercent_ !== numBufferedPercent) {
  6949. /**
  6950. * See {@link Player#progress}
  6951. *
  6952. * @event Tech#progress
  6953. * @type {EventTarget~Event}
  6954. */
  6955. this.trigger('progress');
  6956. }
  6957. this.bufferedPercent_ = numBufferedPercent;
  6958. if (numBufferedPercent === 1) {
  6959. this.stopTrackingProgress();
  6960. }
  6961. }), 500);
  6962. }
  6963. /**
  6964. * Update our internal duration on a `durationchange` event by calling
  6965. * {@link Tech#duration}.
  6966. *
  6967. * @param {EventTarget~Event} event
  6968. * The `durationchange` event that caused this to run.
  6969. *
  6970. * @listens Tech#durationchange
  6971. */
  6972. ;
  6973. _proto.onDurationChange = function onDurationChange(event) {
  6974. this.duration_ = this.duration();
  6975. }
  6976. /**
  6977. * Get and create a `TimeRange` object for buffering.
  6978. *
  6979. * @return {TimeRange}
  6980. * The time range object that was created.
  6981. */
  6982. ;
  6983. _proto.buffered = function buffered() {
  6984. return createTimeRanges(0, 0);
  6985. }
  6986. /**
  6987. * Get the percentage of the current video that is currently buffered.
  6988. *
  6989. * @return {number}
  6990. * A number from 0 to 1 that represents the decimal percentage of the
  6991. * video that is buffered.
  6992. *
  6993. */
  6994. ;
  6995. _proto.bufferedPercent = function bufferedPercent$$1() {
  6996. return bufferedPercent(this.buffered(), this.duration_);
  6997. }
  6998. /**
  6999. * Turn off the polyfill for `progress` events that was created in
  7000. * {@link Tech#manualProgressOn}
  7001. * Stop manually tracking progress events by clearing the interval that was set in
  7002. * {@link Tech#trackProgress}.
  7003. */
  7004. ;
  7005. _proto.stopTrackingProgress = function stopTrackingProgress() {
  7006. this.clearInterval(this.progressInterval);
  7007. }
  7008. /**
  7009. * Polyfill the `timeupdate` event for browsers that don't support it.
  7010. *
  7011. * @see {@link Tech#trackCurrentTime}
  7012. */
  7013. ;
  7014. _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
  7015. this.manualTimeUpdates = true;
  7016. this.on('play', this.trackCurrentTime);
  7017. this.on('pause', this.stopTrackingCurrentTime);
  7018. }
  7019. /**
  7020. * Turn off the polyfill for `timeupdate` events that was created in
  7021. * {@link Tech#manualTimeUpdatesOn}
  7022. */
  7023. ;
  7024. _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
  7025. this.manualTimeUpdates = false;
  7026. this.stopTrackingCurrentTime();
  7027. this.off('play', this.trackCurrentTime);
  7028. this.off('pause', this.stopTrackingCurrentTime);
  7029. }
  7030. /**
  7031. * Sets up an interval function to track current time and trigger `timeupdate` every
  7032. * 250 milliseconds.
  7033. *
  7034. * @listens Tech#play
  7035. * @triggers Tech#timeupdate
  7036. */
  7037. ;
  7038. _proto.trackCurrentTime = function trackCurrentTime() {
  7039. if (this.currentTimeInterval) {
  7040. this.stopTrackingCurrentTime();
  7041. }
  7042. this.currentTimeInterval = this.setInterval(function () {
  7043. /**
  7044. * Triggered at an interval of 250ms to indicated that time is passing in the video.
  7045. *
  7046. * @event Tech#timeupdate
  7047. * @type {EventTarget~Event}
  7048. */
  7049. this.trigger({
  7050. type: 'timeupdate',
  7051. target: this,
  7052. manuallyTriggered: true
  7053. }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  7054. }, 250);
  7055. }
  7056. /**
  7057. * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
  7058. * `timeupdate` event is no longer triggered.
  7059. *
  7060. * @listens {Tech#pause}
  7061. */
  7062. ;
  7063. _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
  7064. this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
  7065. // the progress bar won't make it all the way to the end
  7066. this.trigger({
  7067. type: 'timeupdate',
  7068. target: this,
  7069. manuallyTriggered: true
  7070. });
  7071. }
  7072. /**
  7073. * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
  7074. * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
  7075. *
  7076. * @fires Component#dispose
  7077. */
  7078. ;
  7079. _proto.dispose = function dispose() {
  7080. // clear out all tracks because we can't reuse them between techs
  7081. this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
  7082. if (this.manualProgress) {
  7083. this.manualProgressOff();
  7084. }
  7085. if (this.manualTimeUpdates) {
  7086. this.manualTimeUpdatesOff();
  7087. }
  7088. _Component.prototype.dispose.call(this);
  7089. }
  7090. /**
  7091. * Clear out a single `TrackList` or an array of `TrackLists` given their names.
  7092. *
  7093. * > Note: Techs without source handlers should call this between sources for `video`
  7094. * & `audio` tracks. You don't want to use them between tracks!
  7095. *
  7096. * @param {string[]|string} types
  7097. * TrackList names to clear, valid names are `video`, `audio`, and
  7098. * `text`.
  7099. */
  7100. ;
  7101. _proto.clearTracks = function clearTracks(types) {
  7102. var _this3 = this;
  7103. types = [].concat(types); // clear out all tracks because we can't reuse them between techs
  7104. types.forEach(function (type) {
  7105. var list = _this3[type + "Tracks"]() || [];
  7106. var i = list.length;
  7107. while (i--) {
  7108. var track = list[i];
  7109. if (type === 'text') {
  7110. _this3.removeRemoteTextTrack(track);
  7111. }
  7112. list.removeTrack(track);
  7113. }
  7114. });
  7115. }
  7116. /**
  7117. * Remove any TextTracks added via addRemoteTextTrack that are
  7118. * flagged for automatic garbage collection
  7119. */
  7120. ;
  7121. _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
  7122. var list = this.autoRemoteTextTracks_ || [];
  7123. var i = list.length;
  7124. while (i--) {
  7125. var track = list[i];
  7126. this.removeRemoteTextTrack(track);
  7127. }
  7128. }
  7129. /**
  7130. * Reset the tech, which will removes all sources and reset the internal readyState.
  7131. *
  7132. * @abstract
  7133. */
  7134. ;
  7135. _proto.reset = function reset() {}
  7136. /**
  7137. * Get or set an error on the Tech.
  7138. *
  7139. * @param {MediaError} [err]
  7140. * Error to set on the Tech
  7141. *
  7142. * @return {MediaError|null}
  7143. * The current error object on the tech, or null if there isn't one.
  7144. */
  7145. ;
  7146. _proto.error = function error(err) {
  7147. if (err !== undefined) {
  7148. this.error_ = new MediaError(err);
  7149. this.trigger('error');
  7150. }
  7151. return this.error_;
  7152. }
  7153. /**
  7154. * Returns the `TimeRange`s that have been played through for the current source.
  7155. *
  7156. * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
  7157. * It only checks whether the source has played at all or not.
  7158. *
  7159. * @return {TimeRange}
  7160. * - A single time range if this video has played
  7161. * - An empty set of ranges if not.
  7162. */
  7163. ;
  7164. _proto.played = function played() {
  7165. if (this.hasStarted_) {
  7166. return createTimeRanges(0, 0);
  7167. }
  7168. return createTimeRanges();
  7169. }
  7170. /**
  7171. * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
  7172. * previously called.
  7173. *
  7174. * @fires Tech#timeupdate
  7175. */
  7176. ;
  7177. _proto.setCurrentTime = function setCurrentTime() {
  7178. // improve the accuracy of manual timeupdates
  7179. if (this.manualTimeUpdates) {
  7180. /**
  7181. * A manual `timeupdate` event.
  7182. *
  7183. * @event Tech#timeupdate
  7184. * @type {EventTarget~Event}
  7185. */
  7186. this.trigger({
  7187. type: 'timeupdate',
  7188. target: this,
  7189. manuallyTriggered: true
  7190. });
  7191. }
  7192. }
  7193. /**
  7194. * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
  7195. * {@link TextTrackList} events.
  7196. *
  7197. * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
  7198. *
  7199. * @fires Tech#audiotrackchange
  7200. * @fires Tech#videotrackchange
  7201. * @fires Tech#texttrackchange
  7202. */
  7203. ;
  7204. _proto.initTrackListeners = function initTrackListeners() {
  7205. var _this4 = this;
  7206. /**
  7207. * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
  7208. *
  7209. * @event Tech#audiotrackchange
  7210. * @type {EventTarget~Event}
  7211. */
  7212. /**
  7213. * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
  7214. *
  7215. * @event Tech#videotrackchange
  7216. * @type {EventTarget~Event}
  7217. */
  7218. /**
  7219. * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
  7220. *
  7221. * @event Tech#texttrackchange
  7222. * @type {EventTarget~Event}
  7223. */
  7224. NORMAL.names.forEach(function (name) {
  7225. var props = NORMAL[name];
  7226. var trackListChanges = function trackListChanges() {
  7227. _this4.trigger(name + "trackchange");
  7228. };
  7229. var tracks = _this4[props.getterName]();
  7230. tracks.addEventListener('removetrack', trackListChanges);
  7231. tracks.addEventListener('addtrack', trackListChanges);
  7232. _this4.on('dispose', function () {
  7233. tracks.removeEventListener('removetrack', trackListChanges);
  7234. tracks.removeEventListener('addtrack', trackListChanges);
  7235. });
  7236. });
  7237. }
  7238. /**
  7239. * Emulate TextTracks using vtt.js if necessary
  7240. *
  7241. * @fires Tech#vttjsloaded
  7242. * @fires Tech#vttjserror
  7243. */
  7244. ;
  7245. _proto.addWebVttScript_ = function addWebVttScript_() {
  7246. var _this5 = this;
  7247. if (window$1.WebVTT) {
  7248. return;
  7249. } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
  7250. // signals that the Tech is ready at which point Tech.el_ is part of the DOM
  7251. // before inserting the WebVTT script
  7252. if (document.body.contains(this.el())) {
  7253. // load via require if available and vtt.js script location was not passed in
  7254. // as an option. novtt builds will turn the above require call into an empty object
  7255. // which will cause this if check to always fail.
  7256. if (!this.options_['vtt.js'] && isPlain(vtt) && Object.keys(vtt).length > 0) {
  7257. this.trigger('vttjsloaded');
  7258. return;
  7259. } // load vtt.js via the script location option or the cdn of no location was
  7260. // passed in
  7261. var script = document.createElement('script');
  7262. script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
  7263. script.onload = function () {
  7264. /**
  7265. * Fired when vtt.js is loaded.
  7266. *
  7267. * @event Tech#vttjsloaded
  7268. * @type {EventTarget~Event}
  7269. */
  7270. _this5.trigger('vttjsloaded');
  7271. };
  7272. script.onerror = function () {
  7273. /**
  7274. * Fired when vtt.js was not loaded due to an error
  7275. *
  7276. * @event Tech#vttjsloaded
  7277. * @type {EventTarget~Event}
  7278. */
  7279. _this5.trigger('vttjserror');
  7280. };
  7281. this.on('dispose', function () {
  7282. script.onload = null;
  7283. script.onerror = null;
  7284. }); // but have not loaded yet and we set it to true before the inject so that
  7285. // we don't overwrite the injected window.WebVTT if it loads right away
  7286. window$1.WebVTT = true;
  7287. this.el().parentNode.appendChild(script);
  7288. } else {
  7289. this.ready(this.addWebVttScript_);
  7290. }
  7291. }
  7292. /**
  7293. * Emulate texttracks
  7294. *
  7295. */
  7296. ;
  7297. _proto.emulateTextTracks = function emulateTextTracks() {
  7298. var _this6 = this;
  7299. var tracks = this.textTracks();
  7300. var remoteTracks = this.remoteTextTracks();
  7301. var handleAddTrack = function handleAddTrack(e) {
  7302. return tracks.addTrack(e.track);
  7303. };
  7304. var handleRemoveTrack = function handleRemoveTrack(e) {
  7305. return tracks.removeTrack(e.track);
  7306. };
  7307. remoteTracks.on('addtrack', handleAddTrack);
  7308. remoteTracks.on('removetrack', handleRemoveTrack);
  7309. this.addWebVttScript_();
  7310. var updateDisplay = function updateDisplay() {
  7311. return _this6.trigger('texttrackchange');
  7312. };
  7313. var textTracksChanges = function textTracksChanges() {
  7314. updateDisplay();
  7315. for (var i = 0; i < tracks.length; i++) {
  7316. var track = tracks[i];
  7317. track.removeEventListener('cuechange', updateDisplay);
  7318. if (track.mode === 'showing') {
  7319. track.addEventListener('cuechange', updateDisplay);
  7320. }
  7321. }
  7322. };
  7323. textTracksChanges();
  7324. tracks.addEventListener('change', textTracksChanges);
  7325. tracks.addEventListener('addtrack', textTracksChanges);
  7326. tracks.addEventListener('removetrack', textTracksChanges);
  7327. this.on('dispose', function () {
  7328. remoteTracks.off('addtrack', handleAddTrack);
  7329. remoteTracks.off('removetrack', handleRemoveTrack);
  7330. tracks.removeEventListener('change', textTracksChanges);
  7331. tracks.removeEventListener('addtrack', textTracksChanges);
  7332. tracks.removeEventListener('removetrack', textTracksChanges);
  7333. for (var i = 0; i < tracks.length; i++) {
  7334. var track = tracks[i];
  7335. track.removeEventListener('cuechange', updateDisplay);
  7336. }
  7337. });
  7338. }
  7339. /**
  7340. * Create and returns a remote {@link TextTrack} object.
  7341. *
  7342. * @param {string} kind
  7343. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  7344. *
  7345. * @param {string} [label]
  7346. * Label to identify the text track
  7347. *
  7348. * @param {string} [language]
  7349. * Two letter language abbreviation
  7350. *
  7351. * @return {TextTrack}
  7352. * The TextTrack that gets created.
  7353. */
  7354. ;
  7355. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  7356. if (!kind) {
  7357. throw new Error('TextTrack kind is required but was not provided');
  7358. }
  7359. return createTrackHelper(this, kind, label, language);
  7360. }
  7361. /**
  7362. * Create an emulated TextTrack for use by addRemoteTextTrack
  7363. *
  7364. * This is intended to be overridden by classes that inherit from
  7365. * Tech in order to create native or custom TextTracks.
  7366. *
  7367. * @param {Object} options
  7368. * The object should contain the options to initialize the TextTrack with.
  7369. *
  7370. * @param {string} [options.kind]
  7371. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  7372. *
  7373. * @param {string} [options.label].
  7374. * Label to identify the text track
  7375. *
  7376. * @param {string} [options.language]
  7377. * Two letter language abbreviation.
  7378. *
  7379. * @return {HTMLTrackElement}
  7380. * The track element that gets created.
  7381. */
  7382. ;
  7383. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  7384. var track = mergeOptions(options, {
  7385. tech: this
  7386. });
  7387. return new REMOTE.remoteTextEl.TrackClass(track);
  7388. }
  7389. /**
  7390. * Creates a remote text track object and returns an html track element.
  7391. *
  7392. * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
  7393. *
  7394. * @param {Object} options
  7395. * See {@link Tech#createRemoteTextTrack} for more detailed properties.
  7396. *
  7397. * @param {boolean} [manualCleanup=true]
  7398. * - When false: the TextTrack will be automatically removed from the video
  7399. * element whenever the source changes
  7400. * - When True: The TextTrack will have to be cleaned up manually
  7401. *
  7402. * @return {HTMLTrackElement}
  7403. * An Html Track Element.
  7404. *
  7405. * @deprecated The default functionality for this function will be equivalent
  7406. * to "manualCleanup=false" in the future. The manualCleanup parameter will
  7407. * also be removed.
  7408. */
  7409. ;
  7410. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  7411. var _this7 = this;
  7412. if (options === void 0) {
  7413. options = {};
  7414. }
  7415. var htmlTrackElement = this.createRemoteTextTrack(options);
  7416. if (manualCleanup !== true && manualCleanup !== false) {
  7417. // deprecation warning
  7418. log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
  7419. manualCleanup = true;
  7420. } // store HTMLTrackElement and TextTrack to remote list
  7421. this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
  7422. this.remoteTextTracks().addTrack(htmlTrackElement.track);
  7423. if (manualCleanup !== true) {
  7424. // create the TextTrackList if it doesn't exist
  7425. this.ready(function () {
  7426. return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
  7427. });
  7428. }
  7429. return htmlTrackElement;
  7430. }
  7431. /**
  7432. * Remove a remote text track from the remote `TextTrackList`.
  7433. *
  7434. * @param {TextTrack} track
  7435. * `TextTrack` to remove from the `TextTrackList`
  7436. */
  7437. ;
  7438. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  7439. var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
  7440. this.remoteTextTrackEls().removeTrackElement_(trackElement);
  7441. this.remoteTextTracks().removeTrack(track);
  7442. this.autoRemoteTextTracks_.removeTrack(track);
  7443. }
  7444. /**
  7445. * Gets available media playback quality metrics as specified by the W3C's Media
  7446. * Playback Quality API.
  7447. *
  7448. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  7449. *
  7450. * @return {Object}
  7451. * An object with supported media playback quality metrics
  7452. *
  7453. * @abstract
  7454. */
  7455. ;
  7456. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  7457. return {};
  7458. }
  7459. /**
  7460. * A method to set a poster from a `Tech`.
  7461. *
  7462. * @abstract
  7463. */
  7464. ;
  7465. _proto.setPoster = function setPoster() {}
  7466. /**
  7467. * A method to check for the presence of the 'playsinline' <video> attribute.
  7468. *
  7469. * @abstract
  7470. */
  7471. ;
  7472. _proto.playsinline = function playsinline() {}
  7473. /**
  7474. * A method to set or unset the 'playsinline' <video> attribute.
  7475. *
  7476. * @abstract
  7477. */
  7478. ;
  7479. _proto.setPlaysinline = function setPlaysinline() {}
  7480. /**
  7481. * Attempt to force override of native audio tracks.
  7482. *
  7483. * @param {boolean} override - If set to true native audio will be overridden,
  7484. * otherwise native audio will potentially be used.
  7485. *
  7486. * @abstract
  7487. */
  7488. ;
  7489. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
  7490. /**
  7491. * Attempt to force override of native video tracks.
  7492. *
  7493. * @param {boolean} override - If set to true native video will be overridden,
  7494. * otherwise native video will potentially be used.
  7495. *
  7496. * @abstract
  7497. */
  7498. ;
  7499. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
  7500. /*
  7501. * Check if the tech can support the given mime-type.
  7502. *
  7503. * The base tech does not support any type, but source handlers might
  7504. * overwrite this.
  7505. *
  7506. * @param {string} type
  7507. * The mimetype to check for support
  7508. *
  7509. * @return {string}
  7510. * 'probably', 'maybe', or empty string
  7511. *
  7512. * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
  7513. *
  7514. * @abstract
  7515. */
  7516. ;
  7517. _proto.canPlayType = function canPlayType() {
  7518. return '';
  7519. }
  7520. /**
  7521. * Check if the type is supported by this tech.
  7522. *
  7523. * The base tech does not support any type, but source handlers might
  7524. * overwrite this.
  7525. *
  7526. * @param {string} type
  7527. * The media type to check
  7528. * @return {string} Returns the native video element's response
  7529. */
  7530. ;
  7531. Tech.canPlayType = function canPlayType() {
  7532. return '';
  7533. }
  7534. /**
  7535. * Check if the tech can support the given source
  7536. *
  7537. * @param {Object} srcObj
  7538. * The source object
  7539. * @param {Object} options
  7540. * The options passed to the tech
  7541. * @return {string} 'probably', 'maybe', or '' (empty string)
  7542. */
  7543. ;
  7544. Tech.canPlaySource = function canPlaySource(srcObj, options) {
  7545. return Tech.canPlayType(srcObj.type);
  7546. }
  7547. /*
  7548. * Return whether the argument is a Tech or not.
  7549. * Can be passed either a Class like `Html5` or a instance like `player.tech_`
  7550. *
  7551. * @param {Object} component
  7552. * The item to check
  7553. *
  7554. * @return {boolean}
  7555. * Whether it is a tech or not
  7556. * - True if it is a tech
  7557. * - False if it is not
  7558. */
  7559. ;
  7560. Tech.isTech = function isTech(component) {
  7561. return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
  7562. }
  7563. /**
  7564. * Registers a `Tech` into a shared list for videojs.
  7565. *
  7566. * @param {string} name
  7567. * Name of the `Tech` to register.
  7568. *
  7569. * @param {Object} tech
  7570. * The `Tech` class to register.
  7571. */
  7572. ;
  7573. Tech.registerTech = function registerTech(name, tech) {
  7574. if (!Tech.techs_) {
  7575. Tech.techs_ = {};
  7576. }
  7577. if (!Tech.isTech(tech)) {
  7578. throw new Error("Tech " + name + " must be a Tech");
  7579. }
  7580. if (!Tech.canPlayType) {
  7581. throw new Error('Techs must have a static canPlayType method on them');
  7582. }
  7583. if (!Tech.canPlaySource) {
  7584. throw new Error('Techs must have a static canPlaySource method on them');
  7585. }
  7586. name = toTitleCase(name);
  7587. Tech.techs_[name] = tech;
  7588. if (name !== 'Tech') {
  7589. // camel case the techName for use in techOrder
  7590. Tech.defaultTechOrder_.push(name);
  7591. }
  7592. return tech;
  7593. }
  7594. /**
  7595. * Get a `Tech` from the shared list by name.
  7596. *
  7597. * @param {string} name
  7598. * `camelCase` or `TitleCase` name of the Tech to get
  7599. *
  7600. * @return {Tech|undefined}
  7601. * The `Tech` or undefined if there was no tech with the name requested.
  7602. */
  7603. ;
  7604. Tech.getTech = function getTech(name) {
  7605. if (!name) {
  7606. return;
  7607. }
  7608. name = toTitleCase(name);
  7609. if (Tech.techs_ && Tech.techs_[name]) {
  7610. return Tech.techs_[name];
  7611. }
  7612. if (window$1 && window$1.videojs && window$1.videojs[name]) {
  7613. log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
  7614. return window$1.videojs[name];
  7615. }
  7616. };
  7617. return Tech;
  7618. }(Component);
  7619. /**
  7620. * Get the {@link VideoTrackList}
  7621. *
  7622. * @returns {VideoTrackList}
  7623. * @method Tech.prototype.videoTracks
  7624. */
  7625. /**
  7626. * Get the {@link AudioTrackList}
  7627. *
  7628. * @returns {AudioTrackList}
  7629. * @method Tech.prototype.audioTracks
  7630. */
  7631. /**
  7632. * Get the {@link TextTrackList}
  7633. *
  7634. * @returns {TextTrackList}
  7635. * @method Tech.prototype.textTracks
  7636. */
  7637. /**
  7638. * Get the remote element {@link TextTrackList}
  7639. *
  7640. * @returns {TextTrackList}
  7641. * @method Tech.prototype.remoteTextTracks
  7642. */
  7643. /**
  7644. * Get the remote element {@link HtmlTrackElementList}
  7645. *
  7646. * @returns {HtmlTrackElementList}
  7647. * @method Tech.prototype.remoteTextTrackEls
  7648. */
  7649. ALL.names.forEach(function (name) {
  7650. var props = ALL[name];
  7651. Tech.prototype[props.getterName] = function () {
  7652. this[props.privateName] = this[props.privateName] || new props.ListClass();
  7653. return this[props.privateName];
  7654. };
  7655. });
  7656. /**
  7657. * List of associated text tracks
  7658. *
  7659. * @type {TextTrackList}
  7660. * @private
  7661. * @property Tech#textTracks_
  7662. */
  7663. /**
  7664. * List of associated audio tracks.
  7665. *
  7666. * @type {AudioTrackList}
  7667. * @private
  7668. * @property Tech#audioTracks_
  7669. */
  7670. /**
  7671. * List of associated video tracks.
  7672. *
  7673. * @type {VideoTrackList}
  7674. * @private
  7675. * @property Tech#videoTracks_
  7676. */
  7677. /**
  7678. * Boolean indicating whether the `Tech` supports volume control.
  7679. *
  7680. * @type {boolean}
  7681. * @default
  7682. */
  7683. Tech.prototype.featuresVolumeControl = true;
  7684. /**
  7685. * Boolean indicating whether the `Tech` supports muting volume.
  7686. *
  7687. * @type {bolean}
  7688. * @default
  7689. */
  7690. Tech.prototype.featuresMuteControl = true;
  7691. /**
  7692. * Boolean indicating whether the `Tech` supports fullscreen resize control.
  7693. * Resizing plugins using request fullscreen reloads the plugin
  7694. *
  7695. * @type {boolean}
  7696. * @default
  7697. */
  7698. Tech.prototype.featuresFullscreenResize = false;
  7699. /**
  7700. * Boolean indicating whether the `Tech` supports changing the speed at which the video
  7701. * plays. Examples:
  7702. * - Set player to play 2x (twice) as fast
  7703. * - Set player to play 0.5x (half) as fast
  7704. *
  7705. * @type {boolean}
  7706. * @default
  7707. */
  7708. Tech.prototype.featuresPlaybackRate = false;
  7709. /**
  7710. * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
  7711. * not triggered by video-js-swf. This will be used to determine if
  7712. * {@link Tech#manualProgressOn} should be called.
  7713. *
  7714. * @type {boolean}
  7715. * @default
  7716. */
  7717. Tech.prototype.featuresProgressEvents = false;
  7718. /**
  7719. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  7720. *
  7721. * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
  7722. * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
  7723. * a new source.
  7724. *
  7725. * @type {boolean}
  7726. * @default
  7727. */
  7728. Tech.prototype.featuresSourceset = false;
  7729. /**
  7730. * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
  7731. * not triggered by video-js-swf. This will be used to determine if
  7732. * {@link Tech#manualTimeUpdates} should be called.
  7733. *
  7734. * @type {boolean}
  7735. * @default
  7736. */
  7737. Tech.prototype.featuresTimeupdateEvents = false;
  7738. /**
  7739. * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
  7740. * This will help us integrate with native `TextTrack`s if the browser supports them.
  7741. *
  7742. * @type {boolean}
  7743. * @default
  7744. */
  7745. Tech.prototype.featuresNativeTextTracks = false;
  7746. /**
  7747. * A functional mixin for techs that want to use the Source Handler pattern.
  7748. * Source handlers are scripts for handling specific formats.
  7749. * The source handler pattern is used for adaptive formats (HLS, DASH) that
  7750. * manually load video data and feed it into a Source Buffer (Media Source Extensions)
  7751. * Example: `Tech.withSourceHandlers.call(MyTech);`
  7752. *
  7753. * @param {Tech} _Tech
  7754. * The tech to add source handler functions to.
  7755. *
  7756. * @mixes Tech~SourceHandlerAdditions
  7757. */
  7758. Tech.withSourceHandlers = function (_Tech) {
  7759. /**
  7760. * Register a source handler
  7761. *
  7762. * @param {Function} handler
  7763. * The source handler class
  7764. *
  7765. * @param {number} [index]
  7766. * Register it at the following index
  7767. */
  7768. _Tech.registerSourceHandler = function (handler, index) {
  7769. var handlers = _Tech.sourceHandlers;
  7770. if (!handlers) {
  7771. handlers = _Tech.sourceHandlers = [];
  7772. }
  7773. if (index === undefined) {
  7774. // add to the end of the list
  7775. index = handlers.length;
  7776. }
  7777. handlers.splice(index, 0, handler);
  7778. };
  7779. /**
  7780. * Check if the tech can support the given type. Also checks the
  7781. * Techs sourceHandlers.
  7782. *
  7783. * @param {string} type
  7784. * The mimetype to check.
  7785. *
  7786. * @return {string}
  7787. * 'probably', 'maybe', or '' (empty string)
  7788. */
  7789. _Tech.canPlayType = function (type) {
  7790. var handlers = _Tech.sourceHandlers || [];
  7791. var can;
  7792. for (var i = 0; i < handlers.length; i++) {
  7793. can = handlers[i].canPlayType(type);
  7794. if (can) {
  7795. return can;
  7796. }
  7797. }
  7798. return '';
  7799. };
  7800. /**
  7801. * Returns the first source handler that supports the source.
  7802. *
  7803. * TODO: Answer question: should 'probably' be prioritized over 'maybe'
  7804. *
  7805. * @param {Tech~SourceObject} source
  7806. * The source object
  7807. *
  7808. * @param {Object} options
  7809. * The options passed to the tech
  7810. *
  7811. * @return {SourceHandler|null}
  7812. * The first source handler that supports the source or null if
  7813. * no SourceHandler supports the source
  7814. */
  7815. _Tech.selectSourceHandler = function (source, options) {
  7816. var handlers = _Tech.sourceHandlers || [];
  7817. var can;
  7818. for (var i = 0; i < handlers.length; i++) {
  7819. can = handlers[i].canHandleSource(source, options);
  7820. if (can) {
  7821. return handlers[i];
  7822. }
  7823. }
  7824. return null;
  7825. };
  7826. /**
  7827. * Check if the tech can support the given source.
  7828. *
  7829. * @param {Tech~SourceObject} srcObj
  7830. * The source object
  7831. *
  7832. * @param {Object} options
  7833. * The options passed to the tech
  7834. *
  7835. * @return {string}
  7836. * 'probably', 'maybe', or '' (empty string)
  7837. */
  7838. _Tech.canPlaySource = function (srcObj, options) {
  7839. var sh = _Tech.selectSourceHandler(srcObj, options);
  7840. if (sh) {
  7841. return sh.canHandleSource(srcObj, options);
  7842. }
  7843. return '';
  7844. };
  7845. /**
  7846. * When using a source handler, prefer its implementation of
  7847. * any function normally provided by the tech.
  7848. */
  7849. var deferrable = ['seekable', 'seeking', 'duration'];
  7850. /**
  7851. * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
  7852. * function if it exists, with a fallback to the Techs seekable function.
  7853. *
  7854. * @method _Tech.seekable
  7855. */
  7856. /**
  7857. * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
  7858. * function if it exists, otherwise it will fallback to the techs duration function.
  7859. *
  7860. * @method _Tech.duration
  7861. */
  7862. deferrable.forEach(function (fnName) {
  7863. var originalFn = this[fnName];
  7864. if (typeof originalFn !== 'function') {
  7865. return;
  7866. }
  7867. this[fnName] = function () {
  7868. if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
  7869. return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
  7870. }
  7871. return originalFn.apply(this, arguments);
  7872. };
  7873. }, _Tech.prototype);
  7874. /**
  7875. * Create a function for setting the source using a source object
  7876. * and source handlers.
  7877. * Should never be called unless a source handler was found.
  7878. *
  7879. * @param {Tech~SourceObject} source
  7880. * A source object with src and type keys
  7881. */
  7882. _Tech.prototype.setSource = function (source) {
  7883. var sh = _Tech.selectSourceHandler(source, this.options_);
  7884. if (!sh) {
  7885. // Fall back to a native source hander when unsupported sources are
  7886. // deliberately set
  7887. if (_Tech.nativeSourceHandler) {
  7888. sh = _Tech.nativeSourceHandler;
  7889. } else {
  7890. log.error('No source handler found for the current source.');
  7891. }
  7892. } // Dispose any existing source handler
  7893. this.disposeSourceHandler();
  7894. this.off('dispose', this.disposeSourceHandler);
  7895. if (sh !== _Tech.nativeSourceHandler) {
  7896. this.currentSource_ = source;
  7897. }
  7898. this.sourceHandler_ = sh.handleSource(source, this, this.options_);
  7899. this.one('dispose', this.disposeSourceHandler);
  7900. };
  7901. /**
  7902. * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
  7903. *
  7904. * @listens Tech#dispose
  7905. */
  7906. _Tech.prototype.disposeSourceHandler = function () {
  7907. // if we have a source and get another one
  7908. // then we are loading something new
  7909. // than clear all of our current tracks
  7910. if (this.currentSource_) {
  7911. this.clearTracks(['audio', 'video']);
  7912. this.currentSource_ = null;
  7913. } // always clean up auto-text tracks
  7914. this.cleanupAutoTextTracks();
  7915. if (this.sourceHandler_) {
  7916. if (this.sourceHandler_.dispose) {
  7917. this.sourceHandler_.dispose();
  7918. }
  7919. this.sourceHandler_ = null;
  7920. }
  7921. };
  7922. }; // The base Tech class needs to be registered as a Component. It is the only
  7923. // Tech that can be registered as a Component.
  7924. Component.registerComponent('Tech', Tech);
  7925. Tech.registerTech('Tech', Tech);
  7926. /**
  7927. * A list of techs that should be added to techOrder on Players
  7928. *
  7929. * @private
  7930. */
  7931. Tech.defaultTechOrder_ = [];
  7932. /**
  7933. * @file middleware.js
  7934. * @module middleware
  7935. */
  7936. var middlewares = {};
  7937. var middlewareInstances = {};
  7938. var TERMINATOR = {};
  7939. /**
  7940. * A middleware object is a plain JavaScript object that has methods that
  7941. * match the {@link Tech} methods found in the lists of allowed
  7942. * {@link module:middleware.allowedGetters|getters},
  7943. * {@link module:middleware.allowedSetters|setters}, and
  7944. * {@link module:middleware.allowedMediators|mediators}.
  7945. *
  7946. * @typedef {Object} MiddlewareObject
  7947. */
  7948. /**
  7949. * A middleware factory function that should return a
  7950. * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
  7951. *
  7952. * This factory will be called for each player when needed, with the player
  7953. * passed in as an argument.
  7954. *
  7955. * @callback MiddlewareFactory
  7956. * @param {Player} player
  7957. * A Video.js player.
  7958. */
  7959. /**
  7960. * Define a middleware that the player should use by way of a factory function
  7961. * that returns a middleware object.
  7962. *
  7963. * @param {string} type
  7964. * The MIME type to match or `"*"` for all MIME types.
  7965. *
  7966. * @param {MiddlewareFactory} middleware
  7967. * A middleware factory function that will be executed for
  7968. * matching types.
  7969. */
  7970. function use(type, middleware) {
  7971. middlewares[type] = middlewares[type] || [];
  7972. middlewares[type].push(middleware);
  7973. }
  7974. /**
  7975. * Asynchronously sets a source using middleware by recursing through any
  7976. * matching middlewares and calling `setSource` on each, passing along the
  7977. * previous returned value each time.
  7978. *
  7979. * @param {Player} player
  7980. * A {@link Player} instance.
  7981. *
  7982. * @param {Tech~SourceObject} src
  7983. * A source object.
  7984. *
  7985. * @param {Function}
  7986. * The next middleware to run.
  7987. */
  7988. function setSource(player, src, next) {
  7989. player.setTimeout(function () {
  7990. return setSourceHelper(src, middlewares[src.type], next, player);
  7991. }, 1);
  7992. }
  7993. /**
  7994. * When the tech is set, passes the tech to each middleware's `setTech` method.
  7995. *
  7996. * @param {Object[]} middleware
  7997. * An array of middleware instances.
  7998. *
  7999. * @param {Tech} tech
  8000. * A Video.js tech.
  8001. */
  8002. function setTech(middleware, tech) {
  8003. middleware.forEach(function (mw) {
  8004. return mw.setTech && mw.setTech(tech);
  8005. });
  8006. }
  8007. /**
  8008. * Calls a getter on the tech first, through each middleware
  8009. * from right to left to the player.
  8010. *
  8011. * @param {Object[]} middleware
  8012. * An array of middleware instances.
  8013. *
  8014. * @param {Tech} tech
  8015. * The current tech.
  8016. *
  8017. * @param {string} method
  8018. * A method name.
  8019. *
  8020. * @return {Mixed}
  8021. * The final value from the tech after middleware has intercepted it.
  8022. */
  8023. function get(middleware, tech, method) {
  8024. return middleware.reduceRight(middlewareIterator(method), tech[method]());
  8025. }
  8026. /**
  8027. * Takes the argument given to the player and calls the setter method on each
  8028. * middleware from left to right to the tech.
  8029. *
  8030. * @param {Object[]} middleware
  8031. * An array of middleware instances.
  8032. *
  8033. * @param {Tech} tech
  8034. * The current tech.
  8035. *
  8036. * @param {string} method
  8037. * A method name.
  8038. *
  8039. * @param {Mixed} arg
  8040. * The value to set on the tech.
  8041. *
  8042. * @return {Mixed}
  8043. * The return value of the `method` of the `tech`.
  8044. */
  8045. function set$1(middleware, tech, method, arg) {
  8046. return tech[method](middleware.reduce(middlewareIterator(method), arg));
  8047. }
  8048. /**
  8049. * Takes the argument given to the player and calls the `call` version of the
  8050. * method on each middleware from left to right.
  8051. *
  8052. * Then, call the passed in method on the tech and return the result unchanged
  8053. * back to the player, through middleware, this time from right to left.
  8054. *
  8055. * @param {Object[]} middleware
  8056. * An array of middleware instances.
  8057. *
  8058. * @param {Tech} tech
  8059. * The current tech.
  8060. *
  8061. * @param {string} method
  8062. * A method name.
  8063. *
  8064. * @param {Mixed} arg
  8065. * The value to set on the tech.
  8066. *
  8067. * @return {Mixed}
  8068. * The return value of the `method` of the `tech`, regardless of the
  8069. * return values of middlewares.
  8070. */
  8071. function mediate(middleware, tech, method, arg) {
  8072. if (arg === void 0) {
  8073. arg = null;
  8074. }
  8075. var callMethod = 'call' + toTitleCase(method);
  8076. var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
  8077. var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
  8078. // prevent confusion if a techs method actually returns null.
  8079. var returnValue = terminated ? null : tech[method](middlewareValue);
  8080. executeRight(middleware, method, returnValue, terminated);
  8081. return returnValue;
  8082. }
  8083. /**
  8084. * Enumeration of allowed getters where the keys are method names.
  8085. *
  8086. * @type {Object}
  8087. */
  8088. var allowedGetters = {
  8089. buffered: 1,
  8090. currentTime: 1,
  8091. duration: 1,
  8092. seekable: 1,
  8093. played: 1,
  8094. paused: 1,
  8095. volume: 1
  8096. };
  8097. /**
  8098. * Enumeration of allowed setters where the keys are method names.
  8099. *
  8100. * @type {Object}
  8101. */
  8102. var allowedSetters = {
  8103. setCurrentTime: 1,
  8104. setVolume: 1
  8105. };
  8106. /**
  8107. * Enumeration of allowed mediators where the keys are method names.
  8108. *
  8109. * @type {Object}
  8110. */
  8111. var allowedMediators = {
  8112. play: 1,
  8113. pause: 1
  8114. };
  8115. function middlewareIterator(method) {
  8116. return function (value, mw) {
  8117. // if the previous middleware terminated, pass along the termination
  8118. if (value === TERMINATOR) {
  8119. return TERMINATOR;
  8120. }
  8121. if (mw[method]) {
  8122. return mw[method](value);
  8123. }
  8124. return value;
  8125. };
  8126. }
  8127. function executeRight(mws, method, value, terminated) {
  8128. for (var i = mws.length - 1; i >= 0; i--) {
  8129. var mw = mws[i];
  8130. if (mw[method]) {
  8131. mw[method](terminated, value);
  8132. }
  8133. }
  8134. }
  8135. /**
  8136. * Clear the middleware cache for a player.
  8137. *
  8138. * @param {Player} player
  8139. * A {@link Player} instance.
  8140. */
  8141. function clearCacheForPlayer(player) {
  8142. middlewareInstances[player.id()] = null;
  8143. }
  8144. /**
  8145. * {
  8146. * [playerId]: [[mwFactory, mwInstance], ...]
  8147. * }
  8148. *
  8149. * @private
  8150. */
  8151. function getOrCreateFactory(player, mwFactory) {
  8152. var mws = middlewareInstances[player.id()];
  8153. var mw = null;
  8154. if (mws === undefined || mws === null) {
  8155. mw = mwFactory(player);
  8156. middlewareInstances[player.id()] = [[mwFactory, mw]];
  8157. return mw;
  8158. }
  8159. for (var i = 0; i < mws.length; i++) {
  8160. var _mws$i = mws[i],
  8161. mwf = _mws$i[0],
  8162. mwi = _mws$i[1];
  8163. if (mwf !== mwFactory) {
  8164. continue;
  8165. }
  8166. mw = mwi;
  8167. }
  8168. if (mw === null) {
  8169. mw = mwFactory(player);
  8170. mws.push([mwFactory, mw]);
  8171. }
  8172. return mw;
  8173. }
  8174. function setSourceHelper(src, middleware, next, player, acc, lastRun) {
  8175. if (src === void 0) {
  8176. src = {};
  8177. }
  8178. if (middleware === void 0) {
  8179. middleware = [];
  8180. }
  8181. if (acc === void 0) {
  8182. acc = [];
  8183. }
  8184. if (lastRun === void 0) {
  8185. lastRun = false;
  8186. }
  8187. var _middleware = middleware,
  8188. mwFactory = _middleware[0],
  8189. mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
  8190. if (typeof mwFactory === 'string') {
  8191. setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
  8192. // then call the mw's setSource method
  8193. } else if (mwFactory) {
  8194. var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
  8195. if (!mw.setSource) {
  8196. acc.push(mw);
  8197. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  8198. }
  8199. mw.setSource(assign({}, src), function (err, _src) {
  8200. // something happened, try the next middleware on the current level
  8201. // make sure to use the old src
  8202. if (err) {
  8203. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  8204. } // we've succeeded, now we need to go deeper
  8205. acc.push(mw); // if it's the same type, continue down the current chain
  8206. // otherwise, we want to go down the new chain
  8207. setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
  8208. });
  8209. } else if (mwrest.length) {
  8210. setSourceHelper(src, mwrest, next, player, acc, lastRun);
  8211. } else if (lastRun) {
  8212. next(src, acc);
  8213. } else {
  8214. setSourceHelper(src, middlewares['*'], next, player, acc, true);
  8215. }
  8216. }
  8217. /**
  8218. * Mimetypes
  8219. *
  8220. * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
  8221. * @typedef Mimetypes~Kind
  8222. * @enum
  8223. */
  8224. var MimetypesKind = {
  8225. opus: 'video/ogg',
  8226. ogv: 'video/ogg',
  8227. mp4: 'video/mp4',
  8228. mov: 'video/mp4',
  8229. m4v: 'video/mp4',
  8230. mkv: 'video/x-matroska',
  8231. mp3: 'audio/mpeg',
  8232. aac: 'audio/aac',
  8233. oga: 'audio/ogg',
  8234. m3u8: 'application/x-mpegURL',
  8235. jpg: 'image/jpeg',
  8236. jpeg: 'image/jpeg',
  8237. gif: 'image/gif',
  8238. png: 'image/png',
  8239. svg: 'image/svg+xml',
  8240. webp: 'image/webp'
  8241. };
  8242. /**
  8243. * Get the mimetype of a given src url if possible
  8244. *
  8245. * @param {string} src
  8246. * The url to the src
  8247. *
  8248. * @return {string}
  8249. * return the mimetype if it was known or empty string otherwise
  8250. */
  8251. var getMimetype = function getMimetype(src) {
  8252. if (src === void 0) {
  8253. src = '';
  8254. }
  8255. var ext = getFileExtension(src);
  8256. var mimetype = MimetypesKind[ext.toLowerCase()];
  8257. return mimetype || '';
  8258. };
  8259. /**
  8260. * Find the mime type of a given source string if possible. Uses the player
  8261. * source cache.
  8262. *
  8263. * @param {Player} player
  8264. * The player object
  8265. *
  8266. * @param {string} src
  8267. * The source string
  8268. *
  8269. * @return {string}
  8270. * The type that was found
  8271. */
  8272. var findMimetype = function findMimetype(player, src) {
  8273. if (!src) {
  8274. return '';
  8275. } // 1. check for the type in the `source` cache
  8276. if (player.cache_.source.src === src && player.cache_.source.type) {
  8277. return player.cache_.source.type;
  8278. } // 2. see if we have this source in our `currentSources` cache
  8279. var matchingSources = player.cache_.sources.filter(function (s) {
  8280. return s.src === src;
  8281. });
  8282. if (matchingSources.length) {
  8283. return matchingSources[0].type;
  8284. } // 3. look for the src url in source elements and use the type there
  8285. var sources = player.$$('source');
  8286. for (var i = 0; i < sources.length; i++) {
  8287. var s = sources[i];
  8288. if (s.type && s.src && s.src === src) {
  8289. return s.type;
  8290. }
  8291. } // 4. finally fallback to our list of mime types based on src url extension
  8292. return getMimetype(src);
  8293. };
  8294. /**
  8295. * @module filter-source
  8296. */
  8297. /**
  8298. * Filter out single bad source objects or multiple source objects in an
  8299. * array. Also flattens nested source object arrays into a 1 dimensional
  8300. * array of source objects.
  8301. *
  8302. * @param {Tech~SourceObject|Tech~SourceObject[]} src
  8303. * The src object to filter
  8304. *
  8305. * @return {Tech~SourceObject[]}
  8306. * An array of sourceobjects containing only valid sources
  8307. *
  8308. * @private
  8309. */
  8310. var filterSource = function filterSource(src) {
  8311. // traverse array
  8312. if (Array.isArray(src)) {
  8313. var newsrc = [];
  8314. src.forEach(function (srcobj) {
  8315. srcobj = filterSource(srcobj);
  8316. if (Array.isArray(srcobj)) {
  8317. newsrc = newsrc.concat(srcobj);
  8318. } else if (isObject(srcobj)) {
  8319. newsrc.push(srcobj);
  8320. }
  8321. });
  8322. src = newsrc;
  8323. } else if (typeof src === 'string' && src.trim()) {
  8324. // convert string into object
  8325. src = [fixSource({
  8326. src: src
  8327. })];
  8328. } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
  8329. // src is already valid
  8330. src = [fixSource(src)];
  8331. } else {
  8332. // invalid source, turn it into an empty array
  8333. src = [];
  8334. }
  8335. return src;
  8336. };
  8337. /**
  8338. * Checks src mimetype, adding it when possible
  8339. *
  8340. * @param {Tech~SourceObject} src
  8341. * The src object to check
  8342. * @return {Tech~SourceObject}
  8343. * src Object with known type
  8344. */
  8345. function fixSource(src) {
  8346. var mimetype = getMimetype(src.src);
  8347. if (!src.type && mimetype) {
  8348. src.type = mimetype;
  8349. }
  8350. return src;
  8351. }
  8352. /**
  8353. * The `MediaLoader` is the `Component` that decides which playback technology to load
  8354. * when a player is initialized.
  8355. *
  8356. * @extends Component
  8357. */
  8358. var MediaLoader =
  8359. /*#__PURE__*/
  8360. function (_Component) {
  8361. _inheritsLoose(MediaLoader, _Component);
  8362. /**
  8363. * Create an instance of this class.
  8364. *
  8365. * @param {Player} player
  8366. * The `Player` that this class should attach to.
  8367. *
  8368. * @param {Object} [options]
  8369. * The key/value store of player options.
  8370. *
  8371. * @param {Component~ReadyCallback} [ready]
  8372. * The function that is run when this component is ready.
  8373. */
  8374. function MediaLoader(player, options, ready) {
  8375. var _this;
  8376. // MediaLoader has no element
  8377. var options_ = mergeOptions({
  8378. createEl: false
  8379. }, options);
  8380. _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
  8381. // load the first supported playback technology.
  8382. if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
  8383. for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
  8384. var techName = toTitleCase(j[i]);
  8385. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  8386. // Remove once that deprecated behavior is removed.
  8387. if (!techName) {
  8388. tech = Component.getComponent(techName);
  8389. } // Check if the browser supports this technology
  8390. if (tech && tech.isSupported()) {
  8391. player.loadTech_(techName);
  8392. break;
  8393. }
  8394. }
  8395. } else {
  8396. // Loop through playback technologies (HTML5, Flash) and check for support.
  8397. // Then load the best source.
  8398. // A few assumptions here:
  8399. // All playback technologies respect preload false.
  8400. player.src(options.playerOptions.sources);
  8401. }
  8402. return _this;
  8403. }
  8404. return MediaLoader;
  8405. }(Component);
  8406. Component.registerComponent('MediaLoader', MediaLoader);
  8407. /**
  8408. * Clickable Component which is clickable or keyboard actionable,
  8409. * but is not a native HTML button.
  8410. *
  8411. * @extends Component
  8412. */
  8413. var ClickableComponent =
  8414. /*#__PURE__*/
  8415. function (_Component) {
  8416. _inheritsLoose(ClickableComponent, _Component);
  8417. /**
  8418. * Creates an instance of this class.
  8419. *
  8420. * @param {Player} player
  8421. * The `Player` that this class should be attached to.
  8422. *
  8423. * @param {Object} [options]
  8424. * The key/value store of player options.
  8425. */
  8426. function ClickableComponent(player, options) {
  8427. var _this;
  8428. _this = _Component.call(this, player, options) || this;
  8429. _this.emitTapEvents();
  8430. _this.enable();
  8431. return _this;
  8432. }
  8433. /**
  8434. * Create the `Component`s DOM element.
  8435. *
  8436. * @param {string} [tag=div]
  8437. * The element's node type.
  8438. *
  8439. * @param {Object} [props={}]
  8440. * An object of properties that should be set on the element.
  8441. *
  8442. * @param {Object} [attributes={}]
  8443. * An object of attributes that should be set on the element.
  8444. *
  8445. * @return {Element}
  8446. * The element that gets created.
  8447. */
  8448. var _proto = ClickableComponent.prototype;
  8449. _proto.createEl = function createEl$$1(tag, props, attributes) {
  8450. if (tag === void 0) {
  8451. tag = 'div';
  8452. }
  8453. if (props === void 0) {
  8454. props = {};
  8455. }
  8456. if (attributes === void 0) {
  8457. attributes = {};
  8458. }
  8459. props = assign({
  8460. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  8461. className: this.buildCSSClass(),
  8462. tabIndex: 0
  8463. }, props);
  8464. if (tag === 'button') {
  8465. log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
  8466. } // Add ARIA attributes for clickable element which is not a native HTML button
  8467. attributes = assign({
  8468. role: 'button'
  8469. }, attributes);
  8470. this.tabIndex_ = props.tabIndex;
  8471. var el = _Component.prototype.createEl.call(this, tag, props, attributes);
  8472. this.createControlTextEl(el);
  8473. return el;
  8474. };
  8475. _proto.dispose = function dispose() {
  8476. // remove controlTextEl_ on dispose
  8477. this.controlTextEl_ = null;
  8478. _Component.prototype.dispose.call(this);
  8479. }
  8480. /**
  8481. * Create a control text element on this `Component`
  8482. *
  8483. * @param {Element} [el]
  8484. * Parent element for the control text.
  8485. *
  8486. * @return {Element}
  8487. * The control text element that gets created.
  8488. */
  8489. ;
  8490. _proto.createControlTextEl = function createControlTextEl(el) {
  8491. this.controlTextEl_ = createEl('span', {
  8492. className: 'vjs-control-text'
  8493. }, {
  8494. // let the screen reader user know that the text of the element may change
  8495. 'aria-live': 'polite'
  8496. });
  8497. if (el) {
  8498. el.appendChild(this.controlTextEl_);
  8499. }
  8500. this.controlText(this.controlText_, el);
  8501. return this.controlTextEl_;
  8502. }
  8503. /**
  8504. * Get or set the localize text to use for the controls on the `Component`.
  8505. *
  8506. * @param {string} [text]
  8507. * Control text for element.
  8508. *
  8509. * @param {Element} [el=this.el()]
  8510. * Element to set the title on.
  8511. *
  8512. * @return {string}
  8513. * - The control text when getting
  8514. */
  8515. ;
  8516. _proto.controlText = function controlText(text, el) {
  8517. if (el === void 0) {
  8518. el = this.el();
  8519. }
  8520. if (text === undefined) {
  8521. return this.controlText_ || 'Need Text';
  8522. }
  8523. var localizedText = this.localize(text);
  8524. this.controlText_ = text;
  8525. textContent(this.controlTextEl_, localizedText);
  8526. if (!this.nonIconControl) {
  8527. // Set title attribute if only an icon is shown
  8528. el.setAttribute('title', localizedText);
  8529. }
  8530. }
  8531. /**
  8532. * Builds the default DOM `className`.
  8533. *
  8534. * @return {string}
  8535. * The DOM `className` for this object.
  8536. */
  8537. ;
  8538. _proto.buildCSSClass = function buildCSSClass() {
  8539. return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
  8540. }
  8541. /**
  8542. * Enable this `Component`s element.
  8543. */
  8544. ;
  8545. _proto.enable = function enable() {
  8546. if (!this.enabled_) {
  8547. this.enabled_ = true;
  8548. this.removeClass('vjs-disabled');
  8549. this.el_.setAttribute('aria-disabled', 'false');
  8550. if (typeof this.tabIndex_ !== 'undefined') {
  8551. this.el_.setAttribute('tabIndex', this.tabIndex_);
  8552. }
  8553. this.on(['tap', 'click'], this.handleClick);
  8554. this.on('focus', this.handleFocus);
  8555. this.on('blur', this.handleBlur);
  8556. }
  8557. }
  8558. /**
  8559. * Disable this `Component`s element.
  8560. */
  8561. ;
  8562. _proto.disable = function disable() {
  8563. this.enabled_ = false;
  8564. this.addClass('vjs-disabled');
  8565. this.el_.setAttribute('aria-disabled', 'true');
  8566. if (typeof this.tabIndex_ !== 'undefined') {
  8567. this.el_.removeAttribute('tabIndex');
  8568. }
  8569. this.off(['tap', 'click'], this.handleClick);
  8570. this.off('focus', this.handleFocus);
  8571. this.off('blur', this.handleBlur);
  8572. }
  8573. /**
  8574. * This gets called when a `ClickableComponent` gets:
  8575. * - Clicked (via the `click` event, listening starts in the constructor)
  8576. * - Tapped (via the `tap` event, listening starts in the constructor)
  8577. * - The following things happen in order:
  8578. * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
  8579. * `ClickableComponent`.
  8580. * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
  8581. * {@link ClickableComponent#handleKeyPress}.
  8582. * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
  8583. * the space or enter key.
  8584. * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
  8585. * event as a parameter.
  8586. *
  8587. * @param {EventTarget~Event} event
  8588. * The `keydown`, `tap`, or `click` event that caused this function to be
  8589. * called.
  8590. *
  8591. * @listens tap
  8592. * @listens click
  8593. * @abstract
  8594. */
  8595. ;
  8596. _proto.handleClick = function handleClick(event) {}
  8597. /**
  8598. * This gets called when a `ClickableComponent` gains focus via a `focus` event.
  8599. * Turns on listening for `keydown` events. When they happen it
  8600. * calls `this.handleKeyPress`.
  8601. *
  8602. * @param {EventTarget~Event} event
  8603. * The `focus` event that caused this function to be called.
  8604. *
  8605. * @listens focus
  8606. */
  8607. ;
  8608. _proto.handleFocus = function handleFocus(event) {
  8609. on(document, 'keydown', bind(this, this.handleKeyPress));
  8610. }
  8611. /**
  8612. * Called when this ClickableComponent has focus and a key gets pressed down. By
  8613. * default it will call `this.handleClick` when the key is space or enter.
  8614. *
  8615. * @param {EventTarget~Event} event
  8616. * The `keydown` event that caused this function to be called.
  8617. *
  8618. * @listens keydown
  8619. */
  8620. ;
  8621. _proto.handleKeyPress = function handleKeyPress(event) {
  8622. // Support Space or Enter key operation to fire a click event
  8623. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  8624. event.preventDefault();
  8625. this.trigger('click');
  8626. } else {
  8627. // Pass keypress handling up for unsupported keys
  8628. _Component.prototype.handleKeyPress.call(this, event);
  8629. }
  8630. }
  8631. /**
  8632. * Called when a `ClickableComponent` loses focus. Turns off the listener for
  8633. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  8634. *
  8635. * @param {EventTarget~Event} event
  8636. * The `blur` event that caused this function to be called.
  8637. *
  8638. * @listens blur
  8639. */
  8640. ;
  8641. _proto.handleBlur = function handleBlur(event) {
  8642. off(document, 'keydown', bind(this, this.handleKeyPress));
  8643. };
  8644. return ClickableComponent;
  8645. }(Component);
  8646. Component.registerComponent('ClickableComponent', ClickableComponent);
  8647. /**
  8648. * A `ClickableComponent` that handles showing the poster image for the player.
  8649. *
  8650. * @extends ClickableComponent
  8651. */
  8652. var PosterImage =
  8653. /*#__PURE__*/
  8654. function (_ClickableComponent) {
  8655. _inheritsLoose(PosterImage, _ClickableComponent);
  8656. /**
  8657. * Create an instance of this class.
  8658. *
  8659. * @param {Player} player
  8660. * The `Player` that this class should attach to.
  8661. *
  8662. * @param {Object} [options]
  8663. * The key/value store of player options.
  8664. */
  8665. function PosterImage(player, options) {
  8666. var _this;
  8667. _this = _ClickableComponent.call(this, player, options) || this;
  8668. _this.update();
  8669. player.on('posterchange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
  8670. return _this;
  8671. }
  8672. /**
  8673. * Clean up and dispose of the `PosterImage`.
  8674. */
  8675. var _proto = PosterImage.prototype;
  8676. _proto.dispose = function dispose() {
  8677. this.player().off('posterchange', this.update);
  8678. _ClickableComponent.prototype.dispose.call(this);
  8679. }
  8680. /**
  8681. * Create the `PosterImage`s DOM element.
  8682. *
  8683. * @return {Element}
  8684. * The element that gets created.
  8685. */
  8686. ;
  8687. _proto.createEl = function createEl$$1() {
  8688. var el = createEl('div', {
  8689. className: 'vjs-poster',
  8690. // Don't want poster to be tabbable.
  8691. tabIndex: -1
  8692. });
  8693. return el;
  8694. }
  8695. /**
  8696. * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
  8697. *
  8698. * @listens Player#posterchange
  8699. *
  8700. * @param {EventTarget~Event} [event]
  8701. * The `Player#posterchange` event that triggered this function.
  8702. */
  8703. ;
  8704. _proto.update = function update(event) {
  8705. var url = this.player().poster();
  8706. this.setSrc(url); // If there's no poster source we should display:none on this component
  8707. // so it's not still clickable or right-clickable
  8708. if (url) {
  8709. this.show();
  8710. } else {
  8711. this.hide();
  8712. }
  8713. }
  8714. /**
  8715. * Set the source of the `PosterImage` depending on the display method.
  8716. *
  8717. * @param {string} url
  8718. * The URL to the source for the `PosterImage`.
  8719. */
  8720. ;
  8721. _proto.setSrc = function setSrc(url) {
  8722. var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
  8723. // this will throw an extra error
  8724. if (url) {
  8725. backgroundImage = "url(\"" + url + "\")";
  8726. }
  8727. this.el_.style.backgroundImage = backgroundImage;
  8728. }
  8729. /**
  8730. * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
  8731. * {@link ClickableComponent#handleClick} for instances where this will be triggered.
  8732. *
  8733. * @listens tap
  8734. * @listens click
  8735. * @listens keydown
  8736. *
  8737. * @param {EventTarget~Event} event
  8738. + The `click`, `tap` or `keydown` event that caused this function to be called.
  8739. */
  8740. ;
  8741. _proto.handleClick = function handleClick(event) {
  8742. // We don't want a click to trigger playback when controls are disabled
  8743. if (!this.player_.controls()) {
  8744. return;
  8745. }
  8746. if (this.player_.paused()) {
  8747. silencePromise(this.player_.play());
  8748. } else {
  8749. this.player_.pause();
  8750. } // call handleFocus manually to get hotkeys working
  8751. this.player_.handleFocus({});
  8752. };
  8753. return PosterImage;
  8754. }(ClickableComponent);
  8755. Component.registerComponent('PosterImage', PosterImage);
  8756. var darkGray = '#222';
  8757. var lightGray = '#ccc';
  8758. var fontMap = {
  8759. monospace: 'monospace',
  8760. sansSerif: 'sans-serif',
  8761. serif: 'serif',
  8762. monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
  8763. monospaceSerif: '"Courier New", monospace',
  8764. proportionalSansSerif: 'sans-serif',
  8765. proportionalSerif: 'serif',
  8766. casual: '"Comic Sans MS", Impact, fantasy',
  8767. script: '"Monotype Corsiva", cursive',
  8768. smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
  8769. };
  8770. /**
  8771. * Construct an rgba color from a given hex color code.
  8772. *
  8773. * @param {number} color
  8774. * Hex number for color, like #f0e or #f604e2.
  8775. *
  8776. * @param {number} opacity
  8777. * Value for opacity, 0.0 - 1.0.
  8778. *
  8779. * @return {string}
  8780. * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
  8781. */
  8782. function constructColor(color, opacity) {
  8783. var hex;
  8784. if (color.length === 4) {
  8785. // color looks like "#f0e"
  8786. hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
  8787. } else if (color.length === 7) {
  8788. // color looks like "#f604e2"
  8789. hex = color.slice(1);
  8790. } else {
  8791. throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
  8792. }
  8793. return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
  8794. }
  8795. /**
  8796. * Try to update the style of a DOM element. Some style changes will throw an error,
  8797. * particularly in IE8. Those should be noops.
  8798. *
  8799. * @param {Element} el
  8800. * The DOM element to be styled.
  8801. *
  8802. * @param {string} style
  8803. * The CSS property on the element that should be styled.
  8804. *
  8805. * @param {string} rule
  8806. * The style rule that should be applied to the property.
  8807. *
  8808. * @private
  8809. */
  8810. function tryUpdateStyle(el, style, rule) {
  8811. try {
  8812. el.style[style] = rule;
  8813. } catch (e) {
  8814. // Satisfies linter.
  8815. return;
  8816. }
  8817. }
  8818. /**
  8819. * The component for displaying text track cues.
  8820. *
  8821. * @extends Component
  8822. */
  8823. var TextTrackDisplay =
  8824. /*#__PURE__*/
  8825. function (_Component) {
  8826. _inheritsLoose(TextTrackDisplay, _Component);
  8827. /**
  8828. * Creates an instance of this class.
  8829. *
  8830. * @param {Player} player
  8831. * The `Player` that this class should be attached to.
  8832. *
  8833. * @param {Object} [options]
  8834. * The key/value store of player options.
  8835. *
  8836. * @param {Component~ReadyCallback} [ready]
  8837. * The function to call when `TextTrackDisplay` is ready.
  8838. */
  8839. function TextTrackDisplay(player, options, ready) {
  8840. var _this;
  8841. _this = _Component.call(this, player, options, ready) || this;
  8842. var updateDisplayHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay);
  8843. player.on('loadstart', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.toggleDisplay));
  8844. player.on('texttrackchange', updateDisplayHandler);
  8845. player.on('loadedmetadata', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.preselectTrack)); // This used to be called during player init, but was causing an error
  8846. // if a track should show by default and the display hadn't loaded yet.
  8847. // Should probably be moved to an external track loader when we support
  8848. // tracks that don't need a display.
  8849. player.ready(bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
  8850. if (player.tech_ && player.tech_.featuresNativeTextTracks) {
  8851. this.hide();
  8852. return;
  8853. }
  8854. player.on('fullscreenchange', updateDisplayHandler);
  8855. player.on('playerresize', updateDisplayHandler);
  8856. window$1.addEventListener('orientationchange', updateDisplayHandler);
  8857. player.on('dispose', function () {
  8858. return window$1.removeEventListener('orientationchange', updateDisplayHandler);
  8859. });
  8860. var tracks = this.options_.playerOptions.tracks || [];
  8861. for (var i = 0; i < tracks.length; i++) {
  8862. this.player_.addRemoteTextTrack(tracks[i], true);
  8863. }
  8864. this.preselectTrack();
  8865. }));
  8866. return _this;
  8867. }
  8868. /**
  8869. * Preselect a track following this precedence:
  8870. * - matches the previously selected {@link TextTrack}'s language and kind
  8871. * - matches the previously selected {@link TextTrack}'s language only
  8872. * - is the first default captions track
  8873. * - is the first default descriptions track
  8874. *
  8875. * @listens Player#loadstart
  8876. */
  8877. var _proto = TextTrackDisplay.prototype;
  8878. _proto.preselectTrack = function preselectTrack() {
  8879. var modes = {
  8880. captions: 1,
  8881. subtitles: 1
  8882. };
  8883. var trackList = this.player_.textTracks();
  8884. var userPref = this.player_.cache_.selectedLanguage;
  8885. var firstDesc;
  8886. var firstCaptions;
  8887. var preferredTrack;
  8888. for (var i = 0; i < trackList.length; i++) {
  8889. var track = trackList[i];
  8890. if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
  8891. // Always choose the track that matches both language and kind
  8892. if (track.kind === userPref.kind) {
  8893. preferredTrack = track; // or choose the first track that matches language
  8894. } else if (!preferredTrack) {
  8895. preferredTrack = track;
  8896. } // clear everything if offTextTrackMenuItem was clicked
  8897. } else if (userPref && !userPref.enabled) {
  8898. preferredTrack = null;
  8899. firstDesc = null;
  8900. firstCaptions = null;
  8901. } else if (track.default) {
  8902. if (track.kind === 'descriptions' && !firstDesc) {
  8903. firstDesc = track;
  8904. } else if (track.kind in modes && !firstCaptions) {
  8905. firstCaptions = track;
  8906. }
  8907. }
  8908. } // The preferredTrack matches the user preference and takes
  8909. // precedence over all the other tracks.
  8910. // So, display the preferredTrack before the first default track
  8911. // and the subtitles/captions track before the descriptions track
  8912. if (preferredTrack) {
  8913. preferredTrack.mode = 'showing';
  8914. } else if (firstCaptions) {
  8915. firstCaptions.mode = 'showing';
  8916. } else if (firstDesc) {
  8917. firstDesc.mode = 'showing';
  8918. }
  8919. }
  8920. /**
  8921. * Turn display of {@link TextTrack}'s from the current state into the other state.
  8922. * There are only two states:
  8923. * - 'shown'
  8924. * - 'hidden'
  8925. *
  8926. * @listens Player#loadstart
  8927. */
  8928. ;
  8929. _proto.toggleDisplay = function toggleDisplay() {
  8930. if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
  8931. this.hide();
  8932. } else {
  8933. this.show();
  8934. }
  8935. }
  8936. /**
  8937. * Create the {@link Component}'s DOM element.
  8938. *
  8939. * @return {Element}
  8940. * The element that was created.
  8941. */
  8942. ;
  8943. _proto.createEl = function createEl() {
  8944. return _Component.prototype.createEl.call(this, 'div', {
  8945. className: 'vjs-text-track-display'
  8946. }, {
  8947. 'aria-live': 'off',
  8948. 'aria-atomic': 'true'
  8949. });
  8950. }
  8951. /**
  8952. * Clear all displayed {@link TextTrack}s.
  8953. */
  8954. ;
  8955. _proto.clearDisplay = function clearDisplay() {
  8956. if (typeof window$1.WebVTT === 'function') {
  8957. window$1.WebVTT.processCues(window$1, [], this.el_);
  8958. }
  8959. }
  8960. /**
  8961. * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
  8962. * a {@link Player#fullscreenchange} is fired.
  8963. *
  8964. * @listens Player#texttrackchange
  8965. * @listens Player#fullscreenchange
  8966. */
  8967. ;
  8968. _proto.updateDisplay = function updateDisplay() {
  8969. var tracks = this.player_.textTracks();
  8970. this.clearDisplay(); // Track display prioritization model: if multiple tracks are 'showing',
  8971. // display the first 'subtitles' or 'captions' track which is 'showing',
  8972. // otherwise display the first 'descriptions' track which is 'showing'
  8973. var descriptionsTrack = null;
  8974. var captionsSubtitlesTrack = null;
  8975. var i = tracks.length;
  8976. while (i--) {
  8977. var track = tracks[i];
  8978. if (track.mode === 'showing') {
  8979. if (track.kind === 'descriptions') {
  8980. descriptionsTrack = track;
  8981. } else {
  8982. captionsSubtitlesTrack = track;
  8983. }
  8984. }
  8985. }
  8986. if (captionsSubtitlesTrack) {
  8987. if (this.getAttribute('aria-live') !== 'off') {
  8988. this.setAttribute('aria-live', 'off');
  8989. }
  8990. this.updateForTrack(captionsSubtitlesTrack);
  8991. } else if (descriptionsTrack) {
  8992. if (this.getAttribute('aria-live') !== 'assertive') {
  8993. this.setAttribute('aria-live', 'assertive');
  8994. }
  8995. this.updateForTrack(descriptionsTrack);
  8996. }
  8997. }
  8998. /**
  8999. * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
  9000. *
  9001. * @param {TextTrack} track
  9002. * Text track object to be added to the list.
  9003. */
  9004. ;
  9005. _proto.updateForTrack = function updateForTrack(track) {
  9006. if (typeof window$1.WebVTT !== 'function' || !track.activeCues) {
  9007. return;
  9008. }
  9009. var cues = [];
  9010. for (var _i = 0; _i < track.activeCues.length; _i++) {
  9011. cues.push(track.activeCues[_i]);
  9012. }
  9013. window$1.WebVTT.processCues(window$1, cues, this.el_);
  9014. if (!this.player_.textTrackSettings) {
  9015. return;
  9016. }
  9017. var overrides = this.player_.textTrackSettings.getValues();
  9018. var i = cues.length;
  9019. while (i--) {
  9020. var cue = cues[i];
  9021. if (!cue) {
  9022. continue;
  9023. }
  9024. var cueDiv = cue.displayState;
  9025. if (overrides.color) {
  9026. cueDiv.firstChild.style.color = overrides.color;
  9027. }
  9028. if (overrides.textOpacity) {
  9029. tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
  9030. }
  9031. if (overrides.backgroundColor) {
  9032. cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
  9033. }
  9034. if (overrides.backgroundOpacity) {
  9035. tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
  9036. }
  9037. if (overrides.windowColor) {
  9038. if (overrides.windowOpacity) {
  9039. tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
  9040. } else {
  9041. cueDiv.style.backgroundColor = overrides.windowColor;
  9042. }
  9043. }
  9044. if (overrides.edgeStyle) {
  9045. if (overrides.edgeStyle === 'dropshadow') {
  9046. cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
  9047. } else if (overrides.edgeStyle === 'raised') {
  9048. cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
  9049. } else if (overrides.edgeStyle === 'depressed') {
  9050. cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
  9051. } else if (overrides.edgeStyle === 'uniform') {
  9052. cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
  9053. }
  9054. }
  9055. if (overrides.fontPercent && overrides.fontPercent !== 1) {
  9056. var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
  9057. cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
  9058. cueDiv.style.height = 'auto';
  9059. cueDiv.style.top = 'auto';
  9060. cueDiv.style.bottom = '2px';
  9061. }
  9062. if (overrides.fontFamily && overrides.fontFamily !== 'default') {
  9063. if (overrides.fontFamily === 'small-caps') {
  9064. cueDiv.firstChild.style.fontVariant = 'small-caps';
  9065. } else {
  9066. cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
  9067. }
  9068. }
  9069. }
  9070. };
  9071. return TextTrackDisplay;
  9072. }(Component);
  9073. Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
  9074. /**
  9075. * A loading spinner for use during waiting/loading events.
  9076. *
  9077. * @extends Component
  9078. */
  9079. var LoadingSpinner =
  9080. /*#__PURE__*/
  9081. function (_Component) {
  9082. _inheritsLoose(LoadingSpinner, _Component);
  9083. function LoadingSpinner() {
  9084. return _Component.apply(this, arguments) || this;
  9085. }
  9086. var _proto = LoadingSpinner.prototype;
  9087. /**
  9088. * Create the `LoadingSpinner`s DOM element.
  9089. *
  9090. * @return {Element}
  9091. * The dom element that gets created.
  9092. */
  9093. _proto.createEl = function createEl$$1() {
  9094. var isAudio = this.player_.isAudio();
  9095. var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
  9096. var controlText = createEl('span', {
  9097. className: 'vjs-control-text',
  9098. innerHTML: this.localize('{1} is loading.', [playerType])
  9099. });
  9100. var el = _Component.prototype.createEl.call(this, 'div', {
  9101. className: 'vjs-loading-spinner',
  9102. dir: 'ltr'
  9103. });
  9104. el.appendChild(controlText);
  9105. return el;
  9106. };
  9107. return LoadingSpinner;
  9108. }(Component);
  9109. Component.registerComponent('LoadingSpinner', LoadingSpinner);
  9110. /**
  9111. * Base class for all buttons.
  9112. *
  9113. * @extends ClickableComponent
  9114. */
  9115. var Button =
  9116. /*#__PURE__*/
  9117. function (_ClickableComponent) {
  9118. _inheritsLoose(Button, _ClickableComponent);
  9119. function Button() {
  9120. return _ClickableComponent.apply(this, arguments) || this;
  9121. }
  9122. var _proto = Button.prototype;
  9123. /**
  9124. * Create the `Button`s DOM element.
  9125. *
  9126. * @param {string} [tag="button"]
  9127. * The element's node type. This argument is IGNORED: no matter what
  9128. * is passed, it will always create a `button` element.
  9129. *
  9130. * @param {Object} [props={}]
  9131. * An object of properties that should be set on the element.
  9132. *
  9133. * @param {Object} [attributes={}]
  9134. * An object of attributes that should be set on the element.
  9135. *
  9136. * @return {Element}
  9137. * The element that gets created.
  9138. */
  9139. _proto.createEl = function createEl(tag, props, attributes) {
  9140. if (props === void 0) {
  9141. props = {};
  9142. }
  9143. if (attributes === void 0) {
  9144. attributes = {};
  9145. }
  9146. tag = 'button';
  9147. props = assign({
  9148. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  9149. className: this.buildCSSClass()
  9150. }, props); // Add attributes for button element
  9151. attributes = assign({
  9152. // Necessary since the default button type is "submit"
  9153. type: 'button'
  9154. }, attributes);
  9155. var el = Component.prototype.createEl.call(this, tag, props, attributes);
  9156. this.createControlTextEl(el);
  9157. return el;
  9158. }
  9159. /**
  9160. * Add a child `Component` inside of this `Button`.
  9161. *
  9162. * @param {string|Component} child
  9163. * The name or instance of a child to add.
  9164. *
  9165. * @param {Object} [options={}]
  9166. * The key/value store of options that will get passed to children of
  9167. * the child.
  9168. *
  9169. * @return {Component}
  9170. * The `Component` that gets added as a child. When using a string the
  9171. * `Component` will get created by this process.
  9172. *
  9173. * @deprecated since version 5
  9174. */
  9175. ;
  9176. _proto.addChild = function addChild(child, options) {
  9177. if (options === void 0) {
  9178. options = {};
  9179. }
  9180. var className = this.constructor.name;
  9181. log.warn("Adding an actionable (user controllable) child to a Button (" + className + ") is not supported; use a ClickableComponent instead."); // Avoid the error message generated by ClickableComponent's addChild method
  9182. return Component.prototype.addChild.call(this, child, options);
  9183. }
  9184. /**
  9185. * Enable the `Button` element so that it can be activated or clicked. Use this with
  9186. * {@link Button#disable}.
  9187. */
  9188. ;
  9189. _proto.enable = function enable() {
  9190. _ClickableComponent.prototype.enable.call(this);
  9191. this.el_.removeAttribute('disabled');
  9192. }
  9193. /**
  9194. * Disable the `Button` element so that it cannot be activated or clicked. Use this with
  9195. * {@link Button#enable}.
  9196. */
  9197. ;
  9198. _proto.disable = function disable() {
  9199. _ClickableComponent.prototype.disable.call(this);
  9200. this.el_.setAttribute('disabled', 'disabled');
  9201. }
  9202. /**
  9203. * This gets called when a `Button` has focus and `keydown` is triggered via a key
  9204. * press.
  9205. *
  9206. * @param {EventTarget~Event} event
  9207. * The event that caused this function to get called.
  9208. *
  9209. * @listens keydown
  9210. */
  9211. ;
  9212. _proto.handleKeyPress = function handleKeyPress(event) {
  9213. // Ignore Space or Enter key operation, which is handled by the browser for a button.
  9214. if (!(keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter'))) {
  9215. // Pass keypress handling up for unsupported keys
  9216. _ClickableComponent.prototype.handleKeyPress.call(this, event);
  9217. }
  9218. };
  9219. return Button;
  9220. }(ClickableComponent);
  9221. Component.registerComponent('Button', Button);
  9222. /**
  9223. * The initial play button that shows before the video has played. The hiding of the
  9224. * `BigPlayButton` get done via CSS and `Player` states.
  9225. *
  9226. * @extends Button
  9227. */
  9228. var BigPlayButton =
  9229. /*#__PURE__*/
  9230. function (_Button) {
  9231. _inheritsLoose(BigPlayButton, _Button);
  9232. function BigPlayButton(player, options) {
  9233. var _this;
  9234. _this = _Button.call(this, player, options) || this;
  9235. _this.mouseused_ = false;
  9236. _this.on('mousedown', _this.handleMouseDown);
  9237. return _this;
  9238. }
  9239. /**
  9240. * Builds the default DOM `className`.
  9241. *
  9242. * @return {string}
  9243. * The DOM `className` for this object. Always returns 'vjs-big-play-button'.
  9244. */
  9245. var _proto = BigPlayButton.prototype;
  9246. _proto.buildCSSClass = function buildCSSClass() {
  9247. return 'vjs-big-play-button';
  9248. }
  9249. /**
  9250. * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
  9251. * for more detailed information on what a click can be.
  9252. *
  9253. * @param {EventTarget~Event} event
  9254. * The `keydown`, `tap`, or `click` event that caused this function to be
  9255. * called.
  9256. *
  9257. * @listens tap
  9258. * @listens click
  9259. */
  9260. ;
  9261. _proto.handleClick = function handleClick(event) {
  9262. var playPromise = this.player_.play(); // exit early if clicked via the mouse
  9263. if (this.mouseused_ && event.clientX && event.clientY) {
  9264. silencePromise(playPromise); // call handleFocus manually to get hotkeys working
  9265. this.player_.handleFocus({});
  9266. return;
  9267. }
  9268. var cb = this.player_.getChild('controlBar');
  9269. var playToggle = cb && cb.getChild('playToggle');
  9270. if (!playToggle) {
  9271. this.player_.focus();
  9272. return;
  9273. }
  9274. var playFocus = function playFocus() {
  9275. return playToggle.focus();
  9276. };
  9277. if (isPromise(playPromise)) {
  9278. playPromise.then(playFocus, function () {});
  9279. } else {
  9280. this.setTimeout(playFocus, 1);
  9281. }
  9282. };
  9283. _proto.handleKeyPress = function handleKeyPress(event) {
  9284. this.mouseused_ = false;
  9285. _Button.prototype.handleKeyPress.call(this, event);
  9286. };
  9287. _proto.handleMouseDown = function handleMouseDown(event) {
  9288. this.mouseused_ = true;
  9289. };
  9290. return BigPlayButton;
  9291. }(Button);
  9292. /**
  9293. * The text that should display over the `BigPlayButton`s controls. Added to for localization.
  9294. *
  9295. * @type {string}
  9296. * @private
  9297. */
  9298. BigPlayButton.prototype.controlText_ = 'Play Video';
  9299. Component.registerComponent('BigPlayButton', BigPlayButton);
  9300. /**
  9301. * The `CloseButton` is a `{@link Button}` that fires a `close` event when
  9302. * it gets clicked.
  9303. *
  9304. * @extends Button
  9305. */
  9306. var CloseButton =
  9307. /*#__PURE__*/
  9308. function (_Button) {
  9309. _inheritsLoose(CloseButton, _Button);
  9310. /**
  9311. * Creates an instance of the this class.
  9312. *
  9313. * @param {Player} player
  9314. * The `Player` that this class should be attached to.
  9315. *
  9316. * @param {Object} [options]
  9317. * The key/value store of player options.
  9318. */
  9319. function CloseButton(player, options) {
  9320. var _this;
  9321. _this = _Button.call(this, player, options) || this;
  9322. _this.controlText(options && options.controlText || _this.localize('Close'));
  9323. return _this;
  9324. }
  9325. /**
  9326. * Builds the default DOM `className`.
  9327. *
  9328. * @return {string}
  9329. * The DOM `className` for this object.
  9330. */
  9331. var _proto = CloseButton.prototype;
  9332. _proto.buildCSSClass = function buildCSSClass() {
  9333. return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
  9334. }
  9335. /**
  9336. * This gets called when a `CloseButton` has focus and `keydown` is triggered via a key
  9337. * press.
  9338. *
  9339. * @param {EventTarget~Event} event
  9340. * The event that caused this function to get called.
  9341. *
  9342. * @listens keydown
  9343. */
  9344. ;
  9345. _proto.handleKeyPress = function handleKeyPress(event) {} // Override the default `Button` behavior, and don't pass the keypress event
  9346. // up to the player because this button is part of a `ModalDialog`, which
  9347. // doesn't pass keypresses to the player either.
  9348. /**
  9349. * This gets called when a `CloseButton` gets clicked. See
  9350. * {@link ClickableComponent#handleClick} for more information on when this will be
  9351. * triggered
  9352. *
  9353. * @param {EventTarget~Event} event
  9354. * The `keydown`, `tap`, or `click` event that caused this function to be
  9355. * called.
  9356. *
  9357. * @listens tap
  9358. * @listens click
  9359. * @fires CloseButton#close
  9360. */
  9361. ;
  9362. _proto.handleClick = function handleClick(event) {
  9363. /**
  9364. * Triggered when the a `CloseButton` is clicked.
  9365. *
  9366. * @event CloseButton#close
  9367. * @type {EventTarget~Event}
  9368. *
  9369. * @property {boolean} [bubbles=false]
  9370. * set to false so that the close event does not
  9371. * bubble up to parents if there is no listener
  9372. */
  9373. this.trigger({
  9374. type: 'close',
  9375. bubbles: false
  9376. });
  9377. };
  9378. return CloseButton;
  9379. }(Button);
  9380. Component.registerComponent('CloseButton', CloseButton);
  9381. /**
  9382. * Button to toggle between play and pause.
  9383. *
  9384. * @extends Button
  9385. */
  9386. var PlayToggle =
  9387. /*#__PURE__*/
  9388. function (_Button) {
  9389. _inheritsLoose(PlayToggle, _Button);
  9390. /**
  9391. * Creates an instance of this class.
  9392. *
  9393. * @param {Player} player
  9394. * The `Player` that this class should be attached to.
  9395. *
  9396. * @param {Object} [options={}]
  9397. * The key/value store of player options.
  9398. */
  9399. function PlayToggle(player, options) {
  9400. var _this;
  9401. if (options === void 0) {
  9402. options = {};
  9403. }
  9404. _this = _Button.call(this, player, options) || this; // show or hide replay icon
  9405. options.replay = options.replay === undefined || options.replay;
  9406. _this.on(player, 'play', _this.handlePlay);
  9407. _this.on(player, 'pause', _this.handlePause);
  9408. if (options.replay) {
  9409. _this.on(player, 'ended', _this.handleEnded);
  9410. }
  9411. return _this;
  9412. }
  9413. /**
  9414. * Builds the default DOM `className`.
  9415. *
  9416. * @return {string}
  9417. * The DOM `className` for this object.
  9418. */
  9419. var _proto = PlayToggle.prototype;
  9420. _proto.buildCSSClass = function buildCSSClass() {
  9421. return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
  9422. }
  9423. /**
  9424. * This gets called when an `PlayToggle` is "clicked". See
  9425. * {@link ClickableComponent} for more detailed information on what a click can be.
  9426. *
  9427. * @param {EventTarget~Event} [event]
  9428. * The `keydown`, `tap`, or `click` event that caused this function to be
  9429. * called.
  9430. *
  9431. * @listens tap
  9432. * @listens click
  9433. */
  9434. ;
  9435. _proto.handleClick = function handleClick(event) {
  9436. if (this.player_.paused()) {
  9437. this.player_.play();
  9438. } else {
  9439. this.player_.pause();
  9440. }
  9441. }
  9442. /**
  9443. * This gets called once after the video has ended and the user seeks so that
  9444. * we can change the replay button back to a play button.
  9445. *
  9446. * @param {EventTarget~Event} [event]
  9447. * The event that caused this function to run.
  9448. *
  9449. * @listens Player#seeked
  9450. */
  9451. ;
  9452. _proto.handleSeeked = function handleSeeked(event) {
  9453. this.removeClass('vjs-ended');
  9454. if (this.player_.paused()) {
  9455. this.handlePause(event);
  9456. } else {
  9457. this.handlePlay(event);
  9458. }
  9459. }
  9460. /**
  9461. * Add the vjs-playing class to the element so it can change appearance.
  9462. *
  9463. * @param {EventTarget~Event} [event]
  9464. * The event that caused this function to run.
  9465. *
  9466. * @listens Player#play
  9467. */
  9468. ;
  9469. _proto.handlePlay = function handlePlay(event) {
  9470. this.removeClass('vjs-ended');
  9471. this.removeClass('vjs-paused');
  9472. this.addClass('vjs-playing'); // change the button text to "Pause"
  9473. this.controlText('Pause');
  9474. }
  9475. /**
  9476. * Add the vjs-paused class to the element so it can change appearance.
  9477. *
  9478. * @param {EventTarget~Event} [event]
  9479. * The event that caused this function to run.
  9480. *
  9481. * @listens Player#pause
  9482. */
  9483. ;
  9484. _proto.handlePause = function handlePause(event) {
  9485. this.removeClass('vjs-playing');
  9486. this.addClass('vjs-paused'); // change the button text to "Play"
  9487. this.controlText('Play');
  9488. }
  9489. /**
  9490. * Add the vjs-ended class to the element so it can change appearance
  9491. *
  9492. * @param {EventTarget~Event} [event]
  9493. * The event that caused this function to run.
  9494. *
  9495. * @listens Player#ended
  9496. */
  9497. ;
  9498. _proto.handleEnded = function handleEnded(event) {
  9499. this.removeClass('vjs-playing');
  9500. this.addClass('vjs-ended'); // change the button text to "Replay"
  9501. this.controlText('Replay'); // on the next seek remove the replay button
  9502. this.one(this.player_, 'seeked', this.handleSeeked);
  9503. };
  9504. return PlayToggle;
  9505. }(Button);
  9506. /**
  9507. * The text that should display over the `PlayToggle`s controls. Added for localization.
  9508. *
  9509. * @type {string}
  9510. * @private
  9511. */
  9512. PlayToggle.prototype.controlText_ = 'Play';
  9513. Component.registerComponent('PlayToggle', PlayToggle);
  9514. /**
  9515. * @file format-time.js
  9516. * @module format-time
  9517. */
  9518. /**
  9519. * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
  9520. * seconds) will force a number of leading zeros to cover the length of the
  9521. * guide.
  9522. *
  9523. * @private
  9524. * @param {number} seconds
  9525. * Number of seconds to be turned into a string
  9526. *
  9527. * @param {number} guide
  9528. * Number (in seconds) to model the string after
  9529. *
  9530. * @return {string}
  9531. * Time formatted as H:MM:SS or M:SS
  9532. */
  9533. var defaultImplementation = function defaultImplementation(seconds, guide) {
  9534. seconds = seconds < 0 ? 0 : seconds;
  9535. var s = Math.floor(seconds % 60);
  9536. var m = Math.floor(seconds / 60 % 60);
  9537. var h = Math.floor(seconds / 3600);
  9538. var gm = Math.floor(guide / 60 % 60);
  9539. var gh = Math.floor(guide / 3600); // handle invalid times
  9540. if (isNaN(seconds) || seconds === Infinity) {
  9541. // '-' is false for all relational operators (e.g. <, >=) so this setting
  9542. // will add the minimum number of fields specified by the guide
  9543. h = m = s = '-';
  9544. } // Check if we need to show hours
  9545. h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
  9546. // Always show at least one digit of minutes.
  9547. m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
  9548. s = s < 10 ? '0' + s : s;
  9549. return h + m + s;
  9550. }; // Internal pointer to the current implementation.
  9551. var implementation = defaultImplementation;
  9552. /**
  9553. * Replaces the default formatTime implementation with a custom implementation.
  9554. *
  9555. * @param {Function} customImplementation
  9556. * A function which will be used in place of the default formatTime
  9557. * implementation. Will receive the current time in seconds and the
  9558. * guide (in seconds) as arguments.
  9559. */
  9560. function setFormatTime(customImplementation) {
  9561. implementation = customImplementation;
  9562. }
  9563. /**
  9564. * Resets formatTime to the default implementation.
  9565. */
  9566. function resetFormatTime() {
  9567. implementation = defaultImplementation;
  9568. }
  9569. /**
  9570. * Delegates to either the default time formatting function or a custom
  9571. * function supplied via `setFormatTime`.
  9572. *
  9573. * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
  9574. * guide (in seconds) will force a number of leading zeros to cover the
  9575. * length of the guide.
  9576. *
  9577. * @static
  9578. * @example formatTime(125, 600) === "02:05"
  9579. * @param {number} seconds
  9580. * Number of seconds to be turned into a string
  9581. *
  9582. * @param {number} guide
  9583. * Number (in seconds) to model the string after
  9584. *
  9585. * @return {string}
  9586. * Time formatted as H:MM:SS or M:SS
  9587. */
  9588. function formatTime(seconds, guide) {
  9589. if (guide === void 0) {
  9590. guide = seconds;
  9591. }
  9592. return implementation(seconds, guide);
  9593. }
  9594. /**
  9595. * Displays time information about the video
  9596. *
  9597. * @extends Component
  9598. */
  9599. var TimeDisplay =
  9600. /*#__PURE__*/
  9601. function (_Component) {
  9602. _inheritsLoose(TimeDisplay, _Component);
  9603. /**
  9604. * Creates an instance of this class.
  9605. *
  9606. * @param {Player} player
  9607. * The `Player` that this class should be attached to.
  9608. *
  9609. * @param {Object} [options]
  9610. * The key/value store of player options.
  9611. */
  9612. function TimeDisplay(player, options) {
  9613. var _this;
  9614. _this = _Component.call(this, player, options) || this;
  9615. _this.throttledUpdateContent = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateContent), 25);
  9616. _this.on(player, 'timeupdate', _this.throttledUpdateContent);
  9617. return _this;
  9618. }
  9619. /**
  9620. * Create the `Component`'s DOM element
  9621. *
  9622. * @return {Element}
  9623. * The element that was created.
  9624. */
  9625. var _proto = TimeDisplay.prototype;
  9626. _proto.createEl = function createEl$$1() {
  9627. var className = this.buildCSSClass();
  9628. var el = _Component.prototype.createEl.call(this, 'div', {
  9629. className: className + " vjs-time-control vjs-control",
  9630. innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>"
  9631. });
  9632. this.contentEl_ = createEl('span', {
  9633. className: className + "-display"
  9634. }, {
  9635. // tell screen readers not to automatically read the time as it changes
  9636. 'aria-live': 'off',
  9637. // span elements have no implicit role, but some screen readers (notably VoiceOver)
  9638. // treat them as a break between items in the DOM when using arrow keys
  9639. // (or left-to-right swipes on iOS) to read contents of a page. Using
  9640. // role='presentation' causes VoiceOver to NOT treat this span as a break.
  9641. 'role': 'presentation'
  9642. });
  9643. this.updateTextNode_();
  9644. el.appendChild(this.contentEl_);
  9645. return el;
  9646. };
  9647. _proto.dispose = function dispose() {
  9648. this.contentEl_ = null;
  9649. this.textNode_ = null;
  9650. _Component.prototype.dispose.call(this);
  9651. }
  9652. /**
  9653. * Updates the "remaining time" text node with new content using the
  9654. * contents of the `formattedTime_` property.
  9655. *
  9656. * @private
  9657. */
  9658. ;
  9659. _proto.updateTextNode_ = function updateTextNode_() {
  9660. if (!this.contentEl_) {
  9661. return;
  9662. }
  9663. while (this.contentEl_.firstChild) {
  9664. this.contentEl_.removeChild(this.contentEl_.firstChild);
  9665. }
  9666. this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0));
  9667. this.contentEl_.appendChild(this.textNode_);
  9668. }
  9669. /**
  9670. * Generates a formatted time for this component to use in display.
  9671. *
  9672. * @param {number} time
  9673. * A numeric time, in seconds.
  9674. *
  9675. * @return {string}
  9676. * A formatted time
  9677. *
  9678. * @private
  9679. */
  9680. ;
  9681. _proto.formatTime_ = function formatTime_(time) {
  9682. return formatTime(time);
  9683. }
  9684. /**
  9685. * Updates the time display text node if it has what was passed in changed
  9686. * the formatted time.
  9687. *
  9688. * @param {number} time
  9689. * The time to update to
  9690. *
  9691. * @private
  9692. */
  9693. ;
  9694. _proto.updateFormattedTime_ = function updateFormattedTime_(time) {
  9695. var formattedTime = this.formatTime_(time);
  9696. if (formattedTime === this.formattedTime_) {
  9697. return;
  9698. }
  9699. this.formattedTime_ = formattedTime;
  9700. this.requestAnimationFrame(this.updateTextNode_);
  9701. }
  9702. /**
  9703. * To be filled out in the child class, should update the displayed time
  9704. * in accordance with the fact that the current time has changed.
  9705. *
  9706. * @param {EventTarget~Event} [event]
  9707. * The `timeupdate` event that caused this to run.
  9708. *
  9709. * @listens Player#timeupdate
  9710. */
  9711. ;
  9712. _proto.updateContent = function updateContent(event) {};
  9713. return TimeDisplay;
  9714. }(Component);
  9715. /**
  9716. * The text that is added to the `TimeDisplay` for screen reader users.
  9717. *
  9718. * @type {string}
  9719. * @private
  9720. */
  9721. TimeDisplay.prototype.labelText_ = 'Time';
  9722. /**
  9723. * The text that should display over the `TimeDisplay`s controls. Added to for localization.
  9724. *
  9725. * @type {string}
  9726. * @private
  9727. *
  9728. * @deprecated in v7; controlText_ is not used in non-active display Components
  9729. */
  9730. TimeDisplay.prototype.controlText_ = 'Time';
  9731. Component.registerComponent('TimeDisplay', TimeDisplay);
  9732. /**
  9733. * Displays the current time
  9734. *
  9735. * @extends Component
  9736. */
  9737. var CurrentTimeDisplay =
  9738. /*#__PURE__*/
  9739. function (_TimeDisplay) {
  9740. _inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
  9741. /**
  9742. * Creates an instance of this class.
  9743. *
  9744. * @param {Player} player
  9745. * The `Player` that this class should be attached to.
  9746. *
  9747. * @param {Object} [options]
  9748. * The key/value store of player options.
  9749. */
  9750. function CurrentTimeDisplay(player, options) {
  9751. var _this;
  9752. _this = _TimeDisplay.call(this, player, options) || this;
  9753. _this.on(player, 'ended', _this.handleEnded);
  9754. return _this;
  9755. }
  9756. /**
  9757. * Builds the default DOM `className`.
  9758. *
  9759. * @return {string}
  9760. * The DOM `className` for this object.
  9761. */
  9762. var _proto = CurrentTimeDisplay.prototype;
  9763. _proto.buildCSSClass = function buildCSSClass() {
  9764. return 'vjs-current-time';
  9765. }
  9766. /**
  9767. * Update current time display
  9768. *
  9769. * @param {EventTarget~Event} [event]
  9770. * The `timeupdate` event that caused this function to run.
  9771. *
  9772. * @listens Player#timeupdate
  9773. */
  9774. ;
  9775. _proto.updateContent = function updateContent(event) {
  9776. // Allows for smooth scrubbing, when player can't keep up.
  9777. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  9778. this.updateFormattedTime_(time);
  9779. }
  9780. /**
  9781. * When the player fires ended there should be no time left. Sadly
  9782. * this is not always the case, lets make it seem like that is the case
  9783. * for users.
  9784. *
  9785. * @param {EventTarget~Event} [event]
  9786. * The `ended` event that caused this to run.
  9787. *
  9788. * @listens Player#ended
  9789. */
  9790. ;
  9791. _proto.handleEnded = function handleEnded(event) {
  9792. if (!this.player_.duration()) {
  9793. return;
  9794. }
  9795. this.updateFormattedTime_(this.player_.duration());
  9796. };
  9797. return CurrentTimeDisplay;
  9798. }(TimeDisplay);
  9799. /**
  9800. * The text that is added to the `CurrentTimeDisplay` for screen reader users.
  9801. *
  9802. * @type {string}
  9803. * @private
  9804. */
  9805. CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
  9806. /**
  9807. * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
  9808. *
  9809. * @type {string}
  9810. * @private
  9811. *
  9812. * @deprecated in v7; controlText_ is not used in non-active display Components
  9813. */
  9814. CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
  9815. Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
  9816. /**
  9817. * Displays the duration
  9818. *
  9819. * @extends Component
  9820. */
  9821. var DurationDisplay =
  9822. /*#__PURE__*/
  9823. function (_TimeDisplay) {
  9824. _inheritsLoose(DurationDisplay, _TimeDisplay);
  9825. /**
  9826. * Creates an instance of this class.
  9827. *
  9828. * @param {Player} player
  9829. * The `Player` that this class should be attached to.
  9830. *
  9831. * @param {Object} [options]
  9832. * The key/value store of player options.
  9833. */
  9834. function DurationDisplay(player, options) {
  9835. var _this;
  9836. _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes,
  9837. // as they should always display the changed duration as
  9838. // it has changed
  9839. _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
  9840. // but the durationchange on the user agent will not fire.
  9841. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  9842. _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
  9843. // listeners could have broken dependent applications/libraries. These
  9844. // can likely be removed for 7.0.
  9845. _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
  9846. return _this;
  9847. }
  9848. /**
  9849. * Builds the default DOM `className`.
  9850. *
  9851. * @return {string}
  9852. * The DOM `className` for this object.
  9853. */
  9854. var _proto = DurationDisplay.prototype;
  9855. _proto.buildCSSClass = function buildCSSClass() {
  9856. return 'vjs-duration';
  9857. }
  9858. /**
  9859. * Update duration time display.
  9860. *
  9861. * @param {EventTarget~Event} [event]
  9862. * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
  9863. * this function to be called.
  9864. *
  9865. * @listens Player#durationchange
  9866. * @listens Player#timeupdate
  9867. * @listens Player#loadedmetadata
  9868. */
  9869. ;
  9870. _proto.updateContent = function updateContent(event) {
  9871. var duration = this.player_.duration();
  9872. if (this.duration_ !== duration) {
  9873. this.duration_ = duration;
  9874. this.updateFormattedTime_(duration);
  9875. }
  9876. };
  9877. return DurationDisplay;
  9878. }(TimeDisplay);
  9879. /**
  9880. * The text that is added to the `DurationDisplay` for screen reader users.
  9881. *
  9882. * @type {string}
  9883. * @private
  9884. */
  9885. DurationDisplay.prototype.labelText_ = 'Duration';
  9886. /**
  9887. * The text that should display over the `DurationDisplay`s controls. Added to for localization.
  9888. *
  9889. * @type {string}
  9890. * @private
  9891. *
  9892. * @deprecated in v7; controlText_ is not used in non-active display Components
  9893. */
  9894. DurationDisplay.prototype.controlText_ = 'Duration';
  9895. Component.registerComponent('DurationDisplay', DurationDisplay);
  9896. /**
  9897. * The separator between the current time and duration.
  9898. * Can be hidden if it's not needed in the design.
  9899. *
  9900. * @extends Component
  9901. */
  9902. var TimeDivider =
  9903. /*#__PURE__*/
  9904. function (_Component) {
  9905. _inheritsLoose(TimeDivider, _Component);
  9906. function TimeDivider() {
  9907. return _Component.apply(this, arguments) || this;
  9908. }
  9909. var _proto = TimeDivider.prototype;
  9910. /**
  9911. * Create the component's DOM element
  9912. *
  9913. * @return {Element}
  9914. * The element that was created.
  9915. */
  9916. _proto.createEl = function createEl() {
  9917. return _Component.prototype.createEl.call(this, 'div', {
  9918. className: 'vjs-time-control vjs-time-divider',
  9919. innerHTML: '<div><span>/</span></div>'
  9920. }, {
  9921. // this element and its contents can be hidden from assistive techs since
  9922. // it is made extraneous by the announcement of the control text
  9923. // for the current time and duration displays
  9924. 'aria-hidden': true
  9925. });
  9926. };
  9927. return TimeDivider;
  9928. }(Component);
  9929. Component.registerComponent('TimeDivider', TimeDivider);
  9930. /**
  9931. * Displays the time left in the video
  9932. *
  9933. * @extends Component
  9934. */
  9935. var RemainingTimeDisplay =
  9936. /*#__PURE__*/
  9937. function (_TimeDisplay) {
  9938. _inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
  9939. /**
  9940. * Creates an instance of this class.
  9941. *
  9942. * @param {Player} player
  9943. * The `Player` that this class should be attached to.
  9944. *
  9945. * @param {Object} [options]
  9946. * The key/value store of player options.
  9947. */
  9948. function RemainingTimeDisplay(player, options) {
  9949. var _this;
  9950. _this = _TimeDisplay.call(this, player, options) || this;
  9951. _this.on(player, 'durationchange', _this.throttledUpdateContent);
  9952. _this.on(player, 'ended', _this.handleEnded);
  9953. return _this;
  9954. }
  9955. /**
  9956. * Builds the default DOM `className`.
  9957. *
  9958. * @return {string}
  9959. * The DOM `className` for this object.
  9960. */
  9961. var _proto = RemainingTimeDisplay.prototype;
  9962. _proto.buildCSSClass = function buildCSSClass() {
  9963. return 'vjs-remaining-time';
  9964. }
  9965. /**
  9966. * Create the `Component`'s DOM element with the "minus" characted prepend to the time
  9967. *
  9968. * @return {Element}
  9969. * The element that was created.
  9970. */
  9971. ;
  9972. _proto.createEl = function createEl$$1() {
  9973. var el = _TimeDisplay.prototype.createEl.call(this);
  9974. el.insertBefore(createEl('span', {}, {
  9975. 'aria-hidden': true
  9976. }, '-'), this.contentEl_);
  9977. return el;
  9978. }
  9979. /**
  9980. * Update remaining time display.
  9981. *
  9982. * @param {EventTarget~Event} [event]
  9983. * The `timeupdate` or `durationchange` event that caused this to run.
  9984. *
  9985. * @listens Player#timeupdate
  9986. * @listens Player#durationchange
  9987. */
  9988. ;
  9989. _proto.updateContent = function updateContent(event) {
  9990. if (typeof this.player_.duration() !== 'number') {
  9991. return;
  9992. } // @deprecated We should only use remainingTimeDisplay
  9993. // as of video.js 7
  9994. if (this.player_.remainingTimeDisplay) {
  9995. this.updateFormattedTime_(this.player_.remainingTimeDisplay());
  9996. } else {
  9997. this.updateFormattedTime_(this.player_.remainingTime());
  9998. }
  9999. }
  10000. /**
  10001. * When the player fires ended there should be no time left. Sadly
  10002. * this is not always the case, lets make it seem like that is the case
  10003. * for users.
  10004. *
  10005. * @param {EventTarget~Event} [event]
  10006. * The `ended` event that caused this to run.
  10007. *
  10008. * @listens Player#ended
  10009. */
  10010. ;
  10011. _proto.handleEnded = function handleEnded(event) {
  10012. if (!this.player_.duration()) {
  10013. return;
  10014. }
  10015. this.updateFormattedTime_(0);
  10016. };
  10017. return RemainingTimeDisplay;
  10018. }(TimeDisplay);
  10019. /**
  10020. * The text that is added to the `RemainingTimeDisplay` for screen reader users.
  10021. *
  10022. * @type {string}
  10023. * @private
  10024. */
  10025. RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
  10026. /**
  10027. * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
  10028. *
  10029. * @type {string}
  10030. * @private
  10031. *
  10032. * @deprecated in v7; controlText_ is not used in non-active display Components
  10033. */
  10034. RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
  10035. Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
  10036. /**
  10037. * Displays the live indicator when duration is Infinity.
  10038. *
  10039. * @extends Component
  10040. */
  10041. var LiveDisplay =
  10042. /*#__PURE__*/
  10043. function (_Component) {
  10044. _inheritsLoose(LiveDisplay, _Component);
  10045. /**
  10046. * Creates an instance of this class.
  10047. *
  10048. * @param {Player} player
  10049. * The `Player` that this class should be attached to.
  10050. *
  10051. * @param {Object} [options]
  10052. * The key/value store of player options.
  10053. */
  10054. function LiveDisplay(player, options) {
  10055. var _this;
  10056. _this = _Component.call(this, player, options) || this;
  10057. _this.updateShowing();
  10058. _this.on(_this.player(), 'durationchange', _this.updateShowing);
  10059. return _this;
  10060. }
  10061. /**
  10062. * Create the `Component`'s DOM element
  10063. *
  10064. * @return {Element}
  10065. * The element that was created.
  10066. */
  10067. var _proto = LiveDisplay.prototype;
  10068. _proto.createEl = function createEl$$1() {
  10069. var el = _Component.prototype.createEl.call(this, 'div', {
  10070. className: 'vjs-live-control vjs-control'
  10071. });
  10072. this.contentEl_ = createEl('div', {
  10073. className: 'vjs-live-display',
  10074. innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE')
  10075. }, {
  10076. 'aria-live': 'off'
  10077. });
  10078. el.appendChild(this.contentEl_);
  10079. return el;
  10080. };
  10081. _proto.dispose = function dispose() {
  10082. this.contentEl_ = null;
  10083. _Component.prototype.dispose.call(this);
  10084. }
  10085. /**
  10086. * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
  10087. * it accordingly
  10088. *
  10089. * @param {EventTarget~Event} [event]
  10090. * The {@link Player#durationchange} event that caused this function to run.
  10091. *
  10092. * @listens Player#durationchange
  10093. */
  10094. ;
  10095. _proto.updateShowing = function updateShowing(event) {
  10096. if (this.player().duration() === Infinity) {
  10097. this.show();
  10098. } else {
  10099. this.hide();
  10100. }
  10101. };
  10102. return LiveDisplay;
  10103. }(Component);
  10104. Component.registerComponent('LiveDisplay', LiveDisplay);
  10105. /**
  10106. * Displays the live indicator when duration is Infinity.
  10107. *
  10108. * @extends Component
  10109. */
  10110. var SeekToLive =
  10111. /*#__PURE__*/
  10112. function (_Button) {
  10113. _inheritsLoose(SeekToLive, _Button);
  10114. /**
  10115. * Creates an instance of this class.
  10116. *
  10117. * @param {Player} player
  10118. * The `Player` that this class should be attached to.
  10119. *
  10120. * @param {Object} [options]
  10121. * The key/value store of player options.
  10122. */
  10123. function SeekToLive(player, options) {
  10124. var _this;
  10125. _this = _Button.call(this, player, options) || this;
  10126. _this.updateLiveEdgeStatus();
  10127. if (_this.player_.liveTracker) {
  10128. _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus);
  10129. }
  10130. return _this;
  10131. }
  10132. /**
  10133. * Create the `Component`'s DOM element
  10134. *
  10135. * @return {Element}
  10136. * The element that was created.
  10137. */
  10138. var _proto = SeekToLive.prototype;
  10139. _proto.createEl = function createEl$$1() {
  10140. var el = _Button.prototype.createEl.call(this, 'button', {
  10141. className: 'vjs-seek-to-live-control vjs-control'
  10142. });
  10143. this.textEl_ = createEl('span', {
  10144. className: 'vjs-seek-to-live-text',
  10145. innerHTML: this.localize('LIVE')
  10146. }, {
  10147. 'aria-hidden': 'true'
  10148. });
  10149. el.appendChild(this.textEl_);
  10150. return el;
  10151. }
  10152. /**
  10153. * Update the state of this button if we are at the live edge
  10154. * or not
  10155. */
  10156. ;
  10157. _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) {
  10158. // default to live edge
  10159. if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
  10160. this.setAttribute('aria-disabled', true);
  10161. this.addClass('vjs-at-live-edge');
  10162. this.controlText('Seek to live, currently playing live');
  10163. } else {
  10164. this.setAttribute('aria-disabled', false);
  10165. this.removeClass('vjs-at-live-edge');
  10166. this.controlText('Seek to live, currently behind live');
  10167. }
  10168. }
  10169. /**
  10170. * On click bring us as near to the live point as possible.
  10171. * This requires that we wait for the next `live-seekable-change`
  10172. * event which will happen every segment length seconds.
  10173. */
  10174. ;
  10175. _proto.handleClick = function handleClick() {
  10176. this.player_.liveTracker.seekToLiveEdge();
  10177. }
  10178. /**
  10179. * Dispose of the element and stop tracking
  10180. */
  10181. ;
  10182. _proto.dispose = function dispose() {
  10183. if (this.player_.liveTracker) {
  10184. this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus);
  10185. }
  10186. this.textEl_ = null;
  10187. _Button.prototype.dispose.call(this);
  10188. };
  10189. return SeekToLive;
  10190. }(Button);
  10191. SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
  10192. Component.registerComponent('SeekToLive', SeekToLive);
  10193. /**
  10194. * The base functionality for a slider. Can be vertical or horizontal.
  10195. * For instance the volume bar or the seek bar on a video is a slider.
  10196. *
  10197. * @extends Component
  10198. */
  10199. var Slider =
  10200. /*#__PURE__*/
  10201. function (_Component) {
  10202. _inheritsLoose(Slider, _Component);
  10203. /**
  10204. * Create an instance of this class
  10205. *
  10206. * @param {Player} player
  10207. * The `Player` that this class should be attached to.
  10208. *
  10209. * @param {Object} [options]
  10210. * The key/value store of player options.
  10211. */
  10212. function Slider(player, options) {
  10213. var _this;
  10214. _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for
  10215. _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
  10216. _this.vertical(!!_this.options_.vertical);
  10217. _this.enable();
  10218. return _this;
  10219. }
  10220. /**
  10221. * Are controls are currently enabled for this slider or not.
  10222. *
  10223. * @return {boolean}
  10224. * true if controls are enabled, false otherwise
  10225. */
  10226. var _proto = Slider.prototype;
  10227. _proto.enabled = function enabled() {
  10228. return this.enabled_;
  10229. }
  10230. /**
  10231. * Enable controls for this slider if they are disabled
  10232. */
  10233. ;
  10234. _proto.enable = function enable() {
  10235. if (this.enabled()) {
  10236. return;
  10237. }
  10238. this.on('mousedown', this.handleMouseDown);
  10239. this.on('touchstart', this.handleMouseDown);
  10240. this.on('focus', this.handleFocus);
  10241. this.on('blur', this.handleBlur);
  10242. this.on('click', this.handleClick);
  10243. this.on(this.player_, 'controlsvisible', this.update);
  10244. if (this.playerEvent) {
  10245. this.on(this.player_, this.playerEvent, this.update);
  10246. }
  10247. this.removeClass('disabled');
  10248. this.setAttribute('tabindex', 0);
  10249. this.enabled_ = true;
  10250. }
  10251. /**
  10252. * Disable controls for this slider if they are enabled
  10253. */
  10254. ;
  10255. _proto.disable = function disable() {
  10256. if (!this.enabled()) {
  10257. return;
  10258. }
  10259. var doc = this.bar.el_.ownerDocument;
  10260. this.off('mousedown', this.handleMouseDown);
  10261. this.off('touchstart', this.handleMouseDown);
  10262. this.off('focus', this.handleFocus);
  10263. this.off('blur', this.handleBlur);
  10264. this.off('click', this.handleClick);
  10265. this.off(this.player_, 'controlsvisible', this.update);
  10266. this.off(doc, 'mousemove', this.handleMouseMove);
  10267. this.off(doc, 'mouseup', this.handleMouseUp);
  10268. this.off(doc, 'touchmove', this.handleMouseMove);
  10269. this.off(doc, 'touchend', this.handleMouseUp);
  10270. this.removeAttribute('tabindex');
  10271. this.addClass('disabled');
  10272. if (this.playerEvent) {
  10273. this.off(this.player_, this.playerEvent, this.update);
  10274. }
  10275. this.enabled_ = false;
  10276. }
  10277. /**
  10278. * Create the `Slider`s DOM element.
  10279. *
  10280. * @param {string} type
  10281. * Type of element to create.
  10282. *
  10283. * @param {Object} [props={}]
  10284. * List of properties in Object form.
  10285. *
  10286. * @param {Object} [attributes={}]
  10287. * list of attributes in Object form.
  10288. *
  10289. * @return {Element}
  10290. * The element that gets created.
  10291. */
  10292. ;
  10293. _proto.createEl = function createEl$$1(type, props, attributes) {
  10294. if (props === void 0) {
  10295. props = {};
  10296. }
  10297. if (attributes === void 0) {
  10298. attributes = {};
  10299. }
  10300. // Add the slider element class to all sub classes
  10301. props.className = props.className + ' vjs-slider';
  10302. props = assign({
  10303. tabIndex: 0
  10304. }, props);
  10305. attributes = assign({
  10306. 'role': 'slider',
  10307. 'aria-valuenow': 0,
  10308. 'aria-valuemin': 0,
  10309. 'aria-valuemax': 100,
  10310. 'tabIndex': 0
  10311. }, attributes);
  10312. return _Component.prototype.createEl.call(this, type, props, attributes);
  10313. }
  10314. /**
  10315. * Handle `mousedown` or `touchstart` events on the `Slider`.
  10316. *
  10317. * @param {EventTarget~Event} event
  10318. * `mousedown` or `touchstart` event that triggered this function
  10319. *
  10320. * @listens mousedown
  10321. * @listens touchstart
  10322. * @fires Slider#slideractive
  10323. */
  10324. ;
  10325. _proto.handleMouseDown = function handleMouseDown(event) {
  10326. var doc = this.bar.el_.ownerDocument;
  10327. if (event.type === 'mousedown') {
  10328. event.preventDefault();
  10329. } // Do not call preventDefault() on touchstart in Chrome
  10330. // to avoid console warnings. Use a 'touch-action: none' style
  10331. // instead to prevent unintented scrolling.
  10332. // https://developers.google.com/web/updates/2017/01/scrolling-intervention
  10333. if (event.type === 'touchstart' && !IS_CHROME) {
  10334. event.preventDefault();
  10335. }
  10336. blockTextSelection();
  10337. this.addClass('vjs-sliding');
  10338. /**
  10339. * Triggered when the slider is in an active state
  10340. *
  10341. * @event Slider#slideractive
  10342. * @type {EventTarget~Event}
  10343. */
  10344. this.trigger('slideractive');
  10345. this.on(doc, 'mousemove', this.handleMouseMove);
  10346. this.on(doc, 'mouseup', this.handleMouseUp);
  10347. this.on(doc, 'touchmove', this.handleMouseMove);
  10348. this.on(doc, 'touchend', this.handleMouseUp);
  10349. this.handleMouseMove(event);
  10350. }
  10351. /**
  10352. * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
  10353. * The `mousemove` and `touchmove` events will only only trigger this function during
  10354. * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
  10355. * {@link Slider#handleMouseUp}.
  10356. *
  10357. * @param {EventTarget~Event} event
  10358. * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
  10359. * this function
  10360. *
  10361. * @listens mousemove
  10362. * @listens touchmove
  10363. */
  10364. ;
  10365. _proto.handleMouseMove = function handleMouseMove(event) {}
  10366. /**
  10367. * Handle `mouseup` or `touchend` events on the `Slider`.
  10368. *
  10369. * @param {EventTarget~Event} event
  10370. * `mouseup` or `touchend` event that triggered this function.
  10371. *
  10372. * @listens touchend
  10373. * @listens mouseup
  10374. * @fires Slider#sliderinactive
  10375. */
  10376. ;
  10377. _proto.handleMouseUp = function handleMouseUp() {
  10378. var doc = this.bar.el_.ownerDocument;
  10379. unblockTextSelection();
  10380. this.removeClass('vjs-sliding');
  10381. /**
  10382. * Triggered when the slider is no longer in an active state.
  10383. *
  10384. * @event Slider#sliderinactive
  10385. * @type {EventTarget~Event}
  10386. */
  10387. this.trigger('sliderinactive');
  10388. this.off(doc, 'mousemove', this.handleMouseMove);
  10389. this.off(doc, 'mouseup', this.handleMouseUp);
  10390. this.off(doc, 'touchmove', this.handleMouseMove);
  10391. this.off(doc, 'touchend', this.handleMouseUp);
  10392. this.update();
  10393. }
  10394. /**
  10395. * Update the progress bar of the `Slider`.
  10396. *
  10397. * @return {number}
  10398. * The percentage of progress the progress bar represents as a
  10399. * number from 0 to 1.
  10400. */
  10401. ;
  10402. _proto.update = function update() {
  10403. // In VolumeBar init we have a setTimeout for update that pops and update
  10404. // to the end of the execution stack. The player is destroyed before then
  10405. // update will cause an error
  10406. if (!this.el_) {
  10407. return;
  10408. } // If scrubbing, we could use a cached value to make the handle keep up
  10409. // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
  10410. // some flash players are slow, so we might want to utilize this later.
  10411. // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
  10412. var progress = this.getPercent();
  10413. var bar = this.bar; // If there's no bar...
  10414. if (!bar) {
  10415. return;
  10416. } // Protect against no duration and other division issues
  10417. if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
  10418. progress = 0;
  10419. } // Convert to a percentage for setting
  10420. var percentage = (progress * 100).toFixed(2) + '%';
  10421. var style = bar.el().style; // Set the new bar width or height
  10422. if (this.vertical()) {
  10423. style.height = percentage;
  10424. } else {
  10425. style.width = percentage;
  10426. }
  10427. return progress;
  10428. }
  10429. /**
  10430. * Calculate distance for slider
  10431. *
  10432. * @param {EventTarget~Event} event
  10433. * The event that caused this function to run.
  10434. *
  10435. * @return {number}
  10436. * The current position of the Slider.
  10437. * - position.x for vertical `Slider`s
  10438. * - position.y for horizontal `Slider`s
  10439. */
  10440. ;
  10441. _proto.calculateDistance = function calculateDistance(event) {
  10442. var position = getPointerPosition(this.el_, event);
  10443. if (this.vertical()) {
  10444. return position.y;
  10445. }
  10446. return position.x;
  10447. }
  10448. /**
  10449. * Handle a `focus` event on this `Slider`.
  10450. *
  10451. * @param {EventTarget~Event} event
  10452. * The `focus` event that caused this function to run.
  10453. *
  10454. * @listens focus
  10455. */
  10456. ;
  10457. _proto.handleFocus = function handleFocus() {
  10458. this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  10459. }
  10460. /**
  10461. * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
  10462. * arrow keys. This function will only be called when the slider has focus. See
  10463. * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
  10464. *
  10465. * @param {EventTarget~Event} event
  10466. * the `keydown` event that caused this function to run.
  10467. *
  10468. * @listens keydown
  10469. */
  10470. ;
  10471. _proto.handleKeyPress = function handleKeyPress(event) {
  10472. // Left and Down Arrows
  10473. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  10474. event.preventDefault();
  10475. this.stepBack(); // Up and Right Arrows
  10476. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  10477. event.preventDefault();
  10478. this.stepForward();
  10479. } else {
  10480. // Pass keypress handling up for unsupported keys
  10481. _Component.prototype.handleKeyPress.call(this, event);
  10482. }
  10483. }
  10484. /**
  10485. * Handle a `blur` event on this `Slider`.
  10486. *
  10487. * @param {EventTarget~Event} event
  10488. * The `blur` event that caused this function to run.
  10489. *
  10490. * @listens blur
  10491. */
  10492. ;
  10493. _proto.handleBlur = function handleBlur() {
  10494. this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  10495. }
  10496. /**
  10497. * Listener for click events on slider, used to prevent clicks
  10498. * from bubbling up to parent elements like button menus.
  10499. *
  10500. * @param {Object} event
  10501. * Event that caused this object to run
  10502. */
  10503. ;
  10504. _proto.handleClick = function handleClick(event) {
  10505. event.stopImmediatePropagation();
  10506. event.preventDefault();
  10507. }
  10508. /**
  10509. * Get/set if slider is horizontal for vertical
  10510. *
  10511. * @param {boolean} [bool]
  10512. * - true if slider is vertical,
  10513. * - false is horizontal
  10514. *
  10515. * @return {boolean}
  10516. * - true if slider is vertical, and getting
  10517. * - false if the slider is horizontal, and getting
  10518. */
  10519. ;
  10520. _proto.vertical = function vertical(bool) {
  10521. if (bool === undefined) {
  10522. return this.vertical_ || false;
  10523. }
  10524. this.vertical_ = !!bool;
  10525. if (this.vertical_) {
  10526. this.addClass('vjs-slider-vertical');
  10527. } else {
  10528. this.addClass('vjs-slider-horizontal');
  10529. }
  10530. };
  10531. return Slider;
  10532. }(Component);
  10533. Component.registerComponent('Slider', Slider);
  10534. /**
  10535. * Shows loading progress
  10536. *
  10537. * @extends Component
  10538. */
  10539. var LoadProgressBar =
  10540. /*#__PURE__*/
  10541. function (_Component) {
  10542. _inheritsLoose(LoadProgressBar, _Component);
  10543. /**
  10544. * Creates an instance of this class.
  10545. *
  10546. * @param {Player} player
  10547. * The `Player` that this class should be attached to.
  10548. *
  10549. * @param {Object} [options]
  10550. * The key/value store of player options.
  10551. */
  10552. function LoadProgressBar(player, options) {
  10553. var _this;
  10554. _this = _Component.call(this, player, options) || this;
  10555. _this.partEls_ = [];
  10556. _this.on(player, 'progress', _this.update);
  10557. return _this;
  10558. }
  10559. /**
  10560. * Create the `Component`'s DOM element
  10561. *
  10562. * @return {Element}
  10563. * The element that was created.
  10564. */
  10565. var _proto = LoadProgressBar.prototype;
  10566. _proto.createEl = function createEl$$1() {
  10567. return _Component.prototype.createEl.call(this, 'div', {
  10568. className: 'vjs-load-progress',
  10569. innerHTML: "<span class=\"vjs-control-text\"><span>" + this.localize('Loaded') + "</span>: <span class=\"vjs-control-text-loaded-percentage\">0%</span></span>"
  10570. });
  10571. };
  10572. _proto.dispose = function dispose() {
  10573. this.partEls_ = null;
  10574. _Component.prototype.dispose.call(this);
  10575. }
  10576. /**
  10577. * Update progress bar
  10578. *
  10579. * @param {EventTarget~Event} [event]
  10580. * The `progress` event that caused this function to run.
  10581. *
  10582. * @listens Player#progress
  10583. */
  10584. ;
  10585. _proto.update = function update(event) {
  10586. var liveTracker = this.player_.liveTracker;
  10587. var buffered = this.player_.buffered();
  10588. var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
  10589. var bufferedEnd = this.player_.bufferedEnd();
  10590. var children = this.partEls_;
  10591. var controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); // get the percent width of a time compared to the total end
  10592. var percentify = function percentify(time, end, rounded) {
  10593. // no NaN
  10594. var percent = time / end || 0;
  10595. percent = (percent >= 1 ? 1 : percent) * 100;
  10596. if (rounded) {
  10597. percent = percent.toFixed(2);
  10598. }
  10599. return percent + '%';
  10600. }; // update the width of the progress bar
  10601. this.el_.style.width = percentify(bufferedEnd, duration); // update the control-text
  10602. textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); // add child elements to represent the individual buffered time ranges
  10603. for (var i = 0; i < buffered.length; i++) {
  10604. var start = buffered.start(i);
  10605. var end = buffered.end(i);
  10606. var part = children[i];
  10607. if (!part) {
  10608. part = this.el_.appendChild(createEl());
  10609. children[i] = part;
  10610. } // set the percent based on the width of the progress bar (bufferedEnd)
  10611. part.style.left = percentify(start, bufferedEnd);
  10612. part.style.width = percentify(end - start, bufferedEnd);
  10613. } // remove unused buffered range elements
  10614. for (var _i = children.length; _i > buffered.length; _i--) {
  10615. this.el_.removeChild(children[_i - 1]);
  10616. }
  10617. children.length = buffered.length;
  10618. };
  10619. return LoadProgressBar;
  10620. }(Component);
  10621. Component.registerComponent('LoadProgressBar', LoadProgressBar);
  10622. /**
  10623. * Time tooltips display a time above the progress bar.
  10624. *
  10625. * @extends Component
  10626. */
  10627. var TimeTooltip =
  10628. /*#__PURE__*/
  10629. function (_Component) {
  10630. _inheritsLoose(TimeTooltip, _Component);
  10631. function TimeTooltip() {
  10632. return _Component.apply(this, arguments) || this;
  10633. }
  10634. var _proto = TimeTooltip.prototype;
  10635. /**
  10636. * Create the time tooltip DOM element
  10637. *
  10638. * @return {Element}
  10639. * The element that was created.
  10640. */
  10641. _proto.createEl = function createEl$$1() {
  10642. return _Component.prototype.createEl.call(this, 'div', {
  10643. className: 'vjs-time-tooltip'
  10644. }, {
  10645. 'aria-hidden': 'true'
  10646. });
  10647. }
  10648. /**
  10649. * Updates the position of the time tooltip relative to the `SeekBar`.
  10650. *
  10651. * @param {Object} seekBarRect
  10652. * The `ClientRect` for the {@link SeekBar} element.
  10653. *
  10654. * @param {number} seekBarPoint
  10655. * A number from 0 to 1, representing a horizontal reference point
  10656. * from the left edge of the {@link SeekBar}
  10657. */
  10658. ;
  10659. _proto.update = function update(seekBarRect, seekBarPoint, content) {
  10660. var tooltipRect = getBoundingClientRect(this.el_);
  10661. var playerRect = getBoundingClientRect(this.player_.el());
  10662. var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
  10663. // for example, if the player isn't in the DOM for testing
  10664. if (!playerRect || !tooltipRect) {
  10665. return;
  10666. } // This is the space left of the `seekBarPoint` available within the bounds
  10667. // of the player. We calculate any gap between the left edge of the player
  10668. // and the left edge of the `SeekBar` and add the number of pixels in the
  10669. // `SeekBar` before hitting the `seekBarPoint`
  10670. var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
  10671. // of the player. We calculate the number of pixels from the `seekBarPoint`
  10672. // to the right edge of the `SeekBar` and add to that any gap between the
  10673. // right edge of the `SeekBar` and the player.
  10674. var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
  10675. // further to the right to center it over the `seekBarPoint`.
  10676. var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
  10677. // the results of the space calculations above.
  10678. if (spaceLeftOfPoint < pullTooltipBy) {
  10679. pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
  10680. } else if (spaceRightOfPoint < pullTooltipBy) {
  10681. pullTooltipBy = spaceRightOfPoint;
  10682. } // Due to the imprecision of decimal/ratio based calculations and varying
  10683. // rounding behaviors, there are cases where the spacing adjustment is off
  10684. // by a pixel or two. This adds insurance to these calculations.
  10685. if (pullTooltipBy < 0) {
  10686. pullTooltipBy = 0;
  10687. } else if (pullTooltipBy > tooltipRect.width) {
  10688. pullTooltipBy = tooltipRect.width;
  10689. }
  10690. this.el_.style.right = "-" + pullTooltipBy + "px";
  10691. textContent(this.el_, content);
  10692. }
  10693. /**
  10694. * Updates the position of the time tooltip relative to the `SeekBar`.
  10695. *
  10696. * @param {Object} seekBarRect
  10697. * The `ClientRect` for the {@link SeekBar} element.
  10698. *
  10699. * @param {number} seekBarPoint
  10700. * A number from 0 to 1, representing a horizontal reference point
  10701. * from the left edge of the {@link SeekBar}
  10702. *
  10703. * @param {number} time
  10704. * The time to update the tooltip to, not used during live playback
  10705. *
  10706. * @param {Function} cb
  10707. * A function that will be called during the request animation frame
  10708. * for tooltips that need to do additional animations from the default
  10709. */
  10710. ;
  10711. _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
  10712. var _this = this;
  10713. // If there is an existing rAF ID, cancel it so we don't over-queue.
  10714. if (this.rafId_) {
  10715. this.cancelAnimationFrame(this.rafId_);
  10716. }
  10717. this.rafId_ = this.requestAnimationFrame(function () {
  10718. var content;
  10719. var duration = _this.player_.duration();
  10720. if (_this.player_.liveTracker && _this.player_.liveTracker.isLive()) {
  10721. var liveWindow = _this.player_.liveTracker.liveWindow();
  10722. var secondsBehind = liveWindow - seekBarPoint * liveWindow;
  10723. content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
  10724. } else {
  10725. content = formatTime(time, duration);
  10726. }
  10727. _this.update(seekBarRect, seekBarPoint, content);
  10728. if (cb) {
  10729. cb();
  10730. }
  10731. });
  10732. };
  10733. return TimeTooltip;
  10734. }(Component);
  10735. Component.registerComponent('TimeTooltip', TimeTooltip);
  10736. /**
  10737. * Used by {@link SeekBar} to display media playback progress as part of the
  10738. * {@link ProgressControl}.
  10739. *
  10740. * @extends Component
  10741. */
  10742. var PlayProgressBar =
  10743. /*#__PURE__*/
  10744. function (_Component) {
  10745. _inheritsLoose(PlayProgressBar, _Component);
  10746. function PlayProgressBar() {
  10747. return _Component.apply(this, arguments) || this;
  10748. }
  10749. var _proto = PlayProgressBar.prototype;
  10750. /**
  10751. * Create the the DOM element for this class.
  10752. *
  10753. * @return {Element}
  10754. * The element that was created.
  10755. */
  10756. _proto.createEl = function createEl() {
  10757. return _Component.prototype.createEl.call(this, 'div', {
  10758. className: 'vjs-play-progress vjs-slider-bar'
  10759. }, {
  10760. 'aria-hidden': 'true'
  10761. });
  10762. }
  10763. /**
  10764. * Enqueues updates to its own DOM as well as the DOM of its
  10765. * {@link TimeTooltip} child.
  10766. *
  10767. * @param {Object} seekBarRect
  10768. * The `ClientRect` for the {@link SeekBar} element.
  10769. *
  10770. * @param {number} seekBarPoint
  10771. * A number from 0 to 1, representing a horizontal reference point
  10772. * from the left edge of the {@link SeekBar}
  10773. */
  10774. ;
  10775. _proto.update = function update(seekBarRect, seekBarPoint) {
  10776. var timeTooltip = this.getChild('timeTooltip');
  10777. if (!timeTooltip) {
  10778. return;
  10779. }
  10780. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  10781. timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
  10782. };
  10783. return PlayProgressBar;
  10784. }(Component);
  10785. /**
  10786. * Default options for {@link PlayProgressBar}.
  10787. *
  10788. * @type {Object}
  10789. * @private
  10790. */
  10791. PlayProgressBar.prototype.options_ = {
  10792. children: []
  10793. }; // Time tooltips should not be added to a player on mobile devices
  10794. if (!IS_IOS && !IS_ANDROID) {
  10795. PlayProgressBar.prototype.options_.children.push('timeTooltip');
  10796. }
  10797. Component.registerComponent('PlayProgressBar', PlayProgressBar);
  10798. /**
  10799. * The {@link MouseTimeDisplay} component tracks mouse movement over the
  10800. * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
  10801. * indicating the time which is represented by a given point in the
  10802. * {@link ProgressControl}.
  10803. *
  10804. * @extends Component
  10805. */
  10806. var MouseTimeDisplay =
  10807. /*#__PURE__*/
  10808. function (_Component) {
  10809. _inheritsLoose(MouseTimeDisplay, _Component);
  10810. /**
  10811. * Creates an instance of this class.
  10812. *
  10813. * @param {Player} player
  10814. * The {@link Player} that this class should be attached to.
  10815. *
  10816. * @param {Object} [options]
  10817. * The key/value store of player options.
  10818. */
  10819. function MouseTimeDisplay(player, options) {
  10820. var _this;
  10821. _this = _Component.call(this, player, options) || this;
  10822. _this.update = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update), 25);
  10823. return _this;
  10824. }
  10825. /**
  10826. * Create the DOM element for this class.
  10827. *
  10828. * @return {Element}
  10829. * The element that was created.
  10830. */
  10831. var _proto = MouseTimeDisplay.prototype;
  10832. _proto.createEl = function createEl() {
  10833. return _Component.prototype.createEl.call(this, 'div', {
  10834. className: 'vjs-mouse-display'
  10835. });
  10836. }
  10837. /**
  10838. * Enqueues updates to its own DOM as well as the DOM of its
  10839. * {@link TimeTooltip} child.
  10840. *
  10841. * @param {Object} seekBarRect
  10842. * The `ClientRect` for the {@link SeekBar} element.
  10843. *
  10844. * @param {number} seekBarPoint
  10845. * A number from 0 to 1, representing a horizontal reference point
  10846. * from the left edge of the {@link SeekBar}
  10847. */
  10848. ;
  10849. _proto.update = function update(seekBarRect, seekBarPoint) {
  10850. var _this2 = this;
  10851. var time = seekBarPoint * this.player_.duration();
  10852. this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
  10853. _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
  10854. });
  10855. };
  10856. return MouseTimeDisplay;
  10857. }(Component);
  10858. /**
  10859. * Default options for `MouseTimeDisplay`
  10860. *
  10861. * @type {Object}
  10862. * @private
  10863. */
  10864. MouseTimeDisplay.prototype.options_ = {
  10865. children: ['timeTooltip']
  10866. };
  10867. Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
  10868. var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
  10869. var PAGE_KEY_MULTIPLIER = 12; // The interval at which the bar should update as it progresses.
  10870. var UPDATE_REFRESH_INTERVAL = 30;
  10871. /**
  10872. * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
  10873. * as its `bar`.
  10874. *
  10875. * @extends Slider
  10876. */
  10877. var SeekBar =
  10878. /*#__PURE__*/
  10879. function (_Slider) {
  10880. _inheritsLoose(SeekBar, _Slider);
  10881. /**
  10882. * Creates an instance of this class.
  10883. *
  10884. * @param {Player} player
  10885. * The `Player` that this class should be attached to.
  10886. *
  10887. * @param {Object} [options]
  10888. * The key/value store of player options.
  10889. */
  10890. function SeekBar(player, options) {
  10891. var _this;
  10892. _this = _Slider.call(this, player, options) || this;
  10893. _this.setEventHandlers_();
  10894. return _this;
  10895. }
  10896. /**
  10897. * Sets the event handlers
  10898. *
  10899. * @private
  10900. */
  10901. var _proto = SeekBar.prototype;
  10902. _proto.setEventHandlers_ = function setEventHandlers_() {
  10903. this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
  10904. this.on(this.player_, 'timeupdate', this.update);
  10905. this.on(this.player_, 'ended', this.handleEnded);
  10906. this.on(this.player_, 'durationchange', this.update);
  10907. if (this.player_.liveTracker) {
  10908. this.on(this.player_.liveTracker, 'liveedgechange', this.update);
  10909. } // when playing, let's ensure we smoothly update the play progress bar
  10910. // via an interval
  10911. this.updateInterval = null;
  10912. this.on(this.player_, ['playing'], this.enableInterval_);
  10913. this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden,
  10914. // also, this causes the CPU to spike and eventually crash the page on IE11.
  10915. if ('hidden' in document && 'visibilityState' in document) {
  10916. this.on(document, 'visibilitychange', this.toggleVisibility_);
  10917. }
  10918. };
  10919. _proto.toggleVisibility_ = function toggleVisibility_(e) {
  10920. if (document.hidden) {
  10921. this.disableInterval_(e);
  10922. } else {
  10923. this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP
  10924. this.requestAnimationFrame(this.update);
  10925. }
  10926. };
  10927. _proto.enableInterval_ = function enableInterval_() {
  10928. var _this2 = this;
  10929. this.clearInterval(this.updateInterval);
  10930. this.updateInterval = this.setInterval(function () {
  10931. _this2.requestAnimationFrame(_this2.update);
  10932. }, UPDATE_REFRESH_INTERVAL);
  10933. };
  10934. _proto.disableInterval_ = function disableInterval_(e) {
  10935. if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') {
  10936. return;
  10937. }
  10938. this.clearInterval(this.updateInterval);
  10939. }
  10940. /**
  10941. * Create the `Component`'s DOM element
  10942. *
  10943. * @return {Element}
  10944. * The element that was created.
  10945. */
  10946. ;
  10947. _proto.createEl = function createEl$$1() {
  10948. return _Slider.prototype.createEl.call(this, 'div', {
  10949. className: 'vjs-progress-holder'
  10950. }, {
  10951. 'aria-label': this.localize('Progress Bar')
  10952. });
  10953. }
  10954. /**
  10955. * This function updates the play progress bar and accessibility
  10956. * attributes to whatever is passed in.
  10957. *
  10958. * @param {number} currentTime
  10959. * The currentTime value that should be used for accessibility
  10960. *
  10961. * @param {number} percent
  10962. * The percentage as a decimal that the bar should be filled from 0-1.
  10963. *
  10964. * @private
  10965. */
  10966. ;
  10967. _proto.update_ = function update_(currentTime, percent) {
  10968. var liveTracker = this.player_.liveTracker;
  10969. var duration = this.player_.duration();
  10970. if (liveTracker && liveTracker.isLive()) {
  10971. duration = this.player_.liveTracker.liveCurrentTime();
  10972. } // machine readable value of progress bar (percentage complete)
  10973. this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); // human readable value of progress bar (time complete)
  10974. this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); // Update the `PlayProgressBar`.
  10975. if (this.bar) {
  10976. this.bar.update(getBoundingClientRect(this.el_), percent);
  10977. }
  10978. }
  10979. /**
  10980. * Update the seek bar's UI.
  10981. *
  10982. * @param {EventTarget~Event} [event]
  10983. * The `timeupdate` or `ended` event that caused this to run.
  10984. *
  10985. * @listens Player#timeupdate
  10986. *
  10987. * @return {number}
  10988. * The current percent at a number from 0-1
  10989. */
  10990. ;
  10991. _proto.update = function update(event) {
  10992. // if the offsetParent is null, then this element is hidden, in which case
  10993. // we don't need to update it.
  10994. if (this.el().offsetParent === null) {
  10995. return;
  10996. }
  10997. var percent = _Slider.prototype.update.call(this);
  10998. this.update_(this.getCurrentTime_(), percent);
  10999. return percent;
  11000. }
  11001. /**
  11002. * Get the value of current time but allows for smooth scrubbing,
  11003. * when player can't keep up.
  11004. *
  11005. * @return {number}
  11006. * The current time value to display
  11007. *
  11008. * @private
  11009. */
  11010. ;
  11011. _proto.getCurrentTime_ = function getCurrentTime_() {
  11012. return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  11013. }
  11014. /**
  11015. * We want the seek bar to be full on ended
  11016. * no matter what the actual internal values are. so we force it.
  11017. *
  11018. * @param {EventTarget~Event} [event]
  11019. * The `timeupdate` or `ended` event that caused this to run.
  11020. *
  11021. * @listens Player#ended
  11022. */
  11023. ;
  11024. _proto.handleEnded = function handleEnded(event) {
  11025. this.update_(this.player_.duration(), 1);
  11026. }
  11027. /**
  11028. * Get the percentage of media played so far.
  11029. *
  11030. * @return {number}
  11031. * The percentage of media played so far (0 to 1).
  11032. */
  11033. ;
  11034. _proto.getPercent = function getPercent() {
  11035. var currentTime = this.getCurrentTime_();
  11036. var percent;
  11037. var liveTracker = this.player_.liveTracker;
  11038. if (liveTracker && liveTracker.isLive()) {
  11039. percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
  11040. if (liveTracker.atLiveEdge()) {
  11041. percent = 1;
  11042. }
  11043. } else {
  11044. percent = currentTime / this.player_.duration();
  11045. }
  11046. return percent >= 1 ? 1 : percent || 0;
  11047. }
  11048. /**
  11049. * Handle mouse down on seek bar
  11050. *
  11051. * @param {EventTarget~Event} event
  11052. * The `mousedown` event that caused this to run.
  11053. *
  11054. * @listens mousedown
  11055. */
  11056. ;
  11057. _proto.handleMouseDown = function handleMouseDown(event) {
  11058. if (!isSingleLeftClick(event)) {
  11059. return;
  11060. } // Stop event propagation to prevent double fire in progress-control.js
  11061. event.stopPropagation();
  11062. this.player_.scrubbing(true);
  11063. this.videoWasPlaying = !this.player_.paused();
  11064. this.player_.pause();
  11065. _Slider.prototype.handleMouseDown.call(this, event);
  11066. }
  11067. /**
  11068. * Handle mouse move on seek bar
  11069. *
  11070. * @param {EventTarget~Event} event
  11071. * The `mousemove` event that caused this to run.
  11072. *
  11073. * @listens mousemove
  11074. */
  11075. ;
  11076. _proto.handleMouseMove = function handleMouseMove(event) {
  11077. if (!isSingleLeftClick(event)) {
  11078. return;
  11079. }
  11080. var newTime;
  11081. var distance = this.calculateDistance(event);
  11082. var liveTracker = this.player_.liveTracker;
  11083. if (!liveTracker || !liveTracker.isLive()) {
  11084. newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
  11085. if (newTime === this.player_.duration()) {
  11086. newTime = newTime - 0.1;
  11087. }
  11088. } else {
  11089. var seekableStart = liveTracker.seekableStart();
  11090. var seekableEnd = liveTracker.liveCurrentTime();
  11091. newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
  11092. if (newTime >= seekableEnd) {
  11093. newTime = seekableEnd;
  11094. } // Compensate for precision differences so that currentTime is not less
  11095. // than seekable start
  11096. if (newTime <= seekableStart) {
  11097. newTime = seekableStart + 0.1;
  11098. } // On android seekableEnd can be Infinity sometimes,
  11099. // this will cause newTime to be Infinity, which is
  11100. // not a valid currentTime.
  11101. if (newTime === Infinity) {
  11102. return;
  11103. }
  11104. } // Set new time (tell player to seek to new time)
  11105. this.player_.currentTime(newTime);
  11106. };
  11107. _proto.enable = function enable() {
  11108. _Slider.prototype.enable.call(this);
  11109. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  11110. if (!mouseTimeDisplay) {
  11111. return;
  11112. }
  11113. mouseTimeDisplay.show();
  11114. };
  11115. _proto.disable = function disable() {
  11116. _Slider.prototype.disable.call(this);
  11117. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  11118. if (!mouseTimeDisplay) {
  11119. return;
  11120. }
  11121. mouseTimeDisplay.hide();
  11122. }
  11123. /**
  11124. * Handle mouse up on seek bar
  11125. *
  11126. * @param {EventTarget~Event} event
  11127. * The `mouseup` event that caused this to run.
  11128. *
  11129. * @listens mouseup
  11130. */
  11131. ;
  11132. _proto.handleMouseUp = function handleMouseUp(event) {
  11133. _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
  11134. if (event) {
  11135. event.stopPropagation();
  11136. }
  11137. this.player_.scrubbing(false);
  11138. /**
  11139. * Trigger timeupdate because we're done seeking and the time has changed.
  11140. * This is particularly useful for if the player is paused to time the time displays.
  11141. *
  11142. * @event Tech#timeupdate
  11143. * @type {EventTarget~Event}
  11144. */
  11145. this.player_.trigger({
  11146. type: 'timeupdate',
  11147. target: this,
  11148. manuallyTriggered: true
  11149. });
  11150. if (this.videoWasPlaying) {
  11151. silencePromise(this.player_.play());
  11152. }
  11153. }
  11154. /**
  11155. * Move more quickly fast forward for keyboard-only users
  11156. */
  11157. ;
  11158. _proto.stepForward = function stepForward() {
  11159. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
  11160. }
  11161. /**
  11162. * Move more quickly rewind for keyboard-only users
  11163. */
  11164. ;
  11165. _proto.stepBack = function stepBack() {
  11166. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
  11167. }
  11168. /**
  11169. * Toggles the playback state of the player
  11170. * This gets called when enter or space is used on the seekbar
  11171. *
  11172. * @param {EventTarget~Event} event
  11173. * The `keydown` event that caused this function to be called
  11174. *
  11175. */
  11176. ;
  11177. _proto.handleAction = function handleAction(event) {
  11178. if (this.player_.paused()) {
  11179. this.player_.play();
  11180. } else {
  11181. this.player_.pause();
  11182. }
  11183. }
  11184. /**
  11185. * Called when this SeekBar has focus and a key gets pressed down.
  11186. * Supports the following keys:
  11187. *
  11188. * Space or Enter key fire a click event
  11189. * Home key moves to start of the timeline
  11190. * End key moves to end of the timeline
  11191. * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
  11192. * PageDown key moves back a larger step than ArrowDown
  11193. * PageUp key moves forward a large step
  11194. *
  11195. * @param {EventTarget~Event} event
  11196. * The `keydown` event that caused this function to be called.
  11197. *
  11198. * @listens keydown
  11199. */
  11200. ;
  11201. _proto.handleKeyPress = function handleKeyPress(event) {
  11202. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  11203. event.preventDefault();
  11204. this.handleAction(event);
  11205. } else if (keycode.isEventKey(event, 'Home')) {
  11206. event.preventDefault();
  11207. this.player_.currentTime(0);
  11208. } else if (keycode.isEventKey(event, 'End')) {
  11209. event.preventDefault();
  11210. this.player_.currentTime(this.player_.duration());
  11211. } else if (/^[0-9]$/.test(keycode(event))) {
  11212. event.preventDefault();
  11213. var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
  11214. this.player_.currentTime(this.player_.duration() * gotoFraction);
  11215. } else if (keycode.isEventKey(event, 'PgDn')) {
  11216. event.preventDefault();
  11217. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  11218. } else if (keycode.isEventKey(event, 'PgUp')) {
  11219. event.preventDefault();
  11220. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  11221. } else {
  11222. // Pass keypress handling up for unsupported keys
  11223. _Slider.prototype.handleKeyPress.call(this, event);
  11224. }
  11225. };
  11226. return SeekBar;
  11227. }(Slider);
  11228. /**
  11229. * Default options for the `SeekBar`
  11230. *
  11231. * @type {Object}
  11232. * @private
  11233. */
  11234. SeekBar.prototype.options_ = {
  11235. children: ['loadProgressBar', 'playProgressBar'],
  11236. barName: 'playProgressBar'
  11237. }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
  11238. if (!IS_IOS && !IS_ANDROID) {
  11239. SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
  11240. }
  11241. Component.registerComponent('SeekBar', SeekBar);
  11242. /**
  11243. * The Progress Control component contains the seek bar, load progress,
  11244. * and play progress.
  11245. *
  11246. * @extends Component
  11247. */
  11248. var ProgressControl =
  11249. /*#__PURE__*/
  11250. function (_Component) {
  11251. _inheritsLoose(ProgressControl, _Component);
  11252. /**
  11253. * Creates an instance of this class.
  11254. *
  11255. * @param {Player} player
  11256. * The `Player` that this class should be attached to.
  11257. *
  11258. * @param {Object} [options]
  11259. * The key/value store of player options.
  11260. */
  11261. function ProgressControl(player, options) {
  11262. var _this;
  11263. _this = _Component.call(this, player, options) || this;
  11264. _this.handleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
  11265. _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseSeek), 25);
  11266. _this.enable();
  11267. return _this;
  11268. }
  11269. /**
  11270. * Create the `Component`'s DOM element
  11271. *
  11272. * @return {Element}
  11273. * The element that was created.
  11274. */
  11275. var _proto = ProgressControl.prototype;
  11276. _proto.createEl = function createEl$$1() {
  11277. return _Component.prototype.createEl.call(this, 'div', {
  11278. className: 'vjs-progress-control vjs-control'
  11279. });
  11280. }
  11281. /**
  11282. * When the mouse moves over the `ProgressControl`, the pointer position
  11283. * gets passed down to the `MouseTimeDisplay` component.
  11284. *
  11285. * @param {EventTarget~Event} event
  11286. * The `mousemove` event that caused this function to run.
  11287. *
  11288. * @listen mousemove
  11289. */
  11290. ;
  11291. _proto.handleMouseMove = function handleMouseMove(event) {
  11292. var seekBar = this.getChild('seekBar');
  11293. if (seekBar) {
  11294. var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
  11295. var seekBarEl = seekBar.el();
  11296. var seekBarRect = getBoundingClientRect(seekBarEl);
  11297. var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
  11298. // that it's possible to trigger this behavior outside the boundaries of
  11299. // the `SeekBar`. This ensures we stay within it at all times.
  11300. if (seekBarPoint > 1) {
  11301. seekBarPoint = 1;
  11302. } else if (seekBarPoint < 0) {
  11303. seekBarPoint = 0;
  11304. }
  11305. if (mouseTimeDisplay) {
  11306. mouseTimeDisplay.update(seekBarRect, seekBarPoint);
  11307. }
  11308. }
  11309. }
  11310. /**
  11311. * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
  11312. *
  11313. * @method ProgressControl#throttledHandleMouseSeek
  11314. * @param {EventTarget~Event} event
  11315. * The `mousemove` event that caused this function to run.
  11316. *
  11317. * @listen mousemove
  11318. * @listen touchmove
  11319. */
  11320. /**
  11321. * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
  11322. *
  11323. * @param {EventTarget~Event} event
  11324. * `mousedown` or `touchstart` event that triggered this function
  11325. *
  11326. * @listens mousemove
  11327. * @listens touchmove
  11328. */
  11329. ;
  11330. _proto.handleMouseSeek = function handleMouseSeek(event) {
  11331. var seekBar = this.getChild('seekBar');
  11332. if (seekBar) {
  11333. seekBar.handleMouseMove(event);
  11334. }
  11335. }
  11336. /**
  11337. * Are controls are currently enabled for this progress control.
  11338. *
  11339. * @return {boolean}
  11340. * true if controls are enabled, false otherwise
  11341. */
  11342. ;
  11343. _proto.enabled = function enabled() {
  11344. return this.enabled_;
  11345. }
  11346. /**
  11347. * Disable all controls on the progress control and its children
  11348. */
  11349. ;
  11350. _proto.disable = function disable() {
  11351. this.children().forEach(function (child) {
  11352. return child.disable && child.disable();
  11353. });
  11354. if (!this.enabled()) {
  11355. return;
  11356. }
  11357. this.off(['mousedown', 'touchstart'], this.handleMouseDown);
  11358. this.off(this.el_, 'mousemove', this.handleMouseMove);
  11359. this.handleMouseUp();
  11360. this.addClass('disabled');
  11361. this.enabled_ = false;
  11362. }
  11363. /**
  11364. * Enable all controls on the progress control and its children
  11365. */
  11366. ;
  11367. _proto.enable = function enable() {
  11368. this.children().forEach(function (child) {
  11369. return child.enable && child.enable();
  11370. });
  11371. if (this.enabled()) {
  11372. return;
  11373. }
  11374. this.on(['mousedown', 'touchstart'], this.handleMouseDown);
  11375. this.on(this.el_, 'mousemove', this.handleMouseMove);
  11376. this.removeClass('disabled');
  11377. this.enabled_ = true;
  11378. }
  11379. /**
  11380. * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
  11381. *
  11382. * @param {EventTarget~Event} event
  11383. * `mousedown` or `touchstart` event that triggered this function
  11384. *
  11385. * @listens mousedown
  11386. * @listens touchstart
  11387. */
  11388. ;
  11389. _proto.handleMouseDown = function handleMouseDown(event) {
  11390. var doc = this.el_.ownerDocument;
  11391. var seekBar = this.getChild('seekBar');
  11392. if (seekBar) {
  11393. seekBar.handleMouseDown(event);
  11394. }
  11395. this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
  11396. this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
  11397. this.on(doc, 'mouseup', this.handleMouseUp);
  11398. this.on(doc, 'touchend', this.handleMouseUp);
  11399. }
  11400. /**
  11401. * Handle `mouseup` or `touchend` events on the `ProgressControl`.
  11402. *
  11403. * @param {EventTarget~Event} event
  11404. * `mouseup` or `touchend` event that triggered this function.
  11405. *
  11406. * @listens touchend
  11407. * @listens mouseup
  11408. */
  11409. ;
  11410. _proto.handleMouseUp = function handleMouseUp(event) {
  11411. var doc = this.el_.ownerDocument;
  11412. var seekBar = this.getChild('seekBar');
  11413. if (seekBar) {
  11414. seekBar.handleMouseUp(event);
  11415. }
  11416. this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
  11417. this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
  11418. this.off(doc, 'mouseup', this.handleMouseUp);
  11419. this.off(doc, 'touchend', this.handleMouseUp);
  11420. };
  11421. return ProgressControl;
  11422. }(Component);
  11423. /**
  11424. * Default options for `ProgressControl`
  11425. *
  11426. * @type {Object}
  11427. * @private
  11428. */
  11429. ProgressControl.prototype.options_ = {
  11430. children: ['seekBar']
  11431. };
  11432. Component.registerComponent('ProgressControl', ProgressControl);
  11433. /**
  11434. * Toggle fullscreen video
  11435. *
  11436. * @extends Button
  11437. */
  11438. var FullscreenToggle =
  11439. /*#__PURE__*/
  11440. function (_Button) {
  11441. _inheritsLoose(FullscreenToggle, _Button);
  11442. /**
  11443. * Creates an instance of this class.
  11444. *
  11445. * @param {Player} player
  11446. * The `Player` that this class should be attached to.
  11447. *
  11448. * @param {Object} [options]
  11449. * The key/value store of player options.
  11450. */
  11451. function FullscreenToggle(player, options) {
  11452. var _this;
  11453. _this = _Button.call(this, player, options) || this;
  11454. _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
  11455. if (document[FullscreenApi.fullscreenEnabled] === false) {
  11456. _this.disable();
  11457. }
  11458. return _this;
  11459. }
  11460. /**
  11461. * Builds the default DOM `className`.
  11462. *
  11463. * @return {string}
  11464. * The DOM `className` for this object.
  11465. */
  11466. var _proto = FullscreenToggle.prototype;
  11467. _proto.buildCSSClass = function buildCSSClass() {
  11468. return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
  11469. }
  11470. /**
  11471. * Handles fullscreenchange on the player and change control text accordingly.
  11472. *
  11473. * @param {EventTarget~Event} [event]
  11474. * The {@link Player#fullscreenchange} event that caused this function to be
  11475. * called.
  11476. *
  11477. * @listens Player#fullscreenchange
  11478. */
  11479. ;
  11480. _proto.handleFullscreenChange = function handleFullscreenChange(event) {
  11481. if (this.player_.isFullscreen()) {
  11482. this.controlText('Non-Fullscreen');
  11483. } else {
  11484. this.controlText('Fullscreen');
  11485. }
  11486. }
  11487. /**
  11488. * This gets called when an `FullscreenToggle` is "clicked". See
  11489. * {@link ClickableComponent} for more detailed information on what a click can be.
  11490. *
  11491. * @param {EventTarget~Event} [event]
  11492. * The `keydown`, `tap`, or `click` event that caused this function to be
  11493. * called.
  11494. *
  11495. * @listens tap
  11496. * @listens click
  11497. */
  11498. ;
  11499. _proto.handleClick = function handleClick(event) {
  11500. if (!this.player_.isFullscreen()) {
  11501. this.player_.requestFullscreen();
  11502. } else {
  11503. this.player_.exitFullscreen();
  11504. }
  11505. };
  11506. return FullscreenToggle;
  11507. }(Button);
  11508. /**
  11509. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  11510. *
  11511. * @type {string}
  11512. * @private
  11513. */
  11514. FullscreenToggle.prototype.controlText_ = 'Fullscreen';
  11515. Component.registerComponent('FullscreenToggle', FullscreenToggle);
  11516. /**
  11517. * Check if volume control is supported and if it isn't hide the
  11518. * `Component` that was passed using the `vjs-hidden` class.
  11519. *
  11520. * @param {Component} self
  11521. * The component that should be hidden if volume is unsupported
  11522. *
  11523. * @param {Player} player
  11524. * A reference to the player
  11525. *
  11526. * @private
  11527. */
  11528. var checkVolumeSupport = function checkVolumeSupport(self, player) {
  11529. // hide volume controls when they're not supported by the current tech
  11530. if (player.tech_ && !player.tech_.featuresVolumeControl) {
  11531. self.addClass('vjs-hidden');
  11532. }
  11533. self.on(player, 'loadstart', function () {
  11534. if (!player.tech_.featuresVolumeControl) {
  11535. self.addClass('vjs-hidden');
  11536. } else {
  11537. self.removeClass('vjs-hidden');
  11538. }
  11539. });
  11540. };
  11541. /**
  11542. * Shows volume level
  11543. *
  11544. * @extends Component
  11545. */
  11546. var VolumeLevel =
  11547. /*#__PURE__*/
  11548. function (_Component) {
  11549. _inheritsLoose(VolumeLevel, _Component);
  11550. function VolumeLevel() {
  11551. return _Component.apply(this, arguments) || this;
  11552. }
  11553. var _proto = VolumeLevel.prototype;
  11554. /**
  11555. * Create the `Component`'s DOM element
  11556. *
  11557. * @return {Element}
  11558. * The element that was created.
  11559. */
  11560. _proto.createEl = function createEl() {
  11561. return _Component.prototype.createEl.call(this, 'div', {
  11562. className: 'vjs-volume-level',
  11563. innerHTML: '<span class="vjs-control-text"></span>'
  11564. });
  11565. };
  11566. return VolumeLevel;
  11567. }(Component);
  11568. Component.registerComponent('VolumeLevel', VolumeLevel);
  11569. /**
  11570. * The bar that contains the volume level and can be clicked on to adjust the level
  11571. *
  11572. * @extends Slider
  11573. */
  11574. var VolumeBar =
  11575. /*#__PURE__*/
  11576. function (_Slider) {
  11577. _inheritsLoose(VolumeBar, _Slider);
  11578. /**
  11579. * Creates an instance of this class.
  11580. *
  11581. * @param {Player} player
  11582. * The `Player` that this class should be attached to.
  11583. *
  11584. * @param {Object} [options]
  11585. * The key/value store of player options.
  11586. */
  11587. function VolumeBar(player, options) {
  11588. var _this;
  11589. _this = _Slider.call(this, player, options) || this;
  11590. _this.on('slideractive', _this.updateLastVolume_);
  11591. _this.on(player, 'volumechange', _this.updateARIAAttributes);
  11592. player.ready(function () {
  11593. return _this.updateARIAAttributes();
  11594. });
  11595. return _this;
  11596. }
  11597. /**
  11598. * Create the `Component`'s DOM element
  11599. *
  11600. * @return {Element}
  11601. * The element that was created.
  11602. */
  11603. var _proto = VolumeBar.prototype;
  11604. _proto.createEl = function createEl$$1() {
  11605. return _Slider.prototype.createEl.call(this, 'div', {
  11606. className: 'vjs-volume-bar vjs-slider-bar'
  11607. }, {
  11608. 'aria-label': this.localize('Volume Level'),
  11609. 'aria-live': 'polite'
  11610. });
  11611. }
  11612. /**
  11613. * Handle mouse down on volume bar
  11614. *
  11615. * @param {EventTarget~Event} event
  11616. * The `mousedown` event that caused this to run.
  11617. *
  11618. * @listens mousedown
  11619. */
  11620. ;
  11621. _proto.handleMouseDown = function handleMouseDown(event) {
  11622. if (!isSingleLeftClick(event)) {
  11623. return;
  11624. }
  11625. _Slider.prototype.handleMouseDown.call(this, event);
  11626. }
  11627. /**
  11628. * Handle movement events on the {@link VolumeMenuButton}.
  11629. *
  11630. * @param {EventTarget~Event} event
  11631. * The event that caused this function to run.
  11632. *
  11633. * @listens mousemove
  11634. */
  11635. ;
  11636. _proto.handleMouseMove = function handleMouseMove(event) {
  11637. if (!isSingleLeftClick(event)) {
  11638. return;
  11639. }
  11640. this.checkMuted();
  11641. this.player_.volume(this.calculateDistance(event));
  11642. }
  11643. /**
  11644. * If the player is muted unmute it.
  11645. */
  11646. ;
  11647. _proto.checkMuted = function checkMuted() {
  11648. if (this.player_.muted()) {
  11649. this.player_.muted(false);
  11650. }
  11651. }
  11652. /**
  11653. * Get percent of volume level
  11654. *
  11655. * @return {number}
  11656. * Volume level percent as a decimal number.
  11657. */
  11658. ;
  11659. _proto.getPercent = function getPercent() {
  11660. if (this.player_.muted()) {
  11661. return 0;
  11662. }
  11663. return this.player_.volume();
  11664. }
  11665. /**
  11666. * Increase volume level for keyboard users
  11667. */
  11668. ;
  11669. _proto.stepForward = function stepForward() {
  11670. this.checkMuted();
  11671. this.player_.volume(this.player_.volume() + 0.1);
  11672. }
  11673. /**
  11674. * Decrease volume level for keyboard users
  11675. */
  11676. ;
  11677. _proto.stepBack = function stepBack() {
  11678. this.checkMuted();
  11679. this.player_.volume(this.player_.volume() - 0.1);
  11680. }
  11681. /**
  11682. * Update ARIA accessibility attributes
  11683. *
  11684. * @param {EventTarget~Event} [event]
  11685. * The `volumechange` event that caused this function to run.
  11686. *
  11687. * @listens Player#volumechange
  11688. */
  11689. ;
  11690. _proto.updateARIAAttributes = function updateARIAAttributes(event) {
  11691. var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
  11692. this.el_.setAttribute('aria-valuenow', ariaValue);
  11693. this.el_.setAttribute('aria-valuetext', ariaValue + '%');
  11694. }
  11695. /**
  11696. * Returns the current value of the player volume as a percentage
  11697. *
  11698. * @private
  11699. */
  11700. ;
  11701. _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
  11702. return Math.round(this.player_.volume() * 100);
  11703. }
  11704. /**
  11705. * When user starts dragging the VolumeBar, store the volume and listen for
  11706. * the end of the drag. When the drag ends, if the volume was set to zero,
  11707. * set lastVolume to the stored volume.
  11708. *
  11709. * @listens slideractive
  11710. * @private
  11711. */
  11712. ;
  11713. _proto.updateLastVolume_ = function updateLastVolume_() {
  11714. var _this2 = this;
  11715. var volumeBeforeDrag = this.player_.volume();
  11716. this.one('sliderinactive', function () {
  11717. if (_this2.player_.volume() === 0) {
  11718. _this2.player_.lastVolume_(volumeBeforeDrag);
  11719. }
  11720. });
  11721. };
  11722. return VolumeBar;
  11723. }(Slider);
  11724. /**
  11725. * Default options for the `VolumeBar`
  11726. *
  11727. * @type {Object}
  11728. * @private
  11729. */
  11730. VolumeBar.prototype.options_ = {
  11731. children: ['volumeLevel'],
  11732. barName: 'volumeLevel'
  11733. };
  11734. /**
  11735. * Call the update event for this Slider when this event happens on the player.
  11736. *
  11737. * @type {string}
  11738. */
  11739. VolumeBar.prototype.playerEvent = 'volumechange';
  11740. Component.registerComponent('VolumeBar', VolumeBar);
  11741. /**
  11742. * The component for controlling the volume level
  11743. *
  11744. * @extends Component
  11745. */
  11746. var VolumeControl =
  11747. /*#__PURE__*/
  11748. function (_Component) {
  11749. _inheritsLoose(VolumeControl, _Component);
  11750. /**
  11751. * Creates an instance of this class.
  11752. *
  11753. * @param {Player} player
  11754. * The `Player` that this class should be attached to.
  11755. *
  11756. * @param {Object} [options={}]
  11757. * The key/value store of player options.
  11758. */
  11759. function VolumeControl(player, options) {
  11760. var _this;
  11761. if (options === void 0) {
  11762. options = {};
  11763. }
  11764. options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
  11765. // the VolumeBar is turned on.
  11766. if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
  11767. options.volumeBar = options.volumeBar || {};
  11768. options.volumeBar.vertical = options.vertical;
  11769. }
  11770. _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
  11771. checkVolumeSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
  11772. _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
  11773. _this.on('mousedown', _this.handleMouseDown);
  11774. _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and
  11775. // is dragging) or in focus we do not want to hide the VolumeBar
  11776. _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
  11777. _this.volumeBar.addClass('vjs-slider-active');
  11778. _this.addClass('vjs-slider-active');
  11779. _this.trigger('slideractive');
  11780. });
  11781. _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
  11782. _this.volumeBar.removeClass('vjs-slider-active');
  11783. _this.removeClass('vjs-slider-active');
  11784. _this.trigger('sliderinactive');
  11785. });
  11786. return _this;
  11787. }
  11788. /**
  11789. * Create the `Component`'s DOM element
  11790. *
  11791. * @return {Element}
  11792. * The element that was created.
  11793. */
  11794. var _proto = VolumeControl.prototype;
  11795. _proto.createEl = function createEl() {
  11796. var orientationClass = 'vjs-volume-horizontal';
  11797. if (this.options_.vertical) {
  11798. orientationClass = 'vjs-volume-vertical';
  11799. }
  11800. return _Component.prototype.createEl.call(this, 'div', {
  11801. className: "vjs-volume-control vjs-control " + orientationClass
  11802. });
  11803. }
  11804. /**
  11805. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  11806. *
  11807. * @param {EventTarget~Event} event
  11808. * `mousedown` or `touchstart` event that triggered this function
  11809. *
  11810. * @listens mousedown
  11811. * @listens touchstart
  11812. */
  11813. ;
  11814. _proto.handleMouseDown = function handleMouseDown(event) {
  11815. var doc = this.el_.ownerDocument;
  11816. this.on(doc, 'mousemove', this.throttledHandleMouseMove);
  11817. this.on(doc, 'touchmove', this.throttledHandleMouseMove);
  11818. this.on(doc, 'mouseup', this.handleMouseUp);
  11819. this.on(doc, 'touchend', this.handleMouseUp);
  11820. }
  11821. /**
  11822. * Handle `mouseup` or `touchend` events on the `VolumeControl`.
  11823. *
  11824. * @param {EventTarget~Event} event
  11825. * `mouseup` or `touchend` event that triggered this function.
  11826. *
  11827. * @listens touchend
  11828. * @listens mouseup
  11829. */
  11830. ;
  11831. _proto.handleMouseUp = function handleMouseUp(event) {
  11832. var doc = this.el_.ownerDocument;
  11833. this.off(doc, 'mousemove', this.throttledHandleMouseMove);
  11834. this.off(doc, 'touchmove', this.throttledHandleMouseMove);
  11835. this.off(doc, 'mouseup', this.handleMouseUp);
  11836. this.off(doc, 'touchend', this.handleMouseUp);
  11837. }
  11838. /**
  11839. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  11840. *
  11841. * @param {EventTarget~Event} event
  11842. * `mousedown` or `touchstart` event that triggered this function
  11843. *
  11844. * @listens mousedown
  11845. * @listens touchstart
  11846. */
  11847. ;
  11848. _proto.handleMouseMove = function handleMouseMove(event) {
  11849. this.volumeBar.handleMouseMove(event);
  11850. };
  11851. return VolumeControl;
  11852. }(Component);
  11853. /**
  11854. * Default options for the `VolumeControl`
  11855. *
  11856. * @type {Object}
  11857. * @private
  11858. */
  11859. VolumeControl.prototype.options_ = {
  11860. children: ['volumeBar']
  11861. };
  11862. Component.registerComponent('VolumeControl', VolumeControl);
  11863. /**
  11864. * Check if muting volume is supported and if it isn't hide the mute toggle
  11865. * button.
  11866. *
  11867. * @param {Component} self
  11868. * A reference to the mute toggle button
  11869. *
  11870. * @param {Player} player
  11871. * A reference to the player
  11872. *
  11873. * @private
  11874. */
  11875. var checkMuteSupport = function checkMuteSupport(self, player) {
  11876. // hide mute toggle button if it's not supported by the current tech
  11877. if (player.tech_ && !player.tech_.featuresMuteControl) {
  11878. self.addClass('vjs-hidden');
  11879. }
  11880. self.on(player, 'loadstart', function () {
  11881. if (!player.tech_.featuresMuteControl) {
  11882. self.addClass('vjs-hidden');
  11883. } else {
  11884. self.removeClass('vjs-hidden');
  11885. }
  11886. });
  11887. };
  11888. /**
  11889. * A button component for muting the audio.
  11890. *
  11891. * @extends Button
  11892. */
  11893. var MuteToggle =
  11894. /*#__PURE__*/
  11895. function (_Button) {
  11896. _inheritsLoose(MuteToggle, _Button);
  11897. /**
  11898. * Creates an instance of this class.
  11899. *
  11900. * @param {Player} player
  11901. * The `Player` that this class should be attached to.
  11902. *
  11903. * @param {Object} [options]
  11904. * The key/value store of player options.
  11905. */
  11906. function MuteToggle(player, options) {
  11907. var _this;
  11908. _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
  11909. checkMuteSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
  11910. _this.on(player, ['loadstart', 'volumechange'], _this.update);
  11911. return _this;
  11912. }
  11913. /**
  11914. * Builds the default DOM `className`.
  11915. *
  11916. * @return {string}
  11917. * The DOM `className` for this object.
  11918. */
  11919. var _proto = MuteToggle.prototype;
  11920. _proto.buildCSSClass = function buildCSSClass() {
  11921. return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
  11922. }
  11923. /**
  11924. * This gets called when an `MuteToggle` is "clicked". See
  11925. * {@link ClickableComponent} for more detailed information on what a click can be.
  11926. *
  11927. * @param {EventTarget~Event} [event]
  11928. * The `keydown`, `tap`, or `click` event that caused this function to be
  11929. * called.
  11930. *
  11931. * @listens tap
  11932. * @listens click
  11933. */
  11934. ;
  11935. _proto.handleClick = function handleClick(event) {
  11936. var vol = this.player_.volume();
  11937. var lastVolume = this.player_.lastVolume_();
  11938. if (vol === 0) {
  11939. var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
  11940. this.player_.volume(volumeToSet);
  11941. this.player_.muted(false);
  11942. } else {
  11943. this.player_.muted(this.player_.muted() ? false : true);
  11944. }
  11945. }
  11946. /**
  11947. * Update the `MuteToggle` button based on the state of `volume` and `muted`
  11948. * on the player.
  11949. *
  11950. * @param {EventTarget~Event} [event]
  11951. * The {@link Player#loadstart} event if this function was called
  11952. * through an event.
  11953. *
  11954. * @listens Player#loadstart
  11955. * @listens Player#volumechange
  11956. */
  11957. ;
  11958. _proto.update = function update(event) {
  11959. this.updateIcon_();
  11960. this.updateControlText_();
  11961. }
  11962. /**
  11963. * Update the appearance of the `MuteToggle` icon.
  11964. *
  11965. * Possible states (given `level` variable below):
  11966. * - 0: crossed out
  11967. * - 1: zero bars of volume
  11968. * - 2: one bar of volume
  11969. * - 3: two bars of volume
  11970. *
  11971. * @private
  11972. */
  11973. ;
  11974. _proto.updateIcon_ = function updateIcon_() {
  11975. var vol = this.player_.volume();
  11976. var level = 3; // in iOS when a player is loaded with muted attribute
  11977. // and volume is changed with a native mute button
  11978. // we want to make sure muted state is updated
  11979. if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
  11980. this.player_.muted(this.player_.tech_.el_.muted);
  11981. }
  11982. if (vol === 0 || this.player_.muted()) {
  11983. level = 0;
  11984. } else if (vol < 0.33) {
  11985. level = 1;
  11986. } else if (vol < 0.67) {
  11987. level = 2;
  11988. } // TODO improve muted icon classes
  11989. for (var i = 0; i < 4; i++) {
  11990. removeClass(this.el_, "vjs-vol-" + i);
  11991. }
  11992. addClass(this.el_, "vjs-vol-" + level);
  11993. }
  11994. /**
  11995. * If `muted` has changed on the player, update the control text
  11996. * (`title` attribute on `vjs-mute-control` element and content of
  11997. * `vjs-control-text` element).
  11998. *
  11999. * @private
  12000. */
  12001. ;
  12002. _proto.updateControlText_ = function updateControlText_() {
  12003. var soundOff = this.player_.muted() || this.player_.volume() === 0;
  12004. var text = soundOff ? 'Unmute' : 'Mute';
  12005. if (this.controlText() !== text) {
  12006. this.controlText(text);
  12007. }
  12008. };
  12009. return MuteToggle;
  12010. }(Button);
  12011. /**
  12012. * The text that should display over the `MuteToggle`s controls. Added for localization.
  12013. *
  12014. * @type {string}
  12015. * @private
  12016. */
  12017. MuteToggle.prototype.controlText_ = 'Mute';
  12018. Component.registerComponent('MuteToggle', MuteToggle);
  12019. /**
  12020. * A Component to contain the MuteToggle and VolumeControl so that
  12021. * they can work together.
  12022. *
  12023. * @extends Component
  12024. */
  12025. var VolumePanel =
  12026. /*#__PURE__*/
  12027. function (_Component) {
  12028. _inheritsLoose(VolumePanel, _Component);
  12029. /**
  12030. * Creates an instance of this class.
  12031. *
  12032. * @param {Player} player
  12033. * The `Player` that this class should be attached to.
  12034. *
  12035. * @param {Object} [options={}]
  12036. * The key/value store of player options.
  12037. */
  12038. function VolumePanel(player, options) {
  12039. var _this;
  12040. if (options === void 0) {
  12041. options = {};
  12042. }
  12043. if (typeof options.inline !== 'undefined') {
  12044. options.inline = options.inline;
  12045. } else {
  12046. options.inline = true;
  12047. } // pass the inline option down to the VolumeControl as vertical if
  12048. // the VolumeControl is on.
  12049. if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
  12050. options.volumeControl = options.volumeControl || {};
  12051. options.volumeControl.vertical = !options.inline;
  12052. }
  12053. _this = _Component.call(this, player, options) || this;
  12054. _this.on(player, ['loadstart'], _this.volumePanelState_); // while the slider is active (the mouse has been pressed down and
  12055. // is dragging) we do not want to hide the VolumeBar
  12056. _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
  12057. _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
  12058. return _this;
  12059. }
  12060. /**
  12061. * Add vjs-slider-active class to the VolumePanel
  12062. *
  12063. * @listens VolumeControl#slideractive
  12064. * @private
  12065. */
  12066. var _proto = VolumePanel.prototype;
  12067. _proto.sliderActive_ = function sliderActive_() {
  12068. this.addClass('vjs-slider-active');
  12069. }
  12070. /**
  12071. * Removes vjs-slider-active class to the VolumePanel
  12072. *
  12073. * @listens VolumeControl#sliderinactive
  12074. * @private
  12075. */
  12076. ;
  12077. _proto.sliderInactive_ = function sliderInactive_() {
  12078. this.removeClass('vjs-slider-active');
  12079. }
  12080. /**
  12081. * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
  12082. * depending on MuteToggle and VolumeControl state
  12083. *
  12084. * @listens Player#loadstart
  12085. * @private
  12086. */
  12087. ;
  12088. _proto.volumePanelState_ = function volumePanelState_() {
  12089. // hide volume panel if neither volume control or mute toggle
  12090. // are displayed
  12091. if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
  12092. this.addClass('vjs-hidden');
  12093. } // if only mute toggle is visible we don't want
  12094. // volume panel expanding when hovered or active
  12095. if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
  12096. this.addClass('vjs-mute-toggle-only');
  12097. }
  12098. }
  12099. /**
  12100. * Create the `Component`'s DOM element
  12101. *
  12102. * @return {Element}
  12103. * The element that was created.
  12104. */
  12105. ;
  12106. _proto.createEl = function createEl() {
  12107. var orientationClass = 'vjs-volume-panel-horizontal';
  12108. if (!this.options_.inline) {
  12109. orientationClass = 'vjs-volume-panel-vertical';
  12110. }
  12111. return _Component.prototype.createEl.call(this, 'div', {
  12112. className: "vjs-volume-panel vjs-control " + orientationClass
  12113. });
  12114. };
  12115. return VolumePanel;
  12116. }(Component);
  12117. /**
  12118. * Default options for the `VolumeControl`
  12119. *
  12120. * @type {Object}
  12121. * @private
  12122. */
  12123. VolumePanel.prototype.options_ = {
  12124. children: ['muteToggle', 'volumeControl']
  12125. };
  12126. Component.registerComponent('VolumePanel', VolumePanel);
  12127. /**
  12128. * The Menu component is used to build popup menus, including subtitle and
  12129. * captions selection menus.
  12130. *
  12131. * @extends Component
  12132. */
  12133. var Menu =
  12134. /*#__PURE__*/
  12135. function (_Component) {
  12136. _inheritsLoose(Menu, _Component);
  12137. /**
  12138. * Create an instance of this class.
  12139. *
  12140. * @param {Player} player
  12141. * the player that this component should attach to
  12142. *
  12143. * @param {Object} [options]
  12144. * Object of option names and values
  12145. *
  12146. */
  12147. function Menu(player, options) {
  12148. var _this;
  12149. _this = _Component.call(this, player, options) || this;
  12150. if (options) {
  12151. _this.menuButton_ = options.menuButton;
  12152. }
  12153. _this.focusedChild_ = -1;
  12154. _this.on('keydown', _this.handleKeyPress); // All the menu item instances share the same blur handler provided by the menu container.
  12155. _this.boundHandleBlur_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleBlur);
  12156. _this.boundHandleTapClick_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTapClick);
  12157. return _this;
  12158. }
  12159. /**
  12160. * Add event listeners to the {@link MenuItem}.
  12161. *
  12162. * @param {Object} component
  12163. * The instance of the `MenuItem` to add listeners to.
  12164. *
  12165. */
  12166. var _proto = Menu.prototype;
  12167. _proto.addEventListenerForItem = function addEventListenerForItem(component) {
  12168. if (!(component instanceof Component)) {
  12169. return;
  12170. }
  12171. this.on(component, 'blur', this.boundHandleBlur_);
  12172. this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
  12173. }
  12174. /**
  12175. * Remove event listeners from the {@link MenuItem}.
  12176. *
  12177. * @param {Object} component
  12178. * The instance of the `MenuItem` to remove listeners.
  12179. *
  12180. */
  12181. ;
  12182. _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
  12183. if (!(component instanceof Component)) {
  12184. return;
  12185. }
  12186. this.off(component, 'blur', this.boundHandleBlur_);
  12187. this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
  12188. }
  12189. /**
  12190. * This method will be called indirectly when the component has been added
  12191. * before the component adds to the new menu instance by `addItem`.
  12192. * In this case, the original menu instance will remove the component
  12193. * by calling `removeChild`.
  12194. *
  12195. * @param {Object} component
  12196. * The instance of the `MenuItem`
  12197. */
  12198. ;
  12199. _proto.removeChild = function removeChild(component) {
  12200. if (typeof component === 'string') {
  12201. component = this.getChild(component);
  12202. }
  12203. this.removeEventListenerForItem(component);
  12204. _Component.prototype.removeChild.call(this, component);
  12205. }
  12206. /**
  12207. * Add a {@link MenuItem} to the menu.
  12208. *
  12209. * @param {Object|string} component
  12210. * The name or instance of the `MenuItem` to add.
  12211. *
  12212. */
  12213. ;
  12214. _proto.addItem = function addItem(component) {
  12215. var childComponent = this.addChild(component);
  12216. if (childComponent) {
  12217. this.addEventListenerForItem(childComponent);
  12218. }
  12219. }
  12220. /**
  12221. * Create the `Menu`s DOM element.
  12222. *
  12223. * @return {Element}
  12224. * the element that was created
  12225. */
  12226. ;
  12227. _proto.createEl = function createEl$$1() {
  12228. var contentElType = this.options_.contentElType || 'ul';
  12229. this.contentEl_ = createEl(contentElType, {
  12230. className: 'vjs-menu-content'
  12231. });
  12232. this.contentEl_.setAttribute('role', 'menu');
  12233. var el = _Component.prototype.createEl.call(this, 'div', {
  12234. append: this.contentEl_,
  12235. className: 'vjs-menu'
  12236. });
  12237. el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
  12238. // where a click on the parent is significant
  12239. on(el, 'click', function (event) {
  12240. event.preventDefault();
  12241. event.stopImmediatePropagation();
  12242. });
  12243. return el;
  12244. };
  12245. _proto.dispose = function dispose() {
  12246. this.contentEl_ = null;
  12247. this.boundHandleBlur_ = null;
  12248. this.boundHandleTapClick_ = null;
  12249. _Component.prototype.dispose.call(this);
  12250. }
  12251. /**
  12252. * Called when a `MenuItem` loses focus.
  12253. *
  12254. * @param {EventTarget~Event} event
  12255. * The `blur` event that caused this function to be called.
  12256. *
  12257. * @listens blur
  12258. */
  12259. ;
  12260. _proto.handleBlur = function handleBlur(event) {
  12261. var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu
  12262. if (!this.children().some(function (element) {
  12263. return element.el() === relatedTarget;
  12264. })) {
  12265. var btn = this.menuButton_;
  12266. if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
  12267. btn.unpressButton();
  12268. }
  12269. }
  12270. }
  12271. /**
  12272. * Called when a `MenuItem` gets clicked or tapped.
  12273. *
  12274. * @param {EventTarget~Event} event
  12275. * The `click` or `tap` event that caused this function to be called.
  12276. *
  12277. * @listens click,tap
  12278. */
  12279. ;
  12280. _proto.handleTapClick = function handleTapClick(event) {
  12281. // Unpress the associated MenuButton, and move focus back to it
  12282. if (this.menuButton_) {
  12283. this.menuButton_.unpressButton();
  12284. var childComponents = this.children();
  12285. if (!Array.isArray(childComponents)) {
  12286. return;
  12287. }
  12288. var foundComponent = childComponents.filter(function (component) {
  12289. return component.el() === event.target;
  12290. })[0];
  12291. if (!foundComponent) {
  12292. return;
  12293. } // don't focus menu button if item is a caption settings item
  12294. // because focus will move elsewhere
  12295. if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
  12296. this.menuButton_.focus();
  12297. }
  12298. }
  12299. }
  12300. /**
  12301. * Handle a `keydown` event on this menu. This listener is added in the constructor.
  12302. *
  12303. * @param {EventTarget~Event} event
  12304. * A `keydown` event that happened on the menu.
  12305. *
  12306. * @listens keydown
  12307. */
  12308. ;
  12309. _proto.handleKeyPress = function handleKeyPress(event) {
  12310. // Left and Down Arrows
  12311. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  12312. event.preventDefault();
  12313. this.stepForward(); // Up and Right Arrows
  12314. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  12315. event.preventDefault();
  12316. this.stepBack();
  12317. }
  12318. }
  12319. /**
  12320. * Move to next (lower) menu item for keyboard users.
  12321. */
  12322. ;
  12323. _proto.stepForward = function stepForward() {
  12324. var stepChild = 0;
  12325. if (this.focusedChild_ !== undefined) {
  12326. stepChild = this.focusedChild_ + 1;
  12327. }
  12328. this.focus(stepChild);
  12329. }
  12330. /**
  12331. * Move to previous (higher) menu item for keyboard users.
  12332. */
  12333. ;
  12334. _proto.stepBack = function stepBack() {
  12335. var stepChild = 0;
  12336. if (this.focusedChild_ !== undefined) {
  12337. stepChild = this.focusedChild_ - 1;
  12338. }
  12339. this.focus(stepChild);
  12340. }
  12341. /**
  12342. * Set focus on a {@link MenuItem} in the `Menu`.
  12343. *
  12344. * @param {Object|string} [item=0]
  12345. * Index of child item set focus on.
  12346. */
  12347. ;
  12348. _proto.focus = function focus(item) {
  12349. if (item === void 0) {
  12350. item = 0;
  12351. }
  12352. var children = this.children().slice();
  12353. var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
  12354. if (haveTitle) {
  12355. children.shift();
  12356. }
  12357. if (children.length > 0) {
  12358. if (item < 0) {
  12359. item = 0;
  12360. } else if (item >= children.length) {
  12361. item = children.length - 1;
  12362. }
  12363. this.focusedChild_ = item;
  12364. children[item].el_.focus();
  12365. }
  12366. };
  12367. return Menu;
  12368. }(Component);
  12369. Component.registerComponent('Menu', Menu);
  12370. /**
  12371. * A `MenuButton` class for any popup {@link Menu}.
  12372. *
  12373. * @extends Component
  12374. */
  12375. var MenuButton =
  12376. /*#__PURE__*/
  12377. function (_Component) {
  12378. _inheritsLoose(MenuButton, _Component);
  12379. /**
  12380. * Creates an instance of this class.
  12381. *
  12382. * @param {Player} player
  12383. * The `Player` that this class should be attached to.
  12384. *
  12385. * @param {Object} [options={}]
  12386. * The key/value store of player options.
  12387. */
  12388. function MenuButton(player, options) {
  12389. var _this;
  12390. if (options === void 0) {
  12391. options = {};
  12392. }
  12393. _this = _Component.call(this, player, options) || this;
  12394. _this.menuButton_ = new Button(player, options);
  12395. _this.menuButton_.controlText(_this.controlText_);
  12396. _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
  12397. var buttonClass = Button.prototype.buildCSSClass();
  12398. _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
  12399. _this.menuButton_.removeClass('vjs-control');
  12400. _this.addChild(_this.menuButton_);
  12401. _this.update();
  12402. _this.enabled_ = true;
  12403. _this.on(_this.menuButton_, 'tap', _this.handleClick);
  12404. _this.on(_this.menuButton_, 'click', _this.handleClick);
  12405. _this.on(_this.menuButton_, 'focus', _this.handleFocus);
  12406. _this.on(_this.menuButton_, 'blur', _this.handleBlur);
  12407. _this.on(_this.menuButton_, 'mouseenter', function () {
  12408. _this.menu.show();
  12409. });
  12410. _this.on('keydown', _this.handleSubmenuKeyPress);
  12411. return _this;
  12412. }
  12413. /**
  12414. * Update the menu based on the current state of its items.
  12415. */
  12416. var _proto = MenuButton.prototype;
  12417. _proto.update = function update() {
  12418. var menu = this.createMenu();
  12419. if (this.menu) {
  12420. this.menu.dispose();
  12421. this.removeChild(this.menu);
  12422. }
  12423. this.menu = menu;
  12424. this.addChild(menu);
  12425. /**
  12426. * Track the state of the menu button
  12427. *
  12428. * @type {Boolean}
  12429. * @private
  12430. */
  12431. this.buttonPressed_ = false;
  12432. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  12433. if (this.items && this.items.length <= this.hideThreshold_) {
  12434. this.hide();
  12435. } else {
  12436. this.show();
  12437. }
  12438. }
  12439. /**
  12440. * Create the menu and add all items to it.
  12441. *
  12442. * @return {Menu}
  12443. * The constructed menu
  12444. */
  12445. ;
  12446. _proto.createMenu = function createMenu() {
  12447. var menu = new Menu(this.player_, {
  12448. menuButton: this
  12449. });
  12450. /**
  12451. * Hide the menu if the number of items is less than or equal to this threshold. This defaults
  12452. * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
  12453. * it here because every time we run `createMenu` we need to reset the value.
  12454. *
  12455. * @protected
  12456. * @type {Number}
  12457. */
  12458. this.hideThreshold_ = 0; // Add a title list item to the top
  12459. if (this.options_.title) {
  12460. var titleEl = createEl('li', {
  12461. className: 'vjs-menu-title',
  12462. innerHTML: toTitleCase(this.options_.title),
  12463. tabIndex: -1
  12464. });
  12465. this.hideThreshold_ += 1;
  12466. var titleComponent = new Component(this.player_, {
  12467. el: titleEl
  12468. });
  12469. menu.addItem(titleComponent);
  12470. }
  12471. this.items = this.createItems();
  12472. if (this.items) {
  12473. // Add menu items to the menu
  12474. for (var i = 0; i < this.items.length; i++) {
  12475. menu.addItem(this.items[i]);
  12476. }
  12477. }
  12478. return menu;
  12479. }
  12480. /**
  12481. * Create the list of menu items. Specific to each subclass.
  12482. *
  12483. * @abstract
  12484. */
  12485. ;
  12486. _proto.createItems = function createItems() {}
  12487. /**
  12488. * Create the `MenuButtons`s DOM element.
  12489. *
  12490. * @return {Element}
  12491. * The element that gets created.
  12492. */
  12493. ;
  12494. _proto.createEl = function createEl$$1() {
  12495. return _Component.prototype.createEl.call(this, 'div', {
  12496. className: this.buildWrapperCSSClass()
  12497. }, {});
  12498. }
  12499. /**
  12500. * Allow sub components to stack CSS class names for the wrapper element
  12501. *
  12502. * @return {string}
  12503. * The constructed wrapper DOM `className`
  12504. */
  12505. ;
  12506. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  12507. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  12508. if (this.options_.inline === true) {
  12509. menuButtonClass += '-inline';
  12510. } else {
  12511. menuButtonClass += '-popup';
  12512. } // TODO: Fix the CSS so that this isn't necessary
  12513. var buttonClass = Button.prototype.buildCSSClass();
  12514. return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
  12515. }
  12516. /**
  12517. * Builds the default DOM `className`.
  12518. *
  12519. * @return {string}
  12520. * The DOM `className` for this object.
  12521. */
  12522. ;
  12523. _proto.buildCSSClass = function buildCSSClass() {
  12524. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  12525. if (this.options_.inline === true) {
  12526. menuButtonClass += '-inline';
  12527. } else {
  12528. menuButtonClass += '-popup';
  12529. }
  12530. return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
  12531. }
  12532. /**
  12533. * Get or set the localized control text that will be used for accessibility.
  12534. *
  12535. * > NOTE: This will come from the internal `menuButton_` element.
  12536. *
  12537. * @param {string} [text]
  12538. * Control text for element.
  12539. *
  12540. * @param {Element} [el=this.menuButton_.el()]
  12541. * Element to set the title on.
  12542. *
  12543. * @return {string}
  12544. * - The control text when getting
  12545. */
  12546. ;
  12547. _proto.controlText = function controlText(text, el) {
  12548. if (el === void 0) {
  12549. el = this.menuButton_.el();
  12550. }
  12551. return this.menuButton_.controlText(text, el);
  12552. }
  12553. /**
  12554. * Handle a click on a `MenuButton`.
  12555. * See {@link ClickableComponent#handleClick} for instances where this is called.
  12556. *
  12557. * @param {EventTarget~Event} event
  12558. * The `keydown`, `tap`, or `click` event that caused this function to be
  12559. * called.
  12560. *
  12561. * @listens tap
  12562. * @listens click
  12563. */
  12564. ;
  12565. _proto.handleClick = function handleClick(event) {
  12566. if (this.buttonPressed_) {
  12567. this.unpressButton();
  12568. } else {
  12569. this.pressButton();
  12570. }
  12571. }
  12572. /**
  12573. * Set the focus to the actual button, not to this element
  12574. */
  12575. ;
  12576. _proto.focus = function focus() {
  12577. this.menuButton_.focus();
  12578. }
  12579. /**
  12580. * Remove the focus from the actual button, not this element
  12581. */
  12582. ;
  12583. _proto.blur = function blur() {
  12584. this.menuButton_.blur();
  12585. }
  12586. /**
  12587. * This gets called when a `MenuButton` gains focus via a `focus` event.
  12588. * Turns on listening for `keydown` events. When they happen it
  12589. * calls `this.handleKeyPress`.
  12590. *
  12591. * @param {EventTarget~Event} event
  12592. * The `focus` event that caused this function to be called.
  12593. *
  12594. * @listens focus
  12595. */
  12596. ;
  12597. _proto.handleFocus = function handleFocus() {
  12598. on(document, 'keydown', bind(this, this.handleKeyPress));
  12599. }
  12600. /**
  12601. * Called when a `MenuButton` loses focus. Turns off the listener for
  12602. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  12603. *
  12604. * @param {EventTarget~Event} event
  12605. * The `blur` event that caused this function to be called.
  12606. *
  12607. * @listens blur
  12608. */
  12609. ;
  12610. _proto.handleBlur = function handleBlur() {
  12611. off(document, 'keydown', bind(this, this.handleKeyPress));
  12612. }
  12613. /**
  12614. * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
  12615. * {@link ClickableComponent#handleKeyPress} for instances where this is called.
  12616. *
  12617. * @param {EventTarget~Event} event
  12618. * The `keydown` event that caused this function to be called.
  12619. *
  12620. * @listens keydown
  12621. */
  12622. ;
  12623. _proto.handleKeyPress = function handleKeyPress(event) {
  12624. // Escape or Tab unpress the 'button'
  12625. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  12626. if (this.buttonPressed_) {
  12627. this.unpressButton();
  12628. } // Don't preventDefault for Tab key - we still want to lose focus
  12629. if (!keycode.isEventKey(event, 'Tab')) {
  12630. event.preventDefault(); // Set focus back to the menu button's button
  12631. this.menuButton_.focus();
  12632. } // Up Arrow or Down Arrow also 'press' the button to open the menu
  12633. } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
  12634. if (!this.buttonPressed_) {
  12635. event.preventDefault();
  12636. this.pressButton();
  12637. }
  12638. }
  12639. }
  12640. /**
  12641. * Handle a `keydown` event on a sub-menu. The listener for this is added in
  12642. * the constructor.
  12643. *
  12644. * @param {EventTarget~Event} event
  12645. * Key press event
  12646. *
  12647. * @listens keydown
  12648. */
  12649. ;
  12650. _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
  12651. // Escape or Tab unpress the 'button'
  12652. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  12653. if (this.buttonPressed_) {
  12654. this.unpressButton();
  12655. } // Don't preventDefault for Tab key - we still want to lose focus
  12656. if (!keycode.isEventKey(event, 'Tab')) {
  12657. event.preventDefault(); // Set focus back to the menu button's button
  12658. this.menuButton_.focus();
  12659. }
  12660. }
  12661. }
  12662. /**
  12663. * Put the current `MenuButton` into a pressed state.
  12664. */
  12665. ;
  12666. _proto.pressButton = function pressButton() {
  12667. if (this.enabled_) {
  12668. this.buttonPressed_ = true;
  12669. this.menu.show();
  12670. this.menu.lockShowing();
  12671. this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
  12672. // undesired scrolling behavior when the player is in an iframe
  12673. if (IS_IOS && isInFrame()) {
  12674. // Return early so that the menu isn't focused
  12675. return;
  12676. }
  12677. this.menu.focus();
  12678. }
  12679. }
  12680. /**
  12681. * Take the current `MenuButton` out of a pressed state.
  12682. */
  12683. ;
  12684. _proto.unpressButton = function unpressButton() {
  12685. if (this.enabled_) {
  12686. this.buttonPressed_ = false;
  12687. this.menu.unlockShowing();
  12688. this.menu.hide();
  12689. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  12690. }
  12691. }
  12692. /**
  12693. * Disable the `MenuButton`. Don't allow it to be clicked.
  12694. */
  12695. ;
  12696. _proto.disable = function disable() {
  12697. this.unpressButton();
  12698. this.enabled_ = false;
  12699. this.addClass('vjs-disabled');
  12700. this.menuButton_.disable();
  12701. }
  12702. /**
  12703. * Enable the `MenuButton`. Allow it to be clicked.
  12704. */
  12705. ;
  12706. _proto.enable = function enable() {
  12707. this.enabled_ = true;
  12708. this.removeClass('vjs-disabled');
  12709. this.menuButton_.enable();
  12710. };
  12711. return MenuButton;
  12712. }(Component);
  12713. Component.registerComponent('MenuButton', MenuButton);
  12714. /**
  12715. * The base class for buttons that toggle specific track types (e.g. subtitles).
  12716. *
  12717. * @extends MenuButton
  12718. */
  12719. var TrackButton =
  12720. /*#__PURE__*/
  12721. function (_MenuButton) {
  12722. _inheritsLoose(TrackButton, _MenuButton);
  12723. /**
  12724. * Creates an instance of this class.
  12725. *
  12726. * @param {Player} player
  12727. * The `Player` that this class should be attached to.
  12728. *
  12729. * @param {Object} [options]
  12730. * The key/value store of player options.
  12731. */
  12732. function TrackButton(player, options) {
  12733. var _this;
  12734. var tracks = options.tracks;
  12735. _this = _MenuButton.call(this, player, options) || this;
  12736. if (_this.items.length <= 1) {
  12737. _this.hide();
  12738. }
  12739. if (!tracks) {
  12740. return _assertThisInitialized(_this);
  12741. }
  12742. var updateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update);
  12743. tracks.addEventListener('removetrack', updateHandler);
  12744. tracks.addEventListener('addtrack', updateHandler);
  12745. _this.player_.on('ready', updateHandler);
  12746. _this.player_.on('dispose', function () {
  12747. tracks.removeEventListener('removetrack', updateHandler);
  12748. tracks.removeEventListener('addtrack', updateHandler);
  12749. });
  12750. return _this;
  12751. }
  12752. return TrackButton;
  12753. }(MenuButton);
  12754. Component.registerComponent('TrackButton', TrackButton);
  12755. /**
  12756. * @file menu-keys.js
  12757. */
  12758. /**
  12759. * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
  12760. * Note that 'Enter' and 'Space' are not included here (otherwise they would
  12761. * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
  12762. * @typedef MenuKeys
  12763. * @array
  12764. */
  12765. var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
  12766. /**
  12767. * The component for a menu item. `<li>`
  12768. *
  12769. * @extends ClickableComponent
  12770. */
  12771. var MenuItem =
  12772. /*#__PURE__*/
  12773. function (_ClickableComponent) {
  12774. _inheritsLoose(MenuItem, _ClickableComponent);
  12775. /**
  12776. * Creates an instance of the this class.
  12777. *
  12778. * @param {Player} player
  12779. * The `Player` that this class should be attached to.
  12780. *
  12781. * @param {Object} [options={}]
  12782. * The key/value store of player options.
  12783. *
  12784. */
  12785. function MenuItem(player, options) {
  12786. var _this;
  12787. _this = _ClickableComponent.call(this, player, options) || this;
  12788. _this.selectable = options.selectable;
  12789. _this.isSelected_ = options.selected || false;
  12790. _this.multiSelectable = options.multiSelectable;
  12791. _this.selected(_this.isSelected_);
  12792. if (_this.selectable) {
  12793. if (_this.multiSelectable) {
  12794. _this.el_.setAttribute('role', 'menuitemcheckbox');
  12795. } else {
  12796. _this.el_.setAttribute('role', 'menuitemradio');
  12797. }
  12798. } else {
  12799. _this.el_.setAttribute('role', 'menuitem');
  12800. }
  12801. return _this;
  12802. }
  12803. /**
  12804. * Create the `MenuItem's DOM element
  12805. *
  12806. * @param {string} [type=li]
  12807. * Element's node type, not actually used, always set to `li`.
  12808. *
  12809. * @param {Object} [props={}]
  12810. * An object of properties that should be set on the element
  12811. *
  12812. * @param {Object} [attrs={}]
  12813. * An object of attributes that should be set on the element
  12814. *
  12815. * @return {Element}
  12816. * The element that gets created.
  12817. */
  12818. var _proto = MenuItem.prototype;
  12819. _proto.createEl = function createEl(type, props, attrs) {
  12820. // The control is textual, not just an icon
  12821. this.nonIconControl = true;
  12822. return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
  12823. className: 'vjs-menu-item',
  12824. innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>",
  12825. tabIndex: -1
  12826. }, props), attrs);
  12827. }
  12828. /**
  12829. * Ignore keys which are used by the menu, but pass any other ones up. See
  12830. * {@link ClickableComponent#handleKeyPress} for instances where this is called.
  12831. *
  12832. * @param {EventTarget~Event} event
  12833. * The `keydown` event that caused this function to be called.
  12834. *
  12835. * @listens keydown
  12836. */
  12837. ;
  12838. _proto.handleKeyPress = function handleKeyPress(event) {
  12839. if (!MenuKeys.some(function (key) {
  12840. return keycode.isEventKey(event, key);
  12841. })) {
  12842. // Pass keypress handling up for unused keys
  12843. _ClickableComponent.prototype.handleKeyPress.call(this, event);
  12844. }
  12845. }
  12846. /**
  12847. * Any click on a `MenuItem` puts it into the selected state.
  12848. * See {@link ClickableComponent#handleClick} for instances where this is called.
  12849. *
  12850. * @param {EventTarget~Event} event
  12851. * The `keydown`, `tap`, or `click` event that caused this function to be
  12852. * called.
  12853. *
  12854. * @listens tap
  12855. * @listens click
  12856. */
  12857. ;
  12858. _proto.handleClick = function handleClick(event) {
  12859. this.selected(true);
  12860. }
  12861. /**
  12862. * Set the state for this menu item as selected or not.
  12863. *
  12864. * @param {boolean} selected
  12865. * if the menu item is selected or not
  12866. */
  12867. ;
  12868. _proto.selected = function selected(_selected) {
  12869. if (this.selectable) {
  12870. if (_selected) {
  12871. this.addClass('vjs-selected');
  12872. this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
  12873. // so indicate selected state to screen reader in the control text.
  12874. this.controlText(', selected');
  12875. this.isSelected_ = true;
  12876. } else {
  12877. this.removeClass('vjs-selected');
  12878. this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
  12879. this.controlText('');
  12880. this.isSelected_ = false;
  12881. }
  12882. }
  12883. };
  12884. return MenuItem;
  12885. }(ClickableComponent);
  12886. Component.registerComponent('MenuItem', MenuItem);
  12887. /**
  12888. * The specific menu item type for selecting a language within a text track kind
  12889. *
  12890. * @extends MenuItem
  12891. */
  12892. var TextTrackMenuItem =
  12893. /*#__PURE__*/
  12894. function (_MenuItem) {
  12895. _inheritsLoose(TextTrackMenuItem, _MenuItem);
  12896. /**
  12897. * Creates an instance of this class.
  12898. *
  12899. * @param {Player} player
  12900. * The `Player` that this class should be attached to.
  12901. *
  12902. * @param {Object} [options]
  12903. * The key/value store of player options.
  12904. */
  12905. function TextTrackMenuItem(player, options) {
  12906. var _this;
  12907. var track = options.track;
  12908. var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
  12909. options.label = track.label || track.language || 'Unknown';
  12910. options.selected = track.mode === 'showing';
  12911. _this = _MenuItem.call(this, player, options) || this;
  12912. _this.track = track;
  12913. var changeHandler = function changeHandler() {
  12914. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  12915. args[_key] = arguments[_key];
  12916. }
  12917. _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  12918. };
  12919. var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
  12920. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  12921. args[_key2] = arguments[_key2];
  12922. }
  12923. _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  12924. };
  12925. player.on(['loadstart', 'texttrackchange'], changeHandler);
  12926. tracks.addEventListener('change', changeHandler);
  12927. tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  12928. _this.on('dispose', function () {
  12929. player.off(['loadstart', 'texttrackchange'], changeHandler);
  12930. tracks.removeEventListener('change', changeHandler);
  12931. tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  12932. }); // iOS7 doesn't dispatch change events to TextTrackLists when an
  12933. // associated track's mode changes. Without something like
  12934. // Object.observe() (also not present on iOS7), it's not
  12935. // possible to detect changes to the mode attribute and polyfill
  12936. // the change event. As a poor substitute, we manually dispatch
  12937. // change events whenever the controls modify the mode.
  12938. if (tracks.onchange === undefined) {
  12939. var event;
  12940. _this.on(['tap', 'click'], function () {
  12941. if (typeof window$1.Event !== 'object') {
  12942. // Android 2.3 throws an Illegal Constructor error for window.Event
  12943. try {
  12944. event = new window$1.Event('change');
  12945. } catch (err) {// continue regardless of error
  12946. }
  12947. }
  12948. if (!event) {
  12949. event = document.createEvent('Event');
  12950. event.initEvent('change', true, true);
  12951. }
  12952. tracks.dispatchEvent(event);
  12953. });
  12954. } // set the default state based on current tracks
  12955. _this.handleTracksChange();
  12956. return _this;
  12957. }
  12958. /**
  12959. * This gets called when an `TextTrackMenuItem` is "clicked". See
  12960. * {@link ClickableComponent} for more detailed information on what a click can be.
  12961. *
  12962. * @param {EventTarget~Event} event
  12963. * The `keydown`, `tap`, or `click` event that caused this function to be
  12964. * called.
  12965. *
  12966. * @listens tap
  12967. * @listens click
  12968. */
  12969. var _proto = TextTrackMenuItem.prototype;
  12970. _proto.handleClick = function handleClick(event) {
  12971. var referenceTrack = this.track;
  12972. var tracks = this.player_.textTracks();
  12973. _MenuItem.prototype.handleClick.call(this, event);
  12974. if (!tracks) {
  12975. return;
  12976. } // Determine the relevant kind(s) of tracks for this component and filter
  12977. // out empty kinds.
  12978. var kinds = (referenceTrack.kinds || [referenceTrack.kind]).filter(Boolean);
  12979. for (var i = 0; i < tracks.length; i++) {
  12980. var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
  12981. // skip it. We do not want to affect tracks of incompatible kind(s).
  12982. if (kinds.indexOf(track.kind) === -1) {
  12983. continue;
  12984. } // If this text track is the component's track and it is not showing,
  12985. // set it to showing.
  12986. if (track === referenceTrack) {
  12987. if (track.mode !== 'showing') {
  12988. track.mode = 'showing';
  12989. } // If this text track is not the component's track and it is not
  12990. // disabled, set it to disabled.
  12991. } else if (track.mode !== 'disabled') {
  12992. track.mode = 'disabled';
  12993. }
  12994. }
  12995. }
  12996. /**
  12997. * Handle text track list change
  12998. *
  12999. * @param {EventTarget~Event} event
  13000. * The `change` event that caused this function to be called.
  13001. *
  13002. * @listens TextTrackList#change
  13003. */
  13004. ;
  13005. _proto.handleTracksChange = function handleTracksChange(event) {
  13006. var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
  13007. // screen readers to read the appended control text unnecessarily
  13008. if (shouldBeSelected !== this.isSelected_) {
  13009. this.selected(shouldBeSelected);
  13010. }
  13011. };
  13012. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  13013. if (this.track.mode === 'showing') {
  13014. var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
  13015. if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
  13016. return;
  13017. }
  13018. this.player_.cache_.selectedLanguage = {
  13019. enabled: true,
  13020. language: this.track.language,
  13021. kind: this.track.kind
  13022. };
  13023. }
  13024. };
  13025. _proto.dispose = function dispose() {
  13026. // remove reference to track object on dispose
  13027. this.track = null;
  13028. _MenuItem.prototype.dispose.call(this);
  13029. };
  13030. return TextTrackMenuItem;
  13031. }(MenuItem);
  13032. Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
  13033. /**
  13034. * A special menu item for turning of a specific type of text track
  13035. *
  13036. * @extends TextTrackMenuItem
  13037. */
  13038. var OffTextTrackMenuItem =
  13039. /*#__PURE__*/
  13040. function (_TextTrackMenuItem) {
  13041. _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
  13042. /**
  13043. * Creates an instance of this class.
  13044. *
  13045. * @param {Player} player
  13046. * The `Player` that this class should be attached to.
  13047. *
  13048. * @param {Object} [options]
  13049. * The key/value store of player options.
  13050. */
  13051. function OffTextTrackMenuItem(player, options) {
  13052. // Create pseudo track info
  13053. // Requires options['kind']
  13054. options.track = {
  13055. player: player,
  13056. kind: options.kind,
  13057. kinds: options.kinds,
  13058. default: false,
  13059. mode: 'disabled'
  13060. };
  13061. if (!options.kinds) {
  13062. options.kinds = [options.kind];
  13063. }
  13064. if (options.label) {
  13065. options.track.label = options.label;
  13066. } else {
  13067. options.track.label = options.kinds.join(' and ') + ' off';
  13068. } // MenuItem is selectable
  13069. options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  13070. options.multiSelectable = false;
  13071. return _TextTrackMenuItem.call(this, player, options) || this;
  13072. }
  13073. /**
  13074. * Handle text track change
  13075. *
  13076. * @param {EventTarget~Event} event
  13077. * The event that caused this function to run
  13078. */
  13079. var _proto = OffTextTrackMenuItem.prototype;
  13080. _proto.handleTracksChange = function handleTracksChange(event) {
  13081. var tracks = this.player().textTracks();
  13082. var shouldBeSelected = true;
  13083. for (var i = 0, l = tracks.length; i < l; i++) {
  13084. var track = tracks[i];
  13085. if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
  13086. shouldBeSelected = false;
  13087. break;
  13088. }
  13089. } // Prevent redundant selected() calls because they may cause
  13090. // screen readers to read the appended control text unnecessarily
  13091. if (shouldBeSelected !== this.isSelected_) {
  13092. this.selected(shouldBeSelected);
  13093. }
  13094. };
  13095. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  13096. var tracks = this.player().textTracks();
  13097. var allHidden = true;
  13098. for (var i = 0, l = tracks.length; i < l; i++) {
  13099. var track = tracks[i];
  13100. if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
  13101. allHidden = false;
  13102. break;
  13103. }
  13104. }
  13105. if (allHidden) {
  13106. this.player_.cache_.selectedLanguage = {
  13107. enabled: false
  13108. };
  13109. }
  13110. };
  13111. return OffTextTrackMenuItem;
  13112. }(TextTrackMenuItem);
  13113. Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
  13114. /**
  13115. * The base class for buttons that toggle specific text track types (e.g. subtitles)
  13116. *
  13117. * @extends MenuButton
  13118. */
  13119. var TextTrackButton =
  13120. /*#__PURE__*/
  13121. function (_TrackButton) {
  13122. _inheritsLoose(TextTrackButton, _TrackButton);
  13123. /**
  13124. * Creates an instance of this class.
  13125. *
  13126. * @param {Player} player
  13127. * The `Player` that this class should be attached to.
  13128. *
  13129. * @param {Object} [options={}]
  13130. * The key/value store of player options.
  13131. */
  13132. function TextTrackButton(player, options) {
  13133. if (options === void 0) {
  13134. options = {};
  13135. }
  13136. options.tracks = player.textTracks();
  13137. return _TrackButton.call(this, player, options) || this;
  13138. }
  13139. /**
  13140. * Create a menu item for each text track
  13141. *
  13142. * @param {TextTrackMenuItem[]} [items=[]]
  13143. * Existing array of items to use during creation
  13144. *
  13145. * @return {TextTrackMenuItem[]}
  13146. * Array of menu items that were created
  13147. */
  13148. var _proto = TextTrackButton.prototype;
  13149. _proto.createItems = function createItems(items, TrackMenuItem) {
  13150. if (items === void 0) {
  13151. items = [];
  13152. }
  13153. if (TrackMenuItem === void 0) {
  13154. TrackMenuItem = TextTrackMenuItem;
  13155. }
  13156. // Label is an override for the [track] off label
  13157. // USed to localise captions/subtitles
  13158. var label;
  13159. if (this.label_) {
  13160. label = this.label_ + " off";
  13161. } // Add an OFF menu item to turn all tracks off
  13162. items.push(new OffTextTrackMenuItem(this.player_, {
  13163. kinds: this.kinds_,
  13164. kind: this.kind_,
  13165. label: label
  13166. }));
  13167. this.hideThreshold_ += 1;
  13168. var tracks = this.player_.textTracks();
  13169. if (!Array.isArray(this.kinds_)) {
  13170. this.kinds_ = [this.kind_];
  13171. }
  13172. for (var i = 0; i < tracks.length; i++) {
  13173. var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
  13174. if (this.kinds_.indexOf(track.kind) > -1) {
  13175. var item = new TrackMenuItem(this.player_, {
  13176. track: track,
  13177. // MenuItem is selectable
  13178. selectable: true,
  13179. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  13180. multiSelectable: false
  13181. });
  13182. item.addClass("vjs-" + track.kind + "-menu-item");
  13183. items.push(item);
  13184. }
  13185. }
  13186. return items;
  13187. };
  13188. return TextTrackButton;
  13189. }(TrackButton);
  13190. Component.registerComponent('TextTrackButton', TextTrackButton);
  13191. /**
  13192. * The chapter track menu item
  13193. *
  13194. * @extends MenuItem
  13195. */
  13196. var ChaptersTrackMenuItem =
  13197. /*#__PURE__*/
  13198. function (_MenuItem) {
  13199. _inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
  13200. /**
  13201. * Creates an instance of this class.
  13202. *
  13203. * @param {Player} player
  13204. * The `Player` that this class should be attached to.
  13205. *
  13206. * @param {Object} [options]
  13207. * The key/value store of player options.
  13208. */
  13209. function ChaptersTrackMenuItem(player, options) {
  13210. var _this;
  13211. var track = options.track;
  13212. var cue = options.cue;
  13213. var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
  13214. options.selectable = true;
  13215. options.multiSelectable = false;
  13216. options.label = cue.text;
  13217. options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
  13218. _this = _MenuItem.call(this, player, options) || this;
  13219. _this.track = track;
  13220. _this.cue = cue;
  13221. track.addEventListener('cuechange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
  13222. return _this;
  13223. }
  13224. /**
  13225. * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
  13226. * {@link ClickableComponent} for more detailed information on what a click can be.
  13227. *
  13228. * @param {EventTarget~Event} [event]
  13229. * The `keydown`, `tap`, or `click` event that caused this function to be
  13230. * called.
  13231. *
  13232. * @listens tap
  13233. * @listens click
  13234. */
  13235. var _proto = ChaptersTrackMenuItem.prototype;
  13236. _proto.handleClick = function handleClick(event) {
  13237. _MenuItem.prototype.handleClick.call(this);
  13238. this.player_.currentTime(this.cue.startTime);
  13239. this.update(this.cue.startTime);
  13240. }
  13241. /**
  13242. * Update chapter menu item
  13243. *
  13244. * @param {EventTarget~Event} [event]
  13245. * The `cuechange` event that caused this function to run.
  13246. *
  13247. * @listens TextTrack#cuechange
  13248. */
  13249. ;
  13250. _proto.update = function update(event) {
  13251. var cue = this.cue;
  13252. var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime);
  13253. this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
  13254. };
  13255. return ChaptersTrackMenuItem;
  13256. }(MenuItem);
  13257. Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
  13258. /**
  13259. * The button component for toggling and selecting chapters
  13260. * Chapters act much differently than other text tracks
  13261. * Cues are navigation vs. other tracks of alternative languages
  13262. *
  13263. * @extends TextTrackButton
  13264. */
  13265. var ChaptersButton =
  13266. /*#__PURE__*/
  13267. function (_TextTrackButton) {
  13268. _inheritsLoose(ChaptersButton, _TextTrackButton);
  13269. /**
  13270. * Creates an instance of this class.
  13271. *
  13272. * @param {Player} player
  13273. * The `Player` that this class should be attached to.
  13274. *
  13275. * @param {Object} [options]
  13276. * The key/value store of player options.
  13277. *
  13278. * @param {Component~ReadyCallback} [ready]
  13279. * The function to call when this function is ready.
  13280. */
  13281. function ChaptersButton(player, options, ready) {
  13282. return _TextTrackButton.call(this, player, options, ready) || this;
  13283. }
  13284. /**
  13285. * Builds the default DOM `className`.
  13286. *
  13287. * @return {string}
  13288. * The DOM `className` for this object.
  13289. */
  13290. var _proto = ChaptersButton.prototype;
  13291. _proto.buildCSSClass = function buildCSSClass() {
  13292. return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13293. };
  13294. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13295. return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13296. }
  13297. /**
  13298. * Update the menu based on the current state of its items.
  13299. *
  13300. * @param {EventTarget~Event} [event]
  13301. * An event that triggered this function to run.
  13302. *
  13303. * @listens TextTrackList#addtrack
  13304. * @listens TextTrackList#removetrack
  13305. * @listens TextTrackList#change
  13306. */
  13307. ;
  13308. _proto.update = function update(event) {
  13309. if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
  13310. this.setTrack(this.findChaptersTrack());
  13311. }
  13312. _TextTrackButton.prototype.update.call(this);
  13313. }
  13314. /**
  13315. * Set the currently selected track for the chapters button.
  13316. *
  13317. * @param {TextTrack} track
  13318. * The new track to select. Nothing will change if this is the currently selected
  13319. * track.
  13320. */
  13321. ;
  13322. _proto.setTrack = function setTrack(track) {
  13323. if (this.track_ === track) {
  13324. return;
  13325. }
  13326. if (!this.updateHandler_) {
  13327. this.updateHandler_ = this.update.bind(this);
  13328. } // here this.track_ refers to the old track instance
  13329. if (this.track_) {
  13330. var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  13331. if (remoteTextTrackEl) {
  13332. remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
  13333. }
  13334. this.track_ = null;
  13335. }
  13336. this.track_ = track; // here this.track_ refers to the new track instance
  13337. if (this.track_) {
  13338. this.track_.mode = 'hidden';
  13339. var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  13340. if (_remoteTextTrackEl) {
  13341. _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
  13342. }
  13343. }
  13344. }
  13345. /**
  13346. * Find the track object that is currently in use by this ChaptersButton
  13347. *
  13348. * @return {TextTrack|undefined}
  13349. * The current track or undefined if none was found.
  13350. */
  13351. ;
  13352. _proto.findChaptersTrack = function findChaptersTrack() {
  13353. var tracks = this.player_.textTracks() || [];
  13354. for (var i = tracks.length - 1; i >= 0; i--) {
  13355. // We will always choose the last track as our chaptersTrack
  13356. var track = tracks[i];
  13357. if (track.kind === this.kind_) {
  13358. return track;
  13359. }
  13360. }
  13361. }
  13362. /**
  13363. * Get the caption for the ChaptersButton based on the track label. This will also
  13364. * use the current tracks localized kind as a fallback if a label does not exist.
  13365. *
  13366. * @return {string}
  13367. * The tracks current label or the localized track kind.
  13368. */
  13369. ;
  13370. _proto.getMenuCaption = function getMenuCaption() {
  13371. if (this.track_ && this.track_.label) {
  13372. return this.track_.label;
  13373. }
  13374. return this.localize(toTitleCase(this.kind_));
  13375. }
  13376. /**
  13377. * Create menu from chapter track
  13378. *
  13379. * @return {Menu}
  13380. * New menu for the chapter buttons
  13381. */
  13382. ;
  13383. _proto.createMenu = function createMenu() {
  13384. this.options_.title = this.getMenuCaption();
  13385. return _TextTrackButton.prototype.createMenu.call(this);
  13386. }
  13387. /**
  13388. * Create a menu item for each text track
  13389. *
  13390. * @return {TextTrackMenuItem[]}
  13391. * Array of menu items
  13392. */
  13393. ;
  13394. _proto.createItems = function createItems() {
  13395. var items = [];
  13396. if (!this.track_) {
  13397. return items;
  13398. }
  13399. var cues = this.track_.cues;
  13400. if (!cues) {
  13401. return items;
  13402. }
  13403. for (var i = 0, l = cues.length; i < l; i++) {
  13404. var cue = cues[i];
  13405. var mi = new ChaptersTrackMenuItem(this.player_, {
  13406. track: this.track_,
  13407. cue: cue
  13408. });
  13409. items.push(mi);
  13410. }
  13411. return items;
  13412. };
  13413. return ChaptersButton;
  13414. }(TextTrackButton);
  13415. /**
  13416. * `kind` of TextTrack to look for to associate it with this menu.
  13417. *
  13418. * @type {string}
  13419. * @private
  13420. */
  13421. ChaptersButton.prototype.kind_ = 'chapters';
  13422. /**
  13423. * The text that should display over the `ChaptersButton`s controls. Added for localization.
  13424. *
  13425. * @type {string}
  13426. * @private
  13427. */
  13428. ChaptersButton.prototype.controlText_ = 'Chapters';
  13429. Component.registerComponent('ChaptersButton', ChaptersButton);
  13430. /**
  13431. * The button component for toggling and selecting descriptions
  13432. *
  13433. * @extends TextTrackButton
  13434. */
  13435. var DescriptionsButton =
  13436. /*#__PURE__*/
  13437. function (_TextTrackButton) {
  13438. _inheritsLoose(DescriptionsButton, _TextTrackButton);
  13439. /**
  13440. * Creates an instance of this class.
  13441. *
  13442. * @param {Player} player
  13443. * The `Player` that this class should be attached to.
  13444. *
  13445. * @param {Object} [options]
  13446. * The key/value store of player options.
  13447. *
  13448. * @param {Component~ReadyCallback} [ready]
  13449. * The function to call when this component is ready.
  13450. */
  13451. function DescriptionsButton(player, options, ready) {
  13452. var _this;
  13453. _this = _TextTrackButton.call(this, player, options, ready) || this;
  13454. var tracks = player.textTracks();
  13455. var changeHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTracksChange);
  13456. tracks.addEventListener('change', changeHandler);
  13457. _this.on('dispose', function () {
  13458. tracks.removeEventListener('change', changeHandler);
  13459. });
  13460. return _this;
  13461. }
  13462. /**
  13463. * Handle text track change
  13464. *
  13465. * @param {EventTarget~Event} event
  13466. * The event that caused this function to run
  13467. *
  13468. * @listens TextTrackList#change
  13469. */
  13470. var _proto = DescriptionsButton.prototype;
  13471. _proto.handleTracksChange = function handleTracksChange(event) {
  13472. var tracks = this.player().textTracks();
  13473. var disabled = false; // Check whether a track of a different kind is showing
  13474. for (var i = 0, l = tracks.length; i < l; i++) {
  13475. var track = tracks[i];
  13476. if (track.kind !== this.kind_ && track.mode === 'showing') {
  13477. disabled = true;
  13478. break;
  13479. }
  13480. } // If another track is showing, disable this menu button
  13481. if (disabled) {
  13482. this.disable();
  13483. } else {
  13484. this.enable();
  13485. }
  13486. }
  13487. /**
  13488. * Builds the default DOM `className`.
  13489. *
  13490. * @return {string}
  13491. * The DOM `className` for this object.
  13492. */
  13493. ;
  13494. _proto.buildCSSClass = function buildCSSClass() {
  13495. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13496. };
  13497. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13498. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13499. };
  13500. return DescriptionsButton;
  13501. }(TextTrackButton);
  13502. /**
  13503. * `kind` of TextTrack to look for to associate it with this menu.
  13504. *
  13505. * @type {string}
  13506. * @private
  13507. */
  13508. DescriptionsButton.prototype.kind_ = 'descriptions';
  13509. /**
  13510. * The text that should display over the `DescriptionsButton`s controls. Added for localization.
  13511. *
  13512. * @type {string}
  13513. * @private
  13514. */
  13515. DescriptionsButton.prototype.controlText_ = 'Descriptions';
  13516. Component.registerComponent('DescriptionsButton', DescriptionsButton);
  13517. /**
  13518. * The button component for toggling and selecting subtitles
  13519. *
  13520. * @extends TextTrackButton
  13521. */
  13522. var SubtitlesButton =
  13523. /*#__PURE__*/
  13524. function (_TextTrackButton) {
  13525. _inheritsLoose(SubtitlesButton, _TextTrackButton);
  13526. /**
  13527. * Creates an instance of this class.
  13528. *
  13529. * @param {Player} player
  13530. * The `Player` that this class should be attached to.
  13531. *
  13532. * @param {Object} [options]
  13533. * The key/value store of player options.
  13534. *
  13535. * @param {Component~ReadyCallback} [ready]
  13536. * The function to call when this component is ready.
  13537. */
  13538. function SubtitlesButton(player, options, ready) {
  13539. return _TextTrackButton.call(this, player, options, ready) || this;
  13540. }
  13541. /**
  13542. * Builds the default DOM `className`.
  13543. *
  13544. * @return {string}
  13545. * The DOM `className` for this object.
  13546. */
  13547. var _proto = SubtitlesButton.prototype;
  13548. _proto.buildCSSClass = function buildCSSClass() {
  13549. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13550. };
  13551. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13552. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13553. };
  13554. return SubtitlesButton;
  13555. }(TextTrackButton);
  13556. /**
  13557. * `kind` of TextTrack to look for to associate it with this menu.
  13558. *
  13559. * @type {string}
  13560. * @private
  13561. */
  13562. SubtitlesButton.prototype.kind_ = 'subtitles';
  13563. /**
  13564. * The text that should display over the `SubtitlesButton`s controls. Added for localization.
  13565. *
  13566. * @type {string}
  13567. * @private
  13568. */
  13569. SubtitlesButton.prototype.controlText_ = 'Subtitles';
  13570. Component.registerComponent('SubtitlesButton', SubtitlesButton);
  13571. /**
  13572. * The menu item for caption track settings menu
  13573. *
  13574. * @extends TextTrackMenuItem
  13575. */
  13576. var CaptionSettingsMenuItem =
  13577. /*#__PURE__*/
  13578. function (_TextTrackMenuItem) {
  13579. _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
  13580. /**
  13581. * Creates an instance of this class.
  13582. *
  13583. * @param {Player} player
  13584. * The `Player` that this class should be attached to.
  13585. *
  13586. * @param {Object} [options]
  13587. * The key/value store of player options.
  13588. */
  13589. function CaptionSettingsMenuItem(player, options) {
  13590. var _this;
  13591. options.track = {
  13592. player: player,
  13593. kind: options.kind,
  13594. label: options.kind + ' settings',
  13595. selectable: false,
  13596. default: false,
  13597. mode: 'disabled'
  13598. }; // CaptionSettingsMenuItem has no concept of 'selected'
  13599. options.selectable = false;
  13600. options.name = 'CaptionSettingsMenuItem';
  13601. _this = _TextTrackMenuItem.call(this, player, options) || this;
  13602. _this.addClass('vjs-texttrack-settings');
  13603. _this.controlText(', opens ' + options.kind + ' settings dialog');
  13604. return _this;
  13605. }
  13606. /**
  13607. * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
  13608. * {@link ClickableComponent} for more detailed information on what a click can be.
  13609. *
  13610. * @param {EventTarget~Event} [event]
  13611. * The `keydown`, `tap`, or `click` event that caused this function to be
  13612. * called.
  13613. *
  13614. * @listens tap
  13615. * @listens click
  13616. */
  13617. var _proto = CaptionSettingsMenuItem.prototype;
  13618. _proto.handleClick = function handleClick(event) {
  13619. this.player().getChild('textTrackSettings').open();
  13620. };
  13621. return CaptionSettingsMenuItem;
  13622. }(TextTrackMenuItem);
  13623. Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
  13624. /**
  13625. * The button component for toggling and selecting captions
  13626. *
  13627. * @extends TextTrackButton
  13628. */
  13629. var CaptionsButton =
  13630. /*#__PURE__*/
  13631. function (_TextTrackButton) {
  13632. _inheritsLoose(CaptionsButton, _TextTrackButton);
  13633. /**
  13634. * Creates an instance of this class.
  13635. *
  13636. * @param {Player} player
  13637. * The `Player` that this class should be attached to.
  13638. *
  13639. * @param {Object} [options]
  13640. * The key/value store of player options.
  13641. *
  13642. * @param {Component~ReadyCallback} [ready]
  13643. * The function to call when this component is ready.
  13644. */
  13645. function CaptionsButton(player, options, ready) {
  13646. return _TextTrackButton.call(this, player, options, ready) || this;
  13647. }
  13648. /**
  13649. * Builds the default DOM `className`.
  13650. *
  13651. * @return {string}
  13652. * The DOM `className` for this object.
  13653. */
  13654. var _proto = CaptionsButton.prototype;
  13655. _proto.buildCSSClass = function buildCSSClass() {
  13656. return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13657. };
  13658. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13659. return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13660. }
  13661. /**
  13662. * Create caption menu items
  13663. *
  13664. * @return {CaptionSettingsMenuItem[]}
  13665. * The array of current menu items.
  13666. */
  13667. ;
  13668. _proto.createItems = function createItems() {
  13669. var items = [];
  13670. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  13671. items.push(new CaptionSettingsMenuItem(this.player_, {
  13672. kind: this.kind_
  13673. }));
  13674. this.hideThreshold_ += 1;
  13675. }
  13676. return _TextTrackButton.prototype.createItems.call(this, items);
  13677. };
  13678. return CaptionsButton;
  13679. }(TextTrackButton);
  13680. /**
  13681. * `kind` of TextTrack to look for to associate it with this menu.
  13682. *
  13683. * @type {string}
  13684. * @private
  13685. */
  13686. CaptionsButton.prototype.kind_ = 'captions';
  13687. /**
  13688. * The text that should display over the `CaptionsButton`s controls. Added for localization.
  13689. *
  13690. * @type {string}
  13691. * @private
  13692. */
  13693. CaptionsButton.prototype.controlText_ = 'Captions';
  13694. Component.registerComponent('CaptionsButton', CaptionsButton);
  13695. /**
  13696. * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
  13697. * in the SubsCapsMenu.
  13698. *
  13699. * @extends TextTrackMenuItem
  13700. */
  13701. var SubsCapsMenuItem =
  13702. /*#__PURE__*/
  13703. function (_TextTrackMenuItem) {
  13704. _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
  13705. function SubsCapsMenuItem() {
  13706. return _TextTrackMenuItem.apply(this, arguments) || this;
  13707. }
  13708. var _proto = SubsCapsMenuItem.prototype;
  13709. _proto.createEl = function createEl(type, props, attrs) {
  13710. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  13711. if (this.options_.track.kind === 'captions') {
  13712. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n ";
  13713. }
  13714. innerHTML += '</span>';
  13715. var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
  13716. innerHTML: innerHTML
  13717. }, props), attrs);
  13718. return el;
  13719. };
  13720. return SubsCapsMenuItem;
  13721. }(TextTrackMenuItem);
  13722. Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
  13723. /**
  13724. * The button component for toggling and selecting captions and/or subtitles
  13725. *
  13726. * @extends TextTrackButton
  13727. */
  13728. var SubsCapsButton =
  13729. /*#__PURE__*/
  13730. function (_TextTrackButton) {
  13731. _inheritsLoose(SubsCapsButton, _TextTrackButton);
  13732. function SubsCapsButton(player, options) {
  13733. var _this;
  13734. if (options === void 0) {
  13735. options = {};
  13736. }
  13737. _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
  13738. // "captions and subtitles" other locales use "subtitles"
  13739. _this.label_ = 'subtitles';
  13740. if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
  13741. _this.label_ = 'captions';
  13742. }
  13743. _this.menuButton_.controlText(toTitleCase(_this.label_));
  13744. return _this;
  13745. }
  13746. /**
  13747. * Builds the default DOM `className`.
  13748. *
  13749. * @return {string}
  13750. * The DOM `className` for this object.
  13751. */
  13752. var _proto = SubsCapsButton.prototype;
  13753. _proto.buildCSSClass = function buildCSSClass() {
  13754. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  13755. };
  13756. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13757. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  13758. }
  13759. /**
  13760. * Create caption/subtitles menu items
  13761. *
  13762. * @return {CaptionSettingsMenuItem[]}
  13763. * The array of current menu items.
  13764. */
  13765. ;
  13766. _proto.createItems = function createItems() {
  13767. var items = [];
  13768. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  13769. items.push(new CaptionSettingsMenuItem(this.player_, {
  13770. kind: this.label_
  13771. }));
  13772. this.hideThreshold_ += 1;
  13773. }
  13774. items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
  13775. return items;
  13776. };
  13777. return SubsCapsButton;
  13778. }(TextTrackButton);
  13779. /**
  13780. * `kind`s of TextTrack to look for to associate it with this menu.
  13781. *
  13782. * @type {array}
  13783. * @private
  13784. */
  13785. SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
  13786. /**
  13787. * The text that should display over the `SubsCapsButton`s controls.
  13788. *
  13789. *
  13790. * @type {string}
  13791. * @private
  13792. */
  13793. SubsCapsButton.prototype.controlText_ = 'Subtitles';
  13794. Component.registerComponent('SubsCapsButton', SubsCapsButton);
  13795. /**
  13796. * An {@link AudioTrack} {@link MenuItem}
  13797. *
  13798. * @extends MenuItem
  13799. */
  13800. var AudioTrackMenuItem =
  13801. /*#__PURE__*/
  13802. function (_MenuItem) {
  13803. _inheritsLoose(AudioTrackMenuItem, _MenuItem);
  13804. /**
  13805. * Creates an instance of this class.
  13806. *
  13807. * @param {Player} player
  13808. * The `Player` that this class should be attached to.
  13809. *
  13810. * @param {Object} [options]
  13811. * The key/value store of player options.
  13812. */
  13813. function AudioTrackMenuItem(player, options) {
  13814. var _this;
  13815. var track = options.track;
  13816. var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
  13817. options.label = track.label || track.language || 'Unknown';
  13818. options.selected = track.enabled;
  13819. _this = _MenuItem.call(this, player, options) || this;
  13820. _this.track = track;
  13821. _this.addClass("vjs-" + track.kind + "-menu-item");
  13822. var changeHandler = function changeHandler() {
  13823. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  13824. args[_key] = arguments[_key];
  13825. }
  13826. _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  13827. };
  13828. tracks.addEventListener('change', changeHandler);
  13829. _this.on('dispose', function () {
  13830. tracks.removeEventListener('change', changeHandler);
  13831. });
  13832. return _this;
  13833. }
  13834. var _proto = AudioTrackMenuItem.prototype;
  13835. _proto.createEl = function createEl(type, props, attrs) {
  13836. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  13837. if (this.options_.track.kind === 'main-desc') {
  13838. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n ";
  13839. }
  13840. innerHTML += '</span>';
  13841. var el = _MenuItem.prototype.createEl.call(this, type, assign({
  13842. innerHTML: innerHTML
  13843. }, props), attrs);
  13844. return el;
  13845. }
  13846. /**
  13847. * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
  13848. * for more detailed information on what a click can be.
  13849. *
  13850. * @param {EventTarget~Event} [event]
  13851. * The `keydown`, `tap`, or `click` event that caused this function to be
  13852. * called.
  13853. *
  13854. * @listens tap
  13855. * @listens click
  13856. */
  13857. ;
  13858. _proto.handleClick = function handleClick(event) {
  13859. var tracks = this.player_.audioTracks();
  13860. _MenuItem.prototype.handleClick.call(this, event);
  13861. for (var i = 0; i < tracks.length; i++) {
  13862. var track = tracks[i];
  13863. track.enabled = track === this.track;
  13864. }
  13865. }
  13866. /**
  13867. * Handle any {@link AudioTrack} change.
  13868. *
  13869. * @param {EventTarget~Event} [event]
  13870. * The {@link AudioTrackList#change} event that caused this to run.
  13871. *
  13872. * @listens AudioTrackList#change
  13873. */
  13874. ;
  13875. _proto.handleTracksChange = function handleTracksChange(event) {
  13876. this.selected(this.track.enabled);
  13877. };
  13878. return AudioTrackMenuItem;
  13879. }(MenuItem);
  13880. Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
  13881. /**
  13882. * The base class for buttons that toggle specific {@link AudioTrack} types.
  13883. *
  13884. * @extends TrackButton
  13885. */
  13886. var AudioTrackButton =
  13887. /*#__PURE__*/
  13888. function (_TrackButton) {
  13889. _inheritsLoose(AudioTrackButton, _TrackButton);
  13890. /**
  13891. * Creates an instance of this class.
  13892. *
  13893. * @param {Player} player
  13894. * The `Player` that this class should be attached to.
  13895. *
  13896. * @param {Object} [options={}]
  13897. * The key/value store of player options.
  13898. */
  13899. function AudioTrackButton(player, options) {
  13900. if (options === void 0) {
  13901. options = {};
  13902. }
  13903. options.tracks = player.audioTracks();
  13904. return _TrackButton.call(this, player, options) || this;
  13905. }
  13906. /**
  13907. * Builds the default DOM `className`.
  13908. *
  13909. * @return {string}
  13910. * The DOM `className` for this object.
  13911. */
  13912. var _proto = AudioTrackButton.prototype;
  13913. _proto.buildCSSClass = function buildCSSClass() {
  13914. return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
  13915. };
  13916. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13917. return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
  13918. }
  13919. /**
  13920. * Create a menu item for each audio track
  13921. *
  13922. * @param {AudioTrackMenuItem[]} [items=[]]
  13923. * An array of existing menu items to use.
  13924. *
  13925. * @return {AudioTrackMenuItem[]}
  13926. * An array of menu items
  13927. */
  13928. ;
  13929. _proto.createItems = function createItems(items) {
  13930. if (items === void 0) {
  13931. items = [];
  13932. }
  13933. // if there's only one audio track, there no point in showing it
  13934. this.hideThreshold_ = 1;
  13935. var tracks = this.player_.audioTracks();
  13936. for (var i = 0; i < tracks.length; i++) {
  13937. var track = tracks[i];
  13938. items.push(new AudioTrackMenuItem(this.player_, {
  13939. track: track,
  13940. // MenuItem is selectable
  13941. selectable: true,
  13942. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  13943. multiSelectable: false
  13944. }));
  13945. }
  13946. return items;
  13947. };
  13948. return AudioTrackButton;
  13949. }(TrackButton);
  13950. /**
  13951. * The text that should display over the `AudioTrackButton`s controls. Added for localization.
  13952. *
  13953. * @type {string}
  13954. * @private
  13955. */
  13956. AudioTrackButton.prototype.controlText_ = 'Audio Track';
  13957. Component.registerComponent('AudioTrackButton', AudioTrackButton);
  13958. /**
  13959. * The specific menu item type for selecting a playback rate.
  13960. *
  13961. * @extends MenuItem
  13962. */
  13963. var PlaybackRateMenuItem =
  13964. /*#__PURE__*/
  13965. function (_MenuItem) {
  13966. _inheritsLoose(PlaybackRateMenuItem, _MenuItem);
  13967. /**
  13968. * Creates an instance of this class.
  13969. *
  13970. * @param {Player} player
  13971. * The `Player` that this class should be attached to.
  13972. *
  13973. * @param {Object} [options]
  13974. * The key/value store of player options.
  13975. */
  13976. function PlaybackRateMenuItem(player, options) {
  13977. var _this;
  13978. var label = options.rate;
  13979. var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
  13980. options.label = label;
  13981. options.selected = rate === 1;
  13982. options.selectable = true;
  13983. options.multiSelectable = false;
  13984. _this = _MenuItem.call(this, player, options) || this;
  13985. _this.label = label;
  13986. _this.rate = rate;
  13987. _this.on(player, 'ratechange', _this.update);
  13988. return _this;
  13989. }
  13990. /**
  13991. * This gets called when an `PlaybackRateMenuItem` is "clicked". See
  13992. * {@link ClickableComponent} for more detailed information on what a click can be.
  13993. *
  13994. * @param {EventTarget~Event} [event]
  13995. * The `keydown`, `tap`, or `click` event that caused this function to be
  13996. * called.
  13997. *
  13998. * @listens tap
  13999. * @listens click
  14000. */
  14001. var _proto = PlaybackRateMenuItem.prototype;
  14002. _proto.handleClick = function handleClick(event) {
  14003. _MenuItem.prototype.handleClick.call(this);
  14004. this.player().playbackRate(this.rate);
  14005. }
  14006. /**
  14007. * Update the PlaybackRateMenuItem when the playbackrate changes.
  14008. *
  14009. * @param {EventTarget~Event} [event]
  14010. * The `ratechange` event that caused this function to run.
  14011. *
  14012. * @listens Player#ratechange
  14013. */
  14014. ;
  14015. _proto.update = function update(event) {
  14016. this.selected(this.player().playbackRate() === this.rate);
  14017. };
  14018. return PlaybackRateMenuItem;
  14019. }(MenuItem);
  14020. /**
  14021. * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
  14022. *
  14023. * @type {string}
  14024. * @private
  14025. */
  14026. PlaybackRateMenuItem.prototype.contentElType = 'button';
  14027. Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
  14028. /**
  14029. * The component for controlling the playback rate.
  14030. *
  14031. * @extends MenuButton
  14032. */
  14033. var PlaybackRateMenuButton =
  14034. /*#__PURE__*/
  14035. function (_MenuButton) {
  14036. _inheritsLoose(PlaybackRateMenuButton, _MenuButton);
  14037. /**
  14038. * Creates an instance of this class.
  14039. *
  14040. * @param {Player} player
  14041. * The `Player` that this class should be attached to.
  14042. *
  14043. * @param {Object} [options]
  14044. * The key/value store of player options.
  14045. */
  14046. function PlaybackRateMenuButton(player, options) {
  14047. var _this;
  14048. _this = _MenuButton.call(this, player, options) || this;
  14049. _this.updateVisibility();
  14050. _this.updateLabel();
  14051. _this.on(player, 'loadstart', _this.updateVisibility);
  14052. _this.on(player, 'ratechange', _this.updateLabel);
  14053. return _this;
  14054. }
  14055. /**
  14056. * Create the `Component`'s DOM element
  14057. *
  14058. * @return {Element}
  14059. * The element that was created.
  14060. */
  14061. var _proto = PlaybackRateMenuButton.prototype;
  14062. _proto.createEl = function createEl$$1() {
  14063. var el = _MenuButton.prototype.createEl.call(this);
  14064. this.labelEl_ = createEl('div', {
  14065. className: 'vjs-playback-rate-value',
  14066. innerHTML: '1x'
  14067. });
  14068. el.appendChild(this.labelEl_);
  14069. return el;
  14070. };
  14071. _proto.dispose = function dispose() {
  14072. this.labelEl_ = null;
  14073. _MenuButton.prototype.dispose.call(this);
  14074. }
  14075. /**
  14076. * Builds the default DOM `className`.
  14077. *
  14078. * @return {string}
  14079. * The DOM `className` for this object.
  14080. */
  14081. ;
  14082. _proto.buildCSSClass = function buildCSSClass() {
  14083. return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
  14084. };
  14085. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  14086. return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
  14087. }
  14088. /**
  14089. * Create the playback rate menu
  14090. *
  14091. * @return {Menu}
  14092. * Menu object populated with {@link PlaybackRateMenuItem}s
  14093. */
  14094. ;
  14095. _proto.createMenu = function createMenu() {
  14096. var menu = new Menu(this.player());
  14097. var rates = this.playbackRates();
  14098. if (rates) {
  14099. for (var i = rates.length - 1; i >= 0; i--) {
  14100. menu.addChild(new PlaybackRateMenuItem(this.player(), {
  14101. rate: rates[i] + 'x'
  14102. }));
  14103. }
  14104. }
  14105. return menu;
  14106. }
  14107. /**
  14108. * Updates ARIA accessibility attributes
  14109. */
  14110. ;
  14111. _proto.updateARIAAttributes = function updateARIAAttributes() {
  14112. // Current playback rate
  14113. this.el().setAttribute('aria-valuenow', this.player().playbackRate());
  14114. }
  14115. /**
  14116. * This gets called when an `PlaybackRateMenuButton` is "clicked". See
  14117. * {@link ClickableComponent} for more detailed information on what a click can be.
  14118. *
  14119. * @param {EventTarget~Event} [event]
  14120. * The `keydown`, `tap`, or `click` event that caused this function to be
  14121. * called.
  14122. *
  14123. * @listens tap
  14124. * @listens click
  14125. */
  14126. ;
  14127. _proto.handleClick = function handleClick(event) {
  14128. // select next rate option
  14129. var currentRate = this.player().playbackRate();
  14130. var rates = this.playbackRates(); // this will select first one if the last one currently selected
  14131. var newRate = rates[0];
  14132. for (var i = 0; i < rates.length; i++) {
  14133. if (rates[i] > currentRate) {
  14134. newRate = rates[i];
  14135. break;
  14136. }
  14137. }
  14138. this.player().playbackRate(newRate);
  14139. }
  14140. /**
  14141. * Get possible playback rates
  14142. *
  14143. * @return {Array}
  14144. * All possible playback rates
  14145. */
  14146. ;
  14147. _proto.playbackRates = function playbackRates() {
  14148. return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
  14149. }
  14150. /**
  14151. * Get whether playback rates is supported by the tech
  14152. * and an array of playback rates exists
  14153. *
  14154. * @return {boolean}
  14155. * Whether changing playback rate is supported
  14156. */
  14157. ;
  14158. _proto.playbackRateSupported = function playbackRateSupported() {
  14159. return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
  14160. }
  14161. /**
  14162. * Hide playback rate controls when they're no playback rate options to select
  14163. *
  14164. * @param {EventTarget~Event} [event]
  14165. * The event that caused this function to run.
  14166. *
  14167. * @listens Player#loadstart
  14168. */
  14169. ;
  14170. _proto.updateVisibility = function updateVisibility(event) {
  14171. if (this.playbackRateSupported()) {
  14172. this.removeClass('vjs-hidden');
  14173. } else {
  14174. this.addClass('vjs-hidden');
  14175. }
  14176. }
  14177. /**
  14178. * Update button label when rate changed
  14179. *
  14180. * @param {EventTarget~Event} [event]
  14181. * The event that caused this function to run.
  14182. *
  14183. * @listens Player#ratechange
  14184. */
  14185. ;
  14186. _proto.updateLabel = function updateLabel(event) {
  14187. if (this.playbackRateSupported()) {
  14188. this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
  14189. }
  14190. };
  14191. return PlaybackRateMenuButton;
  14192. }(MenuButton);
  14193. /**
  14194. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  14195. *
  14196. * @type {string}
  14197. * @private
  14198. */
  14199. PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
  14200. Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
  14201. /**
  14202. * Just an empty spacer element that can be used as an append point for plugins, etc.
  14203. * Also can be used to create space between elements when necessary.
  14204. *
  14205. * @extends Component
  14206. */
  14207. var Spacer =
  14208. /*#__PURE__*/
  14209. function (_Component) {
  14210. _inheritsLoose(Spacer, _Component);
  14211. function Spacer() {
  14212. return _Component.apply(this, arguments) || this;
  14213. }
  14214. var _proto = Spacer.prototype;
  14215. /**
  14216. * Builds the default DOM `className`.
  14217. *
  14218. * @return {string}
  14219. * The DOM `className` for this object.
  14220. */
  14221. _proto.buildCSSClass = function buildCSSClass() {
  14222. return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
  14223. }
  14224. /**
  14225. * Create the `Component`'s DOM element
  14226. *
  14227. * @return {Element}
  14228. * The element that was created.
  14229. */
  14230. ;
  14231. _proto.createEl = function createEl() {
  14232. return _Component.prototype.createEl.call(this, 'div', {
  14233. className: this.buildCSSClass()
  14234. });
  14235. };
  14236. return Spacer;
  14237. }(Component);
  14238. Component.registerComponent('Spacer', Spacer);
  14239. /**
  14240. * Spacer specifically meant to be used as an insertion point for new plugins, etc.
  14241. *
  14242. * @extends Spacer
  14243. */
  14244. var CustomControlSpacer =
  14245. /*#__PURE__*/
  14246. function (_Spacer) {
  14247. _inheritsLoose(CustomControlSpacer, _Spacer);
  14248. function CustomControlSpacer() {
  14249. return _Spacer.apply(this, arguments) || this;
  14250. }
  14251. var _proto = CustomControlSpacer.prototype;
  14252. /**
  14253. * Builds the default DOM `className`.
  14254. *
  14255. * @return {string}
  14256. * The DOM `className` for this object.
  14257. */
  14258. _proto.buildCSSClass = function buildCSSClass() {
  14259. return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
  14260. }
  14261. /**
  14262. * Create the `Component`'s DOM element
  14263. *
  14264. * @return {Element}
  14265. * The element that was created.
  14266. */
  14267. ;
  14268. _proto.createEl = function createEl() {
  14269. var el = _Spacer.prototype.createEl.call(this, {
  14270. className: this.buildCSSClass()
  14271. }); // No-flex/table-cell mode requires there be some content
  14272. // in the cell to fill the remaining space of the table.
  14273. el.innerHTML = "\xA0";
  14274. return el;
  14275. };
  14276. return CustomControlSpacer;
  14277. }(Spacer);
  14278. Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
  14279. /**
  14280. * Container of main controls.
  14281. *
  14282. * @extends Component
  14283. */
  14284. var ControlBar =
  14285. /*#__PURE__*/
  14286. function (_Component) {
  14287. _inheritsLoose(ControlBar, _Component);
  14288. function ControlBar() {
  14289. return _Component.apply(this, arguments) || this;
  14290. }
  14291. var _proto = ControlBar.prototype;
  14292. /**
  14293. * Create the `Component`'s DOM element
  14294. *
  14295. * @return {Element}
  14296. * The element that was created.
  14297. */
  14298. _proto.createEl = function createEl() {
  14299. return _Component.prototype.createEl.call(this, 'div', {
  14300. className: 'vjs-control-bar',
  14301. dir: 'ltr'
  14302. });
  14303. };
  14304. return ControlBar;
  14305. }(Component);
  14306. /**
  14307. * Default options for `ControlBar`
  14308. *
  14309. * @type {Object}
  14310. * @private
  14311. */
  14312. ControlBar.prototype.options_ = {
  14313. children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
  14314. };
  14315. Component.registerComponent('ControlBar', ControlBar);
  14316. /**
  14317. * A display that indicates an error has occurred. This means that the video
  14318. * is unplayable.
  14319. *
  14320. * @extends ModalDialog
  14321. */
  14322. var ErrorDisplay =
  14323. /*#__PURE__*/
  14324. function (_ModalDialog) {
  14325. _inheritsLoose(ErrorDisplay, _ModalDialog);
  14326. /**
  14327. * Creates an instance of this class.
  14328. *
  14329. * @param {Player} player
  14330. * The `Player` that this class should be attached to.
  14331. *
  14332. * @param {Object} [options]
  14333. * The key/value store of player options.
  14334. */
  14335. function ErrorDisplay(player, options) {
  14336. var _this;
  14337. _this = _ModalDialog.call(this, player, options) || this;
  14338. _this.on(player, 'error', _this.open);
  14339. return _this;
  14340. }
  14341. /**
  14342. * Builds the default DOM `className`.
  14343. *
  14344. * @return {string}
  14345. * The DOM `className` for this object.
  14346. *
  14347. * @deprecated Since version 5.
  14348. */
  14349. var _proto = ErrorDisplay.prototype;
  14350. _proto.buildCSSClass = function buildCSSClass() {
  14351. return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
  14352. }
  14353. /**
  14354. * Gets the localized error message based on the `Player`s error.
  14355. *
  14356. * @return {string}
  14357. * The `Player`s error message localized or an empty string.
  14358. */
  14359. ;
  14360. _proto.content = function content() {
  14361. var error = this.player().error();
  14362. return error ? this.localize(error.message) : '';
  14363. };
  14364. return ErrorDisplay;
  14365. }(ModalDialog);
  14366. /**
  14367. * The default options for an `ErrorDisplay`.
  14368. *
  14369. * @private
  14370. */
  14371. ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
  14372. pauseOnOpen: false,
  14373. fillAlways: true,
  14374. temporary: false,
  14375. uncloseable: true
  14376. });
  14377. Component.registerComponent('ErrorDisplay', ErrorDisplay);
  14378. var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
  14379. var COLOR_BLACK = ['#000', 'Black'];
  14380. var COLOR_BLUE = ['#00F', 'Blue'];
  14381. var COLOR_CYAN = ['#0FF', 'Cyan'];
  14382. var COLOR_GREEN = ['#0F0', 'Green'];
  14383. var COLOR_MAGENTA = ['#F0F', 'Magenta'];
  14384. var COLOR_RED = ['#F00', 'Red'];
  14385. var COLOR_WHITE = ['#FFF', 'White'];
  14386. var COLOR_YELLOW = ['#FF0', 'Yellow'];
  14387. var OPACITY_OPAQUE = ['1', 'Opaque'];
  14388. var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
  14389. var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
  14390. //
  14391. // Possible keys include:
  14392. //
  14393. // `default`:
  14394. // The default option index. Only needs to be provided if not zero.
  14395. // `parser`:
  14396. // A function which is used to parse the value from the selected option in
  14397. // a customized way.
  14398. // `selector`:
  14399. // The selector used to find the associated <select> element.
  14400. var selectConfigs = {
  14401. backgroundColor: {
  14402. selector: '.vjs-bg-color > select',
  14403. id: 'captions-background-color-%s',
  14404. label: 'Color',
  14405. options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  14406. },
  14407. backgroundOpacity: {
  14408. selector: '.vjs-bg-opacity > select',
  14409. id: 'captions-background-opacity-%s',
  14410. label: 'Transparency',
  14411. options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
  14412. },
  14413. color: {
  14414. selector: '.vjs-fg-color > select',
  14415. id: 'captions-foreground-color-%s',
  14416. label: 'Color',
  14417. options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  14418. },
  14419. edgeStyle: {
  14420. selector: '.vjs-edge-style > select',
  14421. id: '%s',
  14422. label: 'Text Edge Style',
  14423. options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
  14424. },
  14425. fontFamily: {
  14426. selector: '.vjs-font-family > select',
  14427. id: 'captions-font-family-%s',
  14428. label: 'Font Family',
  14429. options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
  14430. },
  14431. fontPercent: {
  14432. selector: '.vjs-font-percent > select',
  14433. id: 'captions-font-size-%s',
  14434. label: 'Font Size',
  14435. 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%']],
  14436. default: 2,
  14437. parser: function parser(v) {
  14438. return v === '1.00' ? null : Number(v);
  14439. }
  14440. },
  14441. textOpacity: {
  14442. selector: '.vjs-text-opacity > select',
  14443. id: 'captions-foreground-opacity-%s',
  14444. label: 'Transparency',
  14445. options: [OPACITY_OPAQUE, OPACITY_SEMI]
  14446. },
  14447. // Options for this object are defined below.
  14448. windowColor: {
  14449. selector: '.vjs-window-color > select',
  14450. id: 'captions-window-color-%s',
  14451. label: 'Color'
  14452. },
  14453. // Options for this object are defined below.
  14454. windowOpacity: {
  14455. selector: '.vjs-window-opacity > select',
  14456. id: 'captions-window-opacity-%s',
  14457. label: 'Transparency',
  14458. options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
  14459. }
  14460. };
  14461. selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
  14462. /**
  14463. * Get the actual value of an option.
  14464. *
  14465. * @param {string} value
  14466. * The value to get
  14467. *
  14468. * @param {Function} [parser]
  14469. * Optional function to adjust the value.
  14470. *
  14471. * @return {Mixed}
  14472. * - Will be `undefined` if no value exists
  14473. * - Will be `undefined` if the given value is "none".
  14474. * - Will be the actual value otherwise.
  14475. *
  14476. * @private
  14477. */
  14478. function parseOptionValue(value, parser) {
  14479. if (parser) {
  14480. value = parser(value);
  14481. }
  14482. if (value && value !== 'none') {
  14483. return value;
  14484. }
  14485. }
  14486. /**
  14487. * Gets the value of the selected <option> element within a <select> element.
  14488. *
  14489. * @param {Element} el
  14490. * the element to look in
  14491. *
  14492. * @param {Function} [parser]
  14493. * Optional function to adjust the value.
  14494. *
  14495. * @return {Mixed}
  14496. * - Will be `undefined` if no value exists
  14497. * - Will be `undefined` if the given value is "none".
  14498. * - Will be the actual value otherwise.
  14499. *
  14500. * @private
  14501. */
  14502. function getSelectedOptionValue(el, parser) {
  14503. var value = el.options[el.options.selectedIndex].value;
  14504. return parseOptionValue(value, parser);
  14505. }
  14506. /**
  14507. * Sets the selected <option> element within a <select> element based on a
  14508. * given value.
  14509. *
  14510. * @param {Element} el
  14511. * The element to look in.
  14512. *
  14513. * @param {string} value
  14514. * the property to look on.
  14515. *
  14516. * @param {Function} [parser]
  14517. * Optional function to adjust the value before comparing.
  14518. *
  14519. * @private
  14520. */
  14521. function setSelectedOption(el, value, parser) {
  14522. if (!value) {
  14523. return;
  14524. }
  14525. for (var i = 0; i < el.options.length; i++) {
  14526. if (parseOptionValue(el.options[i].value, parser) === value) {
  14527. el.selectedIndex = i;
  14528. break;
  14529. }
  14530. }
  14531. }
  14532. /**
  14533. * Manipulate Text Tracks settings.
  14534. *
  14535. * @extends ModalDialog
  14536. */
  14537. var TextTrackSettings =
  14538. /*#__PURE__*/
  14539. function (_ModalDialog) {
  14540. _inheritsLoose(TextTrackSettings, _ModalDialog);
  14541. /**
  14542. * Creates an instance of this class.
  14543. *
  14544. * @param {Player} player
  14545. * The `Player` that this class should be attached to.
  14546. *
  14547. * @param {Object} [options]
  14548. * The key/value store of player options.
  14549. */
  14550. function TextTrackSettings(player, options) {
  14551. var _this;
  14552. options.temporary = false;
  14553. _this = _ModalDialog.call(this, player, options) || this;
  14554. _this.updateDisplay = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay); // fill the modal and pretend we have opened it
  14555. _this.fill();
  14556. _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
  14557. _this.endDialog = createEl('p', {
  14558. className: 'vjs-control-text',
  14559. textContent: _this.localize('End of dialog window.')
  14560. });
  14561. _this.el().appendChild(_this.endDialog);
  14562. _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
  14563. if (options.persistTextTrackSettings === undefined) {
  14564. _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
  14565. }
  14566. _this.on(_this.$('.vjs-done-button'), 'click', function () {
  14567. _this.saveSettings();
  14568. _this.close();
  14569. });
  14570. _this.on(_this.$('.vjs-default-button'), 'click', function () {
  14571. _this.setDefaults();
  14572. _this.updateDisplay();
  14573. });
  14574. each(selectConfigs, function (config) {
  14575. _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
  14576. });
  14577. if (_this.options_.persistTextTrackSettings) {
  14578. _this.restoreSettings();
  14579. }
  14580. return _this;
  14581. }
  14582. var _proto = TextTrackSettings.prototype;
  14583. _proto.dispose = function dispose() {
  14584. this.endDialog = null;
  14585. _ModalDialog.prototype.dispose.call(this);
  14586. }
  14587. /**
  14588. * Create a <select> element with configured options.
  14589. *
  14590. * @param {string} key
  14591. * Configuration key to use during creation.
  14592. *
  14593. * @return {string}
  14594. * An HTML string.
  14595. *
  14596. * @private
  14597. */
  14598. ;
  14599. _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
  14600. var _this2 = this;
  14601. if (legendId === void 0) {
  14602. legendId = '';
  14603. }
  14604. if (type === void 0) {
  14605. type = 'label';
  14606. }
  14607. var config = selectConfigs[key];
  14608. var id = config.id.replace('%s', this.id_);
  14609. var selectLabelledbyIds = [legendId, id].join(' ').trim();
  14610. return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
  14611. var optionId = id + '-' + o[1].replace(/\W+/g, '');
  14612. return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
  14613. })).concat('</select>').join('');
  14614. }
  14615. /**
  14616. * Create foreground color element for the component
  14617. *
  14618. * @return {string}
  14619. * An HTML string.
  14620. *
  14621. * @private
  14622. */
  14623. ;
  14624. _proto.createElFgColor_ = function createElFgColor_() {
  14625. var legendId = "captions-text-legend-" + this.id_;
  14626. 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('');
  14627. }
  14628. /**
  14629. * Create background color element for the component
  14630. *
  14631. * @return {string}
  14632. * An HTML string.
  14633. *
  14634. * @private
  14635. */
  14636. ;
  14637. _proto.createElBgColor_ = function createElBgColor_() {
  14638. var legendId = "captions-background-" + this.id_;
  14639. 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('');
  14640. }
  14641. /**
  14642. * Create window color element for the component
  14643. *
  14644. * @return {string}
  14645. * An HTML string.
  14646. *
  14647. * @private
  14648. */
  14649. ;
  14650. _proto.createElWinColor_ = function createElWinColor_() {
  14651. var legendId = "captions-window-" + this.id_;
  14652. 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('');
  14653. }
  14654. /**
  14655. * Create color elements for the component
  14656. *
  14657. * @return {Element}
  14658. * The element that was created
  14659. *
  14660. * @private
  14661. */
  14662. ;
  14663. _proto.createElColors_ = function createElColors_() {
  14664. return createEl('div', {
  14665. className: 'vjs-track-settings-colors',
  14666. innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
  14667. });
  14668. }
  14669. /**
  14670. * Create font elements for the component
  14671. *
  14672. * @return {Element}
  14673. * The element that was created.
  14674. *
  14675. * @private
  14676. */
  14677. ;
  14678. _proto.createElFont_ = function createElFont_() {
  14679. return createEl('div', {
  14680. className: 'vjs-track-settings-font',
  14681. 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('')
  14682. });
  14683. }
  14684. /**
  14685. * Create controls for the component
  14686. *
  14687. * @return {Element}
  14688. * The element that was created.
  14689. *
  14690. * @private
  14691. */
  14692. ;
  14693. _proto.createElControls_ = function createElControls_() {
  14694. var defaultsDescription = this.localize('restore all settings to the default values');
  14695. return createEl('div', {
  14696. className: 'vjs-track-settings-controls',
  14697. 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('')
  14698. });
  14699. };
  14700. _proto.content = function content() {
  14701. return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
  14702. };
  14703. _proto.label = function label() {
  14704. return this.localize('Caption Settings Dialog');
  14705. };
  14706. _proto.description = function description() {
  14707. return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
  14708. };
  14709. _proto.buildCSSClass = function buildCSSClass() {
  14710. return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
  14711. }
  14712. /**
  14713. * Gets an object of text track settings (or null).
  14714. *
  14715. * @return {Object}
  14716. * An object with config values parsed from the DOM or localStorage.
  14717. */
  14718. ;
  14719. _proto.getValues = function getValues() {
  14720. var _this3 = this;
  14721. return reduce(selectConfigs, function (accum, config, key) {
  14722. var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
  14723. if (value !== undefined) {
  14724. accum[key] = value;
  14725. }
  14726. return accum;
  14727. }, {});
  14728. }
  14729. /**
  14730. * Sets text track settings from an object of values.
  14731. *
  14732. * @param {Object} values
  14733. * An object with config values parsed from the DOM or localStorage.
  14734. */
  14735. ;
  14736. _proto.setValues = function setValues(values) {
  14737. var _this4 = this;
  14738. each(selectConfigs, function (config, key) {
  14739. setSelectedOption(_this4.$(config.selector), values[key], config.parser);
  14740. });
  14741. }
  14742. /**
  14743. * Sets all `<select>` elements to their default values.
  14744. */
  14745. ;
  14746. _proto.setDefaults = function setDefaults() {
  14747. var _this5 = this;
  14748. each(selectConfigs, function (config) {
  14749. var index = config.hasOwnProperty('default') ? config.default : 0;
  14750. _this5.$(config.selector).selectedIndex = index;
  14751. });
  14752. }
  14753. /**
  14754. * Restore texttrack settings from localStorage
  14755. */
  14756. ;
  14757. _proto.restoreSettings = function restoreSettings() {
  14758. var values;
  14759. try {
  14760. values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY));
  14761. } catch (err) {
  14762. log.warn(err);
  14763. }
  14764. if (values) {
  14765. this.setValues(values);
  14766. }
  14767. }
  14768. /**
  14769. * Save text track settings to localStorage
  14770. */
  14771. ;
  14772. _proto.saveSettings = function saveSettings() {
  14773. if (!this.options_.persistTextTrackSettings) {
  14774. return;
  14775. }
  14776. var values = this.getValues();
  14777. try {
  14778. if (Object.keys(values).length) {
  14779. window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
  14780. } else {
  14781. window$1.localStorage.removeItem(LOCAL_STORAGE_KEY);
  14782. }
  14783. } catch (err) {
  14784. log.warn(err);
  14785. }
  14786. }
  14787. /**
  14788. * Update display of text track settings
  14789. */
  14790. ;
  14791. _proto.updateDisplay = function updateDisplay() {
  14792. var ttDisplay = this.player_.getChild('textTrackDisplay');
  14793. if (ttDisplay) {
  14794. ttDisplay.updateDisplay();
  14795. }
  14796. }
  14797. /**
  14798. * conditionally blur the element and refocus the captions button
  14799. *
  14800. * @private
  14801. */
  14802. ;
  14803. _proto.conditionalBlur_ = function conditionalBlur_() {
  14804. this.previouslyActiveEl_ = null;
  14805. this.off(document, 'keydown', this.handleKeyDown);
  14806. var cb = this.player_.controlBar;
  14807. var subsCapsBtn = cb && cb.subsCapsButton;
  14808. var ccBtn = cb && cb.captionsButton;
  14809. if (subsCapsBtn) {
  14810. subsCapsBtn.focus();
  14811. } else if (ccBtn) {
  14812. ccBtn.focus();
  14813. }
  14814. };
  14815. return TextTrackSettings;
  14816. }(ModalDialog);
  14817. Component.registerComponent('TextTrackSettings', TextTrackSettings);
  14818. /**
  14819. * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
  14820. *
  14821. * 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}.
  14822. *
  14823. * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
  14824. * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
  14825. * @example <caption>How to disable the resize manager</caption>
  14826. * const player = videojs('#vid', {
  14827. * resizeManager: false
  14828. * });
  14829. *
  14830. * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
  14831. *
  14832. * @extends Component
  14833. */
  14834. var ResizeManager =
  14835. /*#__PURE__*/
  14836. function (_Component) {
  14837. _inheritsLoose(ResizeManager, _Component);
  14838. /**
  14839. * Create the ResizeManager.
  14840. *
  14841. * @param {Object} player
  14842. * The `Player` that this class should be attached to.
  14843. *
  14844. * @param {Object} [options]
  14845. * The key/value store of ResizeManager options.
  14846. *
  14847. * @param {Object} [options.ResizeObserver]
  14848. * A polyfill for ResizeObserver can be passed in here.
  14849. * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
  14850. */
  14851. function ResizeManager(player, options) {
  14852. var _this;
  14853. var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
  14854. if (options.ResizeObserver === null) {
  14855. RESIZE_OBSERVER_AVAILABLE = false;
  14856. } // Only create an element when ResizeObserver isn't available
  14857. var options_ = mergeOptions({
  14858. createEl: !RESIZE_OBSERVER_AVAILABLE,
  14859. reportTouchActivity: false
  14860. }, options);
  14861. _this = _Component.call(this, player, options_) || this;
  14862. _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
  14863. _this.loadListener_ = null;
  14864. _this.resizeObserver_ = null;
  14865. _this.debouncedHandler_ = debounce(function () {
  14866. _this.resizeHandler();
  14867. }, 100, false, _assertThisInitialized(_assertThisInitialized(_this)));
  14868. if (RESIZE_OBSERVER_AVAILABLE) {
  14869. _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
  14870. _this.resizeObserver_.observe(player.el());
  14871. } else {
  14872. _this.loadListener_ = function () {
  14873. if (!_this.el_ || !_this.el_.contentWindow) {
  14874. return;
  14875. }
  14876. var debouncedHandler_ = _this.debouncedHandler_;
  14877. var unloadListener_ = _this.unloadListener_ = function () {
  14878. off(this, 'resize', debouncedHandler_);
  14879. off(this, 'unload', unloadListener_);
  14880. unloadListener_ = null;
  14881. }; // safari and edge can unload the iframe before resizemanager dispose
  14882. // we have to dispose of event handlers correctly before that happens
  14883. on(_this.el_.contentWindow, 'unload', unloadListener_);
  14884. on(_this.el_.contentWindow, 'resize', debouncedHandler_);
  14885. };
  14886. _this.one('load', _this.loadListener_);
  14887. }
  14888. return _this;
  14889. }
  14890. var _proto = ResizeManager.prototype;
  14891. _proto.createEl = function createEl() {
  14892. return _Component.prototype.createEl.call(this, 'iframe', {
  14893. className: 'vjs-resize-manager',
  14894. tabIndex: -1
  14895. }, {
  14896. 'aria-hidden': 'true'
  14897. });
  14898. }
  14899. /**
  14900. * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
  14901. *
  14902. * @fires Player#playerresize
  14903. */
  14904. ;
  14905. _proto.resizeHandler = function resizeHandler() {
  14906. /**
  14907. * Called when the player size has changed
  14908. *
  14909. * @event Player#playerresize
  14910. * @type {EventTarget~Event}
  14911. */
  14912. // make sure player is still around to trigger
  14913. // prevents this from causing an error after dispose
  14914. if (!this.player_ || !this.player_.trigger) {
  14915. return;
  14916. }
  14917. this.player_.trigger('playerresize');
  14918. };
  14919. _proto.dispose = function dispose() {
  14920. if (this.debouncedHandler_) {
  14921. this.debouncedHandler_.cancel();
  14922. }
  14923. if (this.resizeObserver_) {
  14924. if (this.player_.el()) {
  14925. this.resizeObserver_.unobserve(this.player_.el());
  14926. }
  14927. this.resizeObserver_.disconnect();
  14928. }
  14929. if (this.loadListener_) {
  14930. this.off('load', this.loadListener_);
  14931. }
  14932. if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
  14933. this.unloadListener_.call(this.el_.contentWindow);
  14934. }
  14935. this.ResizeObserver = null;
  14936. this.resizeObserver = null;
  14937. this.debouncedHandler_ = null;
  14938. this.loadListener_ = null;
  14939. _Component.prototype.dispose.call(this);
  14940. };
  14941. return ResizeManager;
  14942. }(Component);
  14943. Component.registerComponent('ResizeManager', ResizeManager);
  14944. /* track when we are at the live edge, and other helpers for live playback */
  14945. var LiveTracker =
  14946. /*#__PURE__*/
  14947. function (_Component) {
  14948. _inheritsLoose(LiveTracker, _Component);
  14949. function LiveTracker(player, options) {
  14950. var _this;
  14951. // LiveTracker does not need an element
  14952. var options_ = mergeOptions({
  14953. createEl: false
  14954. }, options);
  14955. _this = _Component.call(this, player, options_) || this;
  14956. _this.reset_();
  14957. _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden,
  14958. // also, tracking when the document is hidden can
  14959. // cause the CPU to spike and eventually crash the page on IE11.
  14960. if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
  14961. _this.on(document, 'visibilitychange', _this.handleVisibilityChange);
  14962. }
  14963. return _this;
  14964. }
  14965. var _proto = LiveTracker.prototype;
  14966. _proto.handleVisibilityChange = function handleVisibilityChange() {
  14967. if (this.player_.duration() !== Infinity) {
  14968. return;
  14969. }
  14970. if (document.hidden) {
  14971. this.stopTracking();
  14972. } else {
  14973. this.startTracking();
  14974. }
  14975. };
  14976. _proto.isBehind_ = function isBehind_() {
  14977. // don't report that we are behind until a timeupdate has been seen
  14978. if (!this.timeupdateSeen_) {
  14979. return false;
  14980. }
  14981. var liveCurrentTime = this.liveCurrentTime();
  14982. var currentTime = this.player_.currentTime();
  14983. var seekableIncrement = this.seekableIncrement_; // the live edge window is the amount of seconds away from live
  14984. // that a player can be, but still be considered live.
  14985. // we add 0.07 because the live tracking happens every 30ms
  14986. // and we want some wiggle room for short segment live playback
  14987. var liveEdgeWindow = seekableIncrement * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd
  14988. // can be Infinity, so we handle that case.
  14989. return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime;
  14990. } // all the functionality for tracking when seek end changes
  14991. // and for tracking how far past seek end we should be
  14992. ;
  14993. _proto.trackLive_ = function trackLive_() {
  14994. this.pastSeekEnd_ = this.pastSeekEnd_;
  14995. var seekable = this.player_.seekable(); // skip undefined seekable
  14996. if (!seekable || !seekable.length) {
  14997. return;
  14998. }
  14999. var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes
  15000. // once we detect that seekable has changed we check the new seek
  15001. // end against current time, with a fudge value of half a second.
  15002. if (newSeekEnd !== this.lastSeekEnd_) {
  15003. if (this.lastSeekEnd_) {
  15004. this.seekableIncrement_ = Math.abs(newSeekEnd - this.lastSeekEnd_);
  15005. }
  15006. this.pastSeekEnd_ = 0;
  15007. this.lastSeekEnd_ = newSeekEnd;
  15008. this.trigger('seekableendchange');
  15009. }
  15010. this.pastSeekEnd_ = this.pastSeekEnd() + 0.03;
  15011. if (this.isBehind_() !== this.behindLiveEdge()) {
  15012. this.behindLiveEdge_ = this.isBehind_();
  15013. this.trigger('liveedgechange');
  15014. }
  15015. }
  15016. /**
  15017. * handle a durationchange event on the player
  15018. * and start/stop tracking accordingly.
  15019. */
  15020. ;
  15021. _proto.handleDurationchange = function handleDurationchange() {
  15022. if (this.player_.duration() === Infinity) {
  15023. this.startTracking();
  15024. } else {
  15025. this.stopTracking();
  15026. }
  15027. }
  15028. /**
  15029. * start tracking live playback
  15030. */
  15031. ;
  15032. _proto.startTracking = function startTracking() {
  15033. var _this2 = this;
  15034. if (this.isTracking()) {
  15035. return;
  15036. }
  15037. this.trackingInterval_ = this.setInterval(this.trackLive_, 30);
  15038. this.trackLive_();
  15039. this.on(this.player_, 'play', this.trackLive_);
  15040. this.on(this.player_, 'pause', this.trackLive_);
  15041. this.one(this.player_, 'play', this.handlePlay); // this is to prevent showing that we are not live
  15042. // before a video starts to play
  15043. if (!this.timeupdateSeen_) {
  15044. this.handleTimeupdate = function () {
  15045. _this2.timeupdateSeen_ = true;
  15046. _this2.handleTimeupdate = null;
  15047. };
  15048. this.one(this.player_, 'timeupdate', this.handleTimeupdate);
  15049. }
  15050. };
  15051. _proto.handlePlay = function handlePlay() {
  15052. this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
  15053. }
  15054. /**
  15055. * Stop tracking, and set all internal variables to
  15056. * their initial value.
  15057. */
  15058. ;
  15059. _proto.reset_ = function reset_() {
  15060. this.pastSeekEnd_ = 0;
  15061. this.lastSeekEnd_ = null;
  15062. this.behindLiveEdge_ = null;
  15063. this.timeupdateSeen_ = false;
  15064. this.clearInterval(this.trackingInterval_);
  15065. this.trackingInterval_ = null;
  15066. this.seekableIncrement_ = 12;
  15067. this.off(this.player_, 'play', this.trackLive_);
  15068. this.off(this.player_, 'pause', this.trackLive_);
  15069. this.off(this.player_, 'play', this.handlePlay);
  15070. this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
  15071. if (this.handleTimeupdate) {
  15072. this.off(this.player_, 'timeupdate', this.handleTimeupdate);
  15073. this.handleTimeupdate = null;
  15074. }
  15075. }
  15076. /**
  15077. * stop tracking live playback
  15078. */
  15079. ;
  15080. _proto.stopTracking = function stopTracking() {
  15081. if (!this.isTracking()) {
  15082. return;
  15083. }
  15084. this.reset_();
  15085. }
  15086. /**
  15087. * A helper to get the player seekable end
  15088. * so that we don't have to null check everywhere
  15089. */
  15090. ;
  15091. _proto.seekableEnd = function seekableEnd() {
  15092. var seekable = this.player_.seekable();
  15093. var seekableEnds = [];
  15094. var i = seekable ? seekable.length : 0;
  15095. while (i--) {
  15096. seekableEnds.push(seekable.end(i));
  15097. } // grab the furthest seekable end after sorting, or if there are none
  15098. // default to Infinity
  15099. return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
  15100. }
  15101. /**
  15102. * A helper to get the player seekable start
  15103. * so that we don't have to null check everywhere
  15104. */
  15105. ;
  15106. _proto.seekableStart = function seekableStart() {
  15107. var seekable = this.player_.seekable();
  15108. var seekableStarts = [];
  15109. var i = seekable ? seekable.length : 0;
  15110. while (i--) {
  15111. seekableStarts.push(seekable.start(i));
  15112. } // grab the first seekable start after sorting, or if there are none
  15113. // default to 0
  15114. return seekableStarts.length ? seekableStarts.sort()[0] : 0;
  15115. }
  15116. /**
  15117. * Get the live time window
  15118. */
  15119. ;
  15120. _proto.liveWindow = function liveWindow() {
  15121. var liveCurrentTime = this.liveCurrentTime();
  15122. if (liveCurrentTime === Infinity) {
  15123. return Infinity;
  15124. }
  15125. return liveCurrentTime - this.seekableStart();
  15126. }
  15127. /**
  15128. * Determines if the player is live, only checks if this component
  15129. * is tracking live playback or not
  15130. */
  15131. ;
  15132. _proto.isLive = function isLive() {
  15133. return this.isTracking();
  15134. }
  15135. /**
  15136. * Determines if currentTime is at the live edge and won't fall behind
  15137. * on each seekableendchange
  15138. */
  15139. ;
  15140. _proto.atLiveEdge = function atLiveEdge() {
  15141. return !this.behindLiveEdge();
  15142. }
  15143. /**
  15144. * get what we expect the live current time to be
  15145. */
  15146. ;
  15147. _proto.liveCurrentTime = function liveCurrentTime() {
  15148. return this.pastSeekEnd() + this.seekableEnd();
  15149. }
  15150. /**
  15151. * Returns how far past seek end we expect current time to be
  15152. */
  15153. ;
  15154. _proto.pastSeekEnd = function pastSeekEnd() {
  15155. return this.pastSeekEnd_;
  15156. }
  15157. /**
  15158. * If we are currently behind the live edge, aka currentTime will be
  15159. * behind on a seekableendchange
  15160. */
  15161. ;
  15162. _proto.behindLiveEdge = function behindLiveEdge() {
  15163. return this.behindLiveEdge_;
  15164. };
  15165. _proto.isTracking = function isTracking() {
  15166. return typeof this.trackingInterval_ === 'number';
  15167. }
  15168. /**
  15169. * Seek to the live edge if we are behind the live edge
  15170. */
  15171. ;
  15172. _proto.seekToLiveEdge = function seekToLiveEdge() {
  15173. if (this.atLiveEdge()) {
  15174. return;
  15175. }
  15176. this.player_.currentTime(this.liveCurrentTime());
  15177. if (this.player_.paused()) {
  15178. this.player_.play();
  15179. }
  15180. };
  15181. _proto.dispose = function dispose() {
  15182. this.stopTracking();
  15183. _Component.prototype.dispose.call(this);
  15184. };
  15185. return LiveTracker;
  15186. }(Component);
  15187. Component.registerComponent('LiveTracker', LiveTracker);
  15188. /**
  15189. * This function is used to fire a sourceset when there is something
  15190. * similar to `mediaEl.load()` being called. It will try to find the source via
  15191. * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
  15192. * with the source that was found or empty string if we cannot know. If it cannot
  15193. * find a source then `sourceset` will not be fired.
  15194. *
  15195. * @param {Html5} tech
  15196. * The tech object that sourceset was setup on
  15197. *
  15198. * @return {boolean}
  15199. * returns false if the sourceset was not fired and true otherwise.
  15200. */
  15201. var sourcesetLoad = function sourcesetLoad(tech) {
  15202. var el = tech.el(); // if `el.src` is set, that source will be loaded.
  15203. if (el.hasAttribute('src')) {
  15204. tech.triggerSourceset(el.src);
  15205. return true;
  15206. }
  15207. /**
  15208. * Since there isn't a src property on the media element, source elements will be used for
  15209. * implementing the source selection algorithm. This happens asynchronously and
  15210. * for most cases were there is more than one source we cannot tell what source will
  15211. * be loaded, without re-implementing the source selection algorithm. At this time we are not
  15212. * going to do that. There are three special cases that we do handle here though:
  15213. *
  15214. * 1. If there are no sources, do not fire `sourceset`.
  15215. * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
  15216. * 3. If there is more than one `<source>` but all of them have the same `src` url.
  15217. * That will be our src.
  15218. */
  15219. var sources = tech.$$('source');
  15220. var srcUrls = [];
  15221. var src = ''; // if there are no sources, do not fire sourceset
  15222. if (!sources.length) {
  15223. return false;
  15224. } // only count valid/non-duplicate source elements
  15225. for (var i = 0; i < sources.length; i++) {
  15226. var url = sources[i].src;
  15227. if (url && srcUrls.indexOf(url) === -1) {
  15228. srcUrls.push(url);
  15229. }
  15230. } // there were no valid sources
  15231. if (!srcUrls.length) {
  15232. return false;
  15233. } // there is only one valid source element url
  15234. // use that
  15235. if (srcUrls.length === 1) {
  15236. src = srcUrls[0];
  15237. }
  15238. tech.triggerSourceset(src);
  15239. return true;
  15240. };
  15241. /**
  15242. * our implementation of an `innerHTML` descriptor for browsers
  15243. * that do not have one.
  15244. */
  15245. var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
  15246. get: function get() {
  15247. return this.cloneNode(true).innerHTML;
  15248. },
  15249. set: function set(v) {
  15250. // make a dummy node to use innerHTML on
  15251. var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
  15252. dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
  15253. var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
  15254. // to the document fragment
  15255. while (dummy.childNodes.length) {
  15256. docFrag.appendChild(dummy.childNodes[0]);
  15257. } // remove content
  15258. this.innerText = ''; // now we add all of that html in one by appending the
  15259. // document fragment. This is how innerHTML does it.
  15260. window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
  15261. return this.innerHTML;
  15262. }
  15263. });
  15264. /**
  15265. * Get a property descriptor given a list of priorities and the
  15266. * property to get.
  15267. */
  15268. var getDescriptor = function getDescriptor(priority, prop) {
  15269. var descriptor = {};
  15270. for (var i = 0; i < priority.length; i++) {
  15271. descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
  15272. if (descriptor && descriptor.set && descriptor.get) {
  15273. break;
  15274. }
  15275. }
  15276. descriptor.enumerable = true;
  15277. descriptor.configurable = true;
  15278. return descriptor;
  15279. };
  15280. var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
  15281. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
  15282. };
  15283. /**
  15284. * Patches browser internal functions so that we can tell synchronously
  15285. * if a `<source>` was appended to the media element. For some reason this
  15286. * causes a `sourceset` if the the media element is ready and has no source.
  15287. * This happens when:
  15288. * - The page has just loaded and the media element does not have a source.
  15289. * - The media element was emptied of all sources, then `load()` was called.
  15290. *
  15291. * It does this by patching the following functions/properties when they are supported:
  15292. *
  15293. * - `append()` - can be used to add a `<source>` element to the media element
  15294. * - `appendChild()` - can be used to add a `<source>` element to the media element
  15295. * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element
  15296. * - `innerHTML` - can be used to add a `<source>` element to the media element
  15297. *
  15298. * @param {Html5} tech
  15299. * The tech object that sourceset is being setup on.
  15300. */
  15301. var firstSourceWatch = function firstSourceWatch(tech) {
  15302. var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
  15303. if (el.resetSourceWatch_) {
  15304. return;
  15305. }
  15306. var old = {};
  15307. var innerDescriptor = getInnerHTMLDescriptor(tech);
  15308. var appendWrapper = function appendWrapper(appendFn) {
  15309. return function () {
  15310. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  15311. args[_key] = arguments[_key];
  15312. }
  15313. var retval = appendFn.apply(el, args);
  15314. sourcesetLoad(tech);
  15315. return retval;
  15316. };
  15317. };
  15318. ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
  15319. if (!el[k]) {
  15320. return;
  15321. } // store the old function
  15322. old[k] = el[k]; // call the old function with a sourceset if a source
  15323. // was loaded
  15324. el[k] = appendWrapper(old[k]);
  15325. });
  15326. Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
  15327. set: appendWrapper(innerDescriptor.set)
  15328. }));
  15329. el.resetSourceWatch_ = function () {
  15330. el.resetSourceWatch_ = null;
  15331. Object.keys(old).forEach(function (k) {
  15332. el[k] = old[k];
  15333. });
  15334. Object.defineProperty(el, 'innerHTML', innerDescriptor);
  15335. }; // on the first sourceset, we need to revert our changes
  15336. tech.one('sourceset', el.resetSourceWatch_);
  15337. };
  15338. /**
  15339. * our implementation of a `src` descriptor for browsers
  15340. * that do not have one.
  15341. */
  15342. var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
  15343. get: function get() {
  15344. if (this.hasAttribute('src')) {
  15345. return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src'));
  15346. }
  15347. return '';
  15348. },
  15349. set: function set(v) {
  15350. window$1.Element.prototype.setAttribute.call(this, 'src', v);
  15351. return v;
  15352. }
  15353. });
  15354. var getSrcDescriptor = function getSrcDescriptor(tech) {
  15355. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
  15356. };
  15357. /**
  15358. * setup `sourceset` handling on the `Html5` tech. This function
  15359. * patches the following element properties/functions:
  15360. *
  15361. * - `src` - to determine when `src` is set
  15362. * - `setAttribute()` - to determine when `src` is set
  15363. * - `load()` - this re-triggers the source selection algorithm, and can
  15364. * cause a sourceset.
  15365. *
  15366. * If there is no source when we are adding `sourceset` support or during a `load()`
  15367. * we also patch the functions listed in `firstSourceWatch`.
  15368. *
  15369. * @param {Html5} tech
  15370. * The tech to patch
  15371. */
  15372. var setupSourceset = function setupSourceset(tech) {
  15373. if (!tech.featuresSourceset) {
  15374. return;
  15375. }
  15376. var el = tech.el(); // make sure sourceset isn't setup twice.
  15377. if (el.resetSourceset_) {
  15378. return;
  15379. }
  15380. var srcDescriptor = getSrcDescriptor(tech);
  15381. var oldSetAttribute = el.setAttribute;
  15382. var oldLoad = el.load;
  15383. Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
  15384. set: function set(v) {
  15385. var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
  15386. tech.triggerSourceset(el.src);
  15387. return retval;
  15388. }
  15389. }));
  15390. el.setAttribute = function (n, v) {
  15391. var retval = oldSetAttribute.call(el, n, v);
  15392. if (/src/i.test(n)) {
  15393. tech.triggerSourceset(el.src);
  15394. }
  15395. return retval;
  15396. };
  15397. el.load = function () {
  15398. var retval = oldLoad.call(el); // if load was called, but there was no source to fire
  15399. // sourceset on. We have to watch for a source append
  15400. // as that can trigger a `sourceset` when the media element
  15401. // has no source
  15402. if (!sourcesetLoad(tech)) {
  15403. tech.triggerSourceset('');
  15404. firstSourceWatch(tech);
  15405. }
  15406. return retval;
  15407. };
  15408. if (el.currentSrc) {
  15409. tech.triggerSourceset(el.currentSrc);
  15410. } else if (!sourcesetLoad(tech)) {
  15411. firstSourceWatch(tech);
  15412. }
  15413. el.resetSourceset_ = function () {
  15414. el.resetSourceset_ = null;
  15415. el.load = oldLoad;
  15416. el.setAttribute = oldSetAttribute;
  15417. Object.defineProperty(el, 'src', srcDescriptor);
  15418. if (el.resetSourceWatch_) {
  15419. el.resetSourceWatch_();
  15420. }
  15421. };
  15422. };
  15423. function _templateObject$1() {
  15424. var data = _taggedTemplateLiteralLoose(["Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.\n This may prevent text tracks from loading."]);
  15425. _templateObject$1 = function _templateObject() {
  15426. return data;
  15427. };
  15428. return data;
  15429. }
  15430. /**
  15431. * HTML5 Media Controller - Wrapper for HTML5 Media API
  15432. *
  15433. * @mixes Tech~SourceHandlerAdditions
  15434. * @extends Tech
  15435. */
  15436. var Html5 =
  15437. /*#__PURE__*/
  15438. function (_Tech) {
  15439. _inheritsLoose(Html5, _Tech);
  15440. /**
  15441. * Create an instance of this Tech.
  15442. *
  15443. * @param {Object} [options]
  15444. * The key/value store of player options.
  15445. *
  15446. * @param {Component~ReadyCallback} ready
  15447. * Callback function to call when the `HTML5` Tech is ready.
  15448. */
  15449. function Html5(options, ready) {
  15450. var _this;
  15451. _this = _Tech.call(this, options, ready) || this;
  15452. var source = options.source;
  15453. var crossoriginTracks = false; // Set the source if one is provided
  15454. // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
  15455. // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
  15456. // anyway so the error gets fired.
  15457. if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
  15458. _this.setSource(source);
  15459. } else {
  15460. _this.handleLateInit_(_this.el_);
  15461. } // setup sourceset after late sourceset/init
  15462. if (options.enableSourceset) {
  15463. _this.setupSourcesetHandling_();
  15464. }
  15465. if (_this.el_.hasChildNodes()) {
  15466. var nodes = _this.el_.childNodes;
  15467. var nodesLength = nodes.length;
  15468. var removeNodes = [];
  15469. while (nodesLength--) {
  15470. var node = nodes[nodesLength];
  15471. var nodeName = node.nodeName.toLowerCase();
  15472. if (nodeName === 'track') {
  15473. if (!_this.featuresNativeTextTracks) {
  15474. // Empty video tag tracks so the built-in player doesn't use them also.
  15475. // This may not be fast enough to stop HTML5 browsers from reading the tags
  15476. // so we'll need to turn off any default tracks if we're manually doing
  15477. // captions and subtitles. videoElement.textTracks
  15478. removeNodes.push(node);
  15479. } else {
  15480. // store HTMLTrackElement and TextTrack to remote list
  15481. _this.remoteTextTrackEls().addTrackElement_(node);
  15482. _this.remoteTextTracks().addTrack(node.track);
  15483. _this.textTracks().addTrack(node.track);
  15484. if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
  15485. crossoriginTracks = true;
  15486. }
  15487. }
  15488. }
  15489. }
  15490. for (var i = 0; i < removeNodes.length; i++) {
  15491. _this.el_.removeChild(removeNodes[i]);
  15492. }
  15493. }
  15494. _this.proxyNativeTracks_();
  15495. if (_this.featuresNativeTextTracks && crossoriginTracks) {
  15496. log.warn(tsml(_templateObject$1()));
  15497. } // prevent iOS Safari from disabling metadata text tracks during native playback
  15498. _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
  15499. // Our goal should be to get the custom controls on mobile solid everywhere
  15500. // so we can remove this all together. Right now this will block custom
  15501. // controls on touch enabled laptops like the Chrome Pixel
  15502. if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
  15503. _this.setControls(true);
  15504. } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
  15505. // into a `fullscreenchange` event
  15506. _this.proxyWebkitFullscreen_();
  15507. _this.triggerReady();
  15508. return _this;
  15509. }
  15510. /**
  15511. * Dispose of `HTML5` media element and remove all tracks.
  15512. */
  15513. var _proto = Html5.prototype;
  15514. _proto.dispose = function dispose() {
  15515. if (this.el_ && this.el_.resetSourceset_) {
  15516. this.el_.resetSourceset_();
  15517. }
  15518. Html5.disposeMediaElement(this.el_);
  15519. this.options_ = null; // tech will handle clearing of the emulated track list
  15520. _Tech.prototype.dispose.call(this);
  15521. }
  15522. /**
  15523. * Modify the media element so that we can detect when
  15524. * the source is changed. Fires `sourceset` just after the source has changed
  15525. */
  15526. ;
  15527. _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
  15528. setupSourceset(this);
  15529. }
  15530. /**
  15531. * When a captions track is enabled in the iOS Safari native player, all other
  15532. * tracks are disabled (including metadata tracks), which nulls all of their
  15533. * associated cue points. This will restore metadata tracks to their pre-fullscreen
  15534. * state in those cases so that cue points are not needlessly lost.
  15535. *
  15536. * @private
  15537. */
  15538. ;
  15539. _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
  15540. var textTracks = this.textTracks();
  15541. var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
  15542. var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
  15543. metadataTracksPreFullscreenState = [];
  15544. for (var i = 0; i < textTracks.length; i++) {
  15545. var track = textTracks[i];
  15546. if (track.kind === 'metadata') {
  15547. metadataTracksPreFullscreenState.push({
  15548. track: track,
  15549. storedMode: track.mode
  15550. });
  15551. }
  15552. }
  15553. }; // snapshot each metadata track's initial state, and update the snapshot
  15554. // each time there is a track 'change' event
  15555. takeMetadataTrackSnapshot();
  15556. textTracks.addEventListener('change', takeMetadataTrackSnapshot);
  15557. this.on('dispose', function () {
  15558. return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  15559. });
  15560. var restoreTrackMode = function restoreTrackMode() {
  15561. for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
  15562. var storedTrack = metadataTracksPreFullscreenState[i];
  15563. if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
  15564. storedTrack.track.mode = storedTrack.storedMode;
  15565. }
  15566. } // we only want this handler to be executed on the first 'change' event
  15567. textTracks.removeEventListener('change', restoreTrackMode);
  15568. }; // when we enter fullscreen playback, stop updating the snapshot and
  15569. // restore all track modes to their pre-fullscreen state
  15570. this.on('webkitbeginfullscreen', function () {
  15571. textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
  15572. textTracks.removeEventListener('change', restoreTrackMode);
  15573. textTracks.addEventListener('change', restoreTrackMode);
  15574. }); // start updating the snapshot again after leaving fullscreen
  15575. this.on('webkitendfullscreen', function () {
  15576. // remove the listener before adding it just in case it wasn't previously removed
  15577. textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  15578. textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
  15579. textTracks.removeEventListener('change', restoreTrackMode);
  15580. });
  15581. }
  15582. /**
  15583. * Attempt to force override of tracks for the given type
  15584. *
  15585. * @param {string} type - Track type to override, possible values include 'Audio',
  15586. * 'Video', and 'Text'.
  15587. * @param {boolean} override - If set to true native audio/video will be overridden,
  15588. * otherwise native audio/video will potentially be used.
  15589. * @private
  15590. */
  15591. ;
  15592. _proto.overrideNative_ = function overrideNative_(type, override) {
  15593. var _this2 = this;
  15594. // If there is no behavioral change don't add/remove listeners
  15595. if (override !== this["featuresNative" + type + "Tracks"]) {
  15596. return;
  15597. }
  15598. var lowerCaseType = type.toLowerCase();
  15599. if (this[lowerCaseType + "TracksListeners_"]) {
  15600. Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) {
  15601. var elTracks = _this2.el()[lowerCaseType + "Tracks"];
  15602. elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
  15603. });
  15604. }
  15605. this["featuresNative" + type + "Tracks"] = !override;
  15606. this[lowerCaseType + "TracksListeners_"] = null;
  15607. this.proxyNativeTracksForType_(lowerCaseType);
  15608. }
  15609. /**
  15610. * Attempt to force override of native audio tracks.
  15611. *
  15612. * @param {boolean} override - If set to true native audio will be overridden,
  15613. * otherwise native audio will potentially be used.
  15614. */
  15615. ;
  15616. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
  15617. this.overrideNative_('Audio', override);
  15618. }
  15619. /**
  15620. * Attempt to force override of native video tracks.
  15621. *
  15622. * @param {boolean} override - If set to true native video will be overridden,
  15623. * otherwise native video will potentially be used.
  15624. */
  15625. ;
  15626. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
  15627. this.overrideNative_('Video', override);
  15628. }
  15629. /**
  15630. * Proxy native track list events for the given type to our track
  15631. * lists if the browser we are playing in supports that type of track list.
  15632. *
  15633. * @param {string} name - Track type; values include 'audio', 'video', and 'text'
  15634. * @private
  15635. */
  15636. ;
  15637. _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
  15638. var _this3 = this;
  15639. var props = NORMAL[name];
  15640. var elTracks = this.el()[props.getterName];
  15641. var techTracks = this[props.getterName]();
  15642. if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
  15643. return;
  15644. }
  15645. var listeners = {
  15646. change: function change(e) {
  15647. techTracks.trigger({
  15648. type: 'change',
  15649. target: techTracks,
  15650. currentTarget: techTracks,
  15651. srcElement: techTracks
  15652. });
  15653. },
  15654. addtrack: function addtrack(e) {
  15655. techTracks.addTrack(e.track);
  15656. },
  15657. removetrack: function removetrack(e) {
  15658. techTracks.removeTrack(e.track);
  15659. }
  15660. };
  15661. var removeOldTracks = function removeOldTracks() {
  15662. var removeTracks = [];
  15663. for (var i = 0; i < techTracks.length; i++) {
  15664. var found = false;
  15665. for (var j = 0; j < elTracks.length; j++) {
  15666. if (elTracks[j] === techTracks[i]) {
  15667. found = true;
  15668. break;
  15669. }
  15670. }
  15671. if (!found) {
  15672. removeTracks.push(techTracks[i]);
  15673. }
  15674. }
  15675. while (removeTracks.length) {
  15676. techTracks.removeTrack(removeTracks.shift());
  15677. }
  15678. };
  15679. this[props.getterName + 'Listeners_'] = listeners;
  15680. Object.keys(listeners).forEach(function (eventName) {
  15681. var listener = listeners[eventName];
  15682. elTracks.addEventListener(eventName, listener);
  15683. _this3.on('dispose', function (e) {
  15684. return elTracks.removeEventListener(eventName, listener);
  15685. });
  15686. }); // Remove (native) tracks that are not used anymore
  15687. this.on('loadstart', removeOldTracks);
  15688. this.on('dispose', function (e) {
  15689. return _this3.off('loadstart', removeOldTracks);
  15690. });
  15691. }
  15692. /**
  15693. * Proxy all native track list events to our track lists if the browser we are playing
  15694. * in supports that type of track list.
  15695. *
  15696. * @private
  15697. */
  15698. ;
  15699. _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
  15700. var _this4 = this;
  15701. NORMAL.names.forEach(function (name) {
  15702. _this4.proxyNativeTracksForType_(name);
  15703. });
  15704. }
  15705. /**
  15706. * Create the `Html5` Tech's DOM element.
  15707. *
  15708. * @return {Element}
  15709. * The element that gets created.
  15710. */
  15711. ;
  15712. _proto.createEl = function createEl$$1() {
  15713. var el = this.options_.tag; // Check if this browser supports moving the element into the box.
  15714. // On the iPhone video will break if you move the element,
  15715. // So we have to create a brand new element.
  15716. // If we ingested the player div, we do not need to move the media element.
  15717. if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
  15718. // If the original tag is still there, clone and remove it.
  15719. if (el) {
  15720. var clone = el.cloneNode(true);
  15721. if (el.parentNode) {
  15722. el.parentNode.insertBefore(clone, el);
  15723. }
  15724. Html5.disposeMediaElement(el);
  15725. el = clone;
  15726. } else {
  15727. el = document.createElement('video'); // determine if native controls should be used
  15728. var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
  15729. var attributes = mergeOptions({}, tagAttributes);
  15730. if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
  15731. delete attributes.controls;
  15732. }
  15733. setAttributes(el, assign(attributes, {
  15734. id: this.options_.techId,
  15735. class: 'vjs-tech'
  15736. }));
  15737. }
  15738. el.playerId = this.options_.playerId;
  15739. }
  15740. if (typeof this.options_.preload !== 'undefined') {
  15741. setAttribute(el, 'preload', this.options_.preload);
  15742. } // Update specific tag settings, in case they were overridden
  15743. // `autoplay` has to be *last* so that `muted` and `playsinline` are present
  15744. // when iOS/Safari or other browsers attempt to autoplay.
  15745. var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
  15746. for (var i = 0; i < settingsAttrs.length; i++) {
  15747. var attr = settingsAttrs[i];
  15748. var value = this.options_[attr];
  15749. if (typeof value !== 'undefined') {
  15750. if (value) {
  15751. setAttribute(el, attr, attr);
  15752. } else {
  15753. removeAttribute(el, attr);
  15754. }
  15755. el[attr] = value;
  15756. }
  15757. }
  15758. return el;
  15759. }
  15760. /**
  15761. * This will be triggered if the loadstart event has already fired, before videojs was
  15762. * ready. Two known examples of when this can happen are:
  15763. * 1. If we're loading the playback object after it has started loading
  15764. * 2. The media is already playing the (often with autoplay on) then
  15765. *
  15766. * This function will fire another loadstart so that videojs can catchup.
  15767. *
  15768. * @fires Tech#loadstart
  15769. *
  15770. * @return {undefined}
  15771. * returns nothing.
  15772. */
  15773. ;
  15774. _proto.handleLateInit_ = function handleLateInit_(el) {
  15775. if (el.networkState === 0 || el.networkState === 3) {
  15776. // The video element hasn't started loading the source yet
  15777. // or didn't find a source
  15778. return;
  15779. }
  15780. if (el.readyState === 0) {
  15781. // NetworkState is set synchronously BUT loadstart is fired at the
  15782. // end of the current stack, usually before setInterval(fn, 0).
  15783. // So at this point we know loadstart may have already fired or is
  15784. // about to fire, and either way the player hasn't seen it yet.
  15785. // We don't want to fire loadstart prematurely here and cause a
  15786. // double loadstart so we'll wait and see if it happens between now
  15787. // and the next loop, and fire it if not.
  15788. // HOWEVER, we also want to make sure it fires before loadedmetadata
  15789. // which could also happen between now and the next loop, so we'll
  15790. // watch for that also.
  15791. var loadstartFired = false;
  15792. var setLoadstartFired = function setLoadstartFired() {
  15793. loadstartFired = true;
  15794. };
  15795. this.on('loadstart', setLoadstartFired);
  15796. var triggerLoadstart = function triggerLoadstart() {
  15797. // We did miss the original loadstart. Make sure the player
  15798. // sees loadstart before loadedmetadata
  15799. if (!loadstartFired) {
  15800. this.trigger('loadstart');
  15801. }
  15802. };
  15803. this.on('loadedmetadata', triggerLoadstart);
  15804. this.ready(function () {
  15805. this.off('loadstart', setLoadstartFired);
  15806. this.off('loadedmetadata', triggerLoadstart);
  15807. if (!loadstartFired) {
  15808. // We did miss the original native loadstart. Fire it now.
  15809. this.trigger('loadstart');
  15810. }
  15811. });
  15812. return;
  15813. } // From here on we know that loadstart already fired and we missed it.
  15814. // The other readyState events aren't as much of a problem if we double
  15815. // them, so not going to go to as much trouble as loadstart to prevent
  15816. // that unless we find reason to.
  15817. var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
  15818. eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
  15819. if (el.readyState >= 2) {
  15820. eventsToTrigger.push('loadeddata');
  15821. } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
  15822. if (el.readyState >= 3) {
  15823. eventsToTrigger.push('canplay');
  15824. } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
  15825. if (el.readyState >= 4) {
  15826. eventsToTrigger.push('canplaythrough');
  15827. } // We still need to give the player time to add event listeners
  15828. this.ready(function () {
  15829. eventsToTrigger.forEach(function (type) {
  15830. this.trigger(type);
  15831. }, this);
  15832. });
  15833. }
  15834. /**
  15835. * Set current time for the `HTML5` tech.
  15836. *
  15837. * @param {number} seconds
  15838. * Set the current time of the media to this.
  15839. */
  15840. ;
  15841. _proto.setCurrentTime = function setCurrentTime(seconds) {
  15842. try {
  15843. this.el_.currentTime = seconds;
  15844. } catch (e) {
  15845. log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
  15846. }
  15847. }
  15848. /**
  15849. * Get the current duration of the HTML5 media element.
  15850. *
  15851. * @return {number}
  15852. * The duration of the media or 0 if there is no duration.
  15853. */
  15854. ;
  15855. _proto.duration = function duration() {
  15856. var _this5 = this;
  15857. // Android Chrome will report duration as Infinity for VOD HLS until after
  15858. // playback has started, which triggers the live display erroneously.
  15859. // Return NaN if playback has not started and trigger a durationupdate once
  15860. // the duration can be reliably known.
  15861. if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
  15862. // Wait for the first `timeupdate` with currentTime > 0 - there may be
  15863. // several with 0
  15864. var checkProgress = function checkProgress() {
  15865. if (_this5.el_.currentTime > 0) {
  15866. // Trigger durationchange for genuinely live video
  15867. if (_this5.el_.duration === Infinity) {
  15868. _this5.trigger('durationchange');
  15869. }
  15870. _this5.off('timeupdate', checkProgress);
  15871. }
  15872. };
  15873. this.on('timeupdate', checkProgress);
  15874. return NaN;
  15875. }
  15876. return this.el_.duration || NaN;
  15877. }
  15878. /**
  15879. * Get the current width of the HTML5 media element.
  15880. *
  15881. * @return {number}
  15882. * The width of the HTML5 media element.
  15883. */
  15884. ;
  15885. _proto.width = function width() {
  15886. return this.el_.offsetWidth;
  15887. }
  15888. /**
  15889. * Get the current height of the HTML5 media element.
  15890. *
  15891. * @return {number}
  15892. * The height of the HTML5 media element.
  15893. */
  15894. ;
  15895. _proto.height = function height() {
  15896. return this.el_.offsetHeight;
  15897. }
  15898. /**
  15899. * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
  15900. * `fullscreenchange` event.
  15901. *
  15902. * @private
  15903. * @fires fullscreenchange
  15904. * @listens webkitendfullscreen
  15905. * @listens webkitbeginfullscreen
  15906. * @listens webkitbeginfullscreen
  15907. */
  15908. ;
  15909. _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
  15910. var _this6 = this;
  15911. if (!('webkitDisplayingFullscreen' in this.el_)) {
  15912. return;
  15913. }
  15914. var endFn = function endFn() {
  15915. this.trigger('fullscreenchange', {
  15916. isFullscreen: false
  15917. });
  15918. };
  15919. var beginFn = function beginFn() {
  15920. if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
  15921. this.one('webkitendfullscreen', endFn);
  15922. this.trigger('fullscreenchange', {
  15923. isFullscreen: true
  15924. });
  15925. }
  15926. };
  15927. this.on('webkitbeginfullscreen', beginFn);
  15928. this.on('dispose', function () {
  15929. _this6.off('webkitbeginfullscreen', beginFn);
  15930. _this6.off('webkitendfullscreen', endFn);
  15931. });
  15932. }
  15933. /**
  15934. * Check if fullscreen is supported on the current playback device.
  15935. *
  15936. * @return {boolean}
  15937. * - True if fullscreen is supported.
  15938. * - False if fullscreen is not supported.
  15939. */
  15940. ;
  15941. _proto.supportsFullScreen = function supportsFullScreen() {
  15942. if (typeof this.el_.webkitEnterFullScreen === 'function') {
  15943. var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
  15944. if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
  15945. return true;
  15946. }
  15947. }
  15948. return false;
  15949. }
  15950. /**
  15951. * Request that the `HTML5` Tech enter fullscreen.
  15952. */
  15953. ;
  15954. _proto.enterFullScreen = function enterFullScreen() {
  15955. var video = this.el_;
  15956. if (video.paused && video.networkState <= video.HAVE_METADATA) {
  15957. // attempt to prime the video element for programmatic access
  15958. // this isn't necessary on the desktop but shouldn't hurt
  15959. this.el_.play(); // playing and pausing synchronously during the transition to fullscreen
  15960. // can get iOS ~6.1 devices into a play/pause loop
  15961. this.setTimeout(function () {
  15962. video.pause();
  15963. video.webkitEnterFullScreen();
  15964. }, 0);
  15965. } else {
  15966. video.webkitEnterFullScreen();
  15967. }
  15968. }
  15969. /**
  15970. * Request that the `HTML5` Tech exit fullscreen.
  15971. */
  15972. ;
  15973. _proto.exitFullScreen = function exitFullScreen() {
  15974. this.el_.webkitExitFullScreen();
  15975. }
  15976. /**
  15977. * A getter/setter for the `Html5` Tech's source object.
  15978. * > Note: Please use {@link Html5#setSource}
  15979. *
  15980. * @param {Tech~SourceObject} [src]
  15981. * The source object you want to set on the `HTML5` techs element.
  15982. *
  15983. * @return {Tech~SourceObject|undefined}
  15984. * - The current source object when a source is not passed in.
  15985. * - undefined when setting
  15986. *
  15987. * @deprecated Since version 5.
  15988. */
  15989. ;
  15990. _proto.src = function src(_src) {
  15991. if (_src === undefined) {
  15992. return this.el_.src;
  15993. } // Setting src through `src` instead of `setSrc` will be deprecated
  15994. this.setSrc(_src);
  15995. }
  15996. /**
  15997. * Reset the tech by removing all sources and then calling
  15998. * {@link Html5.resetMediaElement}.
  15999. */
  16000. ;
  16001. _proto.reset = function reset() {
  16002. Html5.resetMediaElement(this.el_);
  16003. }
  16004. /**
  16005. * Get the current source on the `HTML5` Tech. Falls back to returning the source from
  16006. * the HTML5 media element.
  16007. *
  16008. * @return {Tech~SourceObject}
  16009. * The current source object from the HTML5 tech. With a fallback to the
  16010. * elements source.
  16011. */
  16012. ;
  16013. _proto.currentSrc = function currentSrc() {
  16014. if (this.currentSource_) {
  16015. return this.currentSource_.src;
  16016. }
  16017. return this.el_.currentSrc;
  16018. }
  16019. /**
  16020. * Set controls attribute for the HTML5 media Element.
  16021. *
  16022. * @param {string} val
  16023. * Value to set the controls attribute to
  16024. */
  16025. ;
  16026. _proto.setControls = function setControls(val) {
  16027. this.el_.controls = !!val;
  16028. }
  16029. /**
  16030. * Create and returns a remote {@link TextTrack} object.
  16031. *
  16032. * @param {string} kind
  16033. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  16034. *
  16035. * @param {string} [label]
  16036. * Label to identify the text track
  16037. *
  16038. * @param {string} [language]
  16039. * Two letter language abbreviation
  16040. *
  16041. * @return {TextTrack}
  16042. * The TextTrack that gets created.
  16043. */
  16044. ;
  16045. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  16046. if (!this.featuresNativeTextTracks) {
  16047. return _Tech.prototype.addTextTrack.call(this, kind, label, language);
  16048. }
  16049. return this.el_.addTextTrack(kind, label, language);
  16050. }
  16051. /**
  16052. * Creates either native TextTrack or an emulated TextTrack depending
  16053. * on the value of `featuresNativeTextTracks`
  16054. *
  16055. * @param {Object} options
  16056. * The object should contain the options to initialize the TextTrack with.
  16057. *
  16058. * @param {string} [options.kind]
  16059. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  16060. *
  16061. * @param {string} [options.label]
  16062. * Label to identify the text track
  16063. *
  16064. * @param {string} [options.language]
  16065. * Two letter language abbreviation.
  16066. *
  16067. * @param {boolean} [options.default]
  16068. * Default this track to on.
  16069. *
  16070. * @param {string} [options.id]
  16071. * The internal id to assign this track.
  16072. *
  16073. * @param {string} [options.src]
  16074. * A source url for the track.
  16075. *
  16076. * @return {HTMLTrackElement}
  16077. * The track element that gets created.
  16078. */
  16079. ;
  16080. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  16081. if (!this.featuresNativeTextTracks) {
  16082. return _Tech.prototype.createRemoteTextTrack.call(this, options);
  16083. }
  16084. var htmlTrackElement = document.createElement('track');
  16085. if (options.kind) {
  16086. htmlTrackElement.kind = options.kind;
  16087. }
  16088. if (options.label) {
  16089. htmlTrackElement.label = options.label;
  16090. }
  16091. if (options.language || options.srclang) {
  16092. htmlTrackElement.srclang = options.language || options.srclang;
  16093. }
  16094. if (options.default) {
  16095. htmlTrackElement.default = options.default;
  16096. }
  16097. if (options.id) {
  16098. htmlTrackElement.id = options.id;
  16099. }
  16100. if (options.src) {
  16101. htmlTrackElement.src = options.src;
  16102. }
  16103. return htmlTrackElement;
  16104. }
  16105. /**
  16106. * Creates a remote text track object and returns an html track element.
  16107. *
  16108. * @param {Object} options The object should contain values for
  16109. * kind, language, label, and src (location of the WebVTT file)
  16110. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  16111. * automatically removed from the video element whenever the source changes
  16112. * @return {HTMLTrackElement} An Html Track Element.
  16113. * This can be an emulated {@link HTMLTrackElement} or a native one.
  16114. * @deprecated The default value of the "manualCleanup" parameter will default
  16115. * to "false" in upcoming versions of Video.js
  16116. */
  16117. ;
  16118. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  16119. var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
  16120. if (this.featuresNativeTextTracks) {
  16121. this.el().appendChild(htmlTrackElement);
  16122. }
  16123. return htmlTrackElement;
  16124. }
  16125. /**
  16126. * Remove remote `TextTrack` from `TextTrackList` object
  16127. *
  16128. * @param {TextTrack} track
  16129. * `TextTrack` object to remove
  16130. */
  16131. ;
  16132. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  16133. _Tech.prototype.removeRemoteTextTrack.call(this, track);
  16134. if (this.featuresNativeTextTracks) {
  16135. var tracks = this.$$('track');
  16136. var i = tracks.length;
  16137. while (i--) {
  16138. if (track === tracks[i] || track === tracks[i].track) {
  16139. this.el().removeChild(tracks[i]);
  16140. }
  16141. }
  16142. }
  16143. }
  16144. /**
  16145. * Gets available media playback quality metrics as specified by the W3C's Media
  16146. * Playback Quality API.
  16147. *
  16148. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  16149. *
  16150. * @return {Object}
  16151. * An object with supported media playback quality metrics
  16152. */
  16153. ;
  16154. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  16155. if (typeof this.el().getVideoPlaybackQuality === 'function') {
  16156. return this.el().getVideoPlaybackQuality();
  16157. }
  16158. var videoPlaybackQuality = {};
  16159. if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
  16160. videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
  16161. videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
  16162. }
  16163. if (window$1.performance && typeof window$1.performance.now === 'function') {
  16164. videoPlaybackQuality.creationTime = window$1.performance.now();
  16165. } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') {
  16166. videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart;
  16167. }
  16168. return videoPlaybackQuality;
  16169. };
  16170. return Html5;
  16171. }(Tech);
  16172. /* HTML5 Support Testing ---------------------------------------------------- */
  16173. if (isReal()) {
  16174. /**
  16175. * Element for testing browser HTML5 media capabilities
  16176. *
  16177. * @type {Element}
  16178. * @constant
  16179. * @private
  16180. */
  16181. Html5.TEST_VID = document.createElement('video');
  16182. var track = document.createElement('track');
  16183. track.kind = 'captions';
  16184. track.srclang = 'en';
  16185. track.label = 'English';
  16186. Html5.TEST_VID.appendChild(track);
  16187. }
  16188. /**
  16189. * Check if HTML5 media is supported by this browser/device.
  16190. *
  16191. * @return {boolean}
  16192. * - True if HTML5 media is supported.
  16193. * - False if HTML5 media is not supported.
  16194. */
  16195. Html5.isSupported = function () {
  16196. // IE with no Media Player is a LIAR! (#984)
  16197. try {
  16198. Html5.TEST_VID.volume = 0.5;
  16199. } catch (e) {
  16200. return false;
  16201. }
  16202. return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
  16203. };
  16204. /**
  16205. * Check if the tech can support the given type
  16206. *
  16207. * @param {string} type
  16208. * The mimetype to check
  16209. * @return {string} 'probably', 'maybe', or '' (empty string)
  16210. */
  16211. Html5.canPlayType = function (type) {
  16212. return Html5.TEST_VID.canPlayType(type);
  16213. };
  16214. /**
  16215. * Check if the tech can support the given source
  16216. *
  16217. * @param {Object} srcObj
  16218. * The source object
  16219. * @param {Object} options
  16220. * The options passed to the tech
  16221. * @return {string} 'probably', 'maybe', or '' (empty string)
  16222. */
  16223. Html5.canPlaySource = function (srcObj, options) {
  16224. return Html5.canPlayType(srcObj.type);
  16225. };
  16226. /**
  16227. * Check if the volume can be changed in this browser/device.
  16228. * Volume cannot be changed in a lot of mobile devices.
  16229. * Specifically, it can't be changed from 1 on iOS.
  16230. *
  16231. * @return {boolean}
  16232. * - True if volume can be controlled
  16233. * - False otherwise
  16234. */
  16235. Html5.canControlVolume = function () {
  16236. // IE will error if Windows Media Player not installed #3315
  16237. try {
  16238. var volume = Html5.TEST_VID.volume;
  16239. Html5.TEST_VID.volume = volume / 2 + 0.1;
  16240. return volume !== Html5.TEST_VID.volume;
  16241. } catch (e) {
  16242. return false;
  16243. }
  16244. };
  16245. /**
  16246. * Check if the volume can be muted in this browser/device.
  16247. * Some devices, e.g. iOS, don't allow changing volume
  16248. * but permits muting/unmuting.
  16249. *
  16250. * @return {bolean}
  16251. * - True if volume can be muted
  16252. * - False otherwise
  16253. */
  16254. Html5.canMuteVolume = function () {
  16255. try {
  16256. var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
  16257. // work, so we want to set both property and attribute
  16258. Html5.TEST_VID.muted = !muted;
  16259. if (Html5.TEST_VID.muted) {
  16260. setAttribute(Html5.TEST_VID, 'muted', 'muted');
  16261. } else {
  16262. removeAttribute(Html5.TEST_VID, 'muted', 'muted');
  16263. }
  16264. return muted !== Html5.TEST_VID.muted;
  16265. } catch (e) {
  16266. return false;
  16267. }
  16268. };
  16269. /**
  16270. * Check if the playback rate can be changed in this browser/device.
  16271. *
  16272. * @return {boolean}
  16273. * - True if playback rate can be controlled
  16274. * - False otherwise
  16275. */
  16276. Html5.canControlPlaybackRate = function () {
  16277. // Playback rate API is implemented in Android Chrome, but doesn't do anything
  16278. // https://github.com/videojs/video.js/issues/3180
  16279. if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
  16280. return false;
  16281. } // IE will error if Windows Media Player not installed #3315
  16282. try {
  16283. var playbackRate = Html5.TEST_VID.playbackRate;
  16284. Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
  16285. return playbackRate !== Html5.TEST_VID.playbackRate;
  16286. } catch (e) {
  16287. return false;
  16288. }
  16289. };
  16290. /**
  16291. * Check if we can override a video/audio elements attributes, with
  16292. * Object.defineProperty.
  16293. *
  16294. * @return {boolean}
  16295. * - True if builtin attributes can be overridden
  16296. * - False otherwise
  16297. */
  16298. Html5.canOverrideAttributes = function () {
  16299. // if we cannot overwrite the src/innerHTML property, there is no support
  16300. // iOS 7 safari for instance cannot do this.
  16301. try {
  16302. var noop = function noop() {};
  16303. Object.defineProperty(document.createElement('video'), 'src', {
  16304. get: noop,
  16305. set: noop
  16306. });
  16307. Object.defineProperty(document.createElement('audio'), 'src', {
  16308. get: noop,
  16309. set: noop
  16310. });
  16311. Object.defineProperty(document.createElement('video'), 'innerHTML', {
  16312. get: noop,
  16313. set: noop
  16314. });
  16315. Object.defineProperty(document.createElement('audio'), 'innerHTML', {
  16316. get: noop,
  16317. set: noop
  16318. });
  16319. } catch (e) {
  16320. return false;
  16321. }
  16322. return true;
  16323. };
  16324. /**
  16325. * Check to see if native `TextTrack`s are supported by this browser/device.
  16326. *
  16327. * @return {boolean}
  16328. * - True if native `TextTrack`s are supported.
  16329. * - False otherwise
  16330. */
  16331. Html5.supportsNativeTextTracks = function () {
  16332. return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
  16333. };
  16334. /**
  16335. * Check to see if native `VideoTrack`s are supported by this browser/device
  16336. *
  16337. * @return {boolean}
  16338. * - True if native `VideoTrack`s are supported.
  16339. * - False otherwise
  16340. */
  16341. Html5.supportsNativeVideoTracks = function () {
  16342. return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
  16343. };
  16344. /**
  16345. * Check to see if native `AudioTrack`s are supported by this browser/device
  16346. *
  16347. * @return {boolean}
  16348. * - True if native `AudioTrack`s are supported.
  16349. * - False otherwise
  16350. */
  16351. Html5.supportsNativeAudioTracks = function () {
  16352. return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
  16353. };
  16354. /**
  16355. * An array of events available on the Html5 tech.
  16356. *
  16357. * @private
  16358. * @type {Array}
  16359. */
  16360. 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'];
  16361. /**
  16362. * Boolean indicating whether the `Tech` supports volume control.
  16363. *
  16364. * @type {boolean}
  16365. * @default {@link Html5.canControlVolume}
  16366. */
  16367. Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
  16368. /**
  16369. * Boolean indicating whether the `Tech` supports muting volume.
  16370. *
  16371. * @type {bolean}
  16372. * @default {@link Html5.canMuteVolume}
  16373. */
  16374. Html5.prototype.featuresMuteControl = Html5.canMuteVolume();
  16375. /**
  16376. * Boolean indicating whether the `Tech` supports changing the speed at which the media
  16377. * plays. Examples:
  16378. * - Set player to play 2x (twice) as fast
  16379. * - Set player to play 0.5x (half) as fast
  16380. *
  16381. * @type {boolean}
  16382. * @default {@link Html5.canControlPlaybackRate}
  16383. */
  16384. Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
  16385. /**
  16386. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  16387. *
  16388. * @type {boolean}
  16389. * @default
  16390. */
  16391. Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
  16392. /**
  16393. * Boolean indicating whether the `HTML5` tech currently supports the media element
  16394. * moving in the DOM. iOS breaks if you move the media element, so this is set this to
  16395. * false there. Everywhere else this should be true.
  16396. *
  16397. * @type {boolean}
  16398. * @default
  16399. */
  16400. Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
  16401. // Is this true?
  16402. /**
  16403. * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
  16404. * when going into fullscreen.
  16405. *
  16406. * @type {boolean}
  16407. * @default
  16408. */
  16409. Html5.prototype.featuresFullscreenResize = true;
  16410. /**
  16411. * Boolean indicating whether the `HTML5` tech currently supports the progress event.
  16412. * If this is false, manual `progress` events will be triggered instead.
  16413. *
  16414. * @type {boolean}
  16415. * @default
  16416. */
  16417. Html5.prototype.featuresProgressEvents = true;
  16418. /**
  16419. * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
  16420. * If this is false, manual `timeupdate` events will be triggered instead.
  16421. *
  16422. * @default
  16423. */
  16424. Html5.prototype.featuresTimeupdateEvents = true;
  16425. /**
  16426. * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
  16427. *
  16428. * @type {boolean}
  16429. * @default {@link Html5.supportsNativeTextTracks}
  16430. */
  16431. Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
  16432. /**
  16433. * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
  16434. *
  16435. * @type {boolean}
  16436. * @default {@link Html5.supportsNativeVideoTracks}
  16437. */
  16438. Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
  16439. /**
  16440. * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
  16441. *
  16442. * @type {boolean}
  16443. * @default {@link Html5.supportsNativeAudioTracks}
  16444. */
  16445. Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); // HTML5 Feature detection and Device Fixes --------------------------------- //
  16446. var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
  16447. var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
  16448. Html5.patchCanPlayType = function () {
  16449. // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
  16450. // Firefox and Chrome report correctly
  16451. if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
  16452. Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
  16453. if (type && mpegurlRE.test(type)) {
  16454. return 'maybe';
  16455. }
  16456. return canPlayType.call(this, type);
  16457. };
  16458. }
  16459. };
  16460. Html5.unpatchCanPlayType = function () {
  16461. var r = Html5.TEST_VID.constructor.prototype.canPlayType;
  16462. Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
  16463. return r;
  16464. }; // by default, patch the media element
  16465. Html5.patchCanPlayType();
  16466. Html5.disposeMediaElement = function (el) {
  16467. if (!el) {
  16468. return;
  16469. }
  16470. if (el.parentNode) {
  16471. el.parentNode.removeChild(el);
  16472. } // remove any child track or source nodes to prevent their loading
  16473. while (el.hasChildNodes()) {
  16474. el.removeChild(el.firstChild);
  16475. } // remove any src reference. not setting `src=''` because that causes a warning
  16476. // in firefox
  16477. el.removeAttribute('src'); // force the media element to update its loading state by calling load()
  16478. // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
  16479. if (typeof el.load === 'function') {
  16480. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  16481. (function () {
  16482. try {
  16483. el.load();
  16484. } catch (e) {// not supported
  16485. }
  16486. })();
  16487. }
  16488. };
  16489. Html5.resetMediaElement = function (el) {
  16490. if (!el) {
  16491. return;
  16492. }
  16493. var sources = el.querySelectorAll('source');
  16494. var i = sources.length;
  16495. while (i--) {
  16496. el.removeChild(sources[i]);
  16497. } // remove any src reference.
  16498. // not setting `src=''` because that throws an error
  16499. el.removeAttribute('src');
  16500. if (typeof el.load === 'function') {
  16501. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  16502. (function () {
  16503. try {
  16504. el.load();
  16505. } catch (e) {// satisfy linter
  16506. }
  16507. })();
  16508. }
  16509. };
  16510. /* Native HTML5 element property wrapping ----------------------------------- */
  16511. // Wrap native boolean attributes with getters that check both property and attribute
  16512. // The list is as followed:
  16513. // muted, defaultMuted, autoplay, controls, loop, playsinline
  16514. [
  16515. /**
  16516. * Get the value of `muted` from the media element. `muted` indicates
  16517. * that the volume for the media should be set to silent. This does not actually change
  16518. * the `volume` attribute.
  16519. *
  16520. * @method Html5#muted
  16521. * @return {boolean}
  16522. * - True if the value of `volume` should be ignored and the audio set to silent.
  16523. * - False if the value of `volume` should be used.
  16524. *
  16525. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  16526. */
  16527. 'muted',
  16528. /**
  16529. * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
  16530. * whether the media should start muted or not. Only changes the default state of the
  16531. * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
  16532. * current state.
  16533. *
  16534. * @method Html5#defaultMuted
  16535. * @return {boolean}
  16536. * - The value of `defaultMuted` from the media element.
  16537. * - True indicates that the media should start muted.
  16538. * - False indicates that the media should not start muted
  16539. *
  16540. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  16541. */
  16542. 'defaultMuted',
  16543. /**
  16544. * Get the value of `autoplay` from the media element. `autoplay` indicates
  16545. * that the media should start to play as soon as the page is ready.
  16546. *
  16547. * @method Html5#autoplay
  16548. * @return {boolean}
  16549. * - The value of `autoplay` from the media element.
  16550. * - True indicates that the media should start as soon as the page loads.
  16551. * - False indicates that the media should not start as soon as the page loads.
  16552. *
  16553. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  16554. */
  16555. 'autoplay',
  16556. /**
  16557. * Get the value of `controls` from the media element. `controls` indicates
  16558. * whether the native media controls should be shown or hidden.
  16559. *
  16560. * @method Html5#controls
  16561. * @return {boolean}
  16562. * - The value of `controls` from the media element.
  16563. * - True indicates that native controls should be showing.
  16564. * - False indicates that native controls should be hidden.
  16565. *
  16566. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
  16567. */
  16568. 'controls',
  16569. /**
  16570. * Get the value of `loop` from the media element. `loop` indicates
  16571. * that the media should return to the start of the media and continue playing once
  16572. * it reaches the end.
  16573. *
  16574. * @method Html5#loop
  16575. * @return {boolean}
  16576. * - The value of `loop` from the media element.
  16577. * - True indicates that playback should seek back to start once
  16578. * the end of a media is reached.
  16579. * - False indicates that playback should not loop back to the start when the
  16580. * end of the media is reached.
  16581. *
  16582. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  16583. */
  16584. 'loop',
  16585. /**
  16586. * Get the value of `playsinline` from the media element. `playsinline` indicates
  16587. * to the browser that non-fullscreen playback is preferred when fullscreen
  16588. * playback is the native default, such as in iOS Safari.
  16589. *
  16590. * @method Html5#playsinline
  16591. * @return {boolean}
  16592. * - The value of `playsinline` from the media element.
  16593. * - True indicates that the media should play inline.
  16594. * - False indicates that the media should not play inline.
  16595. *
  16596. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  16597. */
  16598. 'playsinline'].forEach(function (prop) {
  16599. Html5.prototype[prop] = function () {
  16600. return this.el_[prop] || this.el_.hasAttribute(prop);
  16601. };
  16602. }); // Wrap native boolean attributes with setters that set both property and attribute
  16603. // The list is as followed:
  16604. // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
  16605. // setControls is special-cased above
  16606. [
  16607. /**
  16608. * Set the value of `muted` on the media element. `muted` indicates that the current
  16609. * audio level should be silent.
  16610. *
  16611. * @method Html5#setMuted
  16612. * @param {boolean} muted
  16613. * - True if the audio should be set to silent
  16614. * - False otherwise
  16615. *
  16616. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  16617. */
  16618. 'muted',
  16619. /**
  16620. * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
  16621. * audio level should be silent, but will only effect the muted level on intial playback..
  16622. *
  16623. * @method Html5.prototype.setDefaultMuted
  16624. * @param {boolean} defaultMuted
  16625. * - True if the audio should be set to silent
  16626. * - False otherwise
  16627. *
  16628. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  16629. */
  16630. 'defaultMuted',
  16631. /**
  16632. * Set the value of `autoplay` on the media element. `autoplay` indicates
  16633. * that the media should start to play as soon as the page is ready.
  16634. *
  16635. * @method Html5#setAutoplay
  16636. * @param {boolean} autoplay
  16637. * - True indicates that the media should start as soon as the page loads.
  16638. * - False indicates that the media should not start as soon as the page loads.
  16639. *
  16640. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  16641. */
  16642. 'autoplay',
  16643. /**
  16644. * Set the value of `loop` on the media element. `loop` indicates
  16645. * that the media should return to the start of the media and continue playing once
  16646. * it reaches the end.
  16647. *
  16648. * @method Html5#setLoop
  16649. * @param {boolean} loop
  16650. * - True indicates that playback should seek back to start once
  16651. * the end of a media is reached.
  16652. * - False indicates that playback should not loop back to the start when the
  16653. * end of the media is reached.
  16654. *
  16655. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  16656. */
  16657. 'loop',
  16658. /**
  16659. * Set the value of `playsinline` from the media element. `playsinline` indicates
  16660. * to the browser that non-fullscreen playback is preferred when fullscreen
  16661. * playback is the native default, such as in iOS Safari.
  16662. *
  16663. * @method Html5#setPlaysinline
  16664. * @param {boolean} playsinline
  16665. * - True indicates that the media should play inline.
  16666. * - False indicates that the media should not play inline.
  16667. *
  16668. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  16669. */
  16670. 'playsinline'].forEach(function (prop) {
  16671. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  16672. this.el_[prop] = v;
  16673. if (v) {
  16674. this.el_.setAttribute(prop, prop);
  16675. } else {
  16676. this.el_.removeAttribute(prop);
  16677. }
  16678. };
  16679. }); // Wrap native properties with a getter
  16680. // The list is as followed
  16681. // paused, currentTime, buffered, volume, poster, preload, error, seeking
  16682. // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
  16683. // readyState, videoWidth, videoHeight
  16684. [
  16685. /**
  16686. * Get the value of `paused` from the media element. `paused` indicates whether the media element
  16687. * is currently paused or not.
  16688. *
  16689. * @method Html5#paused
  16690. * @return {boolean}
  16691. * The value of `paused` from the media element.
  16692. *
  16693. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
  16694. */
  16695. 'paused',
  16696. /**
  16697. * Get the value of `currentTime` from the media element. `currentTime` indicates
  16698. * the current second that the media is at in playback.
  16699. *
  16700. * @method Html5#currentTime
  16701. * @return {number}
  16702. * The value of `currentTime` from the media element.
  16703. *
  16704. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
  16705. */
  16706. 'currentTime',
  16707. /**
  16708. * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
  16709. * object that represents the parts of the media that are already downloaded and
  16710. * available for playback.
  16711. *
  16712. * @method Html5#buffered
  16713. * @return {TimeRange}
  16714. * The value of `buffered` from the media element.
  16715. *
  16716. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
  16717. */
  16718. 'buffered',
  16719. /**
  16720. * Get the value of `volume` from the media element. `volume` indicates
  16721. * the current playback volume of audio for a media. `volume` will be a value from 0
  16722. * (silent) to 1 (loudest and default).
  16723. *
  16724. * @method Html5#volume
  16725. * @return {number}
  16726. * The value of `volume` from the media element. Value will be between 0-1.
  16727. *
  16728. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  16729. */
  16730. 'volume',
  16731. /**
  16732. * Get the value of `poster` from the media element. `poster` indicates
  16733. * that the url of an image file that can/will be shown when no media data is available.
  16734. *
  16735. * @method Html5#poster
  16736. * @return {string}
  16737. * The value of `poster` from the media element. Value will be a url to an
  16738. * image.
  16739. *
  16740. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
  16741. */
  16742. 'poster',
  16743. /**
  16744. * Get the value of `preload` from the media element. `preload` indicates
  16745. * what should download before the media is interacted with. It can have the following
  16746. * values:
  16747. * - none: nothing should be downloaded
  16748. * - metadata: poster and the first few frames of the media may be downloaded to get
  16749. * media dimensions and other metadata
  16750. * - auto: allow the media and metadata for the media to be downloaded before
  16751. * interaction
  16752. *
  16753. * @method Html5#preload
  16754. * @return {string}
  16755. * The value of `preload` from the media element. Will be 'none', 'metadata',
  16756. * or 'auto'.
  16757. *
  16758. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  16759. */
  16760. 'preload',
  16761. /**
  16762. * Get the value of the `error` from the media element. `error` indicates any
  16763. * MediaError that may have occurred during playback. If error returns null there is no
  16764. * current error.
  16765. *
  16766. * @method Html5#error
  16767. * @return {MediaError|null}
  16768. * The value of `error` from the media element. Will be `MediaError` if there
  16769. * is a current error and null otherwise.
  16770. *
  16771. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
  16772. */
  16773. 'error',
  16774. /**
  16775. * Get the value of `seeking` from the media element. `seeking` indicates whether the
  16776. * media is currently seeking to a new position or not.
  16777. *
  16778. * @method Html5#seeking
  16779. * @return {boolean}
  16780. * - The value of `seeking` from the media element.
  16781. * - True indicates that the media is currently seeking to a new position.
  16782. * - False indicates that the media is not seeking to a new position at this time.
  16783. *
  16784. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
  16785. */
  16786. 'seeking',
  16787. /**
  16788. * Get the value of `seekable` from the media element. `seekable` returns a
  16789. * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
  16790. *
  16791. * @method Html5#seekable
  16792. * @return {TimeRange}
  16793. * The value of `seekable` from the media element. A `TimeRange` object
  16794. * indicating the current ranges of time that can be seeked to.
  16795. *
  16796. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
  16797. */
  16798. 'seekable',
  16799. /**
  16800. * Get the value of `ended` from the media element. `ended` indicates whether
  16801. * the media has reached the end or not.
  16802. *
  16803. * @method Html5#ended
  16804. * @return {boolean}
  16805. * - The value of `ended` from the media element.
  16806. * - True indicates that the media has ended.
  16807. * - False indicates that the media has not ended.
  16808. *
  16809. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
  16810. */
  16811. 'ended',
  16812. /**
  16813. * Get the value of `playbackRate` from the media element. `playbackRate` indicates
  16814. * the rate at which the media is currently playing back. Examples:
  16815. * - if playbackRate is set to 2, media will play twice as fast.
  16816. * - if playbackRate is set to 0.5, media will play half as fast.
  16817. *
  16818. * @method Html5#playbackRate
  16819. * @return {number}
  16820. * The value of `playbackRate` from the media element. A number indicating
  16821. * the current playback speed of the media, where 1 is normal speed.
  16822. *
  16823. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  16824. */
  16825. 'playbackRate',
  16826. /**
  16827. * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
  16828. * the rate at which the media is currently playing back. This value will not indicate the current
  16829. * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
  16830. *
  16831. * Examples:
  16832. * - if defaultPlaybackRate is set to 2, media will play twice as fast.
  16833. * - if defaultPlaybackRate is set to 0.5, media will play half as fast.
  16834. *
  16835. * @method Html5.prototype.defaultPlaybackRate
  16836. * @return {number}
  16837. * The value of `defaultPlaybackRate` from the media element. A number indicating
  16838. * the current playback speed of the media, where 1 is normal speed.
  16839. *
  16840. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  16841. */
  16842. 'defaultPlaybackRate',
  16843. /**
  16844. * Get the value of `played` from the media element. `played` returns a `TimeRange`
  16845. * object representing points in the media timeline that have been played.
  16846. *
  16847. * @method Html5#played
  16848. * @return {TimeRange}
  16849. * The value of `played` from the media element. A `TimeRange` object indicating
  16850. * the ranges of time that have been played.
  16851. *
  16852. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
  16853. */
  16854. 'played',
  16855. /**
  16856. * Get the value of `networkState` from the media element. `networkState` indicates
  16857. * the current network state. It returns an enumeration from the following list:
  16858. * - 0: NETWORK_EMPTY
  16859. * - 1: NETWORK_IDLE
  16860. * - 2: NETWORK_LOADING
  16861. * - 3: NETWORK_NO_SOURCE
  16862. *
  16863. * @method Html5#networkState
  16864. * @return {number}
  16865. * The value of `networkState` from the media element. This will be a number
  16866. * from the list in the description.
  16867. *
  16868. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
  16869. */
  16870. 'networkState',
  16871. /**
  16872. * Get the value of `readyState` from the media element. `readyState` indicates
  16873. * the current state of the media element. It returns an enumeration from the
  16874. * following list:
  16875. * - 0: HAVE_NOTHING
  16876. * - 1: HAVE_METADATA
  16877. * - 2: HAVE_CURRENT_DATA
  16878. * - 3: HAVE_FUTURE_DATA
  16879. * - 4: HAVE_ENOUGH_DATA
  16880. *
  16881. * @method Html5#readyState
  16882. * @return {number}
  16883. * The value of `readyState` from the media element. This will be a number
  16884. * from the list in the description.
  16885. *
  16886. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
  16887. */
  16888. 'readyState',
  16889. /**
  16890. * Get the value of `videoWidth` from the video element. `videoWidth` indicates
  16891. * the current width of the video in css pixels.
  16892. *
  16893. * @method Html5#videoWidth
  16894. * @return {number}
  16895. * The value of `videoWidth` from the video element. This will be a number
  16896. * in css pixels.
  16897. *
  16898. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  16899. */
  16900. 'videoWidth',
  16901. /**
  16902. * Get the value of `videoHeight` from the video element. `videoHeight` indicates
  16903. * the current height of the video in css pixels.
  16904. *
  16905. * @method Html5#videoHeight
  16906. * @return {number}
  16907. * The value of `videoHeight` from the video element. This will be a number
  16908. * in css pixels.
  16909. *
  16910. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  16911. */
  16912. 'videoHeight'].forEach(function (prop) {
  16913. Html5.prototype[prop] = function () {
  16914. return this.el_[prop];
  16915. };
  16916. }); // Wrap native properties with a setter in this format:
  16917. // set + toTitleCase(name)
  16918. // The list is as follows:
  16919. // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
  16920. [
  16921. /**
  16922. * Set the value of `volume` on the media element. `volume` indicates the current
  16923. * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
  16924. * so on.
  16925. *
  16926. * @method Html5#setVolume
  16927. * @param {number} percentAsDecimal
  16928. * The volume percent as a decimal. Valid range is from 0-1.
  16929. *
  16930. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  16931. */
  16932. 'volume',
  16933. /**
  16934. * Set the value of `src` on the media element. `src` indicates the current
  16935. * {@link Tech~SourceObject} for the media.
  16936. *
  16937. * @method Html5#setSrc
  16938. * @param {Tech~SourceObject} src
  16939. * The source object to set as the current source.
  16940. *
  16941. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
  16942. */
  16943. 'src',
  16944. /**
  16945. * Set the value of `poster` on the media element. `poster` is the url to
  16946. * an image file that can/will be shown when no media data is available.
  16947. *
  16948. * @method Html5#setPoster
  16949. * @param {string} poster
  16950. * The url to an image that should be used as the `poster` for the media
  16951. * element.
  16952. *
  16953. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
  16954. */
  16955. 'poster',
  16956. /**
  16957. * Set the value of `preload` on the media element. `preload` indicates
  16958. * what should download before the media is interacted with. It can have the following
  16959. * values:
  16960. * - none: nothing should be downloaded
  16961. * - metadata: poster and the first few frames of the media may be downloaded to get
  16962. * media dimensions and other metadata
  16963. * - auto: allow the media and metadata for the media to be downloaded before
  16964. * interaction
  16965. *
  16966. * @method Html5#setPreload
  16967. * @param {string} preload
  16968. * The value of `preload` to set on the media element. Must be 'none', 'metadata',
  16969. * or 'auto'.
  16970. *
  16971. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  16972. */
  16973. 'preload',
  16974. /**
  16975. * Set the value of `playbackRate` on the media element. `playbackRate` indicates
  16976. * the rate at which the media should play back. Examples:
  16977. * - if playbackRate is set to 2, media will play twice as fast.
  16978. * - if playbackRate is set to 0.5, media will play half as fast.
  16979. *
  16980. * @method Html5#setPlaybackRate
  16981. * @return {number}
  16982. * The value of `playbackRate` from the media element. A number indicating
  16983. * the current playback speed of the media, where 1 is normal speed.
  16984. *
  16985. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  16986. */
  16987. 'playbackRate',
  16988. /**
  16989. * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
  16990. * the rate at which the media should play back upon initial startup. Changing this value
  16991. * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
  16992. *
  16993. * Example Values:
  16994. * - if playbackRate is set to 2, media will play twice as fast.
  16995. * - if playbackRate is set to 0.5, media will play half as fast.
  16996. *
  16997. * @method Html5.prototype.setDefaultPlaybackRate
  16998. * @return {number}
  16999. * The value of `defaultPlaybackRate` from the media element. A number indicating
  17000. * the current playback speed of the media, where 1 is normal speed.
  17001. *
  17002. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
  17003. */
  17004. 'defaultPlaybackRate'].forEach(function (prop) {
  17005. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  17006. this.el_[prop] = v;
  17007. };
  17008. }); // wrap native functions with a function
  17009. // The list is as follows:
  17010. // pause, load, play
  17011. [
  17012. /**
  17013. * A wrapper around the media elements `pause` function. This will call the `HTML5`
  17014. * media elements `pause` function.
  17015. *
  17016. * @method Html5#pause
  17017. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
  17018. */
  17019. 'pause',
  17020. /**
  17021. * A wrapper around the media elements `load` function. This will call the `HTML5`s
  17022. * media element `load` function.
  17023. *
  17024. * @method Html5#load
  17025. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
  17026. */
  17027. 'load',
  17028. /**
  17029. * A wrapper around the media elements `play` function. This will call the `HTML5`s
  17030. * media element `play` function.
  17031. *
  17032. * @method Html5#play
  17033. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
  17034. */
  17035. 'play'].forEach(function (prop) {
  17036. Html5.prototype[prop] = function () {
  17037. return this.el_[prop]();
  17038. };
  17039. });
  17040. Tech.withSourceHandlers(Html5);
  17041. /**
  17042. * Native source handler for Html5, simply passes the source to the media element.
  17043. *
  17044. * @property {Tech~SourceObject} source
  17045. * The source object
  17046. *
  17047. * @property {Html5} tech
  17048. * The instance of the HTML5 tech.
  17049. */
  17050. Html5.nativeSourceHandler = {};
  17051. /**
  17052. * Check if the media element can play the given mime type.
  17053. *
  17054. * @param {string} type
  17055. * The mimetype to check
  17056. *
  17057. * @return {string}
  17058. * 'probably', 'maybe', or '' (empty string)
  17059. */
  17060. Html5.nativeSourceHandler.canPlayType = function (type) {
  17061. // IE without MediaPlayer throws an error (#519)
  17062. try {
  17063. return Html5.TEST_VID.canPlayType(type);
  17064. } catch (e) {
  17065. return '';
  17066. }
  17067. };
  17068. /**
  17069. * Check if the media element can handle a source natively.
  17070. *
  17071. * @param {Tech~SourceObject} source
  17072. * The source object
  17073. *
  17074. * @param {Object} [options]
  17075. * Options to be passed to the tech.
  17076. *
  17077. * @return {string}
  17078. * 'probably', 'maybe', or '' (empty string).
  17079. */
  17080. Html5.nativeSourceHandler.canHandleSource = function (source, options) {
  17081. // If a type was provided we should rely on that
  17082. if (source.type) {
  17083. return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
  17084. } else if (source.src) {
  17085. var ext = getFileExtension(source.src);
  17086. return Html5.nativeSourceHandler.canPlayType("video/" + ext);
  17087. }
  17088. return '';
  17089. };
  17090. /**
  17091. * Pass the source to the native media element.
  17092. *
  17093. * @param {Tech~SourceObject} source
  17094. * The source object
  17095. *
  17096. * @param {Html5} tech
  17097. * The instance of the Html5 tech
  17098. *
  17099. * @param {Object} [options]
  17100. * The options to pass to the source
  17101. */
  17102. Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
  17103. tech.setSrc(source.src);
  17104. };
  17105. /**
  17106. * A noop for the native dispose function, as cleanup is not needed.
  17107. */
  17108. Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
  17109. Html5.registerSourceHandler(Html5.nativeSourceHandler);
  17110. Tech.registerTech('Html5', Html5);
  17111. function _templateObject$2() {
  17112. var data = _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 "]);
  17113. _templateObject$2 = function _templateObject() {
  17114. return data;
  17115. };
  17116. return data;
  17117. }
  17118. // on the player when they happen
  17119. var TECH_EVENTS_RETRIGGER = [
  17120. /**
  17121. * Fired while the user agent is downloading media data.
  17122. *
  17123. * @event Player#progress
  17124. * @type {EventTarget~Event}
  17125. */
  17126. /**
  17127. * Retrigger the `progress` event that was triggered by the {@link Tech}.
  17128. *
  17129. * @private
  17130. * @method Player#handleTechProgress_
  17131. * @fires Player#progress
  17132. * @listens Tech#progress
  17133. */
  17134. 'progress',
  17135. /**
  17136. * Fires when the loading of an audio/video is aborted.
  17137. *
  17138. * @event Player#abort
  17139. * @type {EventTarget~Event}
  17140. */
  17141. /**
  17142. * Retrigger the `abort` event that was triggered by the {@link Tech}.
  17143. *
  17144. * @private
  17145. * @method Player#handleTechAbort_
  17146. * @fires Player#abort
  17147. * @listens Tech#abort
  17148. */
  17149. 'abort',
  17150. /**
  17151. * Fires when the browser is intentionally not getting media data.
  17152. *
  17153. * @event Player#suspend
  17154. * @type {EventTarget~Event}
  17155. */
  17156. /**
  17157. * Retrigger the `suspend` event that was triggered by the {@link Tech}.
  17158. *
  17159. * @private
  17160. * @method Player#handleTechSuspend_
  17161. * @fires Player#suspend
  17162. * @listens Tech#suspend
  17163. */
  17164. 'suspend',
  17165. /**
  17166. * Fires when the current playlist is empty.
  17167. *
  17168. * @event Player#emptied
  17169. * @type {EventTarget~Event}
  17170. */
  17171. /**
  17172. * Retrigger the `emptied` event that was triggered by the {@link Tech}.
  17173. *
  17174. * @private
  17175. * @method Player#handleTechEmptied_
  17176. * @fires Player#emptied
  17177. * @listens Tech#emptied
  17178. */
  17179. 'emptied',
  17180. /**
  17181. * Fires when the browser is trying to get media data, but data is not available.
  17182. *
  17183. * @event Player#stalled
  17184. * @type {EventTarget~Event}
  17185. */
  17186. /**
  17187. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  17188. *
  17189. * @private
  17190. * @method Player#handleTechStalled_
  17191. * @fires Player#stalled
  17192. * @listens Tech#stalled
  17193. */
  17194. 'stalled',
  17195. /**
  17196. * Fires when the browser has loaded meta data for the audio/video.
  17197. *
  17198. * @event Player#loadedmetadata
  17199. * @type {EventTarget~Event}
  17200. */
  17201. /**
  17202. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  17203. *
  17204. * @private
  17205. * @method Player#handleTechLoadedmetadata_
  17206. * @fires Player#loadedmetadata
  17207. * @listens Tech#loadedmetadata
  17208. */
  17209. 'loadedmetadata',
  17210. /**
  17211. * Fires when the browser has loaded the current frame of the audio/video.
  17212. *
  17213. * @event Player#loadeddata
  17214. * @type {event}
  17215. */
  17216. /**
  17217. * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
  17218. *
  17219. * @private
  17220. * @method Player#handleTechLoaddeddata_
  17221. * @fires Player#loadeddata
  17222. * @listens Tech#loadeddata
  17223. */
  17224. 'loadeddata',
  17225. /**
  17226. * Fires when the current playback position has changed.
  17227. *
  17228. * @event Player#timeupdate
  17229. * @type {event}
  17230. */
  17231. /**
  17232. * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
  17233. *
  17234. * @private
  17235. * @method Player#handleTechTimeUpdate_
  17236. * @fires Player#timeupdate
  17237. * @listens Tech#timeupdate
  17238. */
  17239. 'timeupdate',
  17240. /**
  17241. * Fires when the video's intrinsic dimensions change
  17242. *
  17243. * @event Player#resize
  17244. * @type {event}
  17245. */
  17246. /**
  17247. * Retrigger the `resize` event that was triggered by the {@link Tech}.
  17248. *
  17249. * @private
  17250. * @method Player#handleTechResize_
  17251. * @fires Player#resize
  17252. * @listens Tech#resize
  17253. */
  17254. 'resize',
  17255. /**
  17256. * Fires when the volume has been changed
  17257. *
  17258. * @event Player#volumechange
  17259. * @type {event}
  17260. */
  17261. /**
  17262. * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
  17263. *
  17264. * @private
  17265. * @method Player#handleTechVolumechange_
  17266. * @fires Player#volumechange
  17267. * @listens Tech#volumechange
  17268. */
  17269. 'volumechange',
  17270. /**
  17271. * Fires when the text track has been changed
  17272. *
  17273. * @event Player#texttrackchange
  17274. * @type {event}
  17275. */
  17276. /**
  17277. * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
  17278. *
  17279. * @private
  17280. * @method Player#handleTechTexttrackchange_
  17281. * @fires Player#texttrackchange
  17282. * @listens Tech#texttrackchange
  17283. */
  17284. 'texttrackchange']; // events to queue when playback rate is zero
  17285. // this is a hash for the sole purpose of mapping non-camel-cased event names
  17286. // to camel-cased function names
  17287. var TECH_EVENTS_QUEUE = {
  17288. canplay: 'CanPlay',
  17289. canplaythrough: 'CanPlayThrough',
  17290. playing: 'Playing',
  17291. seeked: 'Seeked'
  17292. };
  17293. var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
  17294. var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
  17295. // grep: vjs-layout-x-small
  17296. // grep: vjs-layout-small
  17297. // grep: vjs-layout-medium
  17298. // grep: vjs-layout-large
  17299. // grep: vjs-layout-x-large
  17300. // grep: vjs-layout-huge
  17301. BREAKPOINT_ORDER.forEach(function (k) {
  17302. var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
  17303. BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
  17304. });
  17305. var DEFAULT_BREAKPOINTS = {
  17306. tiny: 210,
  17307. xsmall: 320,
  17308. small: 425,
  17309. medium: 768,
  17310. large: 1440,
  17311. xlarge: 2560,
  17312. huge: Infinity
  17313. };
  17314. /**
  17315. * An instance of the `Player` class is created when any of the Video.js setup methods
  17316. * are used to initialize a video.
  17317. *
  17318. * After an instance has been created it can be accessed globally in two ways:
  17319. * 1. By calling `videojs('example_video_1');`
  17320. * 2. By using it directly via `videojs.players.example_video_1;`
  17321. *
  17322. * @extends Component
  17323. */
  17324. var Player =
  17325. /*#__PURE__*/
  17326. function (_Component) {
  17327. _inheritsLoose(Player, _Component);
  17328. /**
  17329. * Create an instance of this class.
  17330. *
  17331. * @param {Element} tag
  17332. * The original video DOM element used for configuring options.
  17333. *
  17334. * @param {Object} [options]
  17335. * Object of option names and values.
  17336. *
  17337. * @param {Component~ReadyCallback} [ready]
  17338. * Ready callback function.
  17339. */
  17340. function Player(tag, options, ready) {
  17341. var _this;
  17342. // Make sure tag ID exists
  17343. tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
  17344. // The options argument overrides options set in the video tag
  17345. // which overrides globally set options.
  17346. // This latter part coincides with the load order
  17347. // (tag must exist before Player)
  17348. options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
  17349. // player properties first, and can't use `this` before `super()`
  17350. options.initChildren = false; // Same with creating the element
  17351. options.createEl = false; // don't auto mixin the evented mixin
  17352. options.evented = false; // we don't want the player to report touch activity on itself
  17353. // see enableTouchActivity in Component
  17354. options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
  17355. if (!options.language) {
  17356. if (typeof tag.closest === 'function') {
  17357. var closest = tag.closest('[lang]');
  17358. if (closest && closest.getAttribute) {
  17359. options.language = closest.getAttribute('lang');
  17360. }
  17361. } else {
  17362. var element = tag;
  17363. while (element && element.nodeType === 1) {
  17364. if (getAttributes(element).hasOwnProperty('lang')) {
  17365. options.language = element.getAttribute('lang');
  17366. break;
  17367. }
  17368. element = element.parentNode;
  17369. }
  17370. }
  17371. } // Run base component initializing with new options
  17372. _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
  17373. _this.boundDocumentFullscreenChange_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.documentFullscreenChange_);
  17374. _this.boundFullWindowOnEscKey_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.fullWindowOnEscKey);
  17375. _this.boundHandleKeyPress_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleKeyPress); // create logger
  17376. _this.log = createLogger$1(_this.id_); // Tracks when a tech changes the poster
  17377. _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
  17378. // and a seek is happening
  17379. _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
  17380. _this.isReady_ = false; // Init state hasStarted_
  17381. _this.hasStarted_ = false; // Init state userActive_
  17382. _this.userActive_ = false; // if the global option object was accidentally blown away by
  17383. // someone, bail early with an informative error
  17384. if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
  17385. throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
  17386. } // Store the original tag used to set options
  17387. _this.tag = tag; // Store the tag attributes used to restore html5 element
  17388. _this.tagAttributes = tag && getAttributes(tag); // Update current language
  17389. _this.language(_this.options_.language); // Update Supported Languages
  17390. if (options.languages) {
  17391. // Normalise player option languages to lowercase
  17392. var languagesToLower = {};
  17393. Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) {
  17394. languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1];
  17395. });
  17396. _this.languages_ = languagesToLower;
  17397. } else {
  17398. _this.languages_ = Player.prototype.options_.languages;
  17399. }
  17400. _this.resetCache_(); // Set poster
  17401. _this.poster_ = options.poster || ''; // Set controls
  17402. _this.controls_ = !!options.controls; // Original tag settings stored in options
  17403. // now remove immediately so native controls don't flash.
  17404. // May be turned back on by HTML5 tech if nativeControlsForTouch is true
  17405. tag.controls = false;
  17406. tag.removeAttribute('controls');
  17407. _this.changingSrc_ = false;
  17408. _this.playCallbacks_ = [];
  17409. _this.playTerminatedQueue_ = []; // the attribute overrides the option
  17410. if (tag.hasAttribute('autoplay')) {
  17411. _this.autoplay(true);
  17412. } else {
  17413. // otherwise use the setter to validate and
  17414. // set the correct value.
  17415. _this.autoplay(_this.options_.autoplay);
  17416. } // check plugins
  17417. if (options.plugins) {
  17418. Object.keys(options.plugins).forEach(function (name$$1) {
  17419. if (typeof _this[name$$1] !== 'function') {
  17420. throw new Error("plugin \"" + name$$1 + "\" does not exist");
  17421. }
  17422. });
  17423. }
  17424. /*
  17425. * Store the internal state of scrubbing
  17426. *
  17427. * @private
  17428. * @return {Boolean} True if the user is scrubbing
  17429. */
  17430. _this.scrubbing_ = false;
  17431. _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
  17432. evented(_assertThisInitialized(_assertThisInitialized(_this)), {
  17433. eventBusKey: 'el_'
  17434. });
  17435. if (_this.fluid_) {
  17436. _this.on('playerreset', _this.updateStyleEl_);
  17437. } // We also want to pass the original player options to each component and plugin
  17438. // as well so they don't need to reach back into the player for options later.
  17439. // We also need to do another copy of this.options_ so we don't end up with
  17440. // an infinite loop.
  17441. var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins
  17442. if (options.plugins) {
  17443. Object.keys(options.plugins).forEach(function (name$$1) {
  17444. _this[name$$1](options.plugins[name$$1]);
  17445. });
  17446. }
  17447. _this.options_.playerOptions = playerOptionsCopy;
  17448. _this.middleware_ = [];
  17449. _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
  17450. _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
  17451. // set because the element doesn't exist yet.
  17452. if (_this.controls()) {
  17453. _this.addClass('vjs-controls-enabled');
  17454. } else {
  17455. _this.addClass('vjs-controls-disabled');
  17456. } // Set ARIA label and region role depending on player type
  17457. _this.el_.setAttribute('role', 'region');
  17458. if (_this.isAudio()) {
  17459. _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
  17460. } else {
  17461. _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
  17462. }
  17463. if (_this.isAudio()) {
  17464. _this.addClass('vjs-audio');
  17465. }
  17466. if (_this.flexNotSupported_()) {
  17467. _this.addClass('vjs-no-flex');
  17468. } // TODO: Make this smarter. Toggle user state between touching/mousing
  17469. // using events, since devices can have both touch and mouse events.
  17470. // TODO: Make this check be performed again when the window switches between monitors
  17471. // (See https://github.com/videojs/video.js/issues/5683)
  17472. if (TOUCH_ENABLED) {
  17473. _this.addClass('vjs-touch-enabled');
  17474. } // iOS Safari has broken hover handling
  17475. if (!IS_IOS) {
  17476. _this.addClass('vjs-workinghover');
  17477. } // Make player easily findable by ID
  17478. Player.players[_this.id_] = _assertThisInitialized(_assertThisInitialized(_this)); // Add a major version class to aid css in plugins
  17479. var majorVersion = version.split('.')[0];
  17480. _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
  17481. // like the control bar show themselves if needed
  17482. _this.userActive(true);
  17483. _this.reportUserActivity();
  17484. _this.one('play', _this.listenForUserActivity_);
  17485. _this.on('focus', _this.handleFocus);
  17486. _this.on('blur', _this.handleBlur);
  17487. _this.on('stageclick', _this.handleStageClick_);
  17488. _this.breakpoints(_this.options_.breakpoints);
  17489. _this.responsive(_this.options_.responsive);
  17490. return _this;
  17491. }
  17492. /**
  17493. * Destroys the video player and does any necessary cleanup.
  17494. *
  17495. * This is especially helpful if you are dynamically adding and removing videos
  17496. * to/from the DOM.
  17497. *
  17498. * @fires Player#dispose
  17499. */
  17500. var _proto = Player.prototype;
  17501. _proto.dispose = function dispose() {
  17502. var _this2 = this;
  17503. /**
  17504. * Called when the player is being disposed of.
  17505. *
  17506. * @event Player#dispose
  17507. * @type {EventTarget~Event}
  17508. */
  17509. this.trigger('dispose'); // prevent dispose from being called twice
  17510. this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
  17511. off(document, FullscreenApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  17512. off(document, 'keydown', this.boundFullWindowOnEscKey_);
  17513. off(document, 'keydown', this.boundHandleKeyPress_);
  17514. if (this.styleEl_ && this.styleEl_.parentNode) {
  17515. this.styleEl_.parentNode.removeChild(this.styleEl_);
  17516. this.styleEl_ = null;
  17517. } // Kill reference to this player
  17518. Player.players[this.id_] = null;
  17519. if (this.tag && this.tag.player) {
  17520. this.tag.player = null;
  17521. }
  17522. if (this.el_ && this.el_.player) {
  17523. this.el_.player = null;
  17524. }
  17525. if (this.tech_) {
  17526. this.tech_.dispose();
  17527. this.isPosterFromTech_ = false;
  17528. this.poster_ = '';
  17529. }
  17530. if (this.playerElIngest_) {
  17531. this.playerElIngest_ = null;
  17532. }
  17533. if (this.tag) {
  17534. this.tag = null;
  17535. }
  17536. clearCacheForPlayer(this); // remove all event handlers for track lists
  17537. // all tracks and track listeners are removed on
  17538. // tech dispose
  17539. ALL.names.forEach(function (name$$1) {
  17540. var props = ALL[name$$1];
  17541. var list = _this2[props.getterName](); // if it is not a native list
  17542. // we have to manually remove event listeners
  17543. if (list && list.off) {
  17544. list.off();
  17545. }
  17546. }); // the actual .el_ is removed here
  17547. _Component.prototype.dispose.call(this);
  17548. }
  17549. /**
  17550. * Create the `Player`'s DOM element.
  17551. *
  17552. * @return {Element}
  17553. * The DOM element that gets created.
  17554. */
  17555. ;
  17556. _proto.createEl = function createEl$$1() {
  17557. var tag = this.tag;
  17558. var el;
  17559. var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
  17560. var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
  17561. if (playerElIngest) {
  17562. el = this.el_ = tag.parentNode;
  17563. } else if (!divEmbed) {
  17564. el = this.el_ = _Component.prototype.createEl.call(this, 'div');
  17565. } // Copy over all the attributes from the tag, including ID and class
  17566. // ID will now reference player box, not the video tag
  17567. var attrs = getAttributes(tag);
  17568. if (divEmbed) {
  17569. el = this.el_ = tag;
  17570. tag = this.tag = document.createElement('video');
  17571. while (el.children.length) {
  17572. tag.appendChild(el.firstChild);
  17573. }
  17574. if (!hasClass(el, 'video-js')) {
  17575. addClass(el, 'video-js');
  17576. }
  17577. el.appendChild(tag);
  17578. playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
  17579. // to our new `video` element. This will move things like
  17580. // `src` or `controls` that were set via js before the player
  17581. // was initialized.
  17582. Object.keys(el).forEach(function (k) {
  17583. tag[k] = el[k];
  17584. });
  17585. } // set tabindex to -1 to remove the video element from the focus order
  17586. tag.setAttribute('tabindex', '-1');
  17587. attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and
  17588. // for the same issue with Chrome (on Windows) with JAWS.
  17589. // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
  17590. // Note that we can't detect if JAWS is being used, but this ARIA attribute
  17591. // doesn't change behavior of IE11 or Chrome if JAWS is not being used
  17592. if (IE_VERSION || IS_CHROME && IS_WINDOWS) {
  17593. tag.setAttribute('role', 'application');
  17594. attrs.role = 'application';
  17595. } // Remove width/height attrs from tag so CSS can make it 100% width/height
  17596. tag.removeAttribute('width');
  17597. tag.removeAttribute('height');
  17598. if ('width' in attrs) {
  17599. delete attrs.width;
  17600. }
  17601. if ('height' in attrs) {
  17602. delete attrs.height;
  17603. }
  17604. Object.getOwnPropertyNames(attrs).forEach(function (attr) {
  17605. // don't copy over the class attribute to the player element when we're in a div embed
  17606. // the class is already set up properly in the divEmbed case
  17607. // and we want to make sure that the `video-js` class doesn't get lost
  17608. if (!(divEmbed && attr === 'class')) {
  17609. el.setAttribute(attr, attrs[attr]);
  17610. }
  17611. if (divEmbed) {
  17612. tag.setAttribute(attr, attrs[attr]);
  17613. }
  17614. }); // Update tag id/class for use as HTML5 playback tech
  17615. // Might think we should do this after embedding in container so .vjs-tech class
  17616. // doesn't flash 100% width/height, but class only applies with .video-js parent
  17617. tag.playerId = tag.id;
  17618. tag.id += '_html5_api';
  17619. tag.className = 'vjs-tech'; // Make player findable on elements
  17620. tag.player = el.player = this; // Default state of video is paused
  17621. this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
  17622. // of the player in a way that's still overrideable by CSS, just like the
  17623. // video element
  17624. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
  17625. this.styleEl_ = createStyleElement('vjs-styles-dimensions');
  17626. var defaultsStyleEl = $('.vjs-styles-defaults');
  17627. var head = $('head');
  17628. head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
  17629. }
  17630. this.fill_ = false;
  17631. this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
  17632. this.width(this.options_.width);
  17633. this.height(this.options_.height);
  17634. this.fill(this.options_.fill);
  17635. this.fluid(this.options_.fluid);
  17636. this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag,
  17637. // because IE doesn't hide them completely from screen readers.
  17638. var links = tag.getElementsByTagName('a');
  17639. for (var i = 0; i < links.length; i++) {
  17640. var linkEl = links.item(i);
  17641. addClass(linkEl, 'vjs-hidden');
  17642. linkEl.setAttribute('hidden', 'hidden');
  17643. } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
  17644. // keep track of the original for later so we can know if the source originally failed
  17645. tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
  17646. if (tag.parentNode && !playerElIngest) {
  17647. tag.parentNode.insertBefore(el, tag);
  17648. } // insert the tag as the first child of the player element
  17649. // then manually add it to the children array so that this.addChild
  17650. // will work properly for other components
  17651. //
  17652. // Breaks iPhone, fixed in HTML5 setup.
  17653. prependTo(tag, el);
  17654. this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
  17655. // if it's been set to something different to the doc
  17656. this.el_.setAttribute('lang', this.language_);
  17657. this.el_ = el;
  17658. return el;
  17659. }
  17660. /**
  17661. * A getter/setter for the `Player`'s width. Returns the player's configured value.
  17662. * To get the current width use `currentWidth()`.
  17663. *
  17664. * @param {number} [value]
  17665. * The value to set the `Player`'s width to.
  17666. *
  17667. * @return {number}
  17668. * The current width of the `Player` when getting.
  17669. */
  17670. ;
  17671. _proto.width = function width(value) {
  17672. return this.dimension('width', value);
  17673. }
  17674. /**
  17675. * A getter/setter for the `Player`'s height. Returns the player's configured value.
  17676. * To get the current height use `currentheight()`.
  17677. *
  17678. * @param {number} [value]
  17679. * The value to set the `Player`'s heigth to.
  17680. *
  17681. * @return {number}
  17682. * The current height of the `Player` when getting.
  17683. */
  17684. ;
  17685. _proto.height = function height(value) {
  17686. return this.dimension('height', value);
  17687. }
  17688. /**
  17689. * A getter/setter for the `Player`'s width & height.
  17690. *
  17691. * @param {string} dimension
  17692. * This string can be:
  17693. * - 'width'
  17694. * - 'height'
  17695. *
  17696. * @param {number} [value]
  17697. * Value for dimension specified in the first argument.
  17698. *
  17699. * @return {number}
  17700. * The dimension arguments value when getting (width/height).
  17701. */
  17702. ;
  17703. _proto.dimension = function dimension(_dimension, value) {
  17704. var privDimension = _dimension + '_';
  17705. if (value === undefined) {
  17706. return this[privDimension] || 0;
  17707. }
  17708. if (value === '') {
  17709. // If an empty string is given, reset the dimension to be automatic
  17710. this[privDimension] = undefined;
  17711. this.updateStyleEl_();
  17712. return;
  17713. }
  17714. var parsedVal = parseFloat(value);
  17715. if (isNaN(parsedVal)) {
  17716. log.error("Improper value \"" + value + "\" supplied for for " + _dimension);
  17717. return;
  17718. }
  17719. this[privDimension] = parsedVal;
  17720. this.updateStyleEl_();
  17721. }
  17722. /**
  17723. * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
  17724. *
  17725. * Turning this on will turn off fill mode.
  17726. *
  17727. * @param {boolean} [bool]
  17728. * - A value of true adds the class.
  17729. * - A value of false removes the class.
  17730. * - No value will be a getter.
  17731. *
  17732. * @return {boolean|undefined}
  17733. * - The value of fluid when getting.
  17734. * - `undefined` when setting.
  17735. */
  17736. ;
  17737. _proto.fluid = function fluid(bool) {
  17738. if (bool === undefined) {
  17739. return !!this.fluid_;
  17740. }
  17741. this.fluid_ = !!bool;
  17742. if (isEvented(this)) {
  17743. this.off('playerreset', this.updateStyleEl_);
  17744. }
  17745. if (bool) {
  17746. this.addClass('vjs-fluid');
  17747. this.fill(false);
  17748. addEventedCallback(function () {
  17749. this.on('playerreset', this.updateStyleEl_);
  17750. });
  17751. } else {
  17752. this.removeClass('vjs-fluid');
  17753. }
  17754. this.updateStyleEl_();
  17755. }
  17756. /**
  17757. * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
  17758. *
  17759. * Turning this on will turn off fluid mode.
  17760. *
  17761. * @param {boolean} [bool]
  17762. * - A value of true adds the class.
  17763. * - A value of false removes the class.
  17764. * - No value will be a getter.
  17765. *
  17766. * @return {boolean|undefined}
  17767. * - The value of fluid when getting.
  17768. * - `undefined` when setting.
  17769. */
  17770. ;
  17771. _proto.fill = function fill(bool) {
  17772. if (bool === undefined) {
  17773. return !!this.fill_;
  17774. }
  17775. this.fill_ = !!bool;
  17776. if (bool) {
  17777. this.addClass('vjs-fill');
  17778. this.fluid(false);
  17779. } else {
  17780. this.removeClass('vjs-fill');
  17781. }
  17782. }
  17783. /**
  17784. * Get/Set the aspect ratio
  17785. *
  17786. * @param {string} [ratio]
  17787. * Aspect ratio for player
  17788. *
  17789. * @return {string|undefined}
  17790. * returns the current aspect ratio when getting
  17791. */
  17792. /**
  17793. * A getter/setter for the `Player`'s aspect ratio.
  17794. *
  17795. * @param {string} [ratio]
  17796. * The value to set the `Player's aspect ratio to.
  17797. *
  17798. * @return {string|undefined}
  17799. * - The current aspect ratio of the `Player` when getting.
  17800. * - undefined when setting
  17801. */
  17802. ;
  17803. _proto.aspectRatio = function aspectRatio(ratio) {
  17804. if (ratio === undefined) {
  17805. return this.aspectRatio_;
  17806. } // Check for width:height format
  17807. if (!/^\d+\:\d+$/.test(ratio)) {
  17808. throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
  17809. }
  17810. this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
  17811. // because in fixed mode you could calculate width and height yourself.
  17812. this.fluid(true);
  17813. this.updateStyleEl_();
  17814. }
  17815. /**
  17816. * Update styles of the `Player` element (height, width and aspect ratio).
  17817. *
  17818. * @private
  17819. * @listens Tech#loadedmetadata
  17820. */
  17821. ;
  17822. _proto.updateStyleEl_ = function updateStyleEl_() {
  17823. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
  17824. var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
  17825. var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
  17826. var techEl = this.tech_ && this.tech_.el();
  17827. if (techEl) {
  17828. if (_width >= 0) {
  17829. techEl.width = _width;
  17830. }
  17831. if (_height >= 0) {
  17832. techEl.height = _height;
  17833. }
  17834. }
  17835. return;
  17836. }
  17837. var width;
  17838. var height;
  17839. var aspectRatio;
  17840. var idClass; // The aspect ratio is either used directly or to calculate width and height.
  17841. if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
  17842. // Use any aspectRatio that's been specifically set
  17843. aspectRatio = this.aspectRatio_;
  17844. } else if (this.videoWidth() > 0) {
  17845. // Otherwise try to get the aspect ratio from the video metadata
  17846. aspectRatio = this.videoWidth() + ':' + this.videoHeight();
  17847. } else {
  17848. // Or use a default. The video element's is 2:1, but 16:9 is more common.
  17849. aspectRatio = '16:9';
  17850. } // Get the ratio as a decimal we can use to calculate dimensions
  17851. var ratioParts = aspectRatio.split(':');
  17852. var ratioMultiplier = ratioParts[1] / ratioParts[0];
  17853. if (this.width_ !== undefined) {
  17854. // Use any width that's been specifically set
  17855. width = this.width_;
  17856. } else if (this.height_ !== undefined) {
  17857. // Or calulate the width from the aspect ratio if a height has been set
  17858. width = this.height_ / ratioMultiplier;
  17859. } else {
  17860. // Or use the video's metadata, or use the video el's default of 300
  17861. width = this.videoWidth() || 300;
  17862. }
  17863. if (this.height_ !== undefined) {
  17864. // Use any height that's been specifically set
  17865. height = this.height_;
  17866. } else {
  17867. // Otherwise calculate the height from the ratio and the width
  17868. height = width * ratioMultiplier;
  17869. } // Ensure the CSS class is valid by starting with an alpha character
  17870. if (/^[^a-zA-Z]/.test(this.id())) {
  17871. idClass = 'dimensions-' + this.id();
  17872. } else {
  17873. idClass = this.id() + '-dimensions';
  17874. } // Ensure the right class is still on the player for the style element
  17875. this.addClass(idClass);
  17876. 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 ");
  17877. }
  17878. /**
  17879. * Load/Create an instance of playback {@link Tech} including element
  17880. * and API methods. Then append the `Tech` element in `Player` as a child.
  17881. *
  17882. * @param {string} techName
  17883. * name of the playback technology
  17884. *
  17885. * @param {string} source
  17886. * video source
  17887. *
  17888. * @private
  17889. */
  17890. ;
  17891. _proto.loadTech_ = function loadTech_(techName, source) {
  17892. var _this3 = this;
  17893. // Pause and remove current playback technology
  17894. if (this.tech_) {
  17895. this.unloadTech_();
  17896. }
  17897. var titleTechName = toTitleCase(techName);
  17898. var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
  17899. if (titleTechName !== 'Html5' && this.tag) {
  17900. Tech.getTech('Html5').disposeMediaElement(this.tag);
  17901. this.tag.player = null;
  17902. this.tag = null;
  17903. }
  17904. this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
  17905. this.isReady_ = false; // if autoplay is a string we pass false to the tech
  17906. // because the player is going to handle autoplay on `loadstart`
  17907. var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use.
  17908. var techOptions = {
  17909. source: source,
  17910. autoplay: autoplay,
  17911. 'nativeControlsForTouch': this.options_.nativeControlsForTouch,
  17912. 'playerId': this.id(),
  17913. 'techId': this.id() + "_" + camelTechName + "_api",
  17914. 'playsinline': this.options_.playsinline,
  17915. 'preload': this.options_.preload,
  17916. 'loop': this.options_.loop,
  17917. 'muted': this.options_.muted,
  17918. 'poster': this.poster(),
  17919. 'language': this.language(),
  17920. 'playerElIngest': this.playerElIngest_ || false,
  17921. 'vtt.js': this.options_['vtt.js'],
  17922. 'canOverridePoster': !!this.options_.techCanOverridePoster,
  17923. 'enableSourceset': this.options_.enableSourceset
  17924. };
  17925. ALL.names.forEach(function (name$$1) {
  17926. var props = ALL[name$$1];
  17927. techOptions[props.getterName] = _this3[props.privateName];
  17928. });
  17929. assign(techOptions, this.options_[titleTechName]);
  17930. assign(techOptions, this.options_[camelTechName]);
  17931. assign(techOptions, this.options_[techName.toLowerCase()]);
  17932. if (this.tag) {
  17933. techOptions.tag = this.tag;
  17934. }
  17935. if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
  17936. techOptions.startTime = this.cache_.currentTime;
  17937. } // Initialize tech instance
  17938. var TechClass = Tech.getTech(techName);
  17939. if (!TechClass) {
  17940. throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
  17941. }
  17942. this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
  17943. this.tech_.ready(bind(this, this.handleTechReady_), true);
  17944. textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
  17945. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  17946. _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]);
  17947. });
  17948. Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
  17949. _this3.on(_this3.tech_, event, function (eventObj) {
  17950. if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) {
  17951. _this3.queuedCallbacks_.push({
  17952. callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3),
  17953. event: eventObj
  17954. });
  17955. return;
  17956. }
  17957. _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
  17958. });
  17959. });
  17960. this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
  17961. this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
  17962. this.on(this.tech_, 'waiting', this.handleTechWaiting_);
  17963. this.on(this.tech_, 'ended', this.handleTechEnded_);
  17964. this.on(this.tech_, 'seeking', this.handleTechSeeking_);
  17965. this.on(this.tech_, 'play', this.handleTechPlay_);
  17966. this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
  17967. this.on(this.tech_, 'pause', this.handleTechPause_);
  17968. this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
  17969. this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
  17970. this.on(this.tech_, 'error', this.handleTechError_);
  17971. this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
  17972. this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
  17973. this.on(this.tech_, 'textdata', this.handleTechTextData_);
  17974. this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
  17975. this.usingNativeControls(this.techGet_('controls'));
  17976. if (this.controls() && !this.usingNativeControls()) {
  17977. this.addTechControlsListeners_();
  17978. } // Add the tech element in the DOM if it was not already there
  17979. // Make sure to not insert the original video element if using Html5
  17980. if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
  17981. prependTo(this.tech_.el(), this.el());
  17982. } // Get rid of the original video tag reference after the first tech is loaded
  17983. if (this.tag) {
  17984. this.tag.player = null;
  17985. this.tag = null;
  17986. }
  17987. }
  17988. /**
  17989. * Unload and dispose of the current playback {@link Tech}.
  17990. *
  17991. * @private
  17992. */
  17993. ;
  17994. _proto.unloadTech_ = function unloadTech_() {
  17995. var _this4 = this;
  17996. // Save the current text tracks so that we can reuse the same text tracks with the next tech
  17997. ALL.names.forEach(function (name$$1) {
  17998. var props = ALL[name$$1];
  17999. _this4[props.privateName] = _this4[props.getterName]();
  18000. });
  18001. this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
  18002. this.isReady_ = false;
  18003. this.tech_.dispose();
  18004. this.tech_ = false;
  18005. if (this.isPosterFromTech_) {
  18006. this.poster_ = '';
  18007. this.trigger('posterchange');
  18008. }
  18009. this.isPosterFromTech_ = false;
  18010. }
  18011. /**
  18012. * Return a reference to the current {@link Tech}.
  18013. * It will print a warning by default about the danger of using the tech directly
  18014. * but any argument that is passed in will silence the warning.
  18015. *
  18016. * @param {*} [safety]
  18017. * Anything passed in to silence the warning
  18018. *
  18019. * @return {Tech}
  18020. * The Tech
  18021. */
  18022. ;
  18023. _proto.tech = function tech(safety) {
  18024. if (safety === undefined) {
  18025. log.warn(tsml(_templateObject$2()));
  18026. }
  18027. return this.tech_;
  18028. }
  18029. /**
  18030. * Set up click and touch listeners for the playback element
  18031. *
  18032. * - On desktops: a click on the video itself will toggle playback
  18033. * - On mobile devices: a click on the video toggles controls
  18034. * which is done by toggling the user state between active and
  18035. * inactive
  18036. * - A tap can signal that a user has become active or has become inactive
  18037. * e.g. a quick tap on an iPhone movie should reveal the controls. Another
  18038. * quick tap should hide them again (signaling the user is in an inactive
  18039. * viewing state)
  18040. * - In addition to this, we still want the user to be considered inactive after
  18041. * a few seconds of inactivity.
  18042. *
  18043. * > Note: the only part of iOS interaction we can't mimic with this setup
  18044. * is a touch and hold on the video element counting as activity in order to
  18045. * keep the controls showing, but that shouldn't be an issue. A touch and hold
  18046. * on any controls will still keep the user active
  18047. *
  18048. * @private
  18049. */
  18050. ;
  18051. _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
  18052. // Make sure to remove all the previous listeners in case we are called multiple times.
  18053. this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
  18054. // trigger mousedown/up.
  18055. // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
  18056. // Any touch events are set to block the mousedown event from happening
  18057. this.on(this.tech_, 'mousedown', this.handleTechClick_);
  18058. this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
  18059. // so we'll check if the controls were already showing before reporting user
  18060. // activity
  18061. this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
  18062. this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
  18063. this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
  18064. // listener cancels out any reportedUserActivity when setting userActive(false)
  18065. this.on(this.tech_, 'tap', this.handleTechTap_);
  18066. }
  18067. /**
  18068. * Remove the listeners used for click and tap controls. This is needed for
  18069. * toggling to controls disabled, where a tap/touch should do nothing.
  18070. *
  18071. * @private
  18072. */
  18073. ;
  18074. _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
  18075. // We don't want to just use `this.off()` because there might be other needed
  18076. // listeners added by techs that extend this.
  18077. this.off(this.tech_, 'tap', this.handleTechTap_);
  18078. this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
  18079. this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
  18080. this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
  18081. this.off(this.tech_, 'mousedown', this.handleTechClick_);
  18082. this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
  18083. }
  18084. /**
  18085. * Player waits for the tech to be ready
  18086. *
  18087. * @private
  18088. */
  18089. ;
  18090. _proto.handleTechReady_ = function handleTechReady_() {
  18091. this.triggerReady(); // Keep the same volume as before
  18092. if (this.cache_.volume) {
  18093. this.techCall_('setVolume', this.cache_.volume);
  18094. } // Look if the tech found a higher resolution poster while loading
  18095. this.handleTechPosterChange_(); // Update the duration if available
  18096. this.handleTechDurationChange_();
  18097. }
  18098. /**
  18099. * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
  18100. * function will also trigger {@link Player#firstplay} if it is the first loadstart
  18101. * for a video.
  18102. *
  18103. * @fires Player#loadstart
  18104. * @fires Player#firstplay
  18105. * @listens Tech#loadstart
  18106. * @private
  18107. */
  18108. ;
  18109. _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
  18110. // TODO: Update to use `emptied` event instead. See #1277.
  18111. this.removeClass('vjs-ended');
  18112. this.removeClass('vjs-seeking'); // reset the error state
  18113. this.error(null); // Update the duration
  18114. this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
  18115. // The firstplay event relies on both the play and loadstart events
  18116. // which can happen in any order for a new source
  18117. if (!this.paused()) {
  18118. /**
  18119. * Fired when the user agent begins looking for media data
  18120. *
  18121. * @event Player#loadstart
  18122. * @type {EventTarget~Event}
  18123. */
  18124. this.trigger('loadstart');
  18125. this.trigger('firstplay');
  18126. } else {
  18127. // reset the hasStarted state
  18128. this.hasStarted(false);
  18129. this.trigger('loadstart');
  18130. } // autoplay happens after loadstart for the browser,
  18131. // so we mimic that behavior
  18132. this.manualAutoplay_(this.autoplay());
  18133. }
  18134. /**
  18135. * Handle autoplay string values, rather than the typical boolean
  18136. * values that should be handled by the tech. Note that this is not
  18137. * part of any specification. Valid values and what they do can be
  18138. * found on the autoplay getter at Player#autoplay()
  18139. */
  18140. ;
  18141. _proto.manualAutoplay_ = function manualAutoplay_(type) {
  18142. var _this5 = this;
  18143. if (!this.tech_ || typeof type !== 'string') {
  18144. return;
  18145. }
  18146. var muted = function muted() {
  18147. var previouslyMuted = _this5.muted();
  18148. _this5.muted(true);
  18149. var restoreMuted = function restoreMuted() {
  18150. _this5.muted(previouslyMuted);
  18151. }; // restore muted on play terminatation
  18152. _this5.playTerminatedQueue_.push(restoreMuted);
  18153. var mutedPromise = _this5.play();
  18154. if (!isPromise(mutedPromise)) {
  18155. return;
  18156. }
  18157. return mutedPromise.catch(restoreMuted);
  18158. };
  18159. var promise; // if muted defaults to true
  18160. // the only thing we can do is call play
  18161. if (type === 'any' && this.muted() !== true) {
  18162. promise = this.play();
  18163. if (isPromise(promise)) {
  18164. promise = promise.catch(muted);
  18165. }
  18166. } else if (type === 'muted' && this.muted() !== true) {
  18167. promise = muted();
  18168. } else {
  18169. promise = this.play();
  18170. }
  18171. if (!isPromise(promise)) {
  18172. return;
  18173. }
  18174. return promise.then(function () {
  18175. _this5.trigger({
  18176. type: 'autoplay-success',
  18177. autoplay: type
  18178. });
  18179. }).catch(function (e) {
  18180. _this5.trigger({
  18181. type: 'autoplay-failure',
  18182. autoplay: type
  18183. });
  18184. });
  18185. }
  18186. /**
  18187. * Update the internal source caches so that we return the correct source from
  18188. * `src()`, `currentSource()`, and `currentSources()`.
  18189. *
  18190. * > Note: `currentSources` will not be updated if the source that is passed in exists
  18191. * in the current `currentSources` cache.
  18192. *
  18193. *
  18194. * @param {Tech~SourceObject} srcObj
  18195. * A string or object source to update our caches to.
  18196. */
  18197. ;
  18198. _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
  18199. if (srcObj === void 0) {
  18200. srcObj = '';
  18201. }
  18202. var src = srcObj;
  18203. var type = '';
  18204. if (typeof src !== 'string') {
  18205. src = srcObj.src;
  18206. type = srcObj.type;
  18207. } // make sure all the caches are set to default values
  18208. // to prevent null checking
  18209. this.cache_.source = this.cache_.source || {};
  18210. this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
  18211. if (src && !type) {
  18212. type = findMimetype(this, src);
  18213. } // update `currentSource` cache always
  18214. this.cache_.source = mergeOptions({}, srcObj, {
  18215. src: src,
  18216. type: type
  18217. });
  18218. var matchingSources = this.cache_.sources.filter(function (s) {
  18219. return s.src && s.src === src;
  18220. });
  18221. var sourceElSources = [];
  18222. var sourceEls = this.$$('source');
  18223. var matchingSourceEls = [];
  18224. for (var i = 0; i < sourceEls.length; i++) {
  18225. var sourceObj = getAttributes(sourceEls[i]);
  18226. sourceElSources.push(sourceObj);
  18227. if (sourceObj.src && sourceObj.src === src) {
  18228. matchingSourceEls.push(sourceObj.src);
  18229. }
  18230. } // if we have matching source els but not matching sources
  18231. // the current source cache is not up to date
  18232. if (matchingSourceEls.length && !matchingSources.length) {
  18233. this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
  18234. // sources cache to the `currentSource` cache
  18235. } else if (!matchingSources.length) {
  18236. this.cache_.sources = [this.cache_.source];
  18237. } // update the tech `src` cache
  18238. this.cache_.src = src;
  18239. }
  18240. /**
  18241. * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
  18242. * causing the media element to reload.
  18243. *
  18244. * It will fire for the initial source and each subsequent source.
  18245. * This event is a custom event from Video.js and is triggered by the {@link Tech}.
  18246. *
  18247. * The event object for this event contains a `src` property that will contain the source
  18248. * that was available when the event was triggered. This is generally only necessary if Video.js
  18249. * is switching techs while the source was being changed.
  18250. *
  18251. * It is also fired when `load` is called on the player (or media element)
  18252. * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
  18253. * says that the resource selection algorithm needs to be aborted and restarted.
  18254. * In this case, it is very likely that the `src` property will be set to the
  18255. * empty string `""` to indicate we do not know what the source will be but
  18256. * that it is changing.
  18257. *
  18258. * *This event is currently still experimental and may change in minor releases.*
  18259. * __To use this, pass `enableSourceset` option to the player.__
  18260. *
  18261. * @event Player#sourceset
  18262. * @type {EventTarget~Event}
  18263. * @prop {string} src
  18264. * The source url available when the `sourceset` was triggered.
  18265. * It will be an empty string if we cannot know what the source is
  18266. * but know that the source will change.
  18267. */
  18268. /**
  18269. * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
  18270. *
  18271. * @fires Player#sourceset
  18272. * @listens Tech#sourceset
  18273. * @private
  18274. */
  18275. ;
  18276. _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
  18277. var _this6 = this;
  18278. // only update the source cache when the source
  18279. // was not updated using the player api
  18280. if (!this.changingSrc_) {
  18281. var updateSourceCaches = function updateSourceCaches(src) {
  18282. return _this6.updateSourceCaches_(src);
  18283. };
  18284. var playerSrc = this.currentSource().src;
  18285. var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
  18286. if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
  18287. // if both the tech source and the player source were updated we assume
  18288. // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
  18289. if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
  18290. updateSourceCaches = function updateSourceCaches() {};
  18291. }
  18292. } // update the source to the intial source right away
  18293. // in some cases this will be empty string
  18294. updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
  18295. // wait for a `loadstart` to update the cache to `currentSrc`.
  18296. // If a sourceset happens before a `loadstart`, we reset the state
  18297. // as this function will be called again.
  18298. if (!event.src) {
  18299. var updateCache = function updateCache(e) {
  18300. if (e.type !== 'sourceset') {
  18301. var techSrc = _this6.techGet('currentSrc');
  18302. _this6.lastSource_.tech = techSrc;
  18303. _this6.updateSourceCaches_(techSrc);
  18304. }
  18305. _this6.tech_.off(['sourceset', 'loadstart'], updateCache);
  18306. };
  18307. this.tech_.one(['sourceset', 'loadstart'], updateCache);
  18308. }
  18309. }
  18310. this.lastSource_ = {
  18311. player: this.currentSource().src,
  18312. tech: event.src
  18313. };
  18314. this.trigger({
  18315. src: event.src,
  18316. type: 'sourceset'
  18317. });
  18318. }
  18319. /**
  18320. * Add/remove the vjs-has-started class
  18321. *
  18322. * @fires Player#firstplay
  18323. *
  18324. * @param {boolean} request
  18325. * - true: adds the class
  18326. * - false: remove the class
  18327. *
  18328. * @return {boolean}
  18329. * the boolean value of hasStarted_
  18330. */
  18331. ;
  18332. _proto.hasStarted = function hasStarted(request) {
  18333. if (request === undefined) {
  18334. // act as getter, if we have no request to change
  18335. return this.hasStarted_;
  18336. }
  18337. if (request === this.hasStarted_) {
  18338. return;
  18339. }
  18340. this.hasStarted_ = request;
  18341. if (this.hasStarted_) {
  18342. this.addClass('vjs-has-started');
  18343. this.trigger('firstplay');
  18344. } else {
  18345. this.removeClass('vjs-has-started');
  18346. }
  18347. }
  18348. /**
  18349. * Fired whenever the media begins or resumes playback
  18350. *
  18351. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
  18352. * @fires Player#play
  18353. * @listens Tech#play
  18354. * @private
  18355. */
  18356. ;
  18357. _proto.handleTechPlay_ = function handleTechPlay_() {
  18358. this.removeClass('vjs-ended');
  18359. this.removeClass('vjs-paused');
  18360. this.addClass('vjs-playing'); // hide the poster when the user hits play
  18361. this.hasStarted(true);
  18362. /**
  18363. * Triggered whenever an {@link Tech#play} event happens. Indicates that
  18364. * playback has started or resumed.
  18365. *
  18366. * @event Player#play
  18367. * @type {EventTarget~Event}
  18368. */
  18369. this.trigger('play');
  18370. }
  18371. /**
  18372. * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
  18373. *
  18374. * If there were any events queued while the playback rate was zero, fire
  18375. * those events now.
  18376. *
  18377. * @private
  18378. * @method Player#handleTechRateChange_
  18379. * @fires Player#ratechange
  18380. * @listens Tech#ratechange
  18381. */
  18382. ;
  18383. _proto.handleTechRateChange_ = function handleTechRateChange_() {
  18384. if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
  18385. this.queuedCallbacks_.forEach(function (queued) {
  18386. return queued.callback(queued.event);
  18387. });
  18388. this.queuedCallbacks_ = [];
  18389. }
  18390. this.cache_.lastPlaybackRate = this.tech_.playbackRate();
  18391. /**
  18392. * Fires when the playing speed of the audio/video is changed
  18393. *
  18394. * @event Player#ratechange
  18395. * @type {event}
  18396. */
  18397. this.trigger('ratechange');
  18398. }
  18399. /**
  18400. * Retrigger the `waiting` event that was triggered by the {@link Tech}.
  18401. *
  18402. * @fires Player#waiting
  18403. * @listens Tech#waiting
  18404. * @private
  18405. */
  18406. ;
  18407. _proto.handleTechWaiting_ = function handleTechWaiting_() {
  18408. var _this7 = this;
  18409. this.addClass('vjs-waiting');
  18410. /**
  18411. * A readyState change on the DOM element has caused playback to stop.
  18412. *
  18413. * @event Player#waiting
  18414. * @type {EventTarget~Event}
  18415. */
  18416. this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
  18417. // premature removal of the waiting class, wait for the time to change.
  18418. var timeWhenWaiting = this.currentTime();
  18419. var timeUpdateListener = function timeUpdateListener() {
  18420. if (timeWhenWaiting !== _this7.currentTime()) {
  18421. _this7.removeClass('vjs-waiting');
  18422. _this7.off('timeupdate', timeUpdateListener);
  18423. }
  18424. };
  18425. this.on('timeupdate', timeUpdateListener);
  18426. }
  18427. /**
  18428. * Retrigger the `canplay` event that was triggered by the {@link Tech}.
  18429. * > Note: This is not consistent between browsers. See #1351
  18430. *
  18431. * @fires Player#canplay
  18432. * @listens Tech#canplay
  18433. * @private
  18434. */
  18435. ;
  18436. _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
  18437. this.removeClass('vjs-waiting');
  18438. /**
  18439. * The media has a readyState of HAVE_FUTURE_DATA or greater.
  18440. *
  18441. * @event Player#canplay
  18442. * @type {EventTarget~Event}
  18443. */
  18444. this.trigger('canplay');
  18445. }
  18446. /**
  18447. * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
  18448. *
  18449. * @fires Player#canplaythrough
  18450. * @listens Tech#canplaythrough
  18451. * @private
  18452. */
  18453. ;
  18454. _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
  18455. this.removeClass('vjs-waiting');
  18456. /**
  18457. * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
  18458. * entire media file can be played without buffering.
  18459. *
  18460. * @event Player#canplaythrough
  18461. * @type {EventTarget~Event}
  18462. */
  18463. this.trigger('canplaythrough');
  18464. }
  18465. /**
  18466. * Retrigger the `playing` event that was triggered by the {@link Tech}.
  18467. *
  18468. * @fires Player#playing
  18469. * @listens Tech#playing
  18470. * @private
  18471. */
  18472. ;
  18473. _proto.handleTechPlaying_ = function handleTechPlaying_() {
  18474. this.removeClass('vjs-waiting');
  18475. /**
  18476. * The media is no longer blocked from playback, and has started playing.
  18477. *
  18478. * @event Player#playing
  18479. * @type {EventTarget~Event}
  18480. */
  18481. this.trigger('playing');
  18482. }
  18483. /**
  18484. * Retrigger the `seeking` event that was triggered by the {@link Tech}.
  18485. *
  18486. * @fires Player#seeking
  18487. * @listens Tech#seeking
  18488. * @private
  18489. */
  18490. ;
  18491. _proto.handleTechSeeking_ = function handleTechSeeking_() {
  18492. this.addClass('vjs-seeking');
  18493. /**
  18494. * Fired whenever the player is jumping to a new time
  18495. *
  18496. * @event Player#seeking
  18497. * @type {EventTarget~Event}
  18498. */
  18499. this.trigger('seeking');
  18500. }
  18501. /**
  18502. * Retrigger the `seeked` event that was triggered by the {@link Tech}.
  18503. *
  18504. * @fires Player#seeked
  18505. * @listens Tech#seeked
  18506. * @private
  18507. */
  18508. ;
  18509. _proto.handleTechSeeked_ = function handleTechSeeked_() {
  18510. this.removeClass('vjs-seeking');
  18511. this.removeClass('vjs-ended');
  18512. /**
  18513. * Fired when the player has finished jumping to a new time
  18514. *
  18515. * @event Player#seeked
  18516. * @type {EventTarget~Event}
  18517. */
  18518. this.trigger('seeked');
  18519. }
  18520. /**
  18521. * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
  18522. *
  18523. * @fires Player#firstplay
  18524. * @listens Tech#firstplay
  18525. * @deprecated As of 6.0 firstplay event is deprecated.
  18526. * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
  18527. * @private
  18528. */
  18529. ;
  18530. _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
  18531. // If the first starttime attribute is specified
  18532. // then we will start at the given offset in seconds
  18533. if (this.options_.starttime) {
  18534. log.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
  18535. this.currentTime(this.options_.starttime);
  18536. }
  18537. this.addClass('vjs-has-started');
  18538. /**
  18539. * Fired the first time a video is played. Not part of the HLS spec, and this is
  18540. * probably not the best implementation yet, so use sparingly. If you don't have a
  18541. * reason to prevent playback, use `myPlayer.one('play');` instead.
  18542. *
  18543. * @event Player#firstplay
  18544. * @deprecated As of 6.0 firstplay event is deprecated.
  18545. * @type {EventTarget~Event}
  18546. */
  18547. this.trigger('firstplay');
  18548. }
  18549. /**
  18550. * Retrigger the `pause` event that was triggered by the {@link Tech}.
  18551. *
  18552. * @fires Player#pause
  18553. * @listens Tech#pause
  18554. * @private
  18555. */
  18556. ;
  18557. _proto.handleTechPause_ = function handleTechPause_() {
  18558. this.removeClass('vjs-playing');
  18559. this.addClass('vjs-paused');
  18560. /**
  18561. * Fired whenever the media has been paused
  18562. *
  18563. * @event Player#pause
  18564. * @type {EventTarget~Event}
  18565. */
  18566. this.trigger('pause');
  18567. }
  18568. /**
  18569. * Retrigger the `ended` event that was triggered by the {@link Tech}.
  18570. *
  18571. * @fires Player#ended
  18572. * @listens Tech#ended
  18573. * @private
  18574. */
  18575. ;
  18576. _proto.handleTechEnded_ = function handleTechEnded_() {
  18577. this.addClass('vjs-ended');
  18578. if (this.options_.loop) {
  18579. this.currentTime(0);
  18580. this.play();
  18581. } else if (!this.paused()) {
  18582. this.pause();
  18583. }
  18584. /**
  18585. * Fired when the end of the media resource is reached (currentTime == duration)
  18586. *
  18587. * @event Player#ended
  18588. * @type {EventTarget~Event}
  18589. */
  18590. this.trigger('ended');
  18591. }
  18592. /**
  18593. * Fired when the duration of the media resource is first known or changed
  18594. *
  18595. * @listens Tech#durationchange
  18596. * @private
  18597. */
  18598. ;
  18599. _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
  18600. this.duration(this.techGet_('duration'));
  18601. }
  18602. /**
  18603. * Handle a click on the media element to play/pause
  18604. *
  18605. * @param {EventTarget~Event} event
  18606. * the event that caused this function to trigger
  18607. *
  18608. * @listens Tech#mousedown
  18609. * @private
  18610. */
  18611. ;
  18612. _proto.handleTechClick_ = function handleTechClick_(event) {
  18613. if (!isSingleLeftClick(event)) {
  18614. return;
  18615. } // When controls are disabled a click should not toggle playback because
  18616. // the click is considered a control
  18617. if (!this.controls_) {
  18618. return;
  18619. }
  18620. if (this.paused()) {
  18621. silencePromise(this.play());
  18622. } else {
  18623. this.pause();
  18624. }
  18625. }
  18626. /**
  18627. * Handle a double-click on the media element to enter/exit fullscreen
  18628. *
  18629. * @param {EventTarget~Event} event
  18630. * the event that caused this function to trigger
  18631. *
  18632. * @listens Tech#dblclick
  18633. * @private
  18634. */
  18635. ;
  18636. _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
  18637. if (!this.controls_) {
  18638. return;
  18639. } // we do not want to toggle fullscreen state
  18640. // when double-clicking inside a control bar or a modal
  18641. var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
  18642. return el.contains(event.target);
  18643. });
  18644. if (!inAllowedEls) {
  18645. /*
  18646. * options.userActions.doubleClick
  18647. *
  18648. * If `undefined` or `true`, double-click toggles fullscreen if controls are present
  18649. * Set to `false` to disable double-click handling
  18650. * Set to a function to substitute an external double-click handler
  18651. */
  18652. if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
  18653. if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
  18654. this.options_.userActions.doubleClick.call(this, event);
  18655. } else if (this.isFullscreen()) {
  18656. this.exitFullscreen();
  18657. } else {
  18658. this.requestFullscreen();
  18659. }
  18660. }
  18661. }
  18662. }
  18663. /**
  18664. * Handle a tap on the media element. It will toggle the user
  18665. * activity state, which hides and shows the controls.
  18666. *
  18667. * @listens Tech#tap
  18668. * @private
  18669. */
  18670. ;
  18671. _proto.handleTechTap_ = function handleTechTap_() {
  18672. this.userActive(!this.userActive());
  18673. }
  18674. /**
  18675. * Handle touch to start
  18676. *
  18677. * @listens Tech#touchstart
  18678. * @private
  18679. */
  18680. ;
  18681. _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
  18682. this.userWasActive = this.userActive();
  18683. }
  18684. /**
  18685. * Handle touch to move
  18686. *
  18687. * @listens Tech#touchmove
  18688. * @private
  18689. */
  18690. ;
  18691. _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
  18692. if (this.userWasActive) {
  18693. this.reportUserActivity();
  18694. }
  18695. }
  18696. /**
  18697. * Handle touch to end
  18698. *
  18699. * @param {EventTarget~Event} event
  18700. * the touchend event that triggered
  18701. * this function
  18702. *
  18703. * @listens Tech#touchend
  18704. * @private
  18705. */
  18706. ;
  18707. _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
  18708. // Stop the mouse events from also happening
  18709. event.preventDefault();
  18710. }
  18711. /**
  18712. * native click events on the SWF aren't triggered on IE11, Win8.1RT
  18713. * use stageclick events triggered from inside the SWF instead
  18714. *
  18715. * @private
  18716. * @listens stageclick
  18717. */
  18718. ;
  18719. _proto.handleStageClick_ = function handleStageClick_() {
  18720. this.reportUserActivity();
  18721. }
  18722. /**
  18723. * @private
  18724. */
  18725. ;
  18726. _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
  18727. if (this.isFullscreen()) {
  18728. this.addClass('vjs-fullscreen');
  18729. } else {
  18730. this.removeClass('vjs-fullscreen');
  18731. }
  18732. }
  18733. /**
  18734. * when the document fschange event triggers it calls this
  18735. */
  18736. ;
  18737. _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
  18738. var fsApi = FullscreenApi;
  18739. this.isFullscreen(document[fsApi.fullscreenElement] === this.el() || this.el().matches(':' + fsApi.fullscreen)); // If cancelling fullscreen, remove event listener.
  18740. if (this.isFullscreen() === false) {
  18741. off(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  18742. }
  18743. if (!prefixedAPI) {
  18744. /**
  18745. * @event Player#fullscreenchange
  18746. * @type {EventTarget~Event}
  18747. */
  18748. this.trigger('fullscreenchange');
  18749. }
  18750. }
  18751. /**
  18752. * Handle Tech Fullscreen Change
  18753. *
  18754. * @param {EventTarget~Event} event
  18755. * the fullscreenchange event that triggered this function
  18756. *
  18757. * @param {Object} data
  18758. * the data that was sent with the event
  18759. *
  18760. * @private
  18761. * @listens Tech#fullscreenchange
  18762. * @fires Player#fullscreenchange
  18763. */
  18764. ;
  18765. _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
  18766. if (data) {
  18767. this.isFullscreen(data.isFullscreen);
  18768. }
  18769. /**
  18770. * Fired when going in and out of fullscreen.
  18771. *
  18772. * @event Player#fullscreenchange
  18773. * @type {EventTarget~Event}
  18774. */
  18775. this.trigger('fullscreenchange');
  18776. }
  18777. /**
  18778. * Fires when an error occurred during the loading of an audio/video.
  18779. *
  18780. * @private
  18781. * @listens Tech#error
  18782. */
  18783. ;
  18784. _proto.handleTechError_ = function handleTechError_() {
  18785. var error = this.tech_.error();
  18786. this.error(error);
  18787. }
  18788. /**
  18789. * Retrigger the `textdata` event that was triggered by the {@link Tech}.
  18790. *
  18791. * @fires Player#textdata
  18792. * @listens Tech#textdata
  18793. * @private
  18794. */
  18795. ;
  18796. _proto.handleTechTextData_ = function handleTechTextData_() {
  18797. var data = null;
  18798. if (arguments.length > 1) {
  18799. data = arguments[1];
  18800. }
  18801. /**
  18802. * Fires when we get a textdata event from tech
  18803. *
  18804. * @event Player#textdata
  18805. * @type {EventTarget~Event}
  18806. */
  18807. this.trigger('textdata', data);
  18808. }
  18809. /**
  18810. * Get object for cached values.
  18811. *
  18812. * @return {Object}
  18813. * get the current object cache
  18814. */
  18815. ;
  18816. _proto.getCache = function getCache() {
  18817. return this.cache_;
  18818. }
  18819. /**
  18820. * Resets the internal cache object.
  18821. *
  18822. * Using this function outside the player constructor or reset method may
  18823. * have unintended side-effects.
  18824. *
  18825. * @private
  18826. */
  18827. ;
  18828. _proto.resetCache_ = function resetCache_() {
  18829. this.cache_ = {
  18830. // Right now, the currentTime is not _really_ cached because it is always
  18831. // retrieved from the tech (see: currentTime). However, for completeness,
  18832. // we set it to zero here to ensure that if we do start actually caching
  18833. // it, we reset it along with everything else.
  18834. currentTime: 0,
  18835. inactivityTimeout: this.options_.inactivityTimeout,
  18836. duration: NaN,
  18837. lastVolume: 1,
  18838. lastPlaybackRate: this.defaultPlaybackRate(),
  18839. media: null,
  18840. src: '',
  18841. source: {},
  18842. sources: [],
  18843. volume: 1
  18844. };
  18845. }
  18846. /**
  18847. * Pass values to the playback tech
  18848. *
  18849. * @param {string} [method]
  18850. * the method to call
  18851. *
  18852. * @param {Object} arg
  18853. * the argument to pass
  18854. *
  18855. * @private
  18856. */
  18857. ;
  18858. _proto.techCall_ = function techCall_(method, arg) {
  18859. // If it's not ready yet, call method when it is
  18860. this.ready(function () {
  18861. if (method in allowedSetters) {
  18862. return set$1(this.middleware_, this.tech_, method, arg);
  18863. } else if (method in allowedMediators) {
  18864. return mediate(this.middleware_, this.tech_, method, arg);
  18865. }
  18866. try {
  18867. if (this.tech_) {
  18868. this.tech_[method](arg);
  18869. }
  18870. } catch (e) {
  18871. log(e);
  18872. throw e;
  18873. }
  18874. }, true);
  18875. }
  18876. /**
  18877. * Get calls can't wait for the tech, and sometimes don't need to.
  18878. *
  18879. * @param {string} method
  18880. * Tech method
  18881. *
  18882. * @return {Function|undefined}
  18883. * the method or undefined
  18884. *
  18885. * @private
  18886. */
  18887. ;
  18888. _proto.techGet_ = function techGet_(method) {
  18889. if (!this.tech_ || !this.tech_.isReady_) {
  18890. return;
  18891. }
  18892. if (method in allowedGetters) {
  18893. return get(this.middleware_, this.tech_, method);
  18894. } else if (method in allowedMediators) {
  18895. return mediate(this.middleware_, this.tech_, method);
  18896. } // Flash likes to die and reload when you hide or reposition it.
  18897. // In these cases the object methods go away and we get errors.
  18898. // When that happens we'll catch the errors and inform tech that it's not ready any more.
  18899. try {
  18900. return this.tech_[method]();
  18901. } catch (e) {
  18902. // When building additional tech libs, an expected method may not be defined yet
  18903. if (this.tech_[method] === undefined) {
  18904. log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
  18905. throw e;
  18906. } // When a method isn't available on the object it throws a TypeError
  18907. if (e.name === 'TypeError') {
  18908. log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
  18909. this.tech_.isReady_ = false;
  18910. throw e;
  18911. } // If error unknown, just log and throw
  18912. log(e);
  18913. throw e;
  18914. }
  18915. }
  18916. /**
  18917. * Attempt to begin playback at the first opportunity.
  18918. *
  18919. * @return {Promise|undefined}
  18920. * Returns a promise if the browser supports Promises (or one
  18921. * was passed in as an option). This promise will be resolved on
  18922. * the return value of play. If this is undefined it will fulfill the
  18923. * promise chain otherwise the promise chain will be fulfilled when
  18924. * the promise from play is fulfilled.
  18925. */
  18926. ;
  18927. _proto.play = function play() {
  18928. var _this8 = this;
  18929. var PromiseClass = this.options_.Promise || window$1.Promise;
  18930. if (PromiseClass) {
  18931. return new PromiseClass(function (resolve) {
  18932. _this8.play_(resolve);
  18933. });
  18934. }
  18935. return this.play_();
  18936. }
  18937. /**
  18938. * The actual logic for play, takes a callback that will be resolved on the
  18939. * return value of play. This allows us to resolve to the play promise if there
  18940. * is one on modern browsers.
  18941. *
  18942. * @private
  18943. * @param {Function} [callback]
  18944. * The callback that should be called when the techs play is actually called
  18945. */
  18946. ;
  18947. _proto.play_ = function play_(callback) {
  18948. var _this9 = this;
  18949. if (callback === void 0) {
  18950. callback = silencePromise;
  18951. }
  18952. this.playCallbacks_.push(callback);
  18953. var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function
  18954. if (this.waitToPlay_) {
  18955. this.off(['ready', 'loadstart'], this.waitToPlay_);
  18956. this.waitToPlay_ = null;
  18957. } // if the player/tech is not ready or the src itself is not ready
  18958. // queue up a call to play on `ready` or `loadstart`
  18959. if (!this.isReady_ || !isSrcReady) {
  18960. this.waitToPlay_ = function (e) {
  18961. _this9.play_();
  18962. };
  18963. this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
  18964. // in that case, we need to prime the video element by calling load so it'll be ready in time
  18965. if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) {
  18966. this.load();
  18967. }
  18968. return;
  18969. } // If the player/tech is ready and we have a source, we can attempt playback.
  18970. var val = this.techGet_('play'); // play was terminated if the returned value is null
  18971. if (val === null) {
  18972. this.runPlayTerminatedQueue_();
  18973. } else {
  18974. this.runPlayCallbacks_(val);
  18975. }
  18976. }
  18977. /**
  18978. * These functions will be run when if play is terminated. If play
  18979. * runPlayCallbacks_ is run these function will not be run. This allows us
  18980. * to differenciate between a terminated play and an actual call to play.
  18981. */
  18982. ;
  18983. _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
  18984. var queue = this.playTerminatedQueue_.slice(0);
  18985. this.playTerminatedQueue_ = [];
  18986. queue.forEach(function (q) {
  18987. q();
  18988. });
  18989. }
  18990. /**
  18991. * When a callback to play is delayed we have to run these
  18992. * callbacks when play is actually called on the tech. This function
  18993. * runs the callbacks that were delayed and accepts the return value
  18994. * from the tech.
  18995. *
  18996. * @param {undefined|Promise} val
  18997. * The return value from the tech.
  18998. */
  18999. ;
  19000. _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
  19001. var callbacks = this.playCallbacks_.slice(0);
  19002. this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
  19003. this.playTerminatedQueue_ = [];
  19004. callbacks.forEach(function (cb) {
  19005. cb(val);
  19006. });
  19007. }
  19008. /**
  19009. * Pause the video playback
  19010. *
  19011. * @return {Player}
  19012. * A reference to the player object this function was called on
  19013. */
  19014. ;
  19015. _proto.pause = function pause() {
  19016. this.techCall_('pause');
  19017. }
  19018. /**
  19019. * Check if the player is paused or has yet to play
  19020. *
  19021. * @return {boolean}
  19022. * - false: if the media is currently playing
  19023. * - true: if media is not currently playing
  19024. */
  19025. ;
  19026. _proto.paused = function paused() {
  19027. // The initial state of paused should be true (in Safari it's actually false)
  19028. return this.techGet_('paused') === false ? false : true;
  19029. }
  19030. /**
  19031. * Get a TimeRange object representing the current ranges of time that the user
  19032. * has played.
  19033. *
  19034. * @return {TimeRange}
  19035. * A time range object that represents all the increments of time that have
  19036. * been played.
  19037. */
  19038. ;
  19039. _proto.played = function played() {
  19040. return this.techGet_('played') || createTimeRanges(0, 0);
  19041. }
  19042. /**
  19043. * Returns whether or not the user is "scrubbing". Scrubbing is
  19044. * when the user has clicked the progress bar handle and is
  19045. * dragging it along the progress bar.
  19046. *
  19047. * @param {boolean} [isScrubbing]
  19048. * whether the user is or is not scrubbing
  19049. *
  19050. * @return {boolean}
  19051. * The value of scrubbing when getting
  19052. */
  19053. ;
  19054. _proto.scrubbing = function scrubbing(isScrubbing) {
  19055. if (typeof isScrubbing === 'undefined') {
  19056. return this.scrubbing_;
  19057. }
  19058. this.scrubbing_ = !!isScrubbing;
  19059. if (isScrubbing) {
  19060. this.addClass('vjs-scrubbing');
  19061. } else {
  19062. this.removeClass('vjs-scrubbing');
  19063. }
  19064. }
  19065. /**
  19066. * Get or set the current time (in seconds)
  19067. *
  19068. * @param {number|string} [seconds]
  19069. * The time to seek to in seconds
  19070. *
  19071. * @return {number}
  19072. * - the current time in seconds when getting
  19073. */
  19074. ;
  19075. _proto.currentTime = function currentTime(seconds) {
  19076. if (typeof seconds !== 'undefined') {
  19077. if (seconds < 0) {
  19078. seconds = 0;
  19079. }
  19080. this.techCall_('setCurrentTime', seconds);
  19081. return;
  19082. } // cache last currentTime and return. default to 0 seconds
  19083. //
  19084. // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
  19085. // currentTime when scrubbing, but may not provide much performance benefit afterall.
  19086. // Should be tested. Also something has to read the actual current time or the cache will
  19087. // never get updated.
  19088. this.cache_.currentTime = this.techGet_('currentTime') || 0;
  19089. return this.cache_.currentTime;
  19090. }
  19091. /**
  19092. * Normally gets the length in time of the video in seconds;
  19093. * in all but the rarest use cases an argument will NOT be passed to the method
  19094. *
  19095. * > **NOTE**: The video must have started loading before the duration can be
  19096. * known, and in the case of Flash, may not be known until the video starts
  19097. * playing.
  19098. *
  19099. * @fires Player#durationchange
  19100. *
  19101. * @param {number} [seconds]
  19102. * The duration of the video to set in seconds
  19103. *
  19104. * @return {number}
  19105. * - The duration of the video in seconds when getting
  19106. */
  19107. ;
  19108. _proto.duration = function duration(seconds) {
  19109. if (seconds === undefined) {
  19110. // return NaN if the duration is not known
  19111. return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
  19112. }
  19113. seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
  19114. if (seconds < 0) {
  19115. seconds = Infinity;
  19116. }
  19117. if (seconds !== this.cache_.duration) {
  19118. // Cache the last set value for optimized scrubbing (esp. Flash)
  19119. this.cache_.duration = seconds;
  19120. if (seconds === Infinity) {
  19121. this.addClass('vjs-live');
  19122. if (this.options_.liveui && this.player_.liveTracker) {
  19123. this.addClass('vjs-liveui');
  19124. }
  19125. } else {
  19126. this.removeClass('vjs-live');
  19127. this.removeClass('vjs-liveui');
  19128. }
  19129. if (!isNaN(seconds)) {
  19130. // Do not fire durationchange unless the duration value is known.
  19131. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  19132. /**
  19133. * @event Player#durationchange
  19134. * @type {EventTarget~Event}
  19135. */
  19136. this.trigger('durationchange');
  19137. }
  19138. }
  19139. }
  19140. /**
  19141. * Calculates how much time is left in the video. Not part
  19142. * of the native video API.
  19143. *
  19144. * @return {number}
  19145. * The time remaining in seconds
  19146. */
  19147. ;
  19148. _proto.remainingTime = function remainingTime() {
  19149. return this.duration() - this.currentTime();
  19150. }
  19151. /**
  19152. * A remaining time function that is intented to be used when
  19153. * the time is to be displayed directly to the user.
  19154. *
  19155. * @return {number}
  19156. * The rounded time remaining in seconds
  19157. */
  19158. ;
  19159. _proto.remainingTimeDisplay = function remainingTimeDisplay() {
  19160. return Math.floor(this.duration()) - Math.floor(this.currentTime());
  19161. } //
  19162. // Kind of like an array of portions of the video that have been downloaded.
  19163. /**
  19164. * Get a TimeRange object with an array of the times of the video
  19165. * that have been downloaded. If you just want the percent of the
  19166. * video that's been downloaded, use bufferedPercent.
  19167. *
  19168. * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
  19169. *
  19170. * @return {TimeRange}
  19171. * A mock TimeRange object (following HTML spec)
  19172. */
  19173. ;
  19174. _proto.buffered = function buffered() {
  19175. var buffered = this.techGet_('buffered');
  19176. if (!buffered || !buffered.length) {
  19177. buffered = createTimeRanges(0, 0);
  19178. }
  19179. return buffered;
  19180. }
  19181. /**
  19182. * Get the percent (as a decimal) of the video that's been downloaded.
  19183. * This method is not a part of the native HTML video API.
  19184. *
  19185. * @return {number}
  19186. * A decimal between 0 and 1 representing the percent
  19187. * that is buffered 0 being 0% and 1 being 100%
  19188. */
  19189. ;
  19190. _proto.bufferedPercent = function bufferedPercent$$1() {
  19191. return bufferedPercent(this.buffered(), this.duration());
  19192. }
  19193. /**
  19194. * Get the ending time of the last buffered time range
  19195. * This is used in the progress bar to encapsulate all time ranges.
  19196. *
  19197. * @return {number}
  19198. * The end of the last buffered time range
  19199. */
  19200. ;
  19201. _proto.bufferedEnd = function bufferedEnd() {
  19202. var buffered = this.buffered();
  19203. var duration = this.duration();
  19204. var end = buffered.end(buffered.length - 1);
  19205. if (end > duration) {
  19206. end = duration;
  19207. }
  19208. return end;
  19209. }
  19210. /**
  19211. * Get or set the current volume of the media
  19212. *
  19213. * @param {number} [percentAsDecimal]
  19214. * The new volume as a decimal percent:
  19215. * - 0 is muted/0%/off
  19216. * - 1.0 is 100%/full
  19217. * - 0.5 is half volume or 50%
  19218. *
  19219. * @return {number}
  19220. * The current volume as a percent when getting
  19221. */
  19222. ;
  19223. _proto.volume = function volume(percentAsDecimal) {
  19224. var vol;
  19225. if (percentAsDecimal !== undefined) {
  19226. // Force value to between 0 and 1
  19227. vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
  19228. this.cache_.volume = vol;
  19229. this.techCall_('setVolume', vol);
  19230. if (vol > 0) {
  19231. this.lastVolume_(vol);
  19232. }
  19233. return;
  19234. } // Default to 1 when returning current volume.
  19235. vol = parseFloat(this.techGet_('volume'));
  19236. return isNaN(vol) ? 1 : vol;
  19237. }
  19238. /**
  19239. * Get the current muted state, or turn mute on or off
  19240. *
  19241. * @param {boolean} [muted]
  19242. * - true to mute
  19243. * - false to unmute
  19244. *
  19245. * @return {boolean}
  19246. * - true if mute is on and getting
  19247. * - false if mute is off and getting
  19248. */
  19249. ;
  19250. _proto.muted = function muted(_muted) {
  19251. if (_muted !== undefined) {
  19252. this.techCall_('setMuted', _muted);
  19253. return;
  19254. }
  19255. return this.techGet_('muted') || false;
  19256. }
  19257. /**
  19258. * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
  19259. * indicates the state of muted on initial playback.
  19260. *
  19261. * ```js
  19262. * var myPlayer = videojs('some-player-id');
  19263. *
  19264. * myPlayer.src("http://www.example.com/path/to/video.mp4");
  19265. *
  19266. * // get, should be false
  19267. * console.log(myPlayer.defaultMuted());
  19268. * // set to true
  19269. * myPlayer.defaultMuted(true);
  19270. * // get should be true
  19271. * console.log(myPlayer.defaultMuted());
  19272. * ```
  19273. *
  19274. * @param {boolean} [defaultMuted]
  19275. * - true to mute
  19276. * - false to unmute
  19277. *
  19278. * @return {boolean|Player}
  19279. * - true if defaultMuted is on and getting
  19280. * - false if defaultMuted is off and getting
  19281. * - A reference to the current player when setting
  19282. */
  19283. ;
  19284. _proto.defaultMuted = function defaultMuted(_defaultMuted) {
  19285. if (_defaultMuted !== undefined) {
  19286. return this.techCall_('setDefaultMuted', _defaultMuted);
  19287. }
  19288. return this.techGet_('defaultMuted') || false;
  19289. }
  19290. /**
  19291. * Get the last volume, or set it
  19292. *
  19293. * @param {number} [percentAsDecimal]
  19294. * The new last volume as a decimal percent:
  19295. * - 0 is muted/0%/off
  19296. * - 1.0 is 100%/full
  19297. * - 0.5 is half volume or 50%
  19298. *
  19299. * @return {number}
  19300. * the current value of lastVolume as a percent when getting
  19301. *
  19302. * @private
  19303. */
  19304. ;
  19305. _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
  19306. if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
  19307. this.cache_.lastVolume = percentAsDecimal;
  19308. return;
  19309. }
  19310. return this.cache_.lastVolume;
  19311. }
  19312. /**
  19313. * Check if current tech can support native fullscreen
  19314. * (e.g. with built in controls like iOS, so not our flash swf)
  19315. *
  19316. * @return {boolean}
  19317. * if native fullscreen is supported
  19318. */
  19319. ;
  19320. _proto.supportsFullScreen = function supportsFullScreen() {
  19321. return this.techGet_('supportsFullScreen') || false;
  19322. }
  19323. /**
  19324. * Check if the player is in fullscreen mode or tell the player that it
  19325. * is or is not in fullscreen mode.
  19326. *
  19327. * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
  19328. * property and instead document.fullscreenElement is used. But isFullscreen is
  19329. * still a valuable property for internal player workings.
  19330. *
  19331. * @param {boolean} [isFS]
  19332. * Set the players current fullscreen state
  19333. *
  19334. * @return {boolean}
  19335. * - true if fullscreen is on and getting
  19336. * - false if fullscreen is off and getting
  19337. */
  19338. ;
  19339. _proto.isFullscreen = function isFullscreen(isFS) {
  19340. if (isFS !== undefined) {
  19341. this.isFullscreen_ = !!isFS;
  19342. this.toggleFullscreenClass_();
  19343. return;
  19344. }
  19345. return !!this.isFullscreen_;
  19346. }
  19347. /**
  19348. * Increase the size of the video to full screen
  19349. * In some browsers, full screen is not supported natively, so it enters
  19350. * "full window mode", where the video fills the browser window.
  19351. * In browsers and devices that support native full screen, sometimes the
  19352. * browser's default controls will be shown, and not the Video.js custom skin.
  19353. * This includes most mobile devices (iOS, Android) and older versions of
  19354. * Safari.
  19355. *
  19356. * @fires Player#fullscreenchange
  19357. */
  19358. ;
  19359. _proto.requestFullscreen = function requestFullscreen() {
  19360. var fsApi = FullscreenApi;
  19361. this.isFullscreen(true);
  19362. if (fsApi.requestFullscreen) {
  19363. // the browser supports going fullscreen at the element level so we can
  19364. // take the controls fullscreen as well as the video
  19365. // Trigger fullscreenchange event after change
  19366. // We have to specifically add this each time, and remove
  19367. // when canceling fullscreen. Otherwise if there's multiple
  19368. // players on a page, they would all be reacting to the same fullscreen
  19369. // events
  19370. on(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  19371. silencePromise(this.el_[fsApi.requestFullscreen]());
  19372. } else if (this.tech_.supportsFullScreen()) {
  19373. // we can't take the video.js controls fullscreen but we can go fullscreen
  19374. // with native controls
  19375. this.techCall_('enterFullScreen');
  19376. } else {
  19377. // fullscreen isn't supported so we'll just stretch the video element to
  19378. // fill the viewport
  19379. this.enterFullWindow();
  19380. /**
  19381. * @event Player#fullscreenchange
  19382. * @type {EventTarget~Event}
  19383. */
  19384. this.trigger('fullscreenchange');
  19385. }
  19386. }
  19387. /**
  19388. * Return the video to its normal size after having been in full screen mode
  19389. *
  19390. * @fires Player#fullscreenchange
  19391. */
  19392. ;
  19393. _proto.exitFullscreen = function exitFullscreen() {
  19394. var fsApi = FullscreenApi;
  19395. this.isFullscreen(false); // Check for browser element fullscreen support
  19396. if (fsApi.requestFullscreen) {
  19397. silencePromise(document[fsApi.exitFullscreen]());
  19398. } else if (this.tech_.supportsFullScreen()) {
  19399. this.techCall_('exitFullScreen');
  19400. } else {
  19401. this.exitFullWindow();
  19402. /**
  19403. * @event Player#fullscreenchange
  19404. * @type {EventTarget~Event}
  19405. */
  19406. this.trigger('fullscreenchange');
  19407. }
  19408. }
  19409. /**
  19410. * When fullscreen isn't supported we can stretch the
  19411. * video container to as wide as the browser will let us.
  19412. *
  19413. * @fires Player#enterFullWindow
  19414. */
  19415. ;
  19416. _proto.enterFullWindow = function enterFullWindow() {
  19417. this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
  19418. this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
  19419. on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
  19420. document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
  19421. addClass(document.body, 'vjs-full-window');
  19422. /**
  19423. * @event Player#enterFullWindow
  19424. * @type {EventTarget~Event}
  19425. */
  19426. this.trigger('enterFullWindow');
  19427. }
  19428. /**
  19429. * Check for call to either exit full window or
  19430. * full screen on ESC key
  19431. *
  19432. * @param {string} event
  19433. * Event to check for key press
  19434. */
  19435. ;
  19436. _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
  19437. if (keycode.isEventKey(event, 'Esc')) {
  19438. if (this.isFullscreen() === true) {
  19439. this.exitFullscreen();
  19440. } else {
  19441. this.exitFullWindow();
  19442. }
  19443. }
  19444. }
  19445. /**
  19446. * Exit full window
  19447. *
  19448. * @fires Player#exitFullWindow
  19449. */
  19450. ;
  19451. _proto.exitFullWindow = function exitFullWindow() {
  19452. this.isFullWindow = false;
  19453. off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
  19454. document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
  19455. removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
  19456. // this.positionAll();
  19457. /**
  19458. * @event Player#exitFullWindow
  19459. * @type {EventTarget~Event}
  19460. */
  19461. this.trigger('exitFullWindow');
  19462. }
  19463. /**
  19464. * This gets called when a `Player` gains focus via a `focus` event.
  19465. * Turns on listening for `keydown` events. When they happen it
  19466. * calls `this.handleKeyPress`.
  19467. *
  19468. * @param {EventTarget~Event} event
  19469. * The `focus` event that caused this function to be called.
  19470. *
  19471. * @listens focus
  19472. */
  19473. ;
  19474. _proto.handleFocus = function handleFocus(event) {
  19475. // call off first to make sure we don't keep adding keydown handlers
  19476. off(document, 'keydown', this.boundHandleKeyPress_);
  19477. on(document, 'keydown', this.boundHandleKeyPress_);
  19478. }
  19479. /**
  19480. * Called when a `Player` loses focus. Turns off the listener for
  19481. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  19482. *
  19483. * @param {EventTarget~Event} event
  19484. * The `blur` event that caused this function to be called.
  19485. *
  19486. * @listens blur
  19487. */
  19488. ;
  19489. _proto.handleBlur = function handleBlur(event) {
  19490. off(document, 'keydown', this.boundHandleKeyPress_);
  19491. }
  19492. /**
  19493. * Called when this Player has focus and a key gets pressed down, or when
  19494. * any Component of this player receives a key press that it doesn't handle.
  19495. * This allows player-wide hotkeys (either as defined below, or optionally
  19496. * by an external function).
  19497. *
  19498. * @param {EventTarget~Event} event
  19499. * The `keydown` event that caused this function to be called.
  19500. *
  19501. * @listens keydown
  19502. */
  19503. ;
  19504. _proto.handleKeyPress = function handleKeyPress(event) {
  19505. if (this.options_.userActions && this.options_.userActions.hotkeys && this.options_.userActions.hotkeys !== false) {
  19506. if (typeof this.options_.userActions.hotkeys === 'function') {
  19507. this.options_.userActions.hotkeys.call(this, event);
  19508. } else {
  19509. this.handleHotkeys(event);
  19510. }
  19511. }
  19512. }
  19513. /**
  19514. * Called when this Player receives a hotkey keydown event.
  19515. * Supported player-wide hotkeys are:
  19516. *
  19517. * f - toggle fullscreen
  19518. * m - toggle mute
  19519. * k or Space - toggle play/pause
  19520. *
  19521. * @param {EventTarget~Event} event
  19522. * The `keydown` event that caused this function to be called.
  19523. */
  19524. ;
  19525. _proto.handleHotkeys = function handleHotkeys(event) {
  19526. var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
  19527. var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
  19528. fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
  19529. return keycode.isEventKey(keydownEvent, 'f');
  19530. } : _hotkeys$fullscreenKe,
  19531. _hotkeys$muteKey = hotkeys.muteKey,
  19532. muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
  19533. return keycode.isEventKey(keydownEvent, 'm');
  19534. } : _hotkeys$muteKey,
  19535. _hotkeys$playPauseKey = hotkeys.playPauseKey,
  19536. playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
  19537. return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
  19538. } : _hotkeys$playPauseKey;
  19539. if (fullscreenKey.call(this, event)) {
  19540. event.preventDefault();
  19541. var FSToggle = Component.getComponent('FullscreenToggle');
  19542. if (document[FullscreenApi.fullscreenEnabled] !== false) {
  19543. FSToggle.prototype.handleClick.call(this);
  19544. }
  19545. } else if (muteKey.call(this, event)) {
  19546. event.preventDefault();
  19547. var MuteToggle = Component.getComponent('MuteToggle');
  19548. MuteToggle.prototype.handleClick.call(this);
  19549. } else if (playPauseKey.call(this, event)) {
  19550. event.preventDefault();
  19551. var PlayToggle = Component.getComponent('PlayToggle');
  19552. PlayToggle.prototype.handleClick.call(this);
  19553. }
  19554. }
  19555. /**
  19556. * Check whether the player can play a given mimetype
  19557. *
  19558. * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
  19559. *
  19560. * @param {string} type
  19561. * The mimetype to check
  19562. *
  19563. * @return {string}
  19564. * 'probably', 'maybe', or '' (empty string)
  19565. */
  19566. ;
  19567. _proto.canPlayType = function canPlayType(type) {
  19568. var can; // Loop through each playback technology in the options order
  19569. for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
  19570. var techName = j[i];
  19571. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  19572. // Remove once that deprecated behavior is removed.
  19573. if (!tech) {
  19574. tech = Component.getComponent(techName);
  19575. } // Check if the current tech is defined before continuing
  19576. if (!tech) {
  19577. log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  19578. continue;
  19579. } // Check if the browser supports this technology
  19580. if (tech.isSupported()) {
  19581. can = tech.canPlayType(type);
  19582. if (can) {
  19583. return can;
  19584. }
  19585. }
  19586. }
  19587. return '';
  19588. }
  19589. /**
  19590. * Select source based on tech-order or source-order
  19591. * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
  19592. * defaults to tech-order selection
  19593. *
  19594. * @param {Array} sources
  19595. * The sources for a media asset
  19596. *
  19597. * @return {Object|boolean}
  19598. * Object of source and tech order or false
  19599. */
  19600. ;
  19601. _proto.selectSource = function selectSource(sources) {
  19602. var _this10 = this;
  19603. // Get only the techs specified in `techOrder` that exist and are supported by the
  19604. // current platform
  19605. var techs = this.options_.techOrder.map(function (techName) {
  19606. return [techName, Tech.getTech(techName)];
  19607. }).filter(function (_ref) {
  19608. var techName = _ref[0],
  19609. tech = _ref[1];
  19610. // Check if the current tech is defined before continuing
  19611. if (tech) {
  19612. // Check if the browser supports this technology
  19613. return tech.isSupported();
  19614. }
  19615. log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  19616. return false;
  19617. }); // Iterate over each `innerArray` element once per `outerArray` element and execute
  19618. // `tester` with both. If `tester` returns a non-falsy value, exit early and return
  19619. // that value.
  19620. var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
  19621. var found;
  19622. outerArray.some(function (outerChoice) {
  19623. return innerArray.some(function (innerChoice) {
  19624. found = tester(outerChoice, innerChoice);
  19625. if (found) {
  19626. return true;
  19627. }
  19628. });
  19629. });
  19630. return found;
  19631. };
  19632. var foundSourceAndTech;
  19633. var flip = function flip(fn) {
  19634. return function (a, b) {
  19635. return fn(b, a);
  19636. };
  19637. };
  19638. var finder = function finder(_ref2, source) {
  19639. var techName = _ref2[0],
  19640. tech = _ref2[1];
  19641. if (tech.canPlaySource(source, _this10.options_[techName.toLowerCase()])) {
  19642. return {
  19643. source: source,
  19644. tech: techName
  19645. };
  19646. }
  19647. }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
  19648. // to select from them based on their priority.
  19649. if (this.options_.sourceOrder) {
  19650. // Source-first ordering
  19651. foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
  19652. } else {
  19653. // Tech-first ordering
  19654. foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
  19655. }
  19656. return foundSourceAndTech || false;
  19657. }
  19658. /**
  19659. * Get or set the video source.
  19660. *
  19661. * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
  19662. * A SourceObject, an array of SourceObjects, or a string referencing
  19663. * a URL to a media source. It is _highly recommended_ that an object
  19664. * or array of objects is used here, so that source selection
  19665. * algorithms can take the `type` into account.
  19666. *
  19667. * If not provided, this method acts as a getter.
  19668. *
  19669. * @return {string|undefined}
  19670. * If the `source` argument is missing, returns the current source
  19671. * URL. Otherwise, returns nothing/undefined.
  19672. */
  19673. ;
  19674. _proto.src = function src(source) {
  19675. var _this11 = this;
  19676. // getter usage
  19677. if (typeof source === 'undefined') {
  19678. return this.cache_.src || '';
  19679. } // filter out invalid sources and turn our source into
  19680. // an array of source objects
  19681. var sources = filterSource(source); // if a source was passed in then it is invalid because
  19682. // it was filtered to a zero length Array. So we have to
  19683. // show an error
  19684. if (!sources.length) {
  19685. this.setTimeout(function () {
  19686. this.error({
  19687. code: 4,
  19688. message: this.localize(this.options_.notSupportedMessage)
  19689. });
  19690. }, 0);
  19691. return;
  19692. } // intial sources
  19693. this.changingSrc_ = true;
  19694. this.cache_.sources = sources;
  19695. this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
  19696. setSource(this, sources[0], function (middlewareSource, mws) {
  19697. _this11.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
  19698. // the source that is selected could be out of order from the cache update above this callback.
  19699. _this11.cache_.sources = sources;
  19700. _this11.updateSourceCaches_(middlewareSource);
  19701. var err = _this11.src_(middlewareSource);
  19702. if (err) {
  19703. if (sources.length > 1) {
  19704. return _this11.src(sources.slice(1));
  19705. }
  19706. _this11.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
  19707. _this11.setTimeout(function () {
  19708. this.error({
  19709. code: 4,
  19710. message: this.localize(this.options_.notSupportedMessage)
  19711. });
  19712. }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
  19713. // this needs a better comment about why this is needed
  19714. _this11.triggerReady();
  19715. return;
  19716. }
  19717. setTech(mws, _this11.tech_);
  19718. });
  19719. }
  19720. /**
  19721. * Set the source object on the tech, returns a boolean that indicates whether
  19722. * there is a tech that can play the source or not
  19723. *
  19724. * @param {Tech~SourceObject} source
  19725. * The source object to set on the Tech
  19726. *
  19727. * @return {boolean}
  19728. * - True if there is no Tech to playback this source
  19729. * - False otherwise
  19730. *
  19731. * @private
  19732. */
  19733. ;
  19734. _proto.src_ = function src_(source) {
  19735. var _this12 = this;
  19736. var sourceTech = this.selectSource([source]);
  19737. if (!sourceTech) {
  19738. return true;
  19739. }
  19740. if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
  19741. this.changingSrc_ = true; // load this technology with the chosen source
  19742. this.loadTech_(sourceTech.tech, sourceTech.source);
  19743. this.tech_.ready(function () {
  19744. _this12.changingSrc_ = false;
  19745. });
  19746. return false;
  19747. } // wait until the tech is ready to set the source
  19748. // and set it synchronously if possible (#2326)
  19749. this.ready(function () {
  19750. // The setSource tech method was added with source handlers
  19751. // so older techs won't support it
  19752. // We need to check the direct prototype for the case where subclasses
  19753. // of the tech do not support source handlers
  19754. if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
  19755. this.techCall_('setSource', source);
  19756. } else {
  19757. this.techCall_('src', source.src);
  19758. }
  19759. this.changingSrc_ = false;
  19760. }, true);
  19761. return false;
  19762. }
  19763. /**
  19764. * Begin loading the src data.
  19765. */
  19766. ;
  19767. _proto.load = function load() {
  19768. this.techCall_('load');
  19769. }
  19770. /**
  19771. * Reset the player. Loads the first tech in the techOrder,
  19772. * removes all the text tracks in the existing `tech`,
  19773. * and calls `reset` on the `tech`.
  19774. */
  19775. ;
  19776. _proto.reset = function reset() {
  19777. var _this13 = this;
  19778. var PromiseClass = this.options_.Promise || window$1.Promise;
  19779. if (this.paused() || !PromiseClass) {
  19780. this.doReset_();
  19781. } else {
  19782. var playPromise = this.play();
  19783. silencePromise(playPromise.then(function () {
  19784. return _this13.doReset_();
  19785. }));
  19786. }
  19787. };
  19788. _proto.doReset_ = function doReset_() {
  19789. if (this.tech_) {
  19790. this.tech_.clearTracks('text');
  19791. }
  19792. this.resetCache_();
  19793. this.poster('');
  19794. this.loadTech_(this.options_.techOrder[0], null);
  19795. this.techCall_('reset');
  19796. this.resetControlBarUI_();
  19797. if (isEvented(this)) {
  19798. this.trigger('playerreset');
  19799. }
  19800. }
  19801. /**
  19802. * Reset Control Bar's UI by calling sub-methods that reset
  19803. * all of Control Bar's components
  19804. */
  19805. ;
  19806. _proto.resetControlBarUI_ = function resetControlBarUI_() {
  19807. this.resetProgressBar_();
  19808. this.resetPlaybackRate_();
  19809. this.resetVolumeBar_();
  19810. }
  19811. /**
  19812. * Reset tech's progress so progress bar is reset in the UI
  19813. */
  19814. ;
  19815. _proto.resetProgressBar_ = function resetProgressBar_() {
  19816. this.currentTime(0);
  19817. var _this$controlBar = this.controlBar,
  19818. durationDisplay = _this$controlBar.durationDisplay,
  19819. remainingTimeDisplay = _this$controlBar.remainingTimeDisplay;
  19820. if (durationDisplay) {
  19821. durationDisplay.updateContent();
  19822. }
  19823. if (remainingTimeDisplay) {
  19824. remainingTimeDisplay.updateContent();
  19825. }
  19826. }
  19827. /**
  19828. * Reset Playback ratio
  19829. */
  19830. ;
  19831. _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
  19832. this.playbackRate(this.defaultPlaybackRate());
  19833. this.handleTechRateChange_();
  19834. }
  19835. /**
  19836. * Reset Volume bar
  19837. */
  19838. ;
  19839. _proto.resetVolumeBar_ = function resetVolumeBar_() {
  19840. this.volume(1.0);
  19841. this.trigger('volumechange');
  19842. }
  19843. /**
  19844. * Returns all of the current source objects.
  19845. *
  19846. * @return {Tech~SourceObject[]}
  19847. * The current source objects
  19848. */
  19849. ;
  19850. _proto.currentSources = function currentSources() {
  19851. var source = this.currentSource();
  19852. var sources = []; // assume `{}` or `{ src }`
  19853. if (Object.keys(source).length !== 0) {
  19854. sources.push(source);
  19855. }
  19856. return this.cache_.sources || sources;
  19857. }
  19858. /**
  19859. * Returns the current source object.
  19860. *
  19861. * @return {Tech~SourceObject}
  19862. * The current source object
  19863. */
  19864. ;
  19865. _proto.currentSource = function currentSource() {
  19866. return this.cache_.source || {};
  19867. }
  19868. /**
  19869. * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
  19870. * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
  19871. *
  19872. * @return {string}
  19873. * The current source
  19874. */
  19875. ;
  19876. _proto.currentSrc = function currentSrc() {
  19877. return this.currentSource() && this.currentSource().src || '';
  19878. }
  19879. /**
  19880. * Get the current source type e.g. video/mp4
  19881. * This can allow you rebuild the current source object so that you could load the same
  19882. * source and tech later
  19883. *
  19884. * @return {string}
  19885. * The source MIME type
  19886. */
  19887. ;
  19888. _proto.currentType = function currentType() {
  19889. return this.currentSource() && this.currentSource().type || '';
  19890. }
  19891. /**
  19892. * Get or set the preload attribute
  19893. *
  19894. * @param {boolean} [value]
  19895. * - true means that we should preload
  19896. * - false means that we should not preload
  19897. *
  19898. * @return {string}
  19899. * The preload attribute value when getting
  19900. */
  19901. ;
  19902. _proto.preload = function preload(value) {
  19903. if (value !== undefined) {
  19904. this.techCall_('setPreload', value);
  19905. this.options_.preload = value;
  19906. return;
  19907. }
  19908. return this.techGet_('preload');
  19909. }
  19910. /**
  19911. * Get or set the autoplay option. When this is a boolean it will
  19912. * modify the attribute on the tech. When this is a string the attribute on
  19913. * the tech will be removed and `Player` will handle autoplay on loadstarts.
  19914. *
  19915. * @param {boolean|string} [value]
  19916. * - true: autoplay using the browser behavior
  19917. * - false: do not autoplay
  19918. * - 'play': call play() on every loadstart
  19919. * - 'muted': call muted() then play() on every loadstart
  19920. * - 'any': call play() on every loadstart. if that fails call muted() then play().
  19921. * - *: values other than those listed here will be set `autoplay` to true
  19922. *
  19923. * @return {boolean|string}
  19924. * The current value of autoplay when getting
  19925. */
  19926. ;
  19927. _proto.autoplay = function autoplay(value) {
  19928. // getter usage
  19929. if (value === undefined) {
  19930. return this.options_.autoplay || false;
  19931. }
  19932. var techAutoplay; // if the value is a valid string set it to that
  19933. if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
  19934. this.options_.autoplay = value;
  19935. this.manualAutoplay_(value);
  19936. techAutoplay = false; // any falsy value sets autoplay to false in the browser,
  19937. // lets do the same
  19938. } else if (!value) {
  19939. this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
  19940. } else {
  19941. this.options_.autoplay = true;
  19942. }
  19943. techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
  19944. // a setAutoplay call on tech ready. We do this because the
  19945. // autoplay option will be passed in the constructor and we
  19946. // do not need to set it twice
  19947. if (this.tech_) {
  19948. this.techCall_('setAutoplay', techAutoplay);
  19949. }
  19950. }
  19951. /**
  19952. * Set or unset the playsinline attribute.
  19953. * Playsinline tells the browser that non-fullscreen playback is preferred.
  19954. *
  19955. * @param {boolean} [value]
  19956. * - true means that we should try to play inline by default
  19957. * - false means that we should use the browser's default playback mode,
  19958. * which in most cases is inline. iOS Safari is a notable exception
  19959. * and plays fullscreen by default.
  19960. *
  19961. * @return {string|Player}
  19962. * - the current value of playsinline
  19963. * - the player when setting
  19964. *
  19965. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  19966. */
  19967. ;
  19968. _proto.playsinline = function playsinline(value) {
  19969. if (value !== undefined) {
  19970. this.techCall_('setPlaysinline', value);
  19971. this.options_.playsinline = value;
  19972. return this;
  19973. }
  19974. return this.techGet_('playsinline');
  19975. }
  19976. /**
  19977. * Get or set the loop attribute on the video element.
  19978. *
  19979. * @param {boolean} [value]
  19980. * - true means that we should loop the video
  19981. * - false means that we should not loop the video
  19982. *
  19983. * @return {boolean}
  19984. * The current value of loop when getting
  19985. */
  19986. ;
  19987. _proto.loop = function loop(value) {
  19988. if (value !== undefined) {
  19989. this.techCall_('setLoop', value);
  19990. this.options_.loop = value;
  19991. return;
  19992. }
  19993. return this.techGet_('loop');
  19994. }
  19995. /**
  19996. * Get or set the poster image source url
  19997. *
  19998. * @fires Player#posterchange
  19999. *
  20000. * @param {string} [src]
  20001. * Poster image source URL
  20002. *
  20003. * @return {string}
  20004. * The current value of poster when getting
  20005. */
  20006. ;
  20007. _proto.poster = function poster(src) {
  20008. if (src === undefined) {
  20009. return this.poster_;
  20010. } // The correct way to remove a poster is to set as an empty string
  20011. // other falsey values will throw errors
  20012. if (!src) {
  20013. src = '';
  20014. }
  20015. if (src === this.poster_) {
  20016. return;
  20017. } // update the internal poster variable
  20018. this.poster_ = src; // update the tech's poster
  20019. this.techCall_('setPoster', src);
  20020. this.isPosterFromTech_ = false; // alert components that the poster has been set
  20021. /**
  20022. * This event fires when the poster image is changed on the player.
  20023. *
  20024. * @event Player#posterchange
  20025. * @type {EventTarget~Event}
  20026. */
  20027. this.trigger('posterchange');
  20028. }
  20029. /**
  20030. * Some techs (e.g. YouTube) can provide a poster source in an
  20031. * asynchronous way. We want the poster component to use this
  20032. * poster source so that it covers up the tech's controls.
  20033. * (YouTube's play button). However we only want to use this
  20034. * source if the player user hasn't set a poster through
  20035. * the normal APIs.
  20036. *
  20037. * @fires Player#posterchange
  20038. * @listens Tech#posterchange
  20039. * @private
  20040. */
  20041. ;
  20042. _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
  20043. if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
  20044. var newPoster = this.tech_.poster() || '';
  20045. if (newPoster !== this.poster_) {
  20046. this.poster_ = newPoster;
  20047. this.isPosterFromTech_ = true; // Let components know the poster has changed
  20048. this.trigger('posterchange');
  20049. }
  20050. }
  20051. }
  20052. /**
  20053. * Get or set whether or not the controls are showing.
  20054. *
  20055. * @fires Player#controlsenabled
  20056. *
  20057. * @param {boolean} [bool]
  20058. * - true to turn controls on
  20059. * - false to turn controls off
  20060. *
  20061. * @return {boolean}
  20062. * The current value of controls when getting
  20063. */
  20064. ;
  20065. _proto.controls = function controls(bool) {
  20066. if (bool === undefined) {
  20067. return !!this.controls_;
  20068. }
  20069. bool = !!bool; // Don't trigger a change event unless it actually changed
  20070. if (this.controls_ === bool) {
  20071. return;
  20072. }
  20073. this.controls_ = bool;
  20074. if (this.usingNativeControls()) {
  20075. this.techCall_('setControls', bool);
  20076. }
  20077. if (this.controls_) {
  20078. this.removeClass('vjs-controls-disabled');
  20079. this.addClass('vjs-controls-enabled');
  20080. /**
  20081. * @event Player#controlsenabled
  20082. * @type {EventTarget~Event}
  20083. */
  20084. this.trigger('controlsenabled');
  20085. if (!this.usingNativeControls()) {
  20086. this.addTechControlsListeners_();
  20087. }
  20088. } else {
  20089. this.removeClass('vjs-controls-enabled');
  20090. this.addClass('vjs-controls-disabled');
  20091. /**
  20092. * @event Player#controlsdisabled
  20093. * @type {EventTarget~Event}
  20094. */
  20095. this.trigger('controlsdisabled');
  20096. if (!this.usingNativeControls()) {
  20097. this.removeTechControlsListeners_();
  20098. }
  20099. }
  20100. }
  20101. /**
  20102. * Toggle native controls on/off. Native controls are the controls built into
  20103. * devices (e.g. default iPhone controls), Flash, or other techs
  20104. * (e.g. Vimeo Controls)
  20105. * **This should only be set by the current tech, because only the tech knows
  20106. * if it can support native controls**
  20107. *
  20108. * @fires Player#usingnativecontrols
  20109. * @fires Player#usingcustomcontrols
  20110. *
  20111. * @param {boolean} [bool]
  20112. * - true to turn native controls on
  20113. * - false to turn native controls off
  20114. *
  20115. * @return {boolean}
  20116. * The current value of native controls when getting
  20117. */
  20118. ;
  20119. _proto.usingNativeControls = function usingNativeControls(bool) {
  20120. if (bool === undefined) {
  20121. return !!this.usingNativeControls_;
  20122. }
  20123. bool = !!bool; // Don't trigger a change event unless it actually changed
  20124. if (this.usingNativeControls_ === bool) {
  20125. return;
  20126. }
  20127. this.usingNativeControls_ = bool;
  20128. if (this.usingNativeControls_) {
  20129. this.addClass('vjs-using-native-controls');
  20130. /**
  20131. * player is using the native device controls
  20132. *
  20133. * @event Player#usingnativecontrols
  20134. * @type {EventTarget~Event}
  20135. */
  20136. this.trigger('usingnativecontrols');
  20137. } else {
  20138. this.removeClass('vjs-using-native-controls');
  20139. /**
  20140. * player is using the custom HTML controls
  20141. *
  20142. * @event Player#usingcustomcontrols
  20143. * @type {EventTarget~Event}
  20144. */
  20145. this.trigger('usingcustomcontrols');
  20146. }
  20147. }
  20148. /**
  20149. * Set or get the current MediaError
  20150. *
  20151. * @fires Player#error
  20152. *
  20153. * @param {MediaError|string|number} [err]
  20154. * A MediaError or a string/number to be turned
  20155. * into a MediaError
  20156. *
  20157. * @return {MediaError|null}
  20158. * The current MediaError when getting (or null)
  20159. */
  20160. ;
  20161. _proto.error = function error(err) {
  20162. if (err === undefined) {
  20163. return this.error_ || null;
  20164. } // restoring to default
  20165. if (err === null) {
  20166. this.error_ = err;
  20167. this.removeClass('vjs-error');
  20168. if (this.errorDisplay) {
  20169. this.errorDisplay.close();
  20170. }
  20171. return;
  20172. }
  20173. this.error_ = new MediaError(err); // add the vjs-error classname to the player
  20174. this.addClass('vjs-error'); // log the name of the error type and any message
  20175. // IE11 logs "[object object]" and required you to expand message to see error object
  20176. log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
  20177. /**
  20178. * @event Player#error
  20179. * @type {EventTarget~Event}
  20180. */
  20181. this.trigger('error');
  20182. return;
  20183. }
  20184. /**
  20185. * Report user activity
  20186. *
  20187. * @param {Object} event
  20188. * Event object
  20189. */
  20190. ;
  20191. _proto.reportUserActivity = function reportUserActivity(event) {
  20192. this.userActivity_ = true;
  20193. }
  20194. /**
  20195. * Get/set if user is active
  20196. *
  20197. * @fires Player#useractive
  20198. * @fires Player#userinactive
  20199. *
  20200. * @param {boolean} [bool]
  20201. * - true if the user is active
  20202. * - false if the user is inactive
  20203. *
  20204. * @return {boolean}
  20205. * The current value of userActive when getting
  20206. */
  20207. ;
  20208. _proto.userActive = function userActive(bool) {
  20209. if (bool === undefined) {
  20210. return this.userActive_;
  20211. }
  20212. bool = !!bool;
  20213. if (bool === this.userActive_) {
  20214. return;
  20215. }
  20216. this.userActive_ = bool;
  20217. if (this.userActive_) {
  20218. this.userActivity_ = true;
  20219. this.removeClass('vjs-user-inactive');
  20220. this.addClass('vjs-user-active');
  20221. /**
  20222. * @event Player#useractive
  20223. * @type {EventTarget~Event}
  20224. */
  20225. this.trigger('useractive');
  20226. return;
  20227. } // Chrome/Safari/IE have bugs where when you change the cursor it can
  20228. // trigger a mousemove event. This causes an issue when you're hiding
  20229. // the cursor when the user is inactive, and a mousemove signals user
  20230. // activity. Making it impossible to go into inactive mode. Specifically
  20231. // this happens in fullscreen when we really need to hide the cursor.
  20232. //
  20233. // When this gets resolved in ALL browsers it can be removed
  20234. // https://code.google.com/p/chromium/issues/detail?id=103041
  20235. if (this.tech_) {
  20236. this.tech_.one('mousemove', function (e) {
  20237. e.stopPropagation();
  20238. e.preventDefault();
  20239. });
  20240. }
  20241. this.userActivity_ = false;
  20242. this.removeClass('vjs-user-active');
  20243. this.addClass('vjs-user-inactive');
  20244. /**
  20245. * @event Player#userinactive
  20246. * @type {EventTarget~Event}
  20247. */
  20248. this.trigger('userinactive');
  20249. }
  20250. /**
  20251. * Listen for user activity based on timeout value
  20252. *
  20253. * @private
  20254. */
  20255. ;
  20256. _proto.listenForUserActivity_ = function listenForUserActivity_() {
  20257. var mouseInProgress;
  20258. var lastMoveX;
  20259. var lastMoveY;
  20260. var handleActivity = bind(this, this.reportUserActivity);
  20261. var handleMouseMove = function handleMouseMove(e) {
  20262. // #1068 - Prevent mousemove spamming
  20263. // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
  20264. if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
  20265. lastMoveX = e.screenX;
  20266. lastMoveY = e.screenY;
  20267. handleActivity();
  20268. }
  20269. };
  20270. var handleMouseDown = function handleMouseDown() {
  20271. handleActivity(); // For as long as the they are touching the device or have their mouse down,
  20272. // we consider them active even if they're not moving their finger or mouse.
  20273. // So we want to continue to update that they are active
  20274. this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
  20275. // as the activityCheck interval (250) should ensure we never miss the
  20276. // next activityCheck
  20277. mouseInProgress = this.setInterval(handleActivity, 250);
  20278. };
  20279. var handleMouseUp = function handleMouseUp(event) {
  20280. handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
  20281. this.clearInterval(mouseInProgress);
  20282. }; // Any mouse movement will be considered user activity
  20283. this.on('mousedown', handleMouseDown);
  20284. this.on('mousemove', handleMouseMove);
  20285. this.on('mouseup', handleMouseUp);
  20286. var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
  20287. // controlBar would no longer be hidden by default timeout.
  20288. if (controlBar && !IS_IOS && !IS_ANDROID) {
  20289. controlBar.on('mouseenter', function (event) {
  20290. this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
  20291. this.player().options_.inactivityTimeout = 0;
  20292. });
  20293. controlBar.on('mouseleave', function (event) {
  20294. this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
  20295. });
  20296. } // Listen for keyboard navigation
  20297. // Shouldn't need to use inProgress interval because of key repeat
  20298. this.on('keydown', handleActivity);
  20299. this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
  20300. // the mousemove/touchmove function itself, to prevent performance degradation.
  20301. // `this.reportUserActivity` simply sets this.userActivity_ to true, which
  20302. // then gets picked up by this loop
  20303. // http://ejohn.org/blog/learning-from-twitter/
  20304. var inactivityTimeout;
  20305. this.setInterval(function () {
  20306. // Check to see if mouse/touch activity has happened
  20307. if (!this.userActivity_) {
  20308. return;
  20309. } // Reset the activity tracker
  20310. this.userActivity_ = false; // If the user state was inactive, set the state to active
  20311. this.userActive(true); // Clear any existing inactivity timeout to start the timer over
  20312. this.clearTimeout(inactivityTimeout);
  20313. var timeout = this.options_.inactivityTimeout;
  20314. if (timeout <= 0) {
  20315. return;
  20316. } // In <timeout> milliseconds, if no more activity has occurred the
  20317. // user will be considered inactive
  20318. inactivityTimeout = this.setTimeout(function () {
  20319. // Protect against the case where the inactivityTimeout can trigger just
  20320. // before the next user activity is picked up by the activity check loop
  20321. // causing a flicker
  20322. if (!this.userActivity_) {
  20323. this.userActive(false);
  20324. }
  20325. }, timeout);
  20326. }, 250);
  20327. }
  20328. /**
  20329. * Gets or sets the current playback rate. A playback rate of
  20330. * 1.0 represents normal speed and 0.5 would indicate half-speed
  20331. * playback, for instance.
  20332. *
  20333. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
  20334. *
  20335. * @param {number} [rate]
  20336. * New playback rate to set.
  20337. *
  20338. * @return {number}
  20339. * The current playback rate when getting or 1.0
  20340. */
  20341. ;
  20342. _proto.playbackRate = function playbackRate(rate) {
  20343. if (rate !== undefined) {
  20344. // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
  20345. // that is registered above
  20346. this.techCall_('setPlaybackRate', rate);
  20347. return;
  20348. }
  20349. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  20350. return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
  20351. }
  20352. return 1.0;
  20353. }
  20354. /**
  20355. * Gets or sets the current default playback rate. A default playback rate of
  20356. * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
  20357. * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
  20358. * not the current playbackRate.
  20359. *
  20360. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
  20361. *
  20362. * @param {number} [rate]
  20363. * New default playback rate to set.
  20364. *
  20365. * @return {number|Player}
  20366. * - The default playback rate when getting or 1.0
  20367. * - the player when setting
  20368. */
  20369. ;
  20370. _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
  20371. if (rate !== undefined) {
  20372. return this.techCall_('setDefaultPlaybackRate', rate);
  20373. }
  20374. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  20375. return this.techGet_('defaultPlaybackRate');
  20376. }
  20377. return 1.0;
  20378. }
  20379. /**
  20380. * Gets or sets the audio flag
  20381. *
  20382. * @param {boolean} bool
  20383. * - true signals that this is an audio player
  20384. * - false signals that this is not an audio player
  20385. *
  20386. * @return {boolean}
  20387. * The current value of isAudio when getting
  20388. */
  20389. ;
  20390. _proto.isAudio = function isAudio(bool) {
  20391. if (bool !== undefined) {
  20392. this.isAudio_ = !!bool;
  20393. return;
  20394. }
  20395. return !!this.isAudio_;
  20396. }
  20397. /**
  20398. * A helper method for adding a {@link TextTrack} to our
  20399. * {@link TextTrackList}.
  20400. *
  20401. * In addition to the W3C settings we allow adding additional info through options.
  20402. *
  20403. * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
  20404. *
  20405. * @param {string} [kind]
  20406. * the kind of TextTrack you are adding
  20407. *
  20408. * @param {string} [label]
  20409. * the label to give the TextTrack label
  20410. *
  20411. * @param {string} [language]
  20412. * the language to set on the TextTrack
  20413. *
  20414. * @return {TextTrack|undefined}
  20415. * the TextTrack that was added or undefined
  20416. * if there is no tech
  20417. */
  20418. ;
  20419. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  20420. if (this.tech_) {
  20421. return this.tech_.addTextTrack(kind, label, language);
  20422. }
  20423. }
  20424. /**
  20425. * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
  20426. * automatically removed from the video element whenever the source changes, unless
  20427. * manualCleanup is set to false.
  20428. *
  20429. * @param {Object} options
  20430. * Options to pass to {@link HTMLTrackElement} during creation. See
  20431. * {@link HTMLTrackElement} for object properties that you should use.
  20432. *
  20433. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  20434. *
  20435. * @return {HtmlTrackElement}
  20436. * the HTMLTrackElement that was created and added
  20437. * to the HtmlTrackElementList and the remote
  20438. * TextTrackList
  20439. *
  20440. * @deprecated The default value of the "manualCleanup" parameter will default
  20441. * to "false" in upcoming versions of Video.js
  20442. */
  20443. ;
  20444. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  20445. if (this.tech_) {
  20446. return this.tech_.addRemoteTextTrack(options, manualCleanup);
  20447. }
  20448. }
  20449. /**
  20450. * Remove a remote {@link TextTrack} from the respective
  20451. * {@link TextTrackList} and {@link HtmlTrackElementList}.
  20452. *
  20453. * @param {Object} track
  20454. * Remote {@link TextTrack} to remove
  20455. *
  20456. * @return {undefined}
  20457. * does not return anything
  20458. */
  20459. ;
  20460. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
  20461. if (obj === void 0) {
  20462. obj = {};
  20463. }
  20464. var _obj = obj,
  20465. track = _obj.track;
  20466. if (!track) {
  20467. track = obj;
  20468. } // destructure the input into an object with a track argument, defaulting to arguments[0]
  20469. // default the whole argument to an empty object if nothing was passed in
  20470. if (this.tech_) {
  20471. return this.tech_.removeRemoteTextTrack(track);
  20472. }
  20473. }
  20474. /**
  20475. * Gets available media playback quality metrics as specified by the W3C's Media
  20476. * Playback Quality API.
  20477. *
  20478. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  20479. *
  20480. * @return {Object|undefined}
  20481. * An object with supported media playback quality metrics or undefined if there
  20482. * is no tech or the tech does not support it.
  20483. */
  20484. ;
  20485. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  20486. return this.techGet_('getVideoPlaybackQuality');
  20487. }
  20488. /**
  20489. * Get video width
  20490. *
  20491. * @return {number}
  20492. * current video width
  20493. */
  20494. ;
  20495. _proto.videoWidth = function videoWidth() {
  20496. return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
  20497. }
  20498. /**
  20499. * Get video height
  20500. *
  20501. * @return {number}
  20502. * current video height
  20503. */
  20504. ;
  20505. _proto.videoHeight = function videoHeight() {
  20506. return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
  20507. }
  20508. /**
  20509. * The player's language code
  20510. * NOTE: The language should be set in the player options if you want the
  20511. * the controls to be built with a specific language. Changing the language
  20512. * later will not update controls text.
  20513. *
  20514. * @param {string} [code]
  20515. * the language code to set the player to
  20516. *
  20517. * @return {string}
  20518. * The current language code when getting
  20519. */
  20520. ;
  20521. _proto.language = function language(code) {
  20522. if (code === undefined) {
  20523. return this.language_;
  20524. }
  20525. this.language_ = String(code).toLowerCase();
  20526. }
  20527. /**
  20528. * Get the player's language dictionary
  20529. * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
  20530. * Languages specified directly in the player options have precedence
  20531. *
  20532. * @return {Array}
  20533. * An array of of supported languages
  20534. */
  20535. ;
  20536. _proto.languages = function languages() {
  20537. return mergeOptions(Player.prototype.options_.languages, this.languages_);
  20538. }
  20539. /**
  20540. * returns a JavaScript object reperesenting the current track
  20541. * information. **DOES not return it as JSON**
  20542. *
  20543. * @return {Object}
  20544. * Object representing the current of track info
  20545. */
  20546. ;
  20547. _proto.toJSON = function toJSON() {
  20548. var options = mergeOptions(this.options_);
  20549. var tracks = options.tracks;
  20550. options.tracks = [];
  20551. for (var i = 0; i < tracks.length; i++) {
  20552. var track = tracks[i]; // deep merge tracks and null out player so no circular references
  20553. track = mergeOptions(track);
  20554. track.player = undefined;
  20555. options.tracks[i] = track;
  20556. }
  20557. return options;
  20558. }
  20559. /**
  20560. * Creates a simple modal dialog (an instance of the {@link ModalDialog}
  20561. * component) that immediately overlays the player with arbitrary
  20562. * content and removes itself when closed.
  20563. *
  20564. * @param {string|Function|Element|Array|null} content
  20565. * Same as {@link ModalDialog#content}'s param of the same name.
  20566. * The most straight-forward usage is to provide a string or DOM
  20567. * element.
  20568. *
  20569. * @param {Object} [options]
  20570. * Extra options which will be passed on to the {@link ModalDialog}.
  20571. *
  20572. * @return {ModalDialog}
  20573. * the {@link ModalDialog} that was created
  20574. */
  20575. ;
  20576. _proto.createModal = function createModal(content, options) {
  20577. var _this14 = this;
  20578. options = options || {};
  20579. options.content = content || '';
  20580. var modal = new ModalDialog(this, options);
  20581. this.addChild(modal);
  20582. modal.on('dispose', function () {
  20583. _this14.removeChild(modal);
  20584. });
  20585. modal.open();
  20586. return modal;
  20587. }
  20588. /**
  20589. * Change breakpoint classes when the player resizes.
  20590. *
  20591. * @private
  20592. */
  20593. ;
  20594. _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
  20595. if (!this.responsive()) {
  20596. return;
  20597. }
  20598. var currentBreakpoint = this.currentBreakpoint();
  20599. var currentWidth = this.currentWidth();
  20600. for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
  20601. var candidateBreakpoint = BREAKPOINT_ORDER[i];
  20602. var maxWidth = this.breakpoints_[candidateBreakpoint];
  20603. if (currentWidth <= maxWidth) {
  20604. // The current breakpoint did not change, nothing to do.
  20605. if (currentBreakpoint === candidateBreakpoint) {
  20606. return;
  20607. } // Only remove a class if there is a current breakpoint.
  20608. if (currentBreakpoint) {
  20609. this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
  20610. }
  20611. this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
  20612. this.breakpoint_ = candidateBreakpoint;
  20613. break;
  20614. }
  20615. }
  20616. }
  20617. /**
  20618. * Removes the current breakpoint.
  20619. *
  20620. * @private
  20621. */
  20622. ;
  20623. _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
  20624. var className = this.currentBreakpointClass();
  20625. this.breakpoint_ = '';
  20626. if (className) {
  20627. this.removeClass(className);
  20628. }
  20629. }
  20630. /**
  20631. * Get or set breakpoints on the player.
  20632. *
  20633. * Calling this method with an object or `true` will remove any previous
  20634. * custom breakpoints and start from the defaults again.
  20635. *
  20636. * @param {Object|boolean} [breakpoints]
  20637. * If an object is given, it can be used to provide custom
  20638. * breakpoints. If `true` is given, will set default breakpoints.
  20639. * If this argument is not given, will simply return the current
  20640. * breakpoints.
  20641. *
  20642. * @param {number} [breakpoints.tiny]
  20643. * The maximum width for the "vjs-layout-tiny" class.
  20644. *
  20645. * @param {number} [breakpoints.xsmall]
  20646. * The maximum width for the "vjs-layout-x-small" class.
  20647. *
  20648. * @param {number} [breakpoints.small]
  20649. * The maximum width for the "vjs-layout-small" class.
  20650. *
  20651. * @param {number} [breakpoints.medium]
  20652. * The maximum width for the "vjs-layout-medium" class.
  20653. *
  20654. * @param {number} [breakpoints.large]
  20655. * The maximum width for the "vjs-layout-large" class.
  20656. *
  20657. * @param {number} [breakpoints.xlarge]
  20658. * The maximum width for the "vjs-layout-x-large" class.
  20659. *
  20660. * @param {number} [breakpoints.huge]
  20661. * The maximum width for the "vjs-layout-huge" class.
  20662. *
  20663. * @return {Object}
  20664. * An object mapping breakpoint names to maximum width values.
  20665. */
  20666. ;
  20667. _proto.breakpoints = function breakpoints(_breakpoints) {
  20668. // Used as a getter.
  20669. if (_breakpoints === undefined) {
  20670. return assign(this.breakpoints_);
  20671. }
  20672. this.breakpoint_ = '';
  20673. this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
  20674. // selected breakpoint.
  20675. this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
  20676. return assign(this.breakpoints_);
  20677. }
  20678. /**
  20679. * Get or set a flag indicating whether or not this player should adjust
  20680. * its UI based on its dimensions.
  20681. *
  20682. * @param {boolean} value
  20683. * Should be `true` if the player should adjust its UI based on its
  20684. * dimensions; otherwise, should be `false`.
  20685. *
  20686. * @return {boolean}
  20687. * Will be `true` if this player should adjust its UI based on its
  20688. * dimensions; otherwise, will be `false`.
  20689. */
  20690. ;
  20691. _proto.responsive = function responsive(value) {
  20692. // Used as a getter.
  20693. if (value === undefined) {
  20694. return this.responsive_;
  20695. }
  20696. value = Boolean(value);
  20697. var current = this.responsive_; // Nothing changed.
  20698. if (value === current) {
  20699. return;
  20700. } // The value actually changed, set it.
  20701. this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
  20702. // player is now responsive.
  20703. if (value) {
  20704. this.on('playerresize', this.updateCurrentBreakpoint_);
  20705. this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
  20706. } else {
  20707. this.off('playerresize', this.updateCurrentBreakpoint_);
  20708. this.removeCurrentBreakpoint_();
  20709. }
  20710. return value;
  20711. }
  20712. /**
  20713. * Get current breakpoint name, if any.
  20714. *
  20715. * @return {string}
  20716. * If there is currently a breakpoint set, returns a the key from the
  20717. * breakpoints object matching it. Otherwise, returns an empty string.
  20718. */
  20719. ;
  20720. _proto.currentBreakpoint = function currentBreakpoint() {
  20721. return this.breakpoint_;
  20722. }
  20723. /**
  20724. * Get the current breakpoint class name.
  20725. *
  20726. * @return {string}
  20727. * The matching class name (e.g. `"vjs-layout-tiny"` or
  20728. * `"vjs-layout-large"`) for the current breakpoint. Empty string if
  20729. * there is no current breakpoint.
  20730. */
  20731. ;
  20732. _proto.currentBreakpointClass = function currentBreakpointClass() {
  20733. return BREAKPOINT_CLASSES[this.breakpoint_] || '';
  20734. }
  20735. /**
  20736. * An object that describes a single piece of media.
  20737. *
  20738. * Properties that are not part of this type description will be retained; so,
  20739. * this can be viewed as a generic metadata storage mechanism as well.
  20740. *
  20741. * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
  20742. * @typedef {Object} Player~MediaObject
  20743. *
  20744. * @property {string} [album]
  20745. * Unused, except if this object is passed to the `MediaSession`
  20746. * API.
  20747. *
  20748. * @property {string} [artist]
  20749. * Unused, except if this object is passed to the `MediaSession`
  20750. * API.
  20751. *
  20752. * @property {Object[]} [artwork]
  20753. * Unused, except if this object is passed to the `MediaSession`
  20754. * API. If not specified, will be populated via the `poster`, if
  20755. * available.
  20756. *
  20757. * @property {string} [poster]
  20758. * URL to an image that will display before playback.
  20759. *
  20760. * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
  20761. * A single source object, an array of source objects, or a string
  20762. * referencing a URL to a media source. It is _highly recommended_
  20763. * that an object or array of objects is used here, so that source
  20764. * selection algorithms can take the `type` into account.
  20765. *
  20766. * @property {string} [title]
  20767. * Unused, except if this object is passed to the `MediaSession`
  20768. * API.
  20769. *
  20770. * @property {Object[]} [textTracks]
  20771. * An array of objects to be used to create text tracks, following
  20772. * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
  20773. * For ease of removal, these will be created as "remote" text
  20774. * tracks and set to automatically clean up on source changes.
  20775. *
  20776. * These objects may have properties like `src`, `kind`, `label`,
  20777. * and `language`, see {@link Tech#createRemoteTextTrack}.
  20778. */
  20779. /**
  20780. * Populate the player using a {@link Player~MediaObject|MediaObject}.
  20781. *
  20782. * @param {Player~MediaObject} media
  20783. * A media object.
  20784. *
  20785. * @param {Function} ready
  20786. * A callback to be called when the player is ready.
  20787. */
  20788. ;
  20789. _proto.loadMedia = function loadMedia(media, ready) {
  20790. var _this15 = this;
  20791. if (!media || typeof media !== 'object') {
  20792. return;
  20793. }
  20794. this.reset(); // Clone the media object so it cannot be mutated from outside.
  20795. this.cache_.media = mergeOptions(media);
  20796. var _this$cache_$media = this.cache_.media,
  20797. artwork = _this$cache_$media.artwork,
  20798. poster = _this$cache_$media.poster,
  20799. src = _this$cache_$media.src,
  20800. textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
  20801. if (!artwork && poster) {
  20802. this.cache_.media.artwork = [{
  20803. src: poster,
  20804. type: getMimetype(poster)
  20805. }];
  20806. }
  20807. if (src) {
  20808. this.src(src);
  20809. }
  20810. if (poster) {
  20811. this.poster(poster);
  20812. }
  20813. if (Array.isArray(textTracks)) {
  20814. textTracks.forEach(function (tt) {
  20815. return _this15.addRemoteTextTrack(tt, false);
  20816. });
  20817. }
  20818. this.ready(ready);
  20819. }
  20820. /**
  20821. * Get a clone of the current {@link Player~MediaObject} for this player.
  20822. *
  20823. * If the `loadMedia` method has not been used, will attempt to return a
  20824. * {@link Player~MediaObject} based on the current state of the player.
  20825. *
  20826. * @return {Player~MediaObject}
  20827. */
  20828. ;
  20829. _proto.getMedia = function getMedia() {
  20830. if (!this.cache_.media) {
  20831. var poster = this.poster();
  20832. var src = this.currentSources();
  20833. var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
  20834. return {
  20835. kind: tt.kind,
  20836. label: tt.label,
  20837. language: tt.language,
  20838. src: tt.src
  20839. };
  20840. });
  20841. var media = {
  20842. src: src,
  20843. textTracks: textTracks
  20844. };
  20845. if (poster) {
  20846. media.poster = poster;
  20847. media.artwork = [{
  20848. src: media.poster,
  20849. type: getMimetype(media.poster)
  20850. }];
  20851. }
  20852. return media;
  20853. }
  20854. return mergeOptions(this.cache_.media);
  20855. }
  20856. /**
  20857. * Gets tag settings
  20858. *
  20859. * @param {Element} tag
  20860. * The player tag
  20861. *
  20862. * @return {Object}
  20863. * An object containing all of the settings
  20864. * for a player tag
  20865. */
  20866. ;
  20867. Player.getTagSettings = function getTagSettings(tag) {
  20868. var baseOptions = {
  20869. sources: [],
  20870. tracks: []
  20871. };
  20872. var tagOptions = getAttributes(tag);
  20873. var dataSetup = tagOptions['data-setup'];
  20874. if (hasClass(tag, 'vjs-fill')) {
  20875. tagOptions.fill = true;
  20876. }
  20877. if (hasClass(tag, 'vjs-fluid')) {
  20878. tagOptions.fluid = true;
  20879. } // Check if data-setup attr exists.
  20880. if (dataSetup !== null) {
  20881. // Parse options JSON
  20882. // If empty string, make it a parsable json object.
  20883. var _safeParseTuple = safeParseTuple(dataSetup || '{}'),
  20884. err = _safeParseTuple[0],
  20885. data = _safeParseTuple[1];
  20886. if (err) {
  20887. log.error(err);
  20888. }
  20889. assign(tagOptions, data);
  20890. }
  20891. assign(baseOptions, tagOptions); // Get tag children settings
  20892. if (tag.hasChildNodes()) {
  20893. var children = tag.childNodes;
  20894. for (var i = 0, j = children.length; i < j; i++) {
  20895. var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
  20896. var childName = child.nodeName.toLowerCase();
  20897. if (childName === 'source') {
  20898. baseOptions.sources.push(getAttributes(child));
  20899. } else if (childName === 'track') {
  20900. baseOptions.tracks.push(getAttributes(child));
  20901. }
  20902. }
  20903. }
  20904. return baseOptions;
  20905. }
  20906. /**
  20907. * Determine whether or not flexbox is supported
  20908. *
  20909. * @return {boolean}
  20910. * - true if flexbox is supported
  20911. * - false if flexbox is not supported
  20912. */
  20913. ;
  20914. _proto.flexNotSupported_ = function flexNotSupported_() {
  20915. var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
  20916. // common flex features that we can rely on when checking for flex support.
  20917. return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || // IE10-specific (2012 flex spec), available for completeness
  20918. 'msFlexOrder' in elem.style);
  20919. };
  20920. return Player;
  20921. }(Component);
  20922. /**
  20923. * Get the {@link VideoTrackList}
  20924. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
  20925. *
  20926. * @return {VideoTrackList}
  20927. * the current video track list
  20928. *
  20929. * @method Player.prototype.videoTracks
  20930. */
  20931. /**
  20932. * Get the {@link AudioTrackList}
  20933. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
  20934. *
  20935. * @return {AudioTrackList}
  20936. * the current audio track list
  20937. *
  20938. * @method Player.prototype.audioTracks
  20939. */
  20940. /**
  20941. * Get the {@link TextTrackList}
  20942. *
  20943. * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
  20944. *
  20945. * @return {TextTrackList}
  20946. * the current text track list
  20947. *
  20948. * @method Player.prototype.textTracks
  20949. */
  20950. /**
  20951. * Get the remote {@link TextTrackList}
  20952. *
  20953. * @return {TextTrackList}
  20954. * The current remote text track list
  20955. *
  20956. * @method Player.prototype.remoteTextTracks
  20957. */
  20958. /**
  20959. * Get the remote {@link HtmlTrackElementList} tracks.
  20960. *
  20961. * @return {HtmlTrackElementList}
  20962. * The current remote text track element list
  20963. *
  20964. * @method Player.prototype.remoteTextTrackEls
  20965. */
  20966. ALL.names.forEach(function (name$$1) {
  20967. var props = ALL[name$$1];
  20968. Player.prototype[props.getterName] = function () {
  20969. if (this.tech_) {
  20970. return this.tech_[props.getterName]();
  20971. } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
  20972. // these will be passed to the tech during loading
  20973. this[props.privateName] = this[props.privateName] || new props.ListClass();
  20974. return this[props.privateName];
  20975. };
  20976. });
  20977. /**
  20978. * Global enumeration of players.
  20979. *
  20980. * The keys are the player IDs and the values are either the {@link Player}
  20981. * instance or `null` for disposed players.
  20982. *
  20983. * @type {Object}
  20984. */
  20985. Player.players = {};
  20986. var navigator = window$1.navigator;
  20987. /*
  20988. * Player instance options, surfaced using options
  20989. * options = Player.prototype.options_
  20990. * Make changes in options, not here.
  20991. *
  20992. * @type {Object}
  20993. * @private
  20994. */
  20995. Player.prototype.options_ = {
  20996. // Default order of fallback technology
  20997. techOrder: Tech.defaultTechOrder_,
  20998. html5: {},
  20999. flash: {},
  21000. // default inactivity timeout
  21001. inactivityTimeout: 2000,
  21002. // default playback rates
  21003. playbackRates: [],
  21004. // Add playback rate selection by adding rates
  21005. // 'playbackRates': [0.5, 1, 1.5, 2],
  21006. liveui: false,
  21007. // Included control sets
  21008. children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
  21009. language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
  21010. // locales and their language translations
  21011. languages: {},
  21012. // Default message to show when a video cannot be played.
  21013. notSupportedMessage: 'No compatible source was found for this media.',
  21014. breakpoints: {},
  21015. responsive: false
  21016. };
  21017. [
  21018. /**
  21019. * Returns whether or not the player is in the "ended" state.
  21020. *
  21021. * @return {Boolean} True if the player is in the ended state, false if not.
  21022. * @method Player#ended
  21023. */
  21024. 'ended',
  21025. /**
  21026. * Returns whether or not the player is in the "seeking" state.
  21027. *
  21028. * @return {Boolean} True if the player is in the seeking state, false if not.
  21029. * @method Player#seeking
  21030. */
  21031. 'seeking',
  21032. /**
  21033. * Returns the TimeRanges of the media that are currently available
  21034. * for seeking to.
  21035. *
  21036. * @return {TimeRanges} the seekable intervals of the media timeline
  21037. * @method Player#seekable
  21038. */
  21039. 'seekable',
  21040. /**
  21041. * Returns the current state of network activity for the element, from
  21042. * the codes in the list below.
  21043. * - NETWORK_EMPTY (numeric value 0)
  21044. * The element has not yet been initialised. All attributes are in
  21045. * their initial states.
  21046. * - NETWORK_IDLE (numeric value 1)
  21047. * The element's resource selection algorithm is active and has
  21048. * selected a resource, but it is not actually using the network at
  21049. * this time.
  21050. * - NETWORK_LOADING (numeric value 2)
  21051. * The user agent is actively trying to download data.
  21052. * - NETWORK_NO_SOURCE (numeric value 3)
  21053. * The element's resource selection algorithm is active, but it has
  21054. * not yet found a resource to use.
  21055. *
  21056. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
  21057. * @return {number} the current network activity state
  21058. * @method Player#networkState
  21059. */
  21060. 'networkState',
  21061. /**
  21062. * Returns a value that expresses the current state of the element
  21063. * with respect to rendering the current playback position, from the
  21064. * codes in the list below.
  21065. * - HAVE_NOTHING (numeric value 0)
  21066. * No information regarding the media resource is available.
  21067. * - HAVE_METADATA (numeric value 1)
  21068. * Enough of the resource has been obtained that the duration of the
  21069. * resource is available.
  21070. * - HAVE_CURRENT_DATA (numeric value 2)
  21071. * Data for the immediate current playback position is available.
  21072. * - HAVE_FUTURE_DATA (numeric value 3)
  21073. * Data for the immediate current playback position is available, as
  21074. * well as enough data for the user agent to advance the current
  21075. * playback position in the direction of playback.
  21076. * - HAVE_ENOUGH_DATA (numeric value 4)
  21077. * The user agent estimates that enough data is available for
  21078. * playback to proceed uninterrupted.
  21079. *
  21080. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
  21081. * @return {number} the current playback rendering state
  21082. * @method Player#readyState
  21083. */
  21084. 'readyState'].forEach(function (fn) {
  21085. Player.prototype[fn] = function () {
  21086. return this.techGet_(fn);
  21087. };
  21088. });
  21089. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  21090. Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () {
  21091. return this.trigger(event);
  21092. };
  21093. });
  21094. /**
  21095. * Fired when the player has initial duration and dimension information
  21096. *
  21097. * @event Player#loadedmetadata
  21098. * @type {EventTarget~Event}
  21099. */
  21100. /**
  21101. * Fired when the player has downloaded data at the current playback position
  21102. *
  21103. * @event Player#loadeddata
  21104. * @type {EventTarget~Event}
  21105. */
  21106. /**
  21107. * Fired when the current playback position has changed *
  21108. * During playback this is fired every 15-250 milliseconds, depending on the
  21109. * playback technology in use.
  21110. *
  21111. * @event Player#timeupdate
  21112. * @type {EventTarget~Event}
  21113. */
  21114. /**
  21115. * Fired when the volume changes
  21116. *
  21117. * @event Player#volumechange
  21118. * @type {EventTarget~Event}
  21119. */
  21120. /**
  21121. * Reports whether or not a player has a plugin available.
  21122. *
  21123. * This does not report whether or not the plugin has ever been initialized
  21124. * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
  21125. *
  21126. * @method Player#hasPlugin
  21127. * @param {string} name
  21128. * The name of a plugin.
  21129. *
  21130. * @return {boolean}
  21131. * Whether or not this player has the requested plugin available.
  21132. */
  21133. /**
  21134. * Reports whether or not a player is using a plugin by name.
  21135. *
  21136. * For basic plugins, this only reports whether the plugin has _ever_ been
  21137. * initialized on this player.
  21138. *
  21139. * @method Player#usingPlugin
  21140. * @param {string} name
  21141. * The name of a plugin.
  21142. *
  21143. * @return {boolean}
  21144. * Whether or not this player is using the requested plugin.
  21145. */
  21146. Component.registerComponent('Player', Player);
  21147. /**
  21148. * The base plugin name.
  21149. *
  21150. * @private
  21151. * @constant
  21152. * @type {string}
  21153. */
  21154. var BASE_PLUGIN_NAME = 'plugin';
  21155. /**
  21156. * The key on which a player's active plugins cache is stored.
  21157. *
  21158. * @private
  21159. * @constant
  21160. * @type {string}
  21161. */
  21162. var PLUGIN_CACHE_KEY = 'activePlugins_';
  21163. /**
  21164. * Stores registered plugins in a private space.
  21165. *
  21166. * @private
  21167. * @type {Object}
  21168. */
  21169. var pluginStorage = {};
  21170. /**
  21171. * Reports whether or not a plugin has been registered.
  21172. *
  21173. * @private
  21174. * @param {string} name
  21175. * The name of a plugin.
  21176. *
  21177. * @return {boolean}
  21178. * Whether or not the plugin has been registered.
  21179. */
  21180. var pluginExists = function pluginExists(name) {
  21181. return pluginStorage.hasOwnProperty(name);
  21182. };
  21183. /**
  21184. * Get a single registered plugin by name.
  21185. *
  21186. * @private
  21187. * @param {string} name
  21188. * The name of a plugin.
  21189. *
  21190. * @return {Function|undefined}
  21191. * The plugin (or undefined).
  21192. */
  21193. var getPlugin = function getPlugin(name) {
  21194. return pluginExists(name) ? pluginStorage[name] : undefined;
  21195. };
  21196. /**
  21197. * Marks a plugin as "active" on a player.
  21198. *
  21199. * Also, ensures that the player has an object for tracking active plugins.
  21200. *
  21201. * @private
  21202. * @param {Player} player
  21203. * A Video.js player instance.
  21204. *
  21205. * @param {string} name
  21206. * The name of a plugin.
  21207. */
  21208. var markPluginAsActive = function markPluginAsActive(player, name) {
  21209. player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
  21210. player[PLUGIN_CACHE_KEY][name] = true;
  21211. };
  21212. /**
  21213. * Triggers a pair of plugin setup events.
  21214. *
  21215. * @private
  21216. * @param {Player} player
  21217. * A Video.js player instance.
  21218. *
  21219. * @param {Plugin~PluginEventHash} hash
  21220. * A plugin event hash.
  21221. *
  21222. * @param {boolean} [before]
  21223. * If true, prefixes the event name with "before". In other words,
  21224. * use this to trigger "beforepluginsetup" instead of "pluginsetup".
  21225. */
  21226. var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
  21227. var eventName = (before ? 'before' : '') + 'pluginsetup';
  21228. player.trigger(eventName, hash);
  21229. player.trigger(eventName + ':' + hash.name, hash);
  21230. };
  21231. /**
  21232. * Takes a basic plugin function and returns a wrapper function which marks
  21233. * on the player that the plugin has been activated.
  21234. *
  21235. * @private
  21236. * @param {string} name
  21237. * The name of the plugin.
  21238. *
  21239. * @param {Function} plugin
  21240. * The basic plugin.
  21241. *
  21242. * @return {Function}
  21243. * A wrapper function for the given plugin.
  21244. */
  21245. var createBasicPlugin = function createBasicPlugin(name, plugin) {
  21246. var basicPluginWrapper = function basicPluginWrapper() {
  21247. // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
  21248. // regardless, but we want the hash to be consistent with the hash provided
  21249. // for advanced plugins.
  21250. //
  21251. // The only potentially counter-intuitive thing here is the `instance` in
  21252. // the "pluginsetup" event is the value returned by the `plugin` function.
  21253. triggerSetupEvent(this, {
  21254. name: name,
  21255. plugin: plugin,
  21256. instance: null
  21257. }, true);
  21258. var instance = plugin.apply(this, arguments);
  21259. markPluginAsActive(this, name);
  21260. triggerSetupEvent(this, {
  21261. name: name,
  21262. plugin: plugin,
  21263. instance: instance
  21264. });
  21265. return instance;
  21266. };
  21267. Object.keys(plugin).forEach(function (prop) {
  21268. basicPluginWrapper[prop] = plugin[prop];
  21269. });
  21270. return basicPluginWrapper;
  21271. };
  21272. /**
  21273. * Takes a plugin sub-class and returns a factory function for generating
  21274. * instances of it.
  21275. *
  21276. * This factory function will replace itself with an instance of the requested
  21277. * sub-class of Plugin.
  21278. *
  21279. * @private
  21280. * @param {string} name
  21281. * The name of the plugin.
  21282. *
  21283. * @param {Plugin} PluginSubClass
  21284. * The advanced plugin.
  21285. *
  21286. * @return {Function}
  21287. */
  21288. var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
  21289. // Add a `name` property to the plugin prototype so that each plugin can
  21290. // refer to itself by name.
  21291. PluginSubClass.prototype.name = name;
  21292. return function () {
  21293. triggerSetupEvent(this, {
  21294. name: name,
  21295. plugin: PluginSubClass,
  21296. instance: null
  21297. }, true);
  21298. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  21299. args[_key] = arguments[_key];
  21300. }
  21301. var instance = _construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
  21302. this[name] = function () {
  21303. return instance;
  21304. };
  21305. triggerSetupEvent(this, instance.getEventHash());
  21306. return instance;
  21307. };
  21308. };
  21309. /**
  21310. * Parent class for all advanced plugins.
  21311. *
  21312. * @mixes module:evented~EventedMixin
  21313. * @mixes module:stateful~StatefulMixin
  21314. * @fires Player#beforepluginsetup
  21315. * @fires Player#beforepluginsetup:$name
  21316. * @fires Player#pluginsetup
  21317. * @fires Player#pluginsetup:$name
  21318. * @listens Player#dispose
  21319. * @throws {Error}
  21320. * If attempting to instantiate the base {@link Plugin} class
  21321. * directly instead of via a sub-class.
  21322. */
  21323. var Plugin =
  21324. /*#__PURE__*/
  21325. function () {
  21326. /**
  21327. * Creates an instance of this class.
  21328. *
  21329. * Sub-classes should call `super` to ensure plugins are properly initialized.
  21330. *
  21331. * @param {Player} player
  21332. * A Video.js player instance.
  21333. */
  21334. function Plugin(player) {
  21335. if (this.constructor === Plugin) {
  21336. throw new Error('Plugin must be sub-classed; not directly instantiated.');
  21337. }
  21338. this.player = player; // Make this object evented, but remove the added `trigger` method so we
  21339. // use the prototype version instead.
  21340. evented(this);
  21341. delete this.trigger;
  21342. stateful(this, this.constructor.defaultState);
  21343. markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
  21344. // it later easily.
  21345. this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin.
  21346. player.on('dispose', this.dispose);
  21347. }
  21348. /**
  21349. * Get the version of the plugin that was set on <pluginName>.VERSION
  21350. */
  21351. var _proto = Plugin.prototype;
  21352. _proto.version = function version() {
  21353. return this.constructor.VERSION;
  21354. }
  21355. /**
  21356. * Each event triggered by plugins includes a hash of additional data with
  21357. * conventional properties.
  21358. *
  21359. * This returns that object or mutates an existing hash.
  21360. *
  21361. * @param {Object} [hash={}]
  21362. * An object to be used as event an event hash.
  21363. *
  21364. * @return {Plugin~PluginEventHash}
  21365. * An event hash object with provided properties mixed-in.
  21366. */
  21367. ;
  21368. _proto.getEventHash = function getEventHash(hash) {
  21369. if (hash === void 0) {
  21370. hash = {};
  21371. }
  21372. hash.name = this.name;
  21373. hash.plugin = this.constructor;
  21374. hash.instance = this;
  21375. return hash;
  21376. }
  21377. /**
  21378. * Triggers an event on the plugin object and overrides
  21379. * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
  21380. *
  21381. * @param {string|Object} event
  21382. * An event type or an object with a type property.
  21383. *
  21384. * @param {Object} [hash={}]
  21385. * Additional data hash to merge with a
  21386. * {@link Plugin~PluginEventHash|PluginEventHash}.
  21387. *
  21388. * @return {boolean}
  21389. * Whether or not default was prevented.
  21390. */
  21391. ;
  21392. _proto.trigger = function trigger$$1(event, hash) {
  21393. if (hash === void 0) {
  21394. hash = {};
  21395. }
  21396. return trigger(this.eventBusEl_, event, this.getEventHash(hash));
  21397. }
  21398. /**
  21399. * Handles "statechanged" events on the plugin. No-op by default, override by
  21400. * subclassing.
  21401. *
  21402. * @abstract
  21403. * @param {Event} e
  21404. * An event object provided by a "statechanged" event.
  21405. *
  21406. * @param {Object} e.changes
  21407. * An object describing changes that occurred with the "statechanged"
  21408. * event.
  21409. */
  21410. ;
  21411. _proto.handleStateChanged = function handleStateChanged(e) {}
  21412. /**
  21413. * Disposes a plugin.
  21414. *
  21415. * Subclasses can override this if they want, but for the sake of safety,
  21416. * it's probably best to subscribe the "dispose" event.
  21417. *
  21418. * @fires Plugin#dispose
  21419. */
  21420. ;
  21421. _proto.dispose = function dispose() {
  21422. var name = this.name,
  21423. player = this.player;
  21424. /**
  21425. * Signals that a advanced plugin is about to be disposed.
  21426. *
  21427. * @event Plugin#dispose
  21428. * @type {EventTarget~Event}
  21429. */
  21430. this.trigger('dispose');
  21431. this.off();
  21432. player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
  21433. // references between the player and the plugin instance and nulling out
  21434. // the plugin's state and replacing methods with a function that throws.
  21435. player[PLUGIN_CACHE_KEY][name] = false;
  21436. this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
  21437. // function, so that the plugin is ready to be set up again.
  21438. player[name] = createPluginFactory(name, pluginStorage[name]);
  21439. }
  21440. /**
  21441. * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
  21442. *
  21443. * @param {string|Function} plugin
  21444. * If a string, matches the name of a plugin. If a function, will be
  21445. * tested directly.
  21446. *
  21447. * @return {boolean}
  21448. * Whether or not a plugin is a basic plugin.
  21449. */
  21450. ;
  21451. Plugin.isBasic = function isBasic(plugin) {
  21452. var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
  21453. return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
  21454. }
  21455. /**
  21456. * Register a Video.js plugin.
  21457. *
  21458. * @param {string} name
  21459. * The name of the plugin to be registered. Must be a string and
  21460. * must not match an existing plugin or a method on the `Player`
  21461. * prototype.
  21462. *
  21463. * @param {Function} plugin
  21464. * A sub-class of `Plugin` or a function for basic plugins.
  21465. *
  21466. * @return {Function}
  21467. * For advanced plugins, a factory function for that plugin. For
  21468. * basic plugins, a wrapper function that initializes the plugin.
  21469. */
  21470. ;
  21471. Plugin.registerPlugin = function registerPlugin(name, plugin) {
  21472. if (typeof name !== 'string') {
  21473. throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
  21474. }
  21475. if (pluginExists(name)) {
  21476. log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
  21477. } else if (Player.prototype.hasOwnProperty(name)) {
  21478. throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
  21479. }
  21480. if (typeof plugin !== 'function') {
  21481. throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
  21482. }
  21483. pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
  21484. // the base Plugin class).
  21485. if (name !== BASE_PLUGIN_NAME) {
  21486. if (Plugin.isBasic(plugin)) {
  21487. Player.prototype[name] = createBasicPlugin(name, plugin);
  21488. } else {
  21489. Player.prototype[name] = createPluginFactory(name, plugin);
  21490. }
  21491. }
  21492. return plugin;
  21493. }
  21494. /**
  21495. * De-register a Video.js plugin.
  21496. *
  21497. * @param {string} name
  21498. * The name of the plugin to be de-registered. Must be a string that
  21499. * matches an existing plugin.
  21500. *
  21501. * @throws {Error}
  21502. * If an attempt is made to de-register the base plugin.
  21503. */
  21504. ;
  21505. Plugin.deregisterPlugin = function deregisterPlugin(name) {
  21506. if (name === BASE_PLUGIN_NAME) {
  21507. throw new Error('Cannot de-register base plugin.');
  21508. }
  21509. if (pluginExists(name)) {
  21510. delete pluginStorage[name];
  21511. delete Player.prototype[name];
  21512. }
  21513. }
  21514. /**
  21515. * Gets an object containing multiple Video.js plugins.
  21516. *
  21517. * @param {Array} [names]
  21518. * If provided, should be an array of plugin names. Defaults to _all_
  21519. * plugin names.
  21520. *
  21521. * @return {Object|undefined}
  21522. * An object containing plugin(s) associated with their name(s) or
  21523. * `undefined` if no matching plugins exist).
  21524. */
  21525. ;
  21526. Plugin.getPlugins = function getPlugins(names) {
  21527. if (names === void 0) {
  21528. names = Object.keys(pluginStorage);
  21529. }
  21530. var result;
  21531. names.forEach(function (name) {
  21532. var plugin = getPlugin(name);
  21533. if (plugin) {
  21534. result = result || {};
  21535. result[name] = plugin;
  21536. }
  21537. });
  21538. return result;
  21539. }
  21540. /**
  21541. * Gets a plugin's version, if available
  21542. *
  21543. * @param {string} name
  21544. * The name of a plugin.
  21545. *
  21546. * @return {string}
  21547. * The plugin's version or an empty string.
  21548. */
  21549. ;
  21550. Plugin.getPluginVersion = function getPluginVersion(name) {
  21551. var plugin = getPlugin(name);
  21552. return plugin && plugin.VERSION || '';
  21553. };
  21554. return Plugin;
  21555. }();
  21556. /**
  21557. * Gets a plugin by name if it exists.
  21558. *
  21559. * @static
  21560. * @method getPlugin
  21561. * @memberOf Plugin
  21562. * @param {string} name
  21563. * The name of a plugin.
  21564. *
  21565. * @returns {Function|undefined}
  21566. * The plugin (or `undefined`).
  21567. */
  21568. Plugin.getPlugin = getPlugin;
  21569. /**
  21570. * The name of the base plugin class as it is registered.
  21571. *
  21572. * @type {string}
  21573. */
  21574. Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
  21575. Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
  21576. /**
  21577. * Documented in player.js
  21578. *
  21579. * @ignore
  21580. */
  21581. Player.prototype.usingPlugin = function (name) {
  21582. return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
  21583. };
  21584. /**
  21585. * Documented in player.js
  21586. *
  21587. * @ignore
  21588. */
  21589. Player.prototype.hasPlugin = function (name) {
  21590. return !!pluginExists(name);
  21591. };
  21592. /**
  21593. * Signals that a plugin is about to be set up on a player.
  21594. *
  21595. * @event Player#beforepluginsetup
  21596. * @type {Plugin~PluginEventHash}
  21597. */
  21598. /**
  21599. * Signals that a plugin is about to be set up on a player - by name. The name
  21600. * is the name of the plugin.
  21601. *
  21602. * @event Player#beforepluginsetup:$name
  21603. * @type {Plugin~PluginEventHash}
  21604. */
  21605. /**
  21606. * Signals that a plugin has just been set up on a player.
  21607. *
  21608. * @event Player#pluginsetup
  21609. * @type {Plugin~PluginEventHash}
  21610. */
  21611. /**
  21612. * Signals that a plugin has just been set up on a player - by name. The name
  21613. * is the name of the plugin.
  21614. *
  21615. * @event Player#pluginsetup:$name
  21616. * @type {Plugin~PluginEventHash}
  21617. */
  21618. /**
  21619. * @typedef {Object} Plugin~PluginEventHash
  21620. *
  21621. * @property {string} instance
  21622. * For basic plugins, the return value of the plugin function. For
  21623. * advanced plugins, the plugin instance on which the event is fired.
  21624. *
  21625. * @property {string} name
  21626. * The name of the plugin.
  21627. *
  21628. * @property {string} plugin
  21629. * For basic plugins, the plugin function. For advanced plugins, the
  21630. * plugin class/constructor.
  21631. */
  21632. /**
  21633. * @file extend.js
  21634. * @module extend
  21635. */
  21636. /**
  21637. * A combination of node inherits and babel's inherits (after transpile).
  21638. * Both work the same but node adds `super_` to the subClass
  21639. * and Bable adds the superClass as __proto__. Both seem useful.
  21640. *
  21641. * @param {Object} subClass
  21642. * The class to inherit to
  21643. *
  21644. * @param {Object} superClass
  21645. * The class to inherit from
  21646. *
  21647. * @private
  21648. */
  21649. var _inherits$1 = function _inherits(subClass, superClass) {
  21650. if (typeof superClass !== 'function' && superClass !== null) {
  21651. throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
  21652. }
  21653. subClass.prototype = Object.create(superClass && superClass.prototype, {
  21654. constructor: {
  21655. value: subClass,
  21656. enumerable: false,
  21657. writable: true,
  21658. configurable: true
  21659. }
  21660. });
  21661. if (superClass) {
  21662. // node
  21663. subClass.super_ = superClass;
  21664. }
  21665. };
  21666. /**
  21667. * Used to subclass an existing class by emulating ES subclassing using the
  21668. * `extends` keyword.
  21669. *
  21670. * @function
  21671. * @example
  21672. * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
  21673. * myCustomMethod: function() {
  21674. * // Do things in my method.
  21675. * }
  21676. * });
  21677. *
  21678. * @param {Function} superClass
  21679. * The class to inherit from
  21680. *
  21681. * @param {Object} [subClassMethods={}]
  21682. * Methods of the new class
  21683. *
  21684. * @return {Function}
  21685. * The new class with subClassMethods that inherited superClass.
  21686. */
  21687. var extend = function extend(superClass, subClassMethods) {
  21688. if (subClassMethods === void 0) {
  21689. subClassMethods = {};
  21690. }
  21691. var subClass = function subClass() {
  21692. superClass.apply(this, arguments);
  21693. };
  21694. var methods = {};
  21695. if (typeof subClassMethods === 'object') {
  21696. if (subClassMethods.constructor !== Object.prototype.constructor) {
  21697. subClass = subClassMethods.constructor;
  21698. }
  21699. methods = subClassMethods;
  21700. } else if (typeof subClassMethods === 'function') {
  21701. subClass = subClassMethods;
  21702. }
  21703. _inherits$1(subClass, superClass); // Extend subObj's prototype with functions and other properties from props
  21704. for (var name in methods) {
  21705. if (methods.hasOwnProperty(name)) {
  21706. subClass.prototype[name] = methods[name];
  21707. }
  21708. }
  21709. return subClass;
  21710. };
  21711. /**
  21712. * @file video.js
  21713. * @module videojs
  21714. */
  21715. /**
  21716. * Normalize an `id` value by trimming off a leading `#`
  21717. *
  21718. * @private
  21719. * @param {string} id
  21720. * A string, maybe with a leading `#`.
  21721. *
  21722. * @return {string}
  21723. * The string, without any leading `#`.
  21724. */
  21725. var normalizeId = function normalizeId(id) {
  21726. return id.indexOf('#') === 0 ? id.slice(1) : id;
  21727. };
  21728. /**
  21729. * The `videojs()` function doubles as the main function for users to create a
  21730. * {@link Player} instance as well as the main library namespace.
  21731. *
  21732. * It can also be used as a getter for a pre-existing {@link Player} instance.
  21733. * However, we _strongly_ recommend using `videojs.getPlayer()` for this
  21734. * purpose because it avoids any potential for unintended initialization.
  21735. *
  21736. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  21737. * of our JSDoc template, we cannot properly document this as both a function
  21738. * and a namespace, so its function signature is documented here.
  21739. *
  21740. * #### Arguments
  21741. * ##### id
  21742. * string|Element, **required**
  21743. *
  21744. * Video element or video element ID.
  21745. *
  21746. * ##### options
  21747. * Object, optional
  21748. *
  21749. * Options object for providing settings.
  21750. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  21751. *
  21752. * ##### ready
  21753. * {@link Component~ReadyCallback}, optional
  21754. *
  21755. * A function to be called when the {@link Player} and {@link Tech} are ready.
  21756. *
  21757. * #### Return Value
  21758. *
  21759. * The `videojs()` function returns a {@link Player} instance.
  21760. *
  21761. * @namespace
  21762. *
  21763. * @borrows AudioTrack as AudioTrack
  21764. * @borrows Component.getComponent as getComponent
  21765. * @borrows module:computed-style~computedStyle as computedStyle
  21766. * @borrows module:events.on as on
  21767. * @borrows module:events.one as one
  21768. * @borrows module:events.off as off
  21769. * @borrows module:events.trigger as trigger
  21770. * @borrows EventTarget as EventTarget
  21771. * @borrows module:extend~extend as extend
  21772. * @borrows module:fn.bind as bind
  21773. * @borrows module:format-time.formatTime as formatTime
  21774. * @borrows module:format-time.resetFormatTime as resetFormatTime
  21775. * @borrows module:format-time.setFormatTime as setFormatTime
  21776. * @borrows module:merge-options.mergeOptions as mergeOptions
  21777. * @borrows module:middleware.use as use
  21778. * @borrows Player.players as players
  21779. * @borrows Plugin.registerPlugin as registerPlugin
  21780. * @borrows Plugin.deregisterPlugin as deregisterPlugin
  21781. * @borrows Plugin.getPlugins as getPlugins
  21782. * @borrows Plugin.getPlugin as getPlugin
  21783. * @borrows Plugin.getPluginVersion as getPluginVersion
  21784. * @borrows Tech.getTech as getTech
  21785. * @borrows Tech.registerTech as registerTech
  21786. * @borrows TextTrack as TextTrack
  21787. * @borrows module:time-ranges.createTimeRanges as createTimeRange
  21788. * @borrows module:time-ranges.createTimeRanges as createTimeRanges
  21789. * @borrows module:url.isCrossOrigin as isCrossOrigin
  21790. * @borrows module:url.parseUrl as parseUrl
  21791. * @borrows VideoTrack as VideoTrack
  21792. *
  21793. * @param {string|Element} id
  21794. * Video element or video element ID.
  21795. *
  21796. * @param {Object} [options]
  21797. * Options object for providing settings.
  21798. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  21799. *
  21800. * @param {Component~ReadyCallback} [ready]
  21801. * A function to be called when the {@link Player} and {@link Tech} are
  21802. * ready.
  21803. *
  21804. * @return {Player}
  21805. * The `videojs()` function returns a {@link Player|Player} instance.
  21806. */
  21807. function videojs$1(id, options, ready) {
  21808. var player = videojs$1.getPlayer(id);
  21809. if (player) {
  21810. if (options) {
  21811. log.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
  21812. }
  21813. if (ready) {
  21814. player.ready(ready);
  21815. }
  21816. return player;
  21817. }
  21818. var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
  21819. if (!isEl(el)) {
  21820. throw new TypeError('The element or ID supplied is not valid. (videojs)');
  21821. } // document.body.contains(el) will only check if el is contained within that one document.
  21822. // This causes problems for elements in iframes.
  21823. // Instead, use the element's ownerDocument instead of the global document.
  21824. // This will make sure that the element is indeed in the dom of that document.
  21825. // Additionally, check that the document in question has a default view.
  21826. // If the document is no longer attached to the dom, the defaultView of the document will be null.
  21827. if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
  21828. log.warn('The element supplied is not included in the DOM');
  21829. }
  21830. options = options || {};
  21831. videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
  21832. var opts = hookFunction(el, mergeOptions(options));
  21833. if (!isObject(opts) || Array.isArray(opts)) {
  21834. log.error('please return an object in beforesetup hooks');
  21835. return;
  21836. }
  21837. options = mergeOptions(options, opts);
  21838. }); // We get the current "Player" component here in case an integration has
  21839. // replaced it with a custom player.
  21840. var PlayerComponent = Component.getComponent('Player');
  21841. player = new PlayerComponent(el, options, ready);
  21842. videojs$1.hooks('setup').forEach(function (hookFunction) {
  21843. return hookFunction(player);
  21844. });
  21845. return player;
  21846. }
  21847. /**
  21848. * An Object that contains lifecycle hooks as keys which point to an array
  21849. * of functions that are run when a lifecycle is triggered
  21850. *
  21851. * @private
  21852. */
  21853. videojs$1.hooks_ = {};
  21854. /**
  21855. * Get a list of hooks for a specific lifecycle
  21856. *
  21857. * @param {string} type
  21858. * the lifecyle to get hooks from
  21859. *
  21860. * @param {Function|Function[]} [fn]
  21861. * Optionally add a hook (or hooks) to the lifecycle that your are getting.
  21862. *
  21863. * @return {Array}
  21864. * an array of hooks, or an empty array if there are none.
  21865. */
  21866. videojs$1.hooks = function (type, fn) {
  21867. videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
  21868. if (fn) {
  21869. videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
  21870. }
  21871. return videojs$1.hooks_[type];
  21872. };
  21873. /**
  21874. * Add a function hook to a specific videojs lifecycle.
  21875. *
  21876. * @param {string} type
  21877. * the lifecycle to hook the function to.
  21878. *
  21879. * @param {Function|Function[]}
  21880. * The function or array of functions to attach.
  21881. */
  21882. videojs$1.hook = function (type, fn) {
  21883. videojs$1.hooks(type, fn);
  21884. };
  21885. /**
  21886. * Add a function hook that will only run once to a specific videojs lifecycle.
  21887. *
  21888. * @param {string} type
  21889. * the lifecycle to hook the function to.
  21890. *
  21891. * @param {Function|Function[]}
  21892. * The function or array of functions to attach.
  21893. */
  21894. videojs$1.hookOnce = function (type, fn) {
  21895. videojs$1.hooks(type, [].concat(fn).map(function (original) {
  21896. var wrapper = function wrapper() {
  21897. videojs$1.removeHook(type, wrapper);
  21898. return original.apply(void 0, arguments);
  21899. };
  21900. return wrapper;
  21901. }));
  21902. };
  21903. /**
  21904. * Remove a hook from a specific videojs lifecycle.
  21905. *
  21906. * @param {string} type
  21907. * the lifecycle that the function hooked to
  21908. *
  21909. * @param {Function} fn
  21910. * The hooked function to remove
  21911. *
  21912. * @return {boolean}
  21913. * The function that was removed or undef
  21914. */
  21915. videojs$1.removeHook = function (type, fn) {
  21916. var index = videojs$1.hooks(type).indexOf(fn);
  21917. if (index <= -1) {
  21918. return false;
  21919. }
  21920. videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
  21921. videojs$1.hooks_[type].splice(index, 1);
  21922. return true;
  21923. }; // Add default styles
  21924. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
  21925. var style$1 = $('.vjs-styles-defaults');
  21926. if (!style$1) {
  21927. style$1 = createStyleElement('vjs-styles-defaults');
  21928. var head = $('head');
  21929. if (head) {
  21930. head.insertBefore(style$1, head.firstChild);
  21931. }
  21932. setTextContent(style$1, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n ");
  21933. }
  21934. } // Run Auto-load players
  21935. // You have to wait at least once in case this script is loaded after your
  21936. // video in the DOM (weird behavior only with minified version)
  21937. autoSetupTimeout(1, videojs$1);
  21938. /**
  21939. * Current Video.js version. Follows [semantic versioning](https://semver.org/).
  21940. *
  21941. * @type {string}
  21942. */
  21943. videojs$1.VERSION = version;
  21944. /**
  21945. * The global options object. These are the settings that take effect
  21946. * if no overrides are specified when the player is created.
  21947. *
  21948. * @type {Object}
  21949. */
  21950. videojs$1.options = Player.prototype.options_;
  21951. /**
  21952. * Get an object with the currently created players, keyed by player ID
  21953. *
  21954. * @return {Object}
  21955. * The created players
  21956. */
  21957. videojs$1.getPlayers = function () {
  21958. return Player.players;
  21959. };
  21960. /**
  21961. * Get a single player based on an ID or DOM element.
  21962. *
  21963. * This is useful if you want to check if an element or ID has an associated
  21964. * Video.js player, but not create one if it doesn't.
  21965. *
  21966. * @param {string|Element} id
  21967. * An HTML element - `<video>`, `<audio>`, or `<video-js>` -
  21968. * or a string matching the `id` of such an element.
  21969. *
  21970. * @return {Player|undefined}
  21971. * A player instance or `undefined` if there is no player instance
  21972. * matching the argument.
  21973. */
  21974. videojs$1.getPlayer = function (id) {
  21975. var players = Player.players;
  21976. var tag;
  21977. if (typeof id === 'string') {
  21978. var nId = normalizeId(id);
  21979. var player = players[nId];
  21980. if (player) {
  21981. return player;
  21982. }
  21983. tag = $('#' + nId);
  21984. } else {
  21985. tag = id;
  21986. }
  21987. if (isEl(tag)) {
  21988. var _tag = tag,
  21989. _player = _tag.player,
  21990. playerId = _tag.playerId; // Element may have a `player` property referring to an already created
  21991. // player instance. If so, return that.
  21992. if (_player || players[playerId]) {
  21993. return _player || players[playerId];
  21994. }
  21995. }
  21996. };
  21997. /**
  21998. * Returns an array of all current players.
  21999. *
  22000. * @return {Array}
  22001. * An array of all players. The array will be in the order that
  22002. * `Object.keys` provides, which could potentially vary between
  22003. * JavaScript engines.
  22004. *
  22005. */
  22006. videojs$1.getAllPlayers = function () {
  22007. return (// Disposed players leave a key with a `null` value, so we need to make sure
  22008. // we filter those out.
  22009. Object.keys(Player.players).map(function (k) {
  22010. return Player.players[k];
  22011. }).filter(Boolean)
  22012. );
  22013. };
  22014. videojs$1.players = Player.players;
  22015. videojs$1.getComponent = Component.getComponent;
  22016. /**
  22017. * Register a component so it can referred to by name. Used when adding to other
  22018. * components, either through addChild `component.addChild('myComponent')` or through
  22019. * default children options `{ children: ['myComponent'] }`.
  22020. *
  22021. * > NOTE: You could also just initialize the component before adding.
  22022. * `component.addChild(new MyComponent());`
  22023. *
  22024. * @param {string} name
  22025. * The class name of the component
  22026. *
  22027. * @param {Component} comp
  22028. * The component class
  22029. *
  22030. * @return {Component}
  22031. * The newly registered component
  22032. */
  22033. videojs$1.registerComponent = function (name$$1, comp) {
  22034. if (Tech.isTech(comp)) {
  22035. log.warn("The " + name$$1 + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
  22036. }
  22037. Component.registerComponent.call(Component, name$$1, comp);
  22038. };
  22039. videojs$1.getTech = Tech.getTech;
  22040. videojs$1.registerTech = Tech.registerTech;
  22041. videojs$1.use = use;
  22042. /**
  22043. * An object that can be returned by a middleware to signify
  22044. * that the middleware is being terminated.
  22045. *
  22046. * @type {object}
  22047. * @property {object} middleware.TERMINATOR
  22048. */
  22049. Object.defineProperty(videojs$1, 'middleware', {
  22050. value: {},
  22051. writeable: false,
  22052. enumerable: true
  22053. });
  22054. Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
  22055. value: TERMINATOR,
  22056. writeable: false,
  22057. enumerable: true
  22058. });
  22059. /**
  22060. * A reference to the {@link module:browser|browser utility module} as an object.
  22061. *
  22062. * @type {Object}
  22063. * @see {@link module:browser|browser}
  22064. */
  22065. videojs$1.browser = browser;
  22066. /**
  22067. * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
  22068. * included for backward-compatibility with 4.x.
  22069. *
  22070. * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
  22071. * @type {boolean}
  22072. */
  22073. videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
  22074. videojs$1.extend = extend;
  22075. videojs$1.mergeOptions = mergeOptions;
  22076. videojs$1.bind = bind;
  22077. videojs$1.registerPlugin = Plugin.registerPlugin;
  22078. videojs$1.deregisterPlugin = Plugin.deregisterPlugin;
  22079. /**
  22080. * Deprecated method to register a plugin with Video.js
  22081. *
  22082. * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
  22083. *
  22084. * @param {string} name
  22085. * The plugin name
  22086. *
  22087. * @param {Plugin|Function} plugin
  22088. * The plugin sub-class or function
  22089. */
  22090. videojs$1.plugin = function (name$$1, plugin) {
  22091. log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
  22092. return Plugin.registerPlugin(name$$1, plugin);
  22093. };
  22094. videojs$1.getPlugins = Plugin.getPlugins;
  22095. videojs$1.getPlugin = Plugin.getPlugin;
  22096. videojs$1.getPluginVersion = Plugin.getPluginVersion;
  22097. /**
  22098. * Adding languages so that they're available to all players.
  22099. * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
  22100. *
  22101. * @param {string} code
  22102. * The language code or dictionary property
  22103. *
  22104. * @param {Object} data
  22105. * The data values to be translated
  22106. *
  22107. * @return {Object}
  22108. * The resulting language dictionary object
  22109. */
  22110. videojs$1.addLanguage = function (code, data) {
  22111. var _mergeOptions;
  22112. code = ('' + code).toLowerCase();
  22113. videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
  22114. return videojs$1.options.languages[code];
  22115. };
  22116. /**
  22117. * A reference to the {@link module:log|log utility module} as an object.
  22118. *
  22119. * @type {Function}
  22120. * @see {@link module:log|log}
  22121. */
  22122. videojs$1.log = log;
  22123. videojs$1.createLogger = createLogger$1;
  22124. videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
  22125. videojs$1.formatTime = formatTime;
  22126. videojs$1.setFormatTime = setFormatTime;
  22127. videojs$1.resetFormatTime = resetFormatTime;
  22128. videojs$1.parseUrl = parseUrl;
  22129. videojs$1.isCrossOrigin = isCrossOrigin;
  22130. videojs$1.EventTarget = EventTarget;
  22131. videojs$1.on = on;
  22132. videojs$1.one = one;
  22133. videojs$1.off = off;
  22134. videojs$1.trigger = trigger;
  22135. /**
  22136. * A cross-browser XMLHttpRequest wrapper.
  22137. *
  22138. * @function
  22139. * @param {Object} options
  22140. * Settings for the request.
  22141. *
  22142. * @return {XMLHttpRequest|XDomainRequest}
  22143. * The request object.
  22144. *
  22145. * @see https://github.com/Raynos/xhr
  22146. */
  22147. videojs$1.xhr = xhr;
  22148. videojs$1.TextTrack = TextTrack;
  22149. videojs$1.AudioTrack = AudioTrack;
  22150. videojs$1.VideoTrack = VideoTrack;
  22151. ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
  22152. videojs$1[k] = function () {
  22153. log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
  22154. return Dom[k].apply(null, arguments);
  22155. };
  22156. });
  22157. videojs$1.computedStyle = computedStyle;
  22158. /**
  22159. * A reference to the {@link module:dom|DOM utility module} as an object.
  22160. *
  22161. * @type {Object}
  22162. * @see {@link module:dom|dom}
  22163. */
  22164. videojs$1.dom = Dom;
  22165. /**
  22166. * A reference to the {@link module:url|URL utility module} as an object.
  22167. *
  22168. * @type {Object}
  22169. * @see {@link module:url|url}
  22170. */
  22171. videojs$1.url = Url;
  22172. /**
  22173. * @videojs/http-streaming
  22174. * @version 1.10.1
  22175. * @copyright 2019 Brightcove, Inc
  22176. * @license Apache-2.0
  22177. */
  22178. /**
  22179. * @file resolve-url.js - Handling how URLs are resolved and manipulated
  22180. */
  22181. var resolveUrl = function resolveUrl(baseURL, relativeURL) {
  22182. // return early if we don't need to resolve
  22183. if (/^[a-z]+:/i.test(relativeURL)) {
  22184. return relativeURL;
  22185. } // if the base URL is relative then combine with the current location
  22186. if (!/\/\//i.test(baseURL)) {
  22187. baseURL = URLToolkit.buildAbsoluteURL(window$1.location.href, baseURL);
  22188. }
  22189. return URLToolkit.buildAbsoluteURL(baseURL, relativeURL);
  22190. };
  22191. /**
  22192. * Checks whether xhr request was redirected and returns correct url depending
  22193. * on `handleManifestRedirects` option
  22194. *
  22195. * @api private
  22196. *
  22197. * @param {String} url - an url being requested
  22198. * @param {XMLHttpRequest} req - xhr request result
  22199. *
  22200. * @return {String}
  22201. */
  22202. var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
  22203. // To understand how the responseURL below is set and generated:
  22204. // - https://fetch.spec.whatwg.org/#concept-response-url
  22205. // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
  22206. if (handleManifestRedirect && req.responseURL && url !== req.responseURL) {
  22207. return req.responseURL;
  22208. }
  22209. return url;
  22210. };
  22211. var classCallCheck = function classCallCheck(instance, Constructor) {
  22212. if (!(instance instanceof Constructor)) {
  22213. throw new TypeError("Cannot call a class as a function");
  22214. }
  22215. };
  22216. var createClass = function () {
  22217. function defineProperties(target, props) {
  22218. for (var i = 0; i < props.length; i++) {
  22219. var descriptor = props[i];
  22220. descriptor.enumerable = descriptor.enumerable || false;
  22221. descriptor.configurable = true;
  22222. if ("value" in descriptor) descriptor.writable = true;
  22223. Object.defineProperty(target, descriptor.key, descriptor);
  22224. }
  22225. }
  22226. return function (Constructor, protoProps, staticProps) {
  22227. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  22228. if (staticProps) defineProperties(Constructor, staticProps);
  22229. return Constructor;
  22230. };
  22231. }();
  22232. var get$1 = function get(object, property, receiver) {
  22233. if (object === null) object = Function.prototype;
  22234. var desc = Object.getOwnPropertyDescriptor(object, property);
  22235. if (desc === undefined) {
  22236. var parent = Object.getPrototypeOf(object);
  22237. if (parent === null) {
  22238. return undefined;
  22239. } else {
  22240. return get(parent, property, receiver);
  22241. }
  22242. } else if ("value" in desc) {
  22243. return desc.value;
  22244. } else {
  22245. var getter = desc.get;
  22246. if (getter === undefined) {
  22247. return undefined;
  22248. }
  22249. return getter.call(receiver);
  22250. }
  22251. };
  22252. var inherits = function inherits(subClass, superClass) {
  22253. if (typeof superClass !== "function" && superClass !== null) {
  22254. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  22255. }
  22256. subClass.prototype = Object.create(superClass && superClass.prototype, {
  22257. constructor: {
  22258. value: subClass,
  22259. enumerable: false,
  22260. writable: true,
  22261. configurable: true
  22262. }
  22263. });
  22264. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  22265. };
  22266. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  22267. if (!self) {
  22268. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  22269. }
  22270. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  22271. };
  22272. var slicedToArray = function () {
  22273. function sliceIterator(arr, i) {
  22274. var _arr = [];
  22275. var _n = true;
  22276. var _d = false;
  22277. var _e = undefined;
  22278. try {
  22279. for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
  22280. _arr.push(_s.value);
  22281. if (i && _arr.length === i) break;
  22282. }
  22283. } catch (err) {
  22284. _d = true;
  22285. _e = err;
  22286. } finally {
  22287. try {
  22288. if (!_n && _i["return"]) _i["return"]();
  22289. } finally {
  22290. if (_d) throw _e;
  22291. }
  22292. }
  22293. return _arr;
  22294. }
  22295. return function (arr, i) {
  22296. if (Array.isArray(arr)) {
  22297. return arr;
  22298. } else if (Symbol.iterator in Object(arr)) {
  22299. return sliceIterator(arr, i);
  22300. } else {
  22301. throw new TypeError("Invalid attempt to destructure non-iterable instance");
  22302. }
  22303. };
  22304. }();
  22305. /**
  22306. * @file playlist-loader.js
  22307. *
  22308. * A state machine that manages the loading, caching, and updating of
  22309. * M3U8 playlists.
  22310. *
  22311. */
  22312. var mergeOptions$1 = videojs$1.mergeOptions,
  22313. EventTarget$1 = videojs$1.EventTarget,
  22314. log$1 = videojs$1.log;
  22315. /**
  22316. * Loops through all supported media groups in master and calls the provided
  22317. * callback for each group
  22318. *
  22319. * @param {Object} master
  22320. * The parsed master manifest object
  22321. * @param {Function} callback
  22322. * Callback to call for each media group
  22323. */
  22324. var forEachMediaGroup = function forEachMediaGroup(master, callback) {
  22325. ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
  22326. for (var groupKey in master.mediaGroups[mediaType]) {
  22327. for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
  22328. var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
  22329. callback(mediaProperties, mediaType, groupKey, labelKey);
  22330. }
  22331. }
  22332. });
  22333. };
  22334. /**
  22335. * Returns a new array of segments that is the result of merging
  22336. * properties from an older list of segments onto an updated
  22337. * list. No properties on the updated playlist will be overridden.
  22338. *
  22339. * @param {Array} original the outdated list of segments
  22340. * @param {Array} update the updated list of segments
  22341. * @param {Number=} offset the index of the first update
  22342. * segment in the original segment list. For non-live playlists,
  22343. * this should always be zero and does not need to be
  22344. * specified. For live playlists, it should be the difference
  22345. * between the media sequence numbers in the original and updated
  22346. * playlists.
  22347. * @return a list of merged segment objects
  22348. */
  22349. var updateSegments = function updateSegments(original, update, offset) {
  22350. var result = update.slice();
  22351. offset = offset || 0;
  22352. var length = Math.min(original.length, update.length + offset);
  22353. for (var i = offset; i < length; i++) {
  22354. result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
  22355. }
  22356. return result;
  22357. };
  22358. var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
  22359. if (!segment.resolvedUri) {
  22360. segment.resolvedUri = resolveUrl(baseUri, segment.uri);
  22361. }
  22362. if (segment.key && !segment.key.resolvedUri) {
  22363. segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri);
  22364. }
  22365. if (segment.map && !segment.map.resolvedUri) {
  22366. segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri);
  22367. }
  22368. };
  22369. /**
  22370. * Returns a new master playlist that is the result of merging an
  22371. * updated media playlist into the original version. If the
  22372. * updated media playlist does not match any of the playlist
  22373. * entries in the original master playlist, null is returned.
  22374. *
  22375. * @param {Object} master a parsed master M3U8 object
  22376. * @param {Object} media a parsed media M3U8 object
  22377. * @return {Object} a new object that represents the original
  22378. * master playlist with the updated media playlist merged in, or
  22379. * null if the merge produced no change.
  22380. */
  22381. var updateMaster = function updateMaster(master, media) {
  22382. var result = mergeOptions$1(master, {});
  22383. var playlist = result.playlists[media.uri];
  22384. if (!playlist) {
  22385. return null;
  22386. } // consider the playlist unchanged if the number of segments is equal, the media
  22387. // sequence number is unchanged, and this playlist hasn't become the end of the playlist
  22388. if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) {
  22389. return null;
  22390. }
  22391. var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists
  22392. if (playlist.segments) {
  22393. mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
  22394. } // resolve any segment URIs to prevent us from having to do it later
  22395. mergedPlaylist.segments.forEach(function (segment) {
  22396. resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
  22397. }); // TODO Right now in the playlists array there are two references to each playlist, one
  22398. // that is referenced by index, and one by URI. The index reference may no longer be
  22399. // necessary.
  22400. for (var i = 0; i < result.playlists.length; i++) {
  22401. if (result.playlists[i].uri === media.uri) {
  22402. result.playlists[i] = mergedPlaylist;
  22403. }
  22404. }
  22405. result.playlists[media.uri] = mergedPlaylist;
  22406. return result;
  22407. };
  22408. var setupMediaPlaylists = function setupMediaPlaylists(master) {
  22409. // setup by-URI lookups and resolve media playlist URIs
  22410. var i = master.playlists.length;
  22411. while (i--) {
  22412. var playlist = master.playlists[i];
  22413. master.playlists[playlist.uri] = playlist;
  22414. playlist.resolvedUri = resolveUrl(master.uri, playlist.uri);
  22415. playlist.id = i;
  22416. if (!playlist.attributes) {
  22417. // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
  22418. // BANDWIDTH attribute, we can play the stream without it. This means a poorly
  22419. // formatted master playlist may not have an attribute list. An attributes
  22420. // property is added here to prevent undefined references when we encounter
  22421. // this scenario.
  22422. playlist.attributes = {};
  22423. log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
  22424. }
  22425. }
  22426. };
  22427. var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
  22428. forEachMediaGroup(master, function (properties) {
  22429. if (properties.uri) {
  22430. properties.resolvedUri = resolveUrl(master.uri, properties.uri);
  22431. }
  22432. });
  22433. };
  22434. /**
  22435. * Calculates the time to wait before refreshing a live playlist
  22436. *
  22437. * @param {Object} media
  22438. * The current media
  22439. * @param {Boolean} update
  22440. * True if there were any updates from the last refresh, false otherwise
  22441. * @return {Number}
  22442. * The time in ms to wait before refreshing the live playlist
  22443. */
  22444. var refreshDelay = function refreshDelay(media, update) {
  22445. var lastSegment = media.segments[media.segments.length - 1];
  22446. var delay = void 0;
  22447. if (update && lastSegment && lastSegment.duration) {
  22448. delay = lastSegment.duration * 1000;
  22449. } else {
  22450. // if the playlist is unchanged since the last reload or last segment duration
  22451. // cannot be determined, try again after half the target duration
  22452. delay = (media.targetDuration || 10) * 500;
  22453. }
  22454. return delay;
  22455. };
  22456. /**
  22457. * Load a playlist from a remote location
  22458. *
  22459. * @class PlaylistLoader
  22460. * @extends Stream
  22461. * @param {String} srcUrl the url to start with
  22462. * @param {Boolean} withCredentials the withCredentials xhr option
  22463. * @constructor
  22464. */
  22465. var PlaylistLoader = function (_EventTarget) {
  22466. inherits(PlaylistLoader, _EventTarget);
  22467. function PlaylistLoader(srcUrl, hls) {
  22468. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  22469. classCallCheck(this, PlaylistLoader);
  22470. var _this = possibleConstructorReturn(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this));
  22471. var _options$withCredenti = options.withCredentials,
  22472. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  22473. _options$handleManife = options.handleManifestRedirects,
  22474. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  22475. _this.srcUrl = srcUrl;
  22476. _this.hls_ = hls;
  22477. _this.withCredentials = withCredentials;
  22478. _this.handleManifestRedirects = handleManifestRedirects;
  22479. var hlsOptions = hls.options_;
  22480. _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || [];
  22481. _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || [];
  22482. if (!_this.srcUrl) {
  22483. throw new Error('A non-empty playlist URL is required');
  22484. } // initialize the loader state
  22485. _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
  22486. _this.on('mediaupdatetimeout', function () {
  22487. if (_this.state !== 'HAVE_METADATA') {
  22488. // only refresh the media playlist if no other activity is going on
  22489. return;
  22490. }
  22491. _this.state = 'HAVE_CURRENT_METADATA';
  22492. _this.request = _this.hls_.xhr({
  22493. uri: resolveUrl(_this.master.uri, _this.media().uri),
  22494. withCredentials: _this.withCredentials
  22495. }, function (error, req) {
  22496. // disposed
  22497. if (!_this.request) {
  22498. return;
  22499. }
  22500. if (error) {
  22501. return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
  22502. }
  22503. _this.haveMetadata(_this.request, _this.media().uri);
  22504. });
  22505. });
  22506. return _this;
  22507. }
  22508. createClass(PlaylistLoader, [{
  22509. key: 'playlistRequestError',
  22510. value: function playlistRequestError(xhr$$1, url, startingState) {
  22511. // any in-flight request is now finished
  22512. this.request = null;
  22513. if (startingState) {
  22514. this.state = startingState;
  22515. }
  22516. this.error = {
  22517. playlist: this.master.playlists[url],
  22518. status: xhr$$1.status,
  22519. message: 'HLS playlist request error at URL: ' + url,
  22520. responseText: xhr$$1.responseText,
  22521. code: xhr$$1.status >= 500 ? 4 : 2
  22522. };
  22523. this.trigger('error');
  22524. } // update the playlist loader's state in response to a new or
  22525. // updated playlist.
  22526. }, {
  22527. key: 'haveMetadata',
  22528. value: function haveMetadata(xhr$$1, url) {
  22529. var _this2 = this; // any in-flight request is now finished
  22530. this.request = null;
  22531. this.state = 'HAVE_METADATA';
  22532. var parser = new Parser(); // adding custom tag parsers
  22533. this.customTagParsers.forEach(function (customParser) {
  22534. return parser.addParser(customParser);
  22535. }); // adding custom tag mappers
  22536. this.customTagMappers.forEach(function (mapper) {
  22537. return parser.addTagMapper(mapper);
  22538. });
  22539. parser.push(xhr$$1.responseText);
  22540. parser.end();
  22541. parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make
  22542. // sure that the property is attached to avoid undefined reference errors
  22543. parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master
  22544. var update = updateMaster(this.master, parser.manifest);
  22545. this.targetDuration = parser.manifest.targetDuration;
  22546. if (update) {
  22547. this.master = update;
  22548. this.media_ = this.master.playlists[parser.manifest.uri];
  22549. } else {
  22550. this.trigger('playlistunchanged');
  22551. } // refresh live playlists after a target duration passes
  22552. if (!this.media().endList) {
  22553. window$1.clearTimeout(this.mediaUpdateTimeout);
  22554. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  22555. _this2.trigger('mediaupdatetimeout');
  22556. }, refreshDelay(this.media(), !!update));
  22557. }
  22558. this.trigger('loadedplaylist');
  22559. }
  22560. /**
  22561. * Abort any outstanding work and clean up.
  22562. */
  22563. }, {
  22564. key: 'dispose',
  22565. value: function dispose() {
  22566. this.stopRequest();
  22567. window$1.clearTimeout(this.mediaUpdateTimeout);
  22568. }
  22569. }, {
  22570. key: 'stopRequest',
  22571. value: function stopRequest() {
  22572. if (this.request) {
  22573. var oldRequest = this.request;
  22574. this.request = null;
  22575. oldRequest.onreadystatechange = null;
  22576. oldRequest.abort();
  22577. }
  22578. }
  22579. /**
  22580. * When called without any arguments, returns the currently
  22581. * active media playlist. When called with a single argument,
  22582. * triggers the playlist loader to asynchronously switch to the
  22583. * specified media playlist. Calling this method while the
  22584. * loader is in the HAVE_NOTHING causes an error to be emitted
  22585. * but otherwise has no effect.
  22586. *
  22587. * @param {Object=} playlist the parsed media playlist
  22588. * object to switch to
  22589. * @return {Playlist} the current loaded media
  22590. */
  22591. }, {
  22592. key: 'media',
  22593. value: function media(playlist) {
  22594. var _this3 = this; // getter
  22595. if (!playlist) {
  22596. return this.media_;
  22597. } // setter
  22598. if (this.state === 'HAVE_NOTHING') {
  22599. throw new Error('Cannot switch media playlist from ' + this.state);
  22600. }
  22601. var startingState = this.state; // find the playlist object if the target playlist has been
  22602. // specified by URI
  22603. if (typeof playlist === 'string') {
  22604. if (!this.master.playlists[playlist]) {
  22605. throw new Error('Unknown playlist URI: ' + playlist);
  22606. }
  22607. playlist = this.master.playlists[playlist];
  22608. }
  22609. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately
  22610. if (this.master.playlists[playlist.uri].endList) {
  22611. // abort outstanding playlist requests
  22612. if (this.request) {
  22613. this.request.onreadystatechange = null;
  22614. this.request.abort();
  22615. this.request = null;
  22616. }
  22617. this.state = 'HAVE_METADATA';
  22618. this.media_ = playlist; // trigger media change if the active media has been updated
  22619. if (mediaChange) {
  22620. this.trigger('mediachanging');
  22621. this.trigger('mediachange');
  22622. }
  22623. return;
  22624. } // switching to the active playlist is a no-op
  22625. if (!mediaChange) {
  22626. return;
  22627. }
  22628. this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
  22629. if (this.request) {
  22630. if (playlist.resolvedUri === this.request.url) {
  22631. // requesting to switch to the same playlist multiple times
  22632. // has no effect after the first
  22633. return;
  22634. }
  22635. this.request.onreadystatechange = null;
  22636. this.request.abort();
  22637. this.request = null;
  22638. } // request the new playlist
  22639. if (this.media_) {
  22640. this.trigger('mediachanging');
  22641. }
  22642. this.request = this.hls_.xhr({
  22643. uri: playlist.resolvedUri,
  22644. withCredentials: this.withCredentials
  22645. }, function (error, req) {
  22646. // disposed
  22647. if (!_this3.request) {
  22648. return;
  22649. }
  22650. playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req);
  22651. if (error) {
  22652. return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
  22653. }
  22654. _this3.haveMetadata(req, playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
  22655. if (startingState === 'HAVE_MASTER') {
  22656. _this3.trigger('loadedmetadata');
  22657. } else {
  22658. _this3.trigger('mediachange');
  22659. }
  22660. });
  22661. }
  22662. /**
  22663. * pause loading of the playlist
  22664. */
  22665. }, {
  22666. key: 'pause',
  22667. value: function pause() {
  22668. this.stopRequest();
  22669. window$1.clearTimeout(this.mediaUpdateTimeout);
  22670. if (this.state === 'HAVE_NOTHING') {
  22671. // If we pause the loader before any data has been retrieved, its as if we never
  22672. // started, so reset to an unstarted state.
  22673. this.started = false;
  22674. } // Need to restore state now that no activity is happening
  22675. if (this.state === 'SWITCHING_MEDIA') {
  22676. // if the loader was in the process of switching media, it should either return to
  22677. // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
  22678. // playlist yet. This is determined by the existence of loader.media_
  22679. if (this.media_) {
  22680. this.state = 'HAVE_METADATA';
  22681. } else {
  22682. this.state = 'HAVE_MASTER';
  22683. }
  22684. } else if (this.state === 'HAVE_CURRENT_METADATA') {
  22685. this.state = 'HAVE_METADATA';
  22686. }
  22687. }
  22688. /**
  22689. * start loading of the playlist
  22690. */
  22691. }, {
  22692. key: 'load',
  22693. value: function load(isFinalRendition) {
  22694. var _this4 = this;
  22695. window$1.clearTimeout(this.mediaUpdateTimeout);
  22696. var media = this.media();
  22697. if (isFinalRendition) {
  22698. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  22699. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  22700. return _this4.load();
  22701. }, delay);
  22702. return;
  22703. }
  22704. if (!this.started) {
  22705. this.start();
  22706. return;
  22707. }
  22708. if (media && !media.endList) {
  22709. this.trigger('mediaupdatetimeout');
  22710. } else {
  22711. this.trigger('loadedplaylist');
  22712. }
  22713. }
  22714. /**
  22715. * start loading of the playlist
  22716. */
  22717. }, {
  22718. key: 'start',
  22719. value: function start() {
  22720. var _this5 = this;
  22721. this.started = true; // request the specified URL
  22722. this.request = this.hls_.xhr({
  22723. uri: this.srcUrl,
  22724. withCredentials: this.withCredentials
  22725. }, function (error, req) {
  22726. // disposed
  22727. if (!_this5.request) {
  22728. return;
  22729. } // clear the loader's request reference
  22730. _this5.request = null;
  22731. if (error) {
  22732. _this5.error = {
  22733. status: req.status,
  22734. message: 'HLS playlist request error at URL: ' + _this5.srcUrl,
  22735. responseText: req.responseText,
  22736. // MEDIA_ERR_NETWORK
  22737. code: 2
  22738. };
  22739. if (_this5.state === 'HAVE_NOTHING') {
  22740. _this5.started = false;
  22741. }
  22742. return _this5.trigger('error');
  22743. }
  22744. var parser = new Parser(); // adding custom tag parsers
  22745. _this5.customTagParsers.forEach(function (customParser) {
  22746. return parser.addParser(customParser);
  22747. }); // adding custom tag mappers
  22748. _this5.customTagMappers.forEach(function (mapper) {
  22749. return parser.addTagMapper(mapper);
  22750. });
  22751. parser.push(req.responseText);
  22752. parser.end();
  22753. _this5.state = 'HAVE_MASTER';
  22754. _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
  22755. parser.manifest.uri = _this5.srcUrl; // loaded a master playlist
  22756. if (parser.manifest.playlists) {
  22757. _this5.master = parser.manifest;
  22758. setupMediaPlaylists(_this5.master);
  22759. resolveMediaGroupUris(_this5.master);
  22760. _this5.trigger('loadedplaylist');
  22761. if (!_this5.request) {
  22762. // no media playlist was specifically selected so start
  22763. // from the first listed one
  22764. _this5.media(parser.manifest.playlists[0]);
  22765. }
  22766. return;
  22767. } // loaded a media playlist
  22768. // infer a master playlist if none was previously requested
  22769. _this5.master = {
  22770. mediaGroups: {
  22771. 'AUDIO': {},
  22772. 'VIDEO': {},
  22773. 'CLOSED-CAPTIONS': {},
  22774. 'SUBTITLES': {}
  22775. },
  22776. uri: window$1.location.href,
  22777. playlists: [{
  22778. uri: _this5.srcUrl,
  22779. id: 0,
  22780. resolvedUri: _this5.srcUrl,
  22781. // m3u8-parser does not attach an attributes property to media playlists so make
  22782. // sure that the property is attached to avoid undefined reference errors
  22783. attributes: {}
  22784. }]
  22785. };
  22786. _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
  22787. _this5.haveMetadata(req, _this5.srcUrl);
  22788. return _this5.trigger('loadedmetadata');
  22789. });
  22790. }
  22791. }]);
  22792. return PlaylistLoader;
  22793. }(EventTarget$1);
  22794. /**
  22795. * @file playlist.js
  22796. *
  22797. * Playlist related utilities.
  22798. */
  22799. var createTimeRange = videojs$1.createTimeRange;
  22800. /**
  22801. * walk backward until we find a duration we can use
  22802. * or return a failure
  22803. *
  22804. * @param {Playlist} playlist the playlist to walk through
  22805. * @param {Number} endSequence the mediaSequence to stop walking on
  22806. */
  22807. var backwardDuration = function backwardDuration(playlist, endSequence) {
  22808. var result = 0;
  22809. var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
  22810. // the interval, use it
  22811. var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
  22812. // information that is earlier than endSequence
  22813. if (segment) {
  22814. if (typeof segment.start !== 'undefined') {
  22815. return {
  22816. result: segment.start,
  22817. precise: true
  22818. };
  22819. }
  22820. if (typeof segment.end !== 'undefined') {
  22821. return {
  22822. result: segment.end - segment.duration,
  22823. precise: true
  22824. };
  22825. }
  22826. }
  22827. while (i--) {
  22828. segment = playlist.segments[i];
  22829. if (typeof segment.end !== 'undefined') {
  22830. return {
  22831. result: result + segment.end,
  22832. precise: true
  22833. };
  22834. }
  22835. result += segment.duration;
  22836. if (typeof segment.start !== 'undefined') {
  22837. return {
  22838. result: result + segment.start,
  22839. precise: true
  22840. };
  22841. }
  22842. }
  22843. return {
  22844. result: result,
  22845. precise: false
  22846. };
  22847. };
  22848. /**
  22849. * walk forward until we find a duration we can use
  22850. * or return a failure
  22851. *
  22852. * @param {Playlist} playlist the playlist to walk through
  22853. * @param {Number} endSequence the mediaSequence to stop walking on
  22854. */
  22855. var forwardDuration = function forwardDuration(playlist, endSequence) {
  22856. var result = 0;
  22857. var segment = void 0;
  22858. var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
  22859. // information
  22860. for (; i < playlist.segments.length; i++) {
  22861. segment = playlist.segments[i];
  22862. if (typeof segment.start !== 'undefined') {
  22863. return {
  22864. result: segment.start - result,
  22865. precise: true
  22866. };
  22867. }
  22868. result += segment.duration;
  22869. if (typeof segment.end !== 'undefined') {
  22870. return {
  22871. result: segment.end - result,
  22872. precise: true
  22873. };
  22874. }
  22875. } // indicate we didn't find a useful duration estimate
  22876. return {
  22877. result: -1,
  22878. precise: false
  22879. };
  22880. };
  22881. /**
  22882. * Calculate the media duration from the segments associated with a
  22883. * playlist. The duration of a subinterval of the available segments
  22884. * may be calculated by specifying an end index.
  22885. *
  22886. * @param {Object} playlist a media playlist object
  22887. * @param {Number=} endSequence an exclusive upper boundary
  22888. * for the playlist. Defaults to playlist length.
  22889. * @param {Number} expired the amount of time that has dropped
  22890. * off the front of the playlist in a live scenario
  22891. * @return {Number} the duration between the first available segment
  22892. * and end index.
  22893. */
  22894. var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
  22895. var backward = void 0;
  22896. var forward = void 0;
  22897. if (typeof endSequence === 'undefined') {
  22898. endSequence = playlist.mediaSequence + playlist.segments.length;
  22899. }
  22900. if (endSequence < playlist.mediaSequence) {
  22901. return 0;
  22902. } // do a backward walk to estimate the duration
  22903. backward = backwardDuration(playlist, endSequence);
  22904. if (backward.precise) {
  22905. // if we were able to base our duration estimate on timing
  22906. // information provided directly from the Media Source, return
  22907. // it
  22908. return backward.result;
  22909. } // walk forward to see if a precise duration estimate can be made
  22910. // that way
  22911. forward = forwardDuration(playlist, endSequence);
  22912. if (forward.precise) {
  22913. // we found a segment that has been buffered and so it's
  22914. // position is known precisely
  22915. return forward.result;
  22916. } // return the less-precise, playlist-based duration estimate
  22917. return backward.result + expired;
  22918. };
  22919. /**
  22920. * Calculates the duration of a playlist. If a start and end index
  22921. * are specified, the duration will be for the subset of the media
  22922. * timeline between those two indices. The total duration for live
  22923. * playlists is always Infinity.
  22924. *
  22925. * @param {Object} playlist a media playlist object
  22926. * @param {Number=} endSequence an exclusive upper
  22927. * boundary for the playlist. Defaults to the playlist media
  22928. * sequence number plus its length.
  22929. * @param {Number=} expired the amount of time that has
  22930. * dropped off the front of the playlist in a live scenario
  22931. * @return {Number} the duration between the start index and end
  22932. * index.
  22933. */
  22934. var duration = function duration(playlist, endSequence, expired) {
  22935. if (!playlist) {
  22936. return 0;
  22937. }
  22938. if (typeof expired !== 'number') {
  22939. expired = 0;
  22940. } // if a slice of the total duration is not requested, use
  22941. // playlist-level duration indicators when they're present
  22942. if (typeof endSequence === 'undefined') {
  22943. // if present, use the duration specified in the playlist
  22944. if (playlist.totalDuration) {
  22945. return playlist.totalDuration;
  22946. } // duration should be Infinity for live playlists
  22947. if (!playlist.endList) {
  22948. return window$1.Infinity;
  22949. }
  22950. } // calculate the total duration based on the segment durations
  22951. return intervalDuration(playlist, endSequence, expired);
  22952. };
  22953. /**
  22954. * Calculate the time between two indexes in the current playlist
  22955. * neight the start- nor the end-index need to be within the current
  22956. * playlist in which case, the targetDuration of the playlist is used
  22957. * to approximate the durations of the segments
  22958. *
  22959. * @param {Object} playlist a media playlist object
  22960. * @param {Number} startIndex
  22961. * @param {Number} endIndex
  22962. * @return {Number} the number of seconds between startIndex and endIndex
  22963. */
  22964. var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
  22965. var durations = 0;
  22966. if (startIndex > endIndex) {
  22967. var _ref = [endIndex, startIndex];
  22968. startIndex = _ref[0];
  22969. endIndex = _ref[1];
  22970. }
  22971. if (startIndex < 0) {
  22972. for (var i = startIndex; i < Math.min(0, endIndex); i++) {
  22973. durations += playlist.targetDuration;
  22974. }
  22975. startIndex = 0;
  22976. }
  22977. for (var _i = startIndex; _i < endIndex; _i++) {
  22978. durations += playlist.segments[_i].duration;
  22979. }
  22980. return durations;
  22981. };
  22982. /**
  22983. * Determines the media index of the segment corresponding to the safe edge of the live
  22984. * window which is the duration of the last segment plus 2 target durations from the end
  22985. * of the playlist.
  22986. *
  22987. * @param {Object} playlist
  22988. * a media playlist object
  22989. * @return {Number}
  22990. * The media index of the segment at the safe live point. 0 if there is no "safe"
  22991. * point.
  22992. * @function safeLiveIndex
  22993. */
  22994. var safeLiveIndex = function safeLiveIndex(playlist) {
  22995. if (!playlist.segments.length) {
  22996. return 0;
  22997. }
  22998. var i = playlist.segments.length - 1;
  22999. var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
  23000. var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
  23001. while (i--) {
  23002. distanceFromEnd += playlist.segments[i].duration;
  23003. if (distanceFromEnd >= safeDistance) {
  23004. break;
  23005. }
  23006. }
  23007. return Math.max(0, i);
  23008. };
  23009. /**
  23010. * Calculates the playlist end time
  23011. *
  23012. * @param {Object} playlist a media playlist object
  23013. * @param {Number=} expired the amount of time that has
  23014. * dropped off the front of the playlist in a live scenario
  23015. * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
  23016. * playlist end calculation should consider the safe live end
  23017. * (truncate the playlist end by three segments). This is normally
  23018. * used for calculating the end of the playlist's seekable range.
  23019. * @returns {Number} the end time of playlist
  23020. * @function playlistEnd
  23021. */
  23022. var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
  23023. if (!playlist || !playlist.segments) {
  23024. return null;
  23025. }
  23026. if (playlist.endList) {
  23027. return duration(playlist);
  23028. }
  23029. if (expired === null) {
  23030. return null;
  23031. }
  23032. expired = expired || 0;
  23033. var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
  23034. return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
  23035. };
  23036. /**
  23037. * Calculates the interval of time that is currently seekable in a
  23038. * playlist. The returned time ranges are relative to the earliest
  23039. * moment in the specified playlist that is still available. A full
  23040. * seekable implementation for live streams would need to offset
  23041. * these values by the duration of content that has expired from the
  23042. * stream.
  23043. *
  23044. * @param {Object} playlist a media playlist object
  23045. * dropped off the front of the playlist in a live scenario
  23046. * @param {Number=} expired the amount of time that has
  23047. * dropped off the front of the playlist in a live scenario
  23048. * @return {TimeRanges} the periods of time that are valid targets
  23049. * for seeking
  23050. */
  23051. var seekable = function seekable(playlist, expired) {
  23052. var useSafeLiveEnd = true;
  23053. var seekableStart = expired || 0;
  23054. var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
  23055. if (seekableEnd === null) {
  23056. return createTimeRange();
  23057. }
  23058. return createTimeRange(seekableStart, seekableEnd);
  23059. };
  23060. var isWholeNumber = function isWholeNumber(num) {
  23061. return num - Math.floor(num) === 0;
  23062. };
  23063. var roundSignificantDigit = function roundSignificantDigit(increment, num) {
  23064. // If we have a whole number, just add 1 to it
  23065. if (isWholeNumber(num)) {
  23066. return num + increment * 0.1;
  23067. }
  23068. var numDecimalDigits = num.toString().split('.')[1].length;
  23069. for (var i = 1; i <= numDecimalDigits; i++) {
  23070. var scale = Math.pow(10, i);
  23071. var temp = num * scale;
  23072. if (isWholeNumber(temp) || i === numDecimalDigits) {
  23073. return (temp + increment) / scale;
  23074. }
  23075. }
  23076. };
  23077. var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
  23078. var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
  23079. /**
  23080. * Determine the index and estimated starting time of the segment that
  23081. * contains a specified playback position in a media playlist.
  23082. *
  23083. * @param {Object} playlist the media playlist to query
  23084. * @param {Number} currentTime The number of seconds since the earliest
  23085. * possible position to determine the containing segment for
  23086. * @param {Number} startIndex
  23087. * @param {Number} startTime
  23088. * @return {Object}
  23089. */
  23090. var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
  23091. var i = void 0;
  23092. var segment = void 0;
  23093. var numSegments = playlist.segments.length;
  23094. var time = currentTime - startTime;
  23095. if (time < 0) {
  23096. // Walk backward from startIndex in the playlist, adding durations
  23097. // until we find a segment that contains `time` and return it
  23098. if (startIndex > 0) {
  23099. for (i = startIndex - 1; i >= 0; i--) {
  23100. segment = playlist.segments[i];
  23101. time += floorLeastSignificantDigit(segment.duration);
  23102. if (time > 0) {
  23103. return {
  23104. mediaIndex: i,
  23105. startTime: startTime - sumDurations(playlist, startIndex, i)
  23106. };
  23107. }
  23108. }
  23109. } // We were unable to find a good segment within the playlist
  23110. // so select the first segment
  23111. return {
  23112. mediaIndex: 0,
  23113. startTime: currentTime
  23114. };
  23115. } // When startIndex is negative, we first walk forward to first segment
  23116. // adding target durations. If we "run out of time" before getting to
  23117. // the first segment, return the first segment
  23118. if (startIndex < 0) {
  23119. for (i = startIndex; i < 0; i++) {
  23120. time -= playlist.targetDuration;
  23121. if (time < 0) {
  23122. return {
  23123. mediaIndex: 0,
  23124. startTime: currentTime
  23125. };
  23126. }
  23127. }
  23128. startIndex = 0;
  23129. } // Walk forward from startIndex in the playlist, subtracting durations
  23130. // until we find a segment that contains `time` and return it
  23131. for (i = startIndex; i < numSegments; i++) {
  23132. segment = playlist.segments[i];
  23133. time -= ceilLeastSignificantDigit(segment.duration);
  23134. if (time < 0) {
  23135. return {
  23136. mediaIndex: i,
  23137. startTime: startTime + sumDurations(playlist, startIndex, i)
  23138. };
  23139. }
  23140. } // We are out of possible candidates so load the last one...
  23141. return {
  23142. mediaIndex: numSegments - 1,
  23143. startTime: currentTime
  23144. };
  23145. };
  23146. /**
  23147. * Check whether the playlist is blacklisted or not.
  23148. *
  23149. * @param {Object} playlist the media playlist object
  23150. * @return {boolean} whether the playlist is blacklisted or not
  23151. * @function isBlacklisted
  23152. */
  23153. var isBlacklisted = function isBlacklisted(playlist) {
  23154. return playlist.excludeUntil && playlist.excludeUntil > Date.now();
  23155. };
  23156. /**
  23157. * Check whether the playlist is compatible with current playback configuration or has
  23158. * been blacklisted permanently for being incompatible.
  23159. *
  23160. * @param {Object} playlist the media playlist object
  23161. * @return {boolean} whether the playlist is incompatible or not
  23162. * @function isIncompatible
  23163. */
  23164. var isIncompatible = function isIncompatible(playlist) {
  23165. return playlist.excludeUntil && playlist.excludeUntil === Infinity;
  23166. };
  23167. /**
  23168. * Check whether the playlist is enabled or not.
  23169. *
  23170. * @param {Object} playlist the media playlist object
  23171. * @return {boolean} whether the playlist is enabled or not
  23172. * @function isEnabled
  23173. */
  23174. var isEnabled = function isEnabled(playlist) {
  23175. var blacklisted = isBlacklisted(playlist);
  23176. return !playlist.disabled && !blacklisted;
  23177. };
  23178. /**
  23179. * Check whether the playlist has been manually disabled through the representations api.
  23180. *
  23181. * @param {Object} playlist the media playlist object
  23182. * @return {boolean} whether the playlist is disabled manually or not
  23183. * @function isDisabled
  23184. */
  23185. var isDisabled = function isDisabled(playlist) {
  23186. return playlist.disabled;
  23187. };
  23188. /**
  23189. * Returns whether the current playlist is an AES encrypted HLS stream
  23190. *
  23191. * @return {Boolean} true if it's an AES encrypted HLS stream
  23192. */
  23193. var isAes = function isAes(media) {
  23194. for (var i = 0; i < media.segments.length; i++) {
  23195. if (media.segments[i].key) {
  23196. return true;
  23197. }
  23198. }
  23199. return false;
  23200. };
  23201. /**
  23202. * Returns whether the current playlist contains fMP4
  23203. *
  23204. * @return {Boolean} true if the playlist contains fMP4
  23205. */
  23206. var isFmp4 = function isFmp4(media) {
  23207. for (var i = 0; i < media.segments.length; i++) {
  23208. if (media.segments[i].map) {
  23209. return true;
  23210. }
  23211. }
  23212. return false;
  23213. };
  23214. /**
  23215. * Checks if the playlist has a value for the specified attribute
  23216. *
  23217. * @param {String} attr
  23218. * Attribute to check for
  23219. * @param {Object} playlist
  23220. * The media playlist object
  23221. * @return {Boolean}
  23222. * Whether the playlist contains a value for the attribute or not
  23223. * @function hasAttribute
  23224. */
  23225. var hasAttribute = function hasAttribute(attr, playlist) {
  23226. return playlist.attributes && playlist.attributes[attr];
  23227. };
  23228. /**
  23229. * Estimates the time required to complete a segment download from the specified playlist
  23230. *
  23231. * @param {Number} segmentDuration
  23232. * Duration of requested segment
  23233. * @param {Number} bandwidth
  23234. * Current measured bandwidth of the player
  23235. * @param {Object} playlist
  23236. * The media playlist object
  23237. * @param {Number=} bytesReceived
  23238. * Number of bytes already received for the request. Defaults to 0
  23239. * @return {Number|NaN}
  23240. * The estimated time to request the segment. NaN if bandwidth information for
  23241. * the given playlist is unavailable
  23242. * @function estimateSegmentRequestTime
  23243. */
  23244. var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
  23245. var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  23246. if (!hasAttribute('BANDWIDTH', playlist)) {
  23247. return NaN;
  23248. }
  23249. var size = segmentDuration * playlist.attributes.BANDWIDTH;
  23250. return (size - bytesReceived * 8) / bandwidth;
  23251. };
  23252. /*
  23253. * Returns whether the current playlist is the lowest rendition
  23254. *
  23255. * @return {Boolean} true if on lowest rendition
  23256. */
  23257. var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
  23258. if (master.playlists.length === 1) {
  23259. return true;
  23260. }
  23261. var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
  23262. return master.playlists.filter(function (playlist) {
  23263. if (!isEnabled(playlist)) {
  23264. return false;
  23265. }
  23266. return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
  23267. }).length === 0;
  23268. }; // exports
  23269. var Playlist = {
  23270. duration: duration,
  23271. seekable: seekable,
  23272. safeLiveIndex: safeLiveIndex,
  23273. getMediaInfoForTime: getMediaInfoForTime,
  23274. isEnabled: isEnabled,
  23275. isDisabled: isDisabled,
  23276. isBlacklisted: isBlacklisted,
  23277. isIncompatible: isIncompatible,
  23278. playlistEnd: playlistEnd,
  23279. isAes: isAes,
  23280. isFmp4: isFmp4,
  23281. hasAttribute: hasAttribute,
  23282. estimateSegmentRequestTime: estimateSegmentRequestTime,
  23283. isLowestEnabledRendition: isLowestEnabledRendition
  23284. };
  23285. /**
  23286. * @file xhr.js
  23287. */
  23288. var videojsXHR = videojs$1.xhr,
  23289. mergeOptions$1$1 = videojs$1.mergeOptions;
  23290. var xhrFactory = function xhrFactory() {
  23291. var xhr$$1 = function XhrFunction(options, callback) {
  23292. // Add a default timeout for all hls requests
  23293. options = mergeOptions$1$1({
  23294. timeout: 45e3
  23295. }, options); // Allow an optional user-specified function to modify the option
  23296. // object before we construct the xhr request
  23297. var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest;
  23298. if (beforeRequest && typeof beforeRequest === 'function') {
  23299. var newOptions = beforeRequest(options);
  23300. if (newOptions) {
  23301. options = newOptions;
  23302. }
  23303. }
  23304. var request = videojsXHR(options, function (error, response) {
  23305. var reqResponse = request.response;
  23306. if (!error && reqResponse) {
  23307. request.responseTime = Date.now();
  23308. request.roundTripTime = request.responseTime - request.requestTime;
  23309. request.bytesReceived = reqResponse.byteLength || reqResponse.length;
  23310. if (!request.bandwidth) {
  23311. request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
  23312. }
  23313. }
  23314. if (response.headers) {
  23315. request.responseHeaders = response.headers;
  23316. } // videojs.xhr now uses a specific code on the error
  23317. // object to signal that a request has timed out instead
  23318. // of setting a boolean on the request object
  23319. if (error && error.code === 'ETIMEDOUT') {
  23320. request.timedout = true;
  23321. } // videojs.xhr no longer considers status codes outside of 200 and 0
  23322. // (for file uris) to be errors, but the old XHR did, so emulate that
  23323. // behavior. Status 206 may be used in response to byterange requests.
  23324. if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
  23325. error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
  23326. }
  23327. callback(error, request);
  23328. });
  23329. var originalAbort = request.abort;
  23330. request.abort = function () {
  23331. request.aborted = true;
  23332. return originalAbort.apply(request, arguments);
  23333. };
  23334. request.uri = options.uri;
  23335. request.requestTime = Date.now();
  23336. return request;
  23337. };
  23338. return xhr$$1;
  23339. };
  23340. /**
  23341. * Turns segment byterange into a string suitable for use in
  23342. * HTTP Range requests
  23343. *
  23344. * @param {Object} byterange - an object with two values defining the start and end
  23345. * of a byte-range
  23346. */
  23347. var byterangeStr = function byterangeStr(byterange) {
  23348. var byterangeStart = void 0;
  23349. var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range
  23350. // header uses inclusive ranges
  23351. byterangeEnd = byterange.offset + byterange.length - 1;
  23352. byterangeStart = byterange.offset;
  23353. return 'bytes=' + byterangeStart + '-' + byterangeEnd;
  23354. };
  23355. /**
  23356. * Defines headers for use in the xhr request for a particular segment.
  23357. *
  23358. * @param {Object} segment - a simplified copy of the segmentInfo object
  23359. * from SegmentLoader
  23360. */
  23361. var segmentXhrHeaders = function segmentXhrHeaders(segment) {
  23362. var headers = {};
  23363. if (segment.byterange) {
  23364. headers.Range = byterangeStr(segment.byterange);
  23365. }
  23366. return headers;
  23367. };
  23368. /**
  23369. * @file bin-utils.js
  23370. */
  23371. /**
  23372. * convert a TimeRange to text
  23373. *
  23374. * @param {TimeRange} range the timerange to use for conversion
  23375. * @param {Number} i the iterator on the range to convert
  23376. */
  23377. var textRange = function textRange(range, i) {
  23378. return range.start(i) + '-' + range.end(i);
  23379. };
  23380. /**
  23381. * format a number as hex string
  23382. *
  23383. * @param {Number} e The number
  23384. * @param {Number} i the iterator
  23385. */
  23386. var formatHexString = function formatHexString(e, i) {
  23387. var value = e.toString(16);
  23388. return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
  23389. };
  23390. var formatAsciiString = function formatAsciiString(e) {
  23391. if (e >= 0x20 && e < 0x7e) {
  23392. return String.fromCharCode(e);
  23393. }
  23394. return '.';
  23395. };
  23396. /**
  23397. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  23398. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  23399. *
  23400. * @param {Object} message
  23401. * Object of properties and values to send to the web worker
  23402. * @return {Object}
  23403. * Modified message with TypedArray values expanded
  23404. * @function createTransferableMessage
  23405. */
  23406. var createTransferableMessage = function createTransferableMessage(message) {
  23407. var transferable = {};
  23408. Object.keys(message).forEach(function (key) {
  23409. var value = message[key];
  23410. if (ArrayBuffer.isView(value)) {
  23411. transferable[key] = {
  23412. bytes: value.buffer,
  23413. byteOffset: value.byteOffset,
  23414. byteLength: value.byteLength
  23415. };
  23416. } else {
  23417. transferable[key] = value;
  23418. }
  23419. });
  23420. return transferable;
  23421. };
  23422. /**
  23423. * Returns a unique string identifier for a media initialization
  23424. * segment.
  23425. */
  23426. var initSegmentId = function initSegmentId(initSegment) {
  23427. var byterange = initSegment.byterange || {
  23428. length: Infinity,
  23429. offset: 0
  23430. };
  23431. return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
  23432. };
  23433. /**
  23434. * Returns a unique string identifier for a media segment key.
  23435. */
  23436. var segmentKeyId = function segmentKeyId(key) {
  23437. return key.resolvedUri;
  23438. };
  23439. /**
  23440. * utils to help dump binary data to the console
  23441. */
  23442. var hexDump = function hexDump(data) {
  23443. var bytes = Array.prototype.slice.call(data);
  23444. var step = 16;
  23445. var result = '';
  23446. var hex = void 0;
  23447. var ascii = void 0;
  23448. for (var j = 0; j < bytes.length / step; j++) {
  23449. hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
  23450. ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
  23451. result += hex + ' ' + ascii + '\n';
  23452. }
  23453. return result;
  23454. };
  23455. var tagDump = function tagDump(_ref) {
  23456. var bytes = _ref.bytes;
  23457. return hexDump(bytes);
  23458. };
  23459. var textRanges = function textRanges(ranges) {
  23460. var result = '';
  23461. var i = void 0;
  23462. for (i = 0; i < ranges.length; i++) {
  23463. result += textRange(ranges, i) + ' ';
  23464. }
  23465. return result;
  23466. };
  23467. var utils =
  23468. /*#__PURE__*/
  23469. Object.freeze({
  23470. createTransferableMessage: createTransferableMessage,
  23471. initSegmentId: initSegmentId,
  23472. segmentKeyId: segmentKeyId,
  23473. hexDump: hexDump,
  23474. tagDump: tagDump,
  23475. textRanges: textRanges
  23476. }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
  23477. // Add 25% to the segment duration to account for small discrepencies in segment timing.
  23478. // 25% was arbitrarily chosen, and may need to be refined over time.
  23479. var SEGMENT_END_FUDGE_PERCENT = 0.25;
  23480. /**
  23481. * Converts a player time (any time that can be gotten/set from player.currentTime(),
  23482. * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
  23483. * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
  23484. *
  23485. * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
  23486. * point" (a point where we have a mapping from program time to player time, with player
  23487. * time being the post transmux start of the segment).
  23488. *
  23489. * For more details, see [this doc](../../docs/program-time-from-player-time.md).
  23490. *
  23491. * @param {Number} playerTime the player time
  23492. * @param {Object} segment the segment which contains the player time
  23493. * @return {Date} program time
  23494. */
  23495. var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
  23496. if (!segment.dateTimeObject) {
  23497. // Can't convert without an "anchor point" for the program time (i.e., a time that can
  23498. // be used to map the start of a segment with a real world time).
  23499. return null;
  23500. }
  23501. var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
  23502. var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
  23503. var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
  23504. var offsetFromSegmentStart = playerTime - startOfSegment;
  23505. return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
  23506. };
  23507. var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
  23508. return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
  23509. };
  23510. /**
  23511. * Finds a segment that contains the time requested given as an ISO-8601 string. The
  23512. * returned segment might be an estimate or an accurate match.
  23513. *
  23514. * @param {String} programTime The ISO-8601 programTime to find a match for
  23515. * @param {Object} playlist A playlist object to search within
  23516. */
  23517. var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
  23518. // Assumptions:
  23519. // - verifyProgramDateTimeTags has already been run
  23520. // - live streams have been started
  23521. var dateTimeObject = void 0;
  23522. try {
  23523. dateTimeObject = new Date(programTime);
  23524. } catch (e) {
  23525. return null;
  23526. }
  23527. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  23528. return null;
  23529. }
  23530. var segment = playlist.segments[0];
  23531. if (dateTimeObject < segment.dateTimeObject) {
  23532. // Requested time is before stream start.
  23533. return null;
  23534. }
  23535. for (var i = 0; i < playlist.segments.length - 1; i++) {
  23536. segment = playlist.segments[i];
  23537. var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
  23538. if (dateTimeObject < nextSegmentStart) {
  23539. break;
  23540. }
  23541. }
  23542. var lastSegment = playlist.segments[playlist.segments.length - 1];
  23543. var lastSegmentStart = lastSegment.dateTimeObject;
  23544. var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
  23545. var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
  23546. if (dateTimeObject > lastSegmentEnd) {
  23547. // Beyond the end of the stream, or our best guess of the end of the stream.
  23548. return null;
  23549. }
  23550. if (dateTimeObject > lastSegmentStart) {
  23551. segment = lastSegment;
  23552. }
  23553. return {
  23554. segment: segment,
  23555. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
  23556. // Although, given that all segments have accurate date time objects, the segment
  23557. // selected should be accurate, unless the video has been transmuxed at some point
  23558. // (determined by the presence of the videoTimingInfo object), the segment's "player
  23559. // time" (the start time in the player) can't be considered accurate.
  23560. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  23561. };
  23562. };
  23563. /**
  23564. * Finds a segment that contains the given player time(in seconds).
  23565. *
  23566. * @param {Number} time The player time to find a match for
  23567. * @param {Object} playlist A playlist object to search within
  23568. */
  23569. var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
  23570. // Assumptions:
  23571. // - there will always be a segment.duration
  23572. // - we can start from zero
  23573. // - segments are in time order
  23574. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  23575. return null;
  23576. }
  23577. var segmentEnd = 0;
  23578. var segment = void 0;
  23579. for (var i = 0; i < playlist.segments.length; i++) {
  23580. segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
  23581. // should contain the most accurate values we have for the segment's player times.
  23582. //
  23583. // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
  23584. // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
  23585. // calculate an end value.
  23586. segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
  23587. if (time <= segmentEnd) {
  23588. break;
  23589. }
  23590. }
  23591. var lastSegment = playlist.segments[playlist.segments.length - 1];
  23592. if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
  23593. // The time requested is beyond the stream end.
  23594. return null;
  23595. }
  23596. if (time > segmentEnd) {
  23597. // The time is within or beyond the last segment.
  23598. //
  23599. // Check to see if the time is beyond a reasonable guess of the end of the stream.
  23600. if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
  23601. // Technically, because the duration value is only an estimate, the time may still
  23602. // exist in the last segment, however, there isn't enough information to make even
  23603. // a reasonable estimate.
  23604. return null;
  23605. }
  23606. segment = lastSegment;
  23607. }
  23608. return {
  23609. segment: segment,
  23610. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
  23611. // Because videoTimingInfo is only set after transmux, it is the only way to get
  23612. // accurate timing values.
  23613. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  23614. };
  23615. };
  23616. /**
  23617. * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
  23618. * If the offset returned is positive, the programTime occurs after the
  23619. * comparisonTimestamp.
  23620. * If the offset is negative, the programTime occurs before the comparisonTimestamp.
  23621. *
  23622. * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against
  23623. * @param {String} programTime The programTime as an ISO-8601 string
  23624. * @return {Number} offset
  23625. */
  23626. var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
  23627. var segmentDateTime = void 0;
  23628. var programDateTime = void 0;
  23629. try {
  23630. segmentDateTime = new Date(comparisonTimeStamp);
  23631. programDateTime = new Date(programTime);
  23632. } catch (e) {// TODO handle error
  23633. }
  23634. var segmentTimeEpoch = segmentDateTime.getTime();
  23635. var programTimeEpoch = programDateTime.getTime();
  23636. return (programTimeEpoch - segmentTimeEpoch) / 1000;
  23637. };
  23638. /**
  23639. * Checks that all segments in this playlist have programDateTime tags.
  23640. *
  23641. * @param {Object} playlist A playlist object
  23642. */
  23643. var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
  23644. if (!playlist.segments || playlist.segments.length === 0) {
  23645. return false;
  23646. }
  23647. for (var i = 0; i < playlist.segments.length; i++) {
  23648. var segment = playlist.segments[i];
  23649. if (!segment.dateTimeObject) {
  23650. return false;
  23651. }
  23652. }
  23653. return true;
  23654. };
  23655. /**
  23656. * Returns the programTime of the media given a playlist and a playerTime.
  23657. * The playlist must have programDateTime tags for a programDateTime tag to be returned.
  23658. * If the segments containing the time requested have not been buffered yet, an estimate
  23659. * may be returned to the callback.
  23660. *
  23661. * @param {Object} args
  23662. * @param {Object} args.playlist A playlist object to search within
  23663. * @param {Number} time A playerTime in seconds
  23664. * @param {Function} callback(err, programTime)
  23665. * @returns {String} err.message A detailed error message
  23666. * @returns {Object} programTime
  23667. * @returns {Number} programTime.mediaSeconds The streamTime in seconds
  23668. * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String
  23669. */
  23670. var getProgramTime = function getProgramTime(_ref) {
  23671. var playlist = _ref.playlist,
  23672. _ref$time = _ref.time,
  23673. time = _ref$time === undefined ? undefined : _ref$time,
  23674. callback = _ref.callback;
  23675. if (!callback) {
  23676. throw new Error('getProgramTime: callback must be provided');
  23677. }
  23678. if (!playlist || time === undefined) {
  23679. return callback({
  23680. message: 'getProgramTime: playlist and time must be provided'
  23681. });
  23682. }
  23683. var matchedSegment = findSegmentForPlayerTime(time, playlist);
  23684. if (!matchedSegment) {
  23685. return callback({
  23686. message: 'valid programTime was not found'
  23687. });
  23688. }
  23689. if (matchedSegment.type === 'estimate') {
  23690. return callback({
  23691. message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
  23692. seekTime: matchedSegment.estimatedStart
  23693. });
  23694. }
  23695. var programTimeObject = {
  23696. mediaSeconds: time
  23697. };
  23698. var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
  23699. if (programTime) {
  23700. programTimeObject.programDateTime = programTime.toISOString();
  23701. }
  23702. return callback(null, programTimeObject);
  23703. };
  23704. /**
  23705. * Seeks in the player to a time that matches the given programTime ISO-8601 string.
  23706. *
  23707. * @param {Object} args
  23708. * @param {String} args.programTime A programTime to seek to as an ISO-8601 String
  23709. * @param {Object} args.playlist A playlist to look within
  23710. * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2.
  23711. * @param {Function} args.seekTo A method to perform a seek
  23712. * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
  23713. * @param {Object} args.tech The tech to seek on
  23714. * @param {Function} args.callback(err, newTime) A callback to return the new time to
  23715. * @returns {String} err.message A detailed error message
  23716. * @returns {Number} newTime The exact time that was seeked to in seconds
  23717. */
  23718. var seekToProgramTime = function seekToProgramTime(_ref2) {
  23719. var programTime = _ref2.programTime,
  23720. playlist = _ref2.playlist,
  23721. _ref2$retryCount = _ref2.retryCount,
  23722. retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount,
  23723. seekTo = _ref2.seekTo,
  23724. _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
  23725. pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek,
  23726. tech = _ref2.tech,
  23727. callback = _ref2.callback;
  23728. if (!callback) {
  23729. throw new Error('seekToProgramTime: callback must be provided');
  23730. }
  23731. if (typeof programTime === 'undefined' || !playlist || !seekTo) {
  23732. return callback({
  23733. message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
  23734. });
  23735. }
  23736. if (!playlist.endList && !tech.hasStarted_) {
  23737. return callback({
  23738. message: 'player must be playing a live stream to start buffering'
  23739. });
  23740. }
  23741. if (!verifyProgramDateTimeTags(playlist)) {
  23742. return callback({
  23743. message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
  23744. });
  23745. }
  23746. var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
  23747. if (!matchedSegment) {
  23748. return callback({
  23749. message: programTime + ' was not found in the stream'
  23750. });
  23751. }
  23752. var segment = matchedSegment.segment;
  23753. var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
  23754. if (matchedSegment.type === 'estimate') {
  23755. // we've run out of retries
  23756. if (retryCount === 0) {
  23757. return callback({
  23758. message: programTime + ' is not buffered yet. Try again'
  23759. });
  23760. }
  23761. seekTo(matchedSegment.estimatedStart + mediaOffset);
  23762. tech.one('seeked', function () {
  23763. seekToProgramTime({
  23764. programTime: programTime,
  23765. playlist: playlist,
  23766. retryCount: retryCount - 1,
  23767. seekTo: seekTo,
  23768. pauseAfterSeek: pauseAfterSeek,
  23769. tech: tech,
  23770. callback: callback
  23771. });
  23772. });
  23773. return;
  23774. } // Since the segment.start value is determined from the buffered end or ending time
  23775. // of the prior segment, the seekToTime doesn't need to account for any transmuxer
  23776. // modifications.
  23777. var seekToTime = segment.start + mediaOffset;
  23778. var seekedCallback = function seekedCallback() {
  23779. return callback(null, tech.currentTime());
  23780. }; // listen for seeked event
  23781. tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
  23782. if (pauseAfterSeek) {
  23783. tech.pause();
  23784. }
  23785. seekTo(seekToTime);
  23786. };
  23787. /**
  23788. * ranges
  23789. *
  23790. * Utilities for working with TimeRanges.
  23791. *
  23792. */
  23793. // Fudge factor to account for TimeRanges rounding
  23794. var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
  23795. // can be misleading because of precision differences or when the current media has poorly
  23796. // aligned audio and video, which can cause values to be slightly off from what you would
  23797. // expect. This value is what we consider to be safe to use in such comparisons to account
  23798. // for these scenarios.
  23799. var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
  23800. var filterRanges = function filterRanges(timeRanges, predicate) {
  23801. var results = [];
  23802. var i = void 0;
  23803. if (timeRanges && timeRanges.length) {
  23804. // Search for ranges that match the predicate
  23805. for (i = 0; i < timeRanges.length; i++) {
  23806. if (predicate(timeRanges.start(i), timeRanges.end(i))) {
  23807. results.push([timeRanges.start(i), timeRanges.end(i)]);
  23808. }
  23809. }
  23810. }
  23811. return videojs$1.createTimeRanges(results);
  23812. };
  23813. /**
  23814. * Attempts to find the buffered TimeRange that contains the specified
  23815. * time.
  23816. * @param {TimeRanges} buffered - the TimeRanges object to query
  23817. * @param {number} time - the time to filter on.
  23818. * @returns {TimeRanges} a new TimeRanges object
  23819. */
  23820. var findRange = function findRange(buffered, time) {
  23821. return filterRanges(buffered, function (start, end) {
  23822. return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
  23823. });
  23824. };
  23825. /**
  23826. * Returns the TimeRanges that begin later than the specified time.
  23827. * @param {TimeRanges} timeRanges - the TimeRanges object to query
  23828. * @param {number} time - the time to filter on.
  23829. * @returns {TimeRanges} a new TimeRanges object.
  23830. */
  23831. var findNextRange = function findNextRange(timeRanges, time) {
  23832. return filterRanges(timeRanges, function (start) {
  23833. return start - TIME_FUDGE_FACTOR >= time;
  23834. });
  23835. };
  23836. /**
  23837. * Returns gaps within a list of TimeRanges
  23838. * @param {TimeRanges} buffered - the TimeRanges object
  23839. * @return {TimeRanges} a TimeRanges object of gaps
  23840. */
  23841. var findGaps = function findGaps(buffered) {
  23842. if (buffered.length < 2) {
  23843. return videojs$1.createTimeRanges();
  23844. }
  23845. var ranges = [];
  23846. for (var i = 1; i < buffered.length; i++) {
  23847. var start = buffered.end(i - 1);
  23848. var end = buffered.start(i);
  23849. ranges.push([start, end]);
  23850. }
  23851. return videojs$1.createTimeRanges(ranges);
  23852. };
  23853. /**
  23854. * Gets a human readable string for a TimeRange
  23855. *
  23856. * @param {TimeRange} range
  23857. * @returns {String} a human readable string
  23858. */
  23859. var printableRange = function printableRange(range) {
  23860. var strArr = [];
  23861. if (!range || !range.length) {
  23862. return '';
  23863. }
  23864. for (var i = 0; i < range.length; i++) {
  23865. strArr.push(range.start(i) + ' => ' + range.end(i));
  23866. }
  23867. return strArr.join(', ');
  23868. };
  23869. /**
  23870. * Calculates the amount of time left in seconds until the player hits the end of the
  23871. * buffer and causes a rebuffer
  23872. *
  23873. * @param {TimeRange} buffered
  23874. * The state of the buffer
  23875. * @param {Numnber} currentTime
  23876. * The current time of the player
  23877. * @param {Number} playbackRate
  23878. * The current playback rate of the player. Defaults to 1.
  23879. * @return {Number}
  23880. * Time until the player has to start rebuffering in seconds.
  23881. * @function timeUntilRebuffer
  23882. */
  23883. var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
  23884. var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
  23885. var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
  23886. return (bufferedEnd - currentTime) / playbackRate;
  23887. };
  23888. /**
  23889. * Converts a TimeRanges object into an array representation
  23890. * @param {TimeRanges} timeRanges
  23891. * @returns {Array}
  23892. */
  23893. var timeRangesToArray = function timeRangesToArray(timeRanges) {
  23894. var timeRangesList = [];
  23895. for (var i = 0; i < timeRanges.length; i++) {
  23896. timeRangesList.push({
  23897. start: timeRanges.start(i),
  23898. end: timeRanges.end(i)
  23899. });
  23900. }
  23901. return timeRangesList;
  23902. };
  23903. /**
  23904. * @file create-text-tracks-if-necessary.js
  23905. */
  23906. /**
  23907. * Create text tracks on video.js if they exist on a segment.
  23908. *
  23909. * @param {Object} sourceBuffer the VSB or FSB
  23910. * @param {Object} mediaSource the HTML media source
  23911. * @param {Object} segment the segment that may contain the text track
  23912. * @private
  23913. */
  23914. var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
  23915. var player = mediaSource.player_; // create an in-band caption track if one is present in the segment
  23916. if (segment.captions && segment.captions.length) {
  23917. if (!sourceBuffer.inbandTextTracks_) {
  23918. sourceBuffer.inbandTextTracks_ = {};
  23919. }
  23920. for (var trackId in segment.captionStreams) {
  23921. if (!sourceBuffer.inbandTextTracks_[trackId]) {
  23922. player.tech_.trigger({
  23923. type: 'usage',
  23924. name: 'hls-608'
  23925. });
  23926. var track = player.textTracks().getTrackById(trackId);
  23927. if (track) {
  23928. // Resuse an existing track with a CC# id because this was
  23929. // very likely created by videojs-contrib-hls from information
  23930. // in the m3u8 for us to use
  23931. sourceBuffer.inbandTextTracks_[trackId] = track;
  23932. } else {
  23933. // Otherwise, create a track with the default `CC#` label and
  23934. // without a language
  23935. sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({
  23936. kind: 'captions',
  23937. id: trackId,
  23938. label: trackId
  23939. }, false).track;
  23940. }
  23941. }
  23942. }
  23943. }
  23944. if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
  23945. sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({
  23946. kind: 'metadata',
  23947. label: 'Timed Metadata'
  23948. }, false).track;
  23949. sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
  23950. }
  23951. };
  23952. /**
  23953. * @file remove-cues-from-track.js
  23954. */
  23955. /**
  23956. * Remove cues from a track on video.js.
  23957. *
  23958. * @param {Double} start start of where we should remove the cue
  23959. * @param {Double} end end of where the we should remove the cue
  23960. * @param {Object} track the text track to remove the cues from
  23961. * @private
  23962. */
  23963. var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
  23964. var i = void 0;
  23965. var cue = void 0;
  23966. if (!track) {
  23967. return;
  23968. }
  23969. if (!track.cues) {
  23970. return;
  23971. }
  23972. i = track.cues.length;
  23973. while (i--) {
  23974. cue = track.cues[i]; // Remove any overlapping cue
  23975. if (cue.startTime <= end && cue.endTime >= start) {
  23976. track.removeCue(cue);
  23977. }
  23978. }
  23979. };
  23980. /**
  23981. * @file add-text-track-data.js
  23982. */
  23983. /**
  23984. * Define properties on a cue for backwards compatability,
  23985. * but warn the user that the way that they are using it
  23986. * is depricated and will be removed at a later date.
  23987. *
  23988. * @param {Cue} cue the cue to add the properties on
  23989. * @private
  23990. */
  23991. var deprecateOldCue = function deprecateOldCue(cue) {
  23992. Object.defineProperties(cue.frame, {
  23993. id: {
  23994. get: function get() {
  23995. videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
  23996. return cue.value.key;
  23997. }
  23998. },
  23999. value: {
  24000. get: function get() {
  24001. videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
  24002. return cue.value.data;
  24003. }
  24004. },
  24005. privateData: {
  24006. get: function get() {
  24007. videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
  24008. return cue.value.data;
  24009. }
  24010. }
  24011. });
  24012. };
  24013. var durationOfVideo = function durationOfVideo(duration) {
  24014. var dur = void 0;
  24015. if (isNaN(duration) || Math.abs(duration) === Infinity) {
  24016. dur = Number.MAX_VALUE;
  24017. } else {
  24018. dur = duration;
  24019. }
  24020. return dur;
  24021. };
  24022. /**
  24023. * Add text track data to a source handler given the captions and
  24024. * metadata from the buffer.
  24025. *
  24026. * @param {Object} sourceHandler the virtual source buffer
  24027. * @param {Array} captionArray an array of caption data
  24028. * @param {Array} metadataArray an array of meta data
  24029. * @private
  24030. */
  24031. var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
  24032. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  24033. if (captionArray) {
  24034. captionArray.forEach(function (caption) {
  24035. var track = caption.stream;
  24036. this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
  24037. }, sourceHandler);
  24038. }
  24039. if (metadataArray) {
  24040. var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration);
  24041. metadataArray.forEach(function (metadata) {
  24042. var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
  24043. // ignore this bit of metadata.
  24044. // This likely occurs when you have an non-timed ID3 tag like TIT2,
  24045. // which is the "Title/Songname/Content description" frame
  24046. if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) {
  24047. return;
  24048. }
  24049. metadata.frames.forEach(function (frame) {
  24050. var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
  24051. cue.frame = frame;
  24052. cue.value = frame;
  24053. deprecateOldCue(cue);
  24054. this.metadataTrack_.addCue(cue);
  24055. }, this);
  24056. }, sourceHandler); // Updating the metadeta cues so that
  24057. // the endTime of each cue is the startTime of the next cue
  24058. // the endTime of last cue is the duration of the video
  24059. if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) {
  24060. var cues = sourceHandler.metadataTrack_.cues;
  24061. var cuesArray = []; // Create a copy of the TextTrackCueList...
  24062. // ...disregarding cues with a falsey value
  24063. for (var i = 0; i < cues.length; i++) {
  24064. if (cues[i]) {
  24065. cuesArray.push(cues[i]);
  24066. }
  24067. } // Group cues by their startTime value
  24068. var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
  24069. var timeSlot = obj[cue.startTime] || [];
  24070. timeSlot.push(cue);
  24071. obj[cue.startTime] = timeSlot;
  24072. return obj;
  24073. }, {}); // Sort startTimes by ascending order
  24074. var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
  24075. return Number(a) - Number(b);
  24076. }); // Map each cue group's endTime to the next group's startTime
  24077. sortedStartTimes.forEach(function (startTime, idx) {
  24078. var cueGroup = cuesGroupedByStartTime[startTime];
  24079. var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
  24080. cueGroup.forEach(function (cue) {
  24081. cue.endTime = nextTime;
  24082. });
  24083. });
  24084. }
  24085. }
  24086. };
  24087. var win = typeof window !== 'undefined' ? window : {},
  24088. TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
  24089. SCRIPT_TYPE = 'application/javascript',
  24090. BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder,
  24091. URL = win.URL || win.webkitURL || URL && URL.msURL,
  24092. Worker = win.Worker;
  24093. /**
  24094. * Returns a wrapper around Web Worker code that is constructible.
  24095. *
  24096. * @function shimWorker
  24097. *
  24098. * @param { String } filename The name of the file
  24099. * @param { Function } fn Function wrapping the code of the worker
  24100. */
  24101. function shimWorker(filename, fn) {
  24102. return function ShimWorker(forceFallback) {
  24103. var o = this;
  24104. if (!fn) {
  24105. return new Worker(filename);
  24106. } else if (Worker && !forceFallback) {
  24107. // Convert the function's inner code to a string to construct the worker
  24108. var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
  24109. objURL = createSourceObject(source);
  24110. this[TARGET] = new Worker(objURL);
  24111. wrapTerminate(this[TARGET], objURL);
  24112. return this[TARGET];
  24113. } else {
  24114. var selfShim = {
  24115. postMessage: function postMessage(m) {
  24116. if (o.onmessage) {
  24117. setTimeout(function () {
  24118. o.onmessage({
  24119. data: m,
  24120. target: selfShim
  24121. });
  24122. });
  24123. }
  24124. }
  24125. };
  24126. fn.call(selfShim);
  24127. this.postMessage = function (m) {
  24128. setTimeout(function () {
  24129. selfShim.onmessage({
  24130. data: m,
  24131. target: o
  24132. });
  24133. });
  24134. };
  24135. this.isThisThread = true;
  24136. }
  24137. };
  24138. } // Test Worker capabilities
  24139. if (Worker) {
  24140. var testWorker,
  24141. objURL = createSourceObject('self.onmessage = function () {}'),
  24142. testArray = new Uint8Array(1);
  24143. try {
  24144. testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it
  24145. testWorker.postMessage(testArray, [testArray.buffer]);
  24146. } catch (e) {
  24147. Worker = null;
  24148. } finally {
  24149. URL.revokeObjectURL(objURL);
  24150. if (testWorker) {
  24151. testWorker.terminate();
  24152. }
  24153. }
  24154. }
  24155. function createSourceObject(str) {
  24156. try {
  24157. return URL.createObjectURL(new Blob([str], {
  24158. type: SCRIPT_TYPE
  24159. }));
  24160. } catch (e) {
  24161. var blob = new BlobBuilder();
  24162. blob.append(str);
  24163. return URL.createObjectURL(blob.getBlob(type));
  24164. }
  24165. }
  24166. function wrapTerminate(worker, objURL) {
  24167. if (!worker || !objURL) return;
  24168. var term = worker.terminate;
  24169. worker.objURL = objURL;
  24170. worker.terminate = function () {
  24171. if (worker.objURL) URL.revokeObjectURL(worker.objURL);
  24172. term.call(worker);
  24173. };
  24174. }
  24175. var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) {
  24176. var self = this;
  24177. var transmuxerWorker = function () {
  24178. /**
  24179. * mux.js
  24180. *
  24181. * Copyright (c) 2015 Brightcove
  24182. * All rights reserved.
  24183. *
  24184. * Functions that generate fragmented MP4s suitable for use with Media
  24185. * Source Extensions.
  24186. */
  24187. var UINT32_MAX = Math.pow(2, 32) - 1;
  24188. var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
  24189. (function () {
  24190. var i;
  24191. types = {
  24192. avc1: [],
  24193. // codingname
  24194. avcC: [],
  24195. btrt: [],
  24196. dinf: [],
  24197. dref: [],
  24198. esds: [],
  24199. ftyp: [],
  24200. hdlr: [],
  24201. mdat: [],
  24202. mdhd: [],
  24203. mdia: [],
  24204. mfhd: [],
  24205. minf: [],
  24206. moof: [],
  24207. moov: [],
  24208. mp4a: [],
  24209. // codingname
  24210. mvex: [],
  24211. mvhd: [],
  24212. sdtp: [],
  24213. smhd: [],
  24214. stbl: [],
  24215. stco: [],
  24216. stsc: [],
  24217. stsd: [],
  24218. stsz: [],
  24219. stts: [],
  24220. styp: [],
  24221. tfdt: [],
  24222. tfhd: [],
  24223. traf: [],
  24224. trak: [],
  24225. trun: [],
  24226. trex: [],
  24227. tkhd: [],
  24228. vmhd: []
  24229. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  24230. // don't throw an error
  24231. if (typeof Uint8Array === 'undefined') {
  24232. return;
  24233. }
  24234. for (i in types) {
  24235. if (types.hasOwnProperty(i)) {
  24236. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  24237. }
  24238. }
  24239. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  24240. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  24241. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  24242. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  24243. 0x00, 0x00, 0x00, // flags
  24244. 0x00, 0x00, 0x00, 0x00, // pre_defined
  24245. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  24246. 0x00, 0x00, 0x00, 0x00, // reserved
  24247. 0x00, 0x00, 0x00, 0x00, // reserved
  24248. 0x00, 0x00, 0x00, 0x00, // reserved
  24249. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  24250. ]);
  24251. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  24252. 0x00, 0x00, 0x00, // flags
  24253. 0x00, 0x00, 0x00, 0x00, // pre_defined
  24254. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  24255. 0x00, 0x00, 0x00, 0x00, // reserved
  24256. 0x00, 0x00, 0x00, 0x00, // reserved
  24257. 0x00, 0x00, 0x00, 0x00, // reserved
  24258. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  24259. ]);
  24260. HDLR_TYPES = {
  24261. video: VIDEO_HDLR,
  24262. audio: AUDIO_HDLR
  24263. };
  24264. DREF = new Uint8Array([0x00, // version 0
  24265. 0x00, 0x00, 0x00, // flags
  24266. 0x00, 0x00, 0x00, 0x01, // entry_count
  24267. 0x00, 0x00, 0x00, 0x0c, // entry_size
  24268. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  24269. 0x00, // version 0
  24270. 0x00, 0x00, 0x01 // entry_flags
  24271. ]);
  24272. SMHD = new Uint8Array([0x00, // version
  24273. 0x00, 0x00, 0x00, // flags
  24274. 0x00, 0x00, // balance, 0 means centered
  24275. 0x00, 0x00 // reserved
  24276. ]);
  24277. STCO = new Uint8Array([0x00, // version
  24278. 0x00, 0x00, 0x00, // flags
  24279. 0x00, 0x00, 0x00, 0x00 // entry_count
  24280. ]);
  24281. STSC = STCO;
  24282. STSZ = new Uint8Array([0x00, // version
  24283. 0x00, 0x00, 0x00, // flags
  24284. 0x00, 0x00, 0x00, 0x00, // sample_size
  24285. 0x00, 0x00, 0x00, 0x00 // sample_count
  24286. ]);
  24287. STTS = STCO;
  24288. VMHD = new Uint8Array([0x00, // version
  24289. 0x00, 0x00, 0x01, // flags
  24290. 0x00, 0x00, // graphicsmode
  24291. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  24292. ]);
  24293. })();
  24294. box = function box(type) {
  24295. var payload = [],
  24296. size = 0,
  24297. i,
  24298. result,
  24299. view;
  24300. for (i = 1; i < arguments.length; i++) {
  24301. payload.push(arguments[i]);
  24302. }
  24303. i = payload.length; // calculate the total size we need to allocate
  24304. while (i--) {
  24305. size += payload[i].byteLength;
  24306. }
  24307. result = new Uint8Array(size + 8);
  24308. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  24309. view.setUint32(0, result.byteLength);
  24310. result.set(type, 4); // copy the payload into the result
  24311. for (i = 0, size = 8; i < payload.length; i++) {
  24312. result.set(payload[i], size);
  24313. size += payload[i].byteLength;
  24314. }
  24315. return result;
  24316. };
  24317. dinf = function dinf() {
  24318. return box(types.dinf, box(types.dref, DREF));
  24319. };
  24320. esds = function esds(track) {
  24321. return box(types.esds, new Uint8Array([0x00, // version
  24322. 0x00, 0x00, 0x00, // flags
  24323. // ES_Descriptor
  24324. 0x03, // tag, ES_DescrTag
  24325. 0x19, // length
  24326. 0x00, 0x00, // ES_ID
  24327. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  24328. // DecoderConfigDescriptor
  24329. 0x04, // tag, DecoderConfigDescrTag
  24330. 0x11, // length
  24331. 0x40, // object type
  24332. 0x15, // streamType
  24333. 0x00, 0x06, 0x00, // bufferSizeDB
  24334. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  24335. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  24336. // DecoderSpecificInfo
  24337. 0x05, // tag, DecoderSpecificInfoTag
  24338. 0x02, // length
  24339. // ISO/IEC 14496-3, AudioSpecificConfig
  24340. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  24341. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  24342. ]));
  24343. };
  24344. ftyp = function ftyp() {
  24345. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  24346. };
  24347. hdlr = function hdlr(type) {
  24348. return box(types.hdlr, HDLR_TYPES[type]);
  24349. };
  24350. mdat = function mdat(data) {
  24351. return box(types.mdat, data);
  24352. };
  24353. mdhd = function mdhd(track) {
  24354. var result = new Uint8Array([0x00, // version 0
  24355. 0x00, 0x00, 0x00, // flags
  24356. 0x00, 0x00, 0x00, 0x02, // creation_time
  24357. 0x00, 0x00, 0x00, 0x03, // modification_time
  24358. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  24359. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  24360. 0x55, 0xc4, // 'und' language (undetermined)
  24361. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  24362. // defined. The sample rate can be parsed out of an ADTS header, for
  24363. // instance.
  24364. if (track.samplerate) {
  24365. result[12] = track.samplerate >>> 24 & 0xFF;
  24366. result[13] = track.samplerate >>> 16 & 0xFF;
  24367. result[14] = track.samplerate >>> 8 & 0xFF;
  24368. result[15] = track.samplerate & 0xFF;
  24369. }
  24370. return box(types.mdhd, result);
  24371. };
  24372. mdia = function mdia(track) {
  24373. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  24374. };
  24375. mfhd = function mfhd(sequenceNumber) {
  24376. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  24377. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  24378. ]));
  24379. };
  24380. minf = function minf(track) {
  24381. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  24382. };
  24383. moof = function moof(sequenceNumber, tracks) {
  24384. var trackFragments = [],
  24385. i = tracks.length; // build traf boxes for each track fragment
  24386. while (i--) {
  24387. trackFragments[i] = traf(tracks[i]);
  24388. }
  24389. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  24390. };
  24391. /**
  24392. * Returns a movie box.
  24393. * @param tracks {array} the tracks associated with this movie
  24394. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  24395. */
  24396. moov = function moov(tracks) {
  24397. var i = tracks.length,
  24398. boxes = [];
  24399. while (i--) {
  24400. boxes[i] = trak(tracks[i]);
  24401. }
  24402. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  24403. };
  24404. mvex = function mvex(tracks) {
  24405. var i = tracks.length,
  24406. boxes = [];
  24407. while (i--) {
  24408. boxes[i] = trex(tracks[i]);
  24409. }
  24410. return box.apply(null, [types.mvex].concat(boxes));
  24411. };
  24412. mvhd = function mvhd(duration) {
  24413. var bytes = new Uint8Array([0x00, // version 0
  24414. 0x00, 0x00, 0x00, // flags
  24415. 0x00, 0x00, 0x00, 0x01, // creation_time
  24416. 0x00, 0x00, 0x00, 0x02, // modification_time
  24417. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  24418. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  24419. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  24420. 0x01, 0x00, // 1.0 volume
  24421. 0x00, 0x00, // reserved
  24422. 0x00, 0x00, 0x00, 0x00, // reserved
  24423. 0x00, 0x00, 0x00, 0x00, // reserved
  24424. 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  24425. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  24426. 0xff, 0xff, 0xff, 0xff // next_track_ID
  24427. ]);
  24428. return box(types.mvhd, bytes);
  24429. };
  24430. sdtp = function sdtp(track) {
  24431. var samples = track.samples || [],
  24432. bytes = new Uint8Array(4 + samples.length),
  24433. flags,
  24434. i; // leave the full box header (4 bytes) all zero
  24435. // write the sample table
  24436. for (i = 0; i < samples.length; i++) {
  24437. flags = samples[i].flags;
  24438. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  24439. }
  24440. return box(types.sdtp, bytes);
  24441. };
  24442. stbl = function stbl(track) {
  24443. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  24444. };
  24445. (function () {
  24446. var videoSample, audioSample;
  24447. stsd = function stsd(track) {
  24448. return box(types.stsd, new Uint8Array([0x00, // version 0
  24449. 0x00, 0x00, 0x00, // flags
  24450. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  24451. };
  24452. videoSample = function videoSample(track) {
  24453. var sps = track.sps || [],
  24454. pps = track.pps || [],
  24455. sequenceParameterSets = [],
  24456. pictureParameterSets = [],
  24457. i; // assemble the SPSs
  24458. for (i = 0; i < sps.length; i++) {
  24459. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  24460. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  24461. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  24462. } // assemble the PPSs
  24463. for (i = 0; i < pps.length; i++) {
  24464. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  24465. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  24466. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  24467. }
  24468. return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  24469. 0x00, 0x01, // data_reference_index
  24470. 0x00, 0x00, // pre_defined
  24471. 0x00, 0x00, // reserved
  24472. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  24473. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  24474. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  24475. 0x00, 0x48, 0x00, 0x00, // horizresolution
  24476. 0x00, 0x48, 0x00, 0x00, // vertresolution
  24477. 0x00, 0x00, 0x00, 0x00, // reserved
  24478. 0x00, 0x01, // frame_count
  24479. 0x13, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6a, 0x73, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2d, 0x68, 0x6c, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compressorname
  24480. 0x00, 0x18, // depth = 24
  24481. 0x11, 0x11 // pre_defined = -1
  24482. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  24483. track.profileIdc, // AVCProfileIndication
  24484. track.profileCompatibility, // profile_compatibility
  24485. track.levelIdc, // AVCLevelIndication
  24486. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  24487. ].concat([sps.length // numOfSequenceParameterSets
  24488. ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
  24489. ]).concat(pictureParameterSets))), // "PPS"
  24490. box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  24491. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  24492. 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
  24493. );
  24494. };
  24495. audioSample = function audioSample(track) {
  24496. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  24497. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  24498. 0x00, 0x01, // data_reference_index
  24499. // AudioSampleEntry, ISO/IEC 14496-12
  24500. 0x00, 0x00, 0x00, 0x00, // reserved
  24501. 0x00, 0x00, 0x00, 0x00, // reserved
  24502. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  24503. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  24504. 0x00, 0x00, // pre_defined
  24505. 0x00, 0x00, // reserved
  24506. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  24507. // MP4AudioSampleEntry, ISO/IEC 14496-14
  24508. ]), esds(track));
  24509. };
  24510. })();
  24511. tkhd = function tkhd(track) {
  24512. var result = new Uint8Array([0x00, // version 0
  24513. 0x00, 0x00, 0x07, // flags
  24514. 0x00, 0x00, 0x00, 0x00, // creation_time
  24515. 0x00, 0x00, 0x00, 0x00, // modification_time
  24516. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  24517. 0x00, 0x00, 0x00, 0x00, // reserved
  24518. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  24519. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  24520. 0x00, 0x00, // layer
  24521. 0x00, 0x00, // alternate_group
  24522. 0x01, 0x00, // non-audio track volume
  24523. 0x00, 0x00, // reserved
  24524. 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  24525. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  24526. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  24527. ]);
  24528. return box(types.tkhd, result);
  24529. };
  24530. /**
  24531. * Generate a track fragment (traf) box. A traf box collects metadata
  24532. * about tracks in a movie fragment (moof) box.
  24533. */
  24534. traf = function traf(track) {
  24535. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  24536. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  24537. 0x00, 0x00, 0x3a, // flags
  24538. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  24539. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  24540. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  24541. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  24542. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  24543. ]));
  24544. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
  24545. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
  24546. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  24547. 0x00, 0x00, 0x00, // flags
  24548. // baseMediaDecodeTime
  24549. upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF])); // the data offset specifies the number of bytes from the start of
  24550. // the containing moof to the first payload byte of the associated
  24551. // mdat
  24552. dataOffset = 32 + // tfhd
  24553. 20 + // tfdt
  24554. 8 + // traf header
  24555. 16 + // mfhd
  24556. 8 + // moof header
  24557. 8; // mdat header
  24558. // audio tracks require less metadata
  24559. if (track.type === 'audio') {
  24560. trackFragmentRun = trun(track, dataOffset);
  24561. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  24562. } // video tracks should contain an independent and disposable samples
  24563. // box (sdtp)
  24564. // generate one and adjust offsets to match
  24565. sampleDependencyTable = sdtp(track);
  24566. trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
  24567. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  24568. };
  24569. /**
  24570. * Generate a track box.
  24571. * @param track {object} a track definition
  24572. * @return {Uint8Array} the track box
  24573. */
  24574. trak = function trak(track) {
  24575. track.duration = track.duration || 0xffffffff;
  24576. return box(types.trak, tkhd(track), mdia(track));
  24577. };
  24578. trex = function trex(track) {
  24579. var result = new Uint8Array([0x00, // version 0
  24580. 0x00, 0x00, 0x00, // flags
  24581. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  24582. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  24583. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  24584. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  24585. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  24586. ]); // the last two bytes of default_sample_flags is the sample
  24587. // degradation priority, a hint about the importance of this sample
  24588. // relative to others. Lower the degradation priority for all sample
  24589. // types other than video.
  24590. if (track.type !== 'video') {
  24591. result[result.length - 1] = 0x00;
  24592. }
  24593. return box(types.trex, result);
  24594. };
  24595. (function () {
  24596. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  24597. // duration is present for the first sample, it will be present for
  24598. // all subsequent samples.
  24599. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  24600. trunHeader = function trunHeader(samples, offset) {
  24601. var durationPresent = 0,
  24602. sizePresent = 0,
  24603. flagsPresent = 0,
  24604. compositionTimeOffset = 0; // trun flag constants
  24605. if (samples.length) {
  24606. if (samples[0].duration !== undefined) {
  24607. durationPresent = 0x1;
  24608. }
  24609. if (samples[0].size !== undefined) {
  24610. sizePresent = 0x2;
  24611. }
  24612. if (samples[0].flags !== undefined) {
  24613. flagsPresent = 0x4;
  24614. }
  24615. if (samples[0].compositionTimeOffset !== undefined) {
  24616. compositionTimeOffset = 0x8;
  24617. }
  24618. }
  24619. return [0x00, // version 0
  24620. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  24621. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  24622. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  24623. ];
  24624. };
  24625. videoTrun = function videoTrun(track, offset) {
  24626. var bytes, samples, sample, i;
  24627. samples = track.samples || [];
  24628. offset += 8 + 12 + 16 * samples.length;
  24629. bytes = trunHeader(samples, offset);
  24630. for (i = 0; i < samples.length; i++) {
  24631. sample = samples[i];
  24632. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  24633. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
  24634. sample.flags.isLeading << 2 | sample.flags.dependsOn, sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample, sample.flags.degradationPriority & 0xF0 << 8, sample.flags.degradationPriority & 0x0F, // sample_flags
  24635. (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
  24636. ]);
  24637. }
  24638. return box(types.trun, new Uint8Array(bytes));
  24639. };
  24640. audioTrun = function audioTrun(track, offset) {
  24641. var bytes, samples, sample, i;
  24642. samples = track.samples || [];
  24643. offset += 8 + 12 + 8 * samples.length;
  24644. bytes = trunHeader(samples, offset);
  24645. for (i = 0; i < samples.length; i++) {
  24646. sample = samples[i];
  24647. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  24648. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
  24649. }
  24650. return box(types.trun, new Uint8Array(bytes));
  24651. };
  24652. trun = function trun(track, offset) {
  24653. if (track.type === 'audio') {
  24654. return audioTrun(track, offset);
  24655. }
  24656. return videoTrun(track, offset);
  24657. };
  24658. })();
  24659. var mp4Generator = {
  24660. ftyp: ftyp,
  24661. mdat: mdat,
  24662. moof: moof,
  24663. moov: moov,
  24664. initSegment: function initSegment(tracks) {
  24665. var fileType = ftyp(),
  24666. movie = moov(tracks),
  24667. result;
  24668. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  24669. result.set(fileType);
  24670. result.set(movie, fileType.byteLength);
  24671. return result;
  24672. }
  24673. };
  24674. var toUnsigned = function toUnsigned(value) {
  24675. return value >>> 0;
  24676. };
  24677. var bin = {
  24678. toUnsigned: toUnsigned
  24679. };
  24680. var toUnsigned$1 = bin.toUnsigned;
  24681. var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
  24682. _findBox = function findBox(data, path) {
  24683. var results = [],
  24684. i,
  24685. size,
  24686. type,
  24687. end,
  24688. subresults;
  24689. if (!path.length) {
  24690. // short-circuit the search for empty paths
  24691. return null;
  24692. }
  24693. for (i = 0; i < data.byteLength;) {
  24694. size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
  24695. type = parseType(data.subarray(i + 4, i + 8));
  24696. end = size > 1 ? i + size : data.byteLength;
  24697. if (type === path[0]) {
  24698. if (path.length === 1) {
  24699. // this is the end of the path and we've found the box we were
  24700. // looking for
  24701. results.push(data.subarray(i + 8, end));
  24702. } else {
  24703. // recursively search for the next box along the path
  24704. subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
  24705. if (subresults.length) {
  24706. results = results.concat(subresults);
  24707. }
  24708. }
  24709. }
  24710. i = end;
  24711. } // we've finished searching all of data
  24712. return results;
  24713. };
  24714. /**
  24715. * Returns the string representation of an ASCII encoded four byte buffer.
  24716. * @param buffer {Uint8Array} a four-byte buffer to translate
  24717. * @return {string} the corresponding string
  24718. */
  24719. parseType = function parseType(buffer) {
  24720. var result = '';
  24721. result += String.fromCharCode(buffer[0]);
  24722. result += String.fromCharCode(buffer[1]);
  24723. result += String.fromCharCode(buffer[2]);
  24724. result += String.fromCharCode(buffer[3]);
  24725. return result;
  24726. };
  24727. /**
  24728. * Parses an MP4 initialization segment and extracts the timescale
  24729. * values for any declared tracks. Timescale values indicate the
  24730. * number of clock ticks per second to assume for time-based values
  24731. * elsewhere in the MP4.
  24732. *
  24733. * To determine the start time of an MP4, you need two pieces of
  24734. * information: the timescale unit and the earliest base media decode
  24735. * time. Multiple timescales can be specified within an MP4 but the
  24736. * base media decode time is always expressed in the timescale from
  24737. * the media header box for the track:
  24738. * ```
  24739. * moov > trak > mdia > mdhd.timescale
  24740. * ```
  24741. * @param init {Uint8Array} the bytes of the init segment
  24742. * @return {object} a hash of track ids to timescale values or null if
  24743. * the init segment is malformed.
  24744. */
  24745. timescale = function timescale(init) {
  24746. var result = {},
  24747. traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
  24748. return traks.reduce(function (result, trak) {
  24749. var tkhd, version, index, id, mdhd;
  24750. tkhd = _findBox(trak, ['tkhd'])[0];
  24751. if (!tkhd) {
  24752. return null;
  24753. }
  24754. version = tkhd[0];
  24755. index = version === 0 ? 12 : 20;
  24756. id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
  24757. mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
  24758. if (!mdhd) {
  24759. return null;
  24760. }
  24761. version = mdhd[0];
  24762. index = version === 0 ? 12 : 20;
  24763. result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  24764. return result;
  24765. }, result);
  24766. };
  24767. /**
  24768. * Determine the base media decode start time, in seconds, for an MP4
  24769. * fragment. If multiple fragments are specified, the earliest time is
  24770. * returned.
  24771. *
  24772. * The base media decode time can be parsed from track fragment
  24773. * metadata:
  24774. * ```
  24775. * moof > traf > tfdt.baseMediaDecodeTime
  24776. * ```
  24777. * It requires the timescale value from the mdhd to interpret.
  24778. *
  24779. * @param timescale {object} a hash of track ids to timescale values.
  24780. * @return {number} the earliest base media decode start time for the
  24781. * fragment, in seconds
  24782. */
  24783. startTime = function startTime(timescale, fragment) {
  24784. var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
  24785. trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
  24786. baseTimes = [].concat.apply([], trafs.map(function (traf) {
  24787. return _findBox(traf, ['tfhd']).map(function (tfhd) {
  24788. var id, scale, baseTime; // get the track id from the tfhd
  24789. id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
  24790. scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
  24791. baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
  24792. var version, result;
  24793. version = tfdt[0];
  24794. result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
  24795. if (version === 1) {
  24796. result *= Math.pow(2, 32);
  24797. result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
  24798. }
  24799. return result;
  24800. })[0];
  24801. baseTime = baseTime || Infinity; // convert base time to seconds
  24802. return baseTime / scale;
  24803. });
  24804. })); // return the minimum
  24805. result = Math.min.apply(null, baseTimes);
  24806. return isFinite(result) ? result : 0;
  24807. };
  24808. /**
  24809. * Find the trackIds of the video tracks in this source.
  24810. * Found by parsing the Handler Reference and Track Header Boxes:
  24811. * moov > trak > mdia > hdlr
  24812. * moov > trak > tkhd
  24813. *
  24814. * @param {Uint8Array} init - The bytes of the init segment for this source
  24815. * @return {Number[]} A list of trackIds
  24816. *
  24817. * @see ISO-BMFF-12/2015, Section 8.4.3
  24818. **/
  24819. getVideoTrackIds = function getVideoTrackIds(init) {
  24820. var traks = _findBox(init, ['moov', 'trak']);
  24821. var videoTrackIds = [];
  24822. traks.forEach(function (trak) {
  24823. var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
  24824. var tkhds = _findBox(trak, ['tkhd']);
  24825. hdlrs.forEach(function (hdlr, index) {
  24826. var handlerType = parseType(hdlr.subarray(8, 12));
  24827. var tkhd = tkhds[index];
  24828. var view;
  24829. var version;
  24830. var trackId;
  24831. if (handlerType === 'vide') {
  24832. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  24833. version = view.getUint8(0);
  24834. trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
  24835. videoTrackIds.push(trackId);
  24836. }
  24837. });
  24838. });
  24839. return videoTrackIds;
  24840. };
  24841. var probe = {
  24842. findBox: _findBox,
  24843. parseType: parseType,
  24844. timescale: timescale,
  24845. startTime: startTime,
  24846. videoTrackIds: getVideoTrackIds
  24847. };
  24848. /**
  24849. * mux.js
  24850. *
  24851. * Copyright (c) 2014 Brightcove
  24852. * All rights reserved.
  24853. *
  24854. * A lightweight readable stream implemention that handles event dispatching.
  24855. * Objects that inherit from streams should call init in their constructors.
  24856. */
  24857. var Stream = function Stream() {
  24858. this.init = function () {
  24859. var listeners = {};
  24860. /**
  24861. * Add a listener for a specified event type.
  24862. * @param type {string} the event name
  24863. * @param listener {function} the callback to be invoked when an event of
  24864. * the specified type occurs
  24865. */
  24866. this.on = function (type, listener) {
  24867. if (!listeners[type]) {
  24868. listeners[type] = [];
  24869. }
  24870. listeners[type] = listeners[type].concat(listener);
  24871. };
  24872. /**
  24873. * Remove a listener for a specified event type.
  24874. * @param type {string} the event name
  24875. * @param listener {function} a function previously registered for this
  24876. * type of event through `on`
  24877. */
  24878. this.off = function (type, listener) {
  24879. var index;
  24880. if (!listeners[type]) {
  24881. return false;
  24882. }
  24883. index = listeners[type].indexOf(listener);
  24884. listeners[type] = listeners[type].slice();
  24885. listeners[type].splice(index, 1);
  24886. return index > -1;
  24887. };
  24888. /**
  24889. * Trigger an event of the specified type on this stream. Any additional
  24890. * arguments to this function are passed as parameters to event listeners.
  24891. * @param type {string} the event name
  24892. */
  24893. this.trigger = function (type) {
  24894. var callbacks, i, length, args;
  24895. callbacks = listeners[type];
  24896. if (!callbacks) {
  24897. return;
  24898. } // Slicing the arguments on every invocation of this method
  24899. // can add a significant amount of overhead. Avoid the
  24900. // intermediate object creation for the common case of a
  24901. // single callback argument
  24902. if (arguments.length === 2) {
  24903. length = callbacks.length;
  24904. for (i = 0; i < length; ++i) {
  24905. callbacks[i].call(this, arguments[1]);
  24906. }
  24907. } else {
  24908. args = [];
  24909. i = arguments.length;
  24910. for (i = 1; i < arguments.length; ++i) {
  24911. args.push(arguments[i]);
  24912. }
  24913. length = callbacks.length;
  24914. for (i = 0; i < length; ++i) {
  24915. callbacks[i].apply(this, args);
  24916. }
  24917. }
  24918. };
  24919. /**
  24920. * Destroys the stream and cleans up.
  24921. */
  24922. this.dispose = function () {
  24923. listeners = {};
  24924. };
  24925. };
  24926. };
  24927. /**
  24928. * Forwards all `data` events on this stream to the destination stream. The
  24929. * destination stream should provide a method `push` to receive the data
  24930. * events as they arrive.
  24931. * @param destination {stream} the stream that will receive all `data` events
  24932. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  24933. * when the current stream emits a 'done' event
  24934. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  24935. */
  24936. Stream.prototype.pipe = function (destination) {
  24937. this.on('data', function (data) {
  24938. destination.push(data);
  24939. });
  24940. this.on('done', function (flushSource) {
  24941. destination.flush(flushSource);
  24942. });
  24943. return destination;
  24944. }; // Default stream functions that are expected to be overridden to perform
  24945. // actual work. These are provided by the prototype as a sort of no-op
  24946. // implementation so that we don't have to check for their existence in the
  24947. // `pipe` function above.
  24948. Stream.prototype.push = function (data) {
  24949. this.trigger('data', data);
  24950. };
  24951. Stream.prototype.flush = function (flushSource) {
  24952. this.trigger('done', flushSource);
  24953. };
  24954. var stream = Stream; // Convert an array of nal units into an array of frames with each frame being
  24955. // composed of the nal units that make up that frame
  24956. // Also keep track of cummulative data about the frame from the nal units such
  24957. // as the frame duration, starting pts, etc.
  24958. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  24959. var i,
  24960. currentNal,
  24961. currentFrame = [],
  24962. frames = [];
  24963. currentFrame.byteLength = 0;
  24964. for (i = 0; i < nalUnits.length; i++) {
  24965. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  24966. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  24967. // Since the very first nal unit is expected to be an AUD
  24968. // only push to the frames array when currentFrame is not empty
  24969. if (currentFrame.length) {
  24970. currentFrame.duration = currentNal.dts - currentFrame.dts;
  24971. frames.push(currentFrame);
  24972. }
  24973. currentFrame = [currentNal];
  24974. currentFrame.byteLength = currentNal.data.byteLength;
  24975. currentFrame.pts = currentNal.pts;
  24976. currentFrame.dts = currentNal.dts;
  24977. } else {
  24978. // Specifically flag key frames for ease of use later
  24979. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  24980. currentFrame.keyFrame = true;
  24981. }
  24982. currentFrame.duration = currentNal.dts - currentFrame.dts;
  24983. currentFrame.byteLength += currentNal.data.byteLength;
  24984. currentFrame.push(currentNal);
  24985. }
  24986. } // For the last frame, use the duration of the previous frame if we
  24987. // have nothing better to go on
  24988. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  24989. currentFrame.duration = frames[frames.length - 1].duration;
  24990. } // Push the final frame
  24991. frames.push(currentFrame);
  24992. return frames;
  24993. }; // Convert an array of frames into an array of Gop with each Gop being composed
  24994. // of the frames that make up that Gop
  24995. // Also keep track of cummulative data about the Gop from the frames such as the
  24996. // Gop duration, starting pts, etc.
  24997. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  24998. var i,
  24999. currentFrame,
  25000. currentGop = [],
  25001. gops = []; // We must pre-set some of the values on the Gop since we
  25002. // keep running totals of these values
  25003. currentGop.byteLength = 0;
  25004. currentGop.nalCount = 0;
  25005. currentGop.duration = 0;
  25006. currentGop.pts = frames[0].pts;
  25007. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  25008. gops.byteLength = 0;
  25009. gops.nalCount = 0;
  25010. gops.duration = 0;
  25011. gops.pts = frames[0].pts;
  25012. gops.dts = frames[0].dts;
  25013. for (i = 0; i < frames.length; i++) {
  25014. currentFrame = frames[i];
  25015. if (currentFrame.keyFrame) {
  25016. // Since the very first frame is expected to be an keyframe
  25017. // only push to the gops array when currentGop is not empty
  25018. if (currentGop.length) {
  25019. gops.push(currentGop);
  25020. gops.byteLength += currentGop.byteLength;
  25021. gops.nalCount += currentGop.nalCount;
  25022. gops.duration += currentGop.duration;
  25023. }
  25024. currentGop = [currentFrame];
  25025. currentGop.nalCount = currentFrame.length;
  25026. currentGop.byteLength = currentFrame.byteLength;
  25027. currentGop.pts = currentFrame.pts;
  25028. currentGop.dts = currentFrame.dts;
  25029. currentGop.duration = currentFrame.duration;
  25030. } else {
  25031. currentGop.duration += currentFrame.duration;
  25032. currentGop.nalCount += currentFrame.length;
  25033. currentGop.byteLength += currentFrame.byteLength;
  25034. currentGop.push(currentFrame);
  25035. }
  25036. }
  25037. if (gops.length && currentGop.duration <= 0) {
  25038. currentGop.duration = gops[gops.length - 1].duration;
  25039. }
  25040. gops.byteLength += currentGop.byteLength;
  25041. gops.nalCount += currentGop.nalCount;
  25042. gops.duration += currentGop.duration; // push the final Gop
  25043. gops.push(currentGop);
  25044. return gops;
  25045. };
  25046. /*
  25047. * Search for the first keyframe in the GOPs and throw away all frames
  25048. * until that keyframe. Then extend the duration of the pulled keyframe
  25049. * and pull the PTS and DTS of the keyframe so that it covers the time
  25050. * range of the frames that were disposed.
  25051. *
  25052. * @param {Array} gops video GOPs
  25053. * @returns {Array} modified video GOPs
  25054. */
  25055. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  25056. var currentGop;
  25057. if (!gops[0][0].keyFrame && gops.length > 1) {
  25058. // Remove the first GOP
  25059. currentGop = gops.shift();
  25060. gops.byteLength -= currentGop.byteLength;
  25061. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  25062. // first gop to cover the time period of the
  25063. // frames we just removed
  25064. gops[0][0].dts = currentGop.dts;
  25065. gops[0][0].pts = currentGop.pts;
  25066. gops[0][0].duration += currentGop.duration;
  25067. }
  25068. return gops;
  25069. };
  25070. /**
  25071. * Default sample object
  25072. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  25073. */
  25074. var createDefaultSample = function createDefaultSample() {
  25075. return {
  25076. size: 0,
  25077. flags: {
  25078. isLeading: 0,
  25079. dependsOn: 1,
  25080. isDependedOn: 0,
  25081. hasRedundancy: 0,
  25082. degradationPriority: 0,
  25083. isNonSyncSample: 1
  25084. }
  25085. };
  25086. };
  25087. /*
  25088. * Collates information from a video frame into an object for eventual
  25089. * entry into an MP4 sample table.
  25090. *
  25091. * @param {Object} frame the video frame
  25092. * @param {Number} dataOffset the byte offset to position the sample
  25093. * @return {Object} object containing sample table info for a frame
  25094. */
  25095. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  25096. var sample = createDefaultSample();
  25097. sample.dataOffset = dataOffset;
  25098. sample.compositionTimeOffset = frame.pts - frame.dts;
  25099. sample.duration = frame.duration;
  25100. sample.size = 4 * frame.length; // Space for nal unit size
  25101. sample.size += frame.byteLength;
  25102. if (frame.keyFrame) {
  25103. sample.flags.dependsOn = 2;
  25104. sample.flags.isNonSyncSample = 0;
  25105. }
  25106. return sample;
  25107. }; // generate the track's sample table from an array of gops
  25108. var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
  25109. var h,
  25110. i,
  25111. sample,
  25112. currentGop,
  25113. currentFrame,
  25114. dataOffset = baseDataOffset || 0,
  25115. samples = [];
  25116. for (h = 0; h < gops.length; h++) {
  25117. currentGop = gops[h];
  25118. for (i = 0; i < currentGop.length; i++) {
  25119. currentFrame = currentGop[i];
  25120. sample = sampleForFrame(currentFrame, dataOffset);
  25121. dataOffset += sample.size;
  25122. samples.push(sample);
  25123. }
  25124. }
  25125. return samples;
  25126. }; // generate the track's raw mdat data from an array of gops
  25127. var concatenateNalData = function concatenateNalData(gops) {
  25128. var h,
  25129. i,
  25130. j,
  25131. currentGop,
  25132. currentFrame,
  25133. currentNal,
  25134. dataOffset = 0,
  25135. nalsByteLength = gops.byteLength,
  25136. numberOfNals = gops.nalCount,
  25137. totalByteLength = nalsByteLength + 4 * numberOfNals,
  25138. data = new Uint8Array(totalByteLength),
  25139. view = new DataView(data.buffer); // For each Gop..
  25140. for (h = 0; h < gops.length; h++) {
  25141. currentGop = gops[h]; // For each Frame..
  25142. for (i = 0; i < currentGop.length; i++) {
  25143. currentFrame = currentGop[i]; // For each NAL..
  25144. for (j = 0; j < currentFrame.length; j++) {
  25145. currentNal = currentFrame[j];
  25146. view.setUint32(dataOffset, currentNal.data.byteLength);
  25147. dataOffset += 4;
  25148. data.set(currentNal.data, dataOffset);
  25149. dataOffset += currentNal.data.byteLength;
  25150. }
  25151. }
  25152. }
  25153. return data;
  25154. };
  25155. var frameUtils = {
  25156. groupNalsIntoFrames: groupNalsIntoFrames,
  25157. groupFramesIntoGops: groupFramesIntoGops,
  25158. extendFirstKeyFrame: extendFirstKeyFrame,
  25159. generateSampleTable: generateSampleTable,
  25160. concatenateNalData: concatenateNalData
  25161. };
  25162. var highPrefix = [33, 16, 5, 32, 164, 27];
  25163. var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
  25164. var zeroFill = function zeroFill(count) {
  25165. var a = [];
  25166. while (count--) {
  25167. a.push(0);
  25168. }
  25169. return a;
  25170. };
  25171. var makeTable = function makeTable(metaTable) {
  25172. return Object.keys(metaTable).reduce(function (obj, key) {
  25173. obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
  25174. return arr.concat(part);
  25175. }, []));
  25176. return obj;
  25177. }, {});
  25178. }; // Frames-of-silence to use for filling in missing AAC frames
  25179. var coneOfSilence = {
  25180. 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
  25181. 88200: [highPrefix, [231], zeroFill(170), [56]],
  25182. 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
  25183. 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
  25184. 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
  25185. 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
  25186. 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
  25187. 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
  25188. 12000: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 253, 128], zeroFill(259), [56]],
  25189. 11025: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 255, 192], zeroFill(268), [55, 175, 128], zeroFill(108), [112]],
  25190. 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
  25191. };
  25192. var silence = makeTable(coneOfSilence);
  25193. var ONE_SECOND_IN_TS = 90000,
  25194. // 90kHz clock
  25195. secondsToVideoTs,
  25196. secondsToAudioTs,
  25197. videoTsToSeconds,
  25198. audioTsToSeconds,
  25199. audioTsToVideoTs,
  25200. videoTsToAudioTs;
  25201. secondsToVideoTs = function secondsToVideoTs(seconds) {
  25202. return seconds * ONE_SECOND_IN_TS;
  25203. };
  25204. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  25205. return seconds * sampleRate;
  25206. };
  25207. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  25208. return timestamp / ONE_SECOND_IN_TS;
  25209. };
  25210. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  25211. return timestamp / sampleRate;
  25212. };
  25213. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  25214. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  25215. };
  25216. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  25217. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  25218. };
  25219. var clock = {
  25220. secondsToVideoTs: secondsToVideoTs,
  25221. secondsToAudioTs: secondsToAudioTs,
  25222. videoTsToSeconds: videoTsToSeconds,
  25223. audioTsToSeconds: audioTsToSeconds,
  25224. audioTsToVideoTs: audioTsToVideoTs,
  25225. videoTsToAudioTs: videoTsToAudioTs
  25226. };
  25227. var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
  25228. /**
  25229. * Sum the `byteLength` properties of the data in each AAC frame
  25230. */
  25231. var sumFrameByteLengths = function sumFrameByteLengths(array) {
  25232. var i,
  25233. currentObj,
  25234. sum = 0; // sum the byteLength's all each nal unit in the frame
  25235. for (i = 0; i < array.length; i++) {
  25236. currentObj = array[i];
  25237. sum += currentObj.data.byteLength;
  25238. }
  25239. return sum;
  25240. }; // Possibly pad (prefix) the audio track with silence if appending this track
  25241. // would lead to the introduction of a gap in the audio buffer
  25242. var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
  25243. var baseMediaDecodeTimeTs,
  25244. frameDuration = 0,
  25245. audioGapDuration = 0,
  25246. audioFillFrameCount = 0,
  25247. audioFillDuration = 0,
  25248. silentFrame,
  25249. i;
  25250. if (!frames.length) {
  25251. return;
  25252. }
  25253. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
  25254. frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
  25255. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  25256. // insert the shortest possible amount (audio gap or audio to video gap)
  25257. audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
  25258. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  25259. audioFillDuration = audioFillFrameCount * frameDuration;
  25260. } // don't attempt to fill gaps smaller than a single frame or larger
  25261. // than a half second
  25262. if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
  25263. return;
  25264. }
  25265. silentFrame = silence[track.samplerate];
  25266. if (!silentFrame) {
  25267. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  25268. // from the content instead
  25269. silentFrame = frames[0].data;
  25270. }
  25271. for (i = 0; i < audioFillFrameCount; i++) {
  25272. frames.splice(i, 0, {
  25273. data: silentFrame
  25274. });
  25275. }
  25276. track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  25277. }; // If the audio segment extends before the earliest allowed dts
  25278. // value, remove AAC frames until starts at or after the earliest
  25279. // allowed DTS so that we don't end up with a negative baseMedia-
  25280. // DecodeTime for the audio track
  25281. var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
  25282. if (track.minSegmentDts >= earliestAllowedDts) {
  25283. return adtsFrames;
  25284. } // We will need to recalculate the earliest segment Dts
  25285. track.minSegmentDts = Infinity;
  25286. return adtsFrames.filter(function (currentFrame) {
  25287. // If this is an allowed frame, keep it and record it's Dts
  25288. if (currentFrame.dts >= earliestAllowedDts) {
  25289. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  25290. track.minSegmentPts = track.minSegmentDts;
  25291. return true;
  25292. } // Otherwise, discard it
  25293. return false;
  25294. });
  25295. }; // generate the track's raw mdat data from an array of frames
  25296. var generateSampleTable$1 = function generateSampleTable(frames) {
  25297. var i,
  25298. currentFrame,
  25299. samples = [];
  25300. for (i = 0; i < frames.length; i++) {
  25301. currentFrame = frames[i];
  25302. samples.push({
  25303. size: currentFrame.data.byteLength,
  25304. duration: 1024 // For AAC audio, all samples contain 1024 samples
  25305. });
  25306. }
  25307. return samples;
  25308. }; // generate the track's sample table from an array of frames
  25309. var concatenateFrameData = function concatenateFrameData(frames) {
  25310. var i,
  25311. currentFrame,
  25312. dataOffset = 0,
  25313. data = new Uint8Array(sumFrameByteLengths(frames));
  25314. for (i = 0; i < frames.length; i++) {
  25315. currentFrame = frames[i];
  25316. data.set(currentFrame.data, dataOffset);
  25317. dataOffset += currentFrame.data.byteLength;
  25318. }
  25319. return data;
  25320. };
  25321. var audioFrameUtils = {
  25322. prefixWithSilence: prefixWithSilence,
  25323. trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
  25324. generateSampleTable: generateSampleTable$1,
  25325. concatenateFrameData: concatenateFrameData
  25326. };
  25327. var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
  25328. /**
  25329. * Store information about the start and end of the track and the
  25330. * duration for each frame/sample we process in order to calculate
  25331. * the baseMediaDecodeTime
  25332. */
  25333. var collectDtsInfo = function collectDtsInfo(track, data) {
  25334. if (typeof data.pts === 'number') {
  25335. if (track.timelineStartInfo.pts === undefined) {
  25336. track.timelineStartInfo.pts = data.pts;
  25337. }
  25338. if (track.minSegmentPts === undefined) {
  25339. track.minSegmentPts = data.pts;
  25340. } else {
  25341. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  25342. }
  25343. if (track.maxSegmentPts === undefined) {
  25344. track.maxSegmentPts = data.pts;
  25345. } else {
  25346. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  25347. }
  25348. }
  25349. if (typeof data.dts === 'number') {
  25350. if (track.timelineStartInfo.dts === undefined) {
  25351. track.timelineStartInfo.dts = data.dts;
  25352. }
  25353. if (track.minSegmentDts === undefined) {
  25354. track.minSegmentDts = data.dts;
  25355. } else {
  25356. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  25357. }
  25358. if (track.maxSegmentDts === undefined) {
  25359. track.maxSegmentDts = data.dts;
  25360. } else {
  25361. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  25362. }
  25363. }
  25364. };
  25365. /**
  25366. * Clear values used to calculate the baseMediaDecodeTime between
  25367. * tracks
  25368. */
  25369. var clearDtsInfo = function clearDtsInfo(track) {
  25370. delete track.minSegmentDts;
  25371. delete track.maxSegmentDts;
  25372. delete track.minSegmentPts;
  25373. delete track.maxSegmentPts;
  25374. };
  25375. /**
  25376. * Calculate the track's baseMediaDecodeTime based on the earliest
  25377. * DTS the transmuxer has ever seen and the minimum DTS for the
  25378. * current track
  25379. * @param track {object} track metadata configuration
  25380. * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
  25381. * in the source; false to adjust the first segment to start at 0.
  25382. */
  25383. var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
  25384. var baseMediaDecodeTime,
  25385. scale,
  25386. minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
  25387. if (!keepOriginalTimestamps) {
  25388. minSegmentDts -= track.timelineStartInfo.dts;
  25389. } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  25390. // we want the start of the first segment to be placed
  25391. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
  25392. baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
  25393. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  25394. if (track.type === 'audio') {
  25395. // Audio has a different clock equal to the sampling_rate so we need to
  25396. // scale the PTS values into the clock rate of the track
  25397. scale = track.samplerate / ONE_SECOND_IN_TS$2;
  25398. baseMediaDecodeTime *= scale;
  25399. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  25400. }
  25401. return baseMediaDecodeTime;
  25402. };
  25403. var trackDecodeInfo = {
  25404. clearDtsInfo: clearDtsInfo,
  25405. calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
  25406. collectDtsInfo: collectDtsInfo
  25407. };
  25408. /**
  25409. * mux.js
  25410. *
  25411. * Copyright (c) 2015 Brightcove
  25412. * All rights reserved.
  25413. *
  25414. * Reads in-band caption information from a video elementary
  25415. * stream. Captions must follow the CEA-708 standard for injection
  25416. * into an MPEG-2 transport streams.
  25417. * @see https://en.wikipedia.org/wiki/CEA-708
  25418. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  25419. */
  25420. // Supplemental enhancement information (SEI) NAL units have a
  25421. // payload type field to indicate how they are to be
  25422. // interpreted. CEAS-708 caption content is always transmitted with
  25423. // payload type 0x04.
  25424. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  25425. RBSP_TRAILING_BITS = 128;
  25426. /**
  25427. * Parse a supplemental enhancement information (SEI) NAL unit.
  25428. * Stops parsing once a message of type ITU T T35 has been found.
  25429. *
  25430. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  25431. * @return {object} the parsed SEI payload
  25432. * @see Rec. ITU-T H.264, 7.3.2.3.1
  25433. */
  25434. var parseSei = function parseSei(bytes) {
  25435. var i = 0,
  25436. result = {
  25437. payloadType: -1,
  25438. payloadSize: 0
  25439. },
  25440. payloadType = 0,
  25441. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  25442. while (i < bytes.byteLength) {
  25443. // stop once we have hit the end of the sei_rbsp
  25444. if (bytes[i] === RBSP_TRAILING_BITS) {
  25445. break;
  25446. } // Parse payload type
  25447. while (bytes[i] === 0xFF) {
  25448. payloadType += 255;
  25449. i++;
  25450. }
  25451. payloadType += bytes[i++]; // Parse payload size
  25452. while (bytes[i] === 0xFF) {
  25453. payloadSize += 255;
  25454. i++;
  25455. }
  25456. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  25457. // there can only ever be one caption message in a frame's sei
  25458. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  25459. result.payloadType = payloadType;
  25460. result.payloadSize = payloadSize;
  25461. result.payload = bytes.subarray(i, i + payloadSize);
  25462. break;
  25463. } // skip the payload and parse the next message
  25464. i += payloadSize;
  25465. payloadType = 0;
  25466. payloadSize = 0;
  25467. }
  25468. return result;
  25469. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  25470. var parseUserData = function parseUserData(sei) {
  25471. // itu_t_t35_contry_code must be 181 (United States) for
  25472. // captions
  25473. if (sei.payload[0] !== 181) {
  25474. return null;
  25475. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  25476. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  25477. return null;
  25478. } // the user_identifier should be "GA94" to indicate ATSC1 data
  25479. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  25480. return null;
  25481. } // finally, user_data_type_code should be 0x03 for caption data
  25482. if (sei.payload[7] !== 0x03) {
  25483. return null;
  25484. } // return the user_data_type_structure and strip the trailing
  25485. // marker bits
  25486. return sei.payload.subarray(8, sei.payload.length - 1);
  25487. }; // see CEA-708-D, section 4.4
  25488. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  25489. var results = [],
  25490. i,
  25491. count,
  25492. offset,
  25493. data; // if this is just filler, return immediately
  25494. if (!(userData[0] & 0x40)) {
  25495. return results;
  25496. } // parse out the cc_data_1 and cc_data_2 fields
  25497. count = userData[0] & 0x1f;
  25498. for (i = 0; i < count; i++) {
  25499. offset = i * 3;
  25500. data = {
  25501. type: userData[offset + 2] & 0x03,
  25502. pts: pts
  25503. }; // capture cc data when cc_valid is 1
  25504. if (userData[offset + 2] & 0x04) {
  25505. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  25506. results.push(data);
  25507. }
  25508. }
  25509. return results;
  25510. };
  25511. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  25512. var length = data.byteLength,
  25513. emulationPreventionBytesPositions = [],
  25514. i = 1,
  25515. newLength,
  25516. newData; // Find all `Emulation Prevention Bytes`
  25517. while (i < length - 2) {
  25518. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  25519. emulationPreventionBytesPositions.push(i + 2);
  25520. i += 2;
  25521. } else {
  25522. i++;
  25523. }
  25524. } // If no Emulation Prevention Bytes were found just return the original
  25525. // array
  25526. if (emulationPreventionBytesPositions.length === 0) {
  25527. return data;
  25528. } // Create a new array to hold the NAL unit data
  25529. newLength = length - emulationPreventionBytesPositions.length;
  25530. newData = new Uint8Array(newLength);
  25531. var sourceIndex = 0;
  25532. for (i = 0; i < newLength; sourceIndex++, i++) {
  25533. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  25534. // Skip this byte
  25535. sourceIndex++; // Remove this position index
  25536. emulationPreventionBytesPositions.shift();
  25537. }
  25538. newData[i] = data[sourceIndex];
  25539. }
  25540. return newData;
  25541. }; // exports
  25542. var captionPacketParser = {
  25543. parseSei: parseSei,
  25544. parseUserData: parseUserData,
  25545. parseCaptionPackets: parseCaptionPackets,
  25546. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  25547. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  25548. }; // -----------------
  25549. // Link To Transport
  25550. // -----------------
  25551. var CaptionStream = function CaptionStream() {
  25552. CaptionStream.prototype.init.call(this);
  25553. this.captionPackets_ = [];
  25554. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  25555. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  25556. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  25557. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  25558. ];
  25559. this.reset(); // forward data and done events from CCs to this CaptionStream
  25560. this.ccStreams_.forEach(function (cc) {
  25561. cc.on('data', this.trigger.bind(this, 'data'));
  25562. cc.on('done', this.trigger.bind(this, 'done'));
  25563. }, this);
  25564. };
  25565. CaptionStream.prototype = new stream();
  25566. CaptionStream.prototype.push = function (event) {
  25567. var sei, userData, newCaptionPackets; // only examine SEI NALs
  25568. if (event.nalUnitType !== 'sei_rbsp') {
  25569. return;
  25570. } // parse the sei
  25571. sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
  25572. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  25573. return;
  25574. } // parse out the user data payload
  25575. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  25576. if (!userData) {
  25577. return;
  25578. } // Sometimes, the same segment # will be downloaded twice. To stop the
  25579. // caption data from being processed twice, we track the latest dts we've
  25580. // received and ignore everything with a dts before that. However, since
  25581. // data for a specific dts can be split across packets on either side of
  25582. // a segment boundary, we need to make sure we *don't* ignore the packets
  25583. // from the *next* segment that have dts === this.latestDts_. By constantly
  25584. // tracking the number of packets received with dts === this.latestDts_, we
  25585. // know how many should be ignored once we start receiving duplicates.
  25586. if (event.dts < this.latestDts_) {
  25587. // We've started getting older data, so set the flag.
  25588. this.ignoreNextEqualDts_ = true;
  25589. return;
  25590. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  25591. this.numSameDts_--;
  25592. if (!this.numSameDts_) {
  25593. // We've received the last duplicate packet, time to start processing again
  25594. this.ignoreNextEqualDts_ = false;
  25595. }
  25596. return;
  25597. } // parse out CC data packets and save them for later
  25598. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  25599. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  25600. if (this.latestDts_ !== event.dts) {
  25601. this.numSameDts_ = 0;
  25602. }
  25603. this.numSameDts_++;
  25604. this.latestDts_ = event.dts;
  25605. };
  25606. CaptionStream.prototype.flush = function () {
  25607. // make sure we actually parsed captions before proceeding
  25608. if (!this.captionPackets_.length) {
  25609. this.ccStreams_.forEach(function (cc) {
  25610. cc.flush();
  25611. }, this);
  25612. return;
  25613. } // In Chrome, the Array#sort function is not stable so add a
  25614. // presortIndex that we can use to ensure we get a stable-sort
  25615. this.captionPackets_.forEach(function (elem, idx) {
  25616. elem.presortIndex = idx;
  25617. }); // sort caption byte-pairs based on their PTS values
  25618. this.captionPackets_.sort(function (a, b) {
  25619. if (a.pts === b.pts) {
  25620. return a.presortIndex - b.presortIndex;
  25621. }
  25622. return a.pts - b.pts;
  25623. });
  25624. this.captionPackets_.forEach(function (packet) {
  25625. if (packet.type < 2) {
  25626. // Dispatch packet to the right Cea608Stream
  25627. this.dispatchCea608Packet(packet);
  25628. } // this is where an 'else' would go for a dispatching packets
  25629. // to a theoretical Cea708Stream that handles SERVICEn data
  25630. }, this);
  25631. this.captionPackets_.length = 0;
  25632. this.ccStreams_.forEach(function (cc) {
  25633. cc.flush();
  25634. }, this);
  25635. return;
  25636. };
  25637. CaptionStream.prototype.reset = function () {
  25638. this.latestDts_ = null;
  25639. this.ignoreNextEqualDts_ = false;
  25640. this.numSameDts_ = 0;
  25641. this.activeCea608Channel_ = [null, null];
  25642. this.ccStreams_.forEach(function (ccStream) {
  25643. ccStream.reset();
  25644. });
  25645. };
  25646. CaptionStream.prototype.dispatchCea608Packet = function (packet) {
  25647. // NOTE: packet.type is the CEA608 field
  25648. if (this.setsChannel1Active(packet)) {
  25649. this.activeCea608Channel_[packet.type] = 0;
  25650. } else if (this.setsChannel2Active(packet)) {
  25651. this.activeCea608Channel_[packet.type] = 1;
  25652. }
  25653. if (this.activeCea608Channel_[packet.type] === null) {
  25654. // If we haven't received anything to set the active channel, discard the
  25655. // data; we don't want jumbled captions
  25656. return;
  25657. }
  25658. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  25659. };
  25660. CaptionStream.prototype.setsChannel1Active = function (packet) {
  25661. return (packet.ccData & 0x7800) === 0x1000;
  25662. };
  25663. CaptionStream.prototype.setsChannel2Active = function (packet) {
  25664. return (packet.ccData & 0x7800) === 0x1800;
  25665. }; // ----------------------
  25666. // Session to Application
  25667. // ----------------------
  25668. // This hash maps non-ASCII, special, and extended character codes to their
  25669. // proper Unicode equivalent. The first keys that are only a single byte
  25670. // are the non-standard ASCII characters, which simply map the CEA608 byte
  25671. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  25672. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  25673. // can be performed regardless of the field and data channel on which the
  25674. // character code was received.
  25675. var CHARACTER_TRANSLATION = {
  25676. 0x2a: 0xe1,
  25677. // á
  25678. 0x5c: 0xe9,
  25679. // é
  25680. 0x5e: 0xed,
  25681. // í
  25682. 0x5f: 0xf3,
  25683. // ó
  25684. 0x60: 0xfa,
  25685. // ú
  25686. 0x7b: 0xe7,
  25687. // ç
  25688. 0x7c: 0xf7,
  25689. // ÷
  25690. 0x7d: 0xd1,
  25691. // Ñ
  25692. 0x7e: 0xf1,
  25693. // ñ
  25694. 0x7f: 0x2588,
  25695. // █
  25696. 0x0130: 0xae,
  25697. // ®
  25698. 0x0131: 0xb0,
  25699. // °
  25700. 0x0132: 0xbd,
  25701. // ½
  25702. 0x0133: 0xbf,
  25703. // ¿
  25704. 0x0134: 0x2122,
  25705. // ™
  25706. 0x0135: 0xa2,
  25707. // ¢
  25708. 0x0136: 0xa3,
  25709. // £
  25710. 0x0137: 0x266a,
  25711. // ♪
  25712. 0x0138: 0xe0,
  25713. // à
  25714. 0x0139: 0xa0,
  25715. //
  25716. 0x013a: 0xe8,
  25717. // è
  25718. 0x013b: 0xe2,
  25719. // â
  25720. 0x013c: 0xea,
  25721. // ê
  25722. 0x013d: 0xee,
  25723. // î
  25724. 0x013e: 0xf4,
  25725. // ô
  25726. 0x013f: 0xfb,
  25727. // û
  25728. 0x0220: 0xc1,
  25729. // Á
  25730. 0x0221: 0xc9,
  25731. // É
  25732. 0x0222: 0xd3,
  25733. // Ó
  25734. 0x0223: 0xda,
  25735. // Ú
  25736. 0x0224: 0xdc,
  25737. // Ü
  25738. 0x0225: 0xfc,
  25739. // ü
  25740. 0x0226: 0x2018,
  25741. // ‘
  25742. 0x0227: 0xa1,
  25743. // ¡
  25744. 0x0228: 0x2a,
  25745. // *
  25746. 0x0229: 0x27,
  25747. // '
  25748. 0x022a: 0x2014,
  25749. // —
  25750. 0x022b: 0xa9,
  25751. // ©
  25752. 0x022c: 0x2120,
  25753. // ℠
  25754. 0x022d: 0x2022,
  25755. // •
  25756. 0x022e: 0x201c,
  25757. // “
  25758. 0x022f: 0x201d,
  25759. // ”
  25760. 0x0230: 0xc0,
  25761. // À
  25762. 0x0231: 0xc2,
  25763. // Â
  25764. 0x0232: 0xc7,
  25765. // Ç
  25766. 0x0233: 0xc8,
  25767. // È
  25768. 0x0234: 0xca,
  25769. // Ê
  25770. 0x0235: 0xcb,
  25771. // Ë
  25772. 0x0236: 0xeb,
  25773. // ë
  25774. 0x0237: 0xce,
  25775. // Î
  25776. 0x0238: 0xcf,
  25777. // Ï
  25778. 0x0239: 0xef,
  25779. // ï
  25780. 0x023a: 0xd4,
  25781. // Ô
  25782. 0x023b: 0xd9,
  25783. // Ù
  25784. 0x023c: 0xf9,
  25785. // ù
  25786. 0x023d: 0xdb,
  25787. // Û
  25788. 0x023e: 0xab,
  25789. // «
  25790. 0x023f: 0xbb,
  25791. // »
  25792. 0x0320: 0xc3,
  25793. // Ã
  25794. 0x0321: 0xe3,
  25795. // ã
  25796. 0x0322: 0xcd,
  25797. // Í
  25798. 0x0323: 0xcc,
  25799. // Ì
  25800. 0x0324: 0xec,
  25801. // ì
  25802. 0x0325: 0xd2,
  25803. // Ò
  25804. 0x0326: 0xf2,
  25805. // ò
  25806. 0x0327: 0xd5,
  25807. // Õ
  25808. 0x0328: 0xf5,
  25809. // õ
  25810. 0x0329: 0x7b,
  25811. // {
  25812. 0x032a: 0x7d,
  25813. // }
  25814. 0x032b: 0x5c,
  25815. // \
  25816. 0x032c: 0x5e,
  25817. // ^
  25818. 0x032d: 0x5f,
  25819. // _
  25820. 0x032e: 0x7c,
  25821. // |
  25822. 0x032f: 0x7e,
  25823. // ~
  25824. 0x0330: 0xc4,
  25825. // Ä
  25826. 0x0331: 0xe4,
  25827. // ä
  25828. 0x0332: 0xd6,
  25829. // Ö
  25830. 0x0333: 0xf6,
  25831. // ö
  25832. 0x0334: 0xdf,
  25833. // ß
  25834. 0x0335: 0xa5,
  25835. // ¥
  25836. 0x0336: 0xa4,
  25837. // ¤
  25838. 0x0337: 0x2502,
  25839. // │
  25840. 0x0338: 0xc5,
  25841. // Å
  25842. 0x0339: 0xe5,
  25843. // å
  25844. 0x033a: 0xd8,
  25845. // Ø
  25846. 0x033b: 0xf8,
  25847. // ø
  25848. 0x033c: 0x250c,
  25849. // ┌
  25850. 0x033d: 0x2510,
  25851. // ┐
  25852. 0x033e: 0x2514,
  25853. // └
  25854. 0x033f: 0x2518 // ┘
  25855. };
  25856. var getCharFromCode = function getCharFromCode(code) {
  25857. if (code === null) {
  25858. return '';
  25859. }
  25860. code = CHARACTER_TRANSLATION[code] || code;
  25861. return String.fromCharCode(code);
  25862. }; // the index of the last row in a CEA-608 display buffer
  25863. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  25864. // getting it through bit logic.
  25865. var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420]; // CEA-608 captions are rendered onto a 34x15 matrix of character
  25866. // cells. The "bottom" row is the last element in the outer array.
  25867. var createDisplayBuffer = function createDisplayBuffer() {
  25868. var result = [],
  25869. i = BOTTOM_ROW + 1;
  25870. while (i--) {
  25871. result.push('');
  25872. }
  25873. return result;
  25874. };
  25875. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  25876. Cea608Stream.prototype.init.call(this);
  25877. this.field_ = field || 0;
  25878. this.dataChannel_ = dataChannel || 0;
  25879. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  25880. this.setConstants();
  25881. this.reset();
  25882. this.push = function (packet) {
  25883. var data, swap, char0, char1, text; // remove the parity bits
  25884. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  25885. if (data === this.lastControlCode_) {
  25886. this.lastControlCode_ = null;
  25887. return;
  25888. } // Store control codes
  25889. if ((data & 0xf000) === 0x1000) {
  25890. this.lastControlCode_ = data;
  25891. } else if (data !== this.PADDING_) {
  25892. this.lastControlCode_ = null;
  25893. }
  25894. char0 = data >>> 8;
  25895. char1 = data & 0xff;
  25896. if (data === this.PADDING_) {
  25897. return;
  25898. } else if (data === this.RESUME_CAPTION_LOADING_) {
  25899. this.mode_ = 'popOn';
  25900. } else if (data === this.END_OF_CAPTION_) {
  25901. // If an EOC is received while in paint-on mode, the displayed caption
  25902. // text should be swapped to non-displayed memory as if it was a pop-on
  25903. // caption. Because of that, we should explicitly switch back to pop-on
  25904. // mode
  25905. this.mode_ = 'popOn';
  25906. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  25907. this.flushDisplayed(packet.pts); // flip memory
  25908. swap = this.displayed_;
  25909. this.displayed_ = this.nonDisplayed_;
  25910. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  25911. this.startPts_ = packet.pts;
  25912. } else if (data === this.ROLL_UP_2_ROWS_) {
  25913. this.rollUpRows_ = 2;
  25914. this.setRollUp(packet.pts);
  25915. } else if (data === this.ROLL_UP_3_ROWS_) {
  25916. this.rollUpRows_ = 3;
  25917. this.setRollUp(packet.pts);
  25918. } else if (data === this.ROLL_UP_4_ROWS_) {
  25919. this.rollUpRows_ = 4;
  25920. this.setRollUp(packet.pts);
  25921. } else if (data === this.CARRIAGE_RETURN_) {
  25922. this.clearFormatting(packet.pts);
  25923. this.flushDisplayed(packet.pts);
  25924. this.shiftRowsUp_();
  25925. this.startPts_ = packet.pts;
  25926. } else if (data === this.BACKSPACE_) {
  25927. if (this.mode_ === 'popOn') {
  25928. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  25929. } else {
  25930. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  25931. }
  25932. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  25933. this.flushDisplayed(packet.pts);
  25934. this.displayed_ = createDisplayBuffer();
  25935. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  25936. this.nonDisplayed_ = createDisplayBuffer();
  25937. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  25938. if (this.mode_ !== 'paintOn') {
  25939. // NOTE: This should be removed when proper caption positioning is
  25940. // implemented
  25941. this.flushDisplayed(packet.pts);
  25942. this.displayed_ = createDisplayBuffer();
  25943. }
  25944. this.mode_ = 'paintOn';
  25945. this.startPts_ = packet.pts; // Append special characters to caption text
  25946. } else if (this.isSpecialCharacter(char0, char1)) {
  25947. // Bitmask char0 so that we can apply character transformations
  25948. // regardless of field and data channel.
  25949. // Then byte-shift to the left and OR with char1 so we can pass the
  25950. // entire character code to `getCharFromCode`.
  25951. char0 = (char0 & 0x03) << 8;
  25952. text = getCharFromCode(char0 | char1);
  25953. this[this.mode_](packet.pts, text);
  25954. this.column_++; // Append extended characters to caption text
  25955. } else if (this.isExtCharacter(char0, char1)) {
  25956. // Extended characters always follow their "non-extended" equivalents.
  25957. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  25958. // decoders are supposed to drop the "è", while compliant decoders
  25959. // backspace the "e" and insert "è".
  25960. // Delete the previous character
  25961. if (this.mode_ === 'popOn') {
  25962. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  25963. } else {
  25964. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  25965. } // Bitmask char0 so that we can apply character transformations
  25966. // regardless of field and data channel.
  25967. // Then byte-shift to the left and OR with char1 so we can pass the
  25968. // entire character code to `getCharFromCode`.
  25969. char0 = (char0 & 0x03) << 8;
  25970. text = getCharFromCode(char0 | char1);
  25971. this[this.mode_](packet.pts, text);
  25972. this.column_++; // Process mid-row codes
  25973. } else if (this.isMidRowCode(char0, char1)) {
  25974. // Attributes are not additive, so clear all formatting
  25975. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  25976. // should be replaced with spaces, so add one now
  25977. this[this.mode_](packet.pts, ' ');
  25978. this.column_++;
  25979. if ((char1 & 0xe) === 0xe) {
  25980. this.addFormatting(packet.pts, ['i']);
  25981. }
  25982. if ((char1 & 0x1) === 0x1) {
  25983. this.addFormatting(packet.pts, ['u']);
  25984. } // Detect offset control codes and adjust cursor
  25985. } else if (this.isOffsetControlCode(char0, char1)) {
  25986. // Cursor position is set by indent PAC (see below) in 4-column
  25987. // increments, with an additional offset code of 1-3 to reach any
  25988. // of the 32 columns specified by CEA-608. So all we need to do
  25989. // here is increment the column cursor by the given offset.
  25990. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  25991. } else if (this.isPAC(char0, char1)) {
  25992. // There's no logic for PAC -> row mapping, so we have to just
  25993. // find the row code in an array and use its index :(
  25994. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  25995. if (this.mode_ === 'rollUp') {
  25996. // This implies that the base row is incorrectly set.
  25997. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  25998. // of roll-up rows set.
  25999. if (row - this.rollUpRows_ + 1 < 0) {
  26000. row = this.rollUpRows_ - 1;
  26001. }
  26002. this.setRollUp(packet.pts, row);
  26003. }
  26004. if (row !== this.row_) {
  26005. // formatting is only persistent for current row
  26006. this.clearFormatting(packet.pts);
  26007. this.row_ = row;
  26008. } // All PACs can apply underline, so detect and apply
  26009. // (All odd-numbered second bytes set underline)
  26010. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  26011. this.addFormatting(packet.pts, ['u']);
  26012. }
  26013. if ((data & 0x10) === 0x10) {
  26014. // We've got an indent level code. Each successive even number
  26015. // increments the column cursor by 4, so we can get the desired
  26016. // column position by bit-shifting to the right (to get n/2)
  26017. // and multiplying by 4.
  26018. this.column_ = ((data & 0xe) >> 1) * 4;
  26019. }
  26020. if (this.isColorPAC(char1)) {
  26021. // it's a color code, though we only support white, which
  26022. // can be either normal or italicized. white italics can be
  26023. // either 0x4e or 0x6e depending on the row, so we just
  26024. // bitwise-and with 0xe to see if italics should be turned on
  26025. if ((char1 & 0xe) === 0xe) {
  26026. this.addFormatting(packet.pts, ['i']);
  26027. }
  26028. } // We have a normal character in char0, and possibly one in char1
  26029. } else if (this.isNormalChar(char0)) {
  26030. if (char1 === 0x00) {
  26031. char1 = null;
  26032. }
  26033. text = getCharFromCode(char0);
  26034. text += getCharFromCode(char1);
  26035. this[this.mode_](packet.pts, text);
  26036. this.column_ += text.length;
  26037. } // finish data processing
  26038. };
  26039. };
  26040. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  26041. // display buffer
  26042. Cea608Stream.prototype.flushDisplayed = function (pts) {
  26043. var content = this.displayed_ // remove spaces from the start and end of the string
  26044. .map(function (row) {
  26045. try {
  26046. return row.trim();
  26047. } catch (e) {
  26048. // Ordinarily, this shouldn't happen. However, caption
  26049. // parsing errors should not throw exceptions and
  26050. // break playback.
  26051. // eslint-disable-next-line no-console
  26052. console.error('Skipping malformed caption.');
  26053. return '';
  26054. }
  26055. }) // combine all text rows to display in one cue
  26056. .join('\n') // and remove blank rows from the start and end, but not the middle
  26057. .replace(/^\n+|\n+$/g, '');
  26058. if (content.length) {
  26059. this.trigger('data', {
  26060. startPts: this.startPts_,
  26061. endPts: pts,
  26062. text: content,
  26063. stream: this.name_
  26064. });
  26065. }
  26066. };
  26067. /**
  26068. * Zero out the data, used for startup and on seek
  26069. */
  26070. Cea608Stream.prototype.reset = function () {
  26071. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  26072. // actually display captions. If a caption is shifted to a row
  26073. // with a lower index than this, it is cleared from the display
  26074. // buffer
  26075. this.topRow_ = 0;
  26076. this.startPts_ = 0;
  26077. this.displayed_ = createDisplayBuffer();
  26078. this.nonDisplayed_ = createDisplayBuffer();
  26079. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  26080. this.column_ = 0;
  26081. this.row_ = BOTTOM_ROW;
  26082. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  26083. this.formatting_ = [];
  26084. };
  26085. /**
  26086. * Sets up control code and related constants for this instance
  26087. */
  26088. Cea608Stream.prototype.setConstants = function () {
  26089. // The following attributes have these uses:
  26090. // ext_ : char0 for mid-row codes, and the base for extended
  26091. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  26092. // extended codes)
  26093. // control_: char0 for control codes, except byte-shifted to the
  26094. // left so that we can do this.control_ | CONTROL_CODE
  26095. // offset_: char0 for tab offset codes
  26096. //
  26097. // It's also worth noting that control codes, and _only_ control codes,
  26098. // differ between field 1 and field2. Field 2 control codes are always
  26099. // their field 1 value plus 1. That's why there's the "| field" on the
  26100. // control value.
  26101. if (this.dataChannel_ === 0) {
  26102. this.BASE_ = 0x10;
  26103. this.EXT_ = 0x11;
  26104. this.CONTROL_ = (0x14 | this.field_) << 8;
  26105. this.OFFSET_ = 0x17;
  26106. } else if (this.dataChannel_ === 1) {
  26107. this.BASE_ = 0x18;
  26108. this.EXT_ = 0x19;
  26109. this.CONTROL_ = (0x1c | this.field_) << 8;
  26110. this.OFFSET_ = 0x1f;
  26111. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  26112. // list is not exhaustive. For a more comprehensive listing and semantics see
  26113. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  26114. // Padding
  26115. this.PADDING_ = 0x0000; // Pop-on Mode
  26116. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  26117. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  26118. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  26119. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  26120. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  26121. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  26122. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  26123. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  26124. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  26125. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  26126. };
  26127. /**
  26128. * Detects if the 2-byte packet data is a special character
  26129. *
  26130. * Special characters have a second byte in the range 0x30 to 0x3f,
  26131. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  26132. * data channel 2).
  26133. *
  26134. * @param {Integer} char0 The first byte
  26135. * @param {Integer} char1 The second byte
  26136. * @return {Boolean} Whether the 2 bytes are an special character
  26137. */
  26138. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  26139. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  26140. };
  26141. /**
  26142. * Detects if the 2-byte packet data is an extended character
  26143. *
  26144. * Extended characters have a second byte in the range 0x20 to 0x3f,
  26145. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  26146. * 0x1a or 0x1b (for data channel 2).
  26147. *
  26148. * @param {Integer} char0 The first byte
  26149. * @param {Integer} char1 The second byte
  26150. * @return {Boolean} Whether the 2 bytes are an extended character
  26151. */
  26152. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  26153. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  26154. };
  26155. /**
  26156. * Detects if the 2-byte packet is a mid-row code
  26157. *
  26158. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  26159. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  26160. * channel 2).
  26161. *
  26162. * @param {Integer} char0 The first byte
  26163. * @param {Integer} char1 The second byte
  26164. * @return {Boolean} Whether the 2 bytes are a mid-row code
  26165. */
  26166. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  26167. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  26168. };
  26169. /**
  26170. * Detects if the 2-byte packet is an offset control code
  26171. *
  26172. * Offset control codes have a second byte in the range 0x21 to 0x23,
  26173. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  26174. * data channel 2).
  26175. *
  26176. * @param {Integer} char0 The first byte
  26177. * @param {Integer} char1 The second byte
  26178. * @return {Boolean} Whether the 2 bytes are an offset control code
  26179. */
  26180. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  26181. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  26182. };
  26183. /**
  26184. * Detects if the 2-byte packet is a Preamble Address Code
  26185. *
  26186. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  26187. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  26188. * range 0x40 to 0x7f.
  26189. *
  26190. * @param {Integer} char0 The first byte
  26191. * @param {Integer} char1 The second byte
  26192. * @return {Boolean} Whether the 2 bytes are a PAC
  26193. */
  26194. Cea608Stream.prototype.isPAC = function (char0, char1) {
  26195. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  26196. };
  26197. /**
  26198. * Detects if a packet's second byte is in the range of a PAC color code
  26199. *
  26200. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  26201. * 0x60 to 0x6f.
  26202. *
  26203. * @param {Integer} char1 The second byte
  26204. * @return {Boolean} Whether the byte is a color PAC
  26205. */
  26206. Cea608Stream.prototype.isColorPAC = function (char1) {
  26207. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  26208. };
  26209. /**
  26210. * Detects if a single byte is in the range of a normal character
  26211. *
  26212. * Normal text bytes are in the range 0x20 to 0x7f.
  26213. *
  26214. * @param {Integer} char The byte
  26215. * @return {Boolean} Whether the byte is a normal character
  26216. */
  26217. Cea608Stream.prototype.isNormalChar = function (char) {
  26218. return char >= 0x20 && char <= 0x7f;
  26219. };
  26220. /**
  26221. * Configures roll-up
  26222. *
  26223. * @param {Integer} pts Current PTS
  26224. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  26225. * a new position
  26226. */
  26227. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  26228. // Reset the base row to the bottom row when switching modes
  26229. if (this.mode_ !== 'rollUp') {
  26230. this.row_ = BOTTOM_ROW;
  26231. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  26232. this.flushDisplayed(pts);
  26233. this.nonDisplayed_ = createDisplayBuffer();
  26234. this.displayed_ = createDisplayBuffer();
  26235. }
  26236. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  26237. // move currently displayed captions (up or down) to the new base row
  26238. for (var i = 0; i < this.rollUpRows_; i++) {
  26239. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  26240. this.displayed_[this.row_ - i] = '';
  26241. }
  26242. }
  26243. if (newBaseRow === undefined) {
  26244. newBaseRow = this.row_;
  26245. }
  26246. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  26247. }; // Adds the opening HTML tag for the passed character to the caption text,
  26248. // and keeps track of it for later closing
  26249. Cea608Stream.prototype.addFormatting = function (pts, format) {
  26250. this.formatting_ = this.formatting_.concat(format);
  26251. var text = format.reduce(function (text, format) {
  26252. return text + '<' + format + '>';
  26253. }, '');
  26254. this[this.mode_](pts, text);
  26255. }; // Adds HTML closing tags for current formatting to caption text and
  26256. // clears remembered formatting
  26257. Cea608Stream.prototype.clearFormatting = function (pts) {
  26258. if (!this.formatting_.length) {
  26259. return;
  26260. }
  26261. var text = this.formatting_.reverse().reduce(function (text, format) {
  26262. return text + '</' + format + '>';
  26263. }, '');
  26264. this.formatting_ = [];
  26265. this[this.mode_](pts, text);
  26266. }; // Mode Implementations
  26267. Cea608Stream.prototype.popOn = function (pts, text) {
  26268. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  26269. baseRow += text;
  26270. this.nonDisplayed_[this.row_] = baseRow;
  26271. };
  26272. Cea608Stream.prototype.rollUp = function (pts, text) {
  26273. var baseRow = this.displayed_[this.row_];
  26274. baseRow += text;
  26275. this.displayed_[this.row_] = baseRow;
  26276. };
  26277. Cea608Stream.prototype.shiftRowsUp_ = function () {
  26278. var i; // clear out inactive rows
  26279. for (i = 0; i < this.topRow_; i++) {
  26280. this.displayed_[i] = '';
  26281. }
  26282. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  26283. this.displayed_[i] = '';
  26284. } // shift displayed rows up
  26285. for (i = this.topRow_; i < this.row_; i++) {
  26286. this.displayed_[i] = this.displayed_[i + 1];
  26287. } // clear out the bottom row
  26288. this.displayed_[this.row_] = '';
  26289. };
  26290. Cea608Stream.prototype.paintOn = function (pts, text) {
  26291. var baseRow = this.displayed_[this.row_];
  26292. baseRow += text;
  26293. this.displayed_[this.row_] = baseRow;
  26294. }; // exports
  26295. var captionStream = {
  26296. CaptionStream: CaptionStream,
  26297. Cea608Stream: Cea608Stream
  26298. };
  26299. var streamTypes = {
  26300. H264_STREAM_TYPE: 0x1B,
  26301. ADTS_STREAM_TYPE: 0x0F,
  26302. METADATA_STREAM_TYPE: 0x15
  26303. };
  26304. var MAX_TS = 8589934592;
  26305. var RO_THRESH = 4294967296;
  26306. var handleRollover = function handleRollover(value, reference) {
  26307. var direction = 1;
  26308. if (value > reference) {
  26309. // If the current timestamp value is greater than our reference timestamp and we detect a
  26310. // timestamp rollover, this means the roll over is happening in the opposite direction.
  26311. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  26312. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  26313. // rollover point. In loading this segment, the timestamp values will be very large,
  26314. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  26315. // the time stamp to be `value - 2^33`.
  26316. direction = -1;
  26317. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  26318. // cause an incorrect adjustment.
  26319. while (Math.abs(reference - value) > RO_THRESH) {
  26320. value += direction * MAX_TS;
  26321. }
  26322. return value;
  26323. };
  26324. var TimestampRolloverStream = function TimestampRolloverStream(type) {
  26325. var lastDTS, referenceDTS;
  26326. TimestampRolloverStream.prototype.init.call(this);
  26327. this.type_ = type;
  26328. this.push = function (data) {
  26329. if (data.type !== this.type_) {
  26330. return;
  26331. }
  26332. if (referenceDTS === undefined) {
  26333. referenceDTS = data.dts;
  26334. }
  26335. data.dts = handleRollover(data.dts, referenceDTS);
  26336. data.pts = handleRollover(data.pts, referenceDTS);
  26337. lastDTS = data.dts;
  26338. this.trigger('data', data);
  26339. };
  26340. this.flush = function () {
  26341. referenceDTS = lastDTS;
  26342. this.trigger('done');
  26343. };
  26344. this.discontinuity = function () {
  26345. referenceDTS = void 0;
  26346. lastDTS = void 0;
  26347. };
  26348. };
  26349. TimestampRolloverStream.prototype = new stream();
  26350. var timestampRolloverStream = {
  26351. TimestampRolloverStream: TimestampRolloverStream,
  26352. handleRollover: handleRollover
  26353. };
  26354. var percentEncode = function percentEncode(bytes, start, end) {
  26355. var i,
  26356. result = '';
  26357. for (i = start; i < end; i++) {
  26358. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  26359. }
  26360. return result;
  26361. },
  26362. // return the string representation of the specified byte range,
  26363. // interpreted as UTf-8.
  26364. parseUtf8 = function parseUtf8(bytes, start, end) {
  26365. return decodeURIComponent(percentEncode(bytes, start, end));
  26366. },
  26367. // return the string representation of the specified byte range,
  26368. // interpreted as ISO-8859-1.
  26369. parseIso88591 = function parseIso88591(bytes, start, end) {
  26370. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  26371. },
  26372. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  26373. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  26374. },
  26375. tagParsers = {
  26376. TXXX: function TXXX(tag) {
  26377. var i;
  26378. if (tag.data[0] !== 3) {
  26379. // ignore frames with unrecognized character encodings
  26380. return;
  26381. }
  26382. for (i = 1; i < tag.data.length; i++) {
  26383. if (tag.data[i] === 0) {
  26384. // parse the text fields
  26385. tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
  26386. tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
  26387. break;
  26388. }
  26389. }
  26390. tag.data = tag.value;
  26391. },
  26392. WXXX: function WXXX(tag) {
  26393. var i;
  26394. if (tag.data[0] !== 3) {
  26395. // ignore frames with unrecognized character encodings
  26396. return;
  26397. }
  26398. for (i = 1; i < tag.data.length; i++) {
  26399. if (tag.data[i] === 0) {
  26400. // parse the description and URL fields
  26401. tag.description = parseUtf8(tag.data, 1, i);
  26402. tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
  26403. break;
  26404. }
  26405. }
  26406. },
  26407. PRIV: function PRIV(tag) {
  26408. var i;
  26409. for (i = 0; i < tag.data.length; i++) {
  26410. if (tag.data[i] === 0) {
  26411. // parse the description and URL fields
  26412. tag.owner = parseIso88591(tag.data, 0, i);
  26413. break;
  26414. }
  26415. }
  26416. tag.privateData = tag.data.subarray(i + 1);
  26417. tag.data = tag.privateData;
  26418. }
  26419. },
  26420. _MetadataStream;
  26421. _MetadataStream = function MetadataStream(options) {
  26422. var settings = {
  26423. debug: !!(options && options.debug),
  26424. // the bytes of the program-level descriptor field in MP2T
  26425. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  26426. // program element descriptors"
  26427. descriptor: options && options.descriptor
  26428. },
  26429. // the total size in bytes of the ID3 tag being parsed
  26430. tagSize = 0,
  26431. // tag data that is not complete enough to be parsed
  26432. buffer = [],
  26433. // the total number of bytes currently in the buffer
  26434. bufferSize = 0,
  26435. i;
  26436. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  26437. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  26438. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  26439. if (settings.descriptor) {
  26440. for (i = 0; i < settings.descriptor.length; i++) {
  26441. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  26442. }
  26443. }
  26444. this.push = function (chunk) {
  26445. var tag, frameStart, frameSize, frame, i, frameHeader;
  26446. if (chunk.type !== 'timed-metadata') {
  26447. return;
  26448. } // if data_alignment_indicator is set in the PES header,
  26449. // we must have the start of a new ID3 tag. Assume anything
  26450. // remaining in the buffer was malformed and throw it out
  26451. if (chunk.dataAlignmentIndicator) {
  26452. bufferSize = 0;
  26453. buffer.length = 0;
  26454. } // ignore events that don't look like ID3 data
  26455. if (buffer.length === 0 && (chunk.data.length < 10 || chunk.data[0] !== 'I'.charCodeAt(0) || chunk.data[1] !== 'D'.charCodeAt(0) || chunk.data[2] !== '3'.charCodeAt(0))) {
  26456. if (settings.debug) {
  26457. // eslint-disable-next-line no-console
  26458. console.log('Skipping unrecognized metadata packet');
  26459. }
  26460. return;
  26461. } // add this chunk to the data we've collected so far
  26462. buffer.push(chunk);
  26463. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  26464. if (buffer.length === 1) {
  26465. // the frame size is transmitted as a 28-bit integer in the
  26466. // last four bytes of the ID3 header.
  26467. // The most significant bit of each byte is dropped and the
  26468. // results concatenated to recover the actual value.
  26469. tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  26470. // convenient for our comparisons to include it
  26471. tagSize += 10;
  26472. } // if the entire frame has not arrived, wait for more data
  26473. if (bufferSize < tagSize) {
  26474. return;
  26475. } // collect the entire frame so it can be parsed
  26476. tag = {
  26477. data: new Uint8Array(tagSize),
  26478. frames: [],
  26479. pts: buffer[0].pts,
  26480. dts: buffer[0].dts
  26481. };
  26482. for (i = 0; i < tagSize;) {
  26483. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  26484. i += buffer[0].data.byteLength;
  26485. bufferSize -= buffer[0].data.byteLength;
  26486. buffer.shift();
  26487. } // find the start of the first frame and the end of the tag
  26488. frameStart = 10;
  26489. if (tag.data[5] & 0x40) {
  26490. // advance the frame start past the extended header
  26491. frameStart += 4; // header size field
  26492. frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  26493. tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
  26494. } // parse one or more ID3 frames
  26495. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  26496. do {
  26497. // determine the number of bytes in this frame
  26498. frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  26499. if (frameSize < 1) {
  26500. // eslint-disable-next-line no-console
  26501. return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
  26502. }
  26503. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  26504. frame = {
  26505. id: frameHeader,
  26506. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  26507. };
  26508. frame.key = frame.id;
  26509. if (tagParsers[frame.id]) {
  26510. tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
  26511. // time for raw AAC data
  26512. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  26513. var d = frame.data,
  26514. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  26515. size *= 4;
  26516. size += d[7] & 0x03;
  26517. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  26518. // on the value of this frame
  26519. // we couldn't have known the appropriate pts and dts before
  26520. // parsing this ID3 tag so set those values now
  26521. if (tag.pts === undefined && tag.dts === undefined) {
  26522. tag.pts = frame.timeStamp;
  26523. tag.dts = frame.timeStamp;
  26524. }
  26525. this.trigger('timestamp', frame);
  26526. }
  26527. }
  26528. tag.frames.push(frame);
  26529. frameStart += 10; // advance past the frame header
  26530. frameStart += frameSize; // advance past the frame body
  26531. } while (frameStart < tagSize);
  26532. this.trigger('data', tag);
  26533. };
  26534. };
  26535. _MetadataStream.prototype = new stream();
  26536. var metadataStream = _MetadataStream;
  26537. var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
  26538. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  26539. var MP2T_PACKET_LENGTH = 188,
  26540. // bytes
  26541. SYNC_BYTE = 0x47;
  26542. /**
  26543. * Splits an incoming stream of binary data into MPEG-2 Transport
  26544. * Stream packets.
  26545. */
  26546. _TransportPacketStream = function TransportPacketStream() {
  26547. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  26548. bytesInBuffer = 0;
  26549. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  26550. /**
  26551. * Split a stream of data into M2TS packets
  26552. **/
  26553. this.push = function (bytes) {
  26554. var startIndex = 0,
  26555. endIndex = MP2T_PACKET_LENGTH,
  26556. everything; // If there are bytes remaining from the last segment, prepend them to the
  26557. // bytes that were pushed in
  26558. if (bytesInBuffer) {
  26559. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  26560. everything.set(buffer.subarray(0, bytesInBuffer));
  26561. everything.set(bytes, bytesInBuffer);
  26562. bytesInBuffer = 0;
  26563. } else {
  26564. everything = bytes;
  26565. } // While we have enough data for a packet
  26566. while (endIndex < everything.byteLength) {
  26567. // Look for a pair of start and end sync bytes in the data..
  26568. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  26569. // We found a packet so emit it and jump one whole packet forward in
  26570. // the stream
  26571. this.trigger('data', everything.subarray(startIndex, endIndex));
  26572. startIndex += MP2T_PACKET_LENGTH;
  26573. endIndex += MP2T_PACKET_LENGTH;
  26574. continue;
  26575. } // If we get here, we have somehow become de-synchronized and we need to step
  26576. // forward one byte at a time until we find a pair of sync bytes that denote
  26577. // a packet
  26578. startIndex++;
  26579. endIndex++;
  26580. } // If there was some data left over at the end of the segment that couldn't
  26581. // possibly be a whole packet, keep it because it might be the start of a packet
  26582. // that continues in the next segment
  26583. if (startIndex < everything.byteLength) {
  26584. buffer.set(everything.subarray(startIndex), 0);
  26585. bytesInBuffer = everything.byteLength - startIndex;
  26586. }
  26587. };
  26588. /**
  26589. * Passes identified M2TS packets to the TransportParseStream to be parsed
  26590. **/
  26591. this.flush = function () {
  26592. // If the buffer contains a whole packet when we are being flushed, emit it
  26593. // and empty the buffer. Otherwise hold onto the data because it may be
  26594. // important for decoding the next segment
  26595. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  26596. this.trigger('data', buffer);
  26597. bytesInBuffer = 0;
  26598. }
  26599. this.trigger('done');
  26600. };
  26601. };
  26602. _TransportPacketStream.prototype = new stream();
  26603. /**
  26604. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  26605. * forms of the individual transport stream packets.
  26606. */
  26607. _TransportParseStream = function TransportParseStream() {
  26608. var parsePsi, parsePat, parsePmt, self;
  26609. _TransportParseStream.prototype.init.call(this);
  26610. self = this;
  26611. this.packetsWaitingForPmt = [];
  26612. this.programMapTable = undefined;
  26613. parsePsi = function parsePsi(payload, psi) {
  26614. var offset = 0; // PSI packets may be split into multiple sections and those
  26615. // sections may be split into multiple packets. If a PSI
  26616. // section starts in this packet, the payload_unit_start_indicator
  26617. // will be true and the first byte of the payload will indicate
  26618. // the offset from the current position to the start of the
  26619. // section.
  26620. if (psi.payloadUnitStartIndicator) {
  26621. offset += payload[offset] + 1;
  26622. }
  26623. if (psi.type === 'pat') {
  26624. parsePat(payload.subarray(offset), psi);
  26625. } else {
  26626. parsePmt(payload.subarray(offset), psi);
  26627. }
  26628. };
  26629. parsePat = function parsePat(payload, pat) {
  26630. pat.section_number = payload[7]; // eslint-disable-line camelcase
  26631. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  26632. // skip the PSI header and parse the first PMT entry
  26633. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  26634. pat.pmtPid = self.pmtPid;
  26635. };
  26636. /**
  26637. * Parse out the relevant fields of a Program Map Table (PMT).
  26638. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  26639. * packet. The first byte in this array should be the table_id
  26640. * field.
  26641. * @param pmt {object} the object that should be decorated with
  26642. * fields parsed from the PMT.
  26643. */
  26644. parsePmt = function parsePmt(payload, pmt) {
  26645. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  26646. // take effect. We don't believe this should ever be the case
  26647. // for HLS but we'll ignore "forward" PMT declarations if we see
  26648. // them. Future PMT declarations have the current_next_indicator
  26649. // set to zero.
  26650. if (!(payload[5] & 0x01)) {
  26651. return;
  26652. } // overwrite any existing program map table
  26653. self.programMapTable = {
  26654. video: null,
  26655. audio: null,
  26656. 'timed-metadata': {}
  26657. }; // the mapping table ends at the end of the current section
  26658. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  26659. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  26660. // long the program info descriptors are
  26661. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  26662. offset = 12 + programInfoLength;
  26663. while (offset < tableEnd) {
  26664. var streamType = payload[offset];
  26665. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  26666. // TODO: should this be done for metadata too? for now maintain behavior of
  26667. // multiple metadata streams
  26668. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  26669. self.programMapTable.video = pid;
  26670. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  26671. self.programMapTable.audio = pid;
  26672. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  26673. // map pid to stream type for metadata streams
  26674. self.programMapTable['timed-metadata'][pid] = streamType;
  26675. } // move to the next table entry
  26676. // skip past the elementary stream descriptors, if present
  26677. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  26678. } // record the map on the packet as well
  26679. pmt.programMapTable = self.programMapTable;
  26680. };
  26681. /**
  26682. * Deliver a new MP2T packet to the next stream in the pipeline.
  26683. */
  26684. this.push = function (packet) {
  26685. var result = {},
  26686. offset = 4;
  26687. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  26688. result.pid = packet[1] & 0x1f;
  26689. result.pid <<= 8;
  26690. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  26691. // fifth byte of the TS packet header. The adaptation field is
  26692. // used to add stuffing to PES packets that don't fill a complete
  26693. // TS packet, and to specify some forms of timing and control data
  26694. // that we do not currently use.
  26695. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  26696. offset += packet[offset] + 1;
  26697. } // parse the rest of the packet based on the type
  26698. if (result.pid === 0) {
  26699. result.type = 'pat';
  26700. parsePsi(packet.subarray(offset), result);
  26701. this.trigger('data', result);
  26702. } else if (result.pid === this.pmtPid) {
  26703. result.type = 'pmt';
  26704. parsePsi(packet.subarray(offset), result);
  26705. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  26706. while (this.packetsWaitingForPmt.length) {
  26707. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  26708. }
  26709. } else if (this.programMapTable === undefined) {
  26710. // When we have not seen a PMT yet, defer further processing of
  26711. // PES packets until one has been parsed
  26712. this.packetsWaitingForPmt.push([packet, offset, result]);
  26713. } else {
  26714. this.processPes_(packet, offset, result);
  26715. }
  26716. };
  26717. this.processPes_ = function (packet, offset, result) {
  26718. // set the appropriate stream type
  26719. if (result.pid === this.programMapTable.video) {
  26720. result.streamType = streamTypes.H264_STREAM_TYPE;
  26721. } else if (result.pid === this.programMapTable.audio) {
  26722. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  26723. } else {
  26724. // if not video or audio, it is timed-metadata or unknown
  26725. // if unknown, streamType will be undefined
  26726. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  26727. }
  26728. result.type = 'pes';
  26729. result.data = packet.subarray(offset);
  26730. this.trigger('data', result);
  26731. };
  26732. };
  26733. _TransportParseStream.prototype = new stream();
  26734. _TransportParseStream.STREAM_TYPES = {
  26735. h264: 0x1b,
  26736. adts: 0x0f
  26737. };
  26738. /**
  26739. * Reconsistutes program elementary stream (PES) packets from parsed
  26740. * transport stream packets. That is, if you pipe an
  26741. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  26742. * events will be events which capture the bytes for individual PES
  26743. * packets plus relevant metadata that has been extracted from the
  26744. * container.
  26745. */
  26746. _ElementaryStream = function ElementaryStream() {
  26747. var self = this,
  26748. // PES packet fragments
  26749. video = {
  26750. data: [],
  26751. size: 0
  26752. },
  26753. audio = {
  26754. data: [],
  26755. size: 0
  26756. },
  26757. timedMetadata = {
  26758. data: [],
  26759. size: 0
  26760. },
  26761. parsePes = function parsePes(payload, pes) {
  26762. var ptsDtsFlags; // get the packet length, this will be 0 for video
  26763. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  26764. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  26765. // and a DTS value. Determine what combination of values is
  26766. // available to work with.
  26767. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  26768. // performs all bitwise operations on 32-bit integers but javascript
  26769. // supports a much greater range (52-bits) of integer using standard
  26770. // mathematical operations.
  26771. // We construct a 31-bit value using bitwise operators over the 31
  26772. // most significant bits and then multiply by 4 (equal to a left-shift
  26773. // of 2) before we add the final 2 least significant bits of the
  26774. // timestamp (equal to an OR.)
  26775. if (ptsDtsFlags & 0xC0) {
  26776. // the PTS and DTS are not written out directly. For information
  26777. // on how they are encoded, see
  26778. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  26779. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  26780. pes.pts *= 4; // Left shift by 2
  26781. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  26782. pes.dts = pes.pts;
  26783. if (ptsDtsFlags & 0x40) {
  26784. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  26785. pes.dts *= 4; // Left shift by 2
  26786. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  26787. }
  26788. } // the data section starts immediately after the PES header.
  26789. // pes_header_data_length specifies the number of header bytes
  26790. // that follow the last byte of the field.
  26791. pes.data = payload.subarray(9 + payload[8]);
  26792. },
  26793. /**
  26794. * Pass completely parsed PES packets to the next stream in the pipeline
  26795. **/
  26796. flushStream = function flushStream(stream$$1, type, forceFlush) {
  26797. var packetData = new Uint8Array(stream$$1.size),
  26798. event = {
  26799. type: type
  26800. },
  26801. i = 0,
  26802. offset = 0,
  26803. packetFlushable = false,
  26804. fragment; // do nothing if there is not enough buffered data for a complete
  26805. // PES header
  26806. if (!stream$$1.data.length || stream$$1.size < 9) {
  26807. return;
  26808. }
  26809. event.trackId = stream$$1.data[0].pid; // reassemble the packet
  26810. for (i = 0; i < stream$$1.data.length; i++) {
  26811. fragment = stream$$1.data[i];
  26812. packetData.set(fragment.data, offset);
  26813. offset += fragment.data.byteLength;
  26814. } // parse assembled packet's PES header
  26815. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  26816. // check that there is enough stream data to fill the packet
  26817. packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
  26818. if (forceFlush || packetFlushable) {
  26819. stream$$1.size = 0;
  26820. stream$$1.data.length = 0;
  26821. } // only emit packets that are complete. this is to avoid assembling
  26822. // incomplete PES packets due to poor segmentation
  26823. if (packetFlushable) {
  26824. self.trigger('data', event);
  26825. }
  26826. };
  26827. _ElementaryStream.prototype.init.call(this);
  26828. /**
  26829. * Identifies M2TS packet types and parses PES packets using metadata
  26830. * parsed from the PMT
  26831. **/
  26832. this.push = function (data) {
  26833. ({
  26834. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  26835. // have any meaningful metadata
  26836. },
  26837. pes: function pes() {
  26838. var stream$$1, streamType;
  26839. switch (data.streamType) {
  26840. case streamTypes.H264_STREAM_TYPE:
  26841. case streamTypes.H264_STREAM_TYPE:
  26842. stream$$1 = video;
  26843. streamType = 'video';
  26844. break;
  26845. case streamTypes.ADTS_STREAM_TYPE:
  26846. stream$$1 = audio;
  26847. streamType = 'audio';
  26848. break;
  26849. case streamTypes.METADATA_STREAM_TYPE:
  26850. stream$$1 = timedMetadata;
  26851. streamType = 'timed-metadata';
  26852. break;
  26853. default:
  26854. // ignore unknown stream types
  26855. return;
  26856. } // if a new packet is starting, we can flush the completed
  26857. // packet
  26858. if (data.payloadUnitStartIndicator) {
  26859. flushStream(stream$$1, streamType, true);
  26860. } // buffer this fragment until we are sure we've received the
  26861. // complete payload
  26862. stream$$1.data.push(data);
  26863. stream$$1.size += data.data.byteLength;
  26864. },
  26865. pmt: function pmt() {
  26866. var event = {
  26867. type: 'metadata',
  26868. tracks: []
  26869. },
  26870. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  26871. if (programMapTable.video !== null) {
  26872. event.tracks.push({
  26873. timelineStartInfo: {
  26874. baseMediaDecodeTime: 0
  26875. },
  26876. id: +programMapTable.video,
  26877. codec: 'avc',
  26878. type: 'video'
  26879. });
  26880. }
  26881. if (programMapTable.audio !== null) {
  26882. event.tracks.push({
  26883. timelineStartInfo: {
  26884. baseMediaDecodeTime: 0
  26885. },
  26886. id: +programMapTable.audio,
  26887. codec: 'adts',
  26888. type: 'audio'
  26889. });
  26890. }
  26891. self.trigger('data', event);
  26892. }
  26893. })[data.type]();
  26894. };
  26895. /**
  26896. * Flush any remaining input. Video PES packets may be of variable
  26897. * length. Normally, the start of a new video packet can trigger the
  26898. * finalization of the previous packet. That is not possible if no
  26899. * more video is forthcoming, however. In that case, some other
  26900. * mechanism (like the end of the file) has to be employed. When it is
  26901. * clear that no additional data is forthcoming, calling this method
  26902. * will flush the buffered packets.
  26903. */
  26904. this.flush = function () {
  26905. // !!THIS ORDER IS IMPORTANT!!
  26906. // video first then audio
  26907. flushStream(video, 'video');
  26908. flushStream(audio, 'audio');
  26909. flushStream(timedMetadata, 'timed-metadata');
  26910. this.trigger('done');
  26911. };
  26912. };
  26913. _ElementaryStream.prototype = new stream();
  26914. var m2ts = {
  26915. PAT_PID: 0x0000,
  26916. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  26917. TransportPacketStream: _TransportPacketStream,
  26918. TransportParseStream: _TransportParseStream,
  26919. ElementaryStream: _ElementaryStream,
  26920. TimestampRolloverStream: TimestampRolloverStream$1,
  26921. CaptionStream: captionStream.CaptionStream,
  26922. Cea608Stream: captionStream.Cea608Stream,
  26923. MetadataStream: metadataStream
  26924. };
  26925. for (var type in streamTypes) {
  26926. if (streamTypes.hasOwnProperty(type)) {
  26927. m2ts[type] = streamTypes[type];
  26928. }
  26929. }
  26930. var m2ts_1 = m2ts;
  26931. var _AdtsStream;
  26932. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  26933. /*
  26934. * Accepts a ElementaryStream and emits data events with parsed
  26935. * AAC Audio Frames of the individual packets. Input audio in ADTS
  26936. * format is unpacked and re-emitted as AAC frames.
  26937. *
  26938. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  26939. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  26940. */
  26941. _AdtsStream = function AdtsStream() {
  26942. var buffer;
  26943. _AdtsStream.prototype.init.call(this);
  26944. this.push = function (packet) {
  26945. var i = 0,
  26946. frameNum = 0,
  26947. frameLength,
  26948. protectionSkipBytes,
  26949. frameEnd,
  26950. oldBuffer,
  26951. sampleCount,
  26952. adtsFrameDuration;
  26953. if (packet.type !== 'audio') {
  26954. // ignore non-audio data
  26955. return;
  26956. } // Prepend any data in the buffer to the input data so that we can parse
  26957. // aac frames the cross a PES packet boundary
  26958. if (buffer) {
  26959. oldBuffer = buffer;
  26960. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  26961. buffer.set(oldBuffer);
  26962. buffer.set(packet.data, oldBuffer.byteLength);
  26963. } else {
  26964. buffer = packet.data;
  26965. } // unpack any ADTS frames which have been fully received
  26966. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  26967. while (i + 5 < buffer.length) {
  26968. // Loook for the start of an ADTS header..
  26969. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  26970. // If a valid header was not found, jump one forward and attempt to
  26971. // find a valid ADTS header starting at the next byte
  26972. i++;
  26973. continue;
  26974. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  26975. // end of the ADTS header
  26976. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  26977. // end of the sync sequence
  26978. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  26979. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  26980. adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
  26981. frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
  26982. // and wait for more data
  26983. if (buffer.byteLength < frameEnd) {
  26984. return;
  26985. } // Otherwise, deliver the complete AAC frame
  26986. this.trigger('data', {
  26987. pts: packet.pts + frameNum * adtsFrameDuration,
  26988. dts: packet.dts + frameNum * adtsFrameDuration,
  26989. sampleCount: sampleCount,
  26990. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  26991. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  26992. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  26993. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  26994. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  26995. samplesize: 16,
  26996. data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
  26997. }); // If the buffer is empty, clear it and return
  26998. if (buffer.byteLength === frameEnd) {
  26999. buffer = undefined;
  27000. return;
  27001. }
  27002. frameNum++; // Remove the finished frame from the buffer and start the process again
  27003. buffer = buffer.subarray(frameEnd);
  27004. }
  27005. };
  27006. this.flush = function () {
  27007. this.trigger('done');
  27008. };
  27009. };
  27010. _AdtsStream.prototype = new stream();
  27011. var adts = _AdtsStream;
  27012. var ExpGolomb;
  27013. /**
  27014. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  27015. * scheme used by h264.
  27016. */
  27017. ExpGolomb = function ExpGolomb(workingData) {
  27018. var // the number of bytes left to examine in workingData
  27019. workingBytesAvailable = workingData.byteLength,
  27020. // the current word being examined
  27021. workingWord = 0,
  27022. // :uint
  27023. // the number of bits left to examine in the current word
  27024. workingBitsAvailable = 0; // :uint;
  27025. // ():uint
  27026. this.length = function () {
  27027. return 8 * workingBytesAvailable;
  27028. }; // ():uint
  27029. this.bitsAvailable = function () {
  27030. return 8 * workingBytesAvailable + workingBitsAvailable;
  27031. }; // ():void
  27032. this.loadWord = function () {
  27033. var position = workingData.byteLength - workingBytesAvailable,
  27034. workingBytes = new Uint8Array(4),
  27035. availableBytes = Math.min(4, workingBytesAvailable);
  27036. if (availableBytes === 0) {
  27037. throw new Error('no bytes available');
  27038. }
  27039. workingBytes.set(workingData.subarray(position, position + availableBytes));
  27040. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  27041. workingBitsAvailable = availableBytes * 8;
  27042. workingBytesAvailable -= availableBytes;
  27043. }; // (count:int):void
  27044. this.skipBits = function (count) {
  27045. var skipBytes; // :int
  27046. if (workingBitsAvailable > count) {
  27047. workingWord <<= count;
  27048. workingBitsAvailable -= count;
  27049. } else {
  27050. count -= workingBitsAvailable;
  27051. skipBytes = Math.floor(count / 8);
  27052. count -= skipBytes * 8;
  27053. workingBytesAvailable -= skipBytes;
  27054. this.loadWord();
  27055. workingWord <<= count;
  27056. workingBitsAvailable -= count;
  27057. }
  27058. }; // (size:int):uint
  27059. this.readBits = function (size) {
  27060. var bits = Math.min(workingBitsAvailable, size),
  27061. // :uint
  27062. valu = workingWord >>> 32 - bits; // :uint
  27063. // if size > 31, handle error
  27064. workingBitsAvailable -= bits;
  27065. if (workingBitsAvailable > 0) {
  27066. workingWord <<= bits;
  27067. } else if (workingBytesAvailable > 0) {
  27068. this.loadWord();
  27069. }
  27070. bits = size - bits;
  27071. if (bits > 0) {
  27072. return valu << bits | this.readBits(bits);
  27073. }
  27074. return valu;
  27075. }; // ():uint
  27076. this.skipLeadingZeros = function () {
  27077. var leadingZeroCount; // :uint
  27078. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  27079. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  27080. // the first bit of working word is 1
  27081. workingWord <<= leadingZeroCount;
  27082. workingBitsAvailable -= leadingZeroCount;
  27083. return leadingZeroCount;
  27084. }
  27085. } // we exhausted workingWord and still have not found a 1
  27086. this.loadWord();
  27087. return leadingZeroCount + this.skipLeadingZeros();
  27088. }; // ():void
  27089. this.skipUnsignedExpGolomb = function () {
  27090. this.skipBits(1 + this.skipLeadingZeros());
  27091. }; // ():void
  27092. this.skipExpGolomb = function () {
  27093. this.skipBits(1 + this.skipLeadingZeros());
  27094. }; // ():uint
  27095. this.readUnsignedExpGolomb = function () {
  27096. var clz = this.skipLeadingZeros(); // :uint
  27097. return this.readBits(clz + 1) - 1;
  27098. }; // ():int
  27099. this.readExpGolomb = function () {
  27100. var valu = this.readUnsignedExpGolomb(); // :int
  27101. if (0x01 & valu) {
  27102. // the number is odd if the low order bit is set
  27103. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  27104. }
  27105. return -1 * (valu >>> 1); // divide by two then make it negative
  27106. }; // Some convenience functions
  27107. // :Boolean
  27108. this.readBoolean = function () {
  27109. return this.readBits(1) === 1;
  27110. }; // ():int
  27111. this.readUnsignedByte = function () {
  27112. return this.readBits(8);
  27113. };
  27114. this.loadWord();
  27115. };
  27116. var expGolomb = ExpGolomb;
  27117. var _H264Stream, _NalByteStream;
  27118. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  27119. /**
  27120. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  27121. */
  27122. _NalByteStream = function NalByteStream() {
  27123. var syncPoint = 0,
  27124. i,
  27125. buffer;
  27126. _NalByteStream.prototype.init.call(this);
  27127. /*
  27128. * Scans a byte stream and triggers a data event with the NAL units found.
  27129. * @param {Object} data Event received from H264Stream
  27130. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  27131. *
  27132. * @see H264Stream.push
  27133. */
  27134. this.push = function (data) {
  27135. var swapBuffer;
  27136. if (!buffer) {
  27137. buffer = data.data;
  27138. } else {
  27139. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  27140. swapBuffer.set(buffer);
  27141. swapBuffer.set(data.data, buffer.byteLength);
  27142. buffer = swapBuffer;
  27143. } // Rec. ITU-T H.264, Annex B
  27144. // scan for NAL unit boundaries
  27145. // a match looks like this:
  27146. // 0 0 1 .. NAL .. 0 0 1
  27147. // ^ sync point ^ i
  27148. // or this:
  27149. // 0 0 1 .. NAL .. 0 0 0
  27150. // ^ sync point ^ i
  27151. // advance the sync point to a NAL start, if necessary
  27152. for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
  27153. if (buffer[syncPoint + 2] === 1) {
  27154. // the sync point is properly aligned
  27155. i = syncPoint + 5;
  27156. break;
  27157. }
  27158. }
  27159. while (i < buffer.byteLength) {
  27160. // look at the current byte to determine if we've hit the end of
  27161. // a NAL unit boundary
  27162. switch (buffer[i]) {
  27163. case 0:
  27164. // skip past non-sync sequences
  27165. if (buffer[i - 1] !== 0) {
  27166. i += 2;
  27167. break;
  27168. } else if (buffer[i - 2] !== 0) {
  27169. i++;
  27170. break;
  27171. } // deliver the NAL unit if it isn't empty
  27172. if (syncPoint + 3 !== i - 2) {
  27173. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  27174. } // drop trailing zeroes
  27175. do {
  27176. i++;
  27177. } while (buffer[i] !== 1 && i < buffer.length);
  27178. syncPoint = i - 2;
  27179. i += 3;
  27180. break;
  27181. case 1:
  27182. // skip past non-sync sequences
  27183. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  27184. i += 3;
  27185. break;
  27186. } // deliver the NAL unit
  27187. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  27188. syncPoint = i - 2;
  27189. i += 3;
  27190. break;
  27191. default:
  27192. // the current byte isn't a one or zero, so it cannot be part
  27193. // of a sync sequence
  27194. i += 3;
  27195. break;
  27196. }
  27197. } // filter out the NAL units that were delivered
  27198. buffer = buffer.subarray(syncPoint);
  27199. i -= syncPoint;
  27200. syncPoint = 0;
  27201. };
  27202. this.flush = function () {
  27203. // deliver the last buffered NAL unit
  27204. if (buffer && buffer.byteLength > 3) {
  27205. this.trigger('data', buffer.subarray(syncPoint + 3));
  27206. } // reset the stream state
  27207. buffer = null;
  27208. syncPoint = 0;
  27209. this.trigger('done');
  27210. };
  27211. };
  27212. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  27213. // see Recommendation ITU-T H.264 (4/2013),
  27214. // 7.3.2.1.1 Sequence parameter set data syntax
  27215. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  27216. 100: true,
  27217. 110: true,
  27218. 122: true,
  27219. 244: true,
  27220. 44: true,
  27221. 83: true,
  27222. 86: true,
  27223. 118: true,
  27224. 128: true,
  27225. 138: true,
  27226. 139: true,
  27227. 134: true
  27228. };
  27229. /**
  27230. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  27231. * events.
  27232. */
  27233. _H264Stream = function H264Stream() {
  27234. var nalByteStream = new _NalByteStream(),
  27235. self,
  27236. trackId,
  27237. currentPts,
  27238. currentDts,
  27239. discardEmulationPreventionBytes,
  27240. readSequenceParameterSet,
  27241. skipScalingList;
  27242. _H264Stream.prototype.init.call(this);
  27243. self = this;
  27244. /*
  27245. * Pushes a packet from a stream onto the NalByteStream
  27246. *
  27247. * @param {Object} packet - A packet received from a stream
  27248. * @param {Uint8Array} packet.data - The raw bytes of the packet
  27249. * @param {Number} packet.dts - Decode timestamp of the packet
  27250. * @param {Number} packet.pts - Presentation timestamp of the packet
  27251. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  27252. * @param {('video'|'audio')} packet.type - The type of packet
  27253. *
  27254. */
  27255. this.push = function (packet) {
  27256. if (packet.type !== 'video') {
  27257. return;
  27258. }
  27259. trackId = packet.trackId;
  27260. currentPts = packet.pts;
  27261. currentDts = packet.dts;
  27262. nalByteStream.push(packet);
  27263. };
  27264. /*
  27265. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  27266. * for the NALUs to the next stream component.
  27267. * Also, preprocess caption and sequence parameter NALUs.
  27268. *
  27269. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  27270. * @see NalByteStream.push
  27271. */
  27272. nalByteStream.on('data', function (data) {
  27273. var event = {
  27274. trackId: trackId,
  27275. pts: currentPts,
  27276. dts: currentDts,
  27277. data: data
  27278. };
  27279. switch (data[0] & 0x1f) {
  27280. case 0x05:
  27281. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  27282. break;
  27283. case 0x06:
  27284. event.nalUnitType = 'sei_rbsp';
  27285. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  27286. break;
  27287. case 0x07:
  27288. event.nalUnitType = 'seq_parameter_set_rbsp';
  27289. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  27290. event.config = readSequenceParameterSet(event.escapedRBSP);
  27291. break;
  27292. case 0x08:
  27293. event.nalUnitType = 'pic_parameter_set_rbsp';
  27294. break;
  27295. case 0x09:
  27296. event.nalUnitType = 'access_unit_delimiter_rbsp';
  27297. break;
  27298. default:
  27299. break;
  27300. } // This triggers data on the H264Stream
  27301. self.trigger('data', event);
  27302. });
  27303. nalByteStream.on('done', function () {
  27304. self.trigger('done');
  27305. });
  27306. this.flush = function () {
  27307. nalByteStream.flush();
  27308. };
  27309. /**
  27310. * Advance the ExpGolomb decoder past a scaling list. The scaling
  27311. * list is optionally transmitted as part of a sequence parameter
  27312. * set and is not relevant to transmuxing.
  27313. * @param count {number} the number of entries in this scaling list
  27314. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  27315. * start of a scaling list
  27316. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  27317. */
  27318. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  27319. var lastScale = 8,
  27320. nextScale = 8,
  27321. j,
  27322. deltaScale;
  27323. for (j = 0; j < count; j++) {
  27324. if (nextScale !== 0) {
  27325. deltaScale = expGolombDecoder.readExpGolomb();
  27326. nextScale = (lastScale + deltaScale + 256) % 256;
  27327. }
  27328. lastScale = nextScale === 0 ? lastScale : nextScale;
  27329. }
  27330. };
  27331. /**
  27332. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  27333. * Sequence Payload"
  27334. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  27335. * unit
  27336. * @return {Uint8Array} the RBSP without any Emulation
  27337. * Prevention Bytes
  27338. */
  27339. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  27340. var length = data.byteLength,
  27341. emulationPreventionBytesPositions = [],
  27342. i = 1,
  27343. newLength,
  27344. newData; // Find all `Emulation Prevention Bytes`
  27345. while (i < length - 2) {
  27346. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  27347. emulationPreventionBytesPositions.push(i + 2);
  27348. i += 2;
  27349. } else {
  27350. i++;
  27351. }
  27352. } // If no Emulation Prevention Bytes were found just return the original
  27353. // array
  27354. if (emulationPreventionBytesPositions.length === 0) {
  27355. return data;
  27356. } // Create a new array to hold the NAL unit data
  27357. newLength = length - emulationPreventionBytesPositions.length;
  27358. newData = new Uint8Array(newLength);
  27359. var sourceIndex = 0;
  27360. for (i = 0; i < newLength; sourceIndex++, i++) {
  27361. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  27362. // Skip this byte
  27363. sourceIndex++; // Remove this position index
  27364. emulationPreventionBytesPositions.shift();
  27365. }
  27366. newData[i] = data[sourceIndex];
  27367. }
  27368. return newData;
  27369. };
  27370. /**
  27371. * Read a sequence parameter set and return some interesting video
  27372. * properties. A sequence parameter set is the H264 metadata that
  27373. * describes the properties of upcoming video frames.
  27374. * @param data {Uint8Array} the bytes of a sequence parameter set
  27375. * @return {object} an object with configuration parsed from the
  27376. * sequence parameter set, including the dimensions of the
  27377. * associated video frames.
  27378. */
  27379. readSequenceParameterSet = function readSequenceParameterSet(data) {
  27380. var frameCropLeftOffset = 0,
  27381. frameCropRightOffset = 0,
  27382. frameCropTopOffset = 0,
  27383. frameCropBottomOffset = 0,
  27384. sarScale = 1,
  27385. expGolombDecoder,
  27386. profileIdc,
  27387. levelIdc,
  27388. profileCompatibility,
  27389. chromaFormatIdc,
  27390. picOrderCntType,
  27391. numRefFramesInPicOrderCntCycle,
  27392. picWidthInMbsMinus1,
  27393. picHeightInMapUnitsMinus1,
  27394. frameMbsOnlyFlag,
  27395. scalingListCount,
  27396. sarRatio,
  27397. aspectRatioIdc,
  27398. i;
  27399. expGolombDecoder = new expGolomb(data);
  27400. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  27401. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  27402. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  27403. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  27404. // some profiles have more optional data we don't need
  27405. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  27406. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  27407. if (chromaFormatIdc === 3) {
  27408. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  27409. }
  27410. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  27411. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  27412. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  27413. if (expGolombDecoder.readBoolean()) {
  27414. // seq_scaling_matrix_present_flag
  27415. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  27416. for (i = 0; i < scalingListCount; i++) {
  27417. if (expGolombDecoder.readBoolean()) {
  27418. // seq_scaling_list_present_flag[ i ]
  27419. if (i < 6) {
  27420. skipScalingList(16, expGolombDecoder);
  27421. } else {
  27422. skipScalingList(64, expGolombDecoder);
  27423. }
  27424. }
  27425. }
  27426. }
  27427. }
  27428. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  27429. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  27430. if (picOrderCntType === 0) {
  27431. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  27432. } else if (picOrderCntType === 1) {
  27433. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  27434. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  27435. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  27436. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  27437. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  27438. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  27439. }
  27440. }
  27441. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  27442. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  27443. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  27444. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  27445. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  27446. if (frameMbsOnlyFlag === 0) {
  27447. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  27448. }
  27449. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  27450. if (expGolombDecoder.readBoolean()) {
  27451. // frame_cropping_flag
  27452. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  27453. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  27454. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  27455. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  27456. }
  27457. if (expGolombDecoder.readBoolean()) {
  27458. // vui_parameters_present_flag
  27459. if (expGolombDecoder.readBoolean()) {
  27460. // aspect_ratio_info_present_flag
  27461. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  27462. switch (aspectRatioIdc) {
  27463. case 1:
  27464. sarRatio = [1, 1];
  27465. break;
  27466. case 2:
  27467. sarRatio = [12, 11];
  27468. break;
  27469. case 3:
  27470. sarRatio = [10, 11];
  27471. break;
  27472. case 4:
  27473. sarRatio = [16, 11];
  27474. break;
  27475. case 5:
  27476. sarRatio = [40, 33];
  27477. break;
  27478. case 6:
  27479. sarRatio = [24, 11];
  27480. break;
  27481. case 7:
  27482. sarRatio = [20, 11];
  27483. break;
  27484. case 8:
  27485. sarRatio = [32, 11];
  27486. break;
  27487. case 9:
  27488. sarRatio = [80, 33];
  27489. break;
  27490. case 10:
  27491. sarRatio = [18, 11];
  27492. break;
  27493. case 11:
  27494. sarRatio = [15, 11];
  27495. break;
  27496. case 12:
  27497. sarRatio = [64, 33];
  27498. break;
  27499. case 13:
  27500. sarRatio = [160, 99];
  27501. break;
  27502. case 14:
  27503. sarRatio = [4, 3];
  27504. break;
  27505. case 15:
  27506. sarRatio = [3, 2];
  27507. break;
  27508. case 16:
  27509. sarRatio = [2, 1];
  27510. break;
  27511. case 255:
  27512. {
  27513. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  27514. break;
  27515. }
  27516. }
  27517. if (sarRatio) {
  27518. sarScale = sarRatio[0] / sarRatio[1];
  27519. }
  27520. }
  27521. }
  27522. return {
  27523. profileIdc: profileIdc,
  27524. levelIdc: levelIdc,
  27525. profileCompatibility: profileCompatibility,
  27526. width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
  27527. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
  27528. };
  27529. };
  27530. };
  27531. _H264Stream.prototype = new stream();
  27532. var h264 = {
  27533. H264Stream: _H264Stream,
  27534. NalByteStream: _NalByteStream
  27535. };
  27536. /**
  27537. * mux.js
  27538. *
  27539. * Copyright (c) 2016 Brightcove
  27540. * All rights reserved.
  27541. *
  27542. * Utilities to detect basic properties and metadata about Aac data.
  27543. */
  27544. var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  27545. var isLikelyAacData = function isLikelyAacData(data) {
  27546. if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
  27547. return true;
  27548. }
  27549. return false;
  27550. };
  27551. var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
  27552. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  27553. }; // return a percent-encoded representation of the specified byte range
  27554. // @see http://en.wikipedia.org/wiki/Percent-encoding
  27555. var percentEncode$1 = function percentEncode(bytes, start, end) {
  27556. var i,
  27557. result = '';
  27558. for (i = start; i < end; i++) {
  27559. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  27560. }
  27561. return result;
  27562. }; // return the string representation of the specified byte range,
  27563. // interpreted as ISO-8859-1.
  27564. var parseIso88591$1 = function parseIso88591(bytes, start, end) {
  27565. return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
  27566. };
  27567. var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
  27568. var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
  27569. flags = header[byteIndex + 5],
  27570. footerPresent = (flags & 16) >> 4;
  27571. if (footerPresent) {
  27572. return returnSize + 20;
  27573. }
  27574. return returnSize + 10;
  27575. };
  27576. var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
  27577. var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  27578. middle = header[byteIndex + 4] << 3,
  27579. highTwo = header[byteIndex + 3] & 0x3 << 11;
  27580. return highTwo | middle | lowThree;
  27581. };
  27582. var parseType$1 = function parseType(header, byteIndex) {
  27583. if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
  27584. return 'timed-metadata';
  27585. } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
  27586. return 'audio';
  27587. }
  27588. return null;
  27589. };
  27590. var parseSampleRate = function parseSampleRate(packet) {
  27591. var i = 0;
  27592. while (i + 5 < packet.length) {
  27593. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  27594. // If a valid header was not found, jump one forward and attempt to
  27595. // find a valid ADTS header starting at the next byte
  27596. i++;
  27597. continue;
  27598. }
  27599. return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
  27600. }
  27601. return null;
  27602. };
  27603. var parseAacTimestamp = function parseAacTimestamp(packet) {
  27604. var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
  27605. frameStart = 10;
  27606. if (packet[5] & 0x40) {
  27607. // advance the frame start past the extended header
  27608. frameStart += 4; // header size field
  27609. frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
  27610. } // parse one or more ID3 frames
  27611. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  27612. do {
  27613. // determine the number of bytes in this frame
  27614. frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
  27615. if (frameSize < 1) {
  27616. return null;
  27617. }
  27618. frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
  27619. if (frameHeader === 'PRIV') {
  27620. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  27621. for (var i = 0; i < frame.byteLength; i++) {
  27622. if (frame[i] === 0) {
  27623. var owner = parseIso88591$1(frame, 0, i);
  27624. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  27625. var d = frame.subarray(i + 1);
  27626. var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  27627. size *= 4;
  27628. size += d[7] & 0x03;
  27629. return size;
  27630. }
  27631. break;
  27632. }
  27633. }
  27634. }
  27635. frameStart += 10; // advance past the frame header
  27636. frameStart += frameSize; // advance past the frame body
  27637. } while (frameStart < packet.byteLength);
  27638. return null;
  27639. };
  27640. var utils = {
  27641. isLikelyAacData: isLikelyAacData,
  27642. parseId3TagSize: parseId3TagSize,
  27643. parseAdtsSize: parseAdtsSize,
  27644. parseType: parseType$1,
  27645. parseSampleRate: parseSampleRate,
  27646. parseAacTimestamp: parseAacTimestamp
  27647. }; // Constants
  27648. var _AacStream;
  27649. /**
  27650. * Splits an incoming stream of binary data into ADTS and ID3 Frames.
  27651. */
  27652. _AacStream = function AacStream() {
  27653. var everything = new Uint8Array(),
  27654. timeStamp = 0;
  27655. _AacStream.prototype.init.call(this);
  27656. this.setTimestamp = function (timestamp) {
  27657. timeStamp = timestamp;
  27658. };
  27659. this.push = function (bytes) {
  27660. var frameSize = 0,
  27661. byteIndex = 0,
  27662. bytesLeft,
  27663. chunk,
  27664. packet,
  27665. tempLength; // If there are bytes remaining from the last segment, prepend them to the
  27666. // bytes that were pushed in
  27667. if (everything.length) {
  27668. tempLength = everything.length;
  27669. everything = new Uint8Array(bytes.byteLength + tempLength);
  27670. everything.set(everything.subarray(0, tempLength));
  27671. everything.set(bytes, tempLength);
  27672. } else {
  27673. everything = bytes;
  27674. }
  27675. while (everything.length - byteIndex >= 3) {
  27676. if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
  27677. // Exit early because we don't have enough to parse
  27678. // the ID3 tag header
  27679. if (everything.length - byteIndex < 10) {
  27680. break;
  27681. } // check framesize
  27682. frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  27683. // to emit a full packet
  27684. // Add to byteIndex to support multiple ID3 tags in sequence
  27685. if (byteIndex + frameSize > everything.length) {
  27686. break;
  27687. }
  27688. chunk = {
  27689. type: 'timed-metadata',
  27690. data: everything.subarray(byteIndex, byteIndex + frameSize)
  27691. };
  27692. this.trigger('data', chunk);
  27693. byteIndex += frameSize;
  27694. continue;
  27695. } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
  27696. // Exit early because we don't have enough to parse
  27697. // the ADTS frame header
  27698. if (everything.length - byteIndex < 7) {
  27699. break;
  27700. }
  27701. frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  27702. // to emit a full packet
  27703. if (byteIndex + frameSize > everything.length) {
  27704. break;
  27705. }
  27706. packet = {
  27707. type: 'audio',
  27708. data: everything.subarray(byteIndex, byteIndex + frameSize),
  27709. pts: timeStamp,
  27710. dts: timeStamp
  27711. };
  27712. this.trigger('data', packet);
  27713. byteIndex += frameSize;
  27714. continue;
  27715. }
  27716. byteIndex++;
  27717. }
  27718. bytesLeft = everything.length - byteIndex;
  27719. if (bytesLeft > 0) {
  27720. everything = everything.subarray(byteIndex);
  27721. } else {
  27722. everything = new Uint8Array();
  27723. }
  27724. };
  27725. };
  27726. _AacStream.prototype = new stream();
  27727. var aac = _AacStream;
  27728. var H264Stream = h264.H264Stream;
  27729. var isLikelyAacData$1 = utils.isLikelyAacData; // constants
  27730. var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
  27731. var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
  27732. var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
  27733. /**
  27734. * Compare two arrays (even typed) for same-ness
  27735. */
  27736. var arrayEquals = function arrayEquals(a, b) {
  27737. var i;
  27738. if (a.length !== b.length) {
  27739. return false;
  27740. } // compare the value of each element in the array
  27741. for (i = 0; i < a.length; i++) {
  27742. if (a[i] !== b[i]) {
  27743. return false;
  27744. }
  27745. }
  27746. return true;
  27747. };
  27748. var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
  27749. var ptsOffsetFromDts = startPts - startDts,
  27750. decodeDuration = endDts - startDts,
  27751. presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
  27752. // however, the player time values will reflect a start from the baseMediaDecodeTime.
  27753. // In order to provide relevant values for the player times, base timing info on the
  27754. // baseMediaDecodeTime and the DTS and PTS durations of the segment.
  27755. return {
  27756. start: {
  27757. dts: baseMediaDecodeTime,
  27758. pts: baseMediaDecodeTime + ptsOffsetFromDts
  27759. },
  27760. end: {
  27761. dts: baseMediaDecodeTime + decodeDuration,
  27762. pts: baseMediaDecodeTime + presentationDuration
  27763. },
  27764. prependedContentDuration: prependedContentDuration,
  27765. baseMediaDecodeTime: baseMediaDecodeTime
  27766. };
  27767. };
  27768. /**
  27769. * Constructs a single-track, ISO BMFF media segment from AAC data
  27770. * events. The output of this stream can be fed to a SourceBuffer
  27771. * configured with a suitable initialization segment.
  27772. * @param track {object} track metadata configuration
  27773. * @param options {object} transmuxer options object
  27774. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  27775. * in the source; false to adjust the first segment to start at 0.
  27776. */
  27777. _AudioSegmentStream = function AudioSegmentStream(track, options) {
  27778. var adtsFrames = [],
  27779. sequenceNumber = 0,
  27780. earliestAllowedDts = 0,
  27781. audioAppendStartTs = 0,
  27782. videoBaseMediaDecodeTime = Infinity;
  27783. options = options || {};
  27784. _AudioSegmentStream.prototype.init.call(this);
  27785. this.push = function (data) {
  27786. trackDecodeInfo.collectDtsInfo(track, data);
  27787. if (track) {
  27788. AUDIO_PROPERTIES.forEach(function (prop) {
  27789. track[prop] = data[prop];
  27790. });
  27791. } // buffer audio data until end() is called
  27792. adtsFrames.push(data);
  27793. };
  27794. this.setEarliestDts = function (earliestDts) {
  27795. earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
  27796. };
  27797. this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  27798. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  27799. };
  27800. this.setAudioAppendStart = function (timestamp) {
  27801. audioAppendStartTs = timestamp;
  27802. };
  27803. this.flush = function () {
  27804. var frames, moof, mdat, boxes; // return early if no audio data has been observed
  27805. if (adtsFrames.length === 0) {
  27806. this.trigger('done', 'AudioSegmentStream');
  27807. return;
  27808. }
  27809. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
  27810. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  27811. audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
  27812. // samples (that is, adts frames) in the audio data
  27813. track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
  27814. mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
  27815. adtsFrames = [];
  27816. moof = mp4Generator.moof(sequenceNumber, [track]);
  27817. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
  27818. sequenceNumber++;
  27819. boxes.set(moof);
  27820. boxes.set(mdat, moof.byteLength);
  27821. trackDecodeInfo.clearDtsInfo(track);
  27822. this.trigger('data', {
  27823. track: track,
  27824. boxes: boxes
  27825. });
  27826. this.trigger('done', 'AudioSegmentStream');
  27827. };
  27828. };
  27829. _AudioSegmentStream.prototype = new stream();
  27830. /**
  27831. * Constructs a single-track, ISO BMFF media segment from H264 data
  27832. * events. The output of this stream can be fed to a SourceBuffer
  27833. * configured with a suitable initialization segment.
  27834. * @param track {object} track metadata configuration
  27835. * @param options {object} transmuxer options object
  27836. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  27837. * gopsToAlignWith list when attempting to align gop pts
  27838. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  27839. * in the source; false to adjust the first segment to start at 0.
  27840. */
  27841. _VideoSegmentStream = function VideoSegmentStream(track, options) {
  27842. var sequenceNumber = 0,
  27843. nalUnits = [],
  27844. gopsToAlignWith = [],
  27845. config,
  27846. pps;
  27847. options = options || {};
  27848. _VideoSegmentStream.prototype.init.call(this);
  27849. delete track.minPTS;
  27850. this.gopCache_ = [];
  27851. /**
  27852. * Constructs a ISO BMFF segment given H264 nalUnits
  27853. * @param {Object} nalUnit A data event representing a nalUnit
  27854. * @param {String} nalUnit.nalUnitType
  27855. * @param {Object} nalUnit.config Properties for a mp4 track
  27856. * @param {Uint8Array} nalUnit.data The nalUnit bytes
  27857. * @see lib/codecs/h264.js
  27858. **/
  27859. this.push = function (nalUnit) {
  27860. trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
  27861. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  27862. config = nalUnit.config;
  27863. track.sps = [nalUnit.data];
  27864. VIDEO_PROPERTIES.forEach(function (prop) {
  27865. track[prop] = config[prop];
  27866. }, this);
  27867. }
  27868. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  27869. pps = nalUnit.data;
  27870. track.pps = [nalUnit.data];
  27871. } // buffer video until flush() is called
  27872. nalUnits.push(nalUnit);
  27873. };
  27874. /**
  27875. * Pass constructed ISO BMFF track and boxes on to the
  27876. * next stream in the pipeline
  27877. **/
  27878. this.flush = function () {
  27879. var frames,
  27880. gopForFusion,
  27881. gops,
  27882. moof,
  27883. mdat,
  27884. boxes,
  27885. prependedContentDuration = 0,
  27886. firstGop,
  27887. lastGop; // Throw away nalUnits at the start of the byte stream until
  27888. // we find the first AUD
  27889. while (nalUnits.length) {
  27890. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  27891. break;
  27892. }
  27893. nalUnits.shift();
  27894. } // Return early if no video data has been observed
  27895. if (nalUnits.length === 0) {
  27896. this.resetStream_();
  27897. this.trigger('done', 'VideoSegmentStream');
  27898. return;
  27899. } // Organize the raw nal-units into arrays that represent
  27900. // higher-level constructs such as frames and gops
  27901. // (group-of-pictures)
  27902. frames = frameUtils.groupNalsIntoFrames(nalUnits);
  27903. gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
  27904. // a problem since MSE (on Chrome) requires a leading keyframe.
  27905. //
  27906. // We have two approaches to repairing this situation:
  27907. // 1) GOP-FUSION:
  27908. // This is where we keep track of the GOPS (group-of-pictures)
  27909. // from previous fragments and attempt to find one that we can
  27910. // prepend to the current fragment in order to create a valid
  27911. // fragment.
  27912. // 2) KEYFRAME-PULLING:
  27913. // Here we search for the first keyframe in the fragment and
  27914. // throw away all the frames between the start of the fragment
  27915. // and that keyframe. We then extend the duration and pull the
  27916. // PTS of the keyframe forward so that it covers the time range
  27917. // of the frames that were disposed of.
  27918. //
  27919. // #1 is far prefereable over #2 which can cause "stuttering" but
  27920. // requires more things to be just right.
  27921. if (!gops[0][0].keyFrame) {
  27922. // Search for a gop for fusion from our gopCache
  27923. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  27924. if (gopForFusion) {
  27925. // in order to provide more accurate timing information about the segment, save
  27926. // the number of seconds prepended to the original segment due to GOP fusion
  27927. prependedContentDuration = gopForFusion.duration;
  27928. gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
  27929. // new gop at the beginning
  27930. gops.byteLength += gopForFusion.byteLength;
  27931. gops.nalCount += gopForFusion.nalCount;
  27932. gops.pts = gopForFusion.pts;
  27933. gops.dts = gopForFusion.dts;
  27934. gops.duration += gopForFusion.duration;
  27935. } else {
  27936. // If we didn't find a candidate gop fall back to keyframe-pulling
  27937. gops = frameUtils.extendFirstKeyFrame(gops);
  27938. }
  27939. } // Trim gops to align with gopsToAlignWith
  27940. if (gopsToAlignWith.length) {
  27941. var alignedGops;
  27942. if (options.alignGopsAtEnd) {
  27943. alignedGops = this.alignGopsAtEnd_(gops);
  27944. } else {
  27945. alignedGops = this.alignGopsAtStart_(gops);
  27946. }
  27947. if (!alignedGops) {
  27948. // save all the nals in the last GOP into the gop cache
  27949. this.gopCache_.unshift({
  27950. gop: gops.pop(),
  27951. pps: track.pps,
  27952. sps: track.sps
  27953. }); // Keep a maximum of 6 GOPs in the cache
  27954. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  27955. nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
  27956. this.resetStream_();
  27957. this.trigger('done', 'VideoSegmentStream');
  27958. return;
  27959. } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  27960. // when recalculated before sending off to CoalesceStream
  27961. trackDecodeInfo.clearDtsInfo(track);
  27962. gops = alignedGops;
  27963. }
  27964. trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
  27965. // samples (that is, frames) in the video data
  27966. track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
  27967. mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
  27968. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  27969. this.trigger('processedGopsInfo', gops.map(function (gop) {
  27970. return {
  27971. pts: gop.pts,
  27972. dts: gop.dts,
  27973. byteLength: gop.byteLength
  27974. };
  27975. }));
  27976. firstGop = gops[0];
  27977. lastGop = gops[gops.length - 1];
  27978. this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); // save all the nals in the last GOP into the gop cache
  27979. this.gopCache_.unshift({
  27980. gop: gops.pop(),
  27981. pps: track.pps,
  27982. sps: track.sps
  27983. }); // Keep a maximum of 6 GOPs in the cache
  27984. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  27985. nalUnits = [];
  27986. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  27987. this.trigger('timelineStartInfo', track.timelineStartInfo);
  27988. moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
  27989. // throwing away hundreds of media segment fragments
  27990. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
  27991. sequenceNumber++;
  27992. boxes.set(moof);
  27993. boxes.set(mdat, moof.byteLength);
  27994. this.trigger('data', {
  27995. track: track,
  27996. boxes: boxes
  27997. });
  27998. this.resetStream_(); // Continue with the flush process now
  27999. this.trigger('done', 'VideoSegmentStream');
  28000. };
  28001. this.resetStream_ = function () {
  28002. trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
  28003. // for instance, when we are rendition switching
  28004. config = undefined;
  28005. pps = undefined;
  28006. }; // Search for a candidate Gop for gop-fusion from the gop cache and
  28007. // return it or return null if no good candidate was found
  28008. this.getGopForFusion_ = function (nalUnit) {
  28009. var halfSecond = 45000,
  28010. // Half-a-second in a 90khz clock
  28011. allowableOverlap = 10000,
  28012. // About 3 frames @ 30fps
  28013. nearestDistance = Infinity,
  28014. dtsDistance,
  28015. nearestGopObj,
  28016. currentGop,
  28017. currentGopObj,
  28018. i; // Search for the GOP nearest to the beginning of this nal unit
  28019. for (i = 0; i < this.gopCache_.length; i++) {
  28020. currentGopObj = this.gopCache_[i];
  28021. currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
  28022. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  28023. continue;
  28024. } // Reject Gops that would require a negative baseMediaDecodeTime
  28025. if (currentGop.dts < track.timelineStartInfo.dts) {
  28026. continue;
  28027. } // The distance between the end of the gop and the start of the nalUnit
  28028. dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
  28029. // a half-second of the nal unit
  28030. if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
  28031. // Always use the closest GOP we found if there is more than
  28032. // one candidate
  28033. if (!nearestGopObj || nearestDistance > dtsDistance) {
  28034. nearestGopObj = currentGopObj;
  28035. nearestDistance = dtsDistance;
  28036. }
  28037. }
  28038. }
  28039. if (nearestGopObj) {
  28040. return nearestGopObj.gop;
  28041. }
  28042. return null;
  28043. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  28044. // of gopsToAlignWith starting from the START of the list
  28045. this.alignGopsAtStart_ = function (gops) {
  28046. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  28047. byteLength = gops.byteLength;
  28048. nalCount = gops.nalCount;
  28049. duration = gops.duration;
  28050. alignIndex = gopIndex = 0;
  28051. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  28052. align = gopsToAlignWith[alignIndex];
  28053. gop = gops[gopIndex];
  28054. if (align.pts === gop.pts) {
  28055. break;
  28056. }
  28057. if (gop.pts > align.pts) {
  28058. // this current gop starts after the current gop we want to align on, so increment
  28059. // align index
  28060. alignIndex++;
  28061. continue;
  28062. } // current gop starts before the current gop we want to align on. so increment gop
  28063. // index
  28064. gopIndex++;
  28065. byteLength -= gop.byteLength;
  28066. nalCount -= gop.nalCount;
  28067. duration -= gop.duration;
  28068. }
  28069. if (gopIndex === 0) {
  28070. // no gops to trim
  28071. return gops;
  28072. }
  28073. if (gopIndex === gops.length) {
  28074. // all gops trimmed, skip appending all gops
  28075. return null;
  28076. }
  28077. alignedGops = gops.slice(gopIndex);
  28078. alignedGops.byteLength = byteLength;
  28079. alignedGops.duration = duration;
  28080. alignedGops.nalCount = nalCount;
  28081. alignedGops.pts = alignedGops[0].pts;
  28082. alignedGops.dts = alignedGops[0].dts;
  28083. return alignedGops;
  28084. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  28085. // of gopsToAlignWith starting from the END of the list
  28086. this.alignGopsAtEnd_ = function (gops) {
  28087. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  28088. alignIndex = gopsToAlignWith.length - 1;
  28089. gopIndex = gops.length - 1;
  28090. alignEndIndex = null;
  28091. matchFound = false;
  28092. while (alignIndex >= 0 && gopIndex >= 0) {
  28093. align = gopsToAlignWith[alignIndex];
  28094. gop = gops[gopIndex];
  28095. if (align.pts === gop.pts) {
  28096. matchFound = true;
  28097. break;
  28098. }
  28099. if (align.pts > gop.pts) {
  28100. alignIndex--;
  28101. continue;
  28102. }
  28103. if (alignIndex === gopsToAlignWith.length - 1) {
  28104. // gop.pts is greater than the last alignment candidate. If no match is found
  28105. // by the end of this loop, we still want to append gops that come after this
  28106. // point
  28107. alignEndIndex = gopIndex;
  28108. }
  28109. gopIndex--;
  28110. }
  28111. if (!matchFound && alignEndIndex === null) {
  28112. return null;
  28113. }
  28114. var trimIndex;
  28115. if (matchFound) {
  28116. trimIndex = gopIndex;
  28117. } else {
  28118. trimIndex = alignEndIndex;
  28119. }
  28120. if (trimIndex === 0) {
  28121. return gops;
  28122. }
  28123. var alignedGops = gops.slice(trimIndex);
  28124. var metadata = alignedGops.reduce(function (total, gop) {
  28125. total.byteLength += gop.byteLength;
  28126. total.duration += gop.duration;
  28127. total.nalCount += gop.nalCount;
  28128. return total;
  28129. }, {
  28130. byteLength: 0,
  28131. duration: 0,
  28132. nalCount: 0
  28133. });
  28134. alignedGops.byteLength = metadata.byteLength;
  28135. alignedGops.duration = metadata.duration;
  28136. alignedGops.nalCount = metadata.nalCount;
  28137. alignedGops.pts = alignedGops[0].pts;
  28138. alignedGops.dts = alignedGops[0].dts;
  28139. return alignedGops;
  28140. };
  28141. this.alignGopsWith = function (newGopsToAlignWith) {
  28142. gopsToAlignWith = newGopsToAlignWith;
  28143. };
  28144. };
  28145. _VideoSegmentStream.prototype = new stream();
  28146. /**
  28147. * A Stream that can combine multiple streams (ie. audio & video)
  28148. * into a single output segment for MSE. Also supports audio-only
  28149. * and video-only streams.
  28150. * @param options {object} transmuxer options object
  28151. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  28152. * in the source; false to adjust the first segment to start at media timeline start.
  28153. */
  28154. _CoalesceStream = function CoalesceStream(options, metadataStream) {
  28155. // Number of Tracks per output segment
  28156. // If greater than 1, we combine multiple
  28157. // tracks into a single segment
  28158. this.numberOfTracks = 0;
  28159. this.metadataStream = metadataStream;
  28160. options = options || {};
  28161. if (typeof options.remux !== 'undefined') {
  28162. this.remuxTracks = !!options.remux;
  28163. } else {
  28164. this.remuxTracks = true;
  28165. }
  28166. if (typeof options.keepOriginalTimestamps === 'boolean') {
  28167. this.keepOriginalTimestamps = options.keepOriginalTimestamps;
  28168. }
  28169. this.pendingTracks = [];
  28170. this.videoTrack = null;
  28171. this.pendingBoxes = [];
  28172. this.pendingCaptions = [];
  28173. this.pendingMetadata = [];
  28174. this.pendingBytes = 0;
  28175. this.emittedTracks = 0;
  28176. _CoalesceStream.prototype.init.call(this); // Take output from multiple
  28177. this.push = function (output) {
  28178. // buffer incoming captions until the associated video segment
  28179. // finishes
  28180. if (output.text) {
  28181. return this.pendingCaptions.push(output);
  28182. } // buffer incoming id3 tags until the final flush
  28183. if (output.frames) {
  28184. return this.pendingMetadata.push(output);
  28185. } // Add this track to the list of pending tracks and store
  28186. // important information required for the construction of
  28187. // the final segment
  28188. this.pendingTracks.push(output.track);
  28189. this.pendingBoxes.push(output.boxes);
  28190. this.pendingBytes += output.boxes.byteLength;
  28191. if (output.track.type === 'video') {
  28192. this.videoTrack = output.track;
  28193. }
  28194. if (output.track.type === 'audio') {
  28195. this.audioTrack = output.track;
  28196. }
  28197. };
  28198. };
  28199. _CoalesceStream.prototype = new stream();
  28200. _CoalesceStream.prototype.flush = function (flushSource) {
  28201. var offset = 0,
  28202. event = {
  28203. captions: [],
  28204. captionStreams: {},
  28205. metadata: [],
  28206. info: {}
  28207. },
  28208. caption,
  28209. id3,
  28210. initSegment,
  28211. timelineStartPts = 0,
  28212. i;
  28213. if (this.pendingTracks.length < this.numberOfTracks) {
  28214. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  28215. // Return because we haven't received a flush from a data-generating
  28216. // portion of the segment (meaning that we have only recieved meta-data
  28217. // or captions.)
  28218. return;
  28219. } else if (this.remuxTracks) {
  28220. // Return until we have enough tracks from the pipeline to remux (if we
  28221. // are remuxing audio and video into a single MP4)
  28222. return;
  28223. } else if (this.pendingTracks.length === 0) {
  28224. // In the case where we receive a flush without any data having been
  28225. // received we consider it an emitted track for the purposes of coalescing
  28226. // `done` events.
  28227. // We do this for the case where there is an audio and video track in the
  28228. // segment but no audio data. (seen in several playlists with alternate
  28229. // audio tracks and no audio present in the main TS segments.)
  28230. this.emittedTracks++;
  28231. if (this.emittedTracks >= this.numberOfTracks) {
  28232. this.trigger('done');
  28233. this.emittedTracks = 0;
  28234. }
  28235. return;
  28236. }
  28237. }
  28238. if (this.videoTrack) {
  28239. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  28240. VIDEO_PROPERTIES.forEach(function (prop) {
  28241. event.info[prop] = this.videoTrack[prop];
  28242. }, this);
  28243. } else if (this.audioTrack) {
  28244. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  28245. AUDIO_PROPERTIES.forEach(function (prop) {
  28246. event.info[prop] = this.audioTrack[prop];
  28247. }, this);
  28248. }
  28249. if (this.pendingTracks.length === 1) {
  28250. event.type = this.pendingTracks[0].type;
  28251. } else {
  28252. event.type = 'combined';
  28253. }
  28254. this.emittedTracks += this.pendingTracks.length;
  28255. initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
  28256. event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
  28257. // and track definitions
  28258. event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
  28259. event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
  28260. for (i = 0; i < this.pendingBoxes.length; i++) {
  28261. event.data.set(this.pendingBoxes[i], offset);
  28262. offset += this.pendingBoxes[i].byteLength;
  28263. } // Translate caption PTS times into second offsets to match the
  28264. // video timeline for the segment, and add track info
  28265. for (i = 0; i < this.pendingCaptions.length; i++) {
  28266. caption = this.pendingCaptions[i];
  28267. caption.startTime = caption.startPts;
  28268. if (!this.keepOriginalTimestamps) {
  28269. caption.startTime -= timelineStartPts;
  28270. }
  28271. caption.startTime /= 90e3;
  28272. caption.endTime = caption.endPts;
  28273. if (!this.keepOriginalTimestamps) {
  28274. caption.endTime -= timelineStartPts;
  28275. }
  28276. caption.endTime /= 90e3;
  28277. event.captionStreams[caption.stream] = true;
  28278. event.captions.push(caption);
  28279. } // Translate ID3 frame PTS times into second offsets to match the
  28280. // video timeline for the segment
  28281. for (i = 0; i < this.pendingMetadata.length; i++) {
  28282. id3 = this.pendingMetadata[i];
  28283. id3.cueTime = id3.pts;
  28284. if (!this.keepOriginalTimestamps) {
  28285. id3.cueTime -= timelineStartPts;
  28286. }
  28287. id3.cueTime /= 90e3;
  28288. event.metadata.push(id3);
  28289. } // We add this to every single emitted segment even though we only need
  28290. // it for the first
  28291. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  28292. this.pendingTracks.length = 0;
  28293. this.videoTrack = null;
  28294. this.pendingBoxes.length = 0;
  28295. this.pendingCaptions.length = 0;
  28296. this.pendingBytes = 0;
  28297. this.pendingMetadata.length = 0; // Emit the built segment
  28298. this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
  28299. if (this.emittedTracks >= this.numberOfTracks) {
  28300. this.trigger('done');
  28301. this.emittedTracks = 0;
  28302. }
  28303. };
  28304. /**
  28305. * A Stream that expects MP2T binary data as input and produces
  28306. * corresponding media segments, suitable for use with Media Source
  28307. * Extension (MSE) implementations that support the ISO BMFF byte
  28308. * stream format, like Chrome.
  28309. */
  28310. _Transmuxer = function Transmuxer(options) {
  28311. var self = this,
  28312. hasFlushed = true,
  28313. videoTrack,
  28314. audioTrack;
  28315. _Transmuxer.prototype.init.call(this);
  28316. options = options || {};
  28317. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  28318. this.transmuxPipeline_ = {};
  28319. this.setupAacPipeline = function () {
  28320. var pipeline = {};
  28321. this.transmuxPipeline_ = pipeline;
  28322. pipeline.type = 'aac';
  28323. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  28324. pipeline.aacStream = new aac();
  28325. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  28326. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  28327. pipeline.adtsStream = new adts();
  28328. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  28329. pipeline.headOfPipeline = pipeline.aacStream;
  28330. pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  28331. pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
  28332. pipeline.metadataStream.on('timestamp', function (frame) {
  28333. pipeline.aacStream.setTimestamp(frame.timeStamp);
  28334. });
  28335. pipeline.aacStream.on('data', function (data) {
  28336. if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
  28337. audioTrack = audioTrack || {
  28338. timelineStartInfo: {
  28339. baseMediaDecodeTime: self.baseMediaDecodeTime
  28340. },
  28341. codec: 'adts',
  28342. type: 'audio'
  28343. }; // hook up the audio segment stream to the first track with aac data
  28344. pipeline.coalesceStream.numberOfTracks++;
  28345. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  28346. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  28347. }
  28348. }); // Re-emit any data coming from the coalesce stream to the outside world
  28349. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  28350. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  28351. };
  28352. this.setupTsPipeline = function () {
  28353. var pipeline = {};
  28354. this.transmuxPipeline_ = pipeline;
  28355. pipeline.type = 'ts';
  28356. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  28357. pipeline.packetStream = new m2ts_1.TransportPacketStream();
  28358. pipeline.parseStream = new m2ts_1.TransportParseStream();
  28359. pipeline.elementaryStream = new m2ts_1.ElementaryStream();
  28360. pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  28361. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  28362. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  28363. pipeline.adtsStream = new adts();
  28364. pipeline.h264Stream = new H264Stream();
  28365. pipeline.captionStream = new m2ts_1.CaptionStream();
  28366. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  28367. pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
  28368. pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  28369. // demux the streams
  28370. pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
  28371. pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  28372. pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
  28373. pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
  28374. pipeline.elementaryStream.on('data', function (data) {
  28375. var i;
  28376. if (data.type === 'metadata') {
  28377. i = data.tracks.length; // scan the tracks listed in the metadata
  28378. while (i--) {
  28379. if (!videoTrack && data.tracks[i].type === 'video') {
  28380. videoTrack = data.tracks[i];
  28381. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  28382. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  28383. audioTrack = data.tracks[i];
  28384. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  28385. }
  28386. } // hook up the video segment stream to the first track with h264 data
  28387. if (videoTrack && !pipeline.videoSegmentStream) {
  28388. pipeline.coalesceStream.numberOfTracks++;
  28389. pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
  28390. pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
  28391. // When video emits timelineStartInfo data after a flush, we forward that
  28392. // info to the AudioSegmentStream, if it exists, because video timeline
  28393. // data takes precedence.
  28394. if (audioTrack) {
  28395. audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
  28396. // very earliest DTS we have seen in video because Chrome will
  28397. // interpret any video track with a baseMediaDecodeTime that is
  28398. // non-zero as a gap.
  28399. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
  28400. }
  28401. });
  28402. pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
  28403. pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
  28404. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
  28405. if (audioTrack) {
  28406. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  28407. }
  28408. }); // Set up the final part of the video pipeline
  28409. pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
  28410. }
  28411. if (audioTrack && !pipeline.audioSegmentStream) {
  28412. // hook up the audio segment stream to the first track with aac data
  28413. pipeline.coalesceStream.numberOfTracks++;
  28414. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  28415. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  28416. }
  28417. }
  28418. }); // Re-emit any data coming from the coalesce stream to the outside world
  28419. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  28420. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  28421. }; // hook up the segment streams once track metadata is delivered
  28422. this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  28423. var pipeline = this.transmuxPipeline_;
  28424. if (!options.keepOriginalTimestamps) {
  28425. this.baseMediaDecodeTime = baseMediaDecodeTime;
  28426. }
  28427. if (audioTrack) {
  28428. audioTrack.timelineStartInfo.dts = undefined;
  28429. audioTrack.timelineStartInfo.pts = undefined;
  28430. trackDecodeInfo.clearDtsInfo(audioTrack);
  28431. if (!options.keepOriginalTimestamps) {
  28432. audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  28433. }
  28434. if (pipeline.audioTimestampRolloverStream) {
  28435. pipeline.audioTimestampRolloverStream.discontinuity();
  28436. }
  28437. }
  28438. if (videoTrack) {
  28439. if (pipeline.videoSegmentStream) {
  28440. pipeline.videoSegmentStream.gopCache_ = [];
  28441. pipeline.videoTimestampRolloverStream.discontinuity();
  28442. }
  28443. videoTrack.timelineStartInfo.dts = undefined;
  28444. videoTrack.timelineStartInfo.pts = undefined;
  28445. trackDecodeInfo.clearDtsInfo(videoTrack);
  28446. pipeline.captionStream.reset();
  28447. if (!options.keepOriginalTimestamps) {
  28448. videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  28449. }
  28450. }
  28451. if (pipeline.timedMetadataTimestampRolloverStream) {
  28452. pipeline.timedMetadataTimestampRolloverStream.discontinuity();
  28453. }
  28454. };
  28455. this.setAudioAppendStart = function (timestamp) {
  28456. if (audioTrack) {
  28457. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  28458. }
  28459. };
  28460. this.alignGopsWith = function (gopsToAlignWith) {
  28461. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  28462. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  28463. }
  28464. }; // feed incoming data to the front of the parsing pipeline
  28465. this.push = function (data) {
  28466. if (hasFlushed) {
  28467. var isAac = isLikelyAacData$1(data);
  28468. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  28469. this.setupAacPipeline();
  28470. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  28471. this.setupTsPipeline();
  28472. }
  28473. hasFlushed = false;
  28474. }
  28475. this.transmuxPipeline_.headOfPipeline.push(data);
  28476. }; // flush any buffered data
  28477. this.flush = function () {
  28478. hasFlushed = true; // Start at the top of the pipeline and flush all pending work
  28479. this.transmuxPipeline_.headOfPipeline.flush();
  28480. }; // Caption data has to be reset when seeking outside buffered range
  28481. this.resetCaptions = function () {
  28482. if (this.transmuxPipeline_.captionStream) {
  28483. this.transmuxPipeline_.captionStream.reset();
  28484. }
  28485. };
  28486. };
  28487. _Transmuxer.prototype = new stream();
  28488. var transmuxer = {
  28489. Transmuxer: _Transmuxer,
  28490. VideoSegmentStream: _VideoSegmentStream,
  28491. AudioSegmentStream: _AudioSegmentStream,
  28492. AUDIO_PROPERTIES: AUDIO_PROPERTIES,
  28493. VIDEO_PROPERTIES: VIDEO_PROPERTIES,
  28494. // exported for testing
  28495. generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
  28496. };
  28497. var inspectMp4,
  28498. _textifyMp,
  28499. parseType$2 = probe.parseType,
  28500. parseMp4Date = function parseMp4Date(seconds) {
  28501. return new Date(seconds * 1000 - 2082844800000);
  28502. },
  28503. parseSampleFlags = function parseSampleFlags(flags) {
  28504. return {
  28505. isLeading: (flags[0] & 0x0c) >>> 2,
  28506. dependsOn: flags[0] & 0x03,
  28507. isDependedOn: (flags[1] & 0xc0) >>> 6,
  28508. hasRedundancy: (flags[1] & 0x30) >>> 4,
  28509. paddingValue: (flags[1] & 0x0e) >>> 1,
  28510. isNonSyncSample: flags[1] & 0x01,
  28511. degradationPriority: flags[2] << 8 | flags[3]
  28512. };
  28513. },
  28514. nalParse = function nalParse(avcStream) {
  28515. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  28516. result = [],
  28517. i,
  28518. length;
  28519. for (i = 0; i + 4 < avcStream.length; i += length) {
  28520. length = avcView.getUint32(i);
  28521. i += 4; // bail if this doesn't appear to be an H264 stream
  28522. if (length <= 0) {
  28523. result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
  28524. continue;
  28525. }
  28526. switch (avcStream[i] & 0x1F) {
  28527. case 0x01:
  28528. result.push('slice_layer_without_partitioning_rbsp');
  28529. break;
  28530. case 0x05:
  28531. result.push('slice_layer_without_partitioning_rbsp_idr');
  28532. break;
  28533. case 0x06:
  28534. result.push('sei_rbsp');
  28535. break;
  28536. case 0x07:
  28537. result.push('seq_parameter_set_rbsp');
  28538. break;
  28539. case 0x08:
  28540. result.push('pic_parameter_set_rbsp');
  28541. break;
  28542. case 0x09:
  28543. result.push('access_unit_delimiter_rbsp');
  28544. break;
  28545. default:
  28546. result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
  28547. break;
  28548. }
  28549. }
  28550. return result;
  28551. },
  28552. // registry of handlers for individual mp4 box types
  28553. parse$$1 = {
  28554. // codingname, not a first-class box type. stsd entries share the
  28555. // same format as real boxes so the parsing infrastructure can be
  28556. // shared
  28557. avc1: function avc1(data) {
  28558. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28559. return {
  28560. dataReferenceIndex: view.getUint16(6),
  28561. width: view.getUint16(24),
  28562. height: view.getUint16(26),
  28563. horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
  28564. vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
  28565. frameCount: view.getUint16(40),
  28566. depth: view.getUint16(74),
  28567. config: inspectMp4(data.subarray(78, data.byteLength))
  28568. };
  28569. },
  28570. avcC: function avcC(data) {
  28571. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28572. result = {
  28573. configurationVersion: data[0],
  28574. avcProfileIndication: data[1],
  28575. profileCompatibility: data[2],
  28576. avcLevelIndication: data[3],
  28577. lengthSizeMinusOne: data[4] & 0x03,
  28578. sps: [],
  28579. pps: []
  28580. },
  28581. numOfSequenceParameterSets = data[5] & 0x1f,
  28582. numOfPictureParameterSets,
  28583. nalSize,
  28584. offset,
  28585. i; // iterate past any SPSs
  28586. offset = 6;
  28587. for (i = 0; i < numOfSequenceParameterSets; i++) {
  28588. nalSize = view.getUint16(offset);
  28589. offset += 2;
  28590. result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  28591. offset += nalSize;
  28592. } // iterate past any PPSs
  28593. numOfPictureParameterSets = data[offset];
  28594. offset++;
  28595. for (i = 0; i < numOfPictureParameterSets; i++) {
  28596. nalSize = view.getUint16(offset);
  28597. offset += 2;
  28598. result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  28599. offset += nalSize;
  28600. }
  28601. return result;
  28602. },
  28603. btrt: function btrt(data) {
  28604. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28605. return {
  28606. bufferSizeDB: view.getUint32(0),
  28607. maxBitrate: view.getUint32(4),
  28608. avgBitrate: view.getUint32(8)
  28609. };
  28610. },
  28611. esds: function esds(data) {
  28612. return {
  28613. version: data[0],
  28614. flags: new Uint8Array(data.subarray(1, 4)),
  28615. esId: data[6] << 8 | data[7],
  28616. streamPriority: data[8] & 0x1f,
  28617. decoderConfig: {
  28618. objectProfileIndication: data[11],
  28619. streamType: data[12] >>> 2 & 0x3f,
  28620. bufferSize: data[13] << 16 | data[14] << 8 | data[15],
  28621. maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
  28622. avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
  28623. decoderConfigDescriptor: {
  28624. tag: data[24],
  28625. length: data[25],
  28626. audioObjectType: data[26] >>> 3 & 0x1f,
  28627. samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
  28628. channelConfiguration: data[27] >>> 3 & 0x0f
  28629. }
  28630. }
  28631. };
  28632. },
  28633. ftyp: function ftyp(data) {
  28634. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28635. result = {
  28636. majorBrand: parseType$2(data.subarray(0, 4)),
  28637. minorVersion: view.getUint32(4),
  28638. compatibleBrands: []
  28639. },
  28640. i = 8;
  28641. while (i < data.byteLength) {
  28642. result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
  28643. i += 4;
  28644. }
  28645. return result;
  28646. },
  28647. dinf: function dinf(data) {
  28648. return {
  28649. boxes: inspectMp4(data)
  28650. };
  28651. },
  28652. dref: function dref(data) {
  28653. return {
  28654. version: data[0],
  28655. flags: new Uint8Array(data.subarray(1, 4)),
  28656. dataReferences: inspectMp4(data.subarray(8))
  28657. };
  28658. },
  28659. hdlr: function hdlr(data) {
  28660. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28661. result = {
  28662. version: view.getUint8(0),
  28663. flags: new Uint8Array(data.subarray(1, 4)),
  28664. handlerType: parseType$2(data.subarray(8, 12)),
  28665. name: ''
  28666. },
  28667. i = 8; // parse out the name field
  28668. for (i = 24; i < data.byteLength; i++) {
  28669. if (data[i] === 0x00) {
  28670. // the name field is null-terminated
  28671. i++;
  28672. break;
  28673. }
  28674. result.name += String.fromCharCode(data[i]);
  28675. } // decode UTF-8 to javascript's internal representation
  28676. // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  28677. result.name = decodeURIComponent(escape(result.name));
  28678. return result;
  28679. },
  28680. mdat: function mdat(data) {
  28681. return {
  28682. byteLength: data.byteLength,
  28683. nals: nalParse(data)
  28684. };
  28685. },
  28686. mdhd: function mdhd(data) {
  28687. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28688. i = 4,
  28689. language,
  28690. result = {
  28691. version: view.getUint8(0),
  28692. flags: new Uint8Array(data.subarray(1, 4)),
  28693. language: ''
  28694. };
  28695. if (result.version === 1) {
  28696. i += 4;
  28697. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28698. i += 8;
  28699. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28700. i += 4;
  28701. result.timescale = view.getUint32(i);
  28702. i += 8;
  28703. result.duration = view.getUint32(i); // truncating top 4 bytes
  28704. } else {
  28705. result.creationTime = parseMp4Date(view.getUint32(i));
  28706. i += 4;
  28707. result.modificationTime = parseMp4Date(view.getUint32(i));
  28708. i += 4;
  28709. result.timescale = view.getUint32(i);
  28710. i += 4;
  28711. result.duration = view.getUint32(i);
  28712. }
  28713. i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
  28714. // each field is the packed difference between its ASCII value and 0x60
  28715. language = view.getUint16(i);
  28716. result.language += String.fromCharCode((language >> 10) + 0x60);
  28717. result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
  28718. result.language += String.fromCharCode((language & 0x1f) + 0x60);
  28719. return result;
  28720. },
  28721. mdia: function mdia(data) {
  28722. return {
  28723. boxes: inspectMp4(data)
  28724. };
  28725. },
  28726. mfhd: function mfhd(data) {
  28727. return {
  28728. version: data[0],
  28729. flags: new Uint8Array(data.subarray(1, 4)),
  28730. sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  28731. };
  28732. },
  28733. minf: function minf(data) {
  28734. return {
  28735. boxes: inspectMp4(data)
  28736. };
  28737. },
  28738. // codingname, not a first-class box type. stsd entries share the
  28739. // same format as real boxes so the parsing infrastructure can be
  28740. // shared
  28741. mp4a: function mp4a(data) {
  28742. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28743. result = {
  28744. // 6 bytes reserved
  28745. dataReferenceIndex: view.getUint16(6),
  28746. // 4 + 4 bytes reserved
  28747. channelcount: view.getUint16(16),
  28748. samplesize: view.getUint16(18),
  28749. // 2 bytes pre_defined
  28750. // 2 bytes reserved
  28751. samplerate: view.getUint16(24) + view.getUint16(26) / 65536
  28752. }; // if there are more bytes to process, assume this is an ISO/IEC
  28753. // 14496-14 MP4AudioSampleEntry and parse the ESDBox
  28754. if (data.byteLength > 28) {
  28755. result.streamDescriptor = inspectMp4(data.subarray(28))[0];
  28756. }
  28757. return result;
  28758. },
  28759. moof: function moof(data) {
  28760. return {
  28761. boxes: inspectMp4(data)
  28762. };
  28763. },
  28764. moov: function moov(data) {
  28765. return {
  28766. boxes: inspectMp4(data)
  28767. };
  28768. },
  28769. mvex: function mvex(data) {
  28770. return {
  28771. boxes: inspectMp4(data)
  28772. };
  28773. },
  28774. mvhd: function mvhd(data) {
  28775. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28776. i = 4,
  28777. result = {
  28778. version: view.getUint8(0),
  28779. flags: new Uint8Array(data.subarray(1, 4))
  28780. };
  28781. if (result.version === 1) {
  28782. i += 4;
  28783. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28784. i += 8;
  28785. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  28786. i += 4;
  28787. result.timescale = view.getUint32(i);
  28788. i += 8;
  28789. result.duration = view.getUint32(i); // truncating top 4 bytes
  28790. } else {
  28791. result.creationTime = parseMp4Date(view.getUint32(i));
  28792. i += 4;
  28793. result.modificationTime = parseMp4Date(view.getUint32(i));
  28794. i += 4;
  28795. result.timescale = view.getUint32(i);
  28796. i += 4;
  28797. result.duration = view.getUint32(i);
  28798. }
  28799. i += 4; // convert fixed-point, base 16 back to a number
  28800. result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
  28801. i += 4;
  28802. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  28803. i += 2;
  28804. i += 2;
  28805. i += 2 * 4;
  28806. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  28807. i += 9 * 4;
  28808. i += 6 * 4;
  28809. result.nextTrackId = view.getUint32(i);
  28810. return result;
  28811. },
  28812. pdin: function pdin(data) {
  28813. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  28814. return {
  28815. version: view.getUint8(0),
  28816. flags: new Uint8Array(data.subarray(1, 4)),
  28817. rate: view.getUint32(4),
  28818. initialDelay: view.getUint32(8)
  28819. };
  28820. },
  28821. sdtp: function sdtp(data) {
  28822. var result = {
  28823. version: data[0],
  28824. flags: new Uint8Array(data.subarray(1, 4)),
  28825. samples: []
  28826. },
  28827. i;
  28828. for (i = 4; i < data.byteLength; i++) {
  28829. result.samples.push({
  28830. dependsOn: (data[i] & 0x30) >> 4,
  28831. isDependedOn: (data[i] & 0x0c) >> 2,
  28832. hasRedundancy: data[i] & 0x03
  28833. });
  28834. }
  28835. return result;
  28836. },
  28837. sidx: function sidx(data) {
  28838. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28839. result = {
  28840. version: data[0],
  28841. flags: new Uint8Array(data.subarray(1, 4)),
  28842. references: [],
  28843. referenceId: view.getUint32(4),
  28844. timescale: view.getUint32(8),
  28845. earliestPresentationTime: view.getUint32(12),
  28846. firstOffset: view.getUint32(16)
  28847. },
  28848. referenceCount = view.getUint16(22),
  28849. i;
  28850. for (i = 24; referenceCount; i += 12, referenceCount--) {
  28851. result.references.push({
  28852. referenceType: (data[i] & 0x80) >>> 7,
  28853. referencedSize: view.getUint32(i) & 0x7FFFFFFF,
  28854. subsegmentDuration: view.getUint32(i + 4),
  28855. startsWithSap: !!(data[i + 8] & 0x80),
  28856. sapType: (data[i + 8] & 0x70) >>> 4,
  28857. sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
  28858. });
  28859. }
  28860. return result;
  28861. },
  28862. smhd: function smhd(data) {
  28863. return {
  28864. version: data[0],
  28865. flags: new Uint8Array(data.subarray(1, 4)),
  28866. balance: data[4] + data[5] / 256
  28867. };
  28868. },
  28869. stbl: function stbl(data) {
  28870. return {
  28871. boxes: inspectMp4(data)
  28872. };
  28873. },
  28874. stco: function stco(data) {
  28875. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28876. result = {
  28877. version: data[0],
  28878. flags: new Uint8Array(data.subarray(1, 4)),
  28879. chunkOffsets: []
  28880. },
  28881. entryCount = view.getUint32(4),
  28882. i;
  28883. for (i = 8; entryCount; i += 4, entryCount--) {
  28884. result.chunkOffsets.push(view.getUint32(i));
  28885. }
  28886. return result;
  28887. },
  28888. stsc: function stsc(data) {
  28889. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28890. entryCount = view.getUint32(4),
  28891. result = {
  28892. version: data[0],
  28893. flags: new Uint8Array(data.subarray(1, 4)),
  28894. sampleToChunks: []
  28895. },
  28896. i;
  28897. for (i = 8; entryCount; i += 12, entryCount--) {
  28898. result.sampleToChunks.push({
  28899. firstChunk: view.getUint32(i),
  28900. samplesPerChunk: view.getUint32(i + 4),
  28901. sampleDescriptionIndex: view.getUint32(i + 8)
  28902. });
  28903. }
  28904. return result;
  28905. },
  28906. stsd: function stsd(data) {
  28907. return {
  28908. version: data[0],
  28909. flags: new Uint8Array(data.subarray(1, 4)),
  28910. sampleDescriptions: inspectMp4(data.subarray(8))
  28911. };
  28912. },
  28913. stsz: function stsz(data) {
  28914. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28915. result = {
  28916. version: data[0],
  28917. flags: new Uint8Array(data.subarray(1, 4)),
  28918. sampleSize: view.getUint32(4),
  28919. entries: []
  28920. },
  28921. i;
  28922. for (i = 12; i < data.byteLength; i += 4) {
  28923. result.entries.push(view.getUint32(i));
  28924. }
  28925. return result;
  28926. },
  28927. stts: function stts(data) {
  28928. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28929. result = {
  28930. version: data[0],
  28931. flags: new Uint8Array(data.subarray(1, 4)),
  28932. timeToSamples: []
  28933. },
  28934. entryCount = view.getUint32(4),
  28935. i;
  28936. for (i = 8; entryCount; i += 8, entryCount--) {
  28937. result.timeToSamples.push({
  28938. sampleCount: view.getUint32(i),
  28939. sampleDelta: view.getUint32(i + 4)
  28940. });
  28941. }
  28942. return result;
  28943. },
  28944. styp: function styp(data) {
  28945. return parse$$1.ftyp(data);
  28946. },
  28947. tfdt: function tfdt(data) {
  28948. var result = {
  28949. version: data[0],
  28950. flags: new Uint8Array(data.subarray(1, 4)),
  28951. baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  28952. };
  28953. if (result.version === 1) {
  28954. result.baseMediaDecodeTime *= Math.pow(2, 32);
  28955. result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
  28956. }
  28957. return result;
  28958. },
  28959. tfhd: function tfhd(data) {
  28960. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  28961. result = {
  28962. version: data[0],
  28963. flags: new Uint8Array(data.subarray(1, 4)),
  28964. trackId: view.getUint32(4)
  28965. },
  28966. baseDataOffsetPresent = result.flags[2] & 0x01,
  28967. sampleDescriptionIndexPresent = result.flags[2] & 0x02,
  28968. defaultSampleDurationPresent = result.flags[2] & 0x08,
  28969. defaultSampleSizePresent = result.flags[2] & 0x10,
  28970. defaultSampleFlagsPresent = result.flags[2] & 0x20,
  28971. durationIsEmpty = result.flags[0] & 0x010000,
  28972. defaultBaseIsMoof = result.flags[0] & 0x020000,
  28973. i;
  28974. i = 8;
  28975. if (baseDataOffsetPresent) {
  28976. i += 4; // truncate top 4 bytes
  28977. // FIXME: should we read the full 64 bits?
  28978. result.baseDataOffset = view.getUint32(12);
  28979. i += 4;
  28980. }
  28981. if (sampleDescriptionIndexPresent) {
  28982. result.sampleDescriptionIndex = view.getUint32(i);
  28983. i += 4;
  28984. }
  28985. if (defaultSampleDurationPresent) {
  28986. result.defaultSampleDuration = view.getUint32(i);
  28987. i += 4;
  28988. }
  28989. if (defaultSampleSizePresent) {
  28990. result.defaultSampleSize = view.getUint32(i);
  28991. i += 4;
  28992. }
  28993. if (defaultSampleFlagsPresent) {
  28994. result.defaultSampleFlags = view.getUint32(i);
  28995. }
  28996. if (durationIsEmpty) {
  28997. result.durationIsEmpty = true;
  28998. }
  28999. if (!baseDataOffsetPresent && defaultBaseIsMoof) {
  29000. result.baseDataOffsetIsMoof = true;
  29001. }
  29002. return result;
  29003. },
  29004. tkhd: function tkhd(data) {
  29005. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  29006. i = 4,
  29007. result = {
  29008. version: view.getUint8(0),
  29009. flags: new Uint8Array(data.subarray(1, 4))
  29010. };
  29011. if (result.version === 1) {
  29012. i += 4;
  29013. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  29014. i += 8;
  29015. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  29016. i += 4;
  29017. result.trackId = view.getUint32(i);
  29018. i += 4;
  29019. i += 8;
  29020. result.duration = view.getUint32(i); // truncating top 4 bytes
  29021. } else {
  29022. result.creationTime = parseMp4Date(view.getUint32(i));
  29023. i += 4;
  29024. result.modificationTime = parseMp4Date(view.getUint32(i));
  29025. i += 4;
  29026. result.trackId = view.getUint32(i);
  29027. i += 4;
  29028. i += 4;
  29029. result.duration = view.getUint32(i);
  29030. }
  29031. i += 4;
  29032. i += 2 * 4;
  29033. result.layer = view.getUint16(i);
  29034. i += 2;
  29035. result.alternateGroup = view.getUint16(i);
  29036. i += 2; // convert fixed-point, base 16 back to a number
  29037. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  29038. i += 2;
  29039. i += 2;
  29040. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  29041. i += 9 * 4;
  29042. result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
  29043. i += 4;
  29044. result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
  29045. return result;
  29046. },
  29047. traf: function traf(data) {
  29048. return {
  29049. boxes: inspectMp4(data)
  29050. };
  29051. },
  29052. trak: function trak(data) {
  29053. return {
  29054. boxes: inspectMp4(data)
  29055. };
  29056. },
  29057. trex: function trex(data) {
  29058. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  29059. return {
  29060. version: data[0],
  29061. flags: new Uint8Array(data.subarray(1, 4)),
  29062. trackId: view.getUint32(4),
  29063. defaultSampleDescriptionIndex: view.getUint32(8),
  29064. defaultSampleDuration: view.getUint32(12),
  29065. defaultSampleSize: view.getUint32(16),
  29066. sampleDependsOn: data[20] & 0x03,
  29067. sampleIsDependedOn: (data[21] & 0xc0) >> 6,
  29068. sampleHasRedundancy: (data[21] & 0x30) >> 4,
  29069. samplePaddingValue: (data[21] & 0x0e) >> 1,
  29070. sampleIsDifferenceSample: !!(data[21] & 0x01),
  29071. sampleDegradationPriority: view.getUint16(22)
  29072. };
  29073. },
  29074. trun: function trun(data) {
  29075. var result = {
  29076. version: data[0],
  29077. flags: new Uint8Array(data.subarray(1, 4)),
  29078. samples: []
  29079. },
  29080. view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  29081. // Flag interpretation
  29082. dataOffsetPresent = result.flags[2] & 0x01,
  29083. // compare with 2nd byte of 0x1
  29084. firstSampleFlagsPresent = result.flags[2] & 0x04,
  29085. // compare with 2nd byte of 0x4
  29086. sampleDurationPresent = result.flags[1] & 0x01,
  29087. // compare with 2nd byte of 0x100
  29088. sampleSizePresent = result.flags[1] & 0x02,
  29089. // compare with 2nd byte of 0x200
  29090. sampleFlagsPresent = result.flags[1] & 0x04,
  29091. // compare with 2nd byte of 0x400
  29092. sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
  29093. // compare with 2nd byte of 0x800
  29094. sampleCount = view.getUint32(4),
  29095. offset = 8,
  29096. sample;
  29097. if (dataOffsetPresent) {
  29098. // 32 bit signed integer
  29099. result.dataOffset = view.getInt32(offset);
  29100. offset += 4;
  29101. } // Overrides the flags for the first sample only. The order of
  29102. // optional values will be: duration, size, compositionTimeOffset
  29103. if (firstSampleFlagsPresent && sampleCount) {
  29104. sample = {
  29105. flags: parseSampleFlags(data.subarray(offset, offset + 4))
  29106. };
  29107. offset += 4;
  29108. if (sampleDurationPresent) {
  29109. sample.duration = view.getUint32(offset);
  29110. offset += 4;
  29111. }
  29112. if (sampleSizePresent) {
  29113. sample.size = view.getUint32(offset);
  29114. offset += 4;
  29115. }
  29116. if (sampleCompositionTimeOffsetPresent) {
  29117. // Note: this should be a signed int if version is 1
  29118. sample.compositionTimeOffset = view.getUint32(offset);
  29119. offset += 4;
  29120. }
  29121. result.samples.push(sample);
  29122. sampleCount--;
  29123. }
  29124. while (sampleCount--) {
  29125. sample = {};
  29126. if (sampleDurationPresent) {
  29127. sample.duration = view.getUint32(offset);
  29128. offset += 4;
  29129. }
  29130. if (sampleSizePresent) {
  29131. sample.size = view.getUint32(offset);
  29132. offset += 4;
  29133. }
  29134. if (sampleFlagsPresent) {
  29135. sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
  29136. offset += 4;
  29137. }
  29138. if (sampleCompositionTimeOffsetPresent) {
  29139. // Note: this should be a signed int if version is 1
  29140. sample.compositionTimeOffset = view.getUint32(offset);
  29141. offset += 4;
  29142. }
  29143. result.samples.push(sample);
  29144. }
  29145. return result;
  29146. },
  29147. 'url ': function url(data) {
  29148. return {
  29149. version: data[0],
  29150. flags: new Uint8Array(data.subarray(1, 4))
  29151. };
  29152. },
  29153. vmhd: function vmhd(data) {
  29154. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  29155. return {
  29156. version: data[0],
  29157. flags: new Uint8Array(data.subarray(1, 4)),
  29158. graphicsmode: view.getUint16(4),
  29159. opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
  29160. };
  29161. }
  29162. };
  29163. /**
  29164. * Return a javascript array of box objects parsed from an ISO base
  29165. * media file.
  29166. * @param data {Uint8Array} the binary data of the media to be inspected
  29167. * @return {array} a javascript array of potentially nested box objects
  29168. */
  29169. inspectMp4 = function inspectMp4(data) {
  29170. var i = 0,
  29171. result = [],
  29172. view,
  29173. size,
  29174. type,
  29175. end,
  29176. box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
  29177. var ab = new ArrayBuffer(data.length);
  29178. var v = new Uint8Array(ab);
  29179. for (var z = 0; z < data.length; ++z) {
  29180. v[z] = data[z];
  29181. }
  29182. view = new DataView(ab);
  29183. while (i < data.byteLength) {
  29184. // parse box data
  29185. size = view.getUint32(i);
  29186. type = parseType$2(data.subarray(i + 4, i + 8));
  29187. end = size > 1 ? i + size : data.byteLength; // parse type-specific data
  29188. box = (parse$$1[type] || function (data) {
  29189. return {
  29190. data: data
  29191. };
  29192. })(data.subarray(i + 8, end));
  29193. box.size = size;
  29194. box.type = type; // store this box and move to the next
  29195. result.push(box);
  29196. i = end;
  29197. }
  29198. return result;
  29199. };
  29200. /**
  29201. * Returns a textual representation of the javascript represtentation
  29202. * of an MP4 file. You can use it as an alternative to
  29203. * JSON.stringify() to compare inspected MP4s.
  29204. * @param inspectedMp4 {array} the parsed array of boxes in an MP4
  29205. * file
  29206. * @param depth {number} (optional) the number of ancestor boxes of
  29207. * the elements of inspectedMp4. Assumed to be zero if unspecified.
  29208. * @return {string} a text representation of the parsed MP4
  29209. */
  29210. _textifyMp = function textifyMp4(inspectedMp4, depth) {
  29211. var indent;
  29212. depth = depth || 0;
  29213. indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
  29214. return inspectedMp4.map(function (box, index) {
  29215. // list the box type first at the current indentation level
  29216. return indent + box.type + '\n' + // the type is already included and handle child boxes separately
  29217. Object.keys(box).filter(function (key) {
  29218. return key !== 'type' && key !== 'boxes'; // output all the box properties
  29219. }).map(function (key) {
  29220. var prefix = indent + ' ' + key + ': ',
  29221. value = box[key]; // print out raw bytes as hexademical
  29222. if (value instanceof Uint8Array || value instanceof Uint32Array) {
  29223. var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
  29224. return ' ' + ('00' + byte.toString(16)).slice(-2);
  29225. }).join('').match(/.{1,24}/g);
  29226. if (!bytes) {
  29227. return prefix + '<>';
  29228. }
  29229. if (bytes.length === 1) {
  29230. return prefix + '<' + bytes.join('').slice(1) + '>';
  29231. }
  29232. return prefix + '<\n' + bytes.map(function (line) {
  29233. return indent + ' ' + line;
  29234. }).join('\n') + '\n' + indent + ' >';
  29235. } // stringify generic objects
  29236. return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
  29237. if (index === 0) {
  29238. return line;
  29239. }
  29240. return indent + ' ' + line;
  29241. }).join('\n');
  29242. }).join('\n') + ( // recursively textify the child boxes
  29243. box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
  29244. }).join('\n');
  29245. };
  29246. var mp4Inspector$$1 = {
  29247. inspect: inspectMp4,
  29248. textify: _textifyMp,
  29249. parseTfdt: parse$$1.tfdt,
  29250. parseHdlr: parse$$1.hdlr,
  29251. parseTfhd: parse$$1.tfhd,
  29252. parseTrun: parse$$1.trun,
  29253. parseSidx: parse$$1.sidx
  29254. };
  29255. var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
  29256. var CaptionStream$1 = captionStream.CaptionStream;
  29257. /**
  29258. * Maps an offset in the mdat to a sample based on the the size of the samples.
  29259. * Assumes that `parseSamples` has been called first.
  29260. *
  29261. * @param {Number} offset - The offset into the mdat
  29262. * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
  29263. * @return {?Object} The matching sample, or null if no match was found.
  29264. *
  29265. * @see ISO-BMFF-12/2015, Section 8.8.8
  29266. **/
  29267. var mapToSample = function mapToSample(offset, samples) {
  29268. var approximateOffset = offset;
  29269. for (var i = 0; i < samples.length; i++) {
  29270. var sample = samples[i];
  29271. if (approximateOffset < sample.size) {
  29272. return sample;
  29273. }
  29274. approximateOffset -= sample.size;
  29275. }
  29276. return null;
  29277. };
  29278. /**
  29279. * Finds SEI nal units contained in a Media Data Box.
  29280. * Assumes that `parseSamples` has been called first.
  29281. *
  29282. * @param {Uint8Array} avcStream - The bytes of the mdat
  29283. * @param {Object[]} samples - The samples parsed out by `parseSamples`
  29284. * @param {Number} trackId - The trackId of this video track
  29285. * @return {Object[]} seiNals - the parsed SEI NALUs found.
  29286. * The contents of the seiNal should match what is expected by
  29287. * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
  29288. *
  29289. * @see ISO-BMFF-12/2015, Section 8.1.1
  29290. * @see Rec. ITU-T H.264, 7.3.2.3.1
  29291. **/
  29292. var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
  29293. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  29294. result = [],
  29295. seiNal,
  29296. i,
  29297. length,
  29298. lastMatchedSample;
  29299. for (i = 0; i + 4 < avcStream.length; i += length) {
  29300. length = avcView.getUint32(i);
  29301. i += 4; // Bail if this doesn't appear to be an H264 stream
  29302. if (length <= 0) {
  29303. continue;
  29304. }
  29305. switch (avcStream[i] & 0x1F) {
  29306. case 0x06:
  29307. var data = avcStream.subarray(i + 1, i + 1 + length);
  29308. var matchingSample = mapToSample(i, samples);
  29309. seiNal = {
  29310. nalUnitType: 'sei_rbsp',
  29311. size: length,
  29312. data: data,
  29313. escapedRBSP: discardEmulationPreventionBytes$1(data),
  29314. trackId: trackId
  29315. };
  29316. if (matchingSample) {
  29317. seiNal.pts = matchingSample.pts;
  29318. seiNal.dts = matchingSample.dts;
  29319. lastMatchedSample = matchingSample;
  29320. } else {
  29321. // If a matching sample cannot be found, use the last
  29322. // sample's values as they should be as close as possible
  29323. seiNal.pts = lastMatchedSample.pts;
  29324. seiNal.dts = lastMatchedSample.dts;
  29325. }
  29326. result.push(seiNal);
  29327. break;
  29328. default:
  29329. break;
  29330. }
  29331. }
  29332. return result;
  29333. };
  29334. /**
  29335. * Parses sample information out of Track Run Boxes and calculates
  29336. * the absolute presentation and decode timestamps of each sample.
  29337. *
  29338. * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
  29339. * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
  29340. @see ISO-BMFF-12/2015, Section 8.8.12
  29341. * @param {Object} tfhd - The parsed Track Fragment Header
  29342. * @see inspect.parseTfhd
  29343. * @return {Object[]} the parsed samples
  29344. *
  29345. * @see ISO-BMFF-12/2015, Section 8.8.8
  29346. **/
  29347. var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
  29348. var currentDts = baseMediaDecodeTime;
  29349. var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
  29350. var defaultSampleSize = tfhd.defaultSampleSize || 0;
  29351. var trackId = tfhd.trackId;
  29352. var allSamples = [];
  29353. truns.forEach(function (trun) {
  29354. // Note: We currently do not parse the sample table as well
  29355. // as the trun. It's possible some sources will require this.
  29356. // moov > trak > mdia > minf > stbl
  29357. var trackRun = mp4Inspector$$1.parseTrun(trun);
  29358. var samples = trackRun.samples;
  29359. samples.forEach(function (sample) {
  29360. if (sample.duration === undefined) {
  29361. sample.duration = defaultSampleDuration;
  29362. }
  29363. if (sample.size === undefined) {
  29364. sample.size = defaultSampleSize;
  29365. }
  29366. sample.trackId = trackId;
  29367. sample.dts = currentDts;
  29368. if (sample.compositionTimeOffset === undefined) {
  29369. sample.compositionTimeOffset = 0;
  29370. }
  29371. sample.pts = currentDts + sample.compositionTimeOffset;
  29372. currentDts += sample.duration;
  29373. });
  29374. allSamples = allSamples.concat(samples);
  29375. });
  29376. return allSamples;
  29377. };
  29378. /**
  29379. * Parses out caption nals from an FMP4 segment's video tracks.
  29380. *
  29381. * @param {Uint8Array} segment - The bytes of a single segment
  29382. * @param {Number} videoTrackId - The trackId of a video track in the segment
  29383. * @return {Object.<Number, Object[]>} A mapping of video trackId to
  29384. * a list of seiNals found in that track
  29385. **/
  29386. var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
  29387. // To get the samples
  29388. var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
  29389. var mdats = probe.findBox(segment, ['mdat']);
  29390. var captionNals = {};
  29391. var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
  29392. mdats.forEach(function (mdat, index) {
  29393. var matchingTraf = trafs[index];
  29394. mdatTrafPairs.push({
  29395. mdat: mdat,
  29396. traf: matchingTraf
  29397. });
  29398. });
  29399. mdatTrafPairs.forEach(function (pair) {
  29400. var mdat = pair.mdat;
  29401. var traf = pair.traf;
  29402. var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
  29403. var headerInfo = mp4Inspector$$1.parseTfhd(tfhd[0]);
  29404. var trackId = headerInfo.trackId;
  29405. var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
  29406. var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector$$1.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
  29407. var truns = probe.findBox(traf, ['trun']);
  29408. var samples;
  29409. var seiNals; // Only parse video data for the chosen video track
  29410. if (videoTrackId === trackId && truns.length > 0) {
  29411. samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
  29412. seiNals = findSeiNals(mdat, samples, trackId);
  29413. if (!captionNals[trackId]) {
  29414. captionNals[trackId] = [];
  29415. }
  29416. captionNals[trackId] = captionNals[trackId].concat(seiNals);
  29417. }
  29418. });
  29419. return captionNals;
  29420. };
  29421. /**
  29422. * Parses out inband captions from an MP4 container and returns
  29423. * caption objects that can be used by WebVTT and the TextTrack API.
  29424. * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
  29425. * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
  29426. * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
  29427. *
  29428. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  29429. * @param {Number} trackId - The id of the video track to parse
  29430. * @param {Number} timescale - The timescale for the video track from the init segment
  29431. *
  29432. * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
  29433. * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
  29434. * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
  29435. * @return {String} parsedCaptions[].text - The visible content of the caption
  29436. **/
  29437. var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
  29438. var seiNals;
  29439. if (!trackId) {
  29440. return null;
  29441. }
  29442. seiNals = parseCaptionNals(segment, trackId);
  29443. return {
  29444. seiNals: seiNals[trackId],
  29445. timescale: timescale
  29446. };
  29447. };
  29448. /**
  29449. * Converts SEI NALUs into captions that can be used by video.js
  29450. **/
  29451. var CaptionParser$$1 = function CaptionParser$$1() {
  29452. var isInitialized = false;
  29453. var captionStream$$1; // Stores segments seen before trackId and timescale are set
  29454. var segmentCache; // Stores video track ID of the track being parsed
  29455. var trackId; // Stores the timescale of the track being parsed
  29456. var timescale; // Stores captions parsed so far
  29457. var parsedCaptions;
  29458. /**
  29459. * A method to indicate whether a CaptionParser has been initalized
  29460. * @returns {Boolean}
  29461. **/
  29462. this.isInitialized = function () {
  29463. return isInitialized;
  29464. };
  29465. /**
  29466. * Initializes the underlying CaptionStream, SEI NAL parsing
  29467. * and management, and caption collection
  29468. **/
  29469. this.init = function () {
  29470. captionStream$$1 = new CaptionStream$1();
  29471. isInitialized = true; // Collect dispatched captions
  29472. captionStream$$1.on('data', function (event) {
  29473. // Convert to seconds in the source's timescale
  29474. event.startTime = event.startPts / timescale;
  29475. event.endTime = event.endPts / timescale;
  29476. parsedCaptions.captions.push(event);
  29477. parsedCaptions.captionStreams[event.stream] = true;
  29478. });
  29479. };
  29480. /**
  29481. * Determines if a new video track will be selected
  29482. * or if the timescale changed
  29483. * @return {Boolean}
  29484. **/
  29485. this.isNewInit = function (videoTrackIds, timescales) {
  29486. if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
  29487. return false;
  29488. }
  29489. return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
  29490. };
  29491. /**
  29492. * Parses out SEI captions and interacts with underlying
  29493. * CaptionStream to return dispatched captions
  29494. *
  29495. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  29496. * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
  29497. * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
  29498. * @see parseEmbeddedCaptions
  29499. * @see m2ts/caption-stream.js
  29500. **/
  29501. this.parse = function (segment, videoTrackIds, timescales) {
  29502. var parsedData;
  29503. if (!this.isInitialized()) {
  29504. return null; // This is not likely to be a video segment
  29505. } else if (!videoTrackIds || !timescales) {
  29506. return null;
  29507. } else if (this.isNewInit(videoTrackIds, timescales)) {
  29508. // Use the first video track only as there is no
  29509. // mechanism to switch to other video tracks
  29510. trackId = videoTrackIds[0];
  29511. timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
  29512. // data until we have one
  29513. } else if (!trackId || !timescale) {
  29514. segmentCache.push(segment);
  29515. return null;
  29516. } // Now that a timescale and trackId is set, parse cached segments
  29517. while (segmentCache.length > 0) {
  29518. var cachedSegment = segmentCache.shift();
  29519. this.parse(cachedSegment, videoTrackIds, timescales);
  29520. }
  29521. parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
  29522. if (parsedData === null || !parsedData.seiNals) {
  29523. return null;
  29524. }
  29525. this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
  29526. this.flushStream();
  29527. return parsedCaptions;
  29528. };
  29529. /**
  29530. * Pushes SEI NALUs onto CaptionStream
  29531. * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
  29532. * Assumes that `parseCaptionNals` has been called first
  29533. * @see m2ts/caption-stream.js
  29534. **/
  29535. this.pushNals = function (nals) {
  29536. if (!this.isInitialized() || !nals || nals.length === 0) {
  29537. return null;
  29538. }
  29539. nals.forEach(function (nal) {
  29540. captionStream$$1.push(nal);
  29541. });
  29542. };
  29543. /**
  29544. * Flushes underlying CaptionStream to dispatch processed, displayable captions
  29545. * @see m2ts/caption-stream.js
  29546. **/
  29547. this.flushStream = function () {
  29548. if (!this.isInitialized()) {
  29549. return null;
  29550. }
  29551. captionStream$$1.flush();
  29552. };
  29553. /**
  29554. * Reset caption buckets for new data
  29555. **/
  29556. this.clearParsedCaptions = function () {
  29557. parsedCaptions.captions = [];
  29558. parsedCaptions.captionStreams = {};
  29559. };
  29560. /**
  29561. * Resets underlying CaptionStream
  29562. * @see m2ts/caption-stream.js
  29563. **/
  29564. this.resetCaptionStream = function () {
  29565. if (!this.isInitialized()) {
  29566. return null;
  29567. }
  29568. captionStream$$1.reset();
  29569. };
  29570. /**
  29571. * Convenience method to clear all captions flushed from the
  29572. * CaptionStream and still being parsed
  29573. * @see m2ts/caption-stream.js
  29574. **/
  29575. this.clearAllCaptions = function () {
  29576. this.clearParsedCaptions();
  29577. this.resetCaptionStream();
  29578. };
  29579. /**
  29580. * Reset caption parser
  29581. **/
  29582. this.reset = function () {
  29583. segmentCache = [];
  29584. trackId = null;
  29585. timescale = null;
  29586. if (!parsedCaptions) {
  29587. parsedCaptions = {
  29588. captions: [],
  29589. // CC1, CC2, CC3, CC4
  29590. captionStreams: {}
  29591. };
  29592. } else {
  29593. this.clearParsedCaptions();
  29594. }
  29595. this.resetCaptionStream();
  29596. };
  29597. this.reset();
  29598. };
  29599. var captionParser = CaptionParser$$1;
  29600. var mp4 = {
  29601. generator: mp4Generator,
  29602. probe: probe,
  29603. Transmuxer: transmuxer.Transmuxer,
  29604. AudioSegmentStream: transmuxer.AudioSegmentStream,
  29605. VideoSegmentStream: transmuxer.VideoSegmentStream,
  29606. CaptionParser: captionParser
  29607. };
  29608. var classCallCheck = function classCallCheck(instance, Constructor) {
  29609. if (!(instance instanceof Constructor)) {
  29610. throw new TypeError("Cannot call a class as a function");
  29611. }
  29612. };
  29613. var createClass = function () {
  29614. function defineProperties(target, props) {
  29615. for (var i = 0; i < props.length; i++) {
  29616. var descriptor = props[i];
  29617. descriptor.enumerable = descriptor.enumerable || false;
  29618. descriptor.configurable = true;
  29619. if ("value" in descriptor) descriptor.writable = true;
  29620. Object.defineProperty(target, descriptor.key, descriptor);
  29621. }
  29622. }
  29623. return function (Constructor, protoProps, staticProps) {
  29624. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  29625. if (staticProps) defineProperties(Constructor, staticProps);
  29626. return Constructor;
  29627. };
  29628. }();
  29629. /**
  29630. * @file transmuxer-worker.js
  29631. */
  29632. /**
  29633. * Re-emits transmuxer events by converting them into messages to the
  29634. * world outside the worker.
  29635. *
  29636. * @param {Object} transmuxer the transmuxer to wire events on
  29637. * @private
  29638. */
  29639. var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) {
  29640. transmuxer.on('data', function (segment) {
  29641. // transfer ownership of the underlying ArrayBuffer
  29642. // instead of doing a copy to save memory
  29643. // ArrayBuffers are transferable but generic TypedArrays are not
  29644. // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
  29645. var initArray = segment.initSegment;
  29646. segment.initSegment = {
  29647. data: initArray.buffer,
  29648. byteOffset: initArray.byteOffset,
  29649. byteLength: initArray.byteLength
  29650. };
  29651. var typedArray = segment.data;
  29652. segment.data = typedArray.buffer;
  29653. self.postMessage({
  29654. action: 'data',
  29655. segment: segment,
  29656. byteOffset: typedArray.byteOffset,
  29657. byteLength: typedArray.byteLength
  29658. }, [segment.data]);
  29659. });
  29660. if (transmuxer.captionStream) {
  29661. transmuxer.captionStream.on('data', function (caption) {
  29662. self.postMessage({
  29663. action: 'caption',
  29664. data: caption
  29665. });
  29666. });
  29667. }
  29668. transmuxer.on('done', function (data) {
  29669. self.postMessage({
  29670. action: 'done'
  29671. });
  29672. });
  29673. transmuxer.on('gopInfo', function (gopInfo) {
  29674. self.postMessage({
  29675. action: 'gopInfo',
  29676. gopInfo: gopInfo
  29677. });
  29678. });
  29679. transmuxer.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) {
  29680. self.postMessage({
  29681. action: 'videoSegmentTimingInfo',
  29682. videoSegmentTimingInfo: videoSegmentTimingInfo
  29683. });
  29684. });
  29685. };
  29686. /**
  29687. * All incoming messages route through this hash. If no function exists
  29688. * to handle an incoming message, then we ignore the message.
  29689. *
  29690. * @class MessageHandlers
  29691. * @param {Object} options the options to initialize with
  29692. */
  29693. var MessageHandlers = function () {
  29694. function MessageHandlers(self, options) {
  29695. classCallCheck(this, MessageHandlers);
  29696. this.options = options || {};
  29697. this.self = self;
  29698. this.init();
  29699. }
  29700. /**
  29701. * initialize our web worker and wire all the events.
  29702. */
  29703. createClass(MessageHandlers, [{
  29704. key: 'init',
  29705. value: function init() {
  29706. if (this.transmuxer) {
  29707. this.transmuxer.dispose();
  29708. }
  29709. this.transmuxer = new mp4.Transmuxer(this.options);
  29710. wireTransmuxerEvents(this.self, this.transmuxer);
  29711. }
  29712. /**
  29713. * Adds data (a ts segment) to the start of the transmuxer pipeline for
  29714. * processing.
  29715. *
  29716. * @param {ArrayBuffer} data data to push into the muxer
  29717. */
  29718. }, {
  29719. key: 'push',
  29720. value: function push(data) {
  29721. // Cast array buffer to correct type for transmuxer
  29722. var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
  29723. this.transmuxer.push(segment);
  29724. }
  29725. /**
  29726. * Recreate the transmuxer so that the next segment added via `push`
  29727. * start with a fresh transmuxer.
  29728. */
  29729. }, {
  29730. key: 'reset',
  29731. value: function reset() {
  29732. this.init();
  29733. }
  29734. /**
  29735. * Set the value that will be used as the `baseMediaDecodeTime` time for the
  29736. * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
  29737. * set relative to the first based on the PTS values.
  29738. *
  29739. * @param {Object} data used to set the timestamp offset in the muxer
  29740. */
  29741. }, {
  29742. key: 'setTimestampOffset',
  29743. value: function setTimestampOffset(data) {
  29744. var timestampOffset = data.timestampOffset || 0;
  29745. this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
  29746. }
  29747. }, {
  29748. key: 'setAudioAppendStart',
  29749. value: function setAudioAppendStart(data) {
  29750. this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000));
  29751. }
  29752. /**
  29753. * Forces the pipeline to finish processing the last segment and emit it's
  29754. * results.
  29755. *
  29756. * @param {Object} data event data, not really used
  29757. */
  29758. }, {
  29759. key: 'flush',
  29760. value: function flush(data) {
  29761. this.transmuxer.flush();
  29762. }
  29763. }, {
  29764. key: 'resetCaptions',
  29765. value: function resetCaptions() {
  29766. this.transmuxer.resetCaptions();
  29767. }
  29768. }, {
  29769. key: 'alignGopsWith',
  29770. value: function alignGopsWith(data) {
  29771. this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
  29772. }
  29773. }]);
  29774. return MessageHandlers;
  29775. }();
  29776. /**
  29777. * Our web wroker interface so that things can talk to mux.js
  29778. * that will be running in a web worker. the scope is passed to this by
  29779. * webworkify.
  29780. *
  29781. * @param {Object} self the scope for the web worker
  29782. */
  29783. var TransmuxerWorker = function TransmuxerWorker(self) {
  29784. self.onmessage = function (event) {
  29785. if (event.data.action === 'init' && event.data.options) {
  29786. this.messageHandlers = new MessageHandlers(self, event.data.options);
  29787. return;
  29788. }
  29789. if (!this.messageHandlers) {
  29790. this.messageHandlers = new MessageHandlers(self);
  29791. }
  29792. if (event.data && event.data.action && event.data.action !== 'init') {
  29793. if (this.messageHandlers[event.data.action]) {
  29794. this.messageHandlers[event.data.action](event.data);
  29795. }
  29796. }
  29797. };
  29798. };
  29799. var transmuxerWorker = new TransmuxerWorker(self);
  29800. return transmuxerWorker;
  29801. }();
  29802. });
  29803. /**
  29804. * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
  29805. * codec strings, or translating codec strings into objects that can be examined.
  29806. */
  29807. // Default codec parameters if none were provided for video and/or audio
  29808. var defaultCodecs = {
  29809. videoCodec: 'avc1',
  29810. videoObjectTypeIndicator: '.4d400d',
  29811. // AAC-LC
  29812. audioProfile: '2'
  29813. };
  29814. /**
  29815. * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
  29816. * `avc1.<hhhhhh>`
  29817. *
  29818. * @param {Array} codecs an array of codec strings to fix
  29819. * @return {Array} the translated codec array
  29820. * @private
  29821. */
  29822. var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
  29823. return codecs.map(function (codec) {
  29824. return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
  29825. var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
  29826. var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
  29827. return 'avc1.' + profileHex + '00' + avcLevelHex;
  29828. });
  29829. });
  29830. };
  29831. /**
  29832. * Parses a codec string to retrieve the number of codecs specified,
  29833. * the video codec and object type indicator, and the audio profile.
  29834. */
  29835. var parseCodecs = function parseCodecs() {
  29836. var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  29837. var result = {
  29838. codecCount: 0
  29839. };
  29840. var parsed = void 0;
  29841. result.codecCount = codecs.split(',').length;
  29842. result.codecCount = result.codecCount || 2; // parse the video codec
  29843. parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs);
  29844. if (parsed) {
  29845. result.videoCodec = parsed[2];
  29846. result.videoObjectTypeIndicator = parsed[3];
  29847. } // parse the last field of the audio codec
  29848. result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs);
  29849. result.audioProfile = result.audioProfile && result.audioProfile[2];
  29850. return result;
  29851. };
  29852. /**
  29853. * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
  29854. * standard `avc1.<hhhhhh>`.
  29855. *
  29856. * @param codecString {String} the codec string
  29857. * @return {String} the codec string with old apple-style codecs replaced
  29858. *
  29859. * @private
  29860. */
  29861. var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
  29862. return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
  29863. return translateLegacyCodecs([match])[0];
  29864. });
  29865. };
  29866. /**
  29867. * Build a media mime-type string from a set of parameters
  29868. * @param {String} type either 'audio' or 'video'
  29869. * @param {String} container either 'mp2t' or 'mp4'
  29870. * @param {Array} codecs an array of codec strings to add
  29871. * @return {String} a valid media mime-type
  29872. */
  29873. var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
  29874. // The codecs array is filtered so that falsey values are
  29875. // dropped and don't cause Array#join to create spurious
  29876. // commas
  29877. return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
  29878. return !!c;
  29879. }).join(', ') + '"';
  29880. };
  29881. /**
  29882. * Returns the type container based on information in the playlist
  29883. * @param {Playlist} media the current media playlist
  29884. * @return {String} a valid media container type
  29885. */
  29886. var getContainerType = function getContainerType(media) {
  29887. // An initialization segment means the media playlist is an iframe
  29888. // playlist or is using the mp4 container. We don't currently
  29889. // support iframe playlists, so assume this is signalling mp4
  29890. // fragments.
  29891. if (media.segments && media.segments.length && media.segments[0].map) {
  29892. return 'mp4';
  29893. }
  29894. return 'mp2t';
  29895. };
  29896. /**
  29897. * Returns a set of codec strings parsed from the playlist or the default
  29898. * codec strings if no codecs were specified in the playlist
  29899. * @param {Playlist} media the current media playlist
  29900. * @return {Object} an object with the video and audio codecs
  29901. */
  29902. var getCodecs = function getCodecs(media) {
  29903. // if the codecs were explicitly specified, use them instead of the
  29904. // defaults
  29905. var mediaAttributes = media.attributes || {};
  29906. if (mediaAttributes.CODECS) {
  29907. return parseCodecs(mediaAttributes.CODECS);
  29908. }
  29909. return defaultCodecs;
  29910. };
  29911. var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) {
  29912. if (!master.mediaGroups.AUDIO || !audioGroupId) {
  29913. return null;
  29914. }
  29915. var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
  29916. if (!audioGroup) {
  29917. return null;
  29918. }
  29919. for (var name in audioGroup) {
  29920. var audioType = audioGroup[name];
  29921. if (audioType.default && audioType.playlists) {
  29922. // codec should be the same for all playlists within the audio type
  29923. return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
  29924. }
  29925. }
  29926. return null;
  29927. };
  29928. /**
  29929. * Calculates the MIME type strings for a working configuration of
  29930. * SourceBuffers to play variant streams in a master playlist. If
  29931. * there is no possible working configuration, an empty array will be
  29932. * returned.
  29933. *
  29934. * @param master {Object} the m3u8 object for the master playlist
  29935. * @param media {Object} the m3u8 object for the variant playlist
  29936. * @return {Array} the MIME type strings. If the array has more than
  29937. * one entry, the first element should be applied to the video
  29938. * SourceBuffer and the second to the audio SourceBuffer.
  29939. *
  29940. * @private
  29941. */
  29942. var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) {
  29943. var containerType = getContainerType(media);
  29944. var codecInfo = getCodecs(media);
  29945. var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video)
  29946. var isMuxed = true;
  29947. var isMaat = false;
  29948. if (!media) {
  29949. // Not enough information
  29950. return [];
  29951. }
  29952. if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
  29953. var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario
  29954. if (audioGroup) {
  29955. isMaat = true; // Start with the everything demuxed then...
  29956. isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
  29957. for (var groupId in audioGroup) {
  29958. // either a uri is present (if the case of HLS and an external playlist), or
  29959. // playlists is present (in the case of DASH where we don't have external audio
  29960. // playlists)
  29961. if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
  29962. isMuxed = true;
  29963. break;
  29964. }
  29965. }
  29966. }
  29967. } // HLS with multiple-audio tracks must always get an audio codec.
  29968. // Put another way, there is no way to have a video-only multiple-audio HLS!
  29969. if (isMaat && !codecInfo.audioProfile) {
  29970. if (!isMuxed) {
  29971. // It is possible for codecs to be specified on the audio media group playlist but
  29972. // not on the rendition playlist. This is mostly the case for DASH, where audio and
  29973. // video are always separate (and separately specified).
  29974. codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO);
  29975. }
  29976. if (!codecInfo.audioProfile) {
  29977. videojs$1.log.warn('Multiple audio tracks present but no audio codec string is specified. ' + 'Attempting to use the default audio codec (mp4a.40.2)');
  29978. codecInfo.audioProfile = defaultCodecs.audioProfile;
  29979. }
  29980. } // Generate the final codec strings from the codec object generated above
  29981. var codecStrings = {};
  29982. if (codecInfo.videoCodec) {
  29983. codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
  29984. }
  29985. if (codecInfo.audioProfile) {
  29986. codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
  29987. } // Finally, make and return an array with proper mime-types depending on
  29988. // the configuration
  29989. var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
  29990. var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
  29991. var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
  29992. if (isMaat) {
  29993. if (!isMuxed && codecStrings.video) {
  29994. return [justVideo, justAudio];
  29995. }
  29996. if (!isMuxed && !codecStrings.video) {
  29997. // There is no muxed content and no video codec string, so this is an audio only
  29998. // stream with alternate audio.
  29999. return [justAudio, justAudio];
  30000. } // There exists the possiblity that this will return a `video/container`
  30001. // mime-type for the first entry in the array even when there is only audio.
  30002. // This doesn't appear to be a problem and simplifies the code.
  30003. return [bothVideoAudio, justAudio];
  30004. } // If there is no video codec at all, always just return a single
  30005. // audio/<container> mime-type
  30006. if (!codecStrings.video) {
  30007. return [justAudio];
  30008. } // When not using separate audio media groups, audio and video is
  30009. // *always* muxed
  30010. return [bothVideoAudio];
  30011. };
  30012. /**
  30013. * Parse a content type header into a type and parameters
  30014. * object
  30015. *
  30016. * @param {String} type the content type header
  30017. * @return {Object} the parsed content-type
  30018. * @private
  30019. */
  30020. var parseContentType = function parseContentType(type) {
  30021. var object = {
  30022. type: '',
  30023. parameters: {}
  30024. };
  30025. var parameters = type.trim().split(';'); // first parameter should always be content-type
  30026. object.type = parameters.shift().trim();
  30027. parameters.forEach(function (parameter) {
  30028. var pair = parameter.trim().split('=');
  30029. if (pair.length > 1) {
  30030. var name = pair[0].replace(/"/g, '').trim();
  30031. var value = pair[1].replace(/"/g, '').trim();
  30032. object.parameters[name] = value;
  30033. }
  30034. });
  30035. return object;
  30036. };
  30037. /**
  30038. * Check if a codec string refers to an audio codec.
  30039. *
  30040. * @param {String} codec codec string to check
  30041. * @return {Boolean} if this is an audio codec
  30042. * @private
  30043. */
  30044. var isAudioCodec = function isAudioCodec(codec) {
  30045. return /mp4a\.\d+.\d+/i.test(codec);
  30046. };
  30047. /**
  30048. * Check if a codec string refers to a video codec.
  30049. *
  30050. * @param {String} codec codec string to check
  30051. * @return {Boolean} if this is a video codec
  30052. * @private
  30053. */
  30054. var isVideoCodec = function isVideoCodec(codec) {
  30055. return /avc1\.[\da-f]+/i.test(codec);
  30056. };
  30057. /**
  30058. * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
  30059. * front of current time.
  30060. *
  30061. * @param {Array} buffer
  30062. * The current buffer of gop information
  30063. * @param {Number} currentTime
  30064. * The current time
  30065. * @param {Double} mapping
  30066. * Offset to map display time to stream presentation time
  30067. * @return {Array}
  30068. * List of gops considered safe to append over
  30069. */
  30070. var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
  30071. if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
  30072. return [];
  30073. } // pts value for current time + 3 seconds to give a bit more wiggle room
  30074. var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000);
  30075. var i = void 0;
  30076. for (i = 0; i < buffer.length; i++) {
  30077. if (buffer[i].pts > currentTimePts) {
  30078. break;
  30079. }
  30080. }
  30081. return buffer.slice(i);
  30082. };
  30083. /**
  30084. * Appends gop information (timing and byteLength) received by the transmuxer for the
  30085. * gops appended in the last call to appendBuffer
  30086. *
  30087. * @param {Array} buffer
  30088. * The current buffer of gop information
  30089. * @param {Array} gops
  30090. * List of new gop information
  30091. * @param {boolean} replace
  30092. * If true, replace the buffer with the new gop information. If false, append the
  30093. * new gop information to the buffer in the right location of time.
  30094. * @return {Array}
  30095. * Updated list of gop information
  30096. */
  30097. var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
  30098. if (!gops.length) {
  30099. return buffer;
  30100. }
  30101. if (replace) {
  30102. // If we are in safe append mode, then completely overwrite the gop buffer
  30103. // with the most recent appeneded data. This will make sure that when appending
  30104. // future segments, we only try to align with gops that are both ahead of current
  30105. // time and in the last segment appended.
  30106. return gops.slice();
  30107. }
  30108. var start = gops[0].pts;
  30109. var i = 0;
  30110. for (i; i < buffer.length; i++) {
  30111. if (buffer[i].pts >= start) {
  30112. break;
  30113. }
  30114. }
  30115. return buffer.slice(0, i).concat(gops);
  30116. };
  30117. /**
  30118. * Removes gop information in buffer that overlaps with provided start and end
  30119. *
  30120. * @param {Array} buffer
  30121. * The current buffer of gop information
  30122. * @param {Double} start
  30123. * position to start the remove at
  30124. * @param {Double} end
  30125. * position to end the remove at
  30126. * @param {Double} mapping
  30127. * Offset to map display time to stream presentation time
  30128. */
  30129. var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
  30130. var startPts = Math.ceil((start - mapping) * 90000);
  30131. var endPts = Math.ceil((end - mapping) * 90000);
  30132. var updatedBuffer = buffer.slice();
  30133. var i = buffer.length;
  30134. while (i--) {
  30135. if (buffer[i].pts <= endPts) {
  30136. break;
  30137. }
  30138. }
  30139. if (i === -1) {
  30140. // no removal because end of remove range is before start of buffer
  30141. return updatedBuffer;
  30142. }
  30143. var j = i + 1;
  30144. while (j--) {
  30145. if (buffer[j].pts <= startPts) {
  30146. break;
  30147. }
  30148. } // clamp remove range start to 0 index
  30149. j = Math.max(j, 0);
  30150. updatedBuffer.splice(j, i - j + 1);
  30151. return updatedBuffer;
  30152. };
  30153. var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) {
  30154. var start = null;
  30155. var end = null;
  30156. var arity = 0;
  30157. var extents = [];
  30158. var ranges = []; // neither buffer has been created yet
  30159. if (!videoBuffer && !audioBuffer) {
  30160. return videojs$1.createTimeRange();
  30161. } // only one buffer is configured
  30162. if (!videoBuffer) {
  30163. return audioBuffer.buffered;
  30164. }
  30165. if (!audioBuffer) {
  30166. return videoBuffer.buffered;
  30167. } // both buffers are configured
  30168. if (audioDisabled) {
  30169. return videoBuffer.buffered;
  30170. } // both buffers are empty
  30171. if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) {
  30172. return videojs$1.createTimeRange();
  30173. } // Handle the case where we have both buffers and create an
  30174. // intersection of the two
  30175. var videoBuffered = videoBuffer.buffered;
  30176. var audioBuffered = audioBuffer.buffered;
  30177. var count = videoBuffered.length; // A) Gather up all start and end times
  30178. while (count--) {
  30179. extents.push({
  30180. time: videoBuffered.start(count),
  30181. type: 'start'
  30182. });
  30183. extents.push({
  30184. time: videoBuffered.end(count),
  30185. type: 'end'
  30186. });
  30187. }
  30188. count = audioBuffered.length;
  30189. while (count--) {
  30190. extents.push({
  30191. time: audioBuffered.start(count),
  30192. type: 'start'
  30193. });
  30194. extents.push({
  30195. time: audioBuffered.end(count),
  30196. type: 'end'
  30197. });
  30198. } // B) Sort them by time
  30199. extents.sort(function (a, b) {
  30200. return a.time - b.time;
  30201. }); // C) Go along one by one incrementing arity for start and decrementing
  30202. // arity for ends
  30203. for (count = 0; count < extents.length; count++) {
  30204. if (extents[count].type === 'start') {
  30205. arity++; // D) If arity is ever incremented to 2 we are entering an
  30206. // overlapping range
  30207. if (arity === 2) {
  30208. start = extents[count].time;
  30209. }
  30210. } else if (extents[count].type === 'end') {
  30211. arity--; // E) If arity is ever decremented to 1 we leaving an
  30212. // overlapping range
  30213. if (arity === 1) {
  30214. end = extents[count].time;
  30215. }
  30216. } // F) Record overlapping ranges
  30217. if (start !== null && end !== null) {
  30218. ranges.push([start, end]);
  30219. start = null;
  30220. end = null;
  30221. }
  30222. }
  30223. return videojs$1.createTimeRanges(ranges);
  30224. };
  30225. /**
  30226. * @file virtual-source-buffer.js
  30227. */
  30228. var ONE_SECOND_IN_TS = 90000; // We create a wrapper around the SourceBuffer so that we can manage the
  30229. // state of the `updating` property manually. We have to do this because
  30230. // Firefox changes `updating` to false long before triggering `updateend`
  30231. // events and that was causing strange problems in videojs-contrib-hls
  30232. var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) {
  30233. var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
  30234. var wrapper = Object.create(null);
  30235. wrapper.updating = false;
  30236. wrapper.realBuffer_ = sourceBuffer;
  30237. var _loop = function _loop(key) {
  30238. if (typeof sourceBuffer[key] === 'function') {
  30239. wrapper[key] = function () {
  30240. return sourceBuffer[key].apply(sourceBuffer, arguments);
  30241. };
  30242. } else if (typeof wrapper[key] === 'undefined') {
  30243. Object.defineProperty(wrapper, key, {
  30244. get: function get$$1() {
  30245. return sourceBuffer[key];
  30246. },
  30247. set: function set$$1(v) {
  30248. return sourceBuffer[key] = v;
  30249. }
  30250. });
  30251. }
  30252. };
  30253. for (var key in sourceBuffer) {
  30254. _loop(key);
  30255. }
  30256. return wrapper;
  30257. };
  30258. /**
  30259. * VirtualSourceBuffers exist so that we can transmux non native formats
  30260. * into a native format, but keep the same api as a native source buffer.
  30261. * It creates a transmuxer, that works in its own thread (a web worker) and
  30262. * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
  30263. * then send all of that data to the naive sourcebuffer so that it is
  30264. * indestinguishable from a natively supported format.
  30265. *
  30266. * @param {HtmlMediaSource} mediaSource the parent mediaSource
  30267. * @param {Array} codecs array of codecs that we will be dealing with
  30268. * @class VirtualSourceBuffer
  30269. * @extends video.js.EventTarget
  30270. */
  30271. var VirtualSourceBuffer = function (_videojs$EventTarget) {
  30272. inherits(VirtualSourceBuffer, _videojs$EventTarget);
  30273. function VirtualSourceBuffer(mediaSource, codecs) {
  30274. classCallCheck(this, VirtualSourceBuffer);
  30275. var _this = possibleConstructorReturn(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget));
  30276. _this.timestampOffset_ = 0;
  30277. _this.pendingBuffers_ = [];
  30278. _this.bufferUpdating_ = false;
  30279. _this.mediaSource_ = mediaSource;
  30280. _this.codecs_ = codecs;
  30281. _this.audioCodec_ = null;
  30282. _this.videoCodec_ = null;
  30283. _this.audioDisabled_ = false;
  30284. _this.appendAudioInitSegment_ = true;
  30285. _this.gopBuffer_ = [];
  30286. _this.timeMapping_ = 0;
  30287. _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
  30288. var options = {
  30289. remux: false,
  30290. alignGopsAtEnd: _this.safeAppend_
  30291. };
  30292. _this.codecs_.forEach(function (codec) {
  30293. if (isAudioCodec(codec)) {
  30294. _this.audioCodec_ = codec;
  30295. } else if (isVideoCodec(codec)) {
  30296. _this.videoCodec_ = codec;
  30297. }
  30298. }); // append muxed segments to their respective native buffers as
  30299. // soon as they are available
  30300. _this.transmuxer_ = new TransmuxWorker();
  30301. _this.transmuxer_.postMessage({
  30302. action: 'init',
  30303. options: options
  30304. });
  30305. _this.transmuxer_.onmessage = function (event) {
  30306. if (event.data.action === 'data') {
  30307. return _this.data_(event);
  30308. }
  30309. if (event.data.action === 'done') {
  30310. return _this.done_(event);
  30311. }
  30312. if (event.data.action === 'gopInfo') {
  30313. return _this.appendGopInfo_(event);
  30314. }
  30315. if (event.data.action === 'videoSegmentTimingInfo') {
  30316. return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo);
  30317. }
  30318. }; // this timestampOffset is a property with the side-effect of resetting
  30319. // baseMediaDecodeTime in the transmuxer on the setter
  30320. Object.defineProperty(_this, 'timestampOffset', {
  30321. get: function get$$1() {
  30322. return this.timestampOffset_;
  30323. },
  30324. set: function set$$1(val) {
  30325. if (typeof val === 'number' && val >= 0) {
  30326. this.timestampOffset_ = val;
  30327. this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline
  30328. this.gopBuffer_.length = 0;
  30329. this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to
  30330. // the desired timestampOffset for the next segment
  30331. this.transmuxer_.postMessage({
  30332. action: 'setTimestampOffset',
  30333. timestampOffset: val
  30334. });
  30335. }
  30336. }
  30337. }); // setting the append window affects both source buffers
  30338. Object.defineProperty(_this, 'appendWindowStart', {
  30339. get: function get$$1() {
  30340. return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
  30341. },
  30342. set: function set$$1(start) {
  30343. if (this.videoBuffer_) {
  30344. this.videoBuffer_.appendWindowStart = start;
  30345. }
  30346. if (this.audioBuffer_) {
  30347. this.audioBuffer_.appendWindowStart = start;
  30348. }
  30349. }
  30350. }); // this buffer is "updating" if either of its native buffers are
  30351. Object.defineProperty(_this, 'updating', {
  30352. get: function get$$1() {
  30353. return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
  30354. }
  30355. }); // the buffered property is the intersection of the buffered
  30356. // ranges of the native source buffers
  30357. Object.defineProperty(_this, 'buffered', {
  30358. get: function get$$1() {
  30359. return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_);
  30360. }
  30361. });
  30362. return _this;
  30363. }
  30364. /**
  30365. * When we get a data event from the transmuxer
  30366. * we call this function and handle the data that
  30367. * was sent to us
  30368. *
  30369. * @private
  30370. * @param {Event} event the data event from the transmuxer
  30371. */
  30372. createClass(VirtualSourceBuffer, [{
  30373. key: 'data_',
  30374. value: function data_(event) {
  30375. var segment = event.data.segment; // Cast ArrayBuffer to TypedArray
  30376. segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
  30377. segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength);
  30378. createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array
  30379. this.pendingBuffers_.push(segment);
  30380. return;
  30381. }
  30382. /**
  30383. * When we get a done event from the transmuxer
  30384. * we call this function and we process all
  30385. * of the pending data that we have been saving in the
  30386. * data_ function
  30387. *
  30388. * @private
  30389. * @param {Event} event the done event from the transmuxer
  30390. */
  30391. }, {
  30392. key: 'done_',
  30393. value: function done_(event) {
  30394. // Don't process and append data if the mediaSource is closed
  30395. if (this.mediaSource_.readyState === 'closed') {
  30396. this.pendingBuffers_.length = 0;
  30397. return;
  30398. } // All buffers should have been flushed from the muxer
  30399. // start processing anything we have received
  30400. this.processPendingSegments_();
  30401. return;
  30402. }
  30403. }, {
  30404. key: 'videoSegmentTimingInfo_',
  30405. value: function videoSegmentTimingInfo_(timingInfo) {
  30406. var timingInfoInSeconds = {
  30407. start: {
  30408. decode: timingInfo.start.dts / ONE_SECOND_IN_TS,
  30409. presentation: timingInfo.start.pts / ONE_SECOND_IN_TS
  30410. },
  30411. end: {
  30412. decode: timingInfo.end.dts / ONE_SECOND_IN_TS,
  30413. presentation: timingInfo.end.pts / ONE_SECOND_IN_TS
  30414. },
  30415. baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS
  30416. };
  30417. if (timingInfo.prependedContentDuration) {
  30418. timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS;
  30419. }
  30420. this.trigger({
  30421. type: 'videoSegmentTimingInfo',
  30422. videoSegmentTimingInfo: timingInfoInSeconds
  30423. });
  30424. }
  30425. /**
  30426. * Create our internal native audio/video source buffers and add
  30427. * event handlers to them with the following conditions:
  30428. * 1. they do not already exist on the mediaSource
  30429. * 2. this VSB has a codec for them
  30430. *
  30431. * @private
  30432. */
  30433. }, {
  30434. key: 'createRealSourceBuffers_',
  30435. value: function createRealSourceBuffers_() {
  30436. var _this2 = this;
  30437. var types = ['audio', 'video'];
  30438. types.forEach(function (type) {
  30439. // Don't create a SourceBuffer of this type if we don't have a
  30440. // codec for it
  30441. if (!_this2[type + 'Codec_']) {
  30442. return;
  30443. } // Do nothing if a SourceBuffer of this type already exists
  30444. if (_this2[type + 'Buffer_']) {
  30445. return;
  30446. }
  30447. var buffer = null; // If the mediasource already has a SourceBuffer for the codec
  30448. // use that
  30449. if (_this2.mediaSource_[type + 'Buffer_']) {
  30450. buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled
  30451. // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
  30452. // than createRealSourceBuffers_ is called to create the second
  30453. // VirtualSourceBuffer because that happens as a side-effect of
  30454. // videojs-contrib-hls starting the audioSegmentLoader. As a result,
  30455. // the audioBuffer is essentially "ownerless" and no one will toggle
  30456. // the `updating` state back to false once the `updateend` event is received
  30457. //
  30458. // Setting `updating` to false manually will work around this
  30459. // situation and allow work to continue
  30460. buffer.updating = false;
  30461. } else {
  30462. var codecProperty = type + 'Codec_';
  30463. var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"';
  30464. buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType);
  30465. _this2.mediaSource_[type + 'Buffer_'] = buffer;
  30466. }
  30467. _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer
  30468. ['update', 'updatestart', 'updateend'].forEach(function (event) {
  30469. buffer.addEventListener(event, function () {
  30470. // if audio is disabled
  30471. if (type === 'audio' && _this2.audioDisabled_) {
  30472. return;
  30473. }
  30474. if (event === 'updateend') {
  30475. _this2[type + 'Buffer_'].updating = false;
  30476. }
  30477. var shouldTrigger = types.every(function (t) {
  30478. // skip checking audio's updating status if audio
  30479. // is not enabled
  30480. if (t === 'audio' && _this2.audioDisabled_) {
  30481. return true;
  30482. } // if the other type is updating we don't trigger
  30483. if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
  30484. return false;
  30485. }
  30486. return true;
  30487. });
  30488. if (shouldTrigger) {
  30489. return _this2.trigger(event);
  30490. }
  30491. });
  30492. });
  30493. });
  30494. }
  30495. /**
  30496. * Emulate the native mediasource function, but our function will
  30497. * send all of the proposed segments to the transmuxer so that we
  30498. * can transmux them before we append them to our internal
  30499. * native source buffers in the correct format.
  30500. *
  30501. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
  30502. * @param {Uint8Array} segment the segment to append to the buffer
  30503. */
  30504. }, {
  30505. key: 'appendBuffer',
  30506. value: function appendBuffer(segment) {
  30507. // Start the internal "updating" state
  30508. this.bufferUpdating_ = true;
  30509. if (this.audioBuffer_ && this.audioBuffer_.buffered.length) {
  30510. var audioBuffered = this.audioBuffer_.buffered;
  30511. this.transmuxer_.postMessage({
  30512. action: 'setAudioAppendStart',
  30513. appendStart: audioBuffered.end(audioBuffered.length - 1)
  30514. });
  30515. }
  30516. if (this.videoBuffer_) {
  30517. this.transmuxer_.postMessage({
  30518. action: 'alignGopsWith',
  30519. gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_)
  30520. });
  30521. }
  30522. this.transmuxer_.postMessage({
  30523. action: 'push',
  30524. // Send the typed-array of data as an ArrayBuffer so that
  30525. // it can be sent as a "Transferable" and avoid the costly
  30526. // memory copy
  30527. data: segment.buffer,
  30528. // To recreate the original typed-array, we need information
  30529. // about what portion of the ArrayBuffer it was a view into
  30530. byteOffset: segment.byteOffset,
  30531. byteLength: segment.byteLength
  30532. }, [segment.buffer]);
  30533. this.transmuxer_.postMessage({
  30534. action: 'flush'
  30535. });
  30536. }
  30537. /**
  30538. * Appends gop information (timing and byteLength) received by the transmuxer for the
  30539. * gops appended in the last call to appendBuffer
  30540. *
  30541. * @param {Event} event
  30542. * The gopInfo event from the transmuxer
  30543. * @param {Array} event.data.gopInfo
  30544. * List of gop info to append
  30545. */
  30546. }, {
  30547. key: 'appendGopInfo_',
  30548. value: function appendGopInfo_(event) {
  30549. this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_);
  30550. }
  30551. /**
  30552. * Emulate the native mediasource function and remove parts
  30553. * of the buffer from any of our internal buffers that exist
  30554. *
  30555. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
  30556. * @param {Double} start position to start the remove at
  30557. * @param {Double} end position to end the remove at
  30558. */
  30559. }, {
  30560. key: 'remove',
  30561. value: function remove(start, end) {
  30562. if (this.videoBuffer_) {
  30563. this.videoBuffer_.updating = true;
  30564. this.videoBuffer_.remove(start, end);
  30565. this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
  30566. }
  30567. if (!this.audioDisabled_ && this.audioBuffer_) {
  30568. this.audioBuffer_.updating = true;
  30569. this.audioBuffer_.remove(start, end);
  30570. } // Remove Metadata Cues (id3)
  30571. removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions
  30572. if (this.inbandTextTracks_) {
  30573. for (var track in this.inbandTextTracks_) {
  30574. removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
  30575. }
  30576. }
  30577. }
  30578. /**
  30579. * Process any segments that the muxer has output
  30580. * Concatenate segments together based on type and append them into
  30581. * their respective sourceBuffers
  30582. *
  30583. * @private
  30584. */
  30585. }, {
  30586. key: 'processPendingSegments_',
  30587. value: function processPendingSegments_() {
  30588. var sortedSegments = {
  30589. video: {
  30590. segments: [],
  30591. bytes: 0
  30592. },
  30593. audio: {
  30594. segments: [],
  30595. bytes: 0
  30596. },
  30597. captions: [],
  30598. metadata: []
  30599. }; // Sort segments into separate video/audio arrays and
  30600. // keep track of their total byte lengths
  30601. sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
  30602. var type = segment.type;
  30603. var data = segment.data;
  30604. var initSegment = segment.initSegment;
  30605. segmentObj[type].segments.push(data);
  30606. segmentObj[type].bytes += data.byteLength;
  30607. segmentObj[type].initSegment = initSegment; // Gather any captions into a single array
  30608. if (segment.captions) {
  30609. segmentObj.captions = segmentObj.captions.concat(segment.captions);
  30610. }
  30611. if (segment.info) {
  30612. segmentObj[type].info = segment.info;
  30613. } // Gather any metadata into a single array
  30614. if (segment.metadata) {
  30615. segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
  30616. }
  30617. return segmentObj;
  30618. }, sortedSegments); // Create the real source buffers if they don't exist by now since we
  30619. // finally are sure what tracks are contained in the source
  30620. if (!this.videoBuffer_ && !this.audioBuffer_) {
  30621. // Remove any codecs that may have been specified by default but
  30622. // are no longer applicable now
  30623. if (sortedSegments.video.bytes === 0) {
  30624. this.videoCodec_ = null;
  30625. }
  30626. if (sortedSegments.audio.bytes === 0) {
  30627. this.audioCodec_ = null;
  30628. }
  30629. this.createRealSourceBuffers_();
  30630. }
  30631. if (sortedSegments.audio.info) {
  30632. this.mediaSource_.trigger({
  30633. type: 'audioinfo',
  30634. info: sortedSegments.audio.info
  30635. });
  30636. }
  30637. if (sortedSegments.video.info) {
  30638. this.mediaSource_.trigger({
  30639. type: 'videoinfo',
  30640. info: sortedSegments.video.info
  30641. });
  30642. }
  30643. if (this.appendAudioInitSegment_) {
  30644. if (!this.audioDisabled_ && this.audioBuffer_) {
  30645. sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
  30646. sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
  30647. }
  30648. this.appendAudioInitSegment_ = false;
  30649. }
  30650. var triggerUpdateend = false; // Merge multiple video and audio segments into one and append
  30651. if (this.videoBuffer_ && sortedSegments.video.bytes) {
  30652. sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
  30653. sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
  30654. this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
  30655. } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
  30656. // The transmuxer did not return any bytes of video, meaning it was all trimmed
  30657. // for gop alignment. Since we have a video buffer and audio is disabled, updateend
  30658. // will never be triggered by this source buffer, which will cause contrib-hls
  30659. // to be stuck forever waiting for updateend. If audio is not disabled, updateend
  30660. // will be triggered by the audio buffer, which will be sent upwards since the video
  30661. // buffer will not be in an updating state.
  30662. triggerUpdateend = true;
  30663. } // Add text-track data for all
  30664. addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
  30665. if (!this.audioDisabled_ && this.audioBuffer_) {
  30666. this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
  30667. }
  30668. this.pendingBuffers_.length = 0;
  30669. if (triggerUpdateend) {
  30670. this.trigger('updateend');
  30671. } // We are no longer in the internal "updating" state
  30672. this.bufferUpdating_ = false;
  30673. }
  30674. /**
  30675. * Combine all segments into a single Uint8Array and then append them
  30676. * to the destination buffer
  30677. *
  30678. * @param {Object} segmentObj
  30679. * @param {SourceBuffer} destinationBuffer native source buffer to append data to
  30680. * @private
  30681. */
  30682. }, {
  30683. key: 'concatAndAppendSegments_',
  30684. value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
  30685. var offset = 0;
  30686. var tempBuffer = void 0;
  30687. if (segmentObj.bytes) {
  30688. tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array
  30689. segmentObj.segments.forEach(function (segment) {
  30690. tempBuffer.set(segment, offset);
  30691. offset += segment.byteLength;
  30692. });
  30693. try {
  30694. destinationBuffer.updating = true;
  30695. destinationBuffer.appendBuffer(tempBuffer);
  30696. } catch (error) {
  30697. if (this.mediaSource_.player_) {
  30698. this.mediaSource_.player_.error({
  30699. code: -3,
  30700. type: 'APPEND_BUFFER_ERR',
  30701. message: error.message,
  30702. originalError: error
  30703. });
  30704. }
  30705. }
  30706. }
  30707. }
  30708. /**
  30709. * Emulate the native mediasource function. abort any soureBuffer
  30710. * actions and throw out any un-appended data.
  30711. *
  30712. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
  30713. */
  30714. }, {
  30715. key: 'abort',
  30716. value: function abort() {
  30717. if (this.videoBuffer_) {
  30718. this.videoBuffer_.abort();
  30719. }
  30720. if (!this.audioDisabled_ && this.audioBuffer_) {
  30721. this.audioBuffer_.abort();
  30722. }
  30723. if (this.transmuxer_) {
  30724. this.transmuxer_.postMessage({
  30725. action: 'reset'
  30726. });
  30727. }
  30728. this.pendingBuffers_.length = 0;
  30729. this.bufferUpdating_ = false;
  30730. }
  30731. }]);
  30732. return VirtualSourceBuffer;
  30733. }(videojs$1.EventTarget);
  30734. /**
  30735. * @file html-media-source.js
  30736. */
  30737. /**
  30738. * Our MediaSource implementation in HTML, mimics native
  30739. * MediaSource where/if possible.
  30740. *
  30741. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
  30742. * @class HtmlMediaSource
  30743. * @extends videojs.EventTarget
  30744. */
  30745. var HtmlMediaSource = function (_videojs$EventTarget) {
  30746. inherits(HtmlMediaSource, _videojs$EventTarget);
  30747. function HtmlMediaSource() {
  30748. classCallCheck(this, HtmlMediaSource);
  30749. var _this = possibleConstructorReturn(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this));
  30750. var property = void 0;
  30751. _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default
  30752. for (property in _this.nativeMediaSource_) {
  30753. if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') {
  30754. _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_);
  30755. }
  30756. } // emulate `duration` and `seekable` until seeking can be
  30757. // handled uniformly for live streams
  30758. // see https://github.com/w3c/media-source/issues/5
  30759. _this.duration_ = NaN;
  30760. Object.defineProperty(_this, 'duration', {
  30761. get: function get$$1() {
  30762. if (this.duration_ === Infinity) {
  30763. return this.duration_;
  30764. }
  30765. return this.nativeMediaSource_.duration;
  30766. },
  30767. set: function set$$1(duration) {
  30768. this.duration_ = duration;
  30769. if (duration !== Infinity) {
  30770. this.nativeMediaSource_.duration = duration;
  30771. return;
  30772. }
  30773. }
  30774. });
  30775. Object.defineProperty(_this, 'seekable', {
  30776. get: function get$$1() {
  30777. if (this.duration_ === Infinity) {
  30778. return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
  30779. }
  30780. return this.nativeMediaSource_.seekable;
  30781. }
  30782. });
  30783. Object.defineProperty(_this, 'readyState', {
  30784. get: function get$$1() {
  30785. return this.nativeMediaSource_.readyState;
  30786. }
  30787. });
  30788. Object.defineProperty(_this, 'activeSourceBuffers', {
  30789. get: function get$$1() {
  30790. return this.activeSourceBuffers_;
  30791. }
  30792. }); // the list of virtual and native SourceBuffers created by this
  30793. // MediaSource
  30794. _this.sourceBuffers = [];
  30795. _this.activeSourceBuffers_ = [];
  30796. /**
  30797. * update the list of active source buffers based upon various
  30798. * imformation from HLS and video.js
  30799. *
  30800. * @private
  30801. */
  30802. _this.updateActiveSourceBuffers_ = function () {
  30803. // Retain the reference but empty the array
  30804. _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will
  30805. // be disabled based on the codec of the source buffer
  30806. if (_this.sourceBuffers.length === 1) {
  30807. var sourceBuffer = _this.sourceBuffers[0];
  30808. sourceBuffer.appendAudioInitSegment_ = true;
  30809. sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
  30810. _this.activeSourceBuffers_.push(sourceBuffer);
  30811. return;
  30812. } // There are 2 source buffers, a combined (possibly video only) source buffer and
  30813. // and an audio only source buffer.
  30814. // By default, the audio in the combined virtual source buffer is enabled
  30815. // and the audio-only source buffer (if it exists) is disabled.
  30816. var disableCombined = false;
  30817. var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects?
  30818. // safari may do something like this
  30819. for (var i = 0; i < _this.player_.audioTracks().length; i++) {
  30820. var track = _this.player_.audioTracks()[i];
  30821. if (track.enabled && track.kind !== 'main') {
  30822. // The enabled track is an alternate audio track so disable the audio in
  30823. // the combined source buffer and enable the audio-only source buffer.
  30824. disableCombined = true;
  30825. disableAudioOnly = false;
  30826. break;
  30827. }
  30828. }
  30829. _this.sourceBuffers.forEach(function (sourceBuffer, index) {
  30830. /* eslinst-disable */
  30831. // TODO once codecs are required, we can switch to using the codecs to determine
  30832. // what stream is the video stream, rather than relying on videoTracks
  30833. /* eslinst-enable */
  30834. sourceBuffer.appendAudioInitSegment_ = true;
  30835. if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  30836. // combined
  30837. sourceBuffer.audioDisabled_ = disableCombined;
  30838. } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
  30839. // If the "combined" source buffer is video only, then we do not want
  30840. // disable the audio-only source buffer (this is mostly for demuxed
  30841. // audio and video hls)
  30842. sourceBuffer.audioDisabled_ = true;
  30843. disableAudioOnly = false;
  30844. } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  30845. // audio only
  30846. // In the case of audio only with alternate audio and disableAudioOnly is true
  30847. // this means we want to disable the audio on the alternate audio sourcebuffer
  30848. // but not the main "combined" source buffer. The "combined" source buffer is
  30849. // always at index 0, so this ensures audio won't be disabled in both source
  30850. // buffers.
  30851. sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly;
  30852. if (sourceBuffer.audioDisabled_) {
  30853. return;
  30854. }
  30855. }
  30856. _this.activeSourceBuffers_.push(sourceBuffer);
  30857. });
  30858. };
  30859. _this.onPlayerMediachange_ = function () {
  30860. _this.sourceBuffers.forEach(function (sourceBuffer) {
  30861. sourceBuffer.appendAudioInitSegment_ = true;
  30862. });
  30863. };
  30864. _this.onHlsReset_ = function () {
  30865. _this.sourceBuffers.forEach(function (sourceBuffer) {
  30866. if (sourceBuffer.transmuxer_) {
  30867. sourceBuffer.transmuxer_.postMessage({
  30868. action: 'resetCaptions'
  30869. });
  30870. }
  30871. });
  30872. };
  30873. _this.onHlsSegmentTimeMapping_ = function (event) {
  30874. _this.sourceBuffers.forEach(function (buffer) {
  30875. return buffer.timeMapping_ = event.mapping;
  30876. });
  30877. }; // Re-emit MediaSource events on the polyfill
  30878. ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
  30879. this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
  30880. }, _this); // capture the associated player when the MediaSource is
  30881. // successfully attached
  30882. _this.on('sourceopen', function (event) {
  30883. // Get the player this MediaSource is attached to
  30884. var video = document.querySelector('[src="' + _this.url_ + '"]');
  30885. if (!video) {
  30886. return;
  30887. }
  30888. _this.player_ = videojs$1(video.parentNode);
  30889. if (!_this.player_) {
  30890. return;
  30891. } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
  30892. // resets its state and flushes the buffer
  30893. _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
  30894. // SegmentLoader inspects an MTS segment and has an accurate stream to display
  30895. // time mapping
  30896. _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_);
  30897. if (_this.player_.audioTracks && _this.player_.audioTracks()) {
  30898. _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
  30899. _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
  30900. _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
  30901. }
  30902. _this.player_.on('mediachange', _this.onPlayerMediachange_);
  30903. });
  30904. _this.on('sourceended', function (event) {
  30905. var duration = durationOfVideo(_this.duration);
  30906. for (var i = 0; i < _this.sourceBuffers.length; i++) {
  30907. var sourcebuffer = _this.sourceBuffers[i];
  30908. var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues;
  30909. if (cues && cues.length) {
  30910. cues[cues.length - 1].endTime = duration;
  30911. }
  30912. }
  30913. }); // explicitly terminate any WebWorkers that were created
  30914. // by SourceHandlers
  30915. _this.on('sourceclose', function (event) {
  30916. this.sourceBuffers.forEach(function (sourceBuffer) {
  30917. if (sourceBuffer.transmuxer_) {
  30918. sourceBuffer.transmuxer_.terminate();
  30919. }
  30920. });
  30921. this.sourceBuffers.length = 0;
  30922. if (!this.player_) {
  30923. return;
  30924. }
  30925. if (this.player_.audioTracks && this.player_.audioTracks()) {
  30926. this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
  30927. this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
  30928. this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
  30929. } // We can only change this if the player hasn't been disposed of yet
  30930. // because `off` eventually tries to use the el_ property. If it has
  30931. // been disposed of, then don't worry about it because there are no
  30932. // event handlers left to unbind anyway
  30933. if (this.player_.el_) {
  30934. this.player_.off('mediachange', this.onPlayerMediachange_);
  30935. }
  30936. if (this.player_.tech_ && this.player_.tech_.el_) {
  30937. this.player_.tech_.off('hls-reset', this.onHlsReset_);
  30938. this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
  30939. }
  30940. });
  30941. return _this;
  30942. }
  30943. /**
  30944. * Add a range that that can now be seeked to.
  30945. *
  30946. * @param {Double} start where to start the addition
  30947. * @param {Double} end where to end the addition
  30948. * @private
  30949. */
  30950. createClass(HtmlMediaSource, [{
  30951. key: 'addSeekableRange_',
  30952. value: function addSeekableRange_(start, end) {
  30953. var error = void 0;
  30954. if (this.duration !== Infinity) {
  30955. error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
  30956. error.name = 'InvalidStateError';
  30957. error.code = 11;
  30958. throw error;
  30959. }
  30960. if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
  30961. this.nativeMediaSource_.duration = end;
  30962. }
  30963. }
  30964. /**
  30965. * Add a source buffer to the media source.
  30966. *
  30967. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
  30968. * @param {String} type the content-type of the content
  30969. * @return {Object} the created source buffer
  30970. */
  30971. }, {
  30972. key: 'addSourceBuffer',
  30973. value: function addSourceBuffer(type) {
  30974. var buffer = void 0;
  30975. var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport
  30976. // stream segments into fragmented MP4s
  30977. if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) {
  30978. var codecs = [];
  30979. if (parsedType.parameters && parsedType.parameters.codecs) {
  30980. codecs = parsedType.parameters.codecs.split(',');
  30981. codecs = translateLegacyCodecs(codecs);
  30982. codecs = codecs.filter(function (codec) {
  30983. return isAudioCodec(codec) || isVideoCodec(codec);
  30984. });
  30985. }
  30986. if (codecs.length === 0) {
  30987. codecs = ['avc1.4d400d', 'mp4a.40.2'];
  30988. }
  30989. buffer = new VirtualSourceBuffer(this, codecs);
  30990. if (this.sourceBuffers.length !== 0) {
  30991. // If another VirtualSourceBuffer already exists, then we are creating a
  30992. // SourceBuffer for an alternate audio track and therefore we know that
  30993. // the source has both an audio and video track.
  30994. // That means we should trigger the manual creation of the real
  30995. // SourceBuffers instead of waiting for the transmuxer to return data
  30996. this.sourceBuffers[0].createRealSourceBuffers_();
  30997. buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if
  30998. // a second source buffer is ever created
  30999. this.sourceBuffers[0].audioDisabled_ = true;
  31000. }
  31001. } else {
  31002. // delegate to the native implementation
  31003. buffer = this.nativeMediaSource_.addSourceBuffer(type);
  31004. }
  31005. this.sourceBuffers.push(buffer);
  31006. return buffer;
  31007. }
  31008. }]);
  31009. return HtmlMediaSource;
  31010. }(videojs$1.EventTarget);
  31011. /**
  31012. * @file videojs-contrib-media-sources.js
  31013. */
  31014. var urlCount = 0; // ------------
  31015. // Media Source
  31016. // ------------
  31017. // store references to the media sources so they can be connected
  31018. // to a video element (a swf object)
  31019. // TODO: can we store this somewhere local to this module?
  31020. videojs$1.mediaSources = {};
  31021. /**
  31022. * Provide a method for a swf object to notify JS that a
  31023. * media source is now open.
  31024. *
  31025. * @param {String} msObjectURL string referencing the MSE Object URL
  31026. * @param {String} swfId the swf id
  31027. */
  31028. var open = function open(msObjectURL, swfId) {
  31029. var mediaSource = videojs$1.mediaSources[msObjectURL];
  31030. if (mediaSource) {
  31031. mediaSource.trigger({
  31032. type: 'sourceopen',
  31033. swfId: swfId
  31034. });
  31035. } else {
  31036. throw new Error('Media Source not found (Video.js)');
  31037. }
  31038. };
  31039. /**
  31040. * Check to see if the native MediaSource object exists and supports
  31041. * an MP4 container with both H.264 video and AAC-LC audio.
  31042. *
  31043. * @return {Boolean} if native media sources are supported
  31044. */
  31045. var supportsNativeMediaSources = function supportsNativeMediaSources() {
  31046. return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
  31047. };
  31048. /**
  31049. * An emulation of the MediaSource API so that we can support
  31050. * native and non-native functionality. returns an instance of
  31051. * HtmlMediaSource.
  31052. *
  31053. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
  31054. */
  31055. var MediaSource = function MediaSource() {
  31056. this.MediaSource = {
  31057. open: open,
  31058. supportsNativeMediaSources: supportsNativeMediaSources
  31059. };
  31060. if (supportsNativeMediaSources()) {
  31061. return new HtmlMediaSource();
  31062. }
  31063. throw new Error('Cannot use create a virtual MediaSource for this video');
  31064. };
  31065. MediaSource.open = open;
  31066. MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
  31067. /**
  31068. * A wrapper around the native URL for our MSE object
  31069. * implementation, this object is exposed under videojs.URL
  31070. *
  31071. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
  31072. */
  31073. var URL$1 = {
  31074. /**
  31075. * A wrapper around the native createObjectURL for our objects.
  31076. * This function maps a native or emulated mediaSource to a blob
  31077. * url so that it can be loaded into video.js
  31078. *
  31079. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
  31080. * @param {MediaSource} object the object to create a blob url to
  31081. */
  31082. createObjectURL: function createObjectURL(object) {
  31083. var objectUrlPrefix = 'blob:vjs-media-source/';
  31084. var url = void 0; // use the native MediaSource to generate an object URL
  31085. if (object instanceof HtmlMediaSource) {
  31086. url = window$1.URL.createObjectURL(object.nativeMediaSource_);
  31087. object.url_ = url;
  31088. return url;
  31089. } // if the object isn't an emulated MediaSource, delegate to the
  31090. // native implementation
  31091. if (!(object instanceof HtmlMediaSource)) {
  31092. url = window$1.URL.createObjectURL(object);
  31093. object.url_ = url;
  31094. return url;
  31095. } // build a URL that can be used to map back to the emulated
  31096. // MediaSource
  31097. url = objectUrlPrefix + urlCount;
  31098. urlCount++; // setup the mapping back to object
  31099. videojs$1.mediaSources[url] = object;
  31100. return url;
  31101. }
  31102. };
  31103. videojs$1.MediaSource = MediaSource;
  31104. videojs$1.URL = URL$1;
  31105. var EventTarget$1$1 = videojs$1.EventTarget,
  31106. mergeOptions$2 = videojs$1.mergeOptions;
  31107. /**
  31108. * Returns a new master manifest that is the result of merging an updated master manifest
  31109. * into the original version.
  31110. *
  31111. * @param {Object} oldMaster
  31112. * The old parsed mpd object
  31113. * @param {Object} newMaster
  31114. * The updated parsed mpd object
  31115. * @return {Object}
  31116. * A new object representing the original master manifest with the updated media
  31117. * playlists merged in
  31118. */
  31119. var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) {
  31120. var noChanges = void 0;
  31121. var update = mergeOptions$2(oldMaster, {
  31122. // These are top level properties that can be updated
  31123. duration: newMaster.duration,
  31124. minimumUpdatePeriod: newMaster.minimumUpdatePeriod
  31125. }); // First update the playlists in playlist list
  31126. for (var i = 0; i < newMaster.playlists.length; i++) {
  31127. var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
  31128. if (playlistUpdate) {
  31129. update = playlistUpdate;
  31130. } else {
  31131. noChanges = true;
  31132. }
  31133. } // Then update media group playlists
  31134. forEachMediaGroup(newMaster, function (properties, type, group, label) {
  31135. if (properties.playlists && properties.playlists.length) {
  31136. var uri = properties.playlists[0].uri;
  31137. var _playlistUpdate = updateMaster(update, properties.playlists[0]);
  31138. if (_playlistUpdate) {
  31139. update = _playlistUpdate; // update the playlist reference within media groups
  31140. update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri];
  31141. noChanges = false;
  31142. }
  31143. }
  31144. });
  31145. if (noChanges) {
  31146. return null;
  31147. }
  31148. return update;
  31149. };
  31150. var generateSidxKey = function generateSidxKey(sidxInfo) {
  31151. // should be non-inclusive
  31152. var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1;
  31153. return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd;
  31154. }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
  31155. // If the SIDXs have maps, the two maps should match,
  31156. // both `a` and `b` missing SIDXs is considered matching.
  31157. // If `a` or `b` but not both have a map, they aren't matching.
  31158. var equivalentSidx = function equivalentSidx(a, b) {
  31159. var neitherMap = Boolean(!a.map && !b.map);
  31160. var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
  31161. return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
  31162. }; // exported for testing
  31163. var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) {
  31164. var newSidxMapping = {};
  31165. for (var uri in playlists) {
  31166. var playlist = playlists[uri];
  31167. var currentSidxInfo = playlist.sidx;
  31168. if (currentSidxInfo) {
  31169. var key = generateSidxKey(currentSidxInfo);
  31170. if (!oldSidxMapping[key]) {
  31171. break;
  31172. }
  31173. var savedSidxInfo = oldSidxMapping[key].sidxInfo;
  31174. if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
  31175. newSidxMapping[key] = oldSidxMapping[key];
  31176. }
  31177. }
  31178. }
  31179. return newSidxMapping;
  31180. };
  31181. /**
  31182. * A function that filters out changed items as they need to be requested separately.
  31183. *
  31184. * The method is exported for testing
  31185. *
  31186. * @param {Object} masterXml the mpd XML
  31187. * @param {string} srcUrl the mpd url
  31188. * @param {Date} clientOffset a time difference between server and client (passed through and not used)
  31189. * @param {Object} oldSidxMapping the SIDX to compare against
  31190. */
  31191. var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) {
  31192. // Don't pass current sidx mapping
  31193. var master = parse(masterXml, {
  31194. manifestUri: srcUrl,
  31195. clientOffset: clientOffset
  31196. });
  31197. var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping);
  31198. var mediaGroupSidx = videoSidx;
  31199. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  31200. if (properties.playlists && properties.playlists.length) {
  31201. var playlists = properties.playlists;
  31202. mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
  31203. }
  31204. });
  31205. return mediaGroupSidx;
  31206. }; // exported for testing
  31207. var requestSidx_ = function requestSidx_(sidxRange, playlist, xhr$$1, options, finishProcessingFn) {
  31208. var sidxInfo = {
  31209. // resolve the segment URL relative to the playlist
  31210. uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri),
  31211. // resolvedUri: sidxRange.resolvedUri,
  31212. byterange: sidxRange.byterange,
  31213. // the segment's playlist
  31214. playlist: playlist
  31215. };
  31216. var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, {
  31217. responseType: 'arraybuffer',
  31218. headers: segmentXhrHeaders(sidxInfo)
  31219. });
  31220. return xhr$$1(sidxRequestOptions, finishProcessingFn);
  31221. };
  31222. var DashPlaylistLoader = function (_EventTarget) {
  31223. inherits(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
  31224. // playlist loader setups from media groups will expect to be able to pass a playlist
  31225. // (since there aren't external URLs to media playlists with DASH)
  31226. function DashPlaylistLoader(srcUrlOrPlaylist, hls) {
  31227. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  31228. var masterPlaylistLoader = arguments[3];
  31229. classCallCheck(this, DashPlaylistLoader);
  31230. var _this = possibleConstructorReturn(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this));
  31231. var _options$withCredenti = options.withCredentials,
  31232. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  31233. _options$handleManife = options.handleManifestRedirects,
  31234. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  31235. _this.hls_ = hls;
  31236. _this.withCredentials = withCredentials;
  31237. _this.handleManifestRedirects = handleManifestRedirects;
  31238. if (!srcUrlOrPlaylist) {
  31239. throw new Error('A non-empty playlist URL or playlist is required');
  31240. } // event naming?
  31241. _this.on('minimumUpdatePeriod', function () {
  31242. _this.refreshXml_();
  31243. }); // live playlist staleness timeout
  31244. _this.on('mediaupdatetimeout', function () {
  31245. _this.refreshMedia_(_this.media().uri);
  31246. });
  31247. _this.state = 'HAVE_NOTHING';
  31248. _this.loadedPlaylists_ = {}; // initialize the loader state
  31249. // The masterPlaylistLoader will be created with a string
  31250. if (typeof srcUrlOrPlaylist === 'string') {
  31251. _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
  31252. // once multi-period is refactored
  31253. _this.sidxMapping_ = {};
  31254. return possibleConstructorReturn(_this);
  31255. }
  31256. _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist);
  31257. return _this;
  31258. }
  31259. createClass(DashPlaylistLoader, [{
  31260. key: 'setupChildLoader',
  31261. value: function setupChildLoader(masterPlaylistLoader, playlist) {
  31262. this.masterPlaylistLoader_ = masterPlaylistLoader;
  31263. this.childPlaylist_ = playlist;
  31264. }
  31265. }, {
  31266. key: 'dispose',
  31267. value: function dispose() {
  31268. this.stopRequest();
  31269. this.loadedPlaylists_ = {};
  31270. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  31271. window$1.clearTimeout(this.mediaRequest_);
  31272. window$1.clearTimeout(this.mediaUpdateTimeout);
  31273. }
  31274. }, {
  31275. key: 'hasPendingRequest',
  31276. value: function hasPendingRequest() {
  31277. return this.request || this.mediaRequest_;
  31278. }
  31279. }, {
  31280. key: 'stopRequest',
  31281. value: function stopRequest() {
  31282. if (this.request) {
  31283. var oldRequest = this.request;
  31284. this.request = null;
  31285. oldRequest.onreadystatechange = null;
  31286. oldRequest.abort();
  31287. }
  31288. }
  31289. }, {
  31290. key: 'sidxRequestFinished_',
  31291. value: function sidxRequestFinished_(playlist, master, startingState, doneFn) {
  31292. var _this2 = this;
  31293. return function (err, request) {
  31294. // disposed
  31295. if (!_this2.request) {
  31296. return;
  31297. } // pending request is cleared
  31298. _this2.request = null;
  31299. if (err) {
  31300. _this2.error = {
  31301. status: request.status,
  31302. message: 'DASH playlist request error at URL: ' + playlist.uri,
  31303. response: request.response,
  31304. // MEDIA_ERR_NETWORK
  31305. code: 2
  31306. };
  31307. if (startingState) {
  31308. _this2.state = startingState;
  31309. }
  31310. _this2.trigger('error');
  31311. return doneFn(master, null);
  31312. }
  31313. var bytes = new Uint8Array(request.response);
  31314. var sidx = mp4Inspector.parseSidx(bytes.subarray(8));
  31315. return doneFn(master, sidx);
  31316. };
  31317. }
  31318. }, {
  31319. key: 'media',
  31320. value: function media(playlist) {
  31321. var _this3 = this; // getter
  31322. if (!playlist) {
  31323. return this.media_;
  31324. } // setter
  31325. if (this.state === 'HAVE_NOTHING') {
  31326. throw new Error('Cannot switch media playlist from ' + this.state);
  31327. }
  31328. var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
  31329. if (typeof playlist === 'string') {
  31330. if (!this.master.playlists[playlist]) {
  31331. throw new Error('Unknown playlist URI: ' + playlist);
  31332. }
  31333. playlist = this.master.playlists[playlist];
  31334. }
  31335. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately
  31336. if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) {
  31337. this.state = 'HAVE_METADATA';
  31338. this.media_ = playlist; // trigger media change if the active media has been updated
  31339. if (mediaChange) {
  31340. this.trigger('mediachanging');
  31341. this.trigger('mediachange');
  31342. }
  31343. return;
  31344. } // switching to the active playlist is a no-op
  31345. if (!mediaChange) {
  31346. return;
  31347. } // switching from an already loaded playlist
  31348. if (this.media_) {
  31349. this.trigger('mediachanging');
  31350. }
  31351. if (!playlist.sidx) {
  31352. // Continue asynchronously if there is no sidx
  31353. // wait one tick to allow haveMaster to run first on a child loader
  31354. this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, {
  31355. startingState: startingState,
  31356. playlist: playlist
  31357. }), 0); // exit early and don't do sidx work
  31358. return;
  31359. } // we have sidx mappings
  31360. var oldMaster = void 0;
  31361. var sidxMapping = void 0; // sidxMapping is used when parsing the masterXml, so store
  31362. // it on the masterPlaylistLoader
  31363. if (this.masterPlaylistLoader_) {
  31364. oldMaster = this.masterPlaylistLoader_.master;
  31365. sidxMapping = this.masterPlaylistLoader_.sidxMapping_;
  31366. } else {
  31367. oldMaster = this.master;
  31368. sidxMapping = this.sidxMapping_;
  31369. }
  31370. var sidxKey = generateSidxKey(playlist.sidx);
  31371. sidxMapping[sidxKey] = {
  31372. sidxInfo: playlist.sidx
  31373. };
  31374. this.request = requestSidx_(playlist.sidx, playlist, this.hls_.xhr, {
  31375. handleManifestRedirects: this.handleManifestRedirects
  31376. }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) {
  31377. if (!newMaster || !sidx) {
  31378. throw new Error('failed to request sidx');
  31379. } // update loader's sidxMapping with parsed sidx box
  31380. sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata
  31381. _this3.haveMetadata({
  31382. startingState: startingState,
  31383. playlist: newMaster.playlists[playlist.uri]
  31384. });
  31385. }));
  31386. }
  31387. }, {
  31388. key: 'haveMetadata',
  31389. value: function haveMetadata(_ref) {
  31390. var startingState = _ref.startingState,
  31391. playlist = _ref.playlist;
  31392. this.state = 'HAVE_METADATA';
  31393. this.loadedPlaylists_[playlist.uri] = playlist;
  31394. this.mediaRequest_ = null; // This will trigger loadedplaylist
  31395. this.refreshMedia_(playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
  31396. // to resolve setup of media groups
  31397. if (startingState === 'HAVE_MASTER') {
  31398. this.trigger('loadedmetadata');
  31399. } else {
  31400. // trigger media change if the active media has been updated
  31401. this.trigger('mediachange');
  31402. }
  31403. }
  31404. }, {
  31405. key: 'pause',
  31406. value: function pause() {
  31407. this.stopRequest();
  31408. window$1.clearTimeout(this.mediaUpdateTimeout);
  31409. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  31410. if (this.state === 'HAVE_NOTHING') {
  31411. // If we pause the loader before any data has been retrieved, its as if we never
  31412. // started, so reset to an unstarted state.
  31413. this.started = false;
  31414. }
  31415. }
  31416. }, {
  31417. key: 'load',
  31418. value: function load(isFinalRendition) {
  31419. var _this4 = this;
  31420. window$1.clearTimeout(this.mediaUpdateTimeout);
  31421. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  31422. var media = this.media();
  31423. if (isFinalRendition) {
  31424. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  31425. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  31426. return _this4.load();
  31427. }, delay);
  31428. return;
  31429. } // because the playlists are internal to the manifest, load should either load the
  31430. // main manifest, or do nothing but trigger an event
  31431. if (!this.started) {
  31432. this.start();
  31433. return;
  31434. }
  31435. this.trigger('loadedplaylist');
  31436. }
  31437. /**
  31438. * Parses the master xml string and updates playlist uri references
  31439. *
  31440. * @return {Object}
  31441. * The parsed mpd manifest object
  31442. */
  31443. }, {
  31444. key: 'parseMasterXml',
  31445. value: function parseMasterXml() {
  31446. var master = parse(this.masterXml_, {
  31447. manifestUri: this.srcUrl,
  31448. clientOffset: this.clientOffset_,
  31449. sidxMapping: this.sidxMapping_
  31450. });
  31451. master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH
  31452. // but reference playlists by their URI throughout the project
  31453. // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes).
  31454. for (var i = 0; i < master.playlists.length; i++) {
  31455. var phonyUri = 'placeholder-uri-' + i;
  31456. master.playlists[i].uri = phonyUri; // set up by URI references
  31457. master.playlists[phonyUri] = master.playlists[i];
  31458. } // set up phony URIs for the media group playlists since we won't have external
  31459. // URIs for DASH but reference playlists by their URI throughout the project
  31460. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  31461. if (properties.playlists && properties.playlists.length) {
  31462. var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey;
  31463. properties.playlists[0].uri = _phonyUri; // setup URI references
  31464. master.playlists[_phonyUri] = properties.playlists[0];
  31465. }
  31466. });
  31467. setupMediaPlaylists(master);
  31468. resolveMediaGroupUris(master);
  31469. return master;
  31470. }
  31471. }, {
  31472. key: 'start',
  31473. value: function start() {
  31474. var _this5 = this;
  31475. this.started = true; // We don't need to request the master manifest again
  31476. // Call this asynchronously to match the xhr request behavior below
  31477. if (this.masterPlaylistLoader_) {
  31478. this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0);
  31479. return;
  31480. } // request the specified URL
  31481. this.request = this.hls_.xhr({
  31482. uri: this.srcUrl,
  31483. withCredentials: this.withCredentials
  31484. }, function (error, req) {
  31485. // disposed
  31486. if (!_this5.request) {
  31487. return;
  31488. } // clear the loader's request reference
  31489. _this5.request = null;
  31490. if (error) {
  31491. _this5.error = {
  31492. status: req.status,
  31493. message: 'DASH playlist request error at URL: ' + _this5.srcUrl,
  31494. responseText: req.responseText,
  31495. // MEDIA_ERR_NETWORK
  31496. code: 2
  31497. };
  31498. if (_this5.state === 'HAVE_NOTHING') {
  31499. _this5.started = false;
  31500. }
  31501. return _this5.trigger('error');
  31502. }
  31503. _this5.masterXml_ = req.responseText;
  31504. if (req.responseHeaders && req.responseHeaders.date) {
  31505. _this5.masterLoaded_ = Date.parse(req.responseHeaders.date);
  31506. } else {
  31507. _this5.masterLoaded_ = Date.now();
  31508. }
  31509. _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
  31510. _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5));
  31511. });
  31512. }
  31513. /**
  31514. * Parses the master xml for UTCTiming node to sync the client clock to the server
  31515. * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
  31516. *
  31517. * @param {Function} done
  31518. * Function to call when clock sync has completed
  31519. */
  31520. }, {
  31521. key: 'syncClientServerClock_',
  31522. value: function syncClientServerClock_(done) {
  31523. var _this6 = this;
  31524. var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
  31525. // server clock
  31526. if (utcTiming === null) {
  31527. this.clientOffset_ = this.masterLoaded_ - Date.now();
  31528. return done();
  31529. }
  31530. if (utcTiming.method === 'DIRECT') {
  31531. this.clientOffset_ = utcTiming.value - Date.now();
  31532. return done();
  31533. }
  31534. this.request = this.hls_.xhr({
  31535. uri: resolveUrl(this.srcUrl, utcTiming.value),
  31536. method: utcTiming.method,
  31537. withCredentials: this.withCredentials
  31538. }, function (error, req) {
  31539. // disposed
  31540. if (!_this6.request) {
  31541. return;
  31542. }
  31543. if (error) {
  31544. // sync request failed, fall back to using date header from mpd
  31545. // TODO: log warning
  31546. _this6.clientOffset_ = _this6.masterLoaded_ - Date.now();
  31547. return done();
  31548. }
  31549. var serverTime = void 0;
  31550. if (utcTiming.method === 'HEAD') {
  31551. if (!req.responseHeaders || !req.responseHeaders.date) {
  31552. // expected date header not preset, fall back to using date header from mpd
  31553. // TODO: log warning
  31554. serverTime = _this6.masterLoaded_;
  31555. } else {
  31556. serverTime = Date.parse(req.responseHeaders.date);
  31557. }
  31558. } else {
  31559. serverTime = Date.parse(req.responseText);
  31560. }
  31561. _this6.clientOffset_ = serverTime - Date.now();
  31562. done();
  31563. });
  31564. }
  31565. }, {
  31566. key: 'haveMaster_',
  31567. value: function haveMaster_() {
  31568. this.state = 'HAVE_MASTER'; // clear media request
  31569. this.mediaRequest_ = null;
  31570. if (!this.masterPlaylistLoader_) {
  31571. this.master = this.parseMasterXml(); // We have the master playlist at this point, so
  31572. // trigger this to allow MasterPlaylistController
  31573. // to make an initial playlist selection
  31574. this.trigger('loadedplaylist');
  31575. } else if (!this.media_) {
  31576. // no media playlist was specifically selected so select
  31577. // the one the child playlist loader was created with
  31578. this.media(this.childPlaylist_);
  31579. }
  31580. }
  31581. /**
  31582. * Handler for after client/server clock synchronization has happened. Sets up
  31583. * xml refresh timer if specificed by the manifest.
  31584. */
  31585. }, {
  31586. key: 'onClientServerClockSync_',
  31587. value: function onClientServerClockSync_() {
  31588. var _this7 = this;
  31589. this.haveMaster_();
  31590. if (!this.hasPendingRequest() && !this.media_) {
  31591. this.media(this.master.playlists[0]);
  31592. } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not
  31593. // be refreshed when this is the case. The inter-op guide says that when the
  31594. // minimumUpdatePeriod is 0, the manifest should outline all currently available
  31595. // segments, but future segments may require an update. I think a good solution
  31596. // would be to update the manifest at the same rate that the media playlists
  31597. // are "refreshed", i.e. every targetDuration.
  31598. if (this.master && this.master.minimumUpdatePeriod) {
  31599. this.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  31600. _this7.trigger('minimumUpdatePeriod');
  31601. }, this.master.minimumUpdatePeriod);
  31602. }
  31603. }
  31604. /**
  31605. * Sends request to refresh the master xml and updates the parsed master manifest
  31606. * TODO: Does the client offset need to be recalculated when the xml is refreshed?
  31607. */
  31608. }, {
  31609. key: 'refreshXml_',
  31610. value: function refreshXml_() {
  31611. var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when
  31612. // sidx is implemented
  31613. this.request = this.hls_.xhr({
  31614. uri: this.srcUrl,
  31615. withCredentials: this.withCredentials
  31616. }, function (error, req) {
  31617. // disposed
  31618. if (!_this8.request) {
  31619. return;
  31620. } // clear the loader's request reference
  31621. _this8.request = null;
  31622. if (error) {
  31623. _this8.error = {
  31624. status: req.status,
  31625. message: 'DASH playlist request error at URL: ' + _this8.srcUrl,
  31626. responseText: req.responseText,
  31627. // MEDIA_ERR_NETWORK
  31628. code: 2
  31629. };
  31630. if (_this8.state === 'HAVE_NOTHING') {
  31631. _this8.started = false;
  31632. }
  31633. return _this8.trigger('error');
  31634. }
  31635. _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping
  31636. _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_);
  31637. var master = _this8.parseMasterXml();
  31638. var updatedMaster = updateMaster$1(_this8.master, master);
  31639. if (updatedMaster) {
  31640. var sidxKey = generateSidxKey(_this8.media().sidx); // the sidx was updated, so the previous mapping was removed
  31641. if (!_this8.sidxMapping_[sidxKey]) {
  31642. var playlist = _this8.media();
  31643. _this8.request = requestSidx_(playlist.sidx, playlist, _this8.hls_.xhr, {
  31644. handleManifestRedirects: _this8.handleManifestRedirects
  31645. }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) {
  31646. if (!newMaster || !sidx) {
  31647. throw new Error('failed to request sidx on minimumUpdatePeriod');
  31648. } // update loader's sidxMapping with parsed sidx box
  31649. _this8.sidxMapping_[sidxKey].sidx = sidx;
  31650. _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  31651. _this8.trigger('minimumUpdatePeriod');
  31652. }, _this8.master.minimumUpdatePeriod); // TODO: do we need to reload the current playlist?
  31653. _this8.refreshMedia_(_this8.media().uri);
  31654. return;
  31655. }));
  31656. } else {
  31657. _this8.master = updatedMaster;
  31658. }
  31659. }
  31660. _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  31661. _this8.trigger('minimumUpdatePeriod');
  31662. }, _this8.master.minimumUpdatePeriod);
  31663. });
  31664. }
  31665. /**
  31666. * Refreshes the media playlist by re-parsing the master xml and updating playlist
  31667. * references. If this is an alternate loader, the updated parsed manifest is retrieved
  31668. * from the master loader.
  31669. */
  31670. }, {
  31671. key: 'refreshMedia_',
  31672. value: function refreshMedia_(mediaUri) {
  31673. var _this9 = this;
  31674. if (!mediaUri) {
  31675. throw new Error('refreshMedia_ must take a media uri');
  31676. }
  31677. var oldMaster = void 0;
  31678. var newMaster = void 0;
  31679. if (this.masterPlaylistLoader_) {
  31680. oldMaster = this.masterPlaylistLoader_.master;
  31681. newMaster = this.masterPlaylistLoader_.parseMasterXml();
  31682. } else {
  31683. oldMaster = this.master;
  31684. newMaster = this.parseMasterXml();
  31685. }
  31686. var updatedMaster = updateMaster$1(oldMaster, newMaster);
  31687. if (updatedMaster) {
  31688. if (this.masterPlaylistLoader_) {
  31689. this.masterPlaylistLoader_.master = updatedMaster;
  31690. } else {
  31691. this.master = updatedMaster;
  31692. }
  31693. this.media_ = updatedMaster.playlists[mediaUri];
  31694. } else {
  31695. this.media_ = newMaster.playlists[mediaUri];
  31696. this.trigger('playlistunchanged');
  31697. }
  31698. if (!this.media().endList) {
  31699. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  31700. _this9.trigger('mediaupdatetimeout');
  31701. }, refreshDelay(this.media(), !!updatedMaster));
  31702. }
  31703. this.trigger('loadedplaylist');
  31704. }
  31705. }]);
  31706. return DashPlaylistLoader;
  31707. }(EventTarget$1$1);
  31708. var logger = function logger(source) {
  31709. if (videojs$1.log.debug) {
  31710. return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >');
  31711. }
  31712. return function () {};
  31713. };
  31714. function noop() {}
  31715. /**
  31716. * @file source-updater.js
  31717. */
  31718. /**
  31719. * A queue of callbacks to be serialized and applied when a
  31720. * MediaSource and its associated SourceBuffers are not in the
  31721. * updating state. It is used by the segment loader to update the
  31722. * underlying SourceBuffers when new data is loaded, for instance.
  31723. *
  31724. * @class SourceUpdater
  31725. * @param {MediaSource} mediaSource the MediaSource to create the
  31726. * SourceBuffer from
  31727. * @param {String} mimeType the desired MIME type of the underlying
  31728. * SourceBuffer
  31729. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
  31730. * added to the media source
  31731. */
  31732. var SourceUpdater = function () {
  31733. function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) {
  31734. classCallCheck(this, SourceUpdater);
  31735. this.callbacks_ = [];
  31736. this.pendingCallback_ = null;
  31737. this.timestampOffset_ = 0;
  31738. this.mediaSource = mediaSource;
  31739. this.processedAppend_ = false;
  31740. this.type_ = type;
  31741. this.mimeType_ = mimeType;
  31742. this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']');
  31743. if (mediaSource.readyState === 'closed') {
  31744. mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
  31745. } else {
  31746. this.createSourceBuffer_(mimeType, sourceBufferEmitter);
  31747. }
  31748. }
  31749. createClass(SourceUpdater, [{
  31750. key: 'createSourceBuffer_',
  31751. value: function createSourceBuffer_(mimeType, sourceBufferEmitter) {
  31752. var _this = this;
  31753. this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
  31754. this.logger_('created SourceBuffer');
  31755. if (sourceBufferEmitter) {
  31756. sourceBufferEmitter.trigger('sourcebufferadded');
  31757. if (this.mediaSource.sourceBuffers.length < 2) {
  31758. // There's another source buffer we must wait for before we can start updating
  31759. // our own (or else we can get into a bad state, i.e., appending video/audio data
  31760. // before the other video/audio source buffer is available and leading to a video
  31761. // or audio only buffer).
  31762. sourceBufferEmitter.on('sourcebufferadded', function () {
  31763. _this.start_();
  31764. });
  31765. return;
  31766. }
  31767. }
  31768. this.start_();
  31769. }
  31770. }, {
  31771. key: 'start_',
  31772. value: function start_() {
  31773. var _this2 = this;
  31774. this.started_ = true; // run completion handlers and process callbacks as updateend
  31775. // events fire
  31776. this.onUpdateendCallback_ = function () {
  31777. var pendingCallback = _this2.pendingCallback_;
  31778. _this2.pendingCallback_ = null;
  31779. _this2.sourceBuffer_.removing = false;
  31780. _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']');
  31781. if (pendingCallback) {
  31782. pendingCallback();
  31783. }
  31784. _this2.runCallback_();
  31785. };
  31786. this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
  31787. this.runCallback_();
  31788. }
  31789. /**
  31790. * Aborts the current segment and resets the segment parser.
  31791. *
  31792. * @param {Function} done function to call when done
  31793. * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
  31794. */
  31795. }, {
  31796. key: 'abort',
  31797. value: function abort(done) {
  31798. var _this3 = this;
  31799. if (this.processedAppend_) {
  31800. this.queueCallback_(function () {
  31801. _this3.sourceBuffer_.abort();
  31802. }, done);
  31803. }
  31804. }
  31805. /**
  31806. * Queue an update to append an ArrayBuffer.
  31807. *
  31808. * @param {ArrayBuffer} bytes
  31809. * @param {Function} done the function to call when done
  31810. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
  31811. */
  31812. }, {
  31813. key: 'appendBuffer',
  31814. value: function appendBuffer(config, done) {
  31815. var _this4 = this;
  31816. this.processedAppend_ = true;
  31817. this.queueCallback_(function () {
  31818. if (config.videoSegmentTimingInfoCallback) {
  31819. _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  31820. }
  31821. _this4.sourceBuffer_.appendBuffer(config.bytes);
  31822. }, function () {
  31823. if (config.videoSegmentTimingInfoCallback) {
  31824. _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  31825. }
  31826. done();
  31827. });
  31828. }
  31829. /**
  31830. * Indicates what TimeRanges are buffered in the managed SourceBuffer.
  31831. *
  31832. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
  31833. */
  31834. }, {
  31835. key: 'buffered',
  31836. value: function buffered() {
  31837. if (!this.sourceBuffer_) {
  31838. return videojs$1.createTimeRanges();
  31839. }
  31840. return this.sourceBuffer_.buffered;
  31841. }
  31842. /**
  31843. * Queue an update to remove a time range from the buffer.
  31844. *
  31845. * @param {Number} start where to start the removal
  31846. * @param {Number} end where to end the removal
  31847. * @param {Function} [done=noop] optional callback to be executed when the remove
  31848. * operation is complete
  31849. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
  31850. */
  31851. }, {
  31852. key: 'remove',
  31853. value: function remove(start, end) {
  31854. var _this5 = this;
  31855. var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop;
  31856. if (this.processedAppend_) {
  31857. this.queueCallback_(function () {
  31858. _this5.logger_('remove [' + start + ' => ' + end + ']');
  31859. _this5.sourceBuffer_.removing = true;
  31860. _this5.sourceBuffer_.remove(start, end);
  31861. }, done);
  31862. }
  31863. }
  31864. /**
  31865. * Whether the underlying sourceBuffer is updating or not
  31866. *
  31867. * @return {Boolean} the updating status of the SourceBuffer
  31868. */
  31869. }, {
  31870. key: 'updating',
  31871. value: function updating() {
  31872. // we are updating if the sourcebuffer is updating or
  31873. return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop
  31874. !!this.pendingCallback_ && this.pendingCallback_ !== noop;
  31875. }
  31876. /**
  31877. * Set/get the timestampoffset on the SourceBuffer
  31878. *
  31879. * @return {Number} the timestamp offset
  31880. */
  31881. }, {
  31882. key: 'timestampOffset',
  31883. value: function timestampOffset(offset) {
  31884. var _this6 = this;
  31885. if (typeof offset !== 'undefined') {
  31886. this.queueCallback_(function () {
  31887. _this6.sourceBuffer_.timestampOffset = offset;
  31888. });
  31889. this.timestampOffset_ = offset;
  31890. }
  31891. return this.timestampOffset_;
  31892. }
  31893. /**
  31894. * Queue a callback to run
  31895. */
  31896. }, {
  31897. key: 'queueCallback_',
  31898. value: function queueCallback_(callback, done) {
  31899. this.callbacks_.push([callback.bind(this), done]);
  31900. this.runCallback_();
  31901. }
  31902. /**
  31903. * Run a queued callback
  31904. */
  31905. }, {
  31906. key: 'runCallback_',
  31907. value: function runCallback_() {
  31908. var callbacks = void 0;
  31909. if (!this.updating() && this.callbacks_.length && this.started_) {
  31910. callbacks = this.callbacks_.shift();
  31911. this.pendingCallback_ = callbacks[1];
  31912. callbacks[0]();
  31913. }
  31914. }
  31915. /**
  31916. * dispose of the source updater and the underlying sourceBuffer
  31917. */
  31918. }, {
  31919. key: 'dispose',
  31920. value: function dispose() {
  31921. var _this7 = this;
  31922. var disposeFn = function disposeFn() {
  31923. if (_this7.sourceBuffer_ && _this7.mediaSource.readyState === 'open') {
  31924. _this7.sourceBuffer_.abort();
  31925. }
  31926. _this7.sourceBuffer_.removeEventListener('updateend', disposeFn);
  31927. };
  31928. this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
  31929. if (this.sourceBuffer_.removing) {
  31930. this.sourceBuffer_.addEventListener('updateend', disposeFn);
  31931. } else {
  31932. disposeFn();
  31933. }
  31934. }
  31935. }]);
  31936. return SourceUpdater;
  31937. }();
  31938. var Config = {
  31939. GOAL_BUFFER_LENGTH: 30,
  31940. MAX_GOAL_BUFFER_LENGTH: 60,
  31941. GOAL_BUFFER_LENGTH_RATE: 1,
  31942. // 0.5 MB/s
  31943. INITIAL_BANDWIDTH: 4194304,
  31944. // A fudge factor to apply to advertised playlist bitrates to account for
  31945. // temporary flucations in client bandwidth
  31946. BANDWIDTH_VARIANCE: 1.2,
  31947. // How much of the buffer must be filled before we consider upswitching
  31948. BUFFER_LOW_WATER_LINE: 0,
  31949. MAX_BUFFER_LOW_WATER_LINE: 30,
  31950. BUFFER_LOW_WATER_LINE_RATE: 1
  31951. };
  31952. var REQUEST_ERRORS = {
  31953. FAILURE: 2,
  31954. TIMEOUT: -101,
  31955. ABORTED: -102
  31956. };
  31957. /**
  31958. * Abort all requests
  31959. *
  31960. * @param {Object} activeXhrs - an object that tracks all XHR requests
  31961. */
  31962. var abortAll = function abortAll(activeXhrs) {
  31963. activeXhrs.forEach(function (xhr$$1) {
  31964. xhr$$1.abort();
  31965. });
  31966. };
  31967. /**
  31968. * Gather important bandwidth stats once a request has completed
  31969. *
  31970. * @param {Object} request - the XHR request from which to gather stats
  31971. */
  31972. var getRequestStats = function getRequestStats(request) {
  31973. return {
  31974. bandwidth: request.bandwidth,
  31975. bytesReceived: request.bytesReceived || 0,
  31976. roundTripTime: request.roundTripTime || 0
  31977. };
  31978. };
  31979. /**
  31980. * If possible gather bandwidth stats as a request is in
  31981. * progress
  31982. *
  31983. * @param {Event} progressEvent - an event object from an XHR's progress event
  31984. */
  31985. var getProgressStats = function getProgressStats(progressEvent) {
  31986. var request = progressEvent.target;
  31987. var roundTripTime = Date.now() - request.requestTime;
  31988. var stats = {
  31989. bandwidth: Infinity,
  31990. bytesReceived: 0,
  31991. roundTripTime: roundTripTime || 0
  31992. };
  31993. stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
  31994. // because we should only use bandwidth stats on progress to determine when
  31995. // abort a request early due to insufficient bandwidth
  31996. stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
  31997. return stats;
  31998. };
  31999. /**
  32000. * Handle all error conditions in one place and return an object
  32001. * with all the information
  32002. *
  32003. * @param {Error|null} error - if non-null signals an error occured with the XHR
  32004. * @param {Object} request - the XHR request that possibly generated the error
  32005. */
  32006. var handleErrors = function handleErrors(error, request) {
  32007. if (request.timedout) {
  32008. return {
  32009. status: request.status,
  32010. message: 'HLS request timed-out at URL: ' + request.uri,
  32011. code: REQUEST_ERRORS.TIMEOUT,
  32012. xhr: request
  32013. };
  32014. }
  32015. if (request.aborted) {
  32016. return {
  32017. status: request.status,
  32018. message: 'HLS request aborted at URL: ' + request.uri,
  32019. code: REQUEST_ERRORS.ABORTED,
  32020. xhr: request
  32021. };
  32022. }
  32023. if (error) {
  32024. return {
  32025. status: request.status,
  32026. message: 'HLS request errored at URL: ' + request.uri,
  32027. code: REQUEST_ERRORS.FAILURE,
  32028. xhr: request
  32029. };
  32030. }
  32031. return null;
  32032. };
  32033. /**
  32034. * Handle responses for key data and convert the key data to the correct format
  32035. * for the decryption step later
  32036. *
  32037. * @param {Object} segment - a simplified copy of the segmentInfo object
  32038. * from SegmentLoader
  32039. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  32040. * this request
  32041. */
  32042. var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
  32043. return function (error, request) {
  32044. var response = request.response;
  32045. var errorObj = handleErrors(error, request);
  32046. if (errorObj) {
  32047. return finishProcessingFn(errorObj, segment);
  32048. }
  32049. if (response.byteLength !== 16) {
  32050. return finishProcessingFn({
  32051. status: request.status,
  32052. message: 'Invalid HLS key at URL: ' + request.uri,
  32053. code: REQUEST_ERRORS.FAILURE,
  32054. xhr: request
  32055. }, segment);
  32056. }
  32057. var view = new DataView(response);
  32058. segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
  32059. return finishProcessingFn(null, segment);
  32060. };
  32061. };
  32062. /**
  32063. * Handle init-segment responses
  32064. *
  32065. * @param {Object} segment - a simplified copy of the segmentInfo object
  32066. * from SegmentLoader
  32067. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  32068. * this request
  32069. */
  32070. var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) {
  32071. return function (error, request) {
  32072. var response = request.response;
  32073. var errorObj = handleErrors(error, request);
  32074. if (errorObj) {
  32075. return finishProcessingFn(errorObj, segment);
  32076. } // stop processing if received empty content
  32077. if (response.byteLength === 0) {
  32078. return finishProcessingFn({
  32079. status: request.status,
  32080. message: 'Empty HLS segment content at URL: ' + request.uri,
  32081. code: REQUEST_ERRORS.FAILURE,
  32082. xhr: request
  32083. }, segment);
  32084. }
  32085. segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet
  32086. if (!captionParser.isInitialized()) {
  32087. captionParser.init();
  32088. }
  32089. segment.map.timescales = mp4probe.timescale(segment.map.bytes);
  32090. segment.map.videoTrackIds = mp4probe.videoTrackIds(segment.map.bytes);
  32091. return finishProcessingFn(null, segment);
  32092. };
  32093. };
  32094. /**
  32095. * Response handler for segment-requests being sure to set the correct
  32096. * property depending on whether the segment is encryped or not
  32097. * Also records and keeps track of stats that are used for ABR purposes
  32098. *
  32099. * @param {Object} segment - a simplified copy of the segmentInfo object
  32100. * from SegmentLoader
  32101. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  32102. * this request
  32103. */
  32104. var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) {
  32105. return function (error, request) {
  32106. var response = request.response;
  32107. var errorObj = handleErrors(error, request);
  32108. var parsed = void 0;
  32109. if (errorObj) {
  32110. return finishProcessingFn(errorObj, segment);
  32111. } // stop processing if received empty content
  32112. if (response.byteLength === 0) {
  32113. return finishProcessingFn({
  32114. status: request.status,
  32115. message: 'Empty HLS segment content at URL: ' + request.uri,
  32116. code: REQUEST_ERRORS.FAILURE,
  32117. xhr: request
  32118. }, segment);
  32119. }
  32120. segment.stats = getRequestStats(request);
  32121. if (segment.key) {
  32122. segment.encryptedBytes = new Uint8Array(request.response);
  32123. } else {
  32124. segment.bytes = new Uint8Array(request.response);
  32125. } // This is likely an FMP4 and has the init segment.
  32126. // Run through the CaptionParser in case there are captions.
  32127. if (segment.map && segment.map.bytes) {
  32128. // Initialize CaptionParser if it hasn't been yet
  32129. if (!captionParser.isInitialized()) {
  32130. captionParser.init();
  32131. }
  32132. parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales);
  32133. if (parsed && parsed.captions) {
  32134. segment.captionStreams = parsed.captionStreams;
  32135. segment.fmp4Captions = parsed.captions;
  32136. }
  32137. }
  32138. return finishProcessingFn(null, segment);
  32139. };
  32140. };
  32141. /**
  32142. * Decrypt the segment via the decryption web worker
  32143. *
  32144. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  32145. * @param {Object} segment - a simplified copy of the segmentInfo object
  32146. * from SegmentLoader
  32147. * @param {Function} doneFn - a callback that is executed after decryption has completed
  32148. */
  32149. var decryptSegment = function decryptSegment(decrypter, segment, doneFn) {
  32150. var decryptionHandler = function decryptionHandler(event) {
  32151. if (event.data.source === segment.requestId) {
  32152. decrypter.removeEventListener('message', decryptionHandler);
  32153. var decrypted = event.data.decrypted;
  32154. segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
  32155. return doneFn(null, segment);
  32156. }
  32157. };
  32158. decrypter.addEventListener('message', decryptionHandler);
  32159. var keyBytes = segment.key.bytes.slice(); // this is an encrypted segment
  32160. // incrementally decrypt the segment
  32161. decrypter.postMessage(createTransferableMessage({
  32162. source: segment.requestId,
  32163. encrypted: segment.encryptedBytes,
  32164. key: keyBytes,
  32165. iv: segment.key.iv
  32166. }), [segment.encryptedBytes.buffer, keyBytes.buffer]);
  32167. };
  32168. /**
  32169. * This function waits for all XHRs to finish (with either success or failure)
  32170. * before continueing processing via it's callback. The function gathers errors
  32171. * from each request into a single errors array so that the error status for
  32172. * each request can be examined later.
  32173. *
  32174. * @param {Object} activeXhrs - an object that tracks all XHR requests
  32175. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  32176. * @param {Function} doneFn - a callback that is executed after all resources have been
  32177. * downloaded and any decryption completed
  32178. */
  32179. var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) {
  32180. var count = 0;
  32181. var didError = false;
  32182. return function (error, segment) {
  32183. if (didError) {
  32184. return;
  32185. }
  32186. if (error) {
  32187. didError = true; // If there are errors, we have to abort any outstanding requests
  32188. abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
  32189. // handle the aborted events from those requests, there are some cases where we may
  32190. // never get an aborted event. For instance, if the network connection is lost and
  32191. // there were two requests, the first may have triggered an error immediately, while
  32192. // the second request remains unsent. In that case, the aborted algorithm will not
  32193. // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
  32194. //
  32195. // We also can't rely on the ready state of the XHR, since the request that
  32196. // triggered the connection error may also show as a ready state of 0 (unsent).
  32197. // Therefore, we have to finish this group of requests immediately after the first
  32198. // seen error.
  32199. return doneFn(error, segment);
  32200. }
  32201. count += 1;
  32202. if (count === activeXhrs.length) {
  32203. // Keep track of when *all* of the requests have completed
  32204. segment.endOfAllRequests = Date.now();
  32205. if (segment.encryptedBytes) {
  32206. return decryptSegment(decrypter, segment, doneFn);
  32207. } // Otherwise, everything is ready just continue
  32208. return doneFn(null, segment);
  32209. }
  32210. };
  32211. };
  32212. /**
  32213. * Simple progress event callback handler that gathers some stats before
  32214. * executing a provided callback with the `segment` object
  32215. *
  32216. * @param {Object} segment - a simplified copy of the segmentInfo object
  32217. * from SegmentLoader
  32218. * @param {Function} progressFn - a callback that is executed each time a progress event
  32219. * is received
  32220. * @param {Event} event - the progress event object from XMLHttpRequest
  32221. */
  32222. var handleProgress = function handleProgress(segment, progressFn) {
  32223. return function (event) {
  32224. segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
  32225. if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
  32226. segment.stats.firstBytesReceivedAt = Date.now();
  32227. }
  32228. return progressFn(event, segment);
  32229. };
  32230. };
  32231. /**
  32232. * Load all resources and does any processing necessary for a media-segment
  32233. *
  32234. * Features:
  32235. * decrypts the media-segment if it has a key uri and an iv
  32236. * aborts *all* requests if *any* one request fails
  32237. *
  32238. * The segment object, at minimum, has the following format:
  32239. * {
  32240. * resolvedUri: String,
  32241. * [byterange]: {
  32242. * offset: Number,
  32243. * length: Number
  32244. * },
  32245. * [key]: {
  32246. * resolvedUri: String
  32247. * [byterange]: {
  32248. * offset: Number,
  32249. * length: Number
  32250. * },
  32251. * iv: {
  32252. * bytes: Uint32Array
  32253. * }
  32254. * },
  32255. * [map]: {
  32256. * resolvedUri: String,
  32257. * [byterange]: {
  32258. * offset: Number,
  32259. * length: Number
  32260. * },
  32261. * [bytes]: Uint8Array
  32262. * }
  32263. * }
  32264. * ...where [name] denotes optional properties
  32265. *
  32266. * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
  32267. * @param {Object} xhrOptions - the base options to provide to all xhr requests
  32268. * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
  32269. * decryption routines
  32270. * @param {Object} segment - a simplified copy of the segmentInfo object
  32271. * from SegmentLoader
  32272. * @param {Function} progressFn - a callback that receives progress events from the main
  32273. * segment's xhr request
  32274. * @param {Function} doneFn - a callback that is executed only once all requests have
  32275. * succeeded or failed
  32276. * @returns {Function} a function that, when invoked, immediately aborts all
  32277. * outstanding requests
  32278. */
  32279. var mediaSegmentRequest = function mediaSegmentRequest(xhr$$1, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) {
  32280. var activeXhrs = [];
  32281. var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key
  32282. if (segment.key && !segment.key.bytes) {
  32283. var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  32284. uri: segment.key.resolvedUri,
  32285. responseType: 'arraybuffer'
  32286. });
  32287. var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
  32288. var keyXhr = xhr$$1(keyRequestOptions, keyRequestCallback);
  32289. activeXhrs.push(keyXhr);
  32290. } // optionally, request the associated media init segment
  32291. if (segment.map && !segment.map.bytes) {
  32292. var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
  32293. uri: segment.map.resolvedUri,
  32294. responseType: 'arraybuffer',
  32295. headers: segmentXhrHeaders(segment.map)
  32296. });
  32297. var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn);
  32298. var initSegmentXhr = xhr$$1(initSegmentOptions, initSegmentRequestCallback);
  32299. activeXhrs.push(initSegmentXhr);
  32300. }
  32301. var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  32302. uri: segment.resolvedUri,
  32303. responseType: 'arraybuffer',
  32304. headers: segmentXhrHeaders(segment)
  32305. });
  32306. var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn);
  32307. var segmentXhr = xhr$$1(segmentRequestOptions, segmentRequestCallback);
  32308. segmentXhr.addEventListener('progress', handleProgress(segment, progressFn));
  32309. activeXhrs.push(segmentXhr);
  32310. return function () {
  32311. return abortAll(activeXhrs);
  32312. };
  32313. }; // Utilities
  32314. /**
  32315. * Returns the CSS value for the specified property on an element
  32316. * using `getComputedStyle`. Firefox has a long-standing issue where
  32317. * getComputedStyle() may return null when running in an iframe with
  32318. * `display: none`.
  32319. *
  32320. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  32321. * @param {HTMLElement} el the htmlelement to work on
  32322. * @param {string} the proprety to get the style for
  32323. */
  32324. var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
  32325. var result = void 0;
  32326. if (!el) {
  32327. return '';
  32328. }
  32329. result = window$1.getComputedStyle(el);
  32330. if (!result) {
  32331. return '';
  32332. }
  32333. return result[property];
  32334. };
  32335. /**
  32336. * Resuable stable sort function
  32337. *
  32338. * @param {Playlists} array
  32339. * @param {Function} sortFn Different comparators
  32340. * @function stableSort
  32341. */
  32342. var stableSort = function stableSort(array, sortFn) {
  32343. var newArray = array.slice();
  32344. array.sort(function (left, right) {
  32345. var cmp = sortFn(left, right);
  32346. if (cmp === 0) {
  32347. return newArray.indexOf(left) - newArray.indexOf(right);
  32348. }
  32349. return cmp;
  32350. });
  32351. };
  32352. /**
  32353. * A comparator function to sort two playlist object by bandwidth.
  32354. *
  32355. * @param {Object} left a media playlist object
  32356. * @param {Object} right a media playlist object
  32357. * @return {Number} Greater than zero if the bandwidth attribute of
  32358. * left is greater than the corresponding attribute of right. Less
  32359. * than zero if the bandwidth of right is greater than left and
  32360. * exactly zero if the two are equal.
  32361. */
  32362. var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
  32363. var leftBandwidth = void 0;
  32364. var rightBandwidth = void 0;
  32365. if (left.attributes.BANDWIDTH) {
  32366. leftBandwidth = left.attributes.BANDWIDTH;
  32367. }
  32368. leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE;
  32369. if (right.attributes.BANDWIDTH) {
  32370. rightBandwidth = right.attributes.BANDWIDTH;
  32371. }
  32372. rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE;
  32373. return leftBandwidth - rightBandwidth;
  32374. };
  32375. /**
  32376. * A comparator function to sort two playlist object by resolution (width).
  32377. * @param {Object} left a media playlist object
  32378. * @param {Object} right a media playlist object
  32379. * @return {Number} Greater than zero if the resolution.width attribute of
  32380. * left is greater than the corresponding attribute of right. Less
  32381. * than zero if the resolution.width of right is greater than left and
  32382. * exactly zero if the two are equal.
  32383. */
  32384. var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
  32385. var leftWidth = void 0;
  32386. var rightWidth = void 0;
  32387. if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
  32388. leftWidth = left.attributes.RESOLUTION.width;
  32389. }
  32390. leftWidth = leftWidth || window$1.Number.MAX_VALUE;
  32391. if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
  32392. rightWidth = right.attributes.RESOLUTION.width;
  32393. }
  32394. rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
  32395. // have the same media dimensions/ resolution
  32396. if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
  32397. return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
  32398. }
  32399. return leftWidth - rightWidth;
  32400. };
  32401. /**
  32402. * Chooses the appropriate media playlist based on bandwidth and player size
  32403. *
  32404. * @param {Object} master
  32405. * Object representation of the master manifest
  32406. * @param {Number} playerBandwidth
  32407. * Current calculated bandwidth of the player
  32408. * @param {Number} playerWidth
  32409. * Current width of the player element
  32410. * @param {Number} playerHeight
  32411. * Current height of the player element
  32412. * @param {Boolean} limitRenditionByPlayerDimensions
  32413. * True if the player width and height should be used during the selection, false otherwise
  32414. * @return {Playlist} the highest bitrate playlist less than the
  32415. * currently detected bandwidth, accounting for some amount of
  32416. * bandwidth variance
  32417. */
  32418. var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) {
  32419. // convert the playlists to an intermediary representation to make comparisons easier
  32420. var sortedPlaylistReps = master.playlists.map(function (playlist) {
  32421. var width = void 0;
  32422. var height = void 0;
  32423. var bandwidth = void 0;
  32424. width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
  32425. height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
  32426. bandwidth = playlist.attributes.BANDWIDTH;
  32427. bandwidth = bandwidth || window$1.Number.MAX_VALUE;
  32428. return {
  32429. bandwidth: bandwidth,
  32430. width: width,
  32431. height: height,
  32432. playlist: playlist
  32433. };
  32434. });
  32435. stableSort(sortedPlaylistReps, function (left, right) {
  32436. return left.bandwidth - right.bandwidth;
  32437. }); // filter out any playlists that have been excluded due to
  32438. // incompatible configurations
  32439. sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  32440. return !Playlist.isIncompatible(rep.playlist);
  32441. }); // filter out any playlists that have been disabled manually through the representations
  32442. // api or blacklisted temporarily due to playback errors.
  32443. var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  32444. return Playlist.isEnabled(rep.playlist);
  32445. });
  32446. if (!enabledPlaylistReps.length) {
  32447. // if there are no enabled playlists, then they have all been blacklisted or disabled
  32448. // by the user through the representations api. In this case, ignore blacklisting and
  32449. // fallback to what the user wants by using playlists the user has not disabled.
  32450. enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  32451. return !Playlist.isDisabled(rep.playlist);
  32452. });
  32453. } // filter out any variant that has greater effective bitrate
  32454. // than the current estimated bandwidth
  32455. var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
  32456. return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
  32457. });
  32458. var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
  32459. // and then taking the very first element
  32460. var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
  32461. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  32462. })[0]; // if we're not going to limit renditions by player size, make an early decision.
  32463. if (limitRenditionByPlayerDimensions === false) {
  32464. var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  32465. return _chosenRep ? _chosenRep.playlist : null;
  32466. } // filter out playlists without resolution information
  32467. var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
  32468. return rep.width && rep.height;
  32469. }); // sort variants by resolution
  32470. stableSort(haveResolution, function (left, right) {
  32471. return left.width - right.width;
  32472. }); // if we have the exact resolution as the player use it
  32473. var resolutionBestRepList = haveResolution.filter(function (rep) {
  32474. return rep.width === playerWidth && rep.height === playerHeight;
  32475. });
  32476. highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
  32477. var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
  32478. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  32479. })[0];
  32480. var resolutionPlusOneList = void 0;
  32481. var resolutionPlusOneSmallest = void 0;
  32482. var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player
  32483. // if there is no match of exact resolution
  32484. if (!resolutionBestRep) {
  32485. resolutionPlusOneList = haveResolution.filter(function (rep) {
  32486. return rep.width > playerWidth || rep.height > playerHeight;
  32487. }); // find all the variants have the same smallest resolution
  32488. resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
  32489. return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
  32490. }); // ensure that we also pick the highest bandwidth variant that
  32491. // is just-larger-than the video player
  32492. highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
  32493. resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
  32494. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  32495. })[0];
  32496. } // fallback chain of variants
  32497. var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  32498. return chosenRep ? chosenRep.playlist : null;
  32499. }; // Playlist Selectors
  32500. /**
  32501. * Chooses the appropriate media playlist based on the most recent
  32502. * bandwidth estimate and the player size.
  32503. *
  32504. * Expects to be called within the context of an instance of HlsHandler
  32505. *
  32506. * @return {Playlist} the highest bitrate playlist less than the
  32507. * currently detected bandwidth, accounting for some amount of
  32508. * bandwidth variance
  32509. */
  32510. var lastBandwidthSelector = function lastBandwidthSelector() {
  32511. return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions);
  32512. };
  32513. /**
  32514. * Chooses the appropriate media playlist based on the potential to rebuffer
  32515. *
  32516. * @param {Object} settings
  32517. * Object of information required to use this selector
  32518. * @param {Object} settings.master
  32519. * Object representation of the master manifest
  32520. * @param {Number} settings.currentTime
  32521. * The current time of the player
  32522. * @param {Number} settings.bandwidth
  32523. * Current measured bandwidth
  32524. * @param {Number} settings.duration
  32525. * Duration of the media
  32526. * @param {Number} settings.segmentDuration
  32527. * Segment duration to be used in round trip time calculations
  32528. * @param {Number} settings.timeUntilRebuffer
  32529. * Time left in seconds until the player has to rebuffer
  32530. * @param {Number} settings.currentTimeline
  32531. * The current timeline segments are being loaded from
  32532. * @param {SyncController} settings.syncController
  32533. * SyncController for determining if we have a sync point for a given playlist
  32534. * @return {Object|null}
  32535. * {Object} return.playlist
  32536. * The highest bandwidth playlist with the least amount of rebuffering
  32537. * {Number} return.rebufferingImpact
  32538. * The amount of time in seconds switching to this playlist will rebuffer. A
  32539. * negative value means that switching will cause zero rebuffering.
  32540. */
  32541. var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
  32542. var master = settings.master,
  32543. currentTime = settings.currentTime,
  32544. bandwidth = settings.bandwidth,
  32545. duration$$1 = settings.duration,
  32546. segmentDuration = settings.segmentDuration,
  32547. timeUntilRebuffer = settings.timeUntilRebuffer,
  32548. currentTimeline = settings.currentTimeline,
  32549. syncController = settings.syncController; // filter out any playlists that have been excluded due to
  32550. // incompatible configurations
  32551. var compatiblePlaylists = master.playlists.filter(function (playlist) {
  32552. return !Playlist.isIncompatible(playlist);
  32553. }); // filter out any playlists that have been disabled manually through the representations
  32554. // api or blacklisted temporarily due to playback errors.
  32555. var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
  32556. if (!enabledPlaylists.length) {
  32557. // if there are no enabled playlists, then they have all been blacklisted or disabled
  32558. // by the user through the representations api. In this case, ignore blacklisting and
  32559. // fallback to what the user wants by using playlists the user has not disabled.
  32560. enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
  32561. return !Playlist.isDisabled(playlist);
  32562. });
  32563. }
  32564. var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
  32565. var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
  32566. var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
  32567. // sync request first. This will double the request time
  32568. var numRequests = syncPoint ? 1 : 2;
  32569. var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
  32570. var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
  32571. return {
  32572. playlist: playlist,
  32573. rebufferingImpact: rebufferingImpact
  32574. };
  32575. });
  32576. var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
  32577. return estimate.rebufferingImpact <= 0;
  32578. }); // Sort by bandwidth DESC
  32579. stableSort(noRebufferingPlaylists, function (a, b) {
  32580. return comparePlaylistBandwidth(b.playlist, a.playlist);
  32581. });
  32582. if (noRebufferingPlaylists.length) {
  32583. return noRebufferingPlaylists[0];
  32584. }
  32585. stableSort(rebufferingEstimates, function (a, b) {
  32586. return a.rebufferingImpact - b.rebufferingImpact;
  32587. });
  32588. return rebufferingEstimates[0] || null;
  32589. };
  32590. /**
  32591. * Chooses the appropriate media playlist, which in this case is the lowest bitrate
  32592. * one with video. If no renditions with video exist, return the lowest audio rendition.
  32593. *
  32594. * Expects to be called within the context of an instance of HlsHandler
  32595. *
  32596. * @return {Object|null}
  32597. * {Object} return.playlist
  32598. * The lowest bitrate playlist that contains a video codec. If no such rendition
  32599. * exists pick the lowest audio rendition.
  32600. */
  32601. var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
  32602. // filter out any playlists that have been excluded due to
  32603. // incompatible configurations or playback errors
  32604. var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
  32605. stableSort(playlists, function (a, b) {
  32606. return comparePlaylistBandwidth(a, b);
  32607. }); // Parse and assume that playlists with no video codec have no video
  32608. // (this is not necessarily true, although it is generally true).
  32609. //
  32610. // If an entire manifest has no valid videos everything will get filtered
  32611. // out.
  32612. var playlistsWithVideo = playlists.filter(function (playlist) {
  32613. return parseCodecs(playlist.attributes.CODECS).videoCodec;
  32614. });
  32615. return playlistsWithVideo[0] || null;
  32616. };
  32617. /**
  32618. * Create captions text tracks on video.js if they do not exist
  32619. *
  32620. * @param {Object} inbandTextTracks a reference to current inbandTextTracks
  32621. * @param {Object} tech the video.js tech
  32622. * @param {Object} captionStreams the caption streams to create
  32623. * @private
  32624. */
  32625. var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) {
  32626. for (var trackId in captionStreams) {
  32627. if (!inbandTextTracks[trackId]) {
  32628. tech.trigger({
  32629. type: 'usage',
  32630. name: 'hls-608'
  32631. });
  32632. var track = tech.textTracks().getTrackById(trackId);
  32633. if (track) {
  32634. // Resuse an existing track with a CC# id because this was
  32635. // very likely created by videojs-contrib-hls from information
  32636. // in the m3u8 for us to use
  32637. inbandTextTracks[trackId] = track;
  32638. } else {
  32639. // Otherwise, create a track with the default `CC#` label and
  32640. // without a language
  32641. inbandTextTracks[trackId] = tech.addRemoteTextTrack({
  32642. kind: 'captions',
  32643. id: trackId,
  32644. label: trackId
  32645. }, false).track;
  32646. }
  32647. }
  32648. }
  32649. };
  32650. var addCaptionData = function addCaptionData(_ref) {
  32651. var inbandTextTracks = _ref.inbandTextTracks,
  32652. captionArray = _ref.captionArray,
  32653. timestampOffset = _ref.timestampOffset;
  32654. if (!captionArray) {
  32655. return;
  32656. }
  32657. var Cue = window.WebKitDataCue || window.VTTCue;
  32658. captionArray.forEach(function (caption) {
  32659. var track = caption.stream;
  32660. var startTime = caption.startTime;
  32661. var endTime = caption.endTime;
  32662. if (!inbandTextTracks[track]) {
  32663. return;
  32664. }
  32665. startTime += timestampOffset;
  32666. endTime += timestampOffset;
  32667. inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text));
  32668. });
  32669. };
  32670. /**
  32671. * @file segment-loader.js
  32672. */
  32673. // in ms
  32674. var CHECK_BUFFER_DELAY = 500;
  32675. /**
  32676. * Determines if we should call endOfStream on the media source based
  32677. * on the state of the buffer or if appened segment was the final
  32678. * segment in the playlist.
  32679. *
  32680. * @param {Object} playlist a media playlist object
  32681. * @param {Object} mediaSource the MediaSource object
  32682. * @param {Number} segmentIndex the index of segment we last appended
  32683. * @returns {Boolean} do we need to call endOfStream on the MediaSource
  32684. */
  32685. var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
  32686. if (!playlist || !mediaSource) {
  32687. return false;
  32688. }
  32689. var segments = playlist.segments; // determine a few boolean values to help make the branch below easier
  32690. // to read
  32691. var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream
  32692. // so that MediaSources can trigger the `ended` event when it runs out of
  32693. // buffered data instead of waiting for me
  32694. return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
  32695. };
  32696. var finite = function finite(num) {
  32697. return typeof num === 'number' && isFinite(num);
  32698. };
  32699. var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
  32700. // Although these checks should most likely cover non 'main' types, for now it narrows
  32701. // the scope of our checks.
  32702. if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
  32703. return null;
  32704. }
  32705. if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
  32706. return 'Neither audio nor video found in segment.';
  32707. }
  32708. if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
  32709. return 'Only audio found in segment when we expected video.' + ' We can\'t switch to audio only from a stream that had video.' + ' To get rid of this message, please add codec information to the manifest.';
  32710. }
  32711. if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
  32712. return 'Video found in segment when we expected only audio.' + ' We can\'t switch to a stream with video from an audio only stream.' + ' To get rid of this message, please add codec information to the manifest.';
  32713. }
  32714. return null;
  32715. };
  32716. /**
  32717. * Calculates a time value that is safe to remove from the back buffer without interupting
  32718. * playback.
  32719. *
  32720. * @param {TimeRange} seekable
  32721. * The current seekable range
  32722. * @param {Number} currentTime
  32723. * The current time of the player
  32724. * @param {Number} targetDuration
  32725. * The target duration of the current playlist
  32726. * @return {Number}
  32727. * Time that is safe to remove from the back buffer without interupting playback
  32728. */
  32729. var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) {
  32730. var removeToTime = void 0;
  32731. if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) {
  32732. // If we have a seekable range use that as the limit for what can be removed safely
  32733. removeToTime = seekable$$1.start(0);
  32734. } else {
  32735. // otherwise remove anything older than 30 seconds before the current play head
  32736. removeToTime = currentTime - 30;
  32737. } // Don't allow removing from the buffer within target duration of current time
  32738. // to avoid the possibility of removing the GOP currently being played which could
  32739. // cause playback stalls.
  32740. return Math.min(removeToTime, currentTime - targetDuration);
  32741. };
  32742. var segmentInfoString = function segmentInfoString(segmentInfo) {
  32743. var _segmentInfo$segment = segmentInfo.segment,
  32744. start = _segmentInfo$segment.start,
  32745. end = _segmentInfo$segment.end,
  32746. _segmentInfo$playlist = segmentInfo.playlist,
  32747. seq = _segmentInfo$playlist.mediaSequence,
  32748. id = _segmentInfo$playlist.id,
  32749. _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
  32750. segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2,
  32751. index = segmentInfo.mediaIndex,
  32752. timeline = segmentInfo.timeline;
  32753. return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' ');
  32754. };
  32755. /**
  32756. * An object that manages segment loading and appending.
  32757. *
  32758. * @class SegmentLoader
  32759. * @param {Object} options required and optional options
  32760. * @extends videojs.EventTarget
  32761. */
  32762. var SegmentLoader = function (_videojs$EventTarget) {
  32763. inherits(SegmentLoader, _videojs$EventTarget);
  32764. function SegmentLoader(settings) {
  32765. classCallCheck(this, SegmentLoader); // check pre-conditions
  32766. var _this = possibleConstructorReturn(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this));
  32767. if (!settings) {
  32768. throw new TypeError('Initialization settings are required');
  32769. }
  32770. if (typeof settings.currentTime !== 'function') {
  32771. throw new TypeError('No currentTime getter specified');
  32772. }
  32773. if (!settings.mediaSource) {
  32774. throw new TypeError('No MediaSource specified');
  32775. } // public properties
  32776. _this.bandwidth = settings.bandwidth;
  32777. _this.throughput = {
  32778. rate: 0,
  32779. count: 0
  32780. };
  32781. _this.roundTrip = NaN;
  32782. _this.resetStats_();
  32783. _this.mediaIndex = null; // private settings
  32784. _this.hasPlayed_ = settings.hasPlayed;
  32785. _this.currentTime_ = settings.currentTime;
  32786. _this.seekable_ = settings.seekable;
  32787. _this.seeking_ = settings.seeking;
  32788. _this.duration_ = settings.duration;
  32789. _this.mediaSource_ = settings.mediaSource;
  32790. _this.hls_ = settings.hls;
  32791. _this.loaderType_ = settings.loaderType;
  32792. _this.startingMedia_ = void 0;
  32793. _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
  32794. _this.goalBufferLength_ = settings.goalBufferLength;
  32795. _this.sourceType_ = settings.sourceType;
  32796. _this.inbandTextTracks_ = settings.inbandTextTracks;
  32797. _this.state_ = 'INIT'; // private instance variables
  32798. _this.checkBufferTimeout_ = null;
  32799. _this.error_ = void 0;
  32800. _this.currentTimeline_ = -1;
  32801. _this.pendingSegment_ = null;
  32802. _this.mimeType_ = null;
  32803. _this.sourceUpdater_ = null;
  32804. _this.xhrOptions_ = null; // Fragmented mp4 playback
  32805. _this.activeInitSegmentId_ = null;
  32806. _this.initSegments_ = {}; // HLSe playback
  32807. _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
  32808. _this.keyCache_ = {}; // Fmp4 CaptionParser
  32809. _this.captionParser_ = new CaptionParser();
  32810. _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
  32811. // between a time in the display time and a segment index within
  32812. // a playlist
  32813. _this.syncController_ = settings.syncController;
  32814. _this.syncPoint_ = {
  32815. segmentIndex: 0,
  32816. time: 0
  32817. };
  32818. _this.syncController_.on('syncinfoupdate', function () {
  32819. return _this.trigger('syncinfoupdate');
  32820. });
  32821. _this.mediaSource_.addEventListener('sourceopen', function () {
  32822. return _this.ended_ = false;
  32823. }); // ...for determining the fetch location
  32824. _this.fetchAtBuffer_ = false;
  32825. _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']');
  32826. Object.defineProperty(_this, 'state', {
  32827. get: function get$$1() {
  32828. return this.state_;
  32829. },
  32830. set: function set$$1(newState) {
  32831. if (newState !== this.state_) {
  32832. this.logger_(this.state_ + ' -> ' + newState);
  32833. this.state_ = newState;
  32834. }
  32835. }
  32836. });
  32837. return _this;
  32838. }
  32839. /**
  32840. * reset all of our media stats
  32841. *
  32842. * @private
  32843. */
  32844. createClass(SegmentLoader, [{
  32845. key: 'resetStats_',
  32846. value: function resetStats_() {
  32847. this.mediaBytesTransferred = 0;
  32848. this.mediaRequests = 0;
  32849. this.mediaRequestsAborted = 0;
  32850. this.mediaRequestsTimedout = 0;
  32851. this.mediaRequestsErrored = 0;
  32852. this.mediaTransferDuration = 0;
  32853. this.mediaSecondsLoaded = 0;
  32854. }
  32855. /**
  32856. * dispose of the SegmentLoader and reset to the default state
  32857. */
  32858. }, {
  32859. key: 'dispose',
  32860. value: function dispose() {
  32861. this.state = 'DISPOSED';
  32862. this.pause();
  32863. this.abort_();
  32864. if (this.sourceUpdater_) {
  32865. this.sourceUpdater_.dispose();
  32866. }
  32867. this.resetStats_();
  32868. this.captionParser_.reset();
  32869. }
  32870. /**
  32871. * abort anything that is currently doing on with the SegmentLoader
  32872. * and reset to a default state
  32873. */
  32874. }, {
  32875. key: 'abort',
  32876. value: function abort() {
  32877. if (this.state !== 'WAITING') {
  32878. if (this.pendingSegment_) {
  32879. this.pendingSegment_ = null;
  32880. }
  32881. return;
  32882. }
  32883. this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
  32884. // since we are no longer "waiting" on any requests. XHR callback is not always run
  32885. // when the request is aborted. This will prevent the loader from being stuck in the
  32886. // WAITING state indefinitely.
  32887. this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
  32888. // next segment
  32889. if (!this.paused()) {
  32890. this.monitorBuffer_();
  32891. }
  32892. }
  32893. /**
  32894. * abort all pending xhr requests and null any pending segements
  32895. *
  32896. * @private
  32897. */
  32898. }, {
  32899. key: 'abort_',
  32900. value: function abort_() {
  32901. if (this.pendingSegment_) {
  32902. this.pendingSegment_.abortRequests();
  32903. } // clear out the segment being processed
  32904. this.pendingSegment_ = null;
  32905. }
  32906. /**
  32907. * set an error on the segment loader and null out any pending segements
  32908. *
  32909. * @param {Error} error the error to set on the SegmentLoader
  32910. * @return {Error} the error that was set or that is currently set
  32911. */
  32912. }, {
  32913. key: 'error',
  32914. value: function error(_error) {
  32915. if (typeof _error !== 'undefined') {
  32916. this.error_ = _error;
  32917. }
  32918. this.pendingSegment_ = null;
  32919. return this.error_;
  32920. }
  32921. }, {
  32922. key: 'endOfStream',
  32923. value: function endOfStream() {
  32924. this.ended_ = true;
  32925. this.pause();
  32926. this.trigger('ended');
  32927. }
  32928. /**
  32929. * Indicates which time ranges are buffered
  32930. *
  32931. * @return {TimeRange}
  32932. * TimeRange object representing the current buffered ranges
  32933. */
  32934. }, {
  32935. key: 'buffered_',
  32936. value: function buffered_() {
  32937. if (!this.sourceUpdater_) {
  32938. return videojs$1.createTimeRanges();
  32939. }
  32940. return this.sourceUpdater_.buffered();
  32941. }
  32942. /**
  32943. * Gets and sets init segment for the provided map
  32944. *
  32945. * @param {Object} map
  32946. * The map object representing the init segment to get or set
  32947. * @param {Boolean=} set
  32948. * If true, the init segment for the provided map should be saved
  32949. * @return {Object}
  32950. * map object for desired init segment
  32951. */
  32952. }, {
  32953. key: 'initSegment',
  32954. value: function initSegment(map) {
  32955. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  32956. if (!map) {
  32957. return null;
  32958. }
  32959. var id = initSegmentId(map);
  32960. var storedMap = this.initSegments_[id];
  32961. if (set$$1 && !storedMap && map.bytes) {
  32962. this.initSegments_[id] = storedMap = {
  32963. resolvedUri: map.resolvedUri,
  32964. byterange: map.byterange,
  32965. bytes: map.bytes,
  32966. timescales: map.timescales,
  32967. videoTrackIds: map.videoTrackIds
  32968. };
  32969. }
  32970. return storedMap || map;
  32971. }
  32972. /**
  32973. * Gets and sets key for the provided key
  32974. *
  32975. * @param {Object} key
  32976. * The key object representing the key to get or set
  32977. * @param {Boolean=} set
  32978. * If true, the key for the provided key should be saved
  32979. * @return {Object}
  32980. * Key object for desired key
  32981. */
  32982. }, {
  32983. key: 'segmentKey',
  32984. value: function segmentKey(key) {
  32985. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  32986. if (!key) {
  32987. return null;
  32988. }
  32989. var id = segmentKeyId(key);
  32990. var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
  32991. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
  32992. if (this.cacheEncryptionKeys_ && set$$1 && !storedKey && key.bytes) {
  32993. this.keyCache_[id] = storedKey = {
  32994. resolvedUri: key.resolvedUri,
  32995. bytes: key.bytes
  32996. };
  32997. }
  32998. var result = {
  32999. resolvedUri: (storedKey || key).resolvedUri
  33000. };
  33001. if (storedKey) {
  33002. result.bytes = storedKey.bytes;
  33003. }
  33004. return result;
  33005. }
  33006. /**
  33007. * Returns true if all configuration required for loading is present, otherwise false.
  33008. *
  33009. * @return {Boolean} True if the all configuration is ready for loading
  33010. * @private
  33011. */
  33012. }, {
  33013. key: 'couldBeginLoading_',
  33014. value: function couldBeginLoading_() {
  33015. return this.playlist_ && ( // the source updater is created when init_ is called, so either having a
  33016. // source updater or being in the INIT state with a mimeType is enough
  33017. // to say we have all the needed configuration to start loading.
  33018. this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
  33019. }
  33020. /**
  33021. * load a playlist and start to fill the buffer
  33022. */
  33023. }, {
  33024. key: 'load',
  33025. value: function load() {
  33026. // un-pause
  33027. this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
  33028. // specified
  33029. if (!this.playlist_) {
  33030. return;
  33031. } // not sure if this is the best place for this
  33032. this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading
  33033. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  33034. return this.init_();
  33035. } // if we're in the middle of processing a segment already, don't
  33036. // kick off an additional segment request
  33037. if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
  33038. return;
  33039. }
  33040. this.state = 'READY';
  33041. }
  33042. /**
  33043. * Once all the starting parameters have been specified, begin
  33044. * operation. This method should only be invoked from the INIT
  33045. * state.
  33046. *
  33047. * @private
  33048. */
  33049. }, {
  33050. key: 'init_',
  33051. value: function init_() {
  33052. this.state = 'READY';
  33053. this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_);
  33054. this.resetEverything();
  33055. return this.monitorBuffer_();
  33056. }
  33057. /**
  33058. * set a playlist on the segment loader
  33059. *
  33060. * @param {PlaylistLoader} media the playlist to set on the segment loader
  33061. */
  33062. }, {
  33063. key: 'playlist',
  33064. value: function playlist(newPlaylist) {
  33065. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  33066. if (!newPlaylist) {
  33067. return;
  33068. }
  33069. var oldPlaylist = this.playlist_;
  33070. var segmentInfo = this.pendingSegment_;
  33071. this.playlist_ = newPlaylist;
  33072. this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
  33073. // is always our zero-time so force a sync update each time the playlist
  33074. // is refreshed from the server
  33075. if (!this.hasPlayed_()) {
  33076. newPlaylist.syncInfo = {
  33077. mediaSequence: newPlaylist.mediaSequence,
  33078. time: 0
  33079. };
  33080. }
  33081. var oldId = null;
  33082. if (oldPlaylist) {
  33083. if (oldPlaylist.id) {
  33084. oldId = oldPlaylist.id;
  33085. } else if (oldPlaylist.uri) {
  33086. oldId = oldPlaylist.uri;
  33087. }
  33088. }
  33089. this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
  33090. // in LIVE, we always want to update with new playlists (including refreshes)
  33091. this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
  33092. // buffering now
  33093. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  33094. return this.init_();
  33095. }
  33096. if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
  33097. if (this.mediaIndex !== null) {
  33098. // we must "resync" the segment loader when we switch renditions and
  33099. // the segment loader is already synced to the previous rendition
  33100. this.resyncLoader();
  33101. } // the rest of this function depends on `oldPlaylist` being defined
  33102. return;
  33103. } // we reloaded the same playlist so we are in a live scenario
  33104. // and we will likely need to adjust the mediaIndex
  33105. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
  33106. this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader
  33107. // this is important because we can abort a request and this value must be
  33108. // equal to the last appended mediaIndex
  33109. if (this.mediaIndex !== null) {
  33110. this.mediaIndex -= mediaSequenceDiff;
  33111. } // update the mediaIndex on the SegmentInfo object
  33112. // this is important because we will update this.mediaIndex with this value
  33113. // in `handleUpdateEnd_` after the segment has been successfully appended
  33114. if (segmentInfo) {
  33115. segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is
  33116. // saved for the new playlist's segment, however, if the segment fell off the
  33117. // playlist, we can leave the old reference and just lose the timing info
  33118. if (segmentInfo.mediaIndex >= 0) {
  33119. segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
  33120. }
  33121. }
  33122. this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
  33123. }
  33124. /**
  33125. * Prevent the loader from fetching additional segments. If there
  33126. * is a segment request outstanding, it will finish processing
  33127. * before the loader halts. A segment loader can be unpaused by
  33128. * calling load().
  33129. */
  33130. }, {
  33131. key: 'pause',
  33132. value: function pause() {
  33133. if (this.checkBufferTimeout_) {
  33134. window$1.clearTimeout(this.checkBufferTimeout_);
  33135. this.checkBufferTimeout_ = null;
  33136. }
  33137. }
  33138. /**
  33139. * Returns whether the segment loader is fetching additional
  33140. * segments when given the opportunity. This property can be
  33141. * modified through calls to pause() and load().
  33142. */
  33143. }, {
  33144. key: 'paused',
  33145. value: function paused() {
  33146. return this.checkBufferTimeout_ === null;
  33147. }
  33148. /**
  33149. * create/set the following mimetype on the SourceBuffer through a
  33150. * SourceUpdater
  33151. *
  33152. * @param {String} mimeType the mime type string to use
  33153. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer
  33154. * is added to the media source
  33155. */
  33156. }, {
  33157. key: 'mimeType',
  33158. value: function mimeType(_mimeType, sourceBufferEmitter) {
  33159. if (this.mimeType_) {
  33160. return;
  33161. }
  33162. this.mimeType_ = _mimeType;
  33163. this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start
  33164. // buffering now
  33165. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  33166. this.init_();
  33167. }
  33168. }
  33169. /**
  33170. * Delete all the buffered data and reset the SegmentLoader
  33171. * @param {Function} [done] an optional callback to be executed when the remove
  33172. * operation is complete
  33173. */
  33174. }, {
  33175. key: 'resetEverything',
  33176. value: function resetEverything(done) {
  33177. this.ended_ = false;
  33178. this.resetLoader();
  33179. this.remove(0, this.duration_(), done); // clears fmp4 captions
  33180. this.captionParser_.clearAllCaptions();
  33181. this.trigger('reseteverything');
  33182. }
  33183. /**
  33184. * Force the SegmentLoader to resync and start loading around the currentTime instead
  33185. * of starting at the end of the buffer
  33186. *
  33187. * Useful for fast quality changes
  33188. */
  33189. }, {
  33190. key: 'resetLoader',
  33191. value: function resetLoader() {
  33192. this.fetchAtBuffer_ = false;
  33193. this.resyncLoader();
  33194. }
  33195. /**
  33196. * Force the SegmentLoader to restart synchronization and make a conservative guess
  33197. * before returning to the simple walk-forward method
  33198. */
  33199. }, {
  33200. key: 'resyncLoader',
  33201. value: function resyncLoader() {
  33202. this.mediaIndex = null;
  33203. this.syncPoint_ = null;
  33204. this.abort();
  33205. }
  33206. /**
  33207. * Remove any data in the source buffer between start and end times
  33208. * @param {Number} start - the start time of the region to remove from the buffer
  33209. * @param {Number} end - the end time of the region to remove from the buffer
  33210. * @param {Function} [done] - an optional callback to be executed when the remove
  33211. * operation is complete
  33212. */
  33213. }, {
  33214. key: 'remove',
  33215. value: function remove(start, end, done) {
  33216. if (this.sourceUpdater_) {
  33217. this.sourceUpdater_.remove(start, end, done);
  33218. }
  33219. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  33220. if (this.inbandTextTracks_) {
  33221. for (var id in this.inbandTextTracks_) {
  33222. removeCuesFromTrack(start, end, this.inbandTextTracks_[id]);
  33223. }
  33224. }
  33225. }
  33226. /**
  33227. * (re-)schedule monitorBufferTick_ to run as soon as possible
  33228. *
  33229. * @private
  33230. */
  33231. }, {
  33232. key: 'monitorBuffer_',
  33233. value: function monitorBuffer_() {
  33234. if (this.checkBufferTimeout_) {
  33235. window$1.clearTimeout(this.checkBufferTimeout_);
  33236. }
  33237. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1);
  33238. }
  33239. /**
  33240. * As long as the SegmentLoader is in the READY state, periodically
  33241. * invoke fillBuffer_().
  33242. *
  33243. * @private
  33244. */
  33245. }, {
  33246. key: 'monitorBufferTick_',
  33247. value: function monitorBufferTick_() {
  33248. if (this.state === 'READY') {
  33249. this.fillBuffer_();
  33250. }
  33251. if (this.checkBufferTimeout_) {
  33252. window$1.clearTimeout(this.checkBufferTimeout_);
  33253. }
  33254. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
  33255. }
  33256. /**
  33257. * fill the buffer with segements unless the sourceBuffers are
  33258. * currently updating
  33259. *
  33260. * Note: this function should only ever be called by monitorBuffer_
  33261. * and never directly
  33262. *
  33263. * @private
  33264. */
  33265. }, {
  33266. key: 'fillBuffer_',
  33267. value: function fillBuffer_() {
  33268. if (this.sourceUpdater_.updating()) {
  33269. return;
  33270. }
  33271. if (!this.syncPoint_) {
  33272. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  33273. } // see if we need to begin loading immediately
  33274. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  33275. if (!segmentInfo) {
  33276. return;
  33277. }
  33278. if (this.isEndOfStream_(segmentInfo.mediaIndex)) {
  33279. this.endOfStream();
  33280. return;
  33281. }
  33282. if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
  33283. return;
  33284. } // We will need to change timestampOffset of the sourceBuffer if either of
  33285. // the following conditions are true:
  33286. // - The segment.timeline !== this.currentTimeline
  33287. // (we are crossing a discontinuity somehow)
  33288. // - The "timestampOffset" for the start of this segment is less than
  33289. // the currently set timestampOffset
  33290. // Also, clear captions if we are crossing a discontinuity boundary
  33291. if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) {
  33292. this.syncController_.reset();
  33293. segmentInfo.timestampOffset = segmentInfo.startOfSegment;
  33294. this.captionParser_.clearAllCaptions();
  33295. }
  33296. this.loadSegment_(segmentInfo);
  33297. }
  33298. /**
  33299. * Determines if this segment loader is at the end of it's stream.
  33300. *
  33301. * @param {Number} mediaIndex the index of segment we last appended
  33302. * @param {Object} [playlist=this.playlist_] a media playlist object
  33303. * @returns {Boolean} true if at end of stream, false otherwise.
  33304. */
  33305. }, {
  33306. key: 'isEndOfStream_',
  33307. value: function isEndOfStream_(mediaIndex) {
  33308. var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_;
  33309. return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating();
  33310. }
  33311. /**
  33312. * Determines what segment request should be made, given current playback
  33313. * state.
  33314. *
  33315. * @param {TimeRanges} buffered - the state of the buffer
  33316. * @param {Object} playlist - the playlist object to fetch segments from
  33317. * @param {Number} mediaIndex - the previous mediaIndex fetched or null
  33318. * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
  33319. * @param {Number} currentTime - the playback position in seconds
  33320. * @param {Object} syncPoint - a segment info object that describes the
  33321. * @returns {Object} a segment request object that describes the segment to load
  33322. */
  33323. }, {
  33324. key: 'checkBuffer_',
  33325. value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
  33326. var lastBufferedEnd = 0;
  33327. var startOfSegment = void 0;
  33328. if (buffered.length) {
  33329. lastBufferedEnd = buffered.end(buffered.length - 1);
  33330. }
  33331. var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
  33332. if (!playlist.segments.length) {
  33333. return null;
  33334. } // if there is plenty of content buffered, and the video has
  33335. // been played before relax for awhile
  33336. if (bufferedTime >= this.goalBufferLength_()) {
  33337. return null;
  33338. } // if the video has not yet played once, and we already have
  33339. // one segment downloaded do nothing
  33340. if (!hasPlayed && bufferedTime >= 1) {
  33341. return null;
  33342. } // When the syncPoint is null, there is no way of determining a good
  33343. // conservative segment index to fetch from
  33344. // The best thing to do here is to get the kind of sync-point data by
  33345. // making a request
  33346. if (syncPoint === null) {
  33347. mediaIndex = this.getSyncSegmentCandidate_(playlist);
  33348. return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
  33349. } // Under normal playback conditions fetching is a simple walk forward
  33350. if (mediaIndex !== null) {
  33351. var segment = playlist.segments[mediaIndex];
  33352. if (segment && segment.end) {
  33353. startOfSegment = segment.end;
  33354. } else {
  33355. startOfSegment = lastBufferedEnd;
  33356. }
  33357. return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
  33358. } // There is a sync-point but the lack of a mediaIndex indicates that
  33359. // we need to make a good conservative guess about which segment to
  33360. // fetch
  33361. if (this.fetchAtBuffer_) {
  33362. // Find the segment containing the end of the buffer
  33363. var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
  33364. mediaIndex = mediaSourceInfo.mediaIndex;
  33365. startOfSegment = mediaSourceInfo.startTime;
  33366. } else {
  33367. // Find the segment containing currentTime
  33368. var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
  33369. mediaIndex = _mediaSourceInfo.mediaIndex;
  33370. startOfSegment = _mediaSourceInfo.startTime;
  33371. }
  33372. return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
  33373. }
  33374. /**
  33375. * The segment loader has no recourse except to fetch a segment in the
  33376. * current playlist and use the internal timestamps in that segment to
  33377. * generate a syncPoint. This function returns a good candidate index
  33378. * for that process.
  33379. *
  33380. * @param {Object} playlist - the playlist object to look for a
  33381. * @returns {Number} An index of a segment from the playlist to load
  33382. */
  33383. }, {
  33384. key: 'getSyncSegmentCandidate_',
  33385. value: function getSyncSegmentCandidate_(playlist) {
  33386. var _this2 = this;
  33387. if (this.currentTimeline_ === -1) {
  33388. return 0;
  33389. }
  33390. var segmentIndexArray = playlist.segments.map(function (s, i) {
  33391. return {
  33392. timeline: s.timeline,
  33393. segmentIndex: i
  33394. };
  33395. }).filter(function (s) {
  33396. return s.timeline === _this2.currentTimeline_;
  33397. });
  33398. if (segmentIndexArray.length) {
  33399. return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
  33400. }
  33401. return Math.max(playlist.segments.length - 1, 0);
  33402. }
  33403. }, {
  33404. key: 'generateSegmentInfo_',
  33405. value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
  33406. if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
  33407. return null;
  33408. }
  33409. var segment = playlist.segments[mediaIndex];
  33410. return {
  33411. requestId: 'segment-loader-' + Math.random(),
  33412. // resolve the segment URL relative to the playlist
  33413. uri: segment.resolvedUri,
  33414. // the segment's mediaIndex at the time it was requested
  33415. mediaIndex: mediaIndex,
  33416. // whether or not to update the SegmentLoader's state with this
  33417. // segment's mediaIndex
  33418. isSyncRequest: isSyncRequest,
  33419. startOfSegment: startOfSegment,
  33420. // the segment's playlist
  33421. playlist: playlist,
  33422. // unencrypted bytes of the segment
  33423. bytes: null,
  33424. // when a key is defined for this segment, the encrypted bytes
  33425. encryptedBytes: null,
  33426. // The target timestampOffset for this segment when we append it
  33427. // to the source buffer
  33428. timestampOffset: null,
  33429. // The timeline that the segment is in
  33430. timeline: segment.timeline,
  33431. // The expected duration of the segment in seconds
  33432. duration: segment.duration,
  33433. // retain the segment in case the playlist updates while doing an async process
  33434. segment: segment
  33435. };
  33436. }
  33437. /**
  33438. * Determines if the network has enough bandwidth to complete the current segment
  33439. * request in a timely manner. If not, the request will be aborted early and bandwidth
  33440. * updated to trigger a playlist switch.
  33441. *
  33442. * @param {Object} stats
  33443. * Object containing stats about the request timing and size
  33444. * @return {Boolean} True if the request was aborted, false otherwise
  33445. * @private
  33446. */
  33447. }, {
  33448. key: 'abortRequestEarly_',
  33449. value: function abortRequestEarly_(stats) {
  33450. if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
  33451. // TODO: Replace using timeout with a boolean indicating whether this playlist is
  33452. // the lowestEnabledRendition.
  33453. !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
  33454. !this.playlist_.attributes.BANDWIDTH) {
  33455. return false;
  33456. } // Wait at least 1 second since the first byte of data has been received before
  33457. // using the calculated bandwidth from the progress event to allow the bitrate
  33458. // to stabilize
  33459. if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
  33460. return false;
  33461. }
  33462. var currentTime = this.currentTime_();
  33463. var measuredBandwidth = stats.bandwidth;
  33464. var segmentDuration = this.pendingSegment_.duration;
  33465. var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
  33466. // if we are only left with less than 1 second when the request completes.
  33467. // A negative timeUntilRebuffering indicates we are already rebuffering
  33468. var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
  33469. // is larger than the estimated time until the player runs out of forward buffer
  33470. if (requestTimeRemaining <= timeUntilRebuffer$$1) {
  33471. return false;
  33472. }
  33473. var switchCandidate = minRebufferMaxBandwidthSelector({
  33474. master: this.hls_.playlists.master,
  33475. currentTime: currentTime,
  33476. bandwidth: measuredBandwidth,
  33477. duration: this.duration_(),
  33478. segmentDuration: segmentDuration,
  33479. timeUntilRebuffer: timeUntilRebuffer$$1,
  33480. currentTimeline: this.currentTimeline_,
  33481. syncController: this.syncController_
  33482. });
  33483. if (!switchCandidate) {
  33484. return;
  33485. }
  33486. var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1;
  33487. var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
  33488. var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
  33489. // potential round trip time of the new request so that we are not too aggressive
  33490. // with switching to a playlist that might save us a fraction of a second.
  33491. if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) {
  33492. minimumTimeSaving = 1;
  33493. }
  33494. if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
  33495. return false;
  33496. } // set the bandwidth to that of the desired playlist being sure to scale by
  33497. // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
  33498. // don't trigger a bandwidthupdate as the bandwidth is artifial
  33499. this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
  33500. this.abort();
  33501. this.trigger('earlyabort');
  33502. return true;
  33503. }
  33504. /**
  33505. * XHR `progress` event handler
  33506. *
  33507. * @param {Event}
  33508. * The XHR `progress` event
  33509. * @param {Object} simpleSegment
  33510. * A simplified segment object copy
  33511. * @private
  33512. */
  33513. }, {
  33514. key: 'handleProgress_',
  33515. value: function handleProgress_(event, simpleSegment) {
  33516. if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
  33517. return;
  33518. }
  33519. this.trigger('progress');
  33520. }
  33521. /**
  33522. * load a specific segment from a request into the buffer
  33523. *
  33524. * @private
  33525. */
  33526. }, {
  33527. key: 'loadSegment_',
  33528. value: function loadSegment_(segmentInfo) {
  33529. this.state = 'WAITING';
  33530. this.pendingSegment_ = segmentInfo;
  33531. this.trimBackBuffer_(segmentInfo);
  33532. segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback
  33533. this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
  33534. }
  33535. /**
  33536. * trim the back buffer so that we don't have too much data
  33537. * in the source buffer
  33538. *
  33539. * @private
  33540. *
  33541. * @param {Object} segmentInfo - the current segment
  33542. */
  33543. }, {
  33544. key: 'trimBackBuffer_',
  33545. value: function trimBackBuffer_(segmentInfo) {
  33546. var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
  33547. // buffer and a very conservative "garbage collector"
  33548. // We manually clear out the old buffer to ensure
  33549. // we don't trigger the QuotaExceeded error
  33550. // on the source buffer during subsequent appends
  33551. if (removeToTime > 0) {
  33552. this.remove(0, removeToTime);
  33553. }
  33554. }
  33555. /**
  33556. * created a simplified copy of the segment object with just the
  33557. * information necessary to perform the XHR and decryption
  33558. *
  33559. * @private
  33560. *
  33561. * @param {Object} segmentInfo - the current segment
  33562. * @returns {Object} a simplified segment object copy
  33563. */
  33564. }, {
  33565. key: 'createSimplifiedSegmentObj_',
  33566. value: function createSimplifiedSegmentObj_(segmentInfo) {
  33567. var segment = segmentInfo.segment;
  33568. var simpleSegment = {
  33569. resolvedUri: segment.resolvedUri,
  33570. byterange: segment.byterange,
  33571. requestId: segmentInfo.requestId
  33572. };
  33573. if (segment.key) {
  33574. // if the media sequence is greater than 2^32, the IV will be incorrect
  33575. // assuming 10s segments, that would be about 1300 years
  33576. var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
  33577. simpleSegment.key = this.segmentKey(segment.key);
  33578. simpleSegment.key.iv = iv;
  33579. }
  33580. if (segment.map) {
  33581. simpleSegment.map = this.initSegment(segment.map);
  33582. }
  33583. return simpleSegment;
  33584. }
  33585. /**
  33586. * Handle the callback from the segmentRequest function and set the
  33587. * associated SegmentLoader state and errors if necessary
  33588. *
  33589. * @private
  33590. */
  33591. }, {
  33592. key: 'segmentRequestFinished_',
  33593. value: function segmentRequestFinished_(error, simpleSegment) {
  33594. // every request counts as a media request even if it has been aborted
  33595. // or canceled due to a timeout
  33596. this.mediaRequests += 1;
  33597. if (simpleSegment.stats) {
  33598. this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
  33599. this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
  33600. } // The request was aborted and the SegmentLoader has already been reset
  33601. if (!this.pendingSegment_) {
  33602. this.mediaRequestsAborted += 1;
  33603. return;
  33604. } // the request was aborted and the SegmentLoader has already started
  33605. // another request. this can happen when the timeout for an aborted
  33606. // request triggers due to a limitation in the XHR library
  33607. // do not count this as any sort of request or we risk double-counting
  33608. if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
  33609. return;
  33610. } // an error occurred from the active pendingSegment_ so reset everything
  33611. if (error) {
  33612. this.pendingSegment_ = null;
  33613. this.state = 'READY'; // the requests were aborted just record the aborted stat and exit
  33614. // this is not a true error condition and nothing corrective needs
  33615. // to be done
  33616. if (error.code === REQUEST_ERRORS.ABORTED) {
  33617. this.mediaRequestsAborted += 1;
  33618. return;
  33619. }
  33620. this.pause(); // the error is really just that at least one of the requests timed-out
  33621. // set the bandwidth to a very low value and trigger an ABR switch to
  33622. // take emergency action
  33623. if (error.code === REQUEST_ERRORS.TIMEOUT) {
  33624. this.mediaRequestsTimedout += 1;
  33625. this.bandwidth = 1;
  33626. this.roundTrip = NaN;
  33627. this.trigger('bandwidthupdate');
  33628. return;
  33629. } // if control-flow has arrived here, then the error is real
  33630. // emit an error event to blacklist the current playlist
  33631. this.mediaRequestsErrored += 1;
  33632. this.error(error);
  33633. this.trigger('error');
  33634. return;
  33635. } // the response was a success so set any bandwidth stats the request
  33636. // generated for ABR purposes
  33637. this.bandwidth = simpleSegment.stats.bandwidth;
  33638. this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data
  33639. // to the initSegment cache
  33640. if (simpleSegment.map) {
  33641. simpleSegment.map = this.initSegment(simpleSegment.map, true);
  33642. } // if this request included a segment key, save that data in the cache
  33643. if (simpleSegment.key) {
  33644. this.segmentKey(simpleSegment.key, true);
  33645. }
  33646. this.processSegmentResponse_(simpleSegment);
  33647. }
  33648. /**
  33649. * Move any important data from the simplified segment object
  33650. * back to the real segment object for future phases
  33651. *
  33652. * @private
  33653. */
  33654. }, {
  33655. key: 'processSegmentResponse_',
  33656. value: function processSegmentResponse_(simpleSegment) {
  33657. var segmentInfo = this.pendingSegment_;
  33658. segmentInfo.bytes = simpleSegment.bytes;
  33659. if (simpleSegment.map) {
  33660. segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
  33661. }
  33662. segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks
  33663. if (simpleSegment.fmp4Captions) {
  33664. createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams);
  33665. addCaptionData({
  33666. inbandTextTracks: this.inbandTextTracks_,
  33667. captionArray: simpleSegment.fmp4Captions,
  33668. // fmp4s will not have a timestamp offset
  33669. timestampOffset: 0
  33670. }); // Reset stored captions since we added parsed
  33671. // captions to a text track at this point
  33672. this.captionParser_.clearParsedCaptions();
  33673. }
  33674. this.handleSegment_();
  33675. }
  33676. /**
  33677. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  33678. *
  33679. * @private
  33680. */
  33681. }, {
  33682. key: 'handleSegment_',
  33683. value: function handleSegment_() {
  33684. var _this3 = this;
  33685. if (!this.pendingSegment_) {
  33686. this.state = 'READY';
  33687. return;
  33688. }
  33689. var segmentInfo = this.pendingSegment_;
  33690. var segment = segmentInfo.segment;
  33691. var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is
  33692. // dealing with. Although we're maintaining extra state, it helps to preserve the
  33693. // separation of segment loader from the actual source buffers.
  33694. if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are
  33695. // certain that all streams will provide it.
  33696. timingInfo.containsAudio || timingInfo.containsVideo)) {
  33697. this.startingMedia_ = {
  33698. containsAudio: timingInfo.containsAudio,
  33699. containsVideo: timingInfo.containsVideo
  33700. };
  33701. }
  33702. var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
  33703. if (illegalMediaSwitchError) {
  33704. this.error({
  33705. message: illegalMediaSwitchError,
  33706. blacklistDuration: Infinity
  33707. });
  33708. this.trigger('error');
  33709. return;
  33710. }
  33711. if (segmentInfo.isSyncRequest) {
  33712. this.trigger('syncinfoupdate');
  33713. this.pendingSegment_ = null;
  33714. this.state = 'READY';
  33715. return;
  33716. }
  33717. if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
  33718. this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities)
  33719. this.trigger('timestampoffset');
  33720. }
  33721. var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
  33722. if (timelineMapping !== null) {
  33723. this.trigger({
  33724. type: 'segmenttimemapping',
  33725. mapping: timelineMapping
  33726. });
  33727. }
  33728. this.state = 'APPENDING'; // if the media initialization segment is changing, append it
  33729. // before the content segment
  33730. if (segment.map) {
  33731. var initId = initSegmentId(segment.map);
  33732. if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) {
  33733. var initSegment = this.initSegment(segment.map);
  33734. this.sourceUpdater_.appendBuffer({
  33735. bytes: initSegment.bytes
  33736. }, function () {
  33737. _this3.activeInitSegmentId_ = initId;
  33738. });
  33739. }
  33740. }
  33741. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  33742. if (typeof segment.start === 'number' && typeof segment.end === 'number') {
  33743. this.mediaSecondsLoaded += segment.end - segment.start;
  33744. } else {
  33745. this.mediaSecondsLoaded += segment.duration;
  33746. }
  33747. this.logger_(segmentInfoString(segmentInfo));
  33748. this.sourceUpdater_.appendBuffer({
  33749. bytes: segmentInfo.bytes,
  33750. videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId)
  33751. }, this.handleUpdateEnd_.bind(this));
  33752. }
  33753. }, {
  33754. key: 'handleVideoSegmentTimingInfo_',
  33755. value: function handleVideoSegmentTimingInfo_(requestId, event) {
  33756. if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
  33757. return;
  33758. }
  33759. var segment = this.pendingSegment_.segment;
  33760. if (!segment.videoTimingInfo) {
  33761. segment.videoTimingInfo = {};
  33762. }
  33763. segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0;
  33764. segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation;
  33765. segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging
  33766. segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime;
  33767. }
  33768. /**
  33769. * callback to run when appendBuffer is finished. detects if we are
  33770. * in a good state to do things with the data we got, or if we need
  33771. * to wait for more
  33772. *
  33773. * @private
  33774. */
  33775. }, {
  33776. key: 'handleUpdateEnd_',
  33777. value: function handleUpdateEnd_() {
  33778. if (!this.pendingSegment_) {
  33779. this.state = 'READY';
  33780. if (!this.paused()) {
  33781. this.monitorBuffer_();
  33782. }
  33783. return;
  33784. }
  33785. var segmentInfo = this.pendingSegment_;
  33786. var segment = segmentInfo.segment;
  33787. var isWalkingForward = this.mediaIndex !== null;
  33788. this.pendingSegment_ = null;
  33789. this.recordThroughput_(segmentInfo);
  33790. this.addSegmentMetadataCue_(segmentInfo);
  33791. this.state = 'READY';
  33792. this.mediaIndex = segmentInfo.mediaIndex;
  33793. this.fetchAtBuffer_ = true;
  33794. this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
  33795. // the following conditional otherwise it may consider this a bad "guess"
  33796. // and attempt to resync when the post-update seekable window and live
  33797. // point would mean that this was the perfect segment to fetch
  33798. this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before
  33799. // the currentTime_ that means that our conservative guess was too conservative.
  33800. // In that case, reset the loader state so that we try to use any information gained
  33801. // from the previous request to create a new, more accurate, sync-point.
  33802. if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
  33803. this.resetEverything();
  33804. return;
  33805. } // Don't do a rendition switch unless we have enough time to get a sync segment
  33806. // and conservatively guess
  33807. if (isWalkingForward) {
  33808. this.trigger('bandwidthupdate');
  33809. }
  33810. this.trigger('progress'); // any time an update finishes and the last segment is in the
  33811. // buffer, end the stream. this ensures the "ended" event will
  33812. // fire if playback reaches that point.
  33813. if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) {
  33814. this.endOfStream();
  33815. }
  33816. if (!this.paused()) {
  33817. this.monitorBuffer_();
  33818. }
  33819. }
  33820. /**
  33821. * Records the current throughput of the decrypt, transmux, and append
  33822. * portion of the semgment pipeline. `throughput.rate` is a the cumulative
  33823. * moving average of the throughput. `throughput.count` is the number of
  33824. * data points in the average.
  33825. *
  33826. * @private
  33827. * @param {Object} segmentInfo the object returned by loadSegment
  33828. */
  33829. }, {
  33830. key: 'recordThroughput_',
  33831. value: function recordThroughput_(segmentInfo) {
  33832. var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
  33833. // by zero in the case where the throughput is ridiculously high
  33834. var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
  33835. var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
  33836. // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
  33837. this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
  33838. }
  33839. /**
  33840. * Adds a cue to the segment-metadata track with some metadata information about the
  33841. * segment
  33842. *
  33843. * @private
  33844. * @param {Object} segmentInfo
  33845. * the object returned by loadSegment
  33846. * @method addSegmentMetadataCue_
  33847. */
  33848. }, {
  33849. key: 'addSegmentMetadataCue_',
  33850. value: function addSegmentMetadataCue_(segmentInfo) {
  33851. if (!this.segmentMetadataTrack_) {
  33852. return;
  33853. }
  33854. var segment = segmentInfo.segment;
  33855. var start = segment.start;
  33856. var end = segment.end; // Do not try adding the cue if the start and end times are invalid.
  33857. if (!finite(start) || !finite(end)) {
  33858. return;
  33859. }
  33860. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  33861. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  33862. var value = {
  33863. custom: segment.custom,
  33864. dateTimeObject: segment.dateTimeObject,
  33865. dateTimeString: segment.dateTimeString,
  33866. bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
  33867. resolution: segmentInfo.playlist.attributes.RESOLUTION,
  33868. codecs: segmentInfo.playlist.attributes.CODECS,
  33869. byteLength: segmentInfo.byteLength,
  33870. uri: segmentInfo.uri,
  33871. timeline: segmentInfo.timeline,
  33872. playlist: segmentInfo.playlist.uri,
  33873. start: start,
  33874. end: end
  33875. };
  33876. var data = JSON.stringify(value);
  33877. var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
  33878. // the differences of WebKitDataCue in safari and VTTCue in other browsers
  33879. cue.value = value;
  33880. this.segmentMetadataTrack_.addCue(cue);
  33881. }
  33882. }]);
  33883. return SegmentLoader;
  33884. }(videojs$1.EventTarget);
  33885. var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
  33886. return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
  33887. };
  33888. /**
  33889. * @file vtt-segment-loader.js
  33890. */
  33891. var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) {
  33892. return char.charCodeAt(0);
  33893. }));
  33894. /**
  33895. * An object that manages segment loading and appending.
  33896. *
  33897. * @class VTTSegmentLoader
  33898. * @param {Object} options required and optional options
  33899. * @extends videojs.EventTarget
  33900. */
  33901. var VTTSegmentLoader = function (_SegmentLoader) {
  33902. inherits(VTTSegmentLoader, _SegmentLoader);
  33903. function VTTSegmentLoader(settings) {
  33904. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  33905. classCallCheck(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error;
  33906. // however, VTTSegmentLoader has no need of a media source, so delete the reference
  33907. var _this = possibleConstructorReturn(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options));
  33908. _this.mediaSource_ = null;
  33909. _this.subtitlesTrack_ = null;
  33910. return _this;
  33911. }
  33912. /**
  33913. * Indicates which time ranges are buffered
  33914. *
  33915. * @return {TimeRange}
  33916. * TimeRange object representing the current buffered ranges
  33917. */
  33918. createClass(VTTSegmentLoader, [{
  33919. key: 'buffered_',
  33920. value: function buffered_() {
  33921. if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
  33922. return videojs$1.createTimeRanges();
  33923. }
  33924. var cues = this.subtitlesTrack_.cues;
  33925. var start = cues[0].startTime;
  33926. var end = cues[cues.length - 1].startTime;
  33927. return videojs$1.createTimeRanges([[start, end]]);
  33928. }
  33929. /**
  33930. * Gets and sets init segment for the provided map
  33931. *
  33932. * @param {Object} map
  33933. * The map object representing the init segment to get or set
  33934. * @param {Boolean=} set
  33935. * If true, the init segment for the provided map should be saved
  33936. * @return {Object}
  33937. * map object for desired init segment
  33938. */
  33939. }, {
  33940. key: 'initSegment',
  33941. value: function initSegment(map) {
  33942. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  33943. if (!map) {
  33944. return null;
  33945. }
  33946. var id = initSegmentId(map);
  33947. var storedMap = this.initSegments_[id];
  33948. if (set$$1 && !storedMap && map.bytes) {
  33949. // append WebVTT line terminators to the media initialization segment if it exists
  33950. // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
  33951. // requires two or more WebVTT line terminators between the WebVTT header and the
  33952. // rest of the file
  33953. var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
  33954. var combinedSegment = new Uint8Array(combinedByteLength);
  33955. combinedSegment.set(map.bytes);
  33956. combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
  33957. this.initSegments_[id] = storedMap = {
  33958. resolvedUri: map.resolvedUri,
  33959. byterange: map.byterange,
  33960. bytes: combinedSegment
  33961. };
  33962. }
  33963. return storedMap || map;
  33964. }
  33965. /**
  33966. * Returns true if all configuration required for loading is present, otherwise false.
  33967. *
  33968. * @return {Boolean} True if the all configuration is ready for loading
  33969. * @private
  33970. */
  33971. }, {
  33972. key: 'couldBeginLoading_',
  33973. value: function couldBeginLoading_() {
  33974. return this.playlist_ && this.subtitlesTrack_ && !this.paused();
  33975. }
  33976. /**
  33977. * Once all the starting parameters have been specified, begin
  33978. * operation. This method should only be invoked from the INIT
  33979. * state.
  33980. *
  33981. * @private
  33982. */
  33983. }, {
  33984. key: 'init_',
  33985. value: function init_() {
  33986. this.state = 'READY';
  33987. this.resetEverything();
  33988. return this.monitorBuffer_();
  33989. }
  33990. /**
  33991. * Set a subtitle track on the segment loader to add subtitles to
  33992. *
  33993. * @param {TextTrack=} track
  33994. * The text track to add loaded subtitles to
  33995. * @return {TextTrack}
  33996. * Returns the subtitles track
  33997. */
  33998. }, {
  33999. key: 'track',
  34000. value: function track(_track) {
  34001. if (typeof _track === 'undefined') {
  34002. return this.subtitlesTrack_;
  34003. }
  34004. this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
  34005. // buffering now
  34006. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  34007. this.init_();
  34008. }
  34009. return this.subtitlesTrack_;
  34010. }
  34011. /**
  34012. * Remove any data in the source buffer between start and end times
  34013. * @param {Number} start - the start time of the region to remove from the buffer
  34014. * @param {Number} end - the end time of the region to remove from the buffer
  34015. */
  34016. }, {
  34017. key: 'remove',
  34018. value: function remove(start, end) {
  34019. removeCuesFromTrack(start, end, this.subtitlesTrack_);
  34020. }
  34021. /**
  34022. * fill the buffer with segements unless the sourceBuffers are
  34023. * currently updating
  34024. *
  34025. * Note: this function should only ever be called by monitorBuffer_
  34026. * and never directly
  34027. *
  34028. * @private
  34029. */
  34030. }, {
  34031. key: 'fillBuffer_',
  34032. value: function fillBuffer_() {
  34033. var _this2 = this;
  34034. if (!this.syncPoint_) {
  34035. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  34036. } // see if we need to begin loading immediately
  34037. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  34038. segmentInfo = this.skipEmptySegments_(segmentInfo);
  34039. if (!segmentInfo) {
  34040. return;
  34041. }
  34042. if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
  34043. // We don't have the timestamp offset that we need to sync subtitles.
  34044. // Rerun on a timestamp offset or user interaction.
  34045. var checkTimestampOffset = function checkTimestampOffset() {
  34046. _this2.state = 'READY';
  34047. if (!_this2.paused()) {
  34048. // if not paused, queue a buffer check as soon as possible
  34049. _this2.monitorBuffer_();
  34050. }
  34051. };
  34052. this.syncController_.one('timestampoffset', checkTimestampOffset);
  34053. this.state = 'WAITING_ON_TIMELINE';
  34054. return;
  34055. }
  34056. this.loadSegment_(segmentInfo);
  34057. }
  34058. /**
  34059. * Prevents the segment loader from requesting segments we know contain no subtitles
  34060. * by walking forward until we find the next segment that we don't know whether it is
  34061. * empty or not.
  34062. *
  34063. * @param {Object} segmentInfo
  34064. * a segment info object that describes the current segment
  34065. * @return {Object}
  34066. * a segment info object that describes the current segment
  34067. */
  34068. }, {
  34069. key: 'skipEmptySegments_',
  34070. value: function skipEmptySegments_(segmentInfo) {
  34071. while (segmentInfo && segmentInfo.segment.empty) {
  34072. segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
  34073. }
  34074. return segmentInfo;
  34075. }
  34076. /**
  34077. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  34078. *
  34079. * @private
  34080. */
  34081. }, {
  34082. key: 'handleSegment_',
  34083. value: function handleSegment_() {
  34084. var _this3 = this;
  34085. if (!this.pendingSegment_ || !this.subtitlesTrack_) {
  34086. this.state = 'READY';
  34087. return;
  34088. }
  34089. this.state = 'APPENDING';
  34090. var segmentInfo = this.pendingSegment_;
  34091. var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  34092. if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
  34093. var loadHandler = function loadHandler() {
  34094. _this3.handleSegment_();
  34095. };
  34096. this.state = 'WAITING_ON_VTTJS';
  34097. this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
  34098. this.subtitlesTrack_.tech_.one('vttjserror', function () {
  34099. _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
  34100. _this3.error({
  34101. message: 'Error loading vtt.js'
  34102. });
  34103. _this3.state = 'READY';
  34104. _this3.pause();
  34105. _this3.trigger('error');
  34106. });
  34107. return;
  34108. }
  34109. segment.requested = true;
  34110. try {
  34111. this.parseVTTCues_(segmentInfo);
  34112. } catch (e) {
  34113. this.error({
  34114. message: e.message
  34115. });
  34116. this.state = 'READY';
  34117. this.pause();
  34118. return this.trigger('error');
  34119. }
  34120. this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
  34121. if (segmentInfo.isSyncRequest) {
  34122. this.trigger('syncinfoupdate');
  34123. this.pendingSegment_ = null;
  34124. this.state = 'READY';
  34125. return;
  34126. }
  34127. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  34128. this.mediaSecondsLoaded += segment.duration;
  34129. if (segmentInfo.cues.length) {
  34130. // remove any overlapping cues to prevent doubling
  34131. this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime);
  34132. }
  34133. segmentInfo.cues.forEach(function (cue) {
  34134. _this3.subtitlesTrack_.addCue(cue);
  34135. });
  34136. this.handleUpdateEnd_();
  34137. }
  34138. /**
  34139. * Uses the WebVTT parser to parse the segment response
  34140. *
  34141. * @param {Object} segmentInfo
  34142. * a segment info object that describes the current segment
  34143. * @private
  34144. */
  34145. }, {
  34146. key: 'parseVTTCues_',
  34147. value: function parseVTTCues_(segmentInfo) {
  34148. var decoder = void 0;
  34149. var decodeBytesToString = false;
  34150. if (typeof window$1.TextDecoder === 'function') {
  34151. decoder = new window$1.TextDecoder('utf8');
  34152. } else {
  34153. decoder = window$1.WebVTT.StringDecoder();
  34154. decodeBytesToString = true;
  34155. }
  34156. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder);
  34157. segmentInfo.cues = [];
  34158. segmentInfo.timestampmap = {
  34159. MPEGTS: 0,
  34160. LOCAL: 0
  34161. };
  34162. parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
  34163. parser.ontimestampmap = function (map) {
  34164. return segmentInfo.timestampmap = map;
  34165. };
  34166. parser.onparsingerror = function (error) {
  34167. videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
  34168. };
  34169. if (segmentInfo.segment.map) {
  34170. var mapData = segmentInfo.segment.map.bytes;
  34171. if (decodeBytesToString) {
  34172. mapData = uint8ToUtf8(mapData);
  34173. }
  34174. parser.parse(mapData);
  34175. }
  34176. var segmentData = segmentInfo.bytes;
  34177. if (decodeBytesToString) {
  34178. segmentData = uint8ToUtf8(segmentData);
  34179. }
  34180. parser.parse(segmentData);
  34181. parser.flush();
  34182. }
  34183. /**
  34184. * Updates the start and end times of any cues parsed by the WebVTT parser using
  34185. * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
  34186. * from the SyncController
  34187. *
  34188. * @param {Object} segmentInfo
  34189. * a segment info object that describes the current segment
  34190. * @param {Object} mappingObj
  34191. * object containing a mapping from TS to media time
  34192. * @param {Object} playlist
  34193. * the playlist object containing the segment
  34194. * @private
  34195. */
  34196. }, {
  34197. key: 'updateTimeMapping_',
  34198. value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
  34199. var segment = segmentInfo.segment;
  34200. if (!mappingObj) {
  34201. // If the sync controller does not have a mapping of TS to Media Time for the
  34202. // timeline, then we don't have enough information to update the cue
  34203. // start/end times
  34204. return;
  34205. }
  34206. if (!segmentInfo.cues.length) {
  34207. // If there are no cues, we also do not have enough information to figure out
  34208. // segment timing. Mark that the segment contains no cues so we don't re-request
  34209. // an empty segment.
  34210. segment.empty = true;
  34211. return;
  34212. }
  34213. var timestampmap = segmentInfo.timestampmap;
  34214. var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping;
  34215. segmentInfo.cues.forEach(function (cue) {
  34216. // First convert cue time to TS time using the timestamp-map provided within the vtt
  34217. cue.startTime += diff;
  34218. cue.endTime += diff;
  34219. });
  34220. if (!playlist.syncInfo) {
  34221. var firstStart = segmentInfo.cues[0].startTime;
  34222. var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
  34223. playlist.syncInfo = {
  34224. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  34225. time: Math.min(firstStart, lastStart - segment.duration)
  34226. };
  34227. }
  34228. }
  34229. }]);
  34230. return VTTSegmentLoader;
  34231. }(SegmentLoader);
  34232. /**
  34233. * @file ad-cue-tags.js
  34234. */
  34235. /**
  34236. * Searches for an ad cue that overlaps with the given mediaTime
  34237. */
  34238. var findAdCue = function findAdCue(track, mediaTime) {
  34239. var cues = track.cues;
  34240. for (var i = 0; i < cues.length; i++) {
  34241. var cue = cues[i];
  34242. if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
  34243. return cue;
  34244. }
  34245. }
  34246. return null;
  34247. };
  34248. var updateAdCues = function updateAdCues(media, track) {
  34249. var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  34250. if (!media.segments) {
  34251. return;
  34252. }
  34253. var mediaTime = offset;
  34254. var cue = void 0;
  34255. for (var i = 0; i < media.segments.length; i++) {
  34256. var segment = media.segments[i];
  34257. if (!cue) {
  34258. // Since the cues will span for at least the segment duration, adding a fudge
  34259. // factor of half segment duration will prevent duplicate cues from being
  34260. // created when timing info is not exact (e.g. cue start time initialized
  34261. // at 10.006677, but next call mediaTime is 10.003332 )
  34262. cue = findAdCue(track, mediaTime + segment.duration / 2);
  34263. }
  34264. if (cue) {
  34265. if ('cueIn' in segment) {
  34266. // Found a CUE-IN so end the cue
  34267. cue.endTime = mediaTime;
  34268. cue.adEndTime = mediaTime;
  34269. mediaTime += segment.duration;
  34270. cue = null;
  34271. continue;
  34272. }
  34273. if (mediaTime < cue.endTime) {
  34274. // Already processed this mediaTime for this cue
  34275. mediaTime += segment.duration;
  34276. continue;
  34277. } // otherwise extend cue until a CUE-IN is found
  34278. cue.endTime += segment.duration;
  34279. } else {
  34280. if ('cueOut' in segment) {
  34281. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
  34282. cue.adStartTime = mediaTime; // Assumes tag format to be
  34283. // #EXT-X-CUE-OUT:30
  34284. cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
  34285. track.addCue(cue);
  34286. }
  34287. if ('cueOutCont' in segment) {
  34288. // Entered into the middle of an ad cue
  34289. var adOffset = void 0;
  34290. var adTotal = void 0; // Assumes tag formate to be
  34291. // #EXT-X-CUE-OUT-CONT:10/30
  34292. var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat);
  34293. var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2);
  34294. adOffset = _segment$cueOutCont$s2[0];
  34295. adTotal = _segment$cueOutCont$s2[1];
  34296. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, '');
  34297. cue.adStartTime = mediaTime - adOffset;
  34298. cue.adEndTime = cue.adStartTime + adTotal;
  34299. track.addCue(cue);
  34300. }
  34301. }
  34302. mediaTime += segment.duration;
  34303. }
  34304. };
  34305. /**
  34306. * @file sync-controller.js
  34307. */
  34308. var tsprobe = tsInspector.inspect;
  34309. var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
  34310. // the equivalence display-time 0 === segment-index 0
  34311. {
  34312. name: 'VOD',
  34313. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34314. if (duration$$1 !== Infinity) {
  34315. var syncPoint = {
  34316. time: 0,
  34317. segmentIndex: 0
  34318. };
  34319. return syncPoint;
  34320. }
  34321. return null;
  34322. }
  34323. }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
  34324. {
  34325. name: 'ProgramDateTime',
  34326. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34327. if (!syncController.datetimeToDisplayTime) {
  34328. return null;
  34329. }
  34330. var segments = playlist.segments || [];
  34331. var syncPoint = null;
  34332. var lastDistance = null;
  34333. currentTime = currentTime || 0;
  34334. for (var i = 0; i < segments.length; i++) {
  34335. var segment = segments[i];
  34336. if (segment.dateTimeObject) {
  34337. var segmentTime = segment.dateTimeObject.getTime() / 1000;
  34338. var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
  34339. var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, we have passed
  34340. // currentTime and can stop looking for better candidates
  34341. if (lastDistance !== null && lastDistance < distance) {
  34342. break;
  34343. }
  34344. lastDistance = distance;
  34345. syncPoint = {
  34346. time: segmentStart,
  34347. segmentIndex: i
  34348. };
  34349. }
  34350. }
  34351. return syncPoint;
  34352. }
  34353. }, // Stategy "Segment": We have a known time mapping for a timeline and a
  34354. // segment in the current timeline with timing data
  34355. {
  34356. name: 'Segment',
  34357. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34358. var segments = playlist.segments || [];
  34359. var syncPoint = null;
  34360. var lastDistance = null;
  34361. currentTime = currentTime || 0;
  34362. for (var i = 0; i < segments.length; i++) {
  34363. var segment = segments[i];
  34364. if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
  34365. var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed
  34366. // currentTime and can stop looking for better candidates
  34367. if (lastDistance !== null && lastDistance < distance) {
  34368. break;
  34369. }
  34370. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  34371. lastDistance = distance;
  34372. syncPoint = {
  34373. time: segment.start,
  34374. segmentIndex: i
  34375. };
  34376. }
  34377. }
  34378. }
  34379. return syncPoint;
  34380. }
  34381. }, // Stategy "Discontinuity": We have a discontinuity with a known
  34382. // display-time
  34383. {
  34384. name: 'Discontinuity',
  34385. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34386. var syncPoint = null;
  34387. currentTime = currentTime || 0;
  34388. if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  34389. var lastDistance = null;
  34390. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  34391. var segmentIndex = playlist.discontinuityStarts[i];
  34392. var discontinuity = playlist.discontinuitySequence + i + 1;
  34393. var discontinuitySync = syncController.discontinuities[discontinuity];
  34394. if (discontinuitySync) {
  34395. var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
  34396. // currentTime and can stop looking for better candidates
  34397. if (lastDistance !== null && lastDistance < distance) {
  34398. break;
  34399. }
  34400. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  34401. lastDistance = distance;
  34402. syncPoint = {
  34403. time: discontinuitySync.time,
  34404. segmentIndex: segmentIndex
  34405. };
  34406. }
  34407. }
  34408. }
  34409. }
  34410. return syncPoint;
  34411. }
  34412. }, // Stategy "Playlist": We have a playlist with a known mapping of
  34413. // segment index to display time
  34414. {
  34415. name: 'Playlist',
  34416. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  34417. if (playlist.syncInfo) {
  34418. var syncPoint = {
  34419. time: playlist.syncInfo.time,
  34420. segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
  34421. };
  34422. return syncPoint;
  34423. }
  34424. return null;
  34425. }
  34426. }];
  34427. var SyncController = function (_videojs$EventTarget) {
  34428. inherits(SyncController, _videojs$EventTarget);
  34429. function SyncController() {
  34430. classCallCheck(this, SyncController); // Segment Loader state variables...
  34431. // ...for synching across variants
  34432. var _this = possibleConstructorReturn(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this));
  34433. _this.inspectCache_ = undefined; // ...for synching across variants
  34434. _this.timelines = [];
  34435. _this.discontinuities = [];
  34436. _this.datetimeToDisplayTime = null;
  34437. _this.logger_ = logger('SyncController');
  34438. return _this;
  34439. }
  34440. /**
  34441. * Find a sync-point for the playlist specified
  34442. *
  34443. * A sync-point is defined as a known mapping from display-time to
  34444. * a segment-index in the current playlist.
  34445. *
  34446. * @param {Playlist} playlist
  34447. * The playlist that needs a sync-point
  34448. * @param {Number} duration
  34449. * Duration of the MediaSource (Infinite if playing a live source)
  34450. * @param {Number} currentTimeline
  34451. * The last timeline from which a segment was loaded
  34452. * @returns {Object}
  34453. * A sync-point object
  34454. */
  34455. createClass(SyncController, [{
  34456. key: 'getSyncPoint',
  34457. value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) {
  34458. var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime);
  34459. if (!syncPoints.length) {
  34460. // Signal that we need to attempt to get a sync-point manually
  34461. // by fetching a segment in the playlist and constructing
  34462. // a sync-point from that information
  34463. return null;
  34464. } // Now find the sync-point that is closest to the currentTime because
  34465. // that should result in the most accurate guess about which segment
  34466. // to fetch
  34467. return this.selectSyncPoint_(syncPoints, {
  34468. key: 'time',
  34469. value: currentTime
  34470. });
  34471. }
  34472. /**
  34473. * Calculate the amount of time that has expired off the playlist during playback
  34474. *
  34475. * @param {Playlist} playlist
  34476. * Playlist object to calculate expired from
  34477. * @param {Number} duration
  34478. * Duration of the MediaSource (Infinity if playling a live source)
  34479. * @returns {Number|null}
  34480. * The amount of time that has expired off the playlist during playback. Null
  34481. * if no sync-points for the playlist can be found.
  34482. */
  34483. }, {
  34484. key: 'getExpiredTime',
  34485. value: function getExpiredTime(playlist, duration$$1) {
  34486. if (!playlist || !playlist.segments) {
  34487. return null;
  34488. }
  34489. var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
  34490. if (!syncPoints.length) {
  34491. return null;
  34492. }
  34493. var syncPoint = this.selectSyncPoint_(syncPoints, {
  34494. key: 'segmentIndex',
  34495. value: 0
  34496. }); // If the sync-point is beyond the start of the playlist, we want to subtract the
  34497. // duration from index 0 to syncPoint.segmentIndex instead of adding.
  34498. if (syncPoint.segmentIndex > 0) {
  34499. syncPoint.time *= -1;
  34500. }
  34501. return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
  34502. }
  34503. /**
  34504. * Runs each sync-point strategy and returns a list of sync-points returned by the
  34505. * strategies
  34506. *
  34507. * @private
  34508. * @param {Playlist} playlist
  34509. * The playlist that needs a sync-point
  34510. * @param {Number} duration
  34511. * Duration of the MediaSource (Infinity if playing a live source)
  34512. * @param {Number} currentTimeline
  34513. * The last timeline from which a segment was loaded
  34514. * @returns {Array}
  34515. * A list of sync-point objects
  34516. */
  34517. }, {
  34518. key: 'runStrategies_',
  34519. value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) {
  34520. var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
  34521. for (var i = 0; i < syncPointStrategies.length; i++) {
  34522. var strategy = syncPointStrategies[i];
  34523. var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime);
  34524. if (syncPoint) {
  34525. syncPoint.strategy = strategy.name;
  34526. syncPoints.push({
  34527. strategy: strategy.name,
  34528. syncPoint: syncPoint
  34529. });
  34530. }
  34531. }
  34532. return syncPoints;
  34533. }
  34534. /**
  34535. * Selects the sync-point nearest the specified target
  34536. *
  34537. * @private
  34538. * @param {Array} syncPoints
  34539. * List of sync-points to select from
  34540. * @param {Object} target
  34541. * Object specifying the property and value we are targeting
  34542. * @param {String} target.key
  34543. * Specifies the property to target. Must be either 'time' or 'segmentIndex'
  34544. * @param {Number} target.value
  34545. * The value to target for the specified key.
  34546. * @returns {Object}
  34547. * The sync-point nearest the target
  34548. */
  34549. }, {
  34550. key: 'selectSyncPoint_',
  34551. value: function selectSyncPoint_(syncPoints, target) {
  34552. var bestSyncPoint = syncPoints[0].syncPoint;
  34553. var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
  34554. var bestStrategy = syncPoints[0].strategy;
  34555. for (var i = 1; i < syncPoints.length; i++) {
  34556. var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
  34557. if (newDistance < bestDistance) {
  34558. bestDistance = newDistance;
  34559. bestSyncPoint = syncPoints[i].syncPoint;
  34560. bestStrategy = syncPoints[i].strategy;
  34561. }
  34562. }
  34563. this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']'));
  34564. return bestSyncPoint;
  34565. }
  34566. /**
  34567. * Save any meta-data present on the segments when segments leave
  34568. * the live window to the playlist to allow for synchronization at the
  34569. * playlist level later.
  34570. *
  34571. * @param {Playlist} oldPlaylist - The previous active playlist
  34572. * @param {Playlist} newPlaylist - The updated and most current playlist
  34573. */
  34574. }, {
  34575. key: 'saveExpiredSegmentInfo',
  34576. value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
  34577. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time
  34578. // save that information as a possible sync-point reference in future
  34579. for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
  34580. var lastRemovedSegment = oldPlaylist.segments[i];
  34581. if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
  34582. newPlaylist.syncInfo = {
  34583. mediaSequence: oldPlaylist.mediaSequence + i,
  34584. time: lastRemovedSegment.start
  34585. };
  34586. this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']'));
  34587. this.trigger('syncinfoupdate');
  34588. break;
  34589. }
  34590. }
  34591. }
  34592. /**
  34593. * Save the mapping from playlist's ProgramDateTime to display. This should
  34594. * only ever happen once at the start of playback.
  34595. *
  34596. * @param {Playlist} playlist - The currently active playlist
  34597. */
  34598. }, {
  34599. key: 'setDateTimeMapping',
  34600. value: function setDateTimeMapping(playlist) {
  34601. if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
  34602. var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
  34603. this.datetimeToDisplayTime = -playlistTimestamp;
  34604. }
  34605. }
  34606. /**
  34607. * Reset the state of the inspection cache when we do a rendition
  34608. * switch
  34609. */
  34610. }, {
  34611. key: 'reset',
  34612. value: function reset() {
  34613. this.inspectCache_ = undefined;
  34614. }
  34615. /**
  34616. * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
  34617. * and end of the segment in it's internal "media time". Used to generate
  34618. * mappings from that internal "media time" to the display time that is
  34619. * shown on the player.
  34620. *
  34621. * @param {SegmentInfo} segmentInfo - The current active request information
  34622. */
  34623. }, {
  34624. key: 'probeSegmentInfo',
  34625. value: function probeSegmentInfo(segmentInfo) {
  34626. var segment = segmentInfo.segment;
  34627. var playlist = segmentInfo.playlist;
  34628. var timingInfo = void 0;
  34629. if (segment.map) {
  34630. timingInfo = this.probeMp4Segment_(segmentInfo);
  34631. } else {
  34632. timingInfo = this.probeTsSegment_(segmentInfo);
  34633. }
  34634. if (timingInfo) {
  34635. if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
  34636. this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
  34637. // now with segment timing information
  34638. if (!playlist.syncInfo) {
  34639. playlist.syncInfo = {
  34640. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  34641. time: segment.start
  34642. };
  34643. }
  34644. }
  34645. }
  34646. return timingInfo;
  34647. }
  34648. /**
  34649. * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment
  34650. * in it's internal "media time".
  34651. *
  34652. * @private
  34653. * @param {SegmentInfo} segmentInfo - The current active request information
  34654. * @return {object} The start and end time of the current segment in "media time"
  34655. */
  34656. }, {
  34657. key: 'probeMp4Segment_',
  34658. value: function probeMp4Segment_(segmentInfo) {
  34659. var segment = segmentInfo.segment;
  34660. var timescales = mp4probe.timescale(segment.map.bytes);
  34661. var startTime = mp4probe.startTime(timescales, segmentInfo.bytes);
  34662. if (segmentInfo.timestampOffset !== null) {
  34663. segmentInfo.timestampOffset -= startTime;
  34664. }
  34665. return {
  34666. start: startTime,
  34667. end: startTime + segment.duration
  34668. };
  34669. }
  34670. /**
  34671. * Probe an mpeg2-ts segment to determine the start and end of the segment
  34672. * in it's internal "media time".
  34673. *
  34674. * @private
  34675. * @param {SegmentInfo} segmentInfo - The current active request information
  34676. * @return {object} The start and end time of the current segment in "media time"
  34677. */
  34678. }, {
  34679. key: 'probeTsSegment_',
  34680. value: function probeTsSegment_(segmentInfo) {
  34681. var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_);
  34682. var segmentStartTime = void 0;
  34683. var segmentEndTime = void 0;
  34684. if (!timeInfo) {
  34685. return null;
  34686. }
  34687. if (timeInfo.video && timeInfo.video.length === 2) {
  34688. this.inspectCache_ = timeInfo.video[1].dts;
  34689. segmentStartTime = timeInfo.video[0].dtsTime;
  34690. segmentEndTime = timeInfo.video[1].dtsTime;
  34691. } else if (timeInfo.audio && timeInfo.audio.length === 2) {
  34692. this.inspectCache_ = timeInfo.audio[1].dts;
  34693. segmentStartTime = timeInfo.audio[0].dtsTime;
  34694. segmentEndTime = timeInfo.audio[1].dtsTime;
  34695. }
  34696. var probedInfo = {
  34697. start: segmentStartTime,
  34698. end: segmentEndTime,
  34699. containsVideo: timeInfo.video && timeInfo.video.length === 2,
  34700. containsAudio: timeInfo.audio && timeInfo.audio.length === 2
  34701. };
  34702. return probedInfo;
  34703. }
  34704. }, {
  34705. key: 'timestampOffsetForTimeline',
  34706. value: function timestampOffsetForTimeline(timeline) {
  34707. if (typeof this.timelines[timeline] === 'undefined') {
  34708. return null;
  34709. }
  34710. return this.timelines[timeline].time;
  34711. }
  34712. }, {
  34713. key: 'mappingForTimeline',
  34714. value: function mappingForTimeline(timeline) {
  34715. if (typeof this.timelines[timeline] === 'undefined') {
  34716. return null;
  34717. }
  34718. return this.timelines[timeline].mapping;
  34719. }
  34720. /**
  34721. * Use the "media time" for a segment to generate a mapping to "display time" and
  34722. * save that display time to the segment.
  34723. *
  34724. * @private
  34725. * @param {SegmentInfo} segmentInfo
  34726. * The current active request information
  34727. * @param {object} timingInfo
  34728. * The start and end time of the current segment in "media time"
  34729. * @returns {Boolean}
  34730. * Returns false if segment time mapping could not be calculated
  34731. */
  34732. }, {
  34733. key: 'calculateSegmentTimeMapping_',
  34734. value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
  34735. var segment = segmentInfo.segment;
  34736. var mappingObj = this.timelines[segmentInfo.timeline];
  34737. if (segmentInfo.timestampOffset !== null) {
  34738. mappingObj = {
  34739. time: segmentInfo.startOfSegment,
  34740. mapping: segmentInfo.startOfSegment - timingInfo.start
  34741. };
  34742. this.timelines[segmentInfo.timeline] = mappingObj;
  34743. this.trigger('timestampoffset');
  34744. this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']'));
  34745. segment.start = segmentInfo.startOfSegment;
  34746. segment.end = timingInfo.end + mappingObj.mapping;
  34747. } else if (mappingObj) {
  34748. segment.start = timingInfo.start + mappingObj.mapping;
  34749. segment.end = timingInfo.end + mappingObj.mapping;
  34750. } else {
  34751. return false;
  34752. }
  34753. return true;
  34754. }
  34755. /**
  34756. * Each time we have discontinuity in the playlist, attempt to calculate the location
  34757. * in display of the start of the discontinuity and save that. We also save an accuracy
  34758. * value so that we save values with the most accuracy (closest to 0.)
  34759. *
  34760. * @private
  34761. * @param {SegmentInfo} segmentInfo - The current active request information
  34762. */
  34763. }, {
  34764. key: 'saveDiscontinuitySyncInfo_',
  34765. value: function saveDiscontinuitySyncInfo_(segmentInfo) {
  34766. var playlist = segmentInfo.playlist;
  34767. var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
  34768. // the start of the range and it's accuracy is 0 (greater accuracy values
  34769. // mean more approximation)
  34770. if (segment.discontinuity) {
  34771. this.discontinuities[segment.timeline] = {
  34772. time: segment.start,
  34773. accuracy: 0
  34774. };
  34775. } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  34776. // Search for future discontinuities that we can provide better timing
  34777. // information for and save that information for sync purposes
  34778. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  34779. var segmentIndex = playlist.discontinuityStarts[i];
  34780. var discontinuity = playlist.discontinuitySequence + i + 1;
  34781. var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
  34782. var accuracy = Math.abs(mediaIndexDiff);
  34783. if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
  34784. var time = void 0;
  34785. if (mediaIndexDiff < 0) {
  34786. time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
  34787. } else {
  34788. time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
  34789. }
  34790. this.discontinuities[discontinuity] = {
  34791. time: time,
  34792. accuracy: accuracy
  34793. };
  34794. }
  34795. }
  34796. }
  34797. }
  34798. }]);
  34799. return SyncController;
  34800. }(videojs$1.EventTarget);
  34801. var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) {
  34802. var self = this;
  34803. var decrypterWorker = function () {
  34804. /*
  34805. * pkcs7.pad
  34806. * https://github.com/brightcove/pkcs7
  34807. *
  34808. * Copyright (c) 2014 Brightcove
  34809. * Licensed under the apache2 license.
  34810. */
  34811. /**
  34812. * Returns the subarray of a Uint8Array without PKCS#7 padding.
  34813. * @param padded {Uint8Array} unencrypted bytes that have been padded
  34814. * @return {Uint8Array} the unpadded bytes
  34815. * @see http://tools.ietf.org/html/rfc5652
  34816. */
  34817. function unpad(padded) {
  34818. return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
  34819. }
  34820. var classCallCheck = function classCallCheck(instance, Constructor) {
  34821. if (!(instance instanceof Constructor)) {
  34822. throw new TypeError("Cannot call a class as a function");
  34823. }
  34824. };
  34825. var createClass = function () {
  34826. function defineProperties(target, props) {
  34827. for (var i = 0; i < props.length; i++) {
  34828. var descriptor = props[i];
  34829. descriptor.enumerable = descriptor.enumerable || false;
  34830. descriptor.configurable = true;
  34831. if ("value" in descriptor) descriptor.writable = true;
  34832. Object.defineProperty(target, descriptor.key, descriptor);
  34833. }
  34834. }
  34835. return function (Constructor, protoProps, staticProps) {
  34836. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  34837. if (staticProps) defineProperties(Constructor, staticProps);
  34838. return Constructor;
  34839. };
  34840. }();
  34841. var inherits = function inherits(subClass, superClass) {
  34842. if (typeof superClass !== "function" && superClass !== null) {
  34843. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  34844. }
  34845. subClass.prototype = Object.create(superClass && superClass.prototype, {
  34846. constructor: {
  34847. value: subClass,
  34848. enumerable: false,
  34849. writable: true,
  34850. configurable: true
  34851. }
  34852. });
  34853. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  34854. };
  34855. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  34856. if (!self) {
  34857. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  34858. }
  34859. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  34860. };
  34861. /**
  34862. * @file aes.js
  34863. *
  34864. * This file contains an adaptation of the AES decryption algorithm
  34865. * from the Standford Javascript Cryptography Library. That work is
  34866. * covered by the following copyright and permissions notice:
  34867. *
  34868. * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
  34869. * All rights reserved.
  34870. *
  34871. * Redistribution and use in source and binary forms, with or without
  34872. * modification, are permitted provided that the following conditions are
  34873. * met:
  34874. *
  34875. * 1. Redistributions of source code must retain the above copyright
  34876. * notice, this list of conditions and the following disclaimer.
  34877. *
  34878. * 2. Redistributions in binary form must reproduce the above
  34879. * copyright notice, this list of conditions and the following
  34880. * disclaimer in the documentation and/or other materials provided
  34881. * with the distribution.
  34882. *
  34883. * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
  34884. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  34885. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  34886. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
  34887. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  34888. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  34889. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  34890. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  34891. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  34892. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  34893. * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  34894. *
  34895. * The views and conclusions contained in the software and documentation
  34896. * are those of the authors and should not be interpreted as representing
  34897. * official policies, either expressed or implied, of the authors.
  34898. */
  34899. /**
  34900. * Expand the S-box tables.
  34901. *
  34902. * @private
  34903. */
  34904. var precompute = function precompute() {
  34905. var tables = [[[], [], [], [], []], [[], [], [], [], []]];
  34906. var encTable = tables[0];
  34907. var decTable = tables[1];
  34908. var sbox = encTable[4];
  34909. var sboxInv = decTable[4];
  34910. var i = void 0;
  34911. var x = void 0;
  34912. var xInv = void 0;
  34913. var d = [];
  34914. var th = [];
  34915. var x2 = void 0;
  34916. var x4 = void 0;
  34917. var x8 = void 0;
  34918. var s = void 0;
  34919. var tEnc = void 0;
  34920. var tDec = void 0; // Compute double and third tables
  34921. for (i = 0; i < 256; i++) {
  34922. th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
  34923. }
  34924. for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
  34925. // Compute sbox
  34926. s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
  34927. s = s >> 8 ^ s & 255 ^ 99;
  34928. sbox[x] = s;
  34929. sboxInv[s] = x; // Compute MixColumns
  34930. x8 = d[x4 = d[x2 = d[x]]];
  34931. tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
  34932. tEnc = d[s] * 0x101 ^ s * 0x1010100;
  34933. for (i = 0; i < 4; i++) {
  34934. encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
  34935. decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
  34936. }
  34937. } // Compactify. Considerable speedup on Firefox.
  34938. for (i = 0; i < 5; i++) {
  34939. encTable[i] = encTable[i].slice(0);
  34940. decTable[i] = decTable[i].slice(0);
  34941. }
  34942. return tables;
  34943. };
  34944. var aesTables = null;
  34945. /**
  34946. * Schedule out an AES key for both encryption and decryption. This
  34947. * is a low-level class. Use a cipher mode to do bulk encryption.
  34948. *
  34949. * @class AES
  34950. * @param key {Array} The key as an array of 4, 6 or 8 words.
  34951. */
  34952. var AES = function () {
  34953. function AES(key) {
  34954. classCallCheck(this, AES);
  34955. /**
  34956. * The expanded S-box and inverse S-box tables. These will be computed
  34957. * on the client so that we don't have to send them down the wire.
  34958. *
  34959. * There are two tables, _tables[0] is for encryption and
  34960. * _tables[1] is for decryption.
  34961. *
  34962. * The first 4 sub-tables are the expanded S-box with MixColumns. The
  34963. * last (_tables[01][4]) is the S-box itself.
  34964. *
  34965. * @private
  34966. */
  34967. // if we have yet to precompute the S-box tables
  34968. // do so now
  34969. if (!aesTables) {
  34970. aesTables = precompute();
  34971. } // then make a copy of that object for use
  34972. this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]];
  34973. var i = void 0;
  34974. var j = void 0;
  34975. var tmp = void 0;
  34976. var encKey = void 0;
  34977. var decKey = void 0;
  34978. var sbox = this._tables[0][4];
  34979. var decTable = this._tables[1];
  34980. var keyLen = key.length;
  34981. var rcon = 1;
  34982. if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
  34983. throw new Error('Invalid aes key size');
  34984. }
  34985. encKey = key.slice(0);
  34986. decKey = [];
  34987. this._key = [encKey, decKey]; // schedule encryption keys
  34988. for (i = keyLen; i < 4 * keyLen + 28; i++) {
  34989. tmp = encKey[i - 1]; // apply sbox
  34990. if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
  34991. tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
  34992. if (i % keyLen === 0) {
  34993. tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
  34994. rcon = rcon << 1 ^ (rcon >> 7) * 283;
  34995. }
  34996. }
  34997. encKey[i] = encKey[i - keyLen] ^ tmp;
  34998. } // schedule decryption keys
  34999. for (j = 0; i; j++, i--) {
  35000. tmp = encKey[j & 3 ? i : i - 4];
  35001. if (i <= 4 || j < 4) {
  35002. decKey[j] = tmp;
  35003. } else {
  35004. decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
  35005. }
  35006. }
  35007. }
  35008. /**
  35009. * Decrypt 16 bytes, specified as four 32-bit words.
  35010. *
  35011. * @param {Number} encrypted0 the first word to decrypt
  35012. * @param {Number} encrypted1 the second word to decrypt
  35013. * @param {Number} encrypted2 the third word to decrypt
  35014. * @param {Number} encrypted3 the fourth word to decrypt
  35015. * @param {Int32Array} out the array to write the decrypted words
  35016. * into
  35017. * @param {Number} offset the offset into the output array to start
  35018. * writing results
  35019. * @return {Array} The plaintext.
  35020. */
  35021. AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
  35022. var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
  35023. var a = encrypted0 ^ key[0];
  35024. var b = encrypted3 ^ key[1];
  35025. var c = encrypted2 ^ key[2];
  35026. var d = encrypted1 ^ key[3];
  35027. var a2 = void 0;
  35028. var b2 = void 0;
  35029. var c2 = void 0; // key.length === 2 ?
  35030. var nInnerRounds = key.length / 4 - 2;
  35031. var i = void 0;
  35032. var kIndex = 4;
  35033. var table = this._tables[1]; // load up the tables
  35034. var table0 = table[0];
  35035. var table1 = table[1];
  35036. var table2 = table[2];
  35037. var table3 = table[3];
  35038. var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
  35039. for (i = 0; i < nInnerRounds; i++) {
  35040. a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
  35041. b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
  35042. c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
  35043. d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
  35044. kIndex += 4;
  35045. a = a2;
  35046. b = b2;
  35047. c = c2;
  35048. } // Last round.
  35049. for (i = 0; i < 4; i++) {
  35050. out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
  35051. a2 = a;
  35052. a = b;
  35053. b = c;
  35054. c = d;
  35055. d = a2;
  35056. }
  35057. };
  35058. return AES;
  35059. }();
  35060. /**
  35061. * @file stream.js
  35062. */
  35063. /**
  35064. * A lightweight readable stream implemention that handles event dispatching.
  35065. *
  35066. * @class Stream
  35067. */
  35068. var Stream = function () {
  35069. function Stream() {
  35070. classCallCheck(this, Stream);
  35071. this.listeners = {};
  35072. }
  35073. /**
  35074. * Add a listener for a specified event type.
  35075. *
  35076. * @param {String} type the event name
  35077. * @param {Function} listener the callback to be invoked when an event of
  35078. * the specified type occurs
  35079. */
  35080. Stream.prototype.on = function on(type, listener) {
  35081. if (!this.listeners[type]) {
  35082. this.listeners[type] = [];
  35083. }
  35084. this.listeners[type].push(listener);
  35085. };
  35086. /**
  35087. * Remove a listener for a specified event type.
  35088. *
  35089. * @param {String} type the event name
  35090. * @param {Function} listener a function previously registered for this
  35091. * type of event through `on`
  35092. * @return {Boolean} if we could turn it off or not
  35093. */
  35094. Stream.prototype.off = function off(type, listener) {
  35095. if (!this.listeners[type]) {
  35096. return false;
  35097. }
  35098. var index = this.listeners[type].indexOf(listener);
  35099. this.listeners[type].splice(index, 1);
  35100. return index > -1;
  35101. };
  35102. /**
  35103. * Trigger an event of the specified type on this stream. Any additional
  35104. * arguments to this function are passed as parameters to event listeners.
  35105. *
  35106. * @param {String} type the event name
  35107. */
  35108. Stream.prototype.trigger = function trigger(type) {
  35109. var callbacks = this.listeners[type];
  35110. if (!callbacks) {
  35111. return;
  35112. } // Slicing the arguments on every invocation of this method
  35113. // can add a significant amount of overhead. Avoid the
  35114. // intermediate object creation for the common case of a
  35115. // single callback argument
  35116. if (arguments.length === 2) {
  35117. var length = callbacks.length;
  35118. for (var i = 0; i < length; ++i) {
  35119. callbacks[i].call(this, arguments[1]);
  35120. }
  35121. } else {
  35122. var args = Array.prototype.slice.call(arguments, 1);
  35123. var _length = callbacks.length;
  35124. for (var _i = 0; _i < _length; ++_i) {
  35125. callbacks[_i].apply(this, args);
  35126. }
  35127. }
  35128. };
  35129. /**
  35130. * Destroys the stream and cleans up.
  35131. */
  35132. Stream.prototype.dispose = function dispose() {
  35133. this.listeners = {};
  35134. };
  35135. /**
  35136. * Forwards all `data` events on this stream to the destination stream. The
  35137. * destination stream should provide a method `push` to receive the data
  35138. * events as they arrive.
  35139. *
  35140. * @param {Stream} destination the stream that will receive all `data` events
  35141. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  35142. */
  35143. Stream.prototype.pipe = function pipe(destination) {
  35144. this.on('data', function (data) {
  35145. destination.push(data);
  35146. });
  35147. };
  35148. return Stream;
  35149. }();
  35150. /**
  35151. * @file async-stream.js
  35152. */
  35153. /**
  35154. * A wrapper around the Stream class to use setTiemout
  35155. * and run stream "jobs" Asynchronously
  35156. *
  35157. * @class AsyncStream
  35158. * @extends Stream
  35159. */
  35160. var AsyncStream$$1 = function (_Stream) {
  35161. inherits(AsyncStream$$1, _Stream);
  35162. function AsyncStream$$1() {
  35163. classCallCheck(this, AsyncStream$$1);
  35164. var _this = possibleConstructorReturn(this, _Stream.call(this, Stream));
  35165. _this.jobs = [];
  35166. _this.delay = 1;
  35167. _this.timeout_ = null;
  35168. return _this;
  35169. }
  35170. /**
  35171. * process an async job
  35172. *
  35173. * @private
  35174. */
  35175. AsyncStream$$1.prototype.processJob_ = function processJob_() {
  35176. this.jobs.shift()();
  35177. if (this.jobs.length) {
  35178. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  35179. } else {
  35180. this.timeout_ = null;
  35181. }
  35182. };
  35183. /**
  35184. * push a job into the stream
  35185. *
  35186. * @param {Function} job the job to push into the stream
  35187. */
  35188. AsyncStream$$1.prototype.push = function push(job) {
  35189. this.jobs.push(job);
  35190. if (!this.timeout_) {
  35191. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  35192. }
  35193. };
  35194. return AsyncStream$$1;
  35195. }(Stream);
  35196. /**
  35197. * @file decrypter.js
  35198. *
  35199. * An asynchronous implementation of AES-128 CBC decryption with
  35200. * PKCS#7 padding.
  35201. */
  35202. /**
  35203. * Convert network-order (big-endian) bytes into their little-endian
  35204. * representation.
  35205. */
  35206. var ntoh = function ntoh(word) {
  35207. return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
  35208. };
  35209. /**
  35210. * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
  35211. *
  35212. * @param {Uint8Array} encrypted the encrypted bytes
  35213. * @param {Uint32Array} key the bytes of the decryption key
  35214. * @param {Uint32Array} initVector the initialization vector (IV) to
  35215. * use for the first round of CBC.
  35216. * @return {Uint8Array} the decrypted bytes
  35217. *
  35218. * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  35219. * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
  35220. * @see https://tools.ietf.org/html/rfc2315
  35221. */
  35222. var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) {
  35223. // word-level access to the encrypted bytes
  35224. var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
  35225. var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
  35226. var decrypted = new Uint8Array(encrypted.byteLength);
  35227. var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
  35228. // decrypted data
  35229. var init0 = void 0;
  35230. var init1 = void 0;
  35231. var init2 = void 0;
  35232. var init3 = void 0;
  35233. var encrypted0 = void 0;
  35234. var encrypted1 = void 0;
  35235. var encrypted2 = void 0;
  35236. var encrypted3 = void 0; // iteration variable
  35237. var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
  35238. // passed-in reference and easier access
  35239. init0 = initVector[0];
  35240. init1 = initVector[1];
  35241. init2 = initVector[2];
  35242. init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
  35243. // to each decrypted block
  35244. for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
  35245. // convert big-endian (network order) words into little-endian
  35246. // (javascript order)
  35247. encrypted0 = ntoh(encrypted32[wordIx]);
  35248. encrypted1 = ntoh(encrypted32[wordIx + 1]);
  35249. encrypted2 = ntoh(encrypted32[wordIx + 2]);
  35250. encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
  35251. decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
  35252. // plaintext
  35253. decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
  35254. decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
  35255. decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
  35256. decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
  35257. init0 = encrypted0;
  35258. init1 = encrypted1;
  35259. init2 = encrypted2;
  35260. init3 = encrypted3;
  35261. }
  35262. return decrypted;
  35263. };
  35264. /**
  35265. * The `Decrypter` class that manages decryption of AES
  35266. * data through `AsyncStream` objects and the `decrypt`
  35267. * function
  35268. *
  35269. * @param {Uint8Array} encrypted the encrypted bytes
  35270. * @param {Uint32Array} key the bytes of the decryption key
  35271. * @param {Uint32Array} initVector the initialization vector (IV) to
  35272. * @param {Function} done the function to run when done
  35273. * @class Decrypter
  35274. */
  35275. var Decrypter$$1 = function () {
  35276. function Decrypter$$1(encrypted, key, initVector, done) {
  35277. classCallCheck(this, Decrypter$$1);
  35278. var step = Decrypter$$1.STEP;
  35279. var encrypted32 = new Int32Array(encrypted.buffer);
  35280. var decrypted = new Uint8Array(encrypted.byteLength);
  35281. var i = 0;
  35282. this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously
  35283. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  35284. for (i = step; i < encrypted32.length; i += step) {
  35285. initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
  35286. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  35287. } // invoke the done() callback when everything is finished
  35288. this.asyncStream_.push(function () {
  35289. // remove pkcs#7 padding from the decrypted bytes
  35290. done(null, unpad(decrypted));
  35291. });
  35292. }
  35293. /**
  35294. * a getter for step the maximum number of bytes to process at one time
  35295. *
  35296. * @return {Number} the value of step 32000
  35297. */
  35298. /**
  35299. * @private
  35300. */
  35301. Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
  35302. return function () {
  35303. var bytes = decrypt$$1(encrypted, key, initVector);
  35304. decrypted.set(bytes, encrypted.byteOffset);
  35305. };
  35306. };
  35307. createClass(Decrypter$$1, null, [{
  35308. key: 'STEP',
  35309. get: function get$$1() {
  35310. // 4 * 8000;
  35311. return 32000;
  35312. }
  35313. }]);
  35314. return Decrypter$$1;
  35315. }();
  35316. /**
  35317. * @file bin-utils.js
  35318. */
  35319. /**
  35320. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  35321. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  35322. *
  35323. * @param {Object} message
  35324. * Object of properties and values to send to the web worker
  35325. * @return {Object}
  35326. * Modified message with TypedArray values expanded
  35327. * @function createTransferableMessage
  35328. */
  35329. var createTransferableMessage = function createTransferableMessage(message) {
  35330. var transferable = {};
  35331. Object.keys(message).forEach(function (key) {
  35332. var value = message[key];
  35333. if (ArrayBuffer.isView(value)) {
  35334. transferable[key] = {
  35335. bytes: value.buffer,
  35336. byteOffset: value.byteOffset,
  35337. byteLength: value.byteLength
  35338. };
  35339. } else {
  35340. transferable[key] = value;
  35341. }
  35342. });
  35343. return transferable;
  35344. };
  35345. /**
  35346. * Our web worker interface so that things can talk to aes-decrypter
  35347. * that will be running in a web worker. the scope is passed to this by
  35348. * webworkify.
  35349. *
  35350. * @param {Object} self
  35351. * the scope for the web worker
  35352. */
  35353. var DecrypterWorker = function DecrypterWorker(self) {
  35354. self.onmessage = function (event) {
  35355. var data = event.data;
  35356. var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
  35357. var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
  35358. var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
  35359. /* eslint-disable no-new, handle-callback-err */
  35360. new Decrypter$$1(encrypted, key, iv, function (err, bytes) {
  35361. self.postMessage(createTransferableMessage({
  35362. source: data.source,
  35363. decrypted: bytes
  35364. }), [bytes.buffer]);
  35365. });
  35366. /* eslint-enable */
  35367. };
  35368. };
  35369. var decrypterWorker = new DecrypterWorker(self);
  35370. return decrypterWorker;
  35371. }();
  35372. });
  35373. /**
  35374. * Convert the properties of an HLS track into an audioTrackKind.
  35375. *
  35376. * @private
  35377. */
  35378. var audioTrackKind_ = function audioTrackKind_(properties) {
  35379. var kind = properties.default ? 'main' : 'alternative';
  35380. if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
  35381. kind = 'main-desc';
  35382. }
  35383. return kind;
  35384. };
  35385. /**
  35386. * Pause provided segment loader and playlist loader if active
  35387. *
  35388. * @param {SegmentLoader} segmentLoader
  35389. * SegmentLoader to pause
  35390. * @param {Object} mediaType
  35391. * Active media type
  35392. * @function stopLoaders
  35393. */
  35394. var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
  35395. segmentLoader.abort();
  35396. segmentLoader.pause();
  35397. if (mediaType && mediaType.activePlaylistLoader) {
  35398. mediaType.activePlaylistLoader.pause();
  35399. mediaType.activePlaylistLoader = null;
  35400. }
  35401. };
  35402. /**
  35403. * Start loading provided segment loader and playlist loader
  35404. *
  35405. * @param {PlaylistLoader} playlistLoader
  35406. * PlaylistLoader to start loading
  35407. * @param {Object} mediaType
  35408. * Active media type
  35409. * @function startLoaders
  35410. */
  35411. var startLoaders = function startLoaders(playlistLoader, mediaType) {
  35412. // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
  35413. // playlist loader
  35414. mediaType.activePlaylistLoader = playlistLoader;
  35415. playlistLoader.load();
  35416. };
  35417. /**
  35418. * Returns a function to be called when the media group changes. It performs a
  35419. * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
  35420. * change of group is merely a rendition switch of the same content at another encoding,
  35421. * rather than a change of content, such as switching audio from English to Spanish.
  35422. *
  35423. * @param {String} type
  35424. * MediaGroup type
  35425. * @param {Object} settings
  35426. * Object containing required information for media groups
  35427. * @return {Function}
  35428. * Handler for a non-destructive resync of SegmentLoader when the active media
  35429. * group changes.
  35430. * @function onGroupChanged
  35431. */
  35432. var onGroupChanged = function onGroupChanged(type, settings) {
  35433. return function () {
  35434. var _settings$segmentLoad = settings.segmentLoaders,
  35435. segmentLoader = _settings$segmentLoad[type],
  35436. mainSegmentLoader = _settings$segmentLoad.main,
  35437. mediaType = settings.mediaTypes[type];
  35438. var activeTrack = mediaType.activeTrack();
  35439. var activeGroup = mediaType.activeGroup(activeTrack);
  35440. var previousActiveLoader = mediaType.activePlaylistLoader;
  35441. stopLoaders(segmentLoader, mediaType);
  35442. if (!activeGroup) {
  35443. // there is no group active
  35444. return;
  35445. }
  35446. if (!activeGroup.playlistLoader) {
  35447. if (previousActiveLoader) {
  35448. // The previous group had a playlist loader but the new active group does not
  35449. // this means we are switching from demuxed to muxed audio. In this case we want to
  35450. // do a destructive reset of the main segment loader and not restart the audio
  35451. // loaders.
  35452. mainSegmentLoader.resetEverything();
  35453. }
  35454. return;
  35455. } // Non-destructive resync
  35456. segmentLoader.resyncLoader();
  35457. startLoaders(activeGroup.playlistLoader, mediaType);
  35458. };
  35459. };
  35460. /**
  35461. * Returns a function to be called when the media track changes. It performs a
  35462. * destructive reset of the SegmentLoader to ensure we start loading as close to
  35463. * currentTime as possible.
  35464. *
  35465. * @param {String} type
  35466. * MediaGroup type
  35467. * @param {Object} settings
  35468. * Object containing required information for media groups
  35469. * @return {Function}
  35470. * Handler for a destructive reset of SegmentLoader when the active media
  35471. * track changes.
  35472. * @function onTrackChanged
  35473. */
  35474. var onTrackChanged = function onTrackChanged(type, settings) {
  35475. return function () {
  35476. var _settings$segmentLoad2 = settings.segmentLoaders,
  35477. segmentLoader = _settings$segmentLoad2[type],
  35478. mainSegmentLoader = _settings$segmentLoad2.main,
  35479. mediaType = settings.mediaTypes[type];
  35480. var activeTrack = mediaType.activeTrack();
  35481. var activeGroup = mediaType.activeGroup(activeTrack);
  35482. var previousActiveLoader = mediaType.activePlaylistLoader;
  35483. stopLoaders(segmentLoader, mediaType);
  35484. if (!activeGroup) {
  35485. // there is no group active so we do not want to restart loaders
  35486. return;
  35487. }
  35488. if (!activeGroup.playlistLoader) {
  35489. // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
  35490. // loader for the audio group), we want to do a destructive reset of the main segment
  35491. // loader and not restart the audio loaders
  35492. mainSegmentLoader.resetEverything();
  35493. return;
  35494. }
  35495. if (previousActiveLoader === activeGroup.playlistLoader) {
  35496. // Nothing has actually changed. This can happen because track change events can fire
  35497. // multiple times for a "single" change. One for enabling the new active track, and
  35498. // one for disabling the track that was active
  35499. startLoaders(activeGroup.playlistLoader, mediaType);
  35500. return;
  35501. }
  35502. if (segmentLoader.track) {
  35503. // For WebVTT, set the new text track in the segmentloader
  35504. segmentLoader.track(activeTrack);
  35505. } // destructive reset
  35506. segmentLoader.resetEverything();
  35507. startLoaders(activeGroup.playlistLoader, mediaType);
  35508. };
  35509. };
  35510. var onError = {
  35511. /**
  35512. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  35513. * an error.
  35514. *
  35515. * @param {String} type
  35516. * MediaGroup type
  35517. * @param {Object} settings
  35518. * Object containing required information for media groups
  35519. * @return {Function}
  35520. * Error handler. Logs warning (or error if the playlist is blacklisted) to
  35521. * console and switches back to default audio track.
  35522. * @function onError.AUDIO
  35523. */
  35524. AUDIO: function AUDIO(type, settings) {
  35525. return function () {
  35526. var segmentLoader = settings.segmentLoaders[type],
  35527. mediaType = settings.mediaTypes[type],
  35528. blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
  35529. stopLoaders(segmentLoader, mediaType); // switch back to default audio track
  35530. var activeTrack = mediaType.activeTrack();
  35531. var activeGroup = mediaType.activeGroup();
  35532. var id = (activeGroup.filter(function (group) {
  35533. return group.default;
  35534. })[0] || activeGroup[0]).id;
  35535. var defaultTrack = mediaType.tracks[id];
  35536. if (activeTrack === defaultTrack) {
  35537. // Default track encountered an error. All we can do now is blacklist the current
  35538. // rendition and hope another will switch audio groups
  35539. blacklistCurrentPlaylist({
  35540. message: 'Problem encountered loading the default audio track.'
  35541. });
  35542. return;
  35543. }
  35544. videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
  35545. for (var trackId in mediaType.tracks) {
  35546. mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
  35547. }
  35548. mediaType.onTrackChanged();
  35549. };
  35550. },
  35551. /**
  35552. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  35553. * an error.
  35554. *
  35555. * @param {String} type
  35556. * MediaGroup type
  35557. * @param {Object} settings
  35558. * Object containing required information for media groups
  35559. * @return {Function}
  35560. * Error handler. Logs warning to console and disables the active subtitle track
  35561. * @function onError.SUBTITLES
  35562. */
  35563. SUBTITLES: function SUBTITLES(type, settings) {
  35564. return function () {
  35565. var segmentLoader = settings.segmentLoaders[type],
  35566. mediaType = settings.mediaTypes[type];
  35567. videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
  35568. stopLoaders(segmentLoader, mediaType);
  35569. var track = mediaType.activeTrack();
  35570. if (track) {
  35571. track.mode = 'disabled';
  35572. }
  35573. mediaType.onTrackChanged();
  35574. };
  35575. }
  35576. };
  35577. var setupListeners = {
  35578. /**
  35579. * Setup event listeners for audio playlist loader
  35580. *
  35581. * @param {String} type
  35582. * MediaGroup type
  35583. * @param {PlaylistLoader|null} playlistLoader
  35584. * PlaylistLoader to register listeners on
  35585. * @param {Object} settings
  35586. * Object containing required information for media groups
  35587. * @function setupListeners.AUDIO
  35588. */
  35589. AUDIO: function AUDIO(type, playlistLoader, settings) {
  35590. if (!playlistLoader) {
  35591. // no playlist loader means audio will be muxed with the video
  35592. return;
  35593. }
  35594. var tech = settings.tech,
  35595. requestOptions = settings.requestOptions,
  35596. segmentLoader = settings.segmentLoaders[type];
  35597. playlistLoader.on('loadedmetadata', function () {
  35598. var media = playlistLoader.media();
  35599. segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
  35600. // permits, start downloading segments
  35601. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  35602. segmentLoader.load();
  35603. }
  35604. });
  35605. playlistLoader.on('loadedplaylist', function () {
  35606. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  35607. if (!tech.paused()) {
  35608. segmentLoader.load();
  35609. }
  35610. });
  35611. playlistLoader.on('error', onError[type](type, settings));
  35612. },
  35613. /**
  35614. * Setup event listeners for subtitle playlist loader
  35615. *
  35616. * @param {String} type
  35617. * MediaGroup type
  35618. * @param {PlaylistLoader|null} playlistLoader
  35619. * PlaylistLoader to register listeners on
  35620. * @param {Object} settings
  35621. * Object containing required information for media groups
  35622. * @function setupListeners.SUBTITLES
  35623. */
  35624. SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
  35625. var tech = settings.tech,
  35626. requestOptions = settings.requestOptions,
  35627. segmentLoader = settings.segmentLoaders[type],
  35628. mediaType = settings.mediaTypes[type];
  35629. playlistLoader.on('loadedmetadata', function () {
  35630. var media = playlistLoader.media();
  35631. segmentLoader.playlist(media, requestOptions);
  35632. segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
  35633. // permits, start downloading segments
  35634. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  35635. segmentLoader.load();
  35636. }
  35637. });
  35638. playlistLoader.on('loadedplaylist', function () {
  35639. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  35640. if (!tech.paused()) {
  35641. segmentLoader.load();
  35642. }
  35643. });
  35644. playlistLoader.on('error', onError[type](type, settings));
  35645. }
  35646. };
  35647. var byGroupId = function byGroupId(type, groupId) {
  35648. return function (playlist) {
  35649. return playlist.attributes[type] === groupId;
  35650. };
  35651. };
  35652. var byResolvedUri = function byResolvedUri(resolvedUri) {
  35653. return function (playlist) {
  35654. return playlist.resolvedUri === resolvedUri;
  35655. };
  35656. };
  35657. var initialize = {
  35658. /**
  35659. * Setup PlaylistLoaders and AudioTracks for the audio groups
  35660. *
  35661. * @param {String} type
  35662. * MediaGroup type
  35663. * @param {Object} settings
  35664. * Object containing required information for media groups
  35665. * @function initialize.AUDIO
  35666. */
  35667. 'AUDIO': function AUDIO(type, settings) {
  35668. var hls = settings.hls,
  35669. sourceType = settings.sourceType,
  35670. segmentLoader = settings.segmentLoaders[type],
  35671. requestOptions = settings.requestOptions,
  35672. _settings$master = settings.master,
  35673. mediaGroups = _settings$master.mediaGroups,
  35674. playlists = _settings$master.playlists,
  35675. _settings$mediaTypes$ = settings.mediaTypes[type],
  35676. groups = _settings$mediaTypes$.groups,
  35677. tracks = _settings$mediaTypes$.tracks,
  35678. masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none
  35679. if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
  35680. mediaGroups[type] = {
  35681. main: {
  35682. default: {
  35683. default: true
  35684. }
  35685. }
  35686. };
  35687. }
  35688. for (var groupId in mediaGroups[type]) {
  35689. if (!groups[groupId]) {
  35690. groups[groupId] = [];
  35691. } // List of playlists that have an AUDIO attribute value matching the current
  35692. // group ID
  35693. var groupPlaylists = playlists.filter(byGroupId(type, groupId));
  35694. for (var variantLabel in mediaGroups[type][groupId]) {
  35695. var properties = mediaGroups[type][groupId][variantLabel]; // List of playlists for the current group ID that have a matching uri with
  35696. // this alternate audio variant
  35697. var matchingPlaylists = groupPlaylists.filter(byResolvedUri(properties.resolvedUri));
  35698. if (matchingPlaylists.length) {
  35699. // If there is a playlist that has the same uri as this audio variant, assume
  35700. // that the playlist is audio only. We delete the resolvedUri property here
  35701. // to prevent a playlist loader from being created so that we don't have
  35702. // both the main and audio segment loaders loading the same audio segments
  35703. // from the same playlist.
  35704. delete properties.resolvedUri;
  35705. }
  35706. var playlistLoader = void 0;
  35707. if (properties.resolvedUri) {
  35708. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  35709. } else if (properties.playlists && sourceType === 'dash') {
  35710. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  35711. } else {
  35712. // no resolvedUri means the audio is muxed with the video when using this
  35713. // audio track
  35714. playlistLoader = null;
  35715. }
  35716. properties = videojs$1.mergeOptions({
  35717. id: variantLabel,
  35718. playlistLoader: playlistLoader
  35719. }, properties);
  35720. setupListeners[type](type, properties.playlistLoader, settings);
  35721. groups[groupId].push(properties);
  35722. if (typeof tracks[variantLabel] === 'undefined') {
  35723. var track = new videojs$1.AudioTrack({
  35724. id: variantLabel,
  35725. kind: audioTrackKind_(properties),
  35726. enabled: false,
  35727. language: properties.language,
  35728. default: properties.default,
  35729. label: variantLabel
  35730. });
  35731. tracks[variantLabel] = track;
  35732. }
  35733. }
  35734. } // setup single error event handler for the segment loader
  35735. segmentLoader.on('error', onError[type](type, settings));
  35736. },
  35737. /**
  35738. * Setup PlaylistLoaders and TextTracks for the subtitle groups
  35739. *
  35740. * @param {String} type
  35741. * MediaGroup type
  35742. * @param {Object} settings
  35743. * Object containing required information for media groups
  35744. * @function initialize.SUBTITLES
  35745. */
  35746. 'SUBTITLES': function SUBTITLES(type, settings) {
  35747. var tech = settings.tech,
  35748. hls = settings.hls,
  35749. sourceType = settings.sourceType,
  35750. segmentLoader = settings.segmentLoaders[type],
  35751. requestOptions = settings.requestOptions,
  35752. mediaGroups = settings.master.mediaGroups,
  35753. _settings$mediaTypes$2 = settings.mediaTypes[type],
  35754. groups = _settings$mediaTypes$2.groups,
  35755. tracks = _settings$mediaTypes$2.tracks,
  35756. masterPlaylistLoader = settings.masterPlaylistLoader;
  35757. for (var groupId in mediaGroups[type]) {
  35758. if (!groups[groupId]) {
  35759. groups[groupId] = [];
  35760. }
  35761. for (var variantLabel in mediaGroups[type][groupId]) {
  35762. if (mediaGroups[type][groupId][variantLabel].forced) {
  35763. // Subtitle playlists with the forced attribute are not selectable in Safari.
  35764. // According to Apple's HLS Authoring Specification:
  35765. // If content has forced subtitles and regular subtitles in a given language,
  35766. // the regular subtitles track in that language MUST contain both the forced
  35767. // subtitles and the regular subtitles for that language.
  35768. // Because of this requirement and that Safari does not add forced subtitles,
  35769. // forced subtitles are skipped here to maintain consistent experience across
  35770. // all platforms
  35771. continue;
  35772. }
  35773. var properties = mediaGroups[type][groupId][variantLabel];
  35774. var playlistLoader = void 0;
  35775. if (sourceType === 'hls') {
  35776. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  35777. } else if (sourceType === 'dash') {
  35778. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  35779. }
  35780. properties = videojs$1.mergeOptions({
  35781. id: variantLabel,
  35782. playlistLoader: playlistLoader
  35783. }, properties);
  35784. setupListeners[type](type, properties.playlistLoader, settings);
  35785. groups[groupId].push(properties);
  35786. if (typeof tracks[variantLabel] === 'undefined') {
  35787. var track = tech.addRemoteTextTrack({
  35788. id: variantLabel,
  35789. kind: 'subtitles',
  35790. default: properties.default && properties.autoselect,
  35791. language: properties.language,
  35792. label: variantLabel
  35793. }, false).track;
  35794. tracks[variantLabel] = track;
  35795. }
  35796. }
  35797. } // setup single error event handler for the segment loader
  35798. segmentLoader.on('error', onError[type](type, settings));
  35799. },
  35800. /**
  35801. * Setup TextTracks for the closed-caption groups
  35802. *
  35803. * @param {String} type
  35804. * MediaGroup type
  35805. * @param {Object} settings
  35806. * Object containing required information for media groups
  35807. * @function initialize['CLOSED-CAPTIONS']
  35808. */
  35809. 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
  35810. var tech = settings.tech,
  35811. mediaGroups = settings.master.mediaGroups,
  35812. _settings$mediaTypes$3 = settings.mediaTypes[type],
  35813. groups = _settings$mediaTypes$3.groups,
  35814. tracks = _settings$mediaTypes$3.tracks;
  35815. for (var groupId in mediaGroups[type]) {
  35816. if (!groups[groupId]) {
  35817. groups[groupId] = [];
  35818. }
  35819. for (var variantLabel in mediaGroups[type][groupId]) {
  35820. var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that
  35821. // doesn't use a CCx INSTREAM-ID
  35822. if (!properties.instreamId.match(/CC\d/)) {
  35823. continue;
  35824. } // No PlaylistLoader is required for Closed-Captions because the captions are
  35825. // embedded within the video stream
  35826. groups[groupId].push(videojs$1.mergeOptions({
  35827. id: variantLabel
  35828. }, properties));
  35829. if (typeof tracks[variantLabel] === 'undefined') {
  35830. var track = tech.addRemoteTextTrack({
  35831. id: properties.instreamId,
  35832. kind: 'captions',
  35833. default: properties.default && properties.autoselect,
  35834. language: properties.language,
  35835. label: variantLabel
  35836. }, false).track;
  35837. tracks[variantLabel] = track;
  35838. }
  35839. }
  35840. }
  35841. }
  35842. };
  35843. /**
  35844. * Returns a function used to get the active group of the provided type
  35845. *
  35846. * @param {String} type
  35847. * MediaGroup type
  35848. * @param {Object} settings
  35849. * Object containing required information for media groups
  35850. * @return {Function}
  35851. * Function that returns the active media group for the provided type. Takes an
  35852. * optional parameter {TextTrack} track. If no track is provided, a list of all
  35853. * variants in the group, otherwise the variant corresponding to the provided
  35854. * track is returned.
  35855. * @function activeGroup
  35856. */
  35857. var activeGroup = function activeGroup(type, settings) {
  35858. return function (track) {
  35859. var masterPlaylistLoader = settings.masterPlaylistLoader,
  35860. groups = settings.mediaTypes[type].groups;
  35861. var media = masterPlaylistLoader.media();
  35862. if (!media) {
  35863. return null;
  35864. }
  35865. var variants = null;
  35866. if (media.attributes[type]) {
  35867. variants = groups[media.attributes[type]];
  35868. }
  35869. variants = variants || groups.main;
  35870. if (typeof track === 'undefined') {
  35871. return variants;
  35872. }
  35873. if (track === null) {
  35874. // An active track was specified so a corresponding group is expected. track === null
  35875. // means no track is currently active so there is no corresponding group
  35876. return null;
  35877. }
  35878. return variants.filter(function (props) {
  35879. return props.id === track.id;
  35880. })[0] || null;
  35881. };
  35882. };
  35883. var activeTrack = {
  35884. /**
  35885. * Returns a function used to get the active track of type provided
  35886. *
  35887. * @param {String} type
  35888. * MediaGroup type
  35889. * @param {Object} settings
  35890. * Object containing required information for media groups
  35891. * @return {Function}
  35892. * Function that returns the active media track for the provided type. Returns
  35893. * null if no track is active
  35894. * @function activeTrack.AUDIO
  35895. */
  35896. AUDIO: function AUDIO(type, settings) {
  35897. return function () {
  35898. var tracks = settings.mediaTypes[type].tracks;
  35899. for (var id in tracks) {
  35900. if (tracks[id].enabled) {
  35901. return tracks[id];
  35902. }
  35903. }
  35904. return null;
  35905. };
  35906. },
  35907. /**
  35908. * Returns a function used to get the active track of type provided
  35909. *
  35910. * @param {String} type
  35911. * MediaGroup type
  35912. * @param {Object} settings
  35913. * Object containing required information for media groups
  35914. * @return {Function}
  35915. * Function that returns the active media track for the provided type. Returns
  35916. * null if no track is active
  35917. * @function activeTrack.SUBTITLES
  35918. */
  35919. SUBTITLES: function SUBTITLES(type, settings) {
  35920. return function () {
  35921. var tracks = settings.mediaTypes[type].tracks;
  35922. for (var id in tracks) {
  35923. if (tracks[id].mode === 'showing') {
  35924. return tracks[id];
  35925. }
  35926. }
  35927. return null;
  35928. };
  35929. }
  35930. };
  35931. /**
  35932. * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
  35933. * Closed-Captions) specified in the master manifest.
  35934. *
  35935. * @param {Object} settings
  35936. * Object containing required information for setting up the media groups
  35937. * @param {SegmentLoader} settings.segmentLoaders.AUDIO
  35938. * Audio segment loader
  35939. * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
  35940. * Subtitle segment loader
  35941. * @param {SegmentLoader} settings.segmentLoaders.main
  35942. * Main segment loader
  35943. * @param {Tech} settings.tech
  35944. * The tech of the player
  35945. * @param {Object} settings.requestOptions
  35946. * XHR request options used by the segment loaders
  35947. * @param {PlaylistLoader} settings.masterPlaylistLoader
  35948. * PlaylistLoader for the master source
  35949. * @param {HlsHandler} settings.hls
  35950. * HLS SourceHandler
  35951. * @param {Object} settings.master
  35952. * The parsed master manifest
  35953. * @param {Object} settings.mediaTypes
  35954. * Object to store the loaders, tracks, and utility methods for each media type
  35955. * @param {Function} settings.blacklistCurrentPlaylist
  35956. * Blacklists the current rendition and forces a rendition switch.
  35957. * @function setupMediaGroups
  35958. */
  35959. var setupMediaGroups = function setupMediaGroups(settings) {
  35960. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  35961. initialize[type](type, settings);
  35962. });
  35963. var mediaTypes = settings.mediaTypes,
  35964. masterPlaylistLoader = settings.masterPlaylistLoader,
  35965. tech = settings.tech,
  35966. hls = settings.hls; // setup active group and track getters and change event handlers
  35967. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  35968. mediaTypes[type].activeGroup = activeGroup(type, settings);
  35969. mediaTypes[type].activeTrack = activeTrack[type](type, settings);
  35970. mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
  35971. mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
  35972. }); // DO NOT enable the default subtitle or caption track.
  35973. // DO enable the default audio track
  35974. var audioGroup = mediaTypes.AUDIO.activeGroup();
  35975. var groupId = (audioGroup.filter(function (group) {
  35976. return group.default;
  35977. })[0] || audioGroup[0]).id;
  35978. mediaTypes.AUDIO.tracks[groupId].enabled = true;
  35979. mediaTypes.AUDIO.onTrackChanged();
  35980. masterPlaylistLoader.on('mediachange', function () {
  35981. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  35982. return mediaTypes[type].onGroupChanged();
  35983. });
  35984. }); // custom audio track change event handler for usage event
  35985. var onAudioTrackChanged = function onAudioTrackChanged() {
  35986. mediaTypes.AUDIO.onTrackChanged();
  35987. tech.trigger({
  35988. type: 'usage',
  35989. name: 'hls-audio-change'
  35990. });
  35991. };
  35992. tech.audioTracks().addEventListener('change', onAudioTrackChanged);
  35993. tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  35994. hls.on('dispose', function () {
  35995. tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
  35996. tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  35997. }); // clear existing audio tracks and add the ones we just created
  35998. tech.clearTracks('audio');
  35999. for (var id in mediaTypes.AUDIO.tracks) {
  36000. tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
  36001. }
  36002. };
  36003. /**
  36004. * Creates skeleton object used to store the loaders, tracks, and utility methods for each
  36005. * media type
  36006. *
  36007. * @return {Object}
  36008. * Object to store the loaders, tracks, and utility methods for each media type
  36009. * @function createMediaTypes
  36010. */
  36011. var createMediaTypes = function createMediaTypes() {
  36012. var mediaTypes = {};
  36013. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  36014. mediaTypes[type] = {
  36015. groups: {},
  36016. tracks: {},
  36017. activePlaylistLoader: null,
  36018. activeGroup: noop,
  36019. activeTrack: noop,
  36020. onGroupChanged: noop,
  36021. onTrackChanged: noop
  36022. };
  36023. });
  36024. return mediaTypes;
  36025. };
  36026. /**
  36027. * @file master-playlist-controller.js
  36028. */
  36029. var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
  36030. var Hls = void 0; // SegmentLoader stats that need to have each loader's
  36031. // values summed to calculate the final value
  36032. var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
  36033. var sumLoaderStat = function sumLoaderStat(stat) {
  36034. return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
  36035. };
  36036. /**
  36037. * the master playlist controller controller all interactons
  36038. * between playlists and segmentloaders. At this time this mainly
  36039. * involves a master playlist and a series of audio playlists
  36040. * if they are available
  36041. *
  36042. * @class MasterPlaylistController
  36043. * @extends videojs.EventTarget
  36044. */
  36045. var MasterPlaylistController = function (_videojs$EventTarget) {
  36046. inherits(MasterPlaylistController, _videojs$EventTarget);
  36047. function MasterPlaylistController(options) {
  36048. classCallCheck(this, MasterPlaylistController);
  36049. var _this = possibleConstructorReturn(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this));
  36050. var url = options.url,
  36051. handleManifestRedirects = options.handleManifestRedirects,
  36052. withCredentials = options.withCredentials,
  36053. tech = options.tech,
  36054. bandwidth = options.bandwidth,
  36055. externHls = options.externHls,
  36056. useCueTags = options.useCueTags,
  36057. blacklistDuration = options.blacklistDuration,
  36058. enableLowInitialPlaylist = options.enableLowInitialPlaylist,
  36059. sourceType = options.sourceType,
  36060. seekTo = options.seekTo,
  36061. cacheEncryptionKeys = options.cacheEncryptionKeys;
  36062. if (!url) {
  36063. throw new Error('A non-empty playlist URL is required');
  36064. }
  36065. Hls = externHls;
  36066. _this.withCredentials = withCredentials;
  36067. _this.tech_ = tech;
  36068. _this.hls_ = tech.hls;
  36069. _this.seekTo_ = seekTo;
  36070. _this.sourceType_ = sourceType;
  36071. _this.useCueTags_ = useCueTags;
  36072. _this.blacklistDuration = blacklistDuration;
  36073. _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
  36074. if (_this.useCueTags_) {
  36075. _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
  36076. _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
  36077. }
  36078. _this.requestOptions_ = {
  36079. withCredentials: withCredentials,
  36080. handleManifestRedirects: handleManifestRedirects,
  36081. timeout: null
  36082. };
  36083. _this.mediaTypes_ = createMediaTypes();
  36084. _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player
  36085. _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this));
  36086. _this.seekable_ = videojs$1.createTimeRanges();
  36087. _this.hasPlayed_ = function () {
  36088. return false;
  36089. };
  36090. _this.syncController_ = new SyncController(options);
  36091. _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
  36092. kind: 'metadata',
  36093. label: 'segment-metadata'
  36094. }, false).track;
  36095. _this.decrypter_ = new Decrypter$1();
  36096. _this.inbandTextTracks_ = {};
  36097. var segmentLoaderSettings = {
  36098. hls: _this.hls_,
  36099. mediaSource: _this.mediaSource,
  36100. currentTime: _this.tech_.currentTime.bind(_this.tech_),
  36101. seekable: function seekable$$1() {
  36102. return _this.seekable();
  36103. },
  36104. seeking: function seeking() {
  36105. return _this.tech_.seeking();
  36106. },
  36107. duration: function duration$$1() {
  36108. return _this.mediaSource.duration;
  36109. },
  36110. hasPlayed: function hasPlayed() {
  36111. return _this.hasPlayed_();
  36112. },
  36113. goalBufferLength: function goalBufferLength() {
  36114. return _this.goalBufferLength();
  36115. },
  36116. bandwidth: bandwidth,
  36117. syncController: _this.syncController_,
  36118. decrypter: _this.decrypter_,
  36119. sourceType: _this.sourceType_,
  36120. inbandTextTracks: _this.inbandTextTracks_,
  36121. cacheEncryptionKeys: cacheEncryptionKeys
  36122. };
  36123. _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_);
  36124. _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
  36125. // combined audio/video or just video when alternate audio track is selected
  36126. _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  36127. segmentMetadataTrack: _this.segmentMetadataTrack_,
  36128. loaderType: 'main'
  36129. }), options); // alternate audio track
  36130. _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  36131. loaderType: 'audio'
  36132. }), options);
  36133. _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  36134. loaderType: 'vtt'
  36135. }), options);
  36136. _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters
  36137. loaderStats.forEach(function (stat) {
  36138. _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
  36139. });
  36140. _this.logger_ = logger('MPC');
  36141. _this.masterPlaylistLoader_.load();
  36142. return _this;
  36143. }
  36144. /**
  36145. * Register event handlers on the master playlist loader. A helper
  36146. * function for construction time.
  36147. *
  36148. * @private
  36149. */
  36150. createClass(MasterPlaylistController, [{
  36151. key: 'setupMasterPlaylistLoaderListeners_',
  36152. value: function setupMasterPlaylistLoaderListeners_() {
  36153. var _this2 = this;
  36154. this.masterPlaylistLoader_.on('loadedmetadata', function () {
  36155. var media = _this2.masterPlaylistLoader_.media();
  36156. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  36157. // timeout the request.
  36158. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  36159. _this2.requestOptions_.timeout = 0;
  36160. } else {
  36161. _this2.requestOptions_.timeout = requestTimeout;
  36162. } // if this isn't a live video and preload permits, start
  36163. // downloading segments
  36164. if (media.endList && _this2.tech_.preload() !== 'none') {
  36165. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  36166. _this2.mainSegmentLoader_.load();
  36167. }
  36168. setupMediaGroups({
  36169. sourceType: _this2.sourceType_,
  36170. segmentLoaders: {
  36171. AUDIO: _this2.audioSegmentLoader_,
  36172. SUBTITLES: _this2.subtitleSegmentLoader_,
  36173. main: _this2.mainSegmentLoader_
  36174. },
  36175. tech: _this2.tech_,
  36176. requestOptions: _this2.requestOptions_,
  36177. masterPlaylistLoader: _this2.masterPlaylistLoader_,
  36178. hls: _this2.hls_,
  36179. master: _this2.master(),
  36180. mediaTypes: _this2.mediaTypes_,
  36181. blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
  36182. });
  36183. _this2.triggerPresenceUsage_(_this2.master(), media);
  36184. try {
  36185. _this2.setupSourceBuffers_();
  36186. } catch (e) {
  36187. videojs$1.log.warn('Failed to create SourceBuffers', e);
  36188. return _this2.mediaSource.endOfStream('decode');
  36189. }
  36190. _this2.setupFirstPlay();
  36191. if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
  36192. _this2.trigger('selectedinitialmedia');
  36193. } else {
  36194. // We must wait for the active audio playlist loader to
  36195. // finish setting up before triggering this event so the
  36196. // representations API and EME setup is correct
  36197. _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
  36198. _this2.trigger('selectedinitialmedia');
  36199. });
  36200. }
  36201. });
  36202. this.masterPlaylistLoader_.on('loadedplaylist', function () {
  36203. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  36204. if (!updatedPlaylist) {
  36205. // blacklist any variants that are not supported by the browser before selecting
  36206. // an initial media as the playlist selectors do not consider browser support
  36207. _this2.excludeUnsupportedVariants_();
  36208. var selectedMedia = void 0;
  36209. if (_this2.enableLowInitialPlaylist) {
  36210. selectedMedia = _this2.selectInitialPlaylist();
  36211. }
  36212. if (!selectedMedia) {
  36213. selectedMedia = _this2.selectPlaylist();
  36214. }
  36215. _this2.initialMedia_ = selectedMedia;
  36216. _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
  36217. return;
  36218. }
  36219. if (_this2.useCueTags_) {
  36220. _this2.updateAdCues_(updatedPlaylist);
  36221. } // TODO: Create a new event on the PlaylistLoader that signals
  36222. // that the segments have changed in some way and use that to
  36223. // update the SegmentLoader instead of doing it twice here and
  36224. // on `mediachange`
  36225. _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
  36226. _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running,
  36227. // as it is possible that it was temporarily stopped while waiting for
  36228. // a playlist (e.g., in case the playlist errored and we re-requested it).
  36229. if (!_this2.tech_.paused()) {
  36230. _this2.mainSegmentLoader_.load();
  36231. if (_this2.audioSegmentLoader_) {
  36232. _this2.audioSegmentLoader_.load();
  36233. }
  36234. }
  36235. if (!updatedPlaylist.endList) {
  36236. var addSeekableRange = function addSeekableRange() {
  36237. var seekable$$1 = _this2.seekable();
  36238. if (seekable$$1.length !== 0) {
  36239. _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0));
  36240. }
  36241. };
  36242. if (_this2.duration() !== Infinity) {
  36243. var onDurationchange = function onDurationchange() {
  36244. if (_this2.duration() === Infinity) {
  36245. addSeekableRange();
  36246. } else {
  36247. _this2.tech_.one('durationchange', onDurationchange);
  36248. }
  36249. };
  36250. _this2.tech_.one('durationchange', onDurationchange);
  36251. } else {
  36252. addSeekableRange();
  36253. }
  36254. }
  36255. });
  36256. this.masterPlaylistLoader_.on('error', function () {
  36257. _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
  36258. });
  36259. this.masterPlaylistLoader_.on('mediachanging', function () {
  36260. _this2.mainSegmentLoader_.abort();
  36261. _this2.mainSegmentLoader_.pause();
  36262. });
  36263. this.masterPlaylistLoader_.on('mediachange', function () {
  36264. var media = _this2.masterPlaylistLoader_.media();
  36265. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  36266. // timeout the request.
  36267. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  36268. _this2.requestOptions_.timeout = 0;
  36269. } else {
  36270. _this2.requestOptions_.timeout = requestTimeout;
  36271. } // TODO: Create a new event on the PlaylistLoader that signals
  36272. // that the segments have changed in some way and use that to
  36273. // update the SegmentLoader instead of doing it twice here and
  36274. // on `loadedplaylist`
  36275. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  36276. _this2.mainSegmentLoader_.load();
  36277. _this2.tech_.trigger({
  36278. type: 'mediachange',
  36279. bubbles: true
  36280. });
  36281. });
  36282. this.masterPlaylistLoader_.on('playlistunchanged', function () {
  36283. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  36284. var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
  36285. if (playlistOutdated) {
  36286. // Playlist has stopped updating and we're stuck at its end. Try to
  36287. // blacklist it and switch to another playlist in the hope that that
  36288. // one is updating (and give the player a chance to re-adjust to the
  36289. // safe live point).
  36290. _this2.blacklistCurrentPlaylist({
  36291. message: 'Playlist no longer updating.'
  36292. }); // useful for monitoring QoS
  36293. _this2.tech_.trigger('playliststuck');
  36294. }
  36295. });
  36296. this.masterPlaylistLoader_.on('renditiondisabled', function () {
  36297. _this2.tech_.trigger({
  36298. type: 'usage',
  36299. name: 'hls-rendition-disabled'
  36300. });
  36301. });
  36302. this.masterPlaylistLoader_.on('renditionenabled', function () {
  36303. _this2.tech_.trigger({
  36304. type: 'usage',
  36305. name: 'hls-rendition-enabled'
  36306. });
  36307. });
  36308. }
  36309. /**
  36310. * A helper function for triggerring presence usage events once per source
  36311. *
  36312. * @private
  36313. */
  36314. }, {
  36315. key: 'triggerPresenceUsage_',
  36316. value: function triggerPresenceUsage_(master, media) {
  36317. var mediaGroups = master.mediaGroups || {};
  36318. var defaultDemuxed = true;
  36319. var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
  36320. for (var mediaGroup in mediaGroups.AUDIO) {
  36321. for (var label in mediaGroups.AUDIO[mediaGroup]) {
  36322. var properties = mediaGroups.AUDIO[mediaGroup][label];
  36323. if (!properties.uri) {
  36324. defaultDemuxed = false;
  36325. }
  36326. }
  36327. }
  36328. if (defaultDemuxed) {
  36329. this.tech_.trigger({
  36330. type: 'usage',
  36331. name: 'hls-demuxed'
  36332. });
  36333. }
  36334. if (Object.keys(mediaGroups.SUBTITLES).length) {
  36335. this.tech_.trigger({
  36336. type: 'usage',
  36337. name: 'hls-webvtt'
  36338. });
  36339. }
  36340. if (Hls.Playlist.isAes(media)) {
  36341. this.tech_.trigger({
  36342. type: 'usage',
  36343. name: 'hls-aes'
  36344. });
  36345. }
  36346. if (Hls.Playlist.isFmp4(media)) {
  36347. this.tech_.trigger({
  36348. type: 'usage',
  36349. name: 'hls-fmp4'
  36350. });
  36351. }
  36352. if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
  36353. this.tech_.trigger({
  36354. type: 'usage',
  36355. name: 'hls-alternate-audio'
  36356. });
  36357. }
  36358. if (this.useCueTags_) {
  36359. this.tech_.trigger({
  36360. type: 'usage',
  36361. name: 'hls-playlist-cue-tags'
  36362. });
  36363. }
  36364. }
  36365. /**
  36366. * Register event handlers on the segment loaders. A helper function
  36367. * for construction time.
  36368. *
  36369. * @private
  36370. */
  36371. }, {
  36372. key: 'setupSegmentLoaderListeners_',
  36373. value: function setupSegmentLoaderListeners_() {
  36374. var _this3 = this;
  36375. this.mainSegmentLoader_.on('bandwidthupdate', function () {
  36376. var nextPlaylist = _this3.selectPlaylist();
  36377. var currentPlaylist = _this3.masterPlaylistLoader_.media();
  36378. var buffered = _this3.tech_.buffered();
  36379. var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
  36380. var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account.
  36381. // This is because in LIVE, the player plays 3 segments from the end of the
  36382. // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
  36383. // in those segments, a viewer will never experience a rendition upswitch.
  36384. if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD
  36385. // duration is below the max potential low water line
  36386. _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but
  36387. nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of
  36388. // buffer while loading a higher rendition.
  36389. forwardBuffer >= bufferLowWaterLine) {
  36390. _this3.masterPlaylistLoader_.media(nextPlaylist);
  36391. }
  36392. _this3.tech_.trigger('bandwidthupdate');
  36393. });
  36394. this.mainSegmentLoader_.on('progress', function () {
  36395. _this3.trigger('progress');
  36396. });
  36397. this.mainSegmentLoader_.on('error', function () {
  36398. _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
  36399. });
  36400. this.mainSegmentLoader_.on('syncinfoupdate', function () {
  36401. _this3.onSyncInfoUpdate_();
  36402. });
  36403. this.mainSegmentLoader_.on('timestampoffset', function () {
  36404. _this3.tech_.trigger({
  36405. type: 'usage',
  36406. name: 'hls-timestamp-offset'
  36407. });
  36408. });
  36409. this.audioSegmentLoader_.on('syncinfoupdate', function () {
  36410. _this3.onSyncInfoUpdate_();
  36411. });
  36412. this.mainSegmentLoader_.on('ended', function () {
  36413. _this3.onEndOfStream();
  36414. });
  36415. this.mainSegmentLoader_.on('earlyabort', function () {
  36416. _this3.blacklistCurrentPlaylist({
  36417. message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
  36418. }, ABORT_EARLY_BLACKLIST_SECONDS);
  36419. });
  36420. this.mainSegmentLoader_.on('reseteverything', function () {
  36421. // If playing an MTS stream, a videojs.MediaSource is listening for
  36422. // hls-reset to reset caption parsing state in the transmuxer
  36423. _this3.tech_.trigger('hls-reset');
  36424. });
  36425. this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
  36426. // If playing an MTS stream in html, a videojs.MediaSource is listening for
  36427. // hls-segment-time-mapping update its internal mapping of stream to display time
  36428. _this3.tech_.trigger({
  36429. type: 'hls-segment-time-mapping',
  36430. mapping: event.mapping
  36431. });
  36432. });
  36433. this.audioSegmentLoader_.on('ended', function () {
  36434. _this3.onEndOfStream();
  36435. });
  36436. }
  36437. }, {
  36438. key: 'mediaSecondsLoaded_',
  36439. value: function mediaSecondsLoaded_() {
  36440. return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
  36441. }
  36442. /**
  36443. * Call load on our SegmentLoaders
  36444. */
  36445. }, {
  36446. key: 'load',
  36447. value: function load() {
  36448. this.mainSegmentLoader_.load();
  36449. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36450. this.audioSegmentLoader_.load();
  36451. }
  36452. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  36453. this.subtitleSegmentLoader_.load();
  36454. }
  36455. }
  36456. /**
  36457. * Re-tune playback quality level for the current player
  36458. * conditions without performing destructive actions, like
  36459. * removing already buffered content
  36460. *
  36461. * @private
  36462. */
  36463. }, {
  36464. key: 'smoothQualityChange_',
  36465. value: function smoothQualityChange_() {
  36466. var media = this.selectPlaylist();
  36467. if (media !== this.masterPlaylistLoader_.media()) {
  36468. this.masterPlaylistLoader_.media(media);
  36469. this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes
  36470. }
  36471. }
  36472. /**
  36473. * Re-tune playback quality level for the current player
  36474. * conditions. This method will perform destructive actions like removing
  36475. * already buffered content in order to readjust the currently active
  36476. * playlist quickly. This is good for manual quality changes
  36477. *
  36478. * @private
  36479. */
  36480. }, {
  36481. key: 'fastQualityChange_',
  36482. value: function fastQualityChange_() {
  36483. var _this4 = this;
  36484. var media = this.selectPlaylist();
  36485. if (media === this.masterPlaylistLoader_.media()) {
  36486. return;
  36487. }
  36488. this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give
  36489. // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
  36490. // ahead is roughly the minimum that will accomplish this across a variety of content
  36491. // in IE and Edge, but seeking in place is sufficient on all other browsers)
  36492. // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
  36493. // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
  36494. this.mainSegmentLoader_.resetEverything(function () {
  36495. // Since this is not a typical seek, we avoid the seekTo method which can cause segments
  36496. // from the previously enabled rendition to load before the new playlist has finished loading
  36497. if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) {
  36498. _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04);
  36499. } else {
  36500. _this4.tech_.setCurrentTime(_this4.tech_.currentTime());
  36501. }
  36502. }); // don't need to reset audio as it is reset when media changes
  36503. }
  36504. /**
  36505. * Begin playback.
  36506. */
  36507. }, {
  36508. key: 'play',
  36509. value: function play() {
  36510. if (this.setupFirstPlay()) {
  36511. return;
  36512. }
  36513. if (this.tech_.ended()) {
  36514. this.seekTo_(0);
  36515. }
  36516. if (this.hasPlayed_()) {
  36517. this.load();
  36518. }
  36519. var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
  36520. // seek forward to the live point
  36521. if (this.tech_.duration() === Infinity) {
  36522. if (this.tech_.currentTime() < seekable$$1.start(0)) {
  36523. return this.seekTo_(seekable$$1.end(seekable$$1.length - 1));
  36524. }
  36525. }
  36526. }
  36527. /**
  36528. * Seek to the latest media position if this is a live video and the
  36529. * player and video are loaded and initialized.
  36530. */
  36531. }, {
  36532. key: 'setupFirstPlay',
  36533. value: function setupFirstPlay() {
  36534. var _this5 = this;
  36535. var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
  36536. // If 1) there is no active media
  36537. // 2) the player is paused
  36538. // 3) the first play has already been setup
  36539. // then exit early
  36540. if (!media || this.tech_.paused() || this.hasPlayed_()) {
  36541. return false;
  36542. } // when the video is a live stream
  36543. if (!media.endList) {
  36544. var seekable$$1 = this.seekable();
  36545. if (!seekable$$1.length) {
  36546. // without a seekable range, the player cannot seek to begin buffering at the live
  36547. // point
  36548. return false;
  36549. }
  36550. if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
  36551. // IE11 throws an InvalidStateError if you try to set currentTime while the
  36552. // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
  36553. this.tech_.one('loadedmetadata', function () {
  36554. _this5.trigger('firstplay');
  36555. _this5.seekTo_(seekable$$1.end(0));
  36556. _this5.hasPlayed_ = function () {
  36557. return true;
  36558. };
  36559. });
  36560. return false;
  36561. } // trigger firstplay to inform the source handler to ignore the next seek event
  36562. this.trigger('firstplay'); // seek to the live point
  36563. this.seekTo_(seekable$$1.end(0));
  36564. }
  36565. this.hasPlayed_ = function () {
  36566. return true;
  36567. }; // we can begin loading now that everything is ready
  36568. this.load();
  36569. return true;
  36570. }
  36571. /**
  36572. * handle the sourceopen event on the MediaSource
  36573. *
  36574. * @private
  36575. */
  36576. }, {
  36577. key: 'handleSourceOpen_',
  36578. value: function handleSourceOpen_() {
  36579. // Only attempt to create the source buffer if none already exist.
  36580. // handleSourceOpen is also called when we are "re-opening" a source buffer
  36581. // after `endOfStream` has been called (in response to a seek for instance)
  36582. try {
  36583. this.setupSourceBuffers_();
  36584. } catch (e) {
  36585. videojs$1.log.warn('Failed to create Source Buffers', e);
  36586. return this.mediaSource.endOfStream('decode');
  36587. } // if autoplay is enabled, begin playback. This is duplicative of
  36588. // code in video.js but is required because play() must be invoked
  36589. // *after* the media source has opened.
  36590. if (this.tech_.autoplay()) {
  36591. var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
  36592. // on browsers which return a promise
  36593. if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
  36594. playPromise.then(null, function (e) {});
  36595. }
  36596. }
  36597. this.trigger('sourceopen');
  36598. }
  36599. /**
  36600. * Calls endOfStream on the media source when all active stream types have called
  36601. * endOfStream
  36602. *
  36603. * @param {string} streamType
  36604. * Stream type of the segment loader that called endOfStream
  36605. * @private
  36606. */
  36607. }, {
  36608. key: 'onEndOfStream',
  36609. value: function onEndOfStream() {
  36610. var isEndOfStream = this.mainSegmentLoader_.ended_;
  36611. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36612. // if the audio playlist loader exists, then alternate audio is active
  36613. if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) {
  36614. // if we do not know if the main segment loader contains video yet or if we
  36615. // definitively know the main segment loader contains video, then we need to wait
  36616. // for both main and audio segment loaders to call endOfStream
  36617. isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
  36618. } else {
  36619. // otherwise just rely on the audio loader
  36620. isEndOfStream = this.audioSegmentLoader_.ended_;
  36621. }
  36622. }
  36623. if (!isEndOfStream) {
  36624. return;
  36625. }
  36626. this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception,
  36627. // even when the media source is in a valid state.
  36628. try {
  36629. this.mediaSource.endOfStream();
  36630. } catch (e) {
  36631. videojs$1.log.warn('Failed to call media source endOfStream', e);
  36632. }
  36633. }
  36634. /**
  36635. * Check if a playlist has stopped being updated
  36636. * @param {Object} playlist the media playlist object
  36637. * @return {boolean} whether the playlist has stopped being updated or not
  36638. */
  36639. }, {
  36640. key: 'stuckAtPlaylistEnd_',
  36641. value: function stuckAtPlaylistEnd_(playlist) {
  36642. var seekable$$1 = this.seekable();
  36643. if (!seekable$$1.length) {
  36644. // playlist doesn't have enough information to determine whether we are stuck
  36645. return false;
  36646. }
  36647. var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
  36648. if (expired === null) {
  36649. return false;
  36650. } // does not use the safe live end to calculate playlist end, since we
  36651. // don't want to say we are stuck while there is still content
  36652. var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
  36653. var currentTime = this.tech_.currentTime();
  36654. var buffered = this.tech_.buffered();
  36655. if (!buffered.length) {
  36656. // return true if the playhead reached the absolute end of the playlist
  36657. return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
  36658. }
  36659. var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
  36660. // end of playlist
  36661. return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
  36662. }
  36663. /**
  36664. * Blacklists a playlist when an error occurs for a set amount of time
  36665. * making it unavailable for selection by the rendition selection algorithm
  36666. * and then forces a new playlist (rendition) selection.
  36667. *
  36668. * @param {Object=} error an optional error that may include the playlist
  36669. * to blacklist
  36670. * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
  36671. * playlist
  36672. */
  36673. }, {
  36674. key: 'blacklistCurrentPlaylist',
  36675. value: function blacklistCurrentPlaylist() {
  36676. var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  36677. var blacklistDuration = arguments[1];
  36678. var currentPlaylist = void 0;
  36679. var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain
  36680. // the playlist we were trying to load (but failed) and that should be
  36681. // blacklisted instead of the currently selected playlist which is likely
  36682. // out-of-date in this scenario
  36683. currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
  36684. blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were
  36685. // trying to load the master OR while we were disposing of the tech
  36686. if (!currentPlaylist) {
  36687. this.error = error;
  36688. try {
  36689. return this.mediaSource.endOfStream('network');
  36690. } catch (e) {
  36691. return this.trigger('error');
  36692. }
  36693. }
  36694. var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1;
  36695. if (isFinalRendition) {
  36696. // Never blacklisting this playlist because it's final rendition
  36697. videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.');
  36698. this.tech_.trigger('retryplaylist');
  36699. return this.masterPlaylistLoader_.load(isFinalRendition);
  36700. } // Blacklist this playlist
  36701. currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
  36702. this.tech_.trigger('blacklistplaylist');
  36703. this.tech_.trigger({
  36704. type: 'usage',
  36705. name: 'hls-rendition-blacklisted'
  36706. }); // Select a new playlist
  36707. nextPlaylist = this.selectPlaylist();
  36708. videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
  36709. return this.masterPlaylistLoader_.media(nextPlaylist);
  36710. }
  36711. /**
  36712. * Pause all segment loaders
  36713. */
  36714. }, {
  36715. key: 'pauseLoading',
  36716. value: function pauseLoading() {
  36717. this.mainSegmentLoader_.pause();
  36718. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36719. this.audioSegmentLoader_.pause();
  36720. }
  36721. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  36722. this.subtitleSegmentLoader_.pause();
  36723. }
  36724. }
  36725. /**
  36726. * set the current time on all segment loaders
  36727. *
  36728. * @param {TimeRange} currentTime the current time to set
  36729. * @return {TimeRange} the current time
  36730. */
  36731. }, {
  36732. key: 'setCurrentTime',
  36733. value: function setCurrentTime(currentTime) {
  36734. var buffered = findRange(this.tech_.buffered(), currentTime);
  36735. if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
  36736. // return immediately if the metadata is not ready yet
  36737. return 0;
  36738. } // it's clearly an edge-case but don't thrown an error if asked to
  36739. // seek within an empty playlist
  36740. if (!this.masterPlaylistLoader_.media().segments) {
  36741. return 0;
  36742. } // In flash playback, the segment loaders should be reset on every seek, even
  36743. // in buffer seeks. If the seek location is already buffered, continue buffering as
  36744. // usual
  36745. // TODO: redo this comment
  36746. if (buffered && buffered.length) {
  36747. return currentTime;
  36748. } // cancel outstanding requests so we begin buffering at the new
  36749. // location
  36750. this.mainSegmentLoader_.resetEverything();
  36751. this.mainSegmentLoader_.abort();
  36752. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36753. this.audioSegmentLoader_.resetEverything();
  36754. this.audioSegmentLoader_.abort();
  36755. }
  36756. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  36757. this.subtitleSegmentLoader_.resetEverything();
  36758. this.subtitleSegmentLoader_.abort();
  36759. } // start segment loader loading in case they are paused
  36760. this.load();
  36761. }
  36762. /**
  36763. * get the current duration
  36764. *
  36765. * @return {TimeRange} the duration
  36766. */
  36767. }, {
  36768. key: 'duration',
  36769. value: function duration$$1() {
  36770. if (!this.masterPlaylistLoader_) {
  36771. return 0;
  36772. }
  36773. if (this.mediaSource) {
  36774. return this.mediaSource.duration;
  36775. }
  36776. return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  36777. }
  36778. /**
  36779. * check the seekable range
  36780. *
  36781. * @return {TimeRange} the seekable range
  36782. */
  36783. }, {
  36784. key: 'seekable',
  36785. value: function seekable$$1() {
  36786. return this.seekable_;
  36787. }
  36788. }, {
  36789. key: 'onSyncInfoUpdate_',
  36790. value: function onSyncInfoUpdate_() {
  36791. var mainSeekable = void 0;
  36792. var audioSeekable = void 0;
  36793. if (!this.masterPlaylistLoader_) {
  36794. return;
  36795. }
  36796. var media = this.masterPlaylistLoader_.media();
  36797. if (!media) {
  36798. return;
  36799. }
  36800. var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  36801. if (expired === null) {
  36802. // not enough information to update seekable
  36803. return;
  36804. }
  36805. mainSeekable = Hls.Playlist.seekable(media, expired);
  36806. if (mainSeekable.length === 0) {
  36807. return;
  36808. }
  36809. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  36810. media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
  36811. expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  36812. if (expired === null) {
  36813. return;
  36814. }
  36815. audioSeekable = Hls.Playlist.seekable(media, expired);
  36816. if (audioSeekable.length === 0) {
  36817. return;
  36818. }
  36819. }
  36820. var oldEnd = void 0;
  36821. var oldStart = void 0;
  36822. if (this.seekable_ && this.seekable_.length) {
  36823. oldEnd = this.seekable_.end(0);
  36824. oldStart = this.seekable_.start(0);
  36825. }
  36826. if (!audioSeekable) {
  36827. // seekable has been calculated based on buffering video data so it
  36828. // can be returned directly
  36829. this.seekable_ = mainSeekable;
  36830. } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
  36831. // seekables are pretty far off, rely on main
  36832. this.seekable_ = mainSeekable;
  36833. } else {
  36834. this.seekable_ = videojs$1.createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]);
  36835. } // seekable is the same as last time
  36836. if (this.seekable_ && this.seekable_.length) {
  36837. if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
  36838. return;
  36839. }
  36840. }
  36841. this.logger_('seekable updated [' + printableRange(this.seekable_) + ']');
  36842. this.tech_.trigger('seekablechanged');
  36843. }
  36844. /**
  36845. * Update the player duration
  36846. */
  36847. }, {
  36848. key: 'updateDuration',
  36849. value: function updateDuration() {
  36850. var _this6 = this;
  36851. var oldDuration = this.mediaSource.duration;
  36852. var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  36853. var buffered = this.tech_.buffered();
  36854. var setDuration = function setDuration() {
  36855. // on firefox setting the duration may sometimes cause an exception
  36856. // even if the media source is open and source buffers are not
  36857. // updating, something about the media source being in an invalid state.
  36858. _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration);
  36859. try {
  36860. _this6.mediaSource.duration = newDuration;
  36861. } catch (e) {
  36862. videojs$1.log.warn('Failed to set media source duration', e);
  36863. }
  36864. _this6.tech_.trigger('durationchange');
  36865. _this6.mediaSource.removeEventListener('sourceopen', setDuration);
  36866. };
  36867. if (buffered.length > 0) {
  36868. newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
  36869. } // if the duration has changed, invalidate the cached value
  36870. if (oldDuration !== newDuration) {
  36871. // update the duration
  36872. if (this.mediaSource.readyState !== 'open') {
  36873. this.mediaSource.addEventListener('sourceopen', setDuration);
  36874. } else {
  36875. setDuration();
  36876. }
  36877. }
  36878. }
  36879. /**
  36880. * dispose of the MasterPlaylistController and everything
  36881. * that it controls
  36882. */
  36883. }, {
  36884. key: 'dispose',
  36885. value: function dispose() {
  36886. var _this7 = this;
  36887. this.decrypter_.terminate();
  36888. this.masterPlaylistLoader_.dispose();
  36889. this.mainSegmentLoader_.dispose();
  36890. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  36891. var groups = _this7.mediaTypes_[type].groups;
  36892. for (var id in groups) {
  36893. groups[id].forEach(function (group) {
  36894. if (group.playlistLoader) {
  36895. group.playlistLoader.dispose();
  36896. }
  36897. });
  36898. }
  36899. });
  36900. this.audioSegmentLoader_.dispose();
  36901. this.subtitleSegmentLoader_.dispose();
  36902. }
  36903. /**
  36904. * return the master playlist object if we have one
  36905. *
  36906. * @return {Object} the master playlist object that we parsed
  36907. */
  36908. }, {
  36909. key: 'master',
  36910. value: function master() {
  36911. return this.masterPlaylistLoader_.master;
  36912. }
  36913. /**
  36914. * return the currently selected playlist
  36915. *
  36916. * @return {Object} the currently selected playlist object that we parsed
  36917. */
  36918. }, {
  36919. key: 'media',
  36920. value: function media() {
  36921. // playlist loader will not return media if it has not been fully loaded
  36922. return this.masterPlaylistLoader_.media() || this.initialMedia_;
  36923. }
  36924. /**
  36925. * setup our internal source buffers on our segment Loaders
  36926. *
  36927. * @private
  36928. */
  36929. }, {
  36930. key: 'setupSourceBuffers_',
  36931. value: function setupSourceBuffers_() {
  36932. var media = this.masterPlaylistLoader_.media();
  36933. var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is
  36934. // attached
  36935. if (!media || this.mediaSource.readyState !== 'open') {
  36936. return;
  36937. }
  36938. mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media);
  36939. if (mimeTypes.length < 1) {
  36940. this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
  36941. return this.mediaSource.endOfStream('decode');
  36942. }
  36943. this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist
  36944. // selection
  36945. this.excludeIncompatibleVariants_(media);
  36946. }
  36947. }, {
  36948. key: 'configureLoaderMimeTypes_',
  36949. value: function configureLoaderMimeTypes_(mimeTypes) {
  36950. // If the content is demuxed, we can't start appending segments to a source buffer
  36951. // until both source buffers are set up, or else the browser may not let us add the
  36952. // second source buffer (it will assume we are playing either audio only or video
  36953. // only).
  36954. var sourceBufferEmitter = // If there is more than one mime type
  36955. mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio
  36956. mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio
  36957. // only with alternate audio)
  36958. mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer
  36959. new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only,
  36960. // video only, or muxed content.
  36961. null;
  36962. this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter);
  36963. if (mimeTypes[1]) {
  36964. this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter);
  36965. }
  36966. }
  36967. /**
  36968. * Blacklists playlists with codecs that are unsupported by the browser.
  36969. */
  36970. }, {
  36971. key: 'excludeUnsupportedVariants_',
  36972. value: function excludeUnsupportedVariants_() {
  36973. this.master().playlists.forEach(function (variant) {
  36974. if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) {
  36975. variant.excludeUntil = Infinity;
  36976. }
  36977. });
  36978. }
  36979. /**
  36980. * Blacklist playlists that are known to be codec or
  36981. * stream-incompatible with the SourceBuffer configuration. For
  36982. * instance, Media Source Extensions would cause the video element to
  36983. * stall waiting for video data if you switched from a variant with
  36984. * video and audio to an audio-only one.
  36985. *
  36986. * @param {Object} media a media playlist compatible with the current
  36987. * set of SourceBuffers. Variants in the current master playlist that
  36988. * do not appear to have compatible codec or stream configurations
  36989. * will be excluded from the default playlist selection algorithm
  36990. * indefinitely.
  36991. * @private
  36992. */
  36993. }, {
  36994. key: 'excludeIncompatibleVariants_',
  36995. value: function excludeIncompatibleVariants_(media) {
  36996. var codecCount = 2;
  36997. var videoCodec = null;
  36998. var codecs = void 0;
  36999. if (media.attributes.CODECS) {
  37000. codecs = parseCodecs(media.attributes.CODECS);
  37001. videoCodec = codecs.videoCodec;
  37002. codecCount = codecs.codecCount;
  37003. }
  37004. this.master().playlists.forEach(function (variant) {
  37005. var variantCodecs = {
  37006. codecCount: 2,
  37007. videoCodec: null
  37008. };
  37009. if (variant.attributes.CODECS) {
  37010. variantCodecs = parseCodecs(variant.attributes.CODECS);
  37011. } // if the streams differ in the presence or absence of audio or
  37012. // video, they are incompatible
  37013. if (variantCodecs.codecCount !== codecCount) {
  37014. variant.excludeUntil = Infinity;
  37015. } // if h.264 is specified on the current playlist, some flavor of
  37016. // it must be specified on all compatible variants
  37017. if (variantCodecs.videoCodec !== videoCodec) {
  37018. variant.excludeUntil = Infinity;
  37019. }
  37020. });
  37021. }
  37022. }, {
  37023. key: 'updateAdCues_',
  37024. value: function updateAdCues_(media) {
  37025. var offset = 0;
  37026. var seekable$$1 = this.seekable();
  37027. if (seekable$$1.length) {
  37028. offset = seekable$$1.start(0);
  37029. }
  37030. updateAdCues(media, this.cueTagsTrack_, offset);
  37031. }
  37032. /**
  37033. * Calculates the desired forward buffer length based on current time
  37034. *
  37035. * @return {Number} Desired forward buffer length in seconds
  37036. */
  37037. }, {
  37038. key: 'goalBufferLength',
  37039. value: function goalBufferLength() {
  37040. var currentTime = this.tech_.currentTime();
  37041. var initial = Config.GOAL_BUFFER_LENGTH;
  37042. var rate = Config.GOAL_BUFFER_LENGTH_RATE;
  37043. var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
  37044. return Math.min(initial + currentTime * rate, max);
  37045. }
  37046. /**
  37047. * Calculates the desired buffer low water line based on current time
  37048. *
  37049. * @return {Number} Desired buffer low water line in seconds
  37050. */
  37051. }, {
  37052. key: 'bufferLowWaterLine',
  37053. value: function bufferLowWaterLine() {
  37054. var currentTime = this.tech_.currentTime();
  37055. var initial = Config.BUFFER_LOW_WATER_LINE;
  37056. var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
  37057. var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
  37058. return Math.min(initial + currentTime * rate, max);
  37059. }
  37060. }]);
  37061. return MasterPlaylistController;
  37062. }(videojs$1.EventTarget);
  37063. /**
  37064. * Returns a function that acts as the Enable/disable playlist function.
  37065. *
  37066. * @param {PlaylistLoader} loader - The master playlist loader
  37067. * @param {String} playlistUri - uri of the playlist
  37068. * @param {Function} changePlaylistFn - A function to be called after a
  37069. * playlist's enabled-state has been changed. Will NOT be called if a
  37070. * playlist's enabled-state is unchanged
  37071. * @param {Boolean=} enable - Value to set the playlist enabled-state to
  37072. * or if undefined returns the current enabled-state for the playlist
  37073. * @return {Function} Function for setting/getting enabled
  37074. */
  37075. var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) {
  37076. return function (enable) {
  37077. var playlist = loader.master.playlists[playlistUri];
  37078. var incompatible = isIncompatible(playlist);
  37079. var currentlyEnabled = isEnabled(playlist);
  37080. if (typeof enable === 'undefined') {
  37081. return currentlyEnabled;
  37082. }
  37083. if (enable) {
  37084. delete playlist.disabled;
  37085. } else {
  37086. playlist.disabled = true;
  37087. }
  37088. if (enable !== currentlyEnabled && !incompatible) {
  37089. // Ensure the outside world knows about our changes
  37090. changePlaylistFn();
  37091. if (enable) {
  37092. loader.trigger('renditionenabled');
  37093. } else {
  37094. loader.trigger('renditiondisabled');
  37095. }
  37096. }
  37097. return enable;
  37098. };
  37099. };
  37100. /**
  37101. * The representation object encapsulates the publicly visible information
  37102. * in a media playlist along with a setter/getter-type function (enabled)
  37103. * for changing the enabled-state of a particular playlist entry
  37104. *
  37105. * @class Representation
  37106. */
  37107. var Representation = function Representation(hlsHandler, playlist, id) {
  37108. classCallCheck(this, Representation);
  37109. var mpc = hlsHandler.masterPlaylistController_,
  37110. smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
  37111. var changeType = smoothQualityChange ? 'smooth' : 'fast';
  37112. var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional
  37113. if (playlist.attributes.RESOLUTION) {
  37114. var resolution = playlist.attributes.RESOLUTION;
  37115. this.width = resolution.width;
  37116. this.height = resolution.height;
  37117. }
  37118. this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist
  37119. // within the master playlist
  37120. this.id = id; // Partially-apply the enableFunction to create a playlist-
  37121. // specific variant
  37122. this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction);
  37123. };
  37124. /**
  37125. * A mixin function that adds the `representations` api to an instance
  37126. * of the HlsHandler class
  37127. * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
  37128. * representation API into
  37129. */
  37130. var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
  37131. var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance
  37132. hlsHandler.representations = function () {
  37133. return playlists.master.playlists.filter(function (media) {
  37134. return !isIncompatible(media);
  37135. }).map(function (e, i) {
  37136. return new Representation(hlsHandler, e, e.uri);
  37137. });
  37138. };
  37139. };
  37140. /**
  37141. * @file playback-watcher.js
  37142. *
  37143. * Playback starts, and now my watch begins. It shall not end until my death. I shall
  37144. * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
  37145. * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
  37146. * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
  37147. * my life and honor to the Playback Watch, for this Player and all the Players to come.
  37148. */
  37149. // Set of events that reset the playback-watcher time check logic and clear the timeout
  37150. var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
  37151. /**
  37152. * @class PlaybackWatcher
  37153. */
  37154. var PlaybackWatcher = function () {
  37155. /**
  37156. * Represents an PlaybackWatcher object.
  37157. * @constructor
  37158. * @param {object} options an object that includes the tech and settings
  37159. */
  37160. function PlaybackWatcher(options) {
  37161. var _this = this;
  37162. classCallCheck(this, PlaybackWatcher);
  37163. this.tech_ = options.tech;
  37164. this.seekable = options.seekable;
  37165. this.seekTo = options.seekTo;
  37166. this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
  37167. this.media = options.media;
  37168. this.consecutiveUpdates = 0;
  37169. this.lastRecordedTime = null;
  37170. this.timer_ = null;
  37171. this.checkCurrentTimeTimeout_ = null;
  37172. this.logger_ = logger('PlaybackWatcher');
  37173. this.logger_('initialize');
  37174. var canPlayHandler = function canPlayHandler() {
  37175. return _this.monitorCurrentTime_();
  37176. };
  37177. var waitingHandler = function waitingHandler() {
  37178. return _this.techWaiting_();
  37179. };
  37180. var cancelTimerHandler = function cancelTimerHandler() {
  37181. return _this.cancelTimer_();
  37182. };
  37183. var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
  37184. return _this.fixesBadSeeks_();
  37185. };
  37186. this.tech_.on('seekablechanged', fixesBadSeeksHandler);
  37187. this.tech_.on('waiting', waitingHandler);
  37188. this.tech_.on(timerCancelEvents, cancelTimerHandler);
  37189. this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events
  37190. this.dispose = function () {
  37191. _this.logger_('dispose');
  37192. _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
  37193. _this.tech_.off('waiting', waitingHandler);
  37194. _this.tech_.off(timerCancelEvents, cancelTimerHandler);
  37195. _this.tech_.off('canplay', canPlayHandler);
  37196. if (_this.checkCurrentTimeTimeout_) {
  37197. window$1.clearTimeout(_this.checkCurrentTimeTimeout_);
  37198. }
  37199. _this.cancelTimer_();
  37200. };
  37201. }
  37202. /**
  37203. * Periodically check current time to see if playback stopped
  37204. *
  37205. * @private
  37206. */
  37207. createClass(PlaybackWatcher, [{
  37208. key: 'monitorCurrentTime_',
  37209. value: function monitorCurrentTime_() {
  37210. this.checkCurrentTime_();
  37211. if (this.checkCurrentTimeTimeout_) {
  37212. window$1.clearTimeout(this.checkCurrentTimeTimeout_);
  37213. } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  37214. this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
  37215. }
  37216. /**
  37217. * The purpose of this function is to emulate the "waiting" event on
  37218. * browsers that do not emit it when they are waiting for more
  37219. * data to continue playback
  37220. *
  37221. * @private
  37222. */
  37223. }, {
  37224. key: 'checkCurrentTime_',
  37225. value: function checkCurrentTime_() {
  37226. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  37227. this.consecutiveUpdates = 0;
  37228. this.lastRecordedTime = this.tech_.currentTime();
  37229. return;
  37230. }
  37231. if (this.tech_.paused() || this.tech_.seeking()) {
  37232. return;
  37233. }
  37234. var currentTime = this.tech_.currentTime();
  37235. var buffered = this.tech_.buffered();
  37236. if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
  37237. // If current time is at the end of the final buffered region, then any playback
  37238. // stall is most likely caused by buffering in a low bandwidth environment. The tech
  37239. // should fire a `waiting` event in this scenario, but due to browser and tech
  37240. // inconsistencies. Calling `techWaiting_` here allows us to simulate
  37241. // responding to a native `waiting` event when the tech fails to emit one.
  37242. return this.techWaiting_();
  37243. }
  37244. if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
  37245. this.consecutiveUpdates++;
  37246. this.waiting_();
  37247. } else if (currentTime === this.lastRecordedTime) {
  37248. this.consecutiveUpdates++;
  37249. } else {
  37250. this.consecutiveUpdates = 0;
  37251. this.lastRecordedTime = currentTime;
  37252. }
  37253. }
  37254. /**
  37255. * Cancels any pending timers and resets the 'timeupdate' mechanism
  37256. * designed to detect that we are stalled
  37257. *
  37258. * @private
  37259. */
  37260. }, {
  37261. key: 'cancelTimer_',
  37262. value: function cancelTimer_() {
  37263. this.consecutiveUpdates = 0;
  37264. if (this.timer_) {
  37265. this.logger_('cancelTimer_');
  37266. clearTimeout(this.timer_);
  37267. }
  37268. this.timer_ = null;
  37269. }
  37270. /**
  37271. * Fixes situations where there's a bad seek
  37272. *
  37273. * @return {Boolean} whether an action was taken to fix the seek
  37274. * @private
  37275. */
  37276. }, {
  37277. key: 'fixesBadSeeks_',
  37278. value: function fixesBadSeeks_() {
  37279. var seeking = this.tech_.seeking();
  37280. if (!seeking) {
  37281. return false;
  37282. }
  37283. var seekable = this.seekable();
  37284. var currentTime = this.tech_.currentTime();
  37285. var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
  37286. var seekTo = void 0;
  37287. if (isAfterSeekableRange) {
  37288. var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
  37289. seekTo = seekableEnd;
  37290. }
  37291. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  37292. var seekableStart = seekable.start(0); // sync to the beginning of the live window
  37293. // provide a buffer of .1 seconds to handle rounding/imprecise numbers
  37294. seekTo = seekableStart + SAFE_TIME_DELTA;
  37295. }
  37296. if (typeof seekTo !== 'undefined') {
  37297. this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.'));
  37298. this.seekTo(seekTo);
  37299. return true;
  37300. }
  37301. return false;
  37302. }
  37303. /**
  37304. * Handler for situations when we determine the player is waiting.
  37305. *
  37306. * @private
  37307. */
  37308. }, {
  37309. key: 'waiting_',
  37310. value: function waiting_() {
  37311. if (this.techWaiting_()) {
  37312. return;
  37313. } // All tech waiting checks failed. Use last resort correction
  37314. var currentTime = this.tech_.currentTime();
  37315. var buffered = this.tech_.buffered();
  37316. var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
  37317. // region with no indication that anything is amiss (seen in Firefox). Seeking to
  37318. // currentTime is usually enough to kickstart the player. This checks that the player
  37319. // is currently within a buffered region before attempting a corrective seek.
  37320. // Chrome does not appear to continue `timeupdate` events after a `waiting` event
  37321. // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
  37322. // make sure there is ~3 seconds of forward buffer before taking any corrective action
  37323. // to avoid triggering an `unknownwaiting` event when the network is slow.
  37324. if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
  37325. this.cancelTimer_();
  37326. this.seekTo(currentTime);
  37327. this.logger_('Stopped at ' + currentTime + ' while inside a buffered region ' + ('[' + currentRange.start(0) + ' -> ' + currentRange.end(0) + ']. Attempting to resume ') + 'playback by seeking to the current time.'); // unknown waiting corrections may be useful for monitoring QoS
  37328. this.tech_.trigger({
  37329. type: 'usage',
  37330. name: 'hls-unknown-waiting'
  37331. });
  37332. return;
  37333. }
  37334. }
  37335. /**
  37336. * Handler for situations when the tech fires a `waiting` event
  37337. *
  37338. * @return {Boolean}
  37339. * True if an action (or none) was needed to correct the waiting. False if no
  37340. * checks passed
  37341. * @private
  37342. */
  37343. }, {
  37344. key: 'techWaiting_',
  37345. value: function techWaiting_() {
  37346. var seekable = this.seekable();
  37347. var currentTime = this.tech_.currentTime();
  37348. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  37349. // Tech is seeking or bad seek fixed, no action needed
  37350. return true;
  37351. }
  37352. if (this.tech_.seeking() || this.timer_ !== null) {
  37353. // Tech is seeking or already waiting on another action, no action needed
  37354. return true;
  37355. }
  37356. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  37357. var livePoint = seekable.end(seekable.length - 1);
  37358. this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint));
  37359. this.cancelTimer_();
  37360. this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS
  37361. this.tech_.trigger({
  37362. type: 'usage',
  37363. name: 'hls-live-resync'
  37364. });
  37365. return true;
  37366. }
  37367. var buffered = this.tech_.buffered();
  37368. var nextRange = findNextRange(buffered, currentTime);
  37369. if (this.videoUnderflow_(nextRange, buffered, currentTime)) {
  37370. // Even though the video underflowed and was stuck in a gap, the audio overplayed
  37371. // the gap, leading currentTime into a buffered range. Seeking to currentTime
  37372. // allows the video to catch up to the audio position without losing any audio
  37373. // (only suffering ~3 seconds of frozen video and a pause in audio playback).
  37374. this.cancelTimer_();
  37375. this.seekTo(currentTime); // video underflow may be useful for monitoring QoS
  37376. this.tech_.trigger({
  37377. type: 'usage',
  37378. name: 'hls-video-underflow'
  37379. });
  37380. return true;
  37381. } // check for gap
  37382. if (nextRange.length > 0) {
  37383. var difference = nextRange.start(0) - currentTime;
  37384. this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0)));
  37385. this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
  37386. return true;
  37387. } // All checks failed. Returning false to indicate failure to correct waiting
  37388. return false;
  37389. }
  37390. }, {
  37391. key: 'afterSeekableWindow_',
  37392. value: function afterSeekableWindow_(seekable, currentTime, playlist) {
  37393. var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
  37394. if (!seekable.length) {
  37395. // we can't make a solid case if there's no seekable, default to false
  37396. return false;
  37397. }
  37398. var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
  37399. var isLive = !playlist.endList;
  37400. if (isLive && allowSeeksWithinUnsafeLiveWindow) {
  37401. allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
  37402. }
  37403. if (currentTime > allowedEnd) {
  37404. return true;
  37405. }
  37406. return false;
  37407. }
  37408. }, {
  37409. key: 'beforeSeekableWindow_',
  37410. value: function beforeSeekableWindow_(seekable, currentTime) {
  37411. if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
  37412. seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
  37413. return true;
  37414. }
  37415. return false;
  37416. }
  37417. }, {
  37418. key: 'videoUnderflow_',
  37419. value: function videoUnderflow_(nextRange, buffered, currentTime) {
  37420. if (nextRange.length === 0) {
  37421. // Even if there is no available next range, there is still a possibility we are
  37422. // stuck in a gap due to video underflow.
  37423. var gap = this.gapFromVideoUnderflow_(buffered, currentTime);
  37424. if (gap) {
  37425. this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime));
  37426. return true;
  37427. }
  37428. }
  37429. return false;
  37430. }
  37431. /**
  37432. * Timer callback. If playback still has not proceeded, then we seek
  37433. * to the start of the next buffered region.
  37434. *
  37435. * @private
  37436. */
  37437. }, {
  37438. key: 'skipTheGap_',
  37439. value: function skipTheGap_(scheduledCurrentTime) {
  37440. var buffered = this.tech_.buffered();
  37441. var currentTime = this.tech_.currentTime();
  37442. var nextRange = findNextRange(buffered, currentTime);
  37443. this.cancelTimer_();
  37444. if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
  37445. return;
  37446. }
  37447. this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
  37448. this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR);
  37449. this.tech_.trigger({
  37450. type: 'usage',
  37451. name: 'hls-gap-skip'
  37452. });
  37453. }
  37454. }, {
  37455. key: 'gapFromVideoUnderflow_',
  37456. value: function gapFromVideoUnderflow_(buffered, currentTime) {
  37457. // At least in Chrome, if there is a gap in the video buffer, the audio will continue
  37458. // playing for ~3 seconds after the video gap starts. This is done to account for
  37459. // video buffer underflow/underrun (note that this is not done when there is audio
  37460. // buffer underflow/underrun -- in that case the video will stop as soon as it
  37461. // encounters the gap, as audio stalls are more noticeable/jarring to a user than
  37462. // video stalls). The player's time will reflect the playthrough of audio, so the
  37463. // time will appear as if we are in a buffered region, even if we are stuck in a
  37464. // "gap."
  37465. //
  37466. // Example:
  37467. // video buffer: 0 => 10.1, 10.2 => 20
  37468. // audio buffer: 0 => 20
  37469. // overall buffer: 0 => 10.1, 10.2 => 20
  37470. // current time: 13
  37471. //
  37472. // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
  37473. // however, the audio continued playing until it reached ~3 seconds past the gap
  37474. // (13 seconds), at which point it stops as well. Since current time is past the
  37475. // gap, findNextRange will return no ranges.
  37476. //
  37477. // To check for this issue, we see if there is a gap that starts somewhere within
  37478. // a 3 second range (3 seconds +/- 1 second) back from our current time.
  37479. var gaps = findGaps(buffered);
  37480. for (var i = 0; i < gaps.length; i++) {
  37481. var start = gaps.start(i);
  37482. var end = gaps.end(i); // gap is starts no more than 4 seconds back
  37483. if (currentTime - start < 4 && currentTime - start > 2) {
  37484. return {
  37485. start: start,
  37486. end: end
  37487. };
  37488. }
  37489. }
  37490. return null;
  37491. }
  37492. }]);
  37493. return PlaybackWatcher;
  37494. }();
  37495. var defaultOptions = {
  37496. errorInterval: 30,
  37497. getSource: function getSource(next) {
  37498. var tech = this.tech({
  37499. IWillNotUseThisInPlugins: true
  37500. });
  37501. var sourceObj = tech.currentSource_;
  37502. return next(sourceObj);
  37503. }
  37504. };
  37505. /**
  37506. * Main entry point for the plugin
  37507. *
  37508. * @param {Player} player a reference to a videojs Player instance
  37509. * @param {Object} [options] an object with plugin options
  37510. * @private
  37511. */
  37512. var initPlugin = function initPlugin(player, options) {
  37513. var lastCalled = 0;
  37514. var seekTo = 0;
  37515. var localOptions = videojs$1.mergeOptions(defaultOptions, options);
  37516. player.ready(function () {
  37517. player.trigger({
  37518. type: 'usage',
  37519. name: 'hls-error-reload-initialized'
  37520. });
  37521. });
  37522. /**
  37523. * Player modifications to perform that must wait until `loadedmetadata`
  37524. * has been triggered
  37525. *
  37526. * @private
  37527. */
  37528. var loadedMetadataHandler = function loadedMetadataHandler() {
  37529. if (seekTo) {
  37530. player.currentTime(seekTo);
  37531. }
  37532. };
  37533. /**
  37534. * Set the source on the player element, play, and seek if necessary
  37535. *
  37536. * @param {Object} sourceObj An object specifying the source url and mime-type to play
  37537. * @private
  37538. */
  37539. var setSource = function setSource(sourceObj) {
  37540. if (sourceObj === null || sourceObj === undefined) {
  37541. return;
  37542. }
  37543. seekTo = player.duration() !== Infinity && player.currentTime() || 0;
  37544. player.one('loadedmetadata', loadedMetadataHandler);
  37545. player.src(sourceObj);
  37546. player.trigger({
  37547. type: 'usage',
  37548. name: 'hls-error-reload'
  37549. });
  37550. player.play();
  37551. };
  37552. /**
  37553. * Attempt to get a source from either the built-in getSource function
  37554. * or a custom function provided via the options
  37555. *
  37556. * @private
  37557. */
  37558. var errorHandler = function errorHandler() {
  37559. // Do not attempt to reload the source if a source-reload occurred before
  37560. // 'errorInterval' time has elapsed since the last source-reload
  37561. if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
  37562. player.trigger({
  37563. type: 'usage',
  37564. name: 'hls-error-reload-canceled'
  37565. });
  37566. return;
  37567. }
  37568. if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
  37569. videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
  37570. return;
  37571. }
  37572. lastCalled = Date.now();
  37573. return localOptions.getSource.call(player, setSource);
  37574. };
  37575. /**
  37576. * Unbind any event handlers that were bound by the plugin
  37577. *
  37578. * @private
  37579. */
  37580. var cleanupEvents = function cleanupEvents() {
  37581. player.off('loadedmetadata', loadedMetadataHandler);
  37582. player.off('error', errorHandler);
  37583. player.off('dispose', cleanupEvents);
  37584. };
  37585. /**
  37586. * Cleanup before re-initializing the plugin
  37587. *
  37588. * @param {Object} [newOptions] an object with plugin options
  37589. * @private
  37590. */
  37591. var reinitPlugin = function reinitPlugin(newOptions) {
  37592. cleanupEvents();
  37593. initPlugin(player, newOptions);
  37594. };
  37595. player.on('error', errorHandler);
  37596. player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
  37597. // initializing the plugin
  37598. player.reloadSourceOnError = reinitPlugin;
  37599. };
  37600. /**
  37601. * Reload the source when an error is detected as long as there
  37602. * wasn't an error previously within the last 30 seconds
  37603. *
  37604. * @param {Object} [options] an object with plugin options
  37605. */
  37606. var reloadSourceOnError = function reloadSourceOnError(options) {
  37607. initPlugin(this, options);
  37608. };
  37609. var version$1 = "1.10.1"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all
  37610. videojs$1.use('*', function (player) {
  37611. return {
  37612. setSource: function setSource(srcObj, next) {
  37613. // pass null as the first argument to indicate that the source is not rejected
  37614. next(null, srcObj);
  37615. },
  37616. // VHS needs to know when seeks happen. For external seeks (generated at the player
  37617. // level), this middleware will capture the action. For internal seeks (generated at
  37618. // the tech level), we use a wrapped function so that we can handle it on our own
  37619. // (specified elsewhere).
  37620. setCurrentTime: function setCurrentTime(time) {
  37621. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  37622. player.vhs.setCurrentTime(time);
  37623. }
  37624. return time;
  37625. },
  37626. // Sync VHS after play requests.
  37627. // This specifically handles replay where the order of actions is
  37628. // play, video element will seek to 0 (skipping the setCurrentTime middleware)
  37629. // then triggers a play event.
  37630. play: function play() {
  37631. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  37632. player.vhs.setCurrentTime(player.tech_.currentTime());
  37633. }
  37634. }
  37635. };
  37636. });
  37637. /**
  37638. * @file videojs-http-streaming.js
  37639. *
  37640. * The main file for the HLS project.
  37641. * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE
  37642. */
  37643. var Hls$1 = {
  37644. PlaylistLoader: PlaylistLoader,
  37645. Playlist: Playlist,
  37646. Decrypter: Decrypter,
  37647. AsyncStream: AsyncStream,
  37648. decrypt: decrypt,
  37649. utils: utils,
  37650. STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
  37651. INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
  37652. comparePlaylistBandwidth: comparePlaylistBandwidth,
  37653. comparePlaylistResolution: comparePlaylistResolution,
  37654. xhr: xhrFactory()
  37655. }; // Define getter/setters for config properites
  37656. ['GOAL_BUFFER_LENGTH', 'MAX_GOAL_BUFFER_LENGTH', 'GOAL_BUFFER_LENGTH_RATE', 'BUFFER_LOW_WATER_LINE', 'MAX_BUFFER_LOW_WATER_LINE', 'BUFFER_LOW_WATER_LINE_RATE', 'BANDWIDTH_VARIANCE'].forEach(function (prop) {
  37657. Object.defineProperty(Hls$1, prop, {
  37658. get: function get$$1() {
  37659. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  37660. return Config[prop];
  37661. },
  37662. set: function set$$1(value) {
  37663. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  37664. if (typeof value !== 'number' || value < 0) {
  37665. videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0');
  37666. return;
  37667. }
  37668. Config[prop] = value;
  37669. }
  37670. });
  37671. });
  37672. var LOCAL_STORAGE_KEY$1 = 'videojs-vhs';
  37673. var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
  37674. var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
  37675. if (mpegurlRE.test(type)) {
  37676. return 'hls';
  37677. }
  37678. var dashRE = /^application\/dash\+xml/i;
  37679. if (dashRE.test(type)) {
  37680. return 'dash';
  37681. }
  37682. return null;
  37683. };
  37684. /**
  37685. * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls.
  37686. *
  37687. * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
  37688. * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
  37689. * @function handleHlsMediaChange
  37690. */
  37691. var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) {
  37692. var newPlaylist = playlistLoader.media();
  37693. var selectedIndex = -1;
  37694. for (var i = 0; i < qualityLevels.length; i++) {
  37695. if (qualityLevels[i].id === newPlaylist.uri) {
  37696. selectedIndex = i;
  37697. break;
  37698. }
  37699. }
  37700. qualityLevels.selectedIndex_ = selectedIndex;
  37701. qualityLevels.trigger({
  37702. selectedIndex: selectedIndex,
  37703. type: 'change'
  37704. });
  37705. };
  37706. /**
  37707. * Adds quality levels to list once playlist metadata is available
  37708. *
  37709. * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
  37710. * @param {Object} hls Hls object to listen to for media events.
  37711. * @function handleHlsLoadedMetadata
  37712. */
  37713. var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
  37714. hls.representations().forEach(function (rep) {
  37715. qualityLevels.addQualityLevel(rep);
  37716. });
  37717. handleHlsMediaChange(qualityLevels, hls.playlists);
  37718. }; // HLS is a source handler, not a tech. Make sure attempts to use it
  37719. // as one do not cause exceptions.
  37720. Hls$1.canPlaySource = function () {
  37721. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  37722. };
  37723. var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) {
  37724. if (!keySystemOptions) {
  37725. return keySystemOptions;
  37726. } // upsert the content types based on the selected playlist
  37727. var keySystemContentTypes = {};
  37728. for (var keySystem in keySystemOptions) {
  37729. keySystemContentTypes[keySystem] = {
  37730. audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"',
  37731. videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"'
  37732. };
  37733. if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
  37734. keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
  37735. } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
  37736. // so we need to prevent overwriting the URL entirely
  37737. if (typeof keySystemOptions[keySystem] === 'string') {
  37738. keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
  37739. }
  37740. }
  37741. return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
  37742. };
  37743. var setupEmeOptions = function setupEmeOptions(hlsHandler) {
  37744. if (hlsHandler.options_.sourceType !== 'dash') {
  37745. return;
  37746. }
  37747. var player = videojs$1.players[hlsHandler.tech_.options_.playerId];
  37748. if (player.eme) {
  37749. var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media());
  37750. if (sourceOptions) {
  37751. player.currentSource().keySystems = sourceOptions; // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449
  37752. if (player.eme.initializeMediaKeys) {
  37753. player.eme.initializeMediaKeys();
  37754. }
  37755. }
  37756. }
  37757. };
  37758. var getVhsLocalStorage = function getVhsLocalStorage() {
  37759. if (!window.localStorage) {
  37760. return null;
  37761. }
  37762. var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1);
  37763. if (!storedObject) {
  37764. return null;
  37765. }
  37766. try {
  37767. return JSON.parse(storedObject);
  37768. } catch (e) {
  37769. // someone may have tampered with the value
  37770. return null;
  37771. }
  37772. };
  37773. var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
  37774. if (!window.localStorage) {
  37775. return false;
  37776. }
  37777. var objectToStore = getVhsLocalStorage();
  37778. objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options;
  37779. try {
  37780. window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore));
  37781. } catch (e) {
  37782. // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
  37783. // storage is set to 0).
  37784. // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
  37785. // No need to perform any operation.
  37786. return false;
  37787. }
  37788. return objectToStore;
  37789. };
  37790. /**
  37791. * Whether the browser has built-in HLS support.
  37792. */
  37793. Hls$1.supportsNativeHls = function () {
  37794. var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
  37795. if (!videojs$1.getTech('Html5').isSupported()) {
  37796. return false;
  37797. } // HLS manifests can go by many mime-types
  37798. var canPlay = [// Apple santioned
  37799. 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
  37800. 'audio/mpegurl', // Very common
  37801. 'audio/x-mpegurl', // Very common
  37802. 'application/x-mpegurl', // Included for completeness
  37803. 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
  37804. return canPlay.some(function (canItPlay) {
  37805. return /maybe|probably/i.test(video.canPlayType(canItPlay));
  37806. });
  37807. }();
  37808. Hls$1.supportsNativeDash = function () {
  37809. if (!videojs$1.getTech('Html5').isSupported()) {
  37810. return false;
  37811. }
  37812. return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
  37813. }();
  37814. Hls$1.supportsTypeNatively = function (type) {
  37815. if (type === 'hls') {
  37816. return Hls$1.supportsNativeHls;
  37817. }
  37818. if (type === 'dash') {
  37819. return Hls$1.supportsNativeDash;
  37820. }
  37821. return false;
  37822. };
  37823. /**
  37824. * HLS is a source handler, not a tech. Make sure attempts to use it
  37825. * as one do not cause exceptions.
  37826. */
  37827. Hls$1.isSupported = function () {
  37828. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  37829. };
  37830. var Component$1 = videojs$1.getComponent('Component');
  37831. /**
  37832. * The Hls Handler object, where we orchestrate all of the parts
  37833. * of HLS to interact with video.js
  37834. *
  37835. * @class HlsHandler
  37836. * @extends videojs.Component
  37837. * @param {Object} source the soruce object
  37838. * @param {Tech} tech the parent tech object
  37839. * @param {Object} options optional and required options
  37840. */
  37841. var HlsHandler = function (_Component) {
  37842. inherits(HlsHandler, _Component);
  37843. function HlsHandler(source, tech, options) {
  37844. classCallCheck(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for
  37845. // backwards-compatibility
  37846. var _this = possibleConstructorReturn(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls));
  37847. if (tech.options_ && tech.options_.playerId) {
  37848. var _player = videojs$1(tech.options_.playerId);
  37849. if (!_player.hasOwnProperty('hls')) {
  37850. Object.defineProperty(_player, 'hls', {
  37851. get: function get$$1() {
  37852. videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.');
  37853. tech.trigger({
  37854. type: 'usage',
  37855. name: 'hls-player-access'
  37856. });
  37857. return _this;
  37858. },
  37859. configurable: true
  37860. });
  37861. } // Set up a reference to the HlsHandler from player.vhs. This allows users to start
  37862. // migrating from player.tech_.hls... to player.vhs... for API access. Although this
  37863. // isn't the most appropriate form of reference for video.js (since all APIs should
  37864. // be provided through core video.js), it is a common pattern for plugins, and vhs
  37865. // will act accordingly.
  37866. _player.vhs = _this; // deprecated, for backwards compatibility
  37867. _player.dash = _this;
  37868. _this.player_ = _player;
  37869. }
  37870. _this.tech_ = tech;
  37871. _this.source_ = source;
  37872. _this.stats = {};
  37873. _this.setOptions_();
  37874. if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
  37875. tech.overrideNativeAudioTracks(true);
  37876. tech.overrideNativeVideoTracks(true);
  37877. } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
  37878. // overriding native HLS only works if audio tracks have been emulated
  37879. // error early if we're misconfigured
  37880. throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
  37881. } // listen for fullscreenchange events for this player so that we
  37882. // can adjust our quality selection quickly
  37883. _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
  37884. var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
  37885. if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
  37886. _this.masterPlaylistController_.smoothQualityChange_();
  37887. }
  37888. }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech
  37889. _this.on(_this.tech_, 'seeking', function () {
  37890. if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) {
  37891. this.setCurrentTime(0);
  37892. }
  37893. });
  37894. _this.on(_this.tech_, 'error', function () {
  37895. if (this.masterPlaylistController_) {
  37896. this.masterPlaylistController_.pauseLoading();
  37897. }
  37898. });
  37899. _this.on(_this.tech_, 'play', _this.play);
  37900. return _this;
  37901. }
  37902. createClass(HlsHandler, [{
  37903. key: 'setOptions_',
  37904. value: function setOptions_() {
  37905. var _this2 = this; // defaults
  37906. this.options_.withCredentials = this.options_.withCredentials || false;
  37907. this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false;
  37908. this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
  37909. this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
  37910. this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
  37911. this.options_.customTagParsers = this.options_.customTagParsers || [];
  37912. this.options_.customTagMappers = this.options_.customTagMappers || [];
  37913. this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
  37914. if (typeof this.options_.blacklistDuration !== 'number') {
  37915. this.options_.blacklistDuration = 5 * 60;
  37916. }
  37917. if (typeof this.options_.bandwidth !== 'number') {
  37918. if (this.options_.useBandwidthFromLocalStorage) {
  37919. var storedObject = getVhsLocalStorage();
  37920. if (storedObject && storedObject.bandwidth) {
  37921. this.options_.bandwidth = storedObject.bandwidth;
  37922. this.tech_.trigger({
  37923. type: 'usage',
  37924. name: 'hls-bandwidth-from-local-storage'
  37925. });
  37926. }
  37927. if (storedObject && storedObject.throughput) {
  37928. this.options_.throughput = storedObject.throughput;
  37929. this.tech_.trigger({
  37930. type: 'usage',
  37931. name: 'hls-throughput-from-local-storage'
  37932. });
  37933. }
  37934. }
  37935. } // if bandwidth was not set by options or pulled from local storage, start playlist
  37936. // selection at a reasonable bandwidth
  37937. if (typeof this.options_.bandwidth !== 'number') {
  37938. this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
  37939. } // If the bandwidth number is unchanged from the initial setting
  37940. // then this takes precedence over the enableLowInitialPlaylist option
  37941. this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
  37942. ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys'].forEach(function (option) {
  37943. if (typeof _this2.source_[option] !== 'undefined') {
  37944. _this2.options_[option] = _this2.source_[option];
  37945. }
  37946. });
  37947. this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
  37948. }
  37949. /**
  37950. * called when player.src gets called, handle a new source
  37951. *
  37952. * @param {Object} src the source object to handle
  37953. */
  37954. }, {
  37955. key: 'src',
  37956. value: function src(_src, type) {
  37957. var _this3 = this; // do nothing if the src is falsey
  37958. if (!_src) {
  37959. return;
  37960. }
  37961. this.setOptions_(); // add master playlist controller options
  37962. this.options_.url = this.source_.src;
  37963. this.options_.tech = this.tech_;
  37964. this.options_.externHls = Hls$1;
  37965. this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own
  37966. // setCurrentTime function. This is needed because "seeking" events aren't always
  37967. // reliable. External seeks (via the player object) are handled via middleware.
  37968. this.options_.seekTo = function (time) {
  37969. _this3.tech_.setCurrentTime(time);
  37970. _this3.setCurrentTime(time);
  37971. };
  37972. this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
  37973. this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
  37974. seekable: function seekable$$1() {
  37975. return _this3.seekable();
  37976. },
  37977. media: function media() {
  37978. return _this3.masterPlaylistController_.media();
  37979. }
  37980. }));
  37981. this.masterPlaylistController_.on('error', function () {
  37982. var player = videojs$1.players[_this3.tech_.options_.playerId];
  37983. player.error(_this3.masterPlaylistController_.error);
  37984. }); // `this` in selectPlaylist should be the HlsHandler for backwards
  37985. // compatibility with < v2
  37986. this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
  37987. this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
  37988. this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
  37989. this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
  37990. // controller. Using a custom property for backwards compatibility
  37991. // with < v2
  37992. Object.defineProperties(this, {
  37993. selectPlaylist: {
  37994. get: function get$$1() {
  37995. return this.masterPlaylistController_.selectPlaylist;
  37996. },
  37997. set: function set$$1(selectPlaylist) {
  37998. this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
  37999. }
  38000. },
  38001. throughput: {
  38002. get: function get$$1() {
  38003. return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
  38004. },
  38005. set: function set$$1(throughput) {
  38006. this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
  38007. // for the cumulative average
  38008. this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
  38009. }
  38010. },
  38011. bandwidth: {
  38012. get: function get$$1() {
  38013. return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
  38014. },
  38015. set: function set$$1(bandwidth) {
  38016. this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
  38017. // `count` is set to zero that current value of `rate` isn't included
  38018. // in the cumulative average
  38019. this.masterPlaylistController_.mainSegmentLoader_.throughput = {
  38020. rate: 0,
  38021. count: 0
  38022. };
  38023. }
  38024. },
  38025. /**
  38026. * `systemBandwidth` is a combination of two serial processes bit-rates. The first
  38027. * is the network bitrate provided by `bandwidth` and the second is the bitrate of
  38028. * the entire process after that - decryption, transmuxing, and appending - provided
  38029. * by `throughput`.
  38030. *
  38031. * Since the two process are serial, the overall system bandwidth is given by:
  38032. * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
  38033. */
  38034. systemBandwidth: {
  38035. get: function get$$1() {
  38036. var invBandwidth = 1 / (this.bandwidth || 1);
  38037. var invThroughput = void 0;
  38038. if (this.throughput > 0) {
  38039. invThroughput = 1 / this.throughput;
  38040. } else {
  38041. invThroughput = 0;
  38042. }
  38043. var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
  38044. return systemBitrate;
  38045. },
  38046. set: function set$$1() {
  38047. videojs$1.log.error('The "systemBandwidth" property is read-only');
  38048. }
  38049. }
  38050. });
  38051. if (this.options_.bandwidth) {
  38052. this.bandwidth = this.options_.bandwidth;
  38053. }
  38054. if (this.options_.throughput) {
  38055. this.throughput = this.options_.throughput;
  38056. }
  38057. Object.defineProperties(this.stats, {
  38058. bandwidth: {
  38059. get: function get$$1() {
  38060. return _this3.bandwidth || 0;
  38061. },
  38062. enumerable: true
  38063. },
  38064. mediaRequests: {
  38065. get: function get$$1() {
  38066. return _this3.masterPlaylistController_.mediaRequests_() || 0;
  38067. },
  38068. enumerable: true
  38069. },
  38070. mediaRequestsAborted: {
  38071. get: function get$$1() {
  38072. return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
  38073. },
  38074. enumerable: true
  38075. },
  38076. mediaRequestsTimedout: {
  38077. get: function get$$1() {
  38078. return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
  38079. },
  38080. enumerable: true
  38081. },
  38082. mediaRequestsErrored: {
  38083. get: function get$$1() {
  38084. return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
  38085. },
  38086. enumerable: true
  38087. },
  38088. mediaTransferDuration: {
  38089. get: function get$$1() {
  38090. return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
  38091. },
  38092. enumerable: true
  38093. },
  38094. mediaBytesTransferred: {
  38095. get: function get$$1() {
  38096. return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
  38097. },
  38098. enumerable: true
  38099. },
  38100. mediaSecondsLoaded: {
  38101. get: function get$$1() {
  38102. return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
  38103. },
  38104. enumerable: true
  38105. },
  38106. buffered: {
  38107. get: function get$$1() {
  38108. return timeRangesToArray(_this3.tech_.buffered());
  38109. },
  38110. enumerable: true
  38111. },
  38112. currentTime: {
  38113. get: function get$$1() {
  38114. return _this3.tech_.currentTime();
  38115. },
  38116. enumerable: true
  38117. },
  38118. currentSource: {
  38119. get: function get$$1() {
  38120. return _this3.tech_.currentSource_;
  38121. },
  38122. enumerable: true
  38123. },
  38124. currentTech: {
  38125. get: function get$$1() {
  38126. return _this3.tech_.name_;
  38127. },
  38128. enumerable: true
  38129. },
  38130. duration: {
  38131. get: function get$$1() {
  38132. return _this3.tech_.duration();
  38133. },
  38134. enumerable: true
  38135. },
  38136. master: {
  38137. get: function get$$1() {
  38138. return _this3.playlists.master;
  38139. },
  38140. enumerable: true
  38141. },
  38142. playerDimensions: {
  38143. get: function get$$1() {
  38144. return _this3.tech_.currentDimensions();
  38145. },
  38146. enumerable: true
  38147. },
  38148. seekable: {
  38149. get: function get$$1() {
  38150. return timeRangesToArray(_this3.tech_.seekable());
  38151. },
  38152. enumerable: true
  38153. },
  38154. timestamp: {
  38155. get: function get$$1() {
  38156. return Date.now();
  38157. },
  38158. enumerable: true
  38159. },
  38160. videoPlaybackQuality: {
  38161. get: function get$$1() {
  38162. return _this3.tech_.getVideoPlaybackQuality();
  38163. },
  38164. enumerable: true
  38165. }
  38166. });
  38167. this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
  38168. this.tech_.on('bandwidthupdate', function () {
  38169. if (_this3.options_.useBandwidthFromLocalStorage) {
  38170. updateVhsLocalStorage({
  38171. bandwidth: _this3.bandwidth,
  38172. throughput: Math.round(_this3.throughput)
  38173. });
  38174. }
  38175. });
  38176. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  38177. // Add the manual rendition mix-in to HlsHandler
  38178. renditionSelectionMixin(_this3);
  38179. setupEmeOptions(_this3);
  38180. }); // the bandwidth of the primary segment loader is our best
  38181. // estimate of overall bandwidth
  38182. this.on(this.masterPlaylistController_, 'progress', function () {
  38183. this.tech_.trigger('progress');
  38184. });
  38185. this.tech_.ready(function () {
  38186. return _this3.setupQualityLevels_();
  38187. }); // do nothing if the tech has been disposed already
  38188. // this can occur if someone sets the src in player.ready(), for instance
  38189. if (!this.tech_.el()) {
  38190. return;
  38191. }
  38192. this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource));
  38193. }
  38194. /**
  38195. * Initializes the quality levels and sets listeners to update them.
  38196. *
  38197. * @method setupQualityLevels_
  38198. * @private
  38199. */
  38200. }, {
  38201. key: 'setupQualityLevels_',
  38202. value: function setupQualityLevels_() {
  38203. var _this4 = this;
  38204. var player = videojs$1.players[this.tech_.options_.playerId];
  38205. if (player && player.qualityLevels) {
  38206. this.qualityLevels_ = player.qualityLevels();
  38207. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  38208. handleHlsLoadedMetadata(_this4.qualityLevels_, _this4);
  38209. });
  38210. this.playlists.on('mediachange', function () {
  38211. handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists);
  38212. });
  38213. }
  38214. }
  38215. /**
  38216. * Begin playing the video.
  38217. */
  38218. }, {
  38219. key: 'play',
  38220. value: function play() {
  38221. this.masterPlaylistController_.play();
  38222. }
  38223. /**
  38224. * a wrapper around the function in MasterPlaylistController
  38225. */
  38226. }, {
  38227. key: 'setCurrentTime',
  38228. value: function setCurrentTime(currentTime) {
  38229. this.masterPlaylistController_.setCurrentTime(currentTime);
  38230. }
  38231. /**
  38232. * a wrapper around the function in MasterPlaylistController
  38233. */
  38234. }, {
  38235. key: 'duration',
  38236. value: function duration$$1() {
  38237. return this.masterPlaylistController_.duration();
  38238. }
  38239. /**
  38240. * a wrapper around the function in MasterPlaylistController
  38241. */
  38242. }, {
  38243. key: 'seekable',
  38244. value: function seekable$$1() {
  38245. return this.masterPlaylistController_.seekable();
  38246. }
  38247. /**
  38248. * Abort all outstanding work and cleanup.
  38249. */
  38250. }, {
  38251. key: 'dispose',
  38252. value: function dispose() {
  38253. if (this.playbackWatcher_) {
  38254. this.playbackWatcher_.dispose();
  38255. }
  38256. if (this.masterPlaylistController_) {
  38257. this.masterPlaylistController_.dispose();
  38258. }
  38259. if (this.qualityLevels_) {
  38260. this.qualityLevels_.dispose();
  38261. }
  38262. if (this.player_) {
  38263. delete this.player_.vhs;
  38264. delete this.player_.dash;
  38265. delete this.player_.hls;
  38266. }
  38267. if (this.tech_ && this.tech_.hls) {
  38268. delete this.tech_.hls;
  38269. }
  38270. get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
  38271. }
  38272. }, {
  38273. key: 'convertToProgramTime',
  38274. value: function convertToProgramTime(time, callback) {
  38275. return getProgramTime({
  38276. playlist: this.masterPlaylistController_.media(),
  38277. time: time,
  38278. callback: callback
  38279. });
  38280. } // the player must be playing before calling this
  38281. }, {
  38282. key: 'seekToProgramTime',
  38283. value: function seekToProgramTime$$1(programTime, callback) {
  38284. var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
  38285. var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
  38286. return seekToProgramTime({
  38287. programTime: programTime,
  38288. playlist: this.masterPlaylistController_.media(),
  38289. retryCount: retryCount,
  38290. pauseAfterSeek: pauseAfterSeek,
  38291. seekTo: this.options_.seekTo,
  38292. tech: this.options_.tech,
  38293. callback: callback
  38294. });
  38295. }
  38296. }]);
  38297. return HlsHandler;
  38298. }(Component$1);
  38299. /**
  38300. * The Source Handler object, which informs video.js what additional
  38301. * MIME types are supported and sets up playback. It is registered
  38302. * automatically to the appropriate tech based on the capabilities of
  38303. * the browser it is running in. It is not necessary to use or modify
  38304. * this object in normal usage.
  38305. */
  38306. var HlsSourceHandler = {
  38307. name: 'videojs-http-streaming',
  38308. VERSION: version$1,
  38309. canHandleSource: function canHandleSource(srcObj) {
  38310. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  38311. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  38312. return HlsSourceHandler.canPlayType(srcObj.type, localOptions);
  38313. },
  38314. handleSource: function handleSource(source, tech) {
  38315. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  38316. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  38317. tech.hls = new HlsHandler(source, tech, localOptions);
  38318. tech.hls.xhr = xhrFactory();
  38319. tech.hls.src(source.src, source.type);
  38320. return tech.hls;
  38321. },
  38322. canPlayType: function canPlayType(type) {
  38323. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  38324. var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
  38325. overrideNative = _videojs$mergeOptions.hls.overrideNative;
  38326. var supportedType = simpleTypeFromSourceType(type);
  38327. var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative);
  38328. return canUseMsePlayback ? 'maybe' : '';
  38329. }
  38330. };
  38331. if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') {
  38332. videojs$1.MediaSource = MediaSource;
  38333. videojs$1.URL = URL$1;
  38334. } // register source handlers with the appropriate techs
  38335. if (MediaSource.supportsNativeMediaSources()) {
  38336. videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0);
  38337. }
  38338. videojs$1.HlsHandler = HlsHandler;
  38339. videojs$1.HlsSourceHandler = HlsSourceHandler;
  38340. videojs$1.Hls = Hls$1;
  38341. if (!videojs$1.use) {
  38342. videojs$1.registerComponent('Hls', Hls$1);
  38343. }
  38344. videojs$1.options.hls = videojs$1.options.hls || {};
  38345. if (videojs$1.registerPlugin) {
  38346. videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
  38347. } else {
  38348. videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
  38349. }
  38350. export default videojs$1;