| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680386813868238683386843868538686386873868838689386903869138692386933869438695386963869738698386993870038701387023870338704387053870638707387083870938710387113871238713387143871538716387173871838719387203872138722387233872438725387263872738728387293873038731387323873338734387353873638737387383873938740387413874238743387443874538746387473874838749387503875138752387533875438755387563875738758387593876038761387623876338764387653876638767387683876938770387713877238773387743877538776387773877838779387803878138782387833878438785387863878738788387893879038791387923879338794387953879638797387983879938800388013880238803388043880538806388073880838809388103881138812388133881438815388163881738818388193882038821388223882338824388253882638827388283882938830388313883238833388343883538836388373883838839388403884138842388433884438845388463884738848388493885038851388523885338854388553885638857388583885938860388613886238863388643886538866388673886838869388703887138872388733887438875388763887738878388793888038881388823888338884388853888638887388883888938890388913889238893388943889538896388973889838899389003890138902389033890438905389063890738908389093891038911389123891338914389153891638917389183891938920389213892238923389243892538926389273892838929389303893138932389333893438935389363893738938389393894038941389423894338944389453894638947389483894938950389513895238953389543895538956389573895838959389603896138962389633896438965389663896738968389693897038971389723897338974389753897638977389783897938980389813898238983389843898538986389873898838989389903899138992389933899438995389963899738998389993900039001390023900339004390053900639007390083900939010390113901239013390143901539016390173901839019390203902139022390233902439025390263902739028390293903039031390323903339034390353903639037390383903939040390413904239043390443904539046390473904839049390503905139052390533905439055390563905739058390593906039061390623906339064390653906639067390683906939070390713907239073390743907539076390773907839079390803908139082390833908439085390863908739088390893909039091390923909339094390953909639097390983909939100391013910239103391043910539106391073910839109391103911139112391133911439115391163911739118391193912039121391223912339124391253912639127391283912939130391313913239133391343913539136391373913839139391403914139142391433914439145391463914739148391493915039151391523915339154391553915639157391583915939160391613916239163391643916539166391673916839169391703917139172391733917439175391763917739178391793918039181391823918339184391853918639187391883918939190391913919239193391943919539196391973919839199392003920139202392033920439205392063920739208392093921039211392123921339214392153921639217392183921939220392213922239223392243922539226392273922839229392303923139232392333923439235392363923739238392393924039241392423924339244392453924639247392483924939250392513925239253392543925539256392573925839259392603926139262392633926439265392663926739268392693927039271392723927339274392753927639277392783927939280392813928239283392843928539286392873928839289392903929139292392933929439295392963929739298392993930039301393023930339304393053930639307393083930939310393113931239313393143931539316393173931839319393203932139322393233932439325393263932739328393293933039331393323933339334393353933639337393383933939340393413934239343393443934539346393473934839349393503935139352393533935439355393563935739358393593936039361393623936339364393653936639367393683936939370393713937239373393743937539376393773937839379393803938139382393833938439385393863938739388393893939039391393923939339394393953939639397393983939939400394013940239403394043940539406394073940839409394103941139412394133941439415394163941739418394193942039421394223942339424394253942639427394283942939430394313943239433394343943539436394373943839439394403944139442394433944439445394463944739448394493945039451394523945339454394553945639457394583945939460394613946239463394643946539466394673946839469394703947139472394733947439475394763947739478394793948039481394823948339484394853948639487394883948939490394913949239493394943949539496394973949839499395003950139502395033950439505395063950739508395093951039511395123951339514395153951639517395183951939520395213952239523395243952539526395273952839529395303953139532395333953439535395363953739538395393954039541395423954339544395453954639547395483954939550395513955239553395543955539556395573955839559395603956139562395633956439565395663956739568395693957039571395723957339574395753957639577395783957939580395813958239583395843958539586395873958839589395903959139592395933959439595395963959739598395993960039601396023960339604396053960639607396083960939610396113961239613396143961539616396173961839619396203962139622396233962439625396263962739628396293963039631396323963339634396353963639637396383963939640396413964239643396443964539646396473964839649396503965139652396533965439655396563965739658396593966039661396623966339664396653966639667396683966939670396713967239673396743967539676396773967839679396803968139682396833968439685396863968739688396893969039691396923969339694396953969639697396983969939700397013970239703397043970539706397073970839709397103971139712397133971439715397163971739718397193972039721397223972339724397253972639727397283972939730397313973239733397343973539736397373973839739397403974139742397433974439745397463974739748397493975039751397523975339754397553975639757397583975939760397613976239763397643976539766397673976839769397703977139772397733977439775397763977739778397793978039781397823978339784397853978639787397883978939790397913979239793397943979539796397973979839799398003980139802398033980439805398063980739808398093981039811398123981339814398153981639817398183981939820398213982239823398243982539826398273982839829398303983139832398333983439835398363983739838398393984039841398423984339844398453984639847398483984939850398513985239853398543985539856398573985839859398603986139862398633986439865398663986739868398693987039871398723987339874398753987639877398783987939880398813988239883398843988539886398873988839889398903989139892398933989439895398963989739898398993990039901399023990339904399053990639907399083990939910399113991239913399143991539916399173991839919399203992139922399233992439925399263992739928399293993039931399323993339934399353993639937399383993939940399413994239943399443994539946399473994839949399503995139952399533995439955399563995739958399593996039961399623996339964399653996639967399683996939970399713997239973399743997539976399773997839979399803998139982399833998439985399863998739988399893999039991399923999339994399953999639997399983999940000400014000240003400044000540006400074000840009400104001140012400134001440015400164001740018400194002040021400224002340024400254002640027400284002940030400314003240033400344003540036400374003840039400404004140042400434004440045400464004740048400494005040051400524005340054400554005640057400584005940060400614006240063400644006540066400674006840069400704007140072400734007440075400764007740078400794008040081400824008340084400854008640087400884008940090400914009240093400944009540096400974009840099401004010140102401034010440105401064010740108401094011040111401124011340114401154011640117401184011940120401214012240123401244012540126401274012840129401304013140132401334013440135401364013740138401394014040141401424014340144401454014640147401484014940150401514015240153401544015540156401574015840159401604016140162401634016440165401664016740168401694017040171401724017340174401754017640177401784017940180401814018240183401844018540186401874018840189401904019140192401934019440195401964019740198401994020040201402024020340204402054020640207402084020940210402114021240213402144021540216402174021840219402204022140222402234022440225402264022740228402294023040231402324023340234402354023640237402384023940240402414024240243402444024540246402474024840249402504025140252402534025440255402564025740258402594026040261402624026340264402654026640267402684026940270402714027240273402744027540276402774027840279402804028140282402834028440285402864028740288402894029040291402924029340294402954029640297402984029940300403014030240303403044030540306403074030840309403104031140312403134031440315403164031740318403194032040321403224032340324403254032640327403284032940330403314033240333403344033540336403374033840339403404034140342403434034440345403464034740348403494035040351403524035340354403554035640357403584035940360403614036240363403644036540366403674036840369403704037140372403734037440375403764037740378403794038040381403824038340384403854038640387403884038940390403914039240393403944039540396403974039840399404004040140402404034040440405404064040740408404094041040411404124041340414404154041640417404184041940420404214042240423404244042540426404274042840429404304043140432404334043440435404364043740438404394044040441404424044340444404454044640447404484044940450404514045240453404544045540456404574045840459404604046140462404634046440465404664046740468404694047040471404724047340474404754047640477404784047940480404814048240483404844048540486404874048840489404904049140492404934049440495404964049740498404994050040501405024050340504405054050640507405084050940510405114051240513405144051540516405174051840519405204052140522405234052440525405264052740528405294053040531405324053340534405354053640537405384053940540405414054240543405444054540546405474054840549405504055140552405534055440555405564055740558405594056040561405624056340564405654056640567405684056940570405714057240573405744057540576405774057840579405804058140582405834058440585405864058740588405894059040591405924059340594405954059640597405984059940600406014060240603406044060540606406074060840609406104061140612406134061440615406164061740618406194062040621406224062340624406254062640627406284062940630406314063240633406344063540636406374063840639406404064140642406434064440645406464064740648406494065040651406524065340654406554065640657406584065940660406614066240663406644066540666406674066840669406704067140672406734067440675406764067740678406794068040681406824068340684406854068640687406884068940690406914069240693406944069540696406974069840699407004070140702407034070440705407064070740708407094071040711407124071340714407154071640717407184071940720407214072240723407244072540726407274072840729407304073140732407334073440735407364073740738407394074040741407424074340744407454074640747407484074940750407514075240753407544075540756407574075840759407604076140762407634076440765407664076740768407694077040771407724077340774407754077640777407784077940780407814078240783407844078540786407874078840789407904079140792407934079440795407964079740798407994080040801408024080340804408054080640807408084080940810408114081240813408144081540816408174081840819408204082140822408234082440825408264082740828408294083040831408324083340834408354083640837408384083940840408414084240843408444084540846408474084840849408504085140852408534085440855408564085740858408594086040861408624086340864408654086640867408684086940870408714087240873408744087540876408774087840879408804088140882408834088440885408864088740888408894089040891408924089340894408954089640897408984089940900409014090240903409044090540906409074090840909409104091140912409134091440915409164091740918409194092040921409224092340924409254092640927409284092940930409314093240933409344093540936409374093840939409404094140942409434094440945409464094740948409494095040951409524095340954409554095640957409584095940960409614096240963409644096540966409674096840969409704097140972409734097440975409764097740978409794098040981409824098340984409854098640987409884098940990409914099240993409944099540996409974099840999410004100141002410034100441005410064100741008410094101041011410124101341014410154101641017410184101941020410214102241023410244102541026410274102841029410304103141032410334103441035410364103741038410394104041041410424104341044410454104641047410484104941050410514105241053410544105541056410574105841059410604106141062410634106441065410664106741068410694107041071410724107341074410754107641077410784107941080410814108241083410844108541086410874108841089410904109141092410934109441095410964109741098410994110041101411024110341104411054110641107411084110941110411114111241113411144111541116411174111841119411204112141122411234112441125411264112741128411294113041131411324113341134411354113641137411384113941140411414114241143411444114541146411474114841149411504115141152411534115441155411564115741158411594116041161411624116341164411654116641167411684116941170411714117241173411744117541176411774117841179411804118141182411834118441185411864118741188411894119041191411924119341194411954119641197411984119941200412014120241203412044120541206412074120841209412104121141212412134121441215412164121741218412194122041221412224122341224412254122641227412284122941230412314123241233412344123541236412374123841239412404124141242412434124441245412464124741248412494125041251412524125341254412554125641257412584125941260412614126241263412644126541266412674126841269412704127141272412734127441275412764127741278412794128041281412824128341284412854128641287412884128941290412914129241293412944129541296412974129841299413004130141302413034130441305413064130741308413094131041311413124131341314413154131641317413184131941320413214132241323413244132541326413274132841329413304133141332413334133441335413364133741338413394134041341413424134341344413454134641347413484134941350413514135241353413544135541356413574135841359413604136141362413634136441365413664136741368413694137041371413724137341374413754137641377413784137941380413814138241383413844138541386413874138841389413904139141392413934139441395413964139741398413994140041401414024140341404414054140641407414084140941410414114141241413414144141541416414174141841419414204142141422414234142441425414264142741428414294143041431414324143341434414354143641437414384143941440414414144241443414444144541446414474144841449414504145141452414534145441455414564145741458414594146041461414624146341464414654146641467414684146941470414714147241473414744147541476414774147841479414804148141482414834148441485414864148741488414894149041491414924149341494414954149641497414984149941500415014150241503415044150541506415074150841509415104151141512415134151441515415164151741518415194152041521415224152341524415254152641527415284152941530415314153241533415344153541536415374153841539415404154141542415434154441545415464154741548415494155041551415524155341554415554155641557415584155941560415614156241563415644156541566415674156841569415704157141572415734157441575415764157741578415794158041581415824158341584415854158641587415884158941590415914159241593415944159541596415974159841599416004160141602416034160441605416064160741608416094161041611416124161341614416154161641617416184161941620416214162241623416244162541626416274162841629416304163141632416334163441635416364163741638416394164041641416424164341644416454164641647416484164941650416514165241653416544165541656416574165841659416604166141662416634166441665416664166741668416694167041671416724167341674416754167641677416784167941680416814168241683416844168541686416874168841689416904169141692416934169441695416964169741698416994170041701417024170341704417054170641707417084170941710417114171241713417144171541716417174171841719417204172141722417234172441725417264172741728417294173041731417324173341734417354173641737417384173941740417414174241743417444174541746417474174841749417504175141752417534175441755417564175741758417594176041761417624176341764417654176641767417684176941770417714177241773417744177541776417774177841779417804178141782417834178441785417864178741788417894179041791417924179341794417954179641797417984179941800418014180241803418044180541806418074180841809418104181141812418134181441815418164181741818418194182041821418224182341824418254182641827418284182941830418314183241833418344183541836418374183841839418404184141842418434184441845418464184741848418494185041851418524185341854418554185641857418584185941860418614186241863418644186541866418674186841869418704187141872418734187441875418764187741878418794188041881418824188341884418854188641887418884188941890418914189241893418944189541896418974189841899419004190141902419034190441905419064190741908419094191041911419124191341914419154191641917419184191941920419214192241923419244192541926419274192841929419304193141932419334193441935419364193741938419394194041941419424194341944419454194641947419484194941950419514195241953419544195541956419574195841959419604196141962419634196441965419664196741968419694197041971419724197341974419754197641977419784197941980419814198241983419844198541986419874198841989419904199141992419934199441995419964199741998419994200042001420024200342004420054200642007420084200942010420114201242013420144201542016420174201842019420204202142022420234202442025420264202742028420294203042031420324203342034420354203642037420384203942040420414204242043420444204542046420474204842049420504205142052420534205442055420564205742058420594206042061420624206342064420654206642067420684206942070420714207242073420744207542076420774207842079420804208142082420834208442085420864208742088420894209042091420924209342094420954209642097420984209942100421014210242103421044210542106421074210842109421104211142112421134211442115421164211742118421194212042121421224212342124421254212642127421284212942130421314213242133421344213542136421374213842139421404214142142421434214442145421464214742148421494215042151421524215342154421554215642157421584215942160421614216242163421644216542166421674216842169421704217142172421734217442175421764217742178421794218042181421824218342184421854218642187421884218942190421914219242193421944219542196421974219842199422004220142202422034220442205422064220742208422094221042211422124221342214422154221642217422184221942220422214222242223422244222542226422274222842229422304223142232422334223442235422364223742238422394224042241422424224342244422454224642247422484224942250422514225242253422544225542256422574225842259422604226142262422634226442265422664226742268422694227042271422724227342274422754227642277422784227942280422814228242283422844228542286422874228842289422904229142292422934229442295422964229742298422994230042301423024230342304423054230642307423084230942310423114231242313423144231542316423174231842319423204232142322423234232442325423264232742328423294233042331423324233342334423354233642337423384233942340423414234242343423444234542346423474234842349423504235142352423534235442355423564235742358423594236042361423624236342364423654236642367423684236942370423714237242373423744237542376423774237842379423804238142382423834238442385423864238742388423894239042391423924239342394423954239642397423984239942400424014240242403424044240542406424074240842409424104241142412424134241442415424164241742418424194242042421424224242342424424254242642427424284242942430424314243242433424344243542436424374243842439424404244142442424434244442445424464244742448424494245042451424524245342454424554245642457424584245942460424614246242463424644246542466424674246842469424704247142472424734247442475424764247742478424794248042481424824248342484424854248642487424884248942490424914249242493424944249542496424974249842499425004250142502425034250442505425064250742508425094251042511425124251342514425154251642517425184251942520425214252242523425244252542526425274252842529425304253142532425334253442535425364253742538425394254042541425424254342544425454254642547425484254942550425514255242553425544255542556425574255842559425604256142562425634256442565425664256742568425694257042571425724257342574425754257642577425784257942580425814258242583425844258542586425874258842589425904259142592425934259442595425964259742598425994260042601426024260342604426054260642607426084260942610426114261242613426144261542616426174261842619426204262142622426234262442625426264262742628426294263042631426324263342634426354263642637426384263942640426414264242643426444264542646426474264842649426504265142652426534265442655426564265742658426594266042661426624266342664426654266642667426684266942670426714267242673426744267542676426774267842679426804268142682426834268442685426864268742688426894269042691426924269342694426954269642697426984269942700427014270242703427044270542706427074270842709427104271142712427134271442715427164271742718427194272042721427224272342724427254272642727427284272942730427314273242733427344273542736427374273842739427404274142742427434274442745427464274742748427494275042751427524275342754427554275642757427584275942760427614276242763427644276542766427674276842769427704277142772427734277442775427764277742778427794278042781427824278342784427854278642787427884278942790427914279242793427944279542796427974279842799428004280142802428034280442805428064280742808428094281042811428124281342814428154281642817428184281942820428214282242823428244282542826428274282842829428304283142832428334283442835428364283742838428394284042841428424284342844428454284642847428484284942850428514285242853428544285542856428574285842859428604286142862428634286442865428664286742868428694287042871428724287342874428754287642877428784287942880428814288242883428844288542886428874288842889428904289142892428934289442895428964289742898428994290042901429024290342904429054290642907429084290942910429114291242913429144291542916429174291842919429204292142922429234292442925429264292742928429294293042931429324293342934429354293642937429384293942940429414294242943429444294542946429474294842949429504295142952429534295442955429564295742958429594296042961429624296342964429654296642967429684296942970429714297242973429744297542976429774297842979429804298142982429834298442985429864298742988429894299042991429924299342994429954299642997429984299943000430014300243003430044300543006430074300843009430104301143012430134301443015430164301743018430194302043021430224302343024430254302643027430284302943030430314303243033430344303543036430374303843039430404304143042430434304443045430464304743048430494305043051430524305343054430554305643057430584305943060430614306243063430644306543066430674306843069430704307143072430734307443075430764307743078430794308043081430824308343084430854308643087430884308943090430914309243093430944309543096430974309843099431004310143102431034310443105431064310743108431094311043111431124311343114431154311643117431184311943120431214312243123431244312543126431274312843129431304313143132431334313443135431364313743138431394314043141431424314343144431454314643147431484314943150431514315243153431544315543156431574315843159431604316143162431634316443165431664316743168431694317043171431724317343174431754317643177431784317943180431814318243183431844318543186431874318843189431904319143192431934319443195431964319743198431994320043201432024320343204432054320643207432084320943210432114321243213432144321543216432174321843219432204322143222432234322443225432264322743228432294323043231432324323343234432354323643237432384323943240432414324243243432444324543246432474324843249432504325143252432534325443255432564325743258432594326043261432624326343264432654326643267432684326943270432714327243273432744327543276432774327843279432804328143282432834328443285432864328743288432894329043291432924329343294432954329643297432984329943300433014330243303433044330543306433074330843309433104331143312433134331443315433164331743318433194332043321433224332343324433254332643327433284332943330433314333243333433344333543336433374333843339433404334143342433434334443345433464334743348433494335043351433524335343354433554335643357433584335943360433614336243363433644336543366433674336843369433704337143372433734337443375433764337743378433794338043381433824338343384433854338643387433884338943390433914339243393433944339543396433974339843399434004340143402434034340443405434064340743408434094341043411434124341343414434154341643417434184341943420434214342243423434244342543426434274342843429434304343143432434334343443435434364343743438434394344043441434424344343444434454344643447434484344943450434514345243453434544345543456434574345843459434604346143462434634346443465434664346743468434694347043471434724347343474434754347643477434784347943480434814348243483434844348543486434874348843489434904349143492434934349443495434964349743498434994350043501435024350343504435054350643507435084350943510435114351243513435144351543516435174351843519435204352143522435234352443525435264352743528435294353043531435324353343534435354353643537435384353943540435414354243543435444354543546435474354843549435504355143552435534355443555435564355743558435594356043561435624356343564435654356643567435684356943570435714357243573435744357543576435774357843579435804358143582435834358443585435864358743588435894359043591435924359343594435954359643597435984359943600436014360243603436044360543606436074360843609436104361143612436134361443615436164361743618436194362043621436224362343624436254362643627436284362943630436314363243633436344363543636436374363843639436404364143642436434364443645436464364743648436494365043651436524365343654436554365643657436584365943660436614366243663436644366543666436674366843669436704367143672436734367443675436764367743678436794368043681436824368343684436854368643687436884368943690436914369243693436944369543696436974369843699437004370143702437034370443705437064370743708437094371043711437124371343714437154371643717437184371943720437214372243723437244372543726437274372843729437304373143732437334373443735437364373743738437394374043741437424374343744437454374643747437484374943750437514375243753437544375543756437574375843759437604376143762437634376443765437664376743768437694377043771437724377343774437754377643777437784377943780437814378243783437844378543786437874378843789437904379143792437934379443795437964379743798437994380043801438024380343804438054380643807438084380943810438114381243813438144381543816438174381843819438204382143822438234382443825438264382743828438294383043831438324383343834438354383643837438384383943840438414384243843438444384543846438474384843849438504385143852438534385443855438564385743858438594386043861438624386343864438654386643867438684386943870438714387243873438744387543876438774387843879438804388143882438834388443885438864388743888438894389043891438924389343894438954389643897438984389943900439014390243903439044390543906439074390843909439104391143912439134391443915439164391743918439194392043921439224392343924439254392643927439284392943930439314393243933439344393543936439374393843939439404394143942439434394443945439464394743948439494395043951439524395343954439554395643957439584395943960439614396243963439644396543966439674396843969439704397143972439734397443975439764397743978439794398043981439824398343984439854398643987439884398943990439914399243993439944399543996439974399843999440004400144002440034400444005440064400744008440094401044011440124401344014440154401644017440184401944020440214402244023440244402544026440274402844029440304403144032440334403444035440364403744038440394404044041440424404344044440454404644047440484404944050440514405244053440544405544056440574405844059440604406144062440634406444065440664406744068440694407044071440724407344074440754407644077440784407944080440814408244083440844408544086440874408844089440904409144092440934409444095440964409744098440994410044101441024410344104441054410644107441084410944110441114411244113441144411544116441174411844119441204412144122441234412444125441264412744128441294413044131441324413344134441354413644137441384413944140441414414244143441444414544146441474414844149441504415144152441534415444155441564415744158441594416044161441624416344164441654416644167441684416944170441714417244173441744417544176441774417844179441804418144182441834418444185441864418744188441894419044191441924419344194441954419644197441984419944200442014420244203442044420544206442074420844209442104421144212442134421444215442164421744218442194422044221442224422344224442254422644227442284422944230442314423244233442344423544236442374423844239442404424144242442434424444245442464424744248442494425044251442524425344254442554425644257442584425944260442614426244263442644426544266442674426844269442704427144272442734427444275442764427744278442794428044281442824428344284442854428644287442884428944290442914429244293442944429544296442974429844299443004430144302443034430444305443064430744308443094431044311443124431344314443154431644317443184431944320443214432244323443244432544326443274432844329443304433144332443334433444335443364433744338443394434044341443424434344344443454434644347443484434944350443514435244353443544435544356443574435844359443604436144362443634436444365443664436744368443694437044371443724437344374443754437644377443784437944380443814438244383443844438544386443874438844389443904439144392443934439444395443964439744398443994440044401444024440344404444054440644407444084440944410444114441244413444144441544416444174441844419444204442144422444234442444425444264442744428444294443044431444324443344434444354443644437444384443944440444414444244443444444444544446444474444844449444504445144452444534445444455444564445744458444594446044461444624446344464444654446644467444684446944470444714447244473444744447544476444774447844479444804448144482444834448444485444864448744488444894449044491444924449344494444954449644497444984449944500445014450244503445044450544506445074450844509445104451144512445134451444515445164451744518445194452044521445224452344524445254452644527445284452944530445314453244533445344453544536445374453844539445404454144542445434454444545445464454744548445494455044551445524455344554445554455644557445584455944560445614456244563445644456544566445674456844569445704457144572445734457444575445764457744578445794458044581445824458344584445854458644587445884458944590445914459244593445944459544596445974459844599446004460144602446034460444605446064460744608446094461044611446124461344614446154461644617446184461944620446214462244623446244462544626446274462844629446304463144632446334463444635446364463744638446394464044641446424464344644446454464644647446484464944650446514465244653446544465544656446574465844659446604466144662446634466444665446664466744668446694467044671446724467344674446754467644677446784467944680446814468244683446844468544686446874468844689446904469144692446934469444695446964469744698446994470044701447024470344704447054470644707447084470944710447114471244713447144471544716447174471844719447204472144722447234472444725447264472744728447294473044731447324473344734447354473644737447384473944740447414474244743447444474544746447474474844749447504475144752447534475444755447564475744758447594476044761447624476344764447654476644767447684476944770447714477244773447744477544776447774477844779447804478144782447834478444785447864478744788447894479044791447924479344794447954479644797447984479944800448014480244803448044480544806448074480844809448104481144812448134481444815448164481744818448194482044821448224482344824448254482644827448284482944830448314483244833448344483544836448374483844839448404484144842448434484444845448464484744848448494485044851448524485344854448554485644857448584485944860448614486244863448644486544866448674486844869448704487144872448734487444875448764487744878448794488044881448824488344884448854488644887448884488944890448914489244893448944489544896448974489844899449004490144902449034490444905449064490744908449094491044911449124491344914449154491644917449184491944920449214492244923449244492544926449274492844929449304493144932449334493444935449364493744938449394494044941449424494344944449454494644947449484494944950449514495244953449544495544956449574495844959449604496144962449634496444965449664496744968449694497044971449724497344974449754497644977449784497944980449814498244983449844498544986449874498844989449904499144992449934499444995449964499744998449994500045001450024500345004450054500645007450084500945010450114501245013450144501545016450174501845019450204502145022450234502445025450264502745028450294503045031450324503345034450354503645037450384503945040450414504245043450444504545046450474504845049450504505145052450534505445055450564505745058450594506045061450624506345064450654506645067450684506945070450714507245073450744507545076450774507845079450804508145082450834508445085450864508745088450894509045091450924509345094450954509645097450984509945100451014510245103451044510545106451074510845109451104511145112451134511445115451164511745118451194512045121451224512345124451254512645127451284512945130451314513245133451344513545136451374513845139451404514145142451434514445145451464514745148451494515045151451524515345154451554515645157451584515945160451614516245163451644516545166451674516845169451704517145172451734517445175451764517745178451794518045181451824518345184451854518645187451884518945190451914519245193451944519545196451974519845199452004520145202452034520445205452064520745208452094521045211452124521345214452154521645217452184521945220452214522245223452244522545226452274522845229452304523145232452334523445235452364523745238452394524045241452424524345244452454524645247452484524945250452514525245253452544525545256452574525845259452604526145262452634526445265452664526745268452694527045271452724527345274452754527645277452784527945280452814528245283452844528545286452874528845289452904529145292452934529445295452964529745298452994530045301453024530345304453054530645307453084530945310453114531245313453144531545316453174531845319453204532145322453234532445325453264532745328453294533045331453324533345334453354533645337453384533945340453414534245343453444534545346453474534845349453504535145352453534535445355453564535745358453594536045361453624536345364453654536645367453684536945370453714537245373453744537545376453774537845379453804538145382453834538445385453864538745388453894539045391453924539345394453954539645397453984539945400454014540245403454044540545406454074540845409454104541145412454134541445415454164541745418454194542045421454224542345424454254542645427454284542945430454314543245433454344543545436454374543845439454404544145442454434544445445454464544745448454494545045451454524545345454454554545645457454584545945460454614546245463454644546545466454674546845469454704547145472454734547445475454764547745478454794548045481454824548345484454854548645487454884548945490454914549245493454944549545496454974549845499455004550145502455034550445505455064550745508455094551045511455124551345514455154551645517455184551945520455214552245523455244552545526455274552845529455304553145532455334553445535455364553745538455394554045541455424554345544455454554645547455484554945550455514555245553455544555545556455574555845559455604556145562455634556445565455664556745568455694557045571455724557345574455754557645577455784557945580455814558245583455844558545586455874558845589455904559145592455934559445595455964559745598455994560045601456024560345604456054560645607456084560945610456114561245613456144561545616456174561845619456204562145622456234562445625456264562745628456294563045631456324563345634456354563645637456384563945640456414564245643456444564545646456474564845649456504565145652456534565445655456564565745658456594566045661456624566345664456654566645667456684566945670456714567245673456744567545676456774567845679456804568145682456834568445685456864568745688456894569045691456924569345694456954569645697456984569945700457014570245703457044570545706457074570845709457104571145712457134571445715457164571745718457194572045721457224572345724457254572645727457284572945730457314573245733457344573545736457374573845739457404574145742457434574445745457464574745748457494575045751457524575345754457554575645757457584575945760457614576245763457644576545766457674576845769457704577145772457734577445775457764577745778457794578045781457824578345784457854578645787457884578945790457914579245793457944579545796457974579845799458004580145802458034580445805458064580745808458094581045811458124581345814458154581645817458184581945820458214582245823458244582545826458274582845829458304583145832458334583445835458364583745838458394584045841458424584345844458454584645847458484584945850458514585245853458544585545856458574585845859458604586145862458634586445865458664586745868458694587045871458724587345874458754587645877458784587945880458814588245883458844588545886458874588845889458904589145892458934589445895458964589745898458994590045901459024590345904459054590645907459084590945910459114591245913459144591545916459174591845919459204592145922459234592445925459264592745928459294593045931459324593345934459354593645937459384593945940459414594245943459444594545946459474594845949459504595145952459534595445955459564595745958459594596045961459624596345964459654596645967459684596945970459714597245973459744597545976459774597845979459804598145982459834598445985459864598745988459894599045991459924599345994459954599645997459984599946000460014600246003460044600546006460074600846009460104601146012460134601446015460164601746018460194602046021460224602346024460254602646027460284602946030460314603246033460344603546036460374603846039460404604146042460434604446045460464604746048460494605046051460524605346054460554605646057460584605946060460614606246063460644606546066460674606846069460704607146072460734607446075460764607746078460794608046081460824608346084460854608646087460884608946090460914609246093460944609546096460974609846099461004610146102461034610446105461064610746108461094611046111461124611346114461154611646117461184611946120461214612246123461244612546126461274612846129461304613146132461334613446135461364613746138461394614046141461424614346144461454614646147461484614946150461514615246153461544615546156461574615846159461604616146162461634616446165461664616746168461694617046171461724617346174461754617646177461784617946180461814618246183461844618546186461874618846189461904619146192461934619446195461964619746198461994620046201462024620346204462054620646207462084620946210462114621246213462144621546216462174621846219462204622146222462234622446225462264622746228462294623046231462324623346234462354623646237462384623946240462414624246243462444624546246462474624846249462504625146252462534625446255462564625746258462594626046261462624626346264462654626646267462684626946270462714627246273462744627546276462774627846279462804628146282462834628446285462864628746288462894629046291462924629346294462954629646297462984629946300463014630246303463044630546306463074630846309463104631146312463134631446315463164631746318463194632046321463224632346324463254632646327463284632946330463314633246333463344633546336463374633846339463404634146342463434634446345463464634746348463494635046351463524635346354463554635646357463584635946360463614636246363463644636546366463674636846369463704637146372463734637446375463764637746378463794638046381463824638346384463854638646387463884638946390463914639246393463944639546396463974639846399464004640146402464034640446405464064640746408464094641046411464124641346414464154641646417464184641946420464214642246423464244642546426464274642846429464304643146432464334643446435464364643746438464394644046441464424644346444464454644646447464484644946450464514645246453464544645546456464574645846459464604646146462464634646446465464664646746468464694647046471464724647346474464754647646477464784647946480464814648246483464844648546486464874648846489464904649146492464934649446495464964649746498464994650046501465024650346504465054650646507465084650946510465114651246513465144651546516465174651846519465204652146522465234652446525465264652746528465294653046531465324653346534465354653646537465384653946540465414654246543465444654546546465474654846549465504655146552465534655446555465564655746558465594656046561465624656346564465654656646567465684656946570465714657246573465744657546576465774657846579465804658146582465834658446585465864658746588465894659046591465924659346594465954659646597465984659946600466014660246603466044660546606466074660846609466104661146612466134661446615466164661746618466194662046621466224662346624466254662646627466284662946630466314663246633466344663546636466374663846639466404664146642466434664446645466464664746648466494665046651466524665346654466554665646657466584665946660466614666246663466644666546666466674666846669466704667146672466734667446675466764667746678466794668046681466824668346684466854668646687466884668946690466914669246693466944669546696466974669846699467004670146702467034670446705467064670746708467094671046711467124671346714467154671646717467184671946720467214672246723467244672546726467274672846729467304673146732467334673446735467364673746738467394674046741467424674346744467454674646747467484674946750467514675246753467544675546756467574675846759467604676146762467634676446765467664676746768467694677046771467724677346774467754677646777467784677946780467814678246783467844678546786467874678846789467904679146792467934679446795467964679746798467994680046801468024680346804468054680646807468084680946810468114681246813468144681546816468174681846819468204682146822468234682446825468264682746828468294683046831468324683346834468354683646837468384683946840468414684246843468444684546846468474684846849468504685146852468534685446855468564685746858468594686046861468624686346864468654686646867468684686946870468714687246873468744687546876468774687846879468804688146882468834688446885468864688746888468894689046891468924689346894468954689646897468984689946900469014690246903469044690546906469074690846909469104691146912469134691446915469164691746918469194692046921469224692346924469254692646927469284692946930469314693246933469344693546936469374693846939469404694146942469434694446945469464694746948469494695046951469524695346954469554695646957469584695946960469614696246963469644696546966469674696846969469704697146972469734697446975469764697746978469794698046981469824698346984469854698646987469884698946990469914699246993469944699546996469974699846999470004700147002470034700447005470064700747008470094701047011470124701347014470154701647017470184701947020470214702247023470244702547026470274702847029470304703147032470334703447035470364703747038470394704047041470424704347044470454704647047470484704947050470514705247053470544705547056470574705847059470604706147062470634706447065470664706747068470694707047071470724707347074470754707647077470784707947080470814708247083470844708547086470874708847089470904709147092470934709447095470964709747098470994710047101471024710347104471054710647107471084710947110471114711247113471144711547116471174711847119471204712147122471234712447125471264712747128471294713047131471324713347134471354713647137471384713947140471414714247143471444714547146471474714847149471504715147152471534715447155471564715747158471594716047161471624716347164471654716647167471684716947170471714717247173471744717547176471774717847179471804718147182471834718447185471864718747188471894719047191471924719347194471954719647197471984719947200472014720247203472044720547206472074720847209472104721147212472134721447215472164721747218472194722047221472224722347224472254722647227472284722947230472314723247233472344723547236472374723847239472404724147242472434724447245472464724747248472494725047251472524725347254472554725647257472584725947260472614726247263472644726547266472674726847269472704727147272472734727447275472764727747278472794728047281472824728347284472854728647287472884728947290472914729247293472944729547296472974729847299473004730147302473034730447305473064730747308473094731047311473124731347314473154731647317473184731947320473214732247323473244732547326473274732847329473304733147332473334733447335473364733747338473394734047341473424734347344473454734647347473484734947350473514735247353473544735547356473574735847359473604736147362473634736447365473664736747368473694737047371473724737347374473754737647377473784737947380473814738247383473844738547386473874738847389473904739147392473934739447395473964739747398473994740047401474024740347404474054740647407474084740947410474114741247413474144741547416474174741847419474204742147422474234742447425474264742747428474294743047431474324743347434474354743647437474384743947440474414744247443474444744547446474474744847449474504745147452474534745447455474564745747458474594746047461474624746347464474654746647467474684746947470474714747247473474744747547476474774747847479474804748147482474834748447485474864748747488474894749047491474924749347494474954749647497474984749947500475014750247503475044750547506475074750847509475104751147512475134751447515475164751747518475194752047521475224752347524475254752647527475284752947530475314753247533475344753547536475374753847539475404754147542475434754447545475464754747548475494755047551475524755347554475554755647557475584755947560475614756247563475644756547566475674756847569475704757147572475734757447575475764757747578475794758047581475824758347584475854758647587475884758947590475914759247593475944759547596475974759847599476004760147602476034760447605476064760747608476094761047611476124761347614476154761647617476184761947620476214762247623476244762547626476274762847629476304763147632476334763447635476364763747638476394764047641476424764347644476454764647647476484764947650476514765247653476544765547656476574765847659476604766147662476634766447665476664766747668476694767047671476724767347674476754767647677476784767947680476814768247683476844768547686476874768847689476904769147692476934769447695476964769747698476994770047701477024770347704477054770647707477084770947710477114771247713477144771547716477174771847719477204772147722477234772447725477264772747728477294773047731477324773347734477354773647737477384773947740477414774247743477444774547746477474774847749477504775147752477534775447755477564775747758477594776047761477624776347764477654776647767477684776947770477714777247773477744777547776477774777847779477804778147782477834778447785477864778747788477894779047791477924779347794477954779647797477984779947800478014780247803478044780547806478074780847809478104781147812478134781447815478164781747818478194782047821478224782347824478254782647827478284782947830478314783247833478344783547836478374783847839478404784147842478434784447845478464784747848478494785047851478524785347854478554785647857478584785947860478614786247863478644786547866478674786847869478704787147872478734787447875478764787747878478794788047881478824788347884478854788647887478884788947890478914789247893478944789547896478974789847899479004790147902479034790447905479064790747908479094791047911479124791347914479154791647917479184791947920479214792247923479244792547926479274792847929479304793147932479334793447935479364793747938479394794047941479424794347944479454794647947479484794947950479514795247953479544795547956479574795847959479604796147962479634796447965479664796747968479694797047971479724797347974479754797647977479784797947980479814798247983479844798547986479874798847989479904799147992479934799447995479964799747998479994800048001480024800348004480054800648007480084800948010480114801248013480144801548016480174801848019480204802148022480234802448025480264802748028480294803048031480324803348034480354803648037480384803948040480414804248043480444804548046480474804848049480504805148052480534805448055480564805748058480594806048061480624806348064480654806648067480684806948070480714807248073480744807548076480774807848079480804808148082480834808448085480864808748088480894809048091480924809348094480954809648097480984809948100481014810248103481044810548106481074810848109481104811148112481134811448115481164811748118481194812048121481224812348124481254812648127481284812948130481314813248133481344813548136481374813848139481404814148142481434814448145481464814748148481494815048151481524815348154481554815648157481584815948160481614816248163481644816548166481674816848169481704817148172481734817448175481764817748178481794818048181481824818348184481854818648187481884818948190481914819248193481944819548196481974819848199482004820148202482034820448205482064820748208482094821048211482124821348214482154821648217482184821948220482214822248223482244822548226482274822848229482304823148232482334823448235482364823748238482394824048241482424824348244482454824648247482484824948250482514825248253482544825548256482574825848259482604826148262482634826448265482664826748268482694827048271482724827348274482754827648277482784827948280482814828248283482844828548286482874828848289482904829148292482934829448295482964829748298482994830048301483024830348304483054830648307483084830948310483114831248313483144831548316483174831848319483204832148322483234832448325483264832748328483294833048331483324833348334483354833648337483384833948340483414834248343483444834548346483474834848349483504835148352483534835448355483564835748358483594836048361483624836348364483654836648367483684836948370483714837248373483744837548376483774837848379483804838148382483834838448385483864838748388483894839048391483924839348394483954839648397483984839948400484014840248403484044840548406484074840848409484104841148412484134841448415484164841748418484194842048421484224842348424484254842648427484284842948430484314843248433484344843548436484374843848439484404844148442484434844448445484464844748448484494845048451484524845348454484554845648457484584845948460484614846248463484644846548466484674846848469484704847148472484734847448475484764847748478484794848048481484824848348484484854848648487484884848948490484914849248493484944849548496484974849848499485004850148502485034850448505485064850748508485094851048511485124851348514485154851648517485184851948520485214852248523485244852548526485274852848529485304853148532485334853448535485364853748538485394854048541485424854348544485454854648547485484854948550485514855248553485544855548556485574855848559485604856148562485634856448565485664856748568485694857048571485724857348574485754857648577485784857948580485814858248583485844858548586485874858848589485904859148592485934859448595485964859748598485994860048601486024860348604486054860648607486084860948610486114861248613486144861548616486174861848619486204862148622486234862448625486264862748628486294863048631486324863348634486354863648637486384863948640486414864248643486444864548646486474864848649486504865148652486534865448655486564865748658486594866048661486624866348664486654866648667486684866948670486714867248673486744867548676486774867848679486804868148682486834868448685486864868748688486894869048691486924869348694486954869648697486984869948700487014870248703487044870548706487074870848709487104871148712487134871448715487164871748718487194872048721487224872348724487254872648727487284872948730487314873248733487344873548736487374873848739487404874148742487434874448745487464874748748487494875048751487524875348754487554875648757487584875948760487614876248763487644876548766487674876848769487704877148772487734877448775487764877748778487794878048781487824878348784487854878648787487884878948790487914879248793487944879548796487974879848799488004880148802488034880448805488064880748808488094881048811488124881348814488154881648817488184881948820488214882248823488244882548826488274882848829488304883148832488334883448835488364883748838488394884048841488424884348844488454884648847488484884948850488514885248853488544885548856488574885848859488604886148862488634886448865488664886748868488694887048871488724887348874488754887648877488784887948880488814888248883488844888548886488874888848889488904889148892488934889448895488964889748898488994890048901489024890348904489054890648907489084890948910489114891248913489144891548916489174891848919489204892148922489234892448925489264892748928489294893048931489324893348934489354893648937489384893948940489414894248943489444894548946489474894848949489504895148952489534895448955489564895748958489594896048961489624896348964489654896648967489684896948970489714897248973489744897548976489774897848979489804898148982489834898448985489864898748988489894899048991489924899348994489954899648997489984899949000490014900249003490044900549006490074900849009490104901149012490134901449015490164901749018490194902049021490224902349024490254902649027490284902949030490314903249033490344903549036490374903849039490404904149042490434904449045490464904749048490494905049051490524905349054490554905649057490584905949060490614906249063490644906549066490674906849069490704907149072490734907449075490764907749078490794908049081490824908349084490854908649087490884908949090490914909249093490944909549096490974909849099491004910149102491034910449105491064910749108491094911049111491124911349114491154911649117491184911949120491214912249123491244912549126491274912849129491304913149132491334913449135491364913749138491394914049141491424914349144491454914649147491484914949150491514915249153491544915549156491574915849159491604916149162491634916449165491664916749168491694917049171491724917349174491754917649177491784917949180491814918249183491844918549186491874918849189491904919149192491934919449195491964919749198491994920049201492024920349204492054920649207492084920949210492114921249213492144921549216492174921849219492204922149222492234922449225492264922749228492294923049231492324923349234492354923649237492384923949240492414924249243492444924549246492474924849249492504925149252492534925449255492564925749258492594926049261492624926349264492654926649267492684926949270492714927249273492744927549276492774927849279492804928149282492834928449285492864928749288492894929049291492924929349294492954929649297492984929949300493014930249303493044930549306493074930849309493104931149312493134931449315493164931749318493194932049321493224932349324493254932649327493284932949330493314933249333493344933549336493374933849339493404934149342493434934449345493464934749348493494935049351493524935349354493554935649357493584935949360493614936249363493644936549366493674936849369493704937149372493734937449375493764937749378493794938049381493824938349384493854938649387493884938949390493914939249393493944939549396493974939849399494004940149402494034940449405494064940749408494094941049411494124941349414494154941649417494184941949420494214942249423494244942549426494274942849429494304943149432494334943449435494364943749438494394944049441494424944349444494454944649447494484944949450494514945249453494544945549456494574945849459494604946149462494634946449465494664946749468494694947049471494724947349474494754947649477494784947949480494814948249483494844948549486494874948849489494904949149492494934949449495494964949749498494994950049501495024950349504495054950649507495084950949510495114951249513495144951549516495174951849519495204952149522495234952449525495264952749528495294953049531495324953349534495354953649537495384953949540495414954249543495444954549546495474954849549495504955149552495534955449555495564955749558495594956049561495624956349564495654956649567495684956949570495714957249573495744957549576495774957849579495804958149582495834958449585495864958749588495894959049591495924959349594495954959649597495984959949600496014960249603496044960549606496074960849609496104961149612496134961449615496164961749618496194962049621496224962349624496254962649627496284962949630496314963249633496344963549636496374963849639496404964149642496434964449645496464964749648496494965049651496524965349654496554965649657496584965949660496614966249663496644966549666496674966849669496704967149672496734967449675496764967749678496794968049681496824968349684496854968649687496884968949690496914969249693496944969549696496974969849699497004970149702497034970449705497064970749708497094971049711497124971349714497154971649717497184971949720497214972249723497244972549726497274972849729497304973149732497334973449735497364973749738497394974049741497424974349744497454974649747497484974949750497514975249753497544975549756497574975849759497604976149762497634976449765497664976749768497694977049771497724977349774497754977649777497784977949780497814978249783497844978549786497874978849789497904979149792497934979449795497964979749798497994980049801498024980349804498054980649807498084980949810498114981249813498144981549816498174981849819498204982149822498234982449825498264982749828498294983049831498324983349834498354983649837498384983949840498414984249843498444984549846498474984849849498504985149852498534985449855498564985749858498594986049861498624986349864498654986649867498684986949870498714987249873498744987549876498774987849879498804988149882498834988449885498864988749888498894989049891498924989349894498954989649897498984989949900499014990249903499044990549906499074990849909499104991149912499134991449915499164991749918499194992049921499224992349924499254992649927499284992949930499314993249933499344993549936499374993849939499404994149942499434994449945499464994749948499494995049951499524995349954499554995649957499584995949960499614996249963499644996549966499674996849969499704997149972499734997449975499764997749978499794998049981499824998349984499854998649987499884998949990499914999249993499944999549996499974999849999500005000150002500035000450005500065000750008500095001050011500125001350014500155001650017500185001950020500215002250023500245002550026500275002850029500305003150032500335003450035500365003750038500395004050041500425004350044500455004650047500485004950050500515005250053500545005550056500575005850059500605006150062500635006450065500665006750068500695007050071500725007350074500755007650077500785007950080500815008250083500845008550086500875008850089500905009150092500935009450095500965009750098500995010050101501025010350104501055010650107501085010950110501115011250113501145011550116501175011850119501205012150122501235012450125501265012750128501295013050131501325013350134501355013650137501385013950140501415014250143501445014550146501475014850149501505015150152501535015450155501565015750158501595016050161501625016350164501655016650167501685016950170501715017250173501745017550176501775017850179501805018150182501835018450185501865018750188501895019050191501925019350194501955019650197501985019950200502015020250203502045020550206502075020850209502105021150212502135021450215502165021750218502195022050221502225022350224502255022650227502285022950230502315023250233502345023550236502375023850239502405024150242502435024450245502465024750248502495025050251502525025350254502555025650257502585025950260502615026250263502645026550266502675026850269502705027150272502735027450275502765027750278502795028050281502825028350284502855028650287502885028950290502915029250293502945029550296502975029850299503005030150302503035030450305503065030750308503095031050311503125031350314503155031650317503185031950320503215032250323503245032550326503275032850329503305033150332503335033450335503365033750338503395034050341503425034350344503455034650347503485034950350503515035250353503545035550356503575035850359503605036150362503635036450365503665036750368503695037050371503725037350374503755037650377503785037950380503815038250383503845038550386503875038850389503905039150392503935039450395503965039750398503995040050401504025040350404504055040650407504085040950410504115041250413504145041550416504175041850419504205042150422504235042450425504265042750428504295043050431504325043350434504355043650437504385043950440504415044250443504445044550446504475044850449504505045150452504535045450455504565045750458504595046050461504625046350464504655046650467504685046950470504715047250473504745047550476504775047850479504805048150482504835048450485504865048750488504895049050491504925049350494504955049650497504985049950500505015050250503505045050550506505075050850509505105051150512505135051450515505165051750518505195052050521505225052350524505255052650527505285052950530505315053250533505345053550536505375053850539505405054150542505435054450545505465054750548505495055050551505525055350554505555055650557505585055950560505615056250563505645056550566505675056850569505705057150572505735057450575505765057750578505795058050581505825058350584505855058650587505885058950590505915059250593505945059550596505975059850599506005060150602506035060450605506065060750608506095061050611506125061350614506155061650617506185061950620506215062250623506245062550626506275062850629506305063150632506335063450635506365063750638506395064050641506425064350644506455064650647506485064950650506515065250653506545065550656506575065850659506605066150662506635066450665506665066750668506695067050671506725067350674506755067650677506785067950680506815068250683506845068550686506875068850689506905069150692506935069450695506965069750698506995070050701507025070350704507055070650707507085070950710507115071250713507145071550716507175071850719507205072150722507235072450725507265072750728507295073050731507325073350734507355073650737507385073950740507415074250743507445074550746507475074850749507505075150752507535075450755507565075750758507595076050761507625076350764507655076650767507685076950770507715077250773507745077550776507775077850779507805078150782507835078450785507865078750788507895079050791507925079350794507955079650797507985079950800508015080250803508045080550806508075080850809508105081150812508135081450815508165081750818508195082050821508225082350824508255082650827508285082950830508315083250833508345083550836508375083850839508405084150842508435084450845508465084750848508495085050851508525085350854508555085650857508585085950860508615086250863508645086550866508675086850869508705087150872508735087450875508765087750878508795088050881508825088350884508855088650887508885088950890508915089250893508945089550896508975089850899509005090150902509035090450905509065090750908509095091050911509125091350914509155091650917509185091950920509215092250923509245092550926509275092850929509305093150932509335093450935509365093750938509395094050941509425094350944509455094650947509485094950950509515095250953509545095550956509575095850959509605096150962509635096450965509665096750968509695097050971509725097350974509755097650977509785097950980509815098250983509845098550986509875098850989509905099150992509935099450995509965099750998509995100051001510025100351004510055100651007510085100951010510115101251013510145101551016510175101851019510205102151022510235102451025510265102751028510295103051031510325103351034510355103651037510385103951040510415104251043510445104551046510475104851049510505105151052510535105451055510565105751058510595106051061510625106351064510655106651067510685106951070510715107251073510745107551076510775107851079510805108151082510835108451085510865108751088510895109051091510925109351094510955109651097510985109951100511015110251103511045110551106511075110851109511105111151112511135111451115511165111751118511195112051121511225112351124511255112651127511285112951130511315113251133511345113551136511375113851139511405114151142511435114451145511465114751148511495115051151511525115351154511555115651157511585115951160511615116251163511645116551166511675116851169511705117151172511735117451175511765117751178511795118051181511825118351184511855118651187511885118951190511915119251193511945119551196511975119851199512005120151202512035120451205512065120751208512095121051211512125121351214512155121651217512185121951220512215122251223512245122551226512275122851229512305123151232512335123451235512365123751238512395124051241512425124351244512455124651247512485124951250512515125251253512545125551256512575125851259512605126151262512635126451265512665126751268512695127051271512725127351274512755127651277512785127951280512815128251283512845128551286512875128851289512905129151292512935129451295512965129751298512995130051301513025130351304513055130651307513085130951310513115131251313513145131551316513175131851319513205132151322513235132451325513265132751328513295133051331513325133351334513355133651337513385133951340513415134251343513445134551346513475134851349513505135151352513535135451355513565135751358513595136051361513625136351364513655136651367513685136951370513715137251373513745137551376513775137851379513805138151382513835138451385513865138751388513895139051391513925139351394513955139651397513985139951400514015140251403514045140551406514075140851409514105141151412514135141451415514165141751418514195142051421514225142351424514255142651427514285142951430514315143251433514345143551436514375143851439514405144151442514435144451445514465144751448514495145051451514525145351454514555145651457514585145951460514615146251463514645146551466514675146851469514705147151472514735147451475514765147751478514795148051481514825148351484514855148651487514885148951490514915149251493514945149551496514975149851499515005150151502515035150451505515065150751508515095151051511515125151351514515155151651517515185151951520515215152251523515245152551526515275152851529515305153151532515335153451535515365153751538515395154051541515425154351544515455154651547515485154951550515515155251553515545155551556515575155851559515605156151562515635156451565515665156751568515695157051571515725157351574515755157651577515785157951580515815158251583515845158551586515875158851589515905159151592515935159451595515965159751598515995160051601516025160351604516055160651607516085160951610516115161251613516145161551616516175161851619516205162151622516235162451625516265162751628516295163051631516325163351634516355163651637516385163951640516415164251643516445164551646516475164851649516505165151652516535165451655516565165751658516595166051661516625166351664516655166651667516685166951670516715167251673516745167551676516775167851679516805168151682516835168451685516865168751688516895169051691516925169351694516955169651697516985169951700517015170251703517045170551706517075170851709517105171151712517135171451715517165171751718517195172051721517225172351724517255172651727517285172951730517315173251733517345173551736517375173851739517405174151742517435174451745517465174751748517495175051751517525175351754517555175651757517585175951760517615176251763517645176551766517675176851769517705177151772517735177451775517765177751778517795178051781517825178351784517855178651787517885178951790517915179251793517945179551796517975179851799518005180151802518035180451805518065180751808518095181051811518125181351814518155181651817518185181951820518215182251823518245182551826518275182851829518305183151832518335183451835518365183751838518395184051841518425184351844518455184651847518485184951850518515185251853518545185551856518575185851859518605186151862518635186451865518665186751868518695187051871518725187351874518755187651877518785187951880518815188251883518845188551886518875188851889518905189151892518935189451895518965189751898518995190051901519025190351904519055190651907519085190951910519115191251913519145191551916519175191851919519205192151922519235192451925519265192751928519295193051931519325193351934519355193651937519385193951940519415194251943519445194551946519475194851949519505195151952519535195451955519565195751958519595196051961519625196351964519655196651967519685196951970519715197251973519745197551976519775197851979519805198151982519835198451985519865198751988519895199051991519925199351994519955199651997519985199952000520015200252003520045200552006520075200852009520105201152012520135201452015520165201752018520195202052021520225202352024520255202652027520285202952030520315203252033520345203552036520375203852039520405204152042520435204452045520465204752048520495205052051520525205352054520555205652057520585205952060520615206252063520645206552066520675206852069520705207152072520735207452075520765207752078520795208052081520825208352084520855208652087520885208952090520915209252093520945209552096520975209852099521005210152102521035210452105521065210752108521095211052111521125211352114521155211652117521185211952120521215212252123521245212552126521275212852129521305213152132521335213452135521365213752138521395214052141521425214352144521455214652147521485214952150521515215252153521545215552156521575215852159521605216152162521635216452165521665216752168521695217052171521725217352174521755217652177521785217952180521815218252183521845218552186521875218852189521905219152192521935219452195521965219752198521995220052201522025220352204522055220652207522085220952210522115221252213522145221552216522175221852219522205222152222522235222452225522265222752228522295223052231522325223352234522355223652237522385223952240522415224252243522445224552246522475224852249522505225152252522535225452255522565225752258522595226052261522625226352264522655226652267522685226952270522715227252273522745227552276522775227852279522805228152282522835228452285522865228752288522895229052291522925229352294522955229652297522985229952300523015230252303523045230552306523075230852309523105231152312523135231452315523165231752318523195232052321523225232352324523255232652327523285232952330523315233252333523345233552336523375233852339523405234152342523435234452345523465234752348523495235052351523525235352354523555235652357523585235952360523615236252363523645236552366523675236852369523705237152372523735237452375523765237752378523795238052381523825238352384523855238652387523885238952390523915239252393523945239552396523975239852399524005240152402524035240452405524065240752408524095241052411524125241352414524155241652417524185241952420524215242252423524245242552426524275242852429524305243152432524335243452435524365243752438524395244052441524425244352444524455244652447524485244952450524515245252453524545245552456524575245852459524605246152462524635246452465524665246752468524695247052471524725247352474524755247652477524785247952480524815248252483524845248552486524875248852489524905249152492524935249452495524965249752498524995250052501525025250352504525055250652507525085250952510525115251252513525145251552516525175251852519525205252152522525235252452525525265252752528525295253052531525325253352534525355253652537525385253952540525415254252543525445254552546525475254852549525505255152552525535255452555525565255752558525595256052561525625256352564525655256652567525685256952570525715257252573525745257552576525775257852579525805258152582525835258452585525865258752588525895259052591525925259352594525955259652597525985259952600526015260252603526045260552606526075260852609526105261152612526135261452615526165261752618526195262052621526225262352624526255262652627526285262952630526315263252633526345263552636526375263852639526405264152642526435264452645526465264752648526495265052651526525265352654526555265652657526585265952660526615266252663526645266552666526675266852669526705267152672526735267452675526765267752678526795268052681526825268352684526855268652687526885268952690526915269252693526945269552696526975269852699527005270152702527035270452705527065270752708527095271052711527125271352714527155271652717527185271952720527215272252723527245272552726527275272852729527305273152732527335273452735527365273752738527395274052741527425274352744527455274652747527485274952750527515275252753527545275552756527575275852759527605276152762527635276452765527665276752768527695277052771527725277352774527755277652777527785277952780527815278252783527845278552786527875278852789527905279152792527935279452795527965279752798527995280052801528025280352804528055280652807528085280952810528115281252813528145281552816528175281852819528205282152822528235282452825528265282752828528295283052831528325283352834528355283652837528385283952840528415284252843528445284552846528475284852849528505285152852528535285452855528565285752858528595286052861528625286352864528655286652867528685286952870528715287252873528745287552876528775287852879528805288152882528835288452885528865288752888528895289052891528925289352894528955289652897528985289952900529015290252903529045290552906529075290852909529105291152912529135291452915529165291752918529195292052921529225292352924529255292652927529285292952930529315293252933529345293552936529375293852939529405294152942529435294452945529465294752948529495295052951529525295352954529555295652957529585295952960529615296252963529645296552966529675296852969529705297152972529735297452975529765297752978529795298052981529825298352984529855298652987529885298952990529915299252993529945299552996529975299852999530005300153002530035300453005530065300753008530095301053011530125301353014530155301653017530185301953020530215302253023530245302553026530275302853029530305303153032530335303453035530365303753038530395304053041530425304353044530455304653047530485304953050530515305253053530545305553056530575305853059530605306153062530635306453065530665306753068530695307053071530725307353074530755307653077530785307953080530815308253083530845308553086530875308853089530905309153092530935309453095530965309753098530995310053101531025310353104531055310653107531085310953110531115311253113531145311553116531175311853119531205312153122531235312453125531265312753128531295313053131531325313353134531355313653137531385313953140531415314253143531445314553146531475314853149531505315153152531535315453155531565315753158531595316053161531625316353164531655316653167531685316953170531715317253173531745317553176531775317853179531805318153182531835318453185531865318753188531895319053191531925319353194531955319653197531985319953200532015320253203532045320553206532075320853209532105321153212532135321453215532165321753218532195322053221532225322353224532255322653227532285322953230532315323253233532345323553236532375323853239532405324153242532435324453245532465324753248532495325053251532525325353254532555325653257532585325953260532615326253263532645326553266532675326853269532705327153272532735327453275532765327753278532795328053281532825328353284532855328653287532885328953290532915329253293532945329553296532975329853299533005330153302533035330453305533065330753308533095331053311533125331353314533155331653317533185331953320533215332253323533245332553326533275332853329533305333153332533335333453335533365333753338533395334053341533425334353344533455334653347533485334953350533515335253353533545335553356533575335853359533605336153362533635336453365533665336753368533695337053371533725337353374533755337653377533785337953380533815338253383533845338553386533875338853389533905339153392533935339453395533965339753398533995340053401534025340353404534055340653407534085340953410534115341253413534145341553416534175341853419534205342153422534235342453425534265342753428534295343053431534325343353434534355343653437534385343953440534415344253443534445344553446534475344853449534505345153452534535345453455534565345753458534595346053461534625346353464534655346653467534685346953470534715347253473534745347553476534775347853479534805348153482534835348453485534865348753488534895349053491534925349353494534955349653497534985349953500535015350253503535045350553506535075350853509535105351153512535135351453515535165351753518535195352053521535225352353524535255352653527535285352953530535315353253533535345353553536535375353853539535405354153542535435354453545535465354753548535495355053551535525355353554535555355653557535585355953560535615356253563535645356553566535675356853569535705357153572535735357453575535765357753578535795358053581535825358353584535855358653587535885358953590535915359253593535945359553596535975359853599536005360153602536035360453605536065360753608536095361053611536125361353614536155361653617536185361953620536215362253623536245362553626536275362853629536305363153632536335363453635536365363753638536395364053641536425364353644536455364653647536485364953650536515365253653536545365553656536575365853659536605366153662536635366453665536665366753668536695367053671536725367353674536755367653677536785367953680536815368253683536845368553686536875368853689536905369153692536935369453695536965369753698536995370053701537025370353704537055370653707537085370953710537115371253713537145371553716537175371853719537205372153722537235372453725537265372753728537295373053731537325373353734537355373653737537385373953740537415374253743537445374553746537475374853749537505375153752537535375453755537565375753758537595376053761537625376353764537655376653767537685376953770537715377253773537745377553776537775377853779537805378153782537835378453785537865378753788537895379053791537925379353794537955379653797537985379953800538015380253803538045380553806538075380853809538105381153812538135381453815538165381753818538195382053821538225382353824538255382653827538285382953830538315383253833538345383553836538375383853839538405384153842538435384453845538465384753848538495385053851538525385353854538555385653857538585385953860538615386253863538645386553866538675386853869538705387153872538735387453875538765387753878538795388053881538825388353884538855388653887538885388953890538915389253893538945389553896538975389853899539005390153902539035390453905539065390753908539095391053911539125391353914539155391653917539185391953920539215392253923539245392553926539275392853929539305393153932539335393453935539365393753938539395394053941539425394353944539455394653947539485394953950539515395253953539545395553956539575395853959539605396153962539635396453965539665396753968539695397053971539725397353974539755397653977539785397953980539815398253983539845398553986539875398853989539905399153992539935399453995539965399753998539995400054001540025400354004540055400654007540085400954010540115401254013540145401554016540175401854019540205402154022540235402454025540265402754028540295403054031540325403354034540355403654037540385403954040540415404254043540445404554046540475404854049540505405154052540535405454055540565405754058540595406054061540625406354064540655406654067540685406954070540715407254073540745407554076540775407854079540805408154082540835408454085540865408754088540895409054091540925409354094540955409654097540985409954100541015410254103541045410554106541075410854109541105411154112541135411454115541165411754118541195412054121541225412354124541255412654127541285412954130541315413254133541345413554136541375413854139541405414154142541435414454145541465414754148541495415054151541525415354154541555415654157541585415954160541615416254163541645416554166541675416854169541705417154172541735417454175541765417754178541795418054181541825418354184541855418654187541885418954190541915419254193541945419554196541975419854199542005420154202542035420454205542065420754208542095421054211542125421354214542155421654217542185421954220542215422254223542245422554226542275422854229542305423154232542335423454235542365423754238542395424054241542425424354244542455424654247542485424954250542515425254253542545425554256542575425854259542605426154262542635426454265542665426754268542695427054271542725427354274542755427654277542785427954280542815428254283542845428554286542875428854289542905429154292542935429454295542965429754298542995430054301543025430354304543055430654307543085430954310543115431254313543145431554316543175431854319543205432154322543235432454325543265432754328543295433054331543325433354334543355433654337543385433954340543415434254343543445434554346543475434854349543505435154352543535435454355543565435754358543595436054361543625436354364543655436654367543685436954370543715437254373543745437554376543775437854379543805438154382543835438454385543865438754388543895439054391543925439354394543955439654397543985439954400544015440254403544045440554406544075440854409544105441154412544135441454415544165441754418544195442054421544225442354424544255442654427544285442954430544315443254433544345443554436544375443854439544405444154442544435444454445544465444754448544495445054451544525445354454544555445654457544585445954460544615446254463544645446554466544675446854469544705447154472544735447454475544765447754478544795448054481544825448354484544855448654487544885448954490544915449254493544945449554496544975449854499545005450154502545035450454505545065450754508545095451054511545125451354514545155451654517545185451954520545215452254523545245452554526545275452854529545305453154532545335453454535545365453754538545395454054541545425454354544545455454654547545485454954550545515455254553545545455554556545575455854559545605456154562545635456454565545665456754568545695457054571545725457354574545755457654577545785457954580545815458254583545845458554586545875458854589545905459154592545935459454595545965459754598545995460054601546025460354604546055460654607546085460954610546115461254613546145461554616546175461854619546205462154622546235462454625546265462754628546295463054631546325463354634546355463654637546385463954640546415464254643546445464554646546475464854649546505465154652546535465454655546565465754658546595466054661546625466354664546655466654667546685466954670546715467254673546745467554676546775467854679546805468154682546835468454685546865468754688546895469054691546925469354694546955469654697546985469954700547015470254703547045470554706547075470854709547105471154712547135471454715547165471754718547195472054721547225472354724547255472654727547285472954730547315473254733547345473554736547375473854739547405474154742547435474454745547465474754748547495475054751547525475354754547555475654757547585475954760547615476254763547645476554766547675476854769547705477154772547735477454775547765477754778547795478054781547825478354784547855478654787547885478954790547915479254793547945479554796547975479854799548005480154802548035480454805548065480754808548095481054811548125481354814548155481654817548185481954820548215482254823548245482554826548275482854829548305483154832548335483454835548365483754838548395484054841548425484354844548455484654847548485484954850548515485254853548545485554856548575485854859548605486154862548635486454865548665486754868548695487054871548725487354874548755487654877548785487954880548815488254883548845488554886548875488854889548905489154892548935489454895548965489754898548995490054901549025490354904549055490654907549085490954910549115491254913549145491554916549175491854919549205492154922549235492454925549265492754928549295493054931549325493354934549355493654937549385493954940549415494254943549445494554946549475494854949549505495154952549535495454955549565495754958549595496054961549625496354964549655496654967549685496954970549715497254973549745497554976549775497854979549805498154982549835498454985549865498754988549895499054991549925499354994549955499654997549985499955000550015500255003550045500555006550075500855009550105501155012550135501455015550165501755018550195502055021550225502355024550255502655027550285502955030550315503255033550345503555036550375503855039550405504155042550435504455045550465504755048550495505055051550525505355054550555505655057550585505955060550615506255063550645506555066550675506855069550705507155072550735507455075550765507755078550795508055081550825508355084550855508655087550885508955090550915509255093550945509555096550975509855099551005510155102551035510455105551065510755108551095511055111551125511355114551155511655117551185511955120551215512255123551245512555126551275512855129551305513155132551335513455135551365513755138551395514055141551425514355144551455514655147551485514955150551515515255153551545515555156551575515855159551605516155162551635516455165551665516755168551695517055171551725517355174551755517655177551785517955180551815518255183551845518555186551875518855189551905519155192551935519455195551965519755198551995520055201552025520355204552055520655207552085520955210552115521255213552145521555216552175521855219552205522155222552235522455225552265522755228552295523055231552325523355234552355523655237552385523955240552415524255243552445524555246552475524855249552505525155252552535525455255552565525755258552595526055261552625526355264552655526655267552685526955270552715527255273552745527555276552775527855279552805528155282552835528455285552865528755288552895529055291552925529355294552955529655297552985529955300553015530255303553045530555306553075530855309553105531155312553135531455315553165531755318553195532055321553225532355324553255532655327553285532955330553315533255333553345533555336553375533855339553405534155342553435534455345553465534755348553495535055351553525535355354553555535655357553585535955360553615536255363553645536555366553675536855369553705537155372553735537455375553765537755378553795538055381553825538355384553855538655387553885538955390553915539255393553945539555396553975539855399554005540155402554035540455405554065540755408554095541055411554125541355414554155541655417554185541955420554215542255423554245542555426554275542855429554305543155432554335543455435554365543755438554395544055441554425544355444554455544655447554485544955450554515545255453554545545555456554575545855459554605546155462554635546455465554665546755468554695547055471554725547355474554755547655477554785547955480554815548255483554845548555486554875548855489554905549155492554935549455495554965549755498554995550055501555025550355504555055550655507555085550955510555115551255513555145551555516555175551855519555205552155522555235552455525555265552755528555295553055531555325553355534555355553655537555385553955540555415554255543555445554555546555475554855549555505555155552555535555455555555565555755558555595556055561555625556355564555655556655567555685556955570555715557255573555745557555576555775557855579555805558155582555835558455585555865558755588555895559055591555925559355594555955559655597555985559955600556015560255603556045560555606556075560855609556105561155612556135561455615556165561755618556195562055621556225562355624556255562655627556285562955630556315563255633556345563555636556375563855639556405564155642556435564455645556465564755648556495565055651556525565355654556555565655657556585565955660556615566255663556645566555666556675566855669556705567155672556735567455675556765567755678556795568055681556825568355684556855568655687556885568955690556915569255693556945569555696556975569855699557005570155702557035570455705557065570755708557095571055711557125571355714557155571655717557185571955720557215572255723557245572555726557275572855729557305573155732557335573455735557365573755738557395574055741557425574355744557455574655747557485574955750557515575255753557545575555756557575575855759557605576155762557635576455765557665576755768557695577055771557725577355774557755577655777557785577955780557815578255783557845578555786557875578855789557905579155792557935579455795557965579755798557995580055801558025580355804558055580655807558085580955810558115581255813558145581555816558175581855819558205582155822558235582455825558265582755828558295583055831558325583355834558355583655837558385583955840558415584255843558445584555846558475584855849558505585155852558535585455855558565585755858558595586055861558625586355864558655586655867558685586955870558715587255873558745587555876558775587855879558805588155882558835588455885558865588755888558895589055891558925589355894558955589655897558985589955900559015590255903559045590555906559075590855909559105591155912559135591455915559165591755918559195592055921559225592355924559255592655927559285592955930559315593255933559345593555936559375593855939559405594155942559435594455945559465594755948559495595055951559525595355954559555595655957559585595955960559615596255963559645596555966559675596855969559705597155972559735597455975559765597755978559795598055981559825598355984559855598655987559885598955990559915599255993559945599555996559975599855999560005600156002560035600456005560065600756008560095601056011560125601356014560155601656017560185601956020560215602256023560245602556026560275602856029560305603156032560335603456035560365603756038560395604056041560425604356044560455604656047560485604956050560515605256053560545605556056560575605856059560605606156062560635606456065560665606756068560695607056071560725607356074560755607656077560785607956080560815608256083560845608556086560875608856089560905609156092560935609456095560965609756098560995610056101561025610356104561055610656107561085610956110561115611256113561145611556116561175611856119561205612156122561235612456125561265612756128561295613056131561325613356134561355613656137561385613956140561415614256143561445614556146561475614856149561505615156152561535615456155561565615756158561595616056161561625616356164561655616656167561685616956170561715617256173561745617556176561775617856179561805618156182561835618456185561865618756188561895619056191561925619356194561955619656197561985619956200562015620256203562045620556206562075620856209562105621156212562135621456215562165621756218562195622056221562225622356224562255622656227562285622956230562315623256233562345623556236562375623856239562405624156242562435624456245562465624756248562495625056251562525625356254562555625656257562585625956260562615626256263562645626556266562675626856269562705627156272562735627456275562765627756278562795628056281562825628356284562855628656287562885628956290562915629256293562945629556296562975629856299563005630156302563035630456305563065630756308563095631056311563125631356314563155631656317563185631956320563215632256323563245632556326563275632856329563305633156332563335633456335563365633756338563395634056341563425634356344563455634656347563485634956350563515635256353563545635556356563575635856359563605636156362563635636456365563665636756368563695637056371563725637356374563755637656377563785637956380563815638256383563845638556386563875638856389563905639156392563935639456395563965639756398563995640056401564025640356404564055640656407564085640956410564115641256413564145641556416564175641856419564205642156422564235642456425564265642756428564295643056431564325643356434564355643656437564385643956440564415644256443564445644556446564475644856449564505645156452564535645456455564565645756458564595646056461564625646356464564655646656467564685646956470564715647256473564745647556476564775647856479564805648156482564835648456485564865648756488564895649056491564925649356494564955649656497564985649956500565015650256503565045650556506565075650856509565105651156512565135651456515565165651756518565195652056521565225652356524565255652656527565285652956530565315653256533565345653556536565375653856539565405654156542565435654456545565465654756548565495655056551565525655356554565555655656557565585655956560565615656256563565645656556566565675656856569565705657156572565735657456575565765657756578565795658056581565825658356584565855658656587565885658956590565915659256593565945659556596565975659856599566005660156602566035660456605566065660756608566095661056611566125661356614566155661656617566185661956620566215662256623566245662556626566275662856629566305663156632566335663456635566365663756638566395664056641566425664356644566455664656647566485664956650566515665256653566545665556656566575665856659566605666156662566635666456665566665666756668566695667056671566725667356674566755667656677566785667956680566815668256683566845668556686566875668856689566905669156692566935669456695566965669756698566995670056701567025670356704567055670656707567085670956710567115671256713567145671556716567175671856719567205672156722567235672456725567265672756728567295673056731567325673356734567355673656737567385673956740567415674256743567445674556746567475674856749567505675156752567535675456755567565675756758567595676056761567625676356764567655676656767567685676956770567715677256773567745677556776567775677856779567805678156782567835678456785567865678756788567895679056791567925679356794567955679656797567985679956800568015680256803568045680556806568075680856809568105681156812568135681456815568165681756818568195682056821568225682356824568255682656827568285682956830568315683256833568345683556836568375683856839568405684156842568435684456845568465684756848568495685056851568525685356854568555685656857568585685956860568615686256863568645686556866568675686856869568705687156872568735687456875568765687756878568795688056881568825688356884568855688656887568885688956890568915689256893568945689556896568975689856899569005690156902569035690456905569065690756908569095691056911569125691356914569155691656917569185691956920569215692256923569245692556926569275692856929569305693156932569335693456935569365693756938569395694056941569425694356944569455694656947569485694956950569515695256953569545695556956569575695856959569605696156962569635696456965569665696756968569695697056971569725697356974569755697656977569785697956980569815698256983569845698556986569875698856989569905699156992569935699456995569965699756998569995700057001570025700357004570055700657007570085700957010570115701257013570145701557016570175701857019570205702157022570235702457025570265702757028570295703057031570325703357034570355703657037570385703957040570415704257043570445704557046570475704857049570505705157052570535705457055570565705757058570595706057061570625706357064570655706657067570685706957070570715707257073570745707557076570775707857079570805708157082570835708457085570865708757088570895709057091570925709357094570955709657097570985709957100571015710257103571045710557106571075710857109571105711157112571135711457115571165711757118571195712057121571225712357124571255712657127571285712957130571315713257133571345713557136571375713857139571405714157142571435714457145571465714757148571495715057151571525715357154571555715657157571585715957160571615716257163571645716557166571675716857169571705717157172571735717457175571765717757178571795718057181571825718357184571855718657187571885718957190571915719257193571945719557196571975719857199572005720157202572035720457205572065720757208572095721057211572125721357214572155721657217572185721957220572215722257223572245722557226572275722857229572305723157232572335723457235572365723757238572395724057241572425724357244572455724657247572485724957250572515725257253572545725557256572575725857259572605726157262572635726457265572665726757268572695727057271572725727357274572755727657277572785727957280572815728257283572845728557286572875728857289572905729157292572935729457295572965729757298572995730057301573025730357304573055730657307573085730957310573115731257313573145731557316573175731857319573205732157322573235732457325573265732757328573295733057331573325733357334573355733657337573385733957340573415734257343573445734557346573475734857349573505735157352573535735457355573565735757358573595736057361573625736357364573655736657367573685736957370573715737257373573745737557376573775737857379573805738157382573835738457385573865738757388573895739057391573925739357394573955739657397573985739957400574015740257403574045740557406574075740857409574105741157412574135741457415574165741757418574195742057421574225742357424574255742657427574285742957430574315743257433574345743557436574375743857439574405744157442574435744457445574465744757448574495745057451574525745357454574555745657457574585745957460574615746257463574645746557466574675746857469574705747157472574735747457475574765747757478574795748057481574825748357484574855748657487574885748957490574915749257493574945749557496574975749857499575005750157502575035750457505575065750757508575095751057511575125751357514575155751657517575185751957520575215752257523575245752557526575275752857529575305753157532575335753457535575365753757538575395754057541575425754357544575455754657547575485754957550575515755257553575545755557556575575755857559575605756157562575635756457565575665756757568575695757057571575725757357574575755757657577575785757957580575815758257583575845758557586575875758857589575905759157592575935759457595575965759757598575995760057601576025760357604576055760657607576085760957610576115761257613576145761557616576175761857619576205762157622576235762457625576265762757628576295763057631576325763357634576355763657637576385763957640576415764257643576445764557646576475764857649576505765157652576535765457655576565765757658576595766057661576625766357664576655766657667576685766957670576715767257673576745767557676576775767857679576805768157682576835768457685576865768757688576895769057691576925769357694576955769657697576985769957700577015770257703577045770557706577075770857709577105771157712577135771457715577165771757718577195772057721577225772357724577255772657727577285772957730577315773257733577345773557736577375773857739577405774157742577435774457745577465774757748577495775057751577525775357754577555775657757577585775957760577615776257763577645776557766577675776857769577705777157772577735777457775577765777757778577795778057781577825778357784577855778657787577885778957790577915779257793577945779557796577975779857799578005780157802578035780457805578065780757808578095781057811578125781357814578155781657817578185781957820578215782257823578245782557826578275782857829578305783157832578335783457835578365783757838578395784057841578425784357844578455784657847578485784957850578515785257853578545785557856578575785857859578605786157862578635786457865578665786757868578695787057871578725787357874578755787657877578785787957880578815788257883578845788557886578875788857889578905789157892578935789457895578965789757898578995790057901579025790357904579055790657907579085790957910579115791257913579145791557916579175791857919579205792157922579235792457925579265792757928579295793057931579325793357934579355793657937579385793957940579415794257943579445794557946579475794857949579505795157952579535795457955579565795757958579595796057961579625796357964579655796657967579685796957970579715797257973579745797557976579775797857979579805798157982579835798457985579865798757988579895799057991579925799357994579955799657997579985799958000580015800258003580045800558006580075800858009580105801158012580135801458015580165801758018580195802058021580225802358024580255802658027580285802958030580315803258033580345803558036580375803858039580405804158042580435804458045580465804758048580495805058051580525805358054580555805658057580585805958060580615806258063580645806558066580675806858069580705807158072580735807458075580765807758078580795808058081580825808358084580855808658087580885808958090580915809258093580945809558096580975809858099581005810158102581035810458105581065810758108581095811058111581125811358114581155811658117581185811958120581215812258123581245812558126581275812858129581305813158132581335813458135581365813758138581395814058141581425814358144581455814658147581485814958150581515815258153581545815558156581575815858159581605816158162581635816458165581665816758168581695817058171581725817358174581755817658177581785817958180581815818258183581845818558186581875818858189581905819158192581935819458195581965819758198581995820058201582025820358204582055820658207582085820958210582115821258213582145821558216582175821858219582205822158222582235822458225582265822758228582295823058231582325823358234582355823658237582385823958240582415824258243582445824558246582475824858249582505825158252582535825458255582565825758258582595826058261582625826358264582655826658267582685826958270582715827258273582745827558276582775827858279582805828158282582835828458285582865828758288582895829058291582925829358294582955829658297582985829958300583015830258303583045830558306583075830858309583105831158312583135831458315583165831758318583195832058321583225832358324583255832658327583285832958330583315833258333583345833558336583375833858339583405834158342583435834458345583465834758348583495835058351583525835358354583555835658357583585835958360583615836258363583645836558366583675836858369583705837158372583735837458375583765837758378583795838058381583825838358384583855838658387583885838958390583915839258393583945839558396583975839858399584005840158402584035840458405584065840758408584095841058411584125841358414584155841658417584185841958420584215842258423584245842558426584275842858429584305843158432584335843458435584365843758438584395844058441584425844358444584455844658447584485844958450584515845258453584545845558456584575845858459584605846158462584635846458465584665846758468584695847058471584725847358474584755847658477584785847958480584815848258483584845848558486584875848858489584905849158492584935849458495584965849758498584995850058501585025850358504585055850658507585085850958510585115851258513585145851558516585175851858519585205852158522585235852458525585265852758528585295853058531585325853358534585355853658537585385853958540585415854258543585445854558546585475854858549585505855158552585535855458555585565855758558585595856058561585625856358564585655856658567585685856958570585715857258573585745857558576585775857858579585805858158582585835858458585585865858758588585895859058591585925859358594585955859658597585985859958600586015860258603586045860558606586075860858609586105861158612586135861458615586165861758618586195862058621586225862358624586255862658627586285862958630586315863258633586345863558636586375863858639586405864158642586435864458645586465864758648586495865058651586525865358654586555865658657586585865958660586615866258663586645866558666586675866858669586705867158672586735867458675586765867758678586795868058681586825868358684586855868658687586885868958690586915869258693586945869558696586975869858699587005870158702587035870458705587065870758708587095871058711587125871358714587155871658717587185871958720587215872258723587245872558726587275872858729587305873158732587335873458735587365873758738587395874058741587425874358744587455874658747587485874958750587515875258753587545875558756587575875858759587605876158762587635876458765587665876758768587695877058771587725877358774587755877658777587785877958780587815878258783587845878558786587875878858789587905879158792587935879458795587965879758798587995880058801588025880358804588055880658807588085880958810588115881258813588145881558816588175881858819588205882158822588235882458825588265882758828588295883058831588325883358834588355883658837588385883958840588415884258843588445884558846588475884858849588505885158852588535885458855588565885758858588595886058861588625886358864588655886658867588685886958870588715887258873588745887558876588775887858879588805888158882588835888458885588865888758888588895889058891588925889358894588955889658897588985889958900589015890258903589045890558906589075890858909589105891158912589135891458915589165891758918589195892058921589225892358924589255892658927589285892958930589315893258933589345893558936589375893858939589405894158942589435894458945589465894758948589495895058951589525895358954589555895658957589585895958960589615896258963589645896558966589675896858969589705897158972589735897458975589765897758978589795898058981589825898358984589855898658987589885898958990589915899258993589945899558996589975899858999590005900159002590035900459005590065900759008590095901059011590125901359014590155901659017590185901959020590215902259023590245902559026590275902859029590305903159032590335903459035590365903759038590395904059041590425904359044590455904659047590485904959050590515905259053590545905559056590575905859059590605906159062590635906459065590665906759068590695907059071590725907359074590755907659077590785907959080590815908259083590845908559086590875908859089590905909159092590935909459095590965909759098590995910059101591025910359104591055910659107591085910959110591115911259113591145911559116591175911859119591205912159122591235912459125591265912759128591295913059131591325913359134591355913659137591385913959140591415914259143591445914559146591475914859149591505915159152591535915459155591565915759158591595916059161591625916359164591655916659167591685916959170591715917259173591745917559176591775917859179591805918159182591835918459185591865918759188591895919059191591925919359194591955919659197591985919959200592015920259203592045920559206592075920859209592105921159212592135921459215592165921759218592195922059221592225922359224592255922659227592285922959230592315923259233592345923559236592375923859239592405924159242592435924459245592465924759248592495925059251592525925359254592555925659257592585925959260592615926259263592645926559266592675926859269592705927159272592735927459275592765927759278592795928059281592825928359284592855928659287592885928959290592915929259293592945929559296592975929859299593005930159302593035930459305593065930759308593095931059311593125931359314593155931659317593185931959320593215932259323593245932559326593275932859329593305933159332593335933459335593365933759338593395934059341593425934359344593455934659347593485934959350593515935259353593545935559356593575935859359593605936159362593635936459365593665936759368593695937059371593725937359374593755937659377593785937959380593815938259383593845938559386593875938859389593905939159392593935939459395593965939759398593995940059401594025940359404594055940659407594085940959410594115941259413594145941559416594175941859419594205942159422594235942459425594265942759428594295943059431594325943359434594355943659437594385943959440594415944259443594445944559446594475944859449594505945159452594535945459455594565945759458594595946059461594625946359464594655946659467594685946959470594715947259473594745947559476594775947859479594805948159482594835948459485594865948759488594895949059491594925949359494594955949659497594985949959500595015950259503595045950559506595075950859509595105951159512595135951459515595165951759518595195952059521595225952359524595255952659527595285952959530595315953259533595345953559536595375953859539595405954159542595435954459545595465954759548595495955059551595525955359554595555955659557595585955959560595615956259563595645956559566595675956859569595705957159572595735957459575595765957759578595795958059581595825958359584595855958659587595885958959590595915959259593595945959559596595975959859599596005960159602596035960459605596065960759608596095961059611596125961359614596155961659617596185961959620596215962259623596245962559626596275962859629596305963159632596335963459635596365963759638596395964059641596425964359644596455964659647596485964959650596515965259653596545965559656596575965859659596605966159662596635966459665596665966759668596695967059671596725967359674596755967659677596785967959680596815968259683596845968559686596875968859689596905969159692596935969459695596965969759698596995970059701597025970359704597055970659707597085970959710597115971259713597145971559716597175971859719597205972159722597235972459725597265972759728597295973059731597325973359734597355973659737597385973959740597415974259743597445974559746597475974859749597505975159752597535975459755597565975759758597595976059761597625976359764597655976659767597685976959770597715977259773597745977559776597775977859779597805978159782597835978459785597865978759788597895979059791597925979359794597955979659797597985979959800598015980259803598045980559806598075980859809598105981159812598135981459815598165981759818598195982059821598225982359824598255982659827598285982959830598315983259833598345983559836598375983859839598405984159842598435984459845598465984759848598495985059851598525985359854598555985659857598585985959860598615986259863598645986559866598675986859869598705987159872598735987459875598765987759878598795988059881598825988359884598855988659887598885988959890598915989259893598945989559896598975989859899599005990159902599035990459905599065990759908599095991059911599125991359914599155991659917599185991959920599215992259923599245992559926599275992859929599305993159932599335993459935599365993759938599395994059941599425994359944599455994659947599485994959950599515995259953599545995559956599575995859959599605996159962599635996459965599665996759968599695997059971599725997359974599755997659977599785997959980599815998259983599845998559986599875998859989599905999159992599935999459995599965999759998599996000060001600026000360004600056000660007600086000960010600116001260013600146001560016600176001860019600206002160022600236002460025600266002760028600296003060031600326003360034600356003660037600386003960040600416004260043600446004560046600476004860049600506005160052600536005460055600566005760058600596006060061600626006360064600656006660067600686006960070600716007260073600746007560076600776007860079600806008160082600836008460085600866008760088600896009060091600926009360094600956009660097600986009960100601016010260103601046010560106601076010860109601106011160112601136011460115601166011760118601196012060121601226012360124601256012660127601286012960130601316013260133601346013560136601376013860139601406014160142601436014460145601466014760148601496015060151601526015360154601556015660157601586015960160601616016260163601646016560166601676016860169601706017160172601736017460175601766017760178601796018060181601826018360184601856018660187601886018960190601916019260193601946019560196601976019860199602006020160202602036020460205602066020760208602096021060211602126021360214602156021660217602186021960220602216022260223602246022560226602276022860229602306023160232602336023460235602366023760238602396024060241602426024360244602456024660247602486024960250602516025260253602546025560256602576025860259602606026160262602636026460265602666026760268602696027060271602726027360274602756027660277602786027960280602816028260283602846028560286602876028860289602906029160292602936029460295602966029760298602996030060301603026030360304603056030660307603086030960310603116031260313603146031560316603176031860319603206032160322603236032460325603266032760328603296033060331603326033360334603356033660337603386033960340603416034260343 | 
							- /**
 
-  * @license
 
-  * Video.js 8.3.0 <http://videojs.com/>
 
-  * Copyright Brightcove, Inc. <https://www.brightcove.com/>
 
-  * Available under Apache License Version 2.0
 
-  * <https://github.com/videojs/video.js/blob/main/LICENSE>
 
-  *
 
-  * Includes vtt.js <https://github.com/mozilla/vtt.js>
 
-  * Available under Apache License Version 2.0
 
-  * <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
 
-  */
 
- (function (global, factory) {
 
-   typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
 
-   typeof define === 'function' && define.amd ? define(factory) :
 
-   (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojs = factory());
 
- })(this, (function () { 'use strict';
 
-   var version$5 = "8.3.0";
 
-   /**
 
-    * An Object that contains lifecycle hooks as keys which point to an array
 
-    * of functions that are run when a lifecycle is triggered
 
-    *
 
-    * @private
 
-    */
 
-   const hooks_ = {};
 
-   /**
 
-    * Get a list of hooks for a specific lifecycle
 
-    *
 
-    * @param  {string} type
 
-    *         the lifecycle to get hooks from
 
-    *
 
-    * @param  {Function|Function[]} [fn]
 
-    *         Optionally add a hook (or hooks) to the lifecycle that your are getting.
 
-    *
 
-    * @return {Array}
 
-    *         an array of hooks, or an empty array if there are none.
 
-    */
 
-   const hooks = function (type, fn) {
 
-     hooks_[type] = hooks_[type] || [];
 
-     if (fn) {
 
-       hooks_[type] = hooks_[type].concat(fn);
 
-     }
 
-     return hooks_[type];
 
-   };
 
-   /**
 
-    * Add a function hook to a specific videojs lifecycle.
 
-    *
 
-    * @param {string} type
 
-    *        the lifecycle to hook the function to.
 
-    *
 
-    * @param {Function|Function[]}
 
-    *        The function or array of functions to attach.
 
-    */
 
-   const hook = function (type, fn) {
 
-     hooks(type, fn);
 
-   };
 
-   /**
 
-    * Remove a hook from a specific videojs lifecycle.
 
-    *
 
-    * @param  {string} type
 
-    *         the lifecycle that the function hooked to
 
-    *
 
-    * @param  {Function} fn
 
-    *         The hooked function to remove
 
-    *
 
-    * @return {boolean}
 
-    *         The function that was removed or undef
 
-    */
 
-   const removeHook = function (type, fn) {
 
-     const index = hooks(type).indexOf(fn);
 
-     if (index <= -1) {
 
-       return false;
 
-     }
 
-     hooks_[type] = hooks_[type].slice();
 
-     hooks_[type].splice(index, 1);
 
-     return true;
 
-   };
 
-   /**
 
-    * Add a function hook that will only run once to a specific videojs lifecycle.
 
-    *
 
-    * @param {string} type
 
-    *        the lifecycle to hook the function to.
 
-    *
 
-    * @param {Function|Function[]}
 
-    *        The function or array of functions to attach.
 
-    */
 
-   const hookOnce = function (type, fn) {
 
-     hooks(type, [].concat(fn).map(original => {
 
-       const wrapper = (...args) => {
 
-         removeHook(type, wrapper);
 
-         return original(...args);
 
-       };
 
-       return wrapper;
 
-     }));
 
-   };
 
-   /**
 
-    * @file fullscreen-api.js
 
-    * @module fullscreen-api
 
-    */
 
-   /**
 
-    * Store the browser-specific methods for the fullscreen API.
 
-    *
 
-    * @type {Object}
 
-    * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
 
-    * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
 
-    */
 
-   const FullscreenApi = {
 
-     prefixed: true
 
-   };
 
-   // browser API methods
 
-   const apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'],
 
-   // WebKit
 
-   ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'],
 
-   // Mozilla
 
-   ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'],
 
-   // Microsoft
 
-   ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
 
-   const specApi = apiMap[0];
 
-   let browserApi;
 
-   // determine the supported set of functions
 
-   for (let i = 0; i < apiMap.length; i++) {
 
-     // check for exitFullscreen function
 
-     if (apiMap[i][1] in document) {
 
-       browserApi = apiMap[i];
 
-       break;
 
-     }
 
-   }
 
-   // map the browser API names to the spec API names
 
-   if (browserApi) {
 
-     for (let i = 0; i < browserApi.length; i++) {
 
-       FullscreenApi[specApi[i]] = browserApi[i];
 
-     }
 
-     FullscreenApi.prefixed = browserApi[0] !== specApi[0];
 
-   }
 
-   /**
 
-    * @file create-logger.js
 
-    * @module create-logger
 
-    */
 
-   // This is the private tracking variable for the logging history.
 
-   let history = [];
 
-   /**
 
-    * Log messages to the console and history based on the type of message
 
-    *
 
-    * @private
 
-    * @param  {string} type
 
-    *         The name of the console method to use.
 
-    *
 
-    * @param  {Array} args
 
-    *         The arguments to be passed to the matching console method.
 
-    */
 
-   const LogByTypeFactory = (name, log) => (type, level, args) => {
 
-     const lvl = log.levels[level];
 
-     const lvlRegExp = new RegExp(`^(${lvl})$`);
 
-     if (type !== 'log') {
 
-       // Add the type to the front of the message when it's not "log".
 
-       args.unshift(type.toUpperCase() + ':');
 
-     }
 
-     // Add console prefix after adding to history.
 
-     args.unshift(name + ':');
 
-     // Add a clone of the args at this point to history.
 
-     if (history) {
 
-       history.push([].concat(args));
 
-       // only store 1000 history entries
 
-       const splice = history.length - 1000;
 
-       history.splice(0, splice > 0 ? splice : 0);
 
-     }
 
-     // If there's no console then don't try to output messages, but they will
 
-     // still be stored in history.
 
-     if (!window.console) {
 
-       return;
 
-     }
 
-     // Was setting these once outside of this function, but containing them
 
-     // in the function makes it easier to test cases where console doesn't exist
 
-     // when the module is executed.
 
-     let fn = window.console[type];
 
-     if (!fn && type === 'debug') {
 
-       // Certain browsers don't have support for console.debug. For those, we
 
-       // should default to the closest comparable log.
 
-       fn = window.console.info || window.console.log;
 
-     }
 
-     // Bail out if there's no console or if this type is not allowed by the
 
-     // current logging level.
 
-     if (!fn || !lvl || !lvlRegExp.test(type)) {
 
-       return;
 
-     }
 
-     fn[Array.isArray(args) ? 'apply' : 'call'](window.console, args);
 
-   };
 
-   function createLogger$1(name) {
 
-     // This is the private tracking variable for logging level.
 
-     let level = 'info';
 
-     // the curried logByType bound to the specific log and history
 
-     let logByType;
 
-     /**
 
-      * Logs plain debug messages. Similar to `console.log`.
 
-      *
 
-      * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
 
-      * of our JSDoc template, we cannot properly document this as both a function
 
-      * and a namespace, so its function signature is documented here.
 
-      *
 
-      * #### Arguments
 
-      * ##### *args
 
-      * *[]
 
-      *
 
-      * Any combination of values that could be passed to `console.log()`.
 
-      *
 
-      * #### Return Value
 
-      *
 
-      * `undefined`
 
-      *
 
-      * @namespace
 
-      * @param    {...*} args
 
-      *           One or more messages or objects that should be logged.
 
-      */
 
-     const log = function (...args) {
 
-       logByType('log', level, args);
 
-     };
 
-     // This is the logByType helper that the logging methods below use
 
-     logByType = LogByTypeFactory(name, log);
 
-     /**
 
-      * Create a new sublogger which chains the old name to the new name.
 
-      *
 
-      * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
 
-      * ```js
 
-      *  mylogger('foo');
 
-      *  // > VIDEOJS: player: foo
 
-      * ```
 
-      *
 
-      * @param {string} name
 
-      *        The name to add call the new logger
 
-      * @return {Object}
 
-      */
 
-     log.createLogger = subname => createLogger$1(name + ': ' + subname);
 
-     /**
 
-      * Enumeration of available logging levels, where the keys are the level names
 
-      * and the values are `|`-separated strings containing logging methods allowed
 
-      * in that logging level. These strings are used to create a regular expression
 
-      * matching the function name being called.
 
-      *
 
-      * Levels provided by Video.js are:
 
-      *
 
-      * - `off`: Matches no calls. Any value that can be cast to `false` will have
 
-      *   this effect. The most restrictive.
 
-      * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
 
-      *   `log.warn`, and `log.error`).
 
-      * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
 
-      * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
 
-      * - `warn`: Matches `log.warn` and `log.error` calls.
 
-      * - `error`: Matches only `log.error` calls.
 
-      *
 
-      * @type {Object}
 
-      */
 
-     log.levels = {
 
-       all: 'debug|log|warn|error',
 
-       off: '',
 
-       debug: 'debug|log|warn|error',
 
-       info: 'log|warn|error',
 
-       warn: 'warn|error',
 
-       error: 'error',
 
-       DEFAULT: level
 
-     };
 
-     /**
 
-      * Get or set the current logging level.
 
-      *
 
-      * If a string matching a key from {@link module:log.levels} is provided, acts
 
-      * as a setter.
 
-      *
 
-      * @param  {string} [lvl]
 
-      *         Pass a valid level to set a new logging level.
 
-      *
 
-      * @return {string}
 
-      *         The current logging level.
 
-      */
 
-     log.level = lvl => {
 
-       if (typeof lvl === 'string') {
 
-         if (!log.levels.hasOwnProperty(lvl)) {
 
-           throw new Error(`"${lvl}" in not a valid log level`);
 
-         }
 
-         level = lvl;
 
-       }
 
-       return level;
 
-     };
 
-     /**
 
-      * Returns an array containing everything that has been logged to the history.
 
-      *
 
-      * This array is a shallow clone of the internal history record. However, its
 
-      * contents are _not_ cloned; so, mutating objects inside this array will
 
-      * mutate them in history.
 
-      *
 
-      * @return {Array}
 
-      */
 
-     log.history = () => history ? [].concat(history) : [];
 
-     /**
 
-      * Allows you to filter the history by the given logger name
 
-      *
 
-      * @param {string} fname
 
-      *        The name to filter by
 
-      *
 
-      * @return {Array}
 
-      *         The filtered list to return
 
-      */
 
-     log.history.filter = fname => {
 
-       return (history || []).filter(historyItem => {
 
-         // if the first item in each historyItem includes `fname`, then it's a match
 
-         return new RegExp(`.*${fname}.*`).test(historyItem[0]);
 
-       });
 
-     };
 
-     /**
 
-      * Clears the internal history tracking, but does not prevent further history
 
-      * tracking.
 
-      */
 
-     log.history.clear = () => {
 
-       if (history) {
 
-         history.length = 0;
 
-       }
 
-     };
 
-     /**
 
-      * Disable history tracking if it is currently enabled.
 
-      */
 
-     log.history.disable = () => {
 
-       if (history !== null) {
 
-         history.length = 0;
 
-         history = null;
 
-       }
 
-     };
 
-     /**
 
-      * Enable history tracking if it is currently disabled.
 
-      */
 
-     log.history.enable = () => {
 
-       if (history === null) {
 
-         history = [];
 
-       }
 
-     };
 
-     /**
 
-      * Logs error messages. Similar to `console.error`.
 
-      *
 
-      * @param {...*} args
 
-      *        One or more messages or objects that should be logged as an error
 
-      */
 
-     log.error = (...args) => logByType('error', level, args);
 
-     /**
 
-      * Logs warning messages. Similar to `console.warn`.
 
-      *
 
-      * @param {...*} args
 
-      *        One or more messages or objects that should be logged as a warning.
 
-      */
 
-     log.warn = (...args) => logByType('warn', level, args);
 
-     /**
 
-      * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
 
-      * log if `console.debug` is not available
 
-      *
 
-      * @param {...*} args
 
-      *        One or more messages or objects that should be logged as debug.
 
-      */
 
-     log.debug = (...args) => logByType('debug', level, args);
 
-     return log;
 
-   }
 
-   /**
 
-    * @file log.js
 
-    * @module log
 
-    */
 
-   const log$1 = createLogger$1('VIDEOJS');
 
-   const createLogger = log$1.createLogger;
 
-   /**
 
-    * @file obj.js
 
-    * @module obj
 
-    */
 
-   /**
 
-    * @callback obj:EachCallback
 
-    *
 
-    * @param {*} value
 
-    *        The current key for the object that is being iterated over.
 
-    *
 
-    * @param {string} key
 
-    *        The current key-value for object that is being iterated over
 
-    */
 
-   /**
 
-    * @callback obj:ReduceCallback
 
-    *
 
-    * @param {*} accum
 
-    *        The value that is accumulating over the reduce loop.
 
-    *
 
-    * @param {*} value
 
-    *        The current key for the object that is being iterated over.
 
-    *
 
-    * @param {string} key
 
-    *        The current key-value for object that is being iterated over
 
-    *
 
-    * @return {*}
 
-    *         The new accumulated value.
 
-    */
 
-   const toString$1 = Object.prototype.toString;
 
-   /**
 
-    * Get the keys of an Object
 
-    *
 
-    * @param {Object}
 
-    *        The Object to get the keys from
 
-    *
 
-    * @return {string[]}
 
-    *         An array of the keys from the object. Returns an empty array if the
 
-    *         object passed in was invalid or had no keys.
 
-    *
 
-    * @private
 
-    */
 
-   const keys = function (object) {
 
-     return isObject$1(object) ? Object.keys(object) : [];
 
-   };
 
-   /**
 
-    * Array-like iteration for objects.
 
-    *
 
-    * @param {Object} object
 
-    *        The object to iterate over
 
-    *
 
-    * @param {obj:EachCallback} fn
 
-    *        The callback function which is called for each key in the object.
 
-    */
 
-   function each(object, fn) {
 
-     keys(object).forEach(key => fn(object[key], key));
 
-   }
 
-   /**
 
-    * Array-like reduce for objects.
 
-    *
 
-    * @param {Object} object
 
-    *        The Object that you want to reduce.
 
-    *
 
-    * @param {Function} fn
 
-    *         A callback function which is called for each key in the object. It
 
-    *         receives the accumulated value and the per-iteration value and key
 
-    *         as arguments.
 
-    *
 
-    * @param {*} [initial = 0]
 
-    *        Starting value
 
-    *
 
-    * @return {*}
 
-    *         The final accumulated value.
 
-    */
 
-   function reduce(object, fn, initial = 0) {
 
-     return keys(object).reduce((accum, key) => fn(accum, object[key], key), initial);
 
-   }
 
-   /**
 
-    * Returns whether a value is an object of any kind - including DOM nodes,
 
-    * arrays, regular expressions, etc. Not functions, though.
 
-    *
 
-    * This avoids the gotcha where using `typeof` on a `null` value
 
-    * results in `'object'`.
 
-    *
 
-    * @param  {Object} value
 
-    * @return {boolean}
 
-    */
 
-   function isObject$1(value) {
 
-     return !!value && typeof value === 'object';
 
-   }
 
-   /**
 
-    * Returns whether an object appears to be a "plain" object - that is, a
 
-    * direct instance of `Object`.
 
-    *
 
-    * @param  {Object} value
 
-    * @return {boolean}
 
-    */
 
-   function isPlain(value) {
 
-     return isObject$1(value) && toString$1.call(value) === '[object Object]' && value.constructor === Object;
 
-   }
 
-   /**
 
-    * Merge two objects recursively.
 
-    *
 
-    * Performs a deep merge like
 
-    * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
 
-    * plain objects (not arrays, elements, or anything else).
 
-    *
 
-    * Non-plain object values will be copied directly from the right-most
 
-    * argument.
 
-    *
 
-    * @param   {Object[]} sources
 
-    *          One or more objects to merge into a new object.
 
-    *
 
-    * @return {Object}
 
-    *          A new object that is the merged result of all sources.
 
-    */
 
-   function merge$2(...sources) {
 
-     const result = {};
 
-     sources.forEach(source => {
 
-       if (!source) {
 
-         return;
 
-       }
 
-       each(source, (value, key) => {
 
-         if (!isPlain(value)) {
 
-           result[key] = value;
 
-           return;
 
-         }
 
-         if (!isPlain(result[key])) {
 
-           result[key] = {};
 
-         }
 
-         result[key] = merge$2(result[key], value);
 
-       });
 
-     });
 
-     return result;
 
-   }
 
-   /**
 
-    * Object.defineProperty but "lazy", which means that the value is only set after
 
-    * it is retrieved the first time, rather than being set right away.
 
-    *
 
-    * @param {Object} obj the object to set the property on
 
-    * @param {string} key the key for the property to set
 
-    * @param {Function} getValue the function used to get the value when it is needed.
 
-    * @param {boolean} setter whether a setter should be allowed or not
 
-    */
 
-   function defineLazyProperty(obj, key, getValue, setter = true) {
 
-     const set = value => Object.defineProperty(obj, key, {
 
-       value,
 
-       enumerable: true,
 
-       writable: true
 
-     });
 
-     const options = {
 
-       configurable: true,
 
-       enumerable: true,
 
-       get() {
 
-         const value = getValue();
 
-         set(value);
 
-         return value;
 
-       }
 
-     };
 
-     if (setter) {
 
-       options.set = set;
 
-     }
 
-     return Object.defineProperty(obj, key, options);
 
-   }
 
-   var Obj = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     each: each,
 
-     reduce: reduce,
 
-     isObject: isObject$1,
 
-     isPlain: isPlain,
 
-     merge: merge$2,
 
-     defineLazyProperty: defineLazyProperty
 
-   });
 
-   /**
 
-    * @file browser.js
 
-    * @module browser
 
-    */
 
-   /**
 
-    * Whether or not this device is an iPod.
 
-    *
 
-    * @static
 
-    * @type {Boolean}
 
-    */
 
-   let IS_IPOD = false;
 
-   /**
 
-    * The detected iOS version - or `null`.
 
-    *
 
-    * @static
 
-    * @type {string|null}
 
-    */
 
-   let IOS_VERSION = null;
 
-   /**
 
-    * Whether or not this is an Android device.
 
-    *
 
-    * @static
 
-    * @type {Boolean}
 
-    */
 
-   let IS_ANDROID = false;
 
-   /**
 
-    * The detected Android version - or `null` if not Android or indeterminable.
 
-    *
 
-    * @static
 
-    * @type {number|string|null}
 
-    */
 
-   let ANDROID_VERSION;
 
-   /**
 
-    * Whether or not this is Mozilla Firefox.
 
-    *
 
-    * @static
 
-    * @type {Boolean}
 
-    */
 
-   let IS_FIREFOX = false;
 
-   /**
 
-    * Whether or not this is Microsoft Edge.
 
-    *
 
-    * @static
 
-    * @type {Boolean}
 
-    */
 
-   let IS_EDGE = false;
 
-   /**
 
-    * Whether or not this is any Chromium Browser
 
-    *
 
-    * @static
 
-    * @type {Boolean}
 
-    */
 
-   let IS_CHROMIUM = false;
 
-   /**
 
-    * Whether or not this is any Chromium browser that is not Edge.
 
-    *
 
-    * This will also be `true` for Chrome on iOS, which will have different support
 
-    * as it is actually Safari under the hood.
 
-    *
 
-    * Deprecated, as the behaviour to not match Edge was to prevent Legacy Edge's UA matching.
 
-    * IS_CHROMIUM should be used instead.
 
-    * "Chromium but not Edge" could be explicitly tested with IS_CHROMIUM && !IS_EDGE
 
-    *
 
-    * @static
 
-    * @deprecated
 
-    * @type {Boolean}
 
-    */
 
-   let IS_CHROME = false;
 
-   /**
 
-    * The detected Chromium version - or `null`.
 
-    *
 
-    * @static
 
-    * @type {number|null}
 
-    */
 
-   let CHROMIUM_VERSION = null;
 
-   /**
 
-    * The detected Google Chrome version - or `null`.
 
-    * This has always been the _Chromium_ version, i.e. would return on Chromium Edge.
 
-    * Deprecated, use CHROMIUM_VERSION instead.
 
-    *
 
-    * @static
 
-    * @deprecated
 
-    * @type {number|null}
 
-    */
 
-   let CHROME_VERSION = null;
 
-   /**
 
-    * The detected Internet Explorer version - or `null`.
 
-    *
 
-    * @static
 
-    * @deprecated
 
-    * @type {number|null}
 
-    */
 
-   let IE_VERSION = null;
 
-   /**
 
-    * Whether or not this is desktop Safari.
 
-    *
 
-    * @static
 
-    * @type {Boolean}
 
-    */
 
-   let IS_SAFARI = false;
 
-   /**
 
-    * Whether or not this is a Windows machine.
 
-    *
 
-    * @static
 
-    * @type {Boolean}
 
-    */
 
-   let IS_WINDOWS = false;
 
-   /**
 
-    * Whether or not this device is an iPad.
 
-    *
 
-    * @static
 
-    * @type {Boolean}
 
-    */
 
-   let IS_IPAD = false;
 
-   /**
 
-    * Whether or not this device is an iPhone.
 
-    *
 
-    * @static
 
-    * @type {Boolean}
 
-    */
 
-   // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
 
-   // to identify iPhones, we need to exclude iPads.
 
-   // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
 
-   let IS_IPHONE = false;
 
-   /**
 
-    * Whether or not this device is touch-enabled.
 
-    *
 
-    * @static
 
-    * @const
 
-    * @type {Boolean}
 
-    */
 
-   const TOUCH_ENABLED = Boolean(isReal() && ('ontouchstart' in window || window.navigator.maxTouchPoints || window.DocumentTouch && window.document instanceof window.DocumentTouch));
 
-   const UAD = window.navigator && window.navigator.userAgentData;
 
-   if (UAD) {
 
-     // If userAgentData is present, use it instead of userAgent to avoid warnings
 
-     // Currently only implemented on Chromium
 
-     // userAgentData does not expose Android version, so ANDROID_VERSION remains `null`
 
-     IS_ANDROID = UAD.platform === 'Android';
 
-     IS_EDGE = Boolean(UAD.brands.find(b => b.brand === 'Microsoft Edge'));
 
-     IS_CHROMIUM = Boolean(UAD.brands.find(b => b.brand === 'Chromium'));
 
-     IS_CHROME = !IS_EDGE && IS_CHROMIUM;
 
-     CHROMIUM_VERSION = CHROME_VERSION = (UAD.brands.find(b => b.brand === 'Chromium') || {}).version || null;
 
-     IS_WINDOWS = UAD.platform === 'Windows';
 
-   }
 
-   // If the browser is not Chromium, either userAgentData is not present which could be an old Chromium browser,
 
-   //  or it's a browser that has added userAgentData since that we don't have tests for yet. In either case,
 
-   // the checks need to be made agiainst the regular userAgent string.
 
-   if (!IS_CHROMIUM) {
 
-     const USER_AGENT = window.navigator && window.navigator.userAgent || '';
 
-     IS_IPOD = /iPod/i.test(USER_AGENT);
 
-     IOS_VERSION = function () {
 
-       const match = USER_AGENT.match(/OS (\d+)_/i);
 
-       if (match && match[1]) {
 
-         return match[1];
 
-       }
 
-       return null;
 
-     }();
 
-     IS_ANDROID = /Android/i.test(USER_AGENT);
 
-     ANDROID_VERSION = function () {
 
-       // This matches Android Major.Minor.Patch versions
 
-       // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
 
-       const match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
 
-       if (!match) {
 
-         return null;
 
-       }
 
-       const major = match[1] && parseFloat(match[1]);
 
-       const minor = match[2] && parseFloat(match[2]);
 
-       if (major && minor) {
 
-         return parseFloat(match[1] + '.' + match[2]);
 
-       } else if (major) {
 
-         return major;
 
-       }
 
-       return null;
 
-     }();
 
-     IS_FIREFOX = /Firefox/i.test(USER_AGENT);
 
-     IS_EDGE = /Edg/i.test(USER_AGENT);
 
-     IS_CHROMIUM = /Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT);
 
-     IS_CHROME = !IS_EDGE && IS_CHROMIUM;
 
-     CHROMIUM_VERSION = CHROME_VERSION = function () {
 
-       const match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
 
-       if (match && match[2]) {
 
-         return parseFloat(match[2]);
 
-       }
 
-       return null;
 
-     }();
 
-     IE_VERSION = function () {
 
-       const result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
 
-       let version = result && parseFloat(result[1]);
 
-       if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
 
-         // IE 11 has a different user agent string than other IE versions
 
-         version = 11.0;
 
-       }
 
-       return version;
 
-     }();
 
-     IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
 
-     IS_WINDOWS = /Windows/i.test(USER_AGENT);
 
-     IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
 
-     IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
 
-   }
 
-   /**
 
-    * Whether or not this is an iOS device.
 
-    *
 
-    * @static
 
-    * @const
 
-    * @type {Boolean}
 
-    */
 
-   const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
 
-   /**
 
-    * Whether or not this is any flavor of Safari - including iOS.
 
-    *
 
-    * @static
 
-    * @const
 
-    * @type {Boolean}
 
-    */
 
-   const IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
 
-   var browser = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     get IS_IPOD () { return IS_IPOD; },
 
-     get IOS_VERSION () { return IOS_VERSION; },
 
-     get IS_ANDROID () { return IS_ANDROID; },
 
-     get ANDROID_VERSION () { return ANDROID_VERSION; },
 
-     get IS_FIREFOX () { return IS_FIREFOX; },
 
-     get IS_EDGE () { return IS_EDGE; },
 
-     get IS_CHROMIUM () { return IS_CHROMIUM; },
 
-     get IS_CHROME () { return IS_CHROME; },
 
-     get CHROMIUM_VERSION () { return CHROMIUM_VERSION; },
 
-     get CHROME_VERSION () { return CHROME_VERSION; },
 
-     get IE_VERSION () { return IE_VERSION; },
 
-     get IS_SAFARI () { return IS_SAFARI; },
 
-     get IS_WINDOWS () { return IS_WINDOWS; },
 
-     get IS_IPAD () { return IS_IPAD; },
 
-     get IS_IPHONE () { return IS_IPHONE; },
 
-     TOUCH_ENABLED: TOUCH_ENABLED,
 
-     IS_IOS: IS_IOS,
 
-     IS_ANY_SAFARI: IS_ANY_SAFARI
 
-   });
 
-   /**
 
-    * @file dom.js
 
-    * @module dom
 
-    */
 
-   /**
 
-    * Detect if a value is a string with any non-whitespace characters.
 
-    *
 
-    * @private
 
-    * @param  {string} str
 
-    *         The string to check
 
-    *
 
-    * @return {boolean}
 
-    *         Will be `true` if the string is non-blank, `false` otherwise.
 
-    *
 
-    */
 
-   function isNonBlankString(str) {
 
-     // we use str.trim as it will trim any whitespace characters
 
-     // from the front or back of non-whitespace characters. aka
 
-     // Any string that contains non-whitespace characters will
 
-     // still contain them after `trim` but whitespace only strings
 
-     // will have a length of 0, failing this check.
 
-     return typeof str === 'string' && Boolean(str.trim());
 
-   }
 
-   /**
 
-    * Throws an error if the passed string has whitespace. This is used by
 
-    * class methods to be relatively consistent with the classList API.
 
-    *
 
-    * @private
 
-    * @param  {string} str
 
-    *         The string to check for whitespace.
 
-    *
 
-    * @throws {Error}
 
-    *         Throws an error if there is whitespace in the string.
 
-    */
 
-   function throwIfWhitespace(str) {
 
-     // str.indexOf instead of regex because str.indexOf is faster performance wise.
 
-     if (str.indexOf(' ') >= 0) {
 
-       throw new Error('class has illegal whitespace characters');
 
-     }
 
-   }
 
-   /**
 
-    * Whether the current DOM interface appears to be real (i.e. not simulated).
 
-    *
 
-    * @return {boolean}
 
-    *         Will be `true` if the DOM appears to be real, `false` otherwise.
 
-    */
 
-   function isReal() {
 
-     // Both document and window will never be undefined thanks to `global`.
 
-     return document === window.document;
 
-   }
 
-   /**
 
-    * Determines, via duck typing, whether or not a value is a DOM element.
 
-    *
 
-    * @param  {*} value
 
-    *         The value to check.
 
-    *
 
-    * @return {boolean}
 
-    *         Will be `true` if the value is a DOM element, `false` otherwise.
 
-    */
 
-   function isEl(value) {
 
-     return isObject$1(value) && value.nodeType === 1;
 
-   }
 
-   /**
 
-    * Determines if the current DOM is embedded in an iframe.
 
-    *
 
-    * @return {boolean}
 
-    *         Will be `true` if the DOM is embedded in an iframe, `false`
 
-    *         otherwise.
 
-    */
 
-   function isInFrame() {
 
-     // We need a try/catch here because Safari will throw errors when attempting
 
-     // to get either `parent` or `self`
 
-     try {
 
-       return window.parent !== window.self;
 
-     } catch (x) {
 
-       return true;
 
-     }
 
-   }
 
-   /**
 
-    * Creates functions to query the DOM using a given method.
 
-    *
 
-    * @private
 
-    * @param   {string} method
 
-    *          The method to create the query with.
 
-    *
 
-    * @return  {Function}
 
-    *          The query method
 
-    */
 
-   function createQuerier(method) {
 
-     return function (selector, context) {
 
-       if (!isNonBlankString(selector)) {
 
-         return document[method](null);
 
-       }
 
-       if (isNonBlankString(context)) {
 
-         context = document.querySelector(context);
 
-       }
 
-       const ctx = isEl(context) ? context : document;
 
-       return ctx[method] && ctx[method](selector);
 
-     };
 
-   }
 
-   /**
 
-    * Creates an element and applies properties, attributes, and inserts content.
 
-    *
 
-    * @param  {string} [tagName='div']
 
-    *         Name of tag to be created.
 
-    *
 
-    * @param  {Object} [properties={}]
 
-    *         Element properties to be applied.
 
-    *
 
-    * @param  {Object} [attributes={}]
 
-    *         Element attributes to be applied.
 
-    *
 
-    * @param {ContentDescriptor} [content]
 
-    *        A content descriptor object.
 
-    *
 
-    * @return {Element}
 
-    *         The element that was created.
 
-    */
 
-   function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
 
-     const el = document.createElement(tagName);
 
-     Object.getOwnPropertyNames(properties).forEach(function (propName) {
 
-       const val = properties[propName];
 
-       // Handle textContent since it's not supported everywhere and we have a
 
-       // method for it.
 
-       if (propName === 'textContent') {
 
-         textContent(el, val);
 
-       } else if (el[propName] !== val || propName === 'tabIndex') {
 
-         el[propName] = val;
 
-       }
 
-     });
 
-     Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
 
-       el.setAttribute(attrName, attributes[attrName]);
 
-     });
 
-     if (content) {
 
-       appendContent(el, content);
 
-     }
 
-     return el;
 
-   }
 
-   /**
 
-    * Injects text into an element, replacing any existing contents entirely.
 
-    *
 
-    * @param  {Element} el
 
-    *         The element to add text content into
 
-    *
 
-    * @param  {string} text
 
-    *         The text content to add.
 
-    *
 
-    * @return {Element}
 
-    *         The element with added text content.
 
-    */
 
-   function textContent(el, text) {
 
-     if (typeof el.textContent === 'undefined') {
 
-       el.innerText = text;
 
-     } else {
 
-       el.textContent = text;
 
-     }
 
-     return el;
 
-   }
 
-   /**
 
-    * Insert an element as the first child node of another
 
-    *
 
-    * @param {Element} child
 
-    *        Element to insert
 
-    *
 
-    * @param {Element} parent
 
-    *        Element to insert child into
 
-    */
 
-   function prependTo(child, parent) {
 
-     if (parent.firstChild) {
 
-       parent.insertBefore(child, parent.firstChild);
 
-     } else {
 
-       parent.appendChild(child);
 
-     }
 
-   }
 
-   /**
 
-    * Check if an element has a class name.
 
-    *
 
-    * @param  {Element} element
 
-    *         Element to check
 
-    *
 
-    * @param  {string} classToCheck
 
-    *         Class name to check for
 
-    *
 
-    * @return {boolean}
 
-    *         Will be `true` if the element has a class, `false` otherwise.
 
-    *
 
-    * @throws {Error}
 
-    *         Throws an error if `classToCheck` has white space.
 
-    */
 
-   function hasClass(element, classToCheck) {
 
-     throwIfWhitespace(classToCheck);
 
-     return element.classList.contains(classToCheck);
 
-   }
 
-   /**
 
-    * Add a class name to an element.
 
-    *
 
-    * @param  {Element} element
 
-    *         Element to add class name to.
 
-    *
 
-    * @param  {...string} classesToAdd
 
-    *         One or more class name to add.
 
-    *
 
-    * @return {Element}
 
-    *         The DOM element with the added class name.
 
-    */
 
-   function addClass(element, ...classesToAdd) {
 
-     element.classList.add(...classesToAdd.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
 
-     return element;
 
-   }
 
-   /**
 
-    * Remove a class name from an element.
 
-    *
 
-    * @param  {Element} element
 
-    *         Element to remove a class name from.
 
-    *
 
-    * @param  {...string} classesToRemove
 
-    *         One or more class name to remove.
 
-    *
 
-    * @return {Element}
 
-    *         The DOM element with class name removed.
 
-    */
 
-   function removeClass(element, ...classesToRemove) {
 
-     // Protect in case the player gets disposed
 
-     if (!element) {
 
-       log$1.warn("removeClass was called with an element that doesn't exist");
 
-       return null;
 
-     }
 
-     element.classList.remove(...classesToRemove.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
 
-     return element;
 
-   }
 
-   /**
 
-    * The callback definition for toggleClass.
 
-    *
 
-    * @callback module:dom~PredicateCallback
 
-    * @param    {Element} element
 
-    *           The DOM element of the Component.
 
-    *
 
-    * @param    {string} classToToggle
 
-    *           The `className` that wants to be toggled
 
-    *
 
-    * @return   {boolean|undefined}
 
-    *           If `true` is returned, the `classToToggle` will be added to the
 
-    *           `element`. If `false`, the `classToToggle` will be removed from
 
-    *           the `element`. If `undefined`, the callback will be ignored.
 
-    */
 
-   /**
 
-    * Adds or removes a class name to/from an element depending on an optional
 
-    * condition or the presence/absence of the class name.
 
-    *
 
-    * @param  {Element} element
 
-    *         The element to toggle a class name on.
 
-    *
 
-    * @param  {string} classToToggle
 
-    *         The class that should be toggled.
 
-    *
 
-    * @param  {boolean|module:dom~PredicateCallback} [predicate]
 
-    *         See the return value for {@link module:dom~PredicateCallback}
 
-    *
 
-    * @return {Element}
 
-    *         The element with a class that has been toggled.
 
-    */
 
-   function toggleClass(element, classToToggle, predicate) {
 
-     if (typeof predicate === 'function') {
 
-       predicate = predicate(element, classToToggle);
 
-     }
 
-     if (typeof predicate !== 'boolean') {
 
-       predicate = undefined;
 
-     }
 
-     classToToggle.split(/\s+/).forEach(className => element.classList.toggle(className, predicate));
 
-     return element;
 
-   }
 
-   /**
 
-    * Apply attributes to an HTML element.
 
-    *
 
-    * @param {Element} el
 
-    *        Element to add attributes to.
 
-    *
 
-    * @param {Object} [attributes]
 
-    *        Attributes to be applied.
 
-    */
 
-   function setAttributes(el, attributes) {
 
-     Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
 
-       const attrValue = attributes[attrName];
 
-       if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
 
-         el.removeAttribute(attrName);
 
-       } else {
 
-         el.setAttribute(attrName, attrValue === true ? '' : attrValue);
 
-       }
 
-     });
 
-   }
 
-   /**
 
-    * Get an element's attribute values, as defined on the HTML tag.
 
-    *
 
-    * Attributes are not the same as properties. They're defined on the tag
 
-    * or with setAttribute.
 
-    *
 
-    * @param  {Element} tag
 
-    *         Element from which to get tag attributes.
 
-    *
 
-    * @return {Object}
 
-    *         All attributes of the element. Boolean attributes will be `true` or
 
-    *         `false`, others will be strings.
 
-    */
 
-   function getAttributes(tag) {
 
-     const obj = {};
 
-     // known boolean attributes
 
-     // we can check for matching boolean properties, but not all browsers
 
-     // and not all tags know about these attributes, so, we still want to check them manually
 
-     const knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
 
-     if (tag && tag.attributes && tag.attributes.length > 0) {
 
-       const attrs = tag.attributes;
 
-       for (let i = attrs.length - 1; i >= 0; i--) {
 
-         const attrName = attrs[i].name;
 
-         let attrVal = attrs[i].value;
 
-         // check for known booleans
 
-         // the matching element property will return a value for typeof
 
-         if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
 
-           // the value of an included boolean attribute is typically an empty
 
-           // string ('') which would equal false if we just check for a false value.
 
-           // we also don't want support bad code like autoplay='false'
 
-           attrVal = attrVal !== null ? true : false;
 
-         }
 
-         obj[attrName] = attrVal;
 
-       }
 
-     }
 
-     return obj;
 
-   }
 
-   /**
 
-    * Get the value of an element's attribute.
 
-    *
 
-    * @param {Element} el
 
-    *        A DOM element.
 
-    *
 
-    * @param {string} attribute
 
-    *        Attribute to get the value of.
 
-    *
 
-    * @return {string}
 
-    *         The value of the attribute.
 
-    */
 
-   function getAttribute(el, attribute) {
 
-     return el.getAttribute(attribute);
 
-   }
 
-   /**
 
-    * Set the value of an element's attribute.
 
-    *
 
-    * @param {Element} el
 
-    *        A DOM element.
 
-    *
 
-    * @param {string} attribute
 
-    *        Attribute to set.
 
-    *
 
-    * @param {string} value
 
-    *        Value to set the attribute to.
 
-    */
 
-   function setAttribute(el, attribute, value) {
 
-     el.setAttribute(attribute, value);
 
-   }
 
-   /**
 
-    * Remove an element's attribute.
 
-    *
 
-    * @param {Element} el
 
-    *        A DOM element.
 
-    *
 
-    * @param {string} attribute
 
-    *        Attribute to remove.
 
-    */
 
-   function removeAttribute(el, attribute) {
 
-     el.removeAttribute(attribute);
 
-   }
 
-   /**
 
-    * Attempt to block the ability to select text.
 
-    */
 
-   function blockTextSelection() {
 
-     document.body.focus();
 
-     document.onselectstart = function () {
 
-       return false;
 
-     };
 
-   }
 
-   /**
 
-    * Turn off text selection blocking.
 
-    */
 
-   function unblockTextSelection() {
 
-     document.onselectstart = function () {
 
-       return true;
 
-     };
 
-   }
 
-   /**
 
-    * Identical to the native `getBoundingClientRect` function, but ensures that
 
-    * the method is supported at all (it is in all browsers we claim to support)
 
-    * and that the element is in the DOM before continuing.
 
-    *
 
-    * This wrapper function also shims properties which are not provided by some
 
-    * older browsers (namely, IE8).
 
-    *
 
-    * Additionally, some browsers do not support adding properties to a
 
-    * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
 
-    * properties (except `x` and `y` which are not widely supported). This helps
 
-    * avoid implementations where keys are non-enumerable.
 
-    *
 
-    * @param  {Element} el
 
-    *         Element whose `ClientRect` we want to calculate.
 
-    *
 
-    * @return {Object|undefined}
 
-    *         Always returns a plain object - or `undefined` if it cannot.
 
-    */
 
-   function getBoundingClientRect(el) {
 
-     if (el && el.getBoundingClientRect && el.parentNode) {
 
-       const rect = el.getBoundingClientRect();
 
-       const result = {};
 
-       ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(k => {
 
-         if (rect[k] !== undefined) {
 
-           result[k] = rect[k];
 
-         }
 
-       });
 
-       if (!result.height) {
 
-         result.height = parseFloat(computedStyle(el, 'height'));
 
-       }
 
-       if (!result.width) {
 
-         result.width = parseFloat(computedStyle(el, 'width'));
 
-       }
 
-       return result;
 
-     }
 
-   }
 
-   /**
 
-    * Represents the position of a DOM element on the page.
 
-    *
 
-    * @typedef  {Object} module:dom~Position
 
-    *
 
-    * @property {number} left
 
-    *           Pixels to the left.
 
-    *
 
-    * @property {number} top
 
-    *           Pixels from the top.
 
-    */
 
-   /**
 
-    * Get the position of an element in the DOM.
 
-    *
 
-    * Uses `getBoundingClientRect` technique from John Resig.
 
-    *
 
-    * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
 
-    *
 
-    * @param  {Element} el
 
-    *         Element from which to get offset.
 
-    *
 
-    * @return {module:dom~Position}
 
-    *         The position of the element that was passed in.
 
-    */
 
-   function findPosition(el) {
 
-     if (!el || el && !el.offsetParent) {
 
-       return {
 
-         left: 0,
 
-         top: 0,
 
-         width: 0,
 
-         height: 0
 
-       };
 
-     }
 
-     const width = el.offsetWidth;
 
-     const height = el.offsetHeight;
 
-     let left = 0;
 
-     let top = 0;
 
-     while (el.offsetParent && el !== document[FullscreenApi.fullscreenElement]) {
 
-       left += el.offsetLeft;
 
-       top += el.offsetTop;
 
-       el = el.offsetParent;
 
-     }
 
-     return {
 
-       left,
 
-       top,
 
-       width,
 
-       height
 
-     };
 
-   }
 
-   /**
 
-    * Represents x and y coordinates for a DOM element or mouse pointer.
 
-    *
 
-    * @typedef  {Object} module:dom~Coordinates
 
-    *
 
-    * @property {number} x
 
-    *           x coordinate in pixels
 
-    *
 
-    * @property {number} y
 
-    *           y coordinate in pixels
 
-    */
 
-   /**
 
-    * Get the pointer position within an element.
 
-    *
 
-    * The base on the coordinates are the bottom left of the element.
 
-    *
 
-    * @param  {Element} el
 
-    *         Element on which to get the pointer position on.
 
-    *
 
-    * @param  {Event} event
 
-    *         Event object.
 
-    *
 
-    * @return {module:dom~Coordinates}
 
-    *         A coordinates object corresponding to the mouse position.
 
-    *
 
-    */
 
-   function getPointerPosition(el, event) {
 
-     const translated = {
 
-       x: 0,
 
-       y: 0
 
-     };
 
-     if (IS_IOS) {
 
-       let item = el;
 
-       while (item && item.nodeName.toLowerCase() !== 'html') {
 
-         const transform = computedStyle(item, 'transform');
 
-         if (/^matrix/.test(transform)) {
 
-           const values = transform.slice(7, -1).split(/,\s/).map(Number);
 
-           translated.x += values[4];
 
-           translated.y += values[5];
 
-         } else if (/^matrix3d/.test(transform)) {
 
-           const values = transform.slice(9, -1).split(/,\s/).map(Number);
 
-           translated.x += values[12];
 
-           translated.y += values[13];
 
-         }
 
-         item = item.parentNode;
 
-       }
 
-     }
 
-     const position = {};
 
-     const boxTarget = findPosition(event.target);
 
-     const box = findPosition(el);
 
-     const boxW = box.width;
 
-     const boxH = box.height;
 
-     let offsetY = event.offsetY - (box.top - boxTarget.top);
 
-     let offsetX = event.offsetX - (box.left - boxTarget.left);
 
-     if (event.changedTouches) {
 
-       offsetX = event.changedTouches[0].pageX - box.left;
 
-       offsetY = event.changedTouches[0].pageY + box.top;
 
-       if (IS_IOS) {
 
-         offsetX -= translated.x;
 
-         offsetY -= translated.y;
 
-       }
 
-     }
 
-     position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH));
 
-     position.x = Math.max(0, Math.min(1, offsetX / boxW));
 
-     return position;
 
-   }
 
-   /**
 
-    * Determines, via duck typing, whether or not a value is a text node.
 
-    *
 
-    * @param  {*} value
 
-    *         Check if this value is a text node.
 
-    *
 
-    * @return {boolean}
 
-    *         Will be `true` if the value is a text node, `false` otherwise.
 
-    */
 
-   function isTextNode$1(value) {
 
-     return isObject$1(value) && value.nodeType === 3;
 
-   }
 
-   /**
 
-    * Empties the contents of an element.
 
-    *
 
-    * @param  {Element} el
 
-    *         The element to empty children from
 
-    *
 
-    * @return {Element}
 
-    *         The element with no children
 
-    */
 
-   function emptyEl(el) {
 
-     while (el.firstChild) {
 
-       el.removeChild(el.firstChild);
 
-     }
 
-     return el;
 
-   }
 
-   /**
 
-    * This is a mixed value that describes content to be injected into the DOM
 
-    * via some method. It can be of the following types:
 
-    *
 
-    * Type       | Description
 
-    * -----------|-------------
 
-    * `string`   | The value will be normalized into a text node.
 
-    * `Element`  | The value will be accepted as-is.
 
-    * `Text`     | A TextNode. The value will be accepted as-is.
 
-    * `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).
 
-    * `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.
 
-    *
 
-    * @typedef {string|Element|Text|Array|Function} ContentDescriptor
 
-    */
 
-   /**
 
-    * Normalizes content for eventual insertion into the DOM.
 
-    *
 
-    * This allows a wide range of content definition methods, but helps protect
 
-    * from falling into the trap of simply writing to `innerHTML`, which could
 
-    * be an XSS concern.
 
-    *
 
-    * The content for an element can be passed in multiple types and
 
-    * combinations, whose behavior is as follows:
 
-    *
 
-    * @param {ContentDescriptor} content
 
-    *        A content descriptor value.
 
-    *
 
-    * @return {Array}
 
-    *         All of the content that was passed in, normalized to an array of
 
-    *         elements or text nodes.
 
-    */
 
-   function normalizeContent(content) {
 
-     // First, invoke content if it is a function. If it produces an array,
 
-     // that needs to happen before normalization.
 
-     if (typeof content === 'function') {
 
-       content = content();
 
-     }
 
-     // Next up, normalize to an array, so one or many items can be normalized,
 
-     // filtered, and returned.
 
-     return (Array.isArray(content) ? content : [content]).map(value => {
 
-       // First, invoke value if it is a function to produce a new value,
 
-       // which will be subsequently normalized to a Node of some kind.
 
-       if (typeof value === 'function') {
 
-         value = value();
 
-       }
 
-       if (isEl(value) || isTextNode$1(value)) {
 
-         return value;
 
-       }
 
-       if (typeof value === 'string' && /\S/.test(value)) {
 
-         return document.createTextNode(value);
 
-       }
 
-     }).filter(value => value);
 
-   }
 
-   /**
 
-    * Normalizes and appends content to an element.
 
-    *
 
-    * @param  {Element} el
 
-    *         Element to append normalized content to.
 
-    *
 
-    * @param {ContentDescriptor} content
 
-    *        A content descriptor value.
 
-    *
 
-    * @return {Element}
 
-    *         The element with appended normalized content.
 
-    */
 
-   function appendContent(el, content) {
 
-     normalizeContent(content).forEach(node => el.appendChild(node));
 
-     return el;
 
-   }
 
-   /**
 
-    * Normalizes and inserts content into an element; this is identical to
 
-    * `appendContent()`, except it empties the element first.
 
-    *
 
-    * @param {Element} el
 
-    *        Element to insert normalized content into.
 
-    *
 
-    * @param {ContentDescriptor} content
 
-    *        A content descriptor value.
 
-    *
 
-    * @return {Element}
 
-    *         The element with inserted normalized content.
 
-    */
 
-   function insertContent(el, content) {
 
-     return appendContent(emptyEl(el), content);
 
-   }
 
-   /**
 
-    * Check if an event was a single left click.
 
-    *
 
-    * @param  {Event} event
 
-    *         Event object.
 
-    *
 
-    * @return {boolean}
 
-    *         Will be `true` if a single left click, `false` otherwise.
 
-    */
 
-   function isSingleLeftClick(event) {
 
-     // Note: if you create something draggable, be sure to
 
-     // call it on both `mousedown` and `mousemove` event,
 
-     // otherwise `mousedown` should be enough for a button
 
-     if (event.button === undefined && event.buttons === undefined) {
 
-       // Why do we need `buttons` ?
 
-       // Because, middle mouse sometimes have this:
 
-       // e.button === 0 and e.buttons === 4
 
-       // Furthermore, we want to prevent combination click, something like
 
-       // HOLD middlemouse then left click, that would be
 
-       // e.button === 0, e.buttons === 5
 
-       // just `button` is not gonna work
 
-       // Alright, then what this block does ?
 
-       // this is for chrome `simulate mobile devices`
 
-       // I want to support this as well
 
-       return true;
 
-     }
 
-     if (event.button === 0 && event.buttons === undefined) {
 
-       // Touch screen, sometimes on some specific device, `buttons`
 
-       // doesn't have anything (safari on ios, blackberry...)
 
-       return true;
 
-     }
 
-     // `mouseup` event on a single left click has
 
-     // `button` and `buttons` equal to 0
 
-     if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) {
 
-       return true;
 
-     }
 
-     if (event.button !== 0 || event.buttons !== 1) {
 
-       // This is the reason we have those if else block above
 
-       // if any special case we can catch and let it slide
 
-       // we do it above, when get to here, this definitely
 
-       // is-not-left-click
 
-       return false;
 
-     }
 
-     return true;
 
-   }
 
-   /**
 
-    * Finds a single DOM element matching `selector` within the optional
 
-    * `context` of another DOM element (defaulting to `document`).
 
-    *
 
-    * @param  {string} selector
 
-    *         A valid CSS selector, which will be passed to `querySelector`.
 
-    *
 
-    * @param  {Element|String} [context=document]
 
-    *         A DOM element within which to query. Can also be a selector
 
-    *         string in which case the first matching element will be used
 
-    *         as context. If missing (or no element matches selector), falls
 
-    *         back to `document`.
 
-    *
 
-    * @return {Element|null}
 
-    *         The element that was found or null.
 
-    */
 
-   const $ = createQuerier('querySelector');
 
-   /**
 
-    * Finds a all DOM elements matching `selector` within the optional
 
-    * `context` of another DOM element (defaulting to `document`).
 
-    *
 
-    * @param  {string} selector
 
-    *         A valid CSS selector, which will be passed to `querySelectorAll`.
 
-    *
 
-    * @param  {Element|String} [context=document]
 
-    *         A DOM element within which to query. Can also be a selector
 
-    *         string in which case the first matching element will be used
 
-    *         as context. If missing (or no element matches selector), falls
 
-    *         back to `document`.
 
-    *
 
-    * @return {NodeList}
 
-    *         A element list of elements that were found. Will be empty if none
 
-    *         were found.
 
-    *
 
-    */
 
-   const $$ = createQuerier('querySelectorAll');
 
-   /**
 
-    * A safe getComputedStyle.
 
-    *
 
-    * This is needed because in Firefox, if the player is loaded in an iframe with
 
-    * `display:none`, then `getComputedStyle` returns `null`, so, we do a
 
-    * null-check to make sure that the player doesn't break in these cases.
 
-    *
 
-    * @param    {Element} el
 
-    *           The element you want the computed style of
 
-    *
 
-    * @param    {string} prop
 
-    *           The property name you want
 
-    *
 
-    * @see      https://bugzilla.mozilla.org/show_bug.cgi?id=548397
 
-    */
 
-   function computedStyle(el, prop) {
 
-     if (!el || !prop) {
 
-       return '';
 
-     }
 
-     if (typeof window.getComputedStyle === 'function') {
 
-       let computedStyleValue;
 
-       try {
 
-         computedStyleValue = window.getComputedStyle(el);
 
-       } catch (e) {
 
-         return '';
 
-       }
 
-       return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
 
-     }
 
-     return '';
 
-   }
 
-   var Dom = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     isReal: isReal,
 
-     isEl: isEl,
 
-     isInFrame: isInFrame,
 
-     createEl: createEl,
 
-     textContent: textContent,
 
-     prependTo: prependTo,
 
-     hasClass: hasClass,
 
-     addClass: addClass,
 
-     removeClass: removeClass,
 
-     toggleClass: toggleClass,
 
-     setAttributes: setAttributes,
 
-     getAttributes: getAttributes,
 
-     getAttribute: getAttribute,
 
-     setAttribute: setAttribute,
 
-     removeAttribute: removeAttribute,
 
-     blockTextSelection: blockTextSelection,
 
-     unblockTextSelection: unblockTextSelection,
 
-     getBoundingClientRect: getBoundingClientRect,
 
-     findPosition: findPosition,
 
-     getPointerPosition: getPointerPosition,
 
-     isTextNode: isTextNode$1,
 
-     emptyEl: emptyEl,
 
-     normalizeContent: normalizeContent,
 
-     appendContent: appendContent,
 
-     insertContent: insertContent,
 
-     isSingleLeftClick: isSingleLeftClick,
 
-     $: $,
 
-     $$: $$,
 
-     computedStyle: computedStyle
 
-   });
 
-   /**
 
-    * @file setup.js - Functions for setting up a player without
 
-    * user interaction based on the data-setup `attribute` of the video tag.
 
-    *
 
-    * @module setup
 
-    */
 
-   let _windowLoaded = false;
 
-   let videojs$1;
 
-   /**
 
-    * Set up any tags that have a data-setup `attribute` when the player is started.
 
-    */
 
-   const autoSetup = function () {
 
-     if (videojs$1.options.autoSetup === false) {
 
-       return;
 
-     }
 
-     const vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
 
-     const audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
 
-     const divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
 
-     const mediaEls = vids.concat(audios, divs);
 
-     // Check if any media elements exist
 
-     if (mediaEls && mediaEls.length > 0) {
 
-       for (let i = 0, e = mediaEls.length; i < e; i++) {
 
-         const mediaEl = mediaEls[i];
 
-         // Check if element exists, has getAttribute func.
 
-         if (mediaEl && mediaEl.getAttribute) {
 
-           // Make sure this player hasn't already been set up.
 
-           if (mediaEl.player === undefined) {
 
-             const options = mediaEl.getAttribute('data-setup');
 
-             // Check if data-setup attr exists.
 
-             // We only auto-setup if they've added the data-setup attr.
 
-             if (options !== null) {
 
-               // Create new video.js instance.
 
-               videojs$1(mediaEl);
 
-             }
 
-           }
 
-           // If getAttribute isn't defined, we need to wait for the DOM.
 
-         } else {
 
-           autoSetupTimeout(1);
 
-           break;
 
-         }
 
-       }
 
-       // No videos were found, so keep looping unless page is finished loading.
 
-     } else if (!_windowLoaded) {
 
-       autoSetupTimeout(1);
 
-     }
 
-   };
 
-   /**
 
-    * Wait until the page is loaded before running autoSetup. This will be called in
 
-    * autoSetup if `hasLoaded` returns false.
 
-    *
 
-    * @param {number} wait
 
-    *        How long to wait in ms
 
-    *
 
-    * @param {module:videojs} [vjs]
 
-    *        The videojs library function
 
-    */
 
-   function autoSetupTimeout(wait, vjs) {
 
-     // Protect against breakage in non-browser environments
 
-     if (!isReal()) {
 
-       return;
 
-     }
 
-     if (vjs) {
 
-       videojs$1 = vjs;
 
-     }
 
-     window.setTimeout(autoSetup, wait);
 
-   }
 
-   /**
 
-    * Used to set the internal tracking of window loaded state to true.
 
-    *
 
-    * @private
 
-    */
 
-   function setWindowLoaded() {
 
-     _windowLoaded = true;
 
-     window.removeEventListener('load', setWindowLoaded);
 
-   }
 
-   if (isReal()) {
 
-     if (document.readyState === 'complete') {
 
-       setWindowLoaded();
 
-     } else {
 
-       /**
 
-        * Listen for the load event on window, and set _windowLoaded to true.
 
-        *
 
-        * We use a standard event listener here to avoid incrementing the GUID
 
-        * before any players are created.
 
-        *
 
-        * @listens load
 
-        */
 
-       window.addEventListener('load', setWindowLoaded);
 
-     }
 
-   }
 
-   /**
 
-    * @file stylesheet.js
 
-    * @module stylesheet
 
-    */
 
-   /**
 
-    * Create a DOM style element given a className for it.
 
-    *
 
-    * @param {string} className
 
-    *        The className to add to the created style element.
 
-    *
 
-    * @return {Element}
 
-    *         The element that was created.
 
-    */
 
-   const createStyleElement = function (className) {
 
-     const style = document.createElement('style');
 
-     style.className = className;
 
-     return style;
 
-   };
 
-   /**
 
-    * Add text to a DOM element.
 
-    *
 
-    * @param {Element} el
 
-    *        The Element to add text content to.
 
-    *
 
-    * @param {string} content
 
-    *        The text to add to the element.
 
-    */
 
-   const setTextContent = function (el, content) {
 
-     if (el.styleSheet) {
 
-       el.styleSheet.cssText = content;
 
-     } else {
 
-       el.textContent = content;
 
-     }
 
-   };
 
-   /**
 
-    * @file dom-data.js
 
-    * @module dom-data
 
-    */
 
-   /**
 
-    * Element Data Store.
 
-    *
 
-    * Allows for binding data to an element without putting it directly on the
 
-    * element. Ex. Event listeners are stored here.
 
-    * (also from jsninja.com, slightly modified and updated for closure compiler)
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   var DomData = new WeakMap();
 
-   /**
 
-    * @file guid.js
 
-    * @module guid
 
-    */
 
-   // Default value for GUIDs. This allows us to reset the GUID counter in tests.
 
-   //
 
-   // The initial GUID is 3 because some users have come to rely on the first
 
-   // default player ID ending up as `vjs_video_3`.
 
-   //
 
-   // See: https://github.com/videojs/video.js/pull/6216
 
-   const _initialGuid = 3;
 
-   /**
 
-    * Unique ID for an element or function
 
-    *
 
-    * @type {Number}
 
-    */
 
-   let _guid = _initialGuid;
 
-   /**
 
-    * Get a unique auto-incrementing ID by number that has not been returned before.
 
-    *
 
-    * @return {number}
 
-    *         A new unique ID.
 
-    */
 
-   function newGUID() {
 
-     return _guid++;
 
-   }
 
-   /**
 
-    * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
 
-    * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
 
-    * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
 
-    * robust as jquery's, so there's probably some differences.
 
-    *
 
-    * @file events.js
 
-    * @module events
 
-    */
 
-   /**
 
-    * Clean up the listener cache and dispatchers
 
-    *
 
-    * @param {Element|Object} elem
 
-    *        Element to clean up
 
-    *
 
-    * @param {string} type
 
-    *        Type of event to clean up
 
-    */
 
-   function _cleanUpEvents(elem, type) {
 
-     if (!DomData.has(elem)) {
 
-       return;
 
-     }
 
-     const data = DomData.get(elem);
 
-     // Remove the events of a particular type if there are none left
 
-     if (data.handlers[type].length === 0) {
 
-       delete data.handlers[type];
 
-       // data.handlers[type] = null;
 
-       // Setting to null was causing an error with data.handlers
 
-       // Remove the meta-handler from the element
 
-       if (elem.removeEventListener) {
 
-         elem.removeEventListener(type, data.dispatcher, false);
 
-       } else if (elem.detachEvent) {
 
-         elem.detachEvent('on' + type, data.dispatcher);
 
-       }
 
-     }
 
-     // Remove the events object if there are no types left
 
-     if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
 
-       delete data.handlers;
 
-       delete data.dispatcher;
 
-       delete data.disabled;
 
-     }
 
-     // Finally remove the element data if there is no data left
 
-     if (Object.getOwnPropertyNames(data).length === 0) {
 
-       DomData.delete(elem);
 
-     }
 
-   }
 
-   /**
 
-    * Loops through an array of event types and calls the requested method for each type.
 
-    *
 
-    * @param {Function} fn
 
-    *        The event method we want to use.
 
-    *
 
-    * @param {Element|Object} elem
 
-    *        Element or object to bind listeners to
 
-    *
 
-    * @param {string} type
 
-    *        Type of event to bind to.
 
-    *
 
-    * @param {Function} callback
 
-    *        Event listener.
 
-    */
 
-   function _handleMultipleEvents(fn, elem, types, callback) {
 
-     types.forEach(function (type) {
 
-       // Call the event method for each one of the types
 
-       fn(elem, type, callback);
 
-     });
 
-   }
 
-   /**
 
-    * Fix a native event to have standard property values
 
-    *
 
-    * @param {Object} event
 
-    *        Event object to fix.
 
-    *
 
-    * @return {Object}
 
-    *         Fixed event object.
 
-    */
 
-   function fixEvent(event) {
 
-     if (event.fixed_) {
 
-       return event;
 
-     }
 
-     function returnTrue() {
 
-       return true;
 
-     }
 
-     function returnFalse() {
 
-       return false;
 
-     }
 
-     // Test if fixing up is needed
 
-     // Used to check if !event.stopPropagation instead of isPropagationStopped
 
-     // But native events return true for stopPropagation, but don't have
 
-     // other expected methods like isPropagationStopped. Seems to be a problem
 
-     // with the Javascript Ninja code. So we're just overriding all events now.
 
-     if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
 
-       const old = event || window.event;
 
-       event = {};
 
-       // Clone the old object so that we can modify the values event = {};
 
-       // IE8 Doesn't like when you mess with native event properties
 
-       // Firefox returns false for event.hasOwnProperty('type') and other props
 
-       //  which makes copying more difficult.
 
-       // TODO: Probably best to create a whitelist of event props
 
-       for (const key in old) {
 
-         // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
 
-         // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
 
-         // and webkitMovementX/Y
 
-         // Lighthouse complains if Event.path is copied
 
-         if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY' && key !== 'path') {
 
-           // Chrome 32+ warns if you try to copy deprecated returnValue, but
 
-           // we still want to if preventDefault isn't supported (IE8).
 
-           if (!(key === 'returnValue' && old.preventDefault)) {
 
-             event[key] = old[key];
 
-           }
 
-         }
 
-       }
 
-       // The event occurred on this element
 
-       if (!event.target) {
 
-         event.target = event.srcElement || document;
 
-       }
 
-       // Handle which other element the event is related to
 
-       if (!event.relatedTarget) {
 
-         event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
 
-       }
 
-       // Stop the default browser action
 
-       event.preventDefault = function () {
 
-         if (old.preventDefault) {
 
-           old.preventDefault();
 
-         }
 
-         event.returnValue = false;
 
-         old.returnValue = false;
 
-         event.defaultPrevented = true;
 
-       };
 
-       event.defaultPrevented = false;
 
-       // Stop the event from bubbling
 
-       event.stopPropagation = function () {
 
-         if (old.stopPropagation) {
 
-           old.stopPropagation();
 
-         }
 
-         event.cancelBubble = true;
 
-         old.cancelBubble = true;
 
-         event.isPropagationStopped = returnTrue;
 
-       };
 
-       event.isPropagationStopped = returnFalse;
 
-       // Stop the event from bubbling and executing other handlers
 
-       event.stopImmediatePropagation = function () {
 
-         if (old.stopImmediatePropagation) {
 
-           old.stopImmediatePropagation();
 
-         }
 
-         event.isImmediatePropagationStopped = returnTrue;
 
-         event.stopPropagation();
 
-       };
 
-       event.isImmediatePropagationStopped = returnFalse;
 
-       // Handle mouse position
 
-       if (event.clientX !== null && event.clientX !== undefined) {
 
-         const doc = document.documentElement;
 
-         const body = document.body;
 
-         event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
 
-         event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
 
-       }
 
-       // Handle key presses
 
-       event.which = event.charCode || event.keyCode;
 
-       // Fix button for mouse clicks:
 
-       // 0 == left; 1 == middle; 2 == right
 
-       if (event.button !== null && event.button !== undefined) {
 
-         // The following is disabled because it does not pass videojs-standard
 
-         // and... yikes.
 
-         /* eslint-disable */
 
-         event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
 
-         /* eslint-enable */
 
-       }
 
-     }
 
-     event.fixed_ = true;
 
-     // Returns fixed-up instance
 
-     return event;
 
-   }
 
-   /**
 
-    * Whether passive event listeners are supported
 
-    */
 
-   let _supportsPassive;
 
-   const supportsPassive = function () {
 
-     if (typeof _supportsPassive !== 'boolean') {
 
-       _supportsPassive = false;
 
-       try {
 
-         const opts = Object.defineProperty({}, 'passive', {
 
-           get() {
 
-             _supportsPassive = true;
 
-           }
 
-         });
 
-         window.addEventListener('test', null, opts);
 
-         window.removeEventListener('test', null, opts);
 
-       } catch (e) {
 
-         // disregard
 
-       }
 
-     }
 
-     return _supportsPassive;
 
-   };
 
-   /**
 
-    * Touch events Chrome expects to be passive
 
-    */
 
-   const passiveEvents = ['touchstart', 'touchmove'];
 
-   /**
 
-    * Add an event listener to element
 
-    * It stores the handler function in a separate cache object
 
-    * and adds a generic handler to the element's event,
 
-    * along with a unique id (guid) to the element.
 
-    *
 
-    * @param {Element|Object} elem
 
-    *        Element or object to bind listeners to
 
-    *
 
-    * @param {string|string[]} type
 
-    *        Type of event to bind to.
 
-    *
 
-    * @param {Function} fn
 
-    *        Event listener.
 
-    */
 
-   function on(elem, type, fn) {
 
-     if (Array.isArray(type)) {
 
-       return _handleMultipleEvents(on, elem, type, fn);
 
-     }
 
-     if (!DomData.has(elem)) {
 
-       DomData.set(elem, {});
 
-     }
 
-     const data = DomData.get(elem);
 
-     // We need a place to store all our handler data
 
-     if (!data.handlers) {
 
-       data.handlers = {};
 
-     }
 
-     if (!data.handlers[type]) {
 
-       data.handlers[type] = [];
 
-     }
 
-     if (!fn.guid) {
 
-       fn.guid = newGUID();
 
-     }
 
-     data.handlers[type].push(fn);
 
-     if (!data.dispatcher) {
 
-       data.disabled = false;
 
-       data.dispatcher = function (event, hash) {
 
-         if (data.disabled) {
 
-           return;
 
-         }
 
-         event = fixEvent(event);
 
-         const handlers = data.handlers[event.type];
 
-         if (handlers) {
 
-           // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
 
-           const handlersCopy = handlers.slice(0);
 
-           for (let m = 0, n = handlersCopy.length; m < n; m++) {
 
-             if (event.isImmediatePropagationStopped()) {
 
-               break;
 
-             } else {
 
-               try {
 
-                 handlersCopy[m].call(elem, event, hash);
 
-               } catch (e) {
 
-                 log$1.error(e);
 
-               }
 
-             }
 
-           }
 
-         }
 
-       };
 
-     }
 
-     if (data.handlers[type].length === 1) {
 
-       if (elem.addEventListener) {
 
-         let options = false;
 
-         if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
 
-           options = {
 
-             passive: true
 
-           };
 
-         }
 
-         elem.addEventListener(type, data.dispatcher, options);
 
-       } else if (elem.attachEvent) {
 
-         elem.attachEvent('on' + type, data.dispatcher);
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * Removes event listeners from an element
 
-    *
 
-    * @param {Element|Object} elem
 
-    *        Object to remove listeners from.
 
-    *
 
-    * @param {string|string[]} [type]
 
-    *        Type of listener to remove. Don't include to remove all events from element.
 
-    *
 
-    * @param {Function} [fn]
 
-    *        Specific listener to remove. Don't include to remove listeners for an event
 
-    *        type.
 
-    */
 
-   function off(elem, type, fn) {
 
-     // Don't want to add a cache object through getElData if not needed
 
-     if (!DomData.has(elem)) {
 
-       return;
 
-     }
 
-     const data = DomData.get(elem);
 
-     // If no events exist, nothing to unbind
 
-     if (!data.handlers) {
 
-       return;
 
-     }
 
-     if (Array.isArray(type)) {
 
-       return _handleMultipleEvents(off, elem, type, fn);
 
-     }
 
-     // Utility function
 
-     const removeType = function (el, t) {
 
-       data.handlers[t] = [];
 
-       _cleanUpEvents(el, t);
 
-     };
 
-     // Are we removing all bound events?
 
-     if (type === undefined) {
 
-       for (const t in data.handlers) {
 
-         if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
 
-           removeType(elem, t);
 
-         }
 
-       }
 
-       return;
 
-     }
 
-     const handlers = data.handlers[type];
 
-     // If no handlers exist, nothing to unbind
 
-     if (!handlers) {
 
-       return;
 
-     }
 
-     // If no listener was provided, remove all listeners for type
 
-     if (!fn) {
 
-       removeType(elem, type);
 
-       return;
 
-     }
 
-     // We're only removing a single handler
 
-     if (fn.guid) {
 
-       for (let n = 0; n < handlers.length; n++) {
 
-         if (handlers[n].guid === fn.guid) {
 
-           handlers.splice(n--, 1);
 
-         }
 
-       }
 
-     }
 
-     _cleanUpEvents(elem, type);
 
-   }
 
-   /**
 
-    * Trigger an event for an element
 
-    *
 
-    * @param {Element|Object} elem
 
-    *        Element to trigger an event on
 
-    *
 
-    * @param {EventTarget~Event|string} event
 
-    *        A string (the type) or an event object with a type attribute
 
-    *
 
-    * @param {Object} [hash]
 
-    *        data hash to pass along with the event
 
-    *
 
-    * @return {boolean|undefined}
 
-    *         Returns the opposite of `defaultPrevented` if default was
 
-    *         prevented. Otherwise, returns `undefined`
 
-    */
 
-   function trigger(elem, event, hash) {
 
-     // Fetches element data and a reference to the parent (for bubbling).
 
-     // Don't want to add a data object to cache for every parent,
 
-     // so checking hasElData first.
 
-     const elemData = DomData.has(elem) ? DomData.get(elem) : {};
 
-     const parent = elem.parentNode || elem.ownerDocument;
 
-     // type = event.type || event,
 
-     // handler;
 
-     // If an event name was passed as a string, creates an event out of it
 
-     if (typeof event === 'string') {
 
-       event = {
 
-         type: event,
 
-         target: elem
 
-       };
 
-     } else if (!event.target) {
 
-       event.target = elem;
 
-     }
 
-     // Normalizes the event properties.
 
-     event = fixEvent(event);
 
-     // If the passed element has a dispatcher, executes the established handlers.
 
-     if (elemData.dispatcher) {
 
-       elemData.dispatcher.call(elem, event, hash);
 
-     }
 
-     // Unless explicitly stopped or the event does not bubble (e.g. media events)
 
-     // recursively calls this function to bubble the event up the DOM.
 
-     if (parent && !event.isPropagationStopped() && event.bubbles === true) {
 
-       trigger.call(null, parent, event, hash);
 
-       // If at the top of the DOM, triggers the default action unless disabled.
 
-     } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
 
-       if (!DomData.has(event.target)) {
 
-         DomData.set(event.target, {});
 
-       }
 
-       const targetData = DomData.get(event.target);
 
-       // Checks if the target has a default action for this event.
 
-       if (event.target[event.type]) {
 
-         // Temporarily disables event dispatching on the target as we have already executed the handler.
 
-         targetData.disabled = true;
 
-         // Executes the default action.
 
-         if (typeof event.target[event.type] === 'function') {
 
-           event.target[event.type]();
 
-         }
 
-         // Re-enables event dispatching.
 
-         targetData.disabled = false;
 
-       }
 
-     }
 
-     // Inform the triggerer if the default was prevented by returning false
 
-     return !event.defaultPrevented;
 
-   }
 
-   /**
 
-    * Trigger a listener only once for an event.
 
-    *
 
-    * @param {Element|Object} elem
 
-    *        Element or object to bind to.
 
-    *
 
-    * @param {string|string[]} type
 
-    *        Name/type of event
 
-    *
 
-    * @param {Event~EventListener} fn
 
-    *        Event listener function
 
-    */
 
-   function one(elem, type, fn) {
 
-     if (Array.isArray(type)) {
 
-       return _handleMultipleEvents(one, elem, type, fn);
 
-     }
 
-     const func = function () {
 
-       off(elem, type, func);
 
-       fn.apply(this, arguments);
 
-     };
 
-     // copy the guid to the new function so it can removed using the original function's ID
 
-     func.guid = fn.guid = fn.guid || newGUID();
 
-     on(elem, type, func);
 
-   }
 
-   /**
 
-    * Trigger a listener only once and then turn if off for all
 
-    * configured events
 
-    *
 
-    * @param {Element|Object} elem
 
-    *        Element or object to bind to.
 
-    *
 
-    * @param {string|string[]} type
 
-    *        Name/type of event
 
-    *
 
-    * @param {Event~EventListener} fn
 
-    *        Event listener function
 
-    */
 
-   function any(elem, type, fn) {
 
-     const func = function () {
 
-       off(elem, type, func);
 
-       fn.apply(this, arguments);
 
-     };
 
-     // copy the guid to the new function so it can removed using the original function's ID
 
-     func.guid = fn.guid = fn.guid || newGUID();
 
-     // multiple ons, but one off for everything
 
-     on(elem, type, func);
 
-   }
 
-   var Events = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     fixEvent: fixEvent,
 
-     on: on,
 
-     off: off,
 
-     trigger: trigger,
 
-     one: one,
 
-     any: any
 
-   });
 
-   /**
 
-    * @file fn.js
 
-    * @module fn
 
-    */
 
-   const UPDATE_REFRESH_INTERVAL = 30;
 
-   /**
 
-    * A private, internal-only function for changing the context of a function.
 
-    *
 
-    * It also stores a unique id on the function so it can be easily removed from
 
-    * events.
 
-    *
 
-    * @private
 
-    * @function
 
-    * @param    {*} context
 
-    *           The object to bind as scope.
 
-    *
 
-    * @param    {Function} fn
 
-    *           The function to be bound to a scope.
 
-    *
 
-    * @param    {number} [uid]
 
-    *           An optional unique ID for the function to be set
 
-    *
 
-    * @return   {Function}
 
-    *           The new function that will be bound into the context given
 
-    */
 
-   const bind_ = function (context, fn, uid) {
 
-     // Make sure the function has a unique ID
 
-     if (!fn.guid) {
 
-       fn.guid = newGUID();
 
-     }
 
-     // Create the new function that changes the context
 
-     const bound = fn.bind(context);
 
-     // Allow for the ability to individualize this function
 
-     // Needed in the case where multiple objects might share the same prototype
 
-     // IF both items add an event listener with the same function, then you try to remove just one
 
-     // it will remove both because they both have the same guid.
 
-     // when using this, you need to use the bind method when you remove the listener as well.
 
-     // currently used in text tracks
 
-     bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
 
-     return bound;
 
-   };
 
-   /**
 
-    * Wraps the given function, `fn`, with a new function that only invokes `fn`
 
-    * at most once per every `wait` milliseconds.
 
-    *
 
-    * @function
 
-    * @param    {Function} fn
 
-    *           The function to be throttled.
 
-    *
 
-    * @param    {number}   wait
 
-    *           The number of milliseconds by which to throttle.
 
-    *
 
-    * @return   {Function}
 
-    */
 
-   const throttle = function (fn, wait) {
 
-     let last = window.performance.now();
 
-     const throttled = function (...args) {
 
-       const now = window.performance.now();
 
-       if (now - last >= wait) {
 
-         fn(...args);
 
-         last = now;
 
-       }
 
-     };
 
-     return throttled;
 
-   };
 
-   /**
 
-    * Creates a debounced function that delays invoking `func` until after `wait`
 
-    * milliseconds have elapsed since the last time the debounced function was
 
-    * invoked.
 
-    *
 
-    * Inspired by lodash and underscore implementations.
 
-    *
 
-    * @function
 
-    * @param    {Function} func
 
-    *           The function to wrap with debounce behavior.
 
-    *
 
-    * @param    {number} wait
 
-    *           The number of milliseconds to wait after the last invocation.
 
-    *
 
-    * @param    {boolean} [immediate]
 
-    *           Whether or not to invoke the function immediately upon creation.
 
-    *
 
-    * @param    {Object} [context=window]
 
-    *           The "context" in which the debounced function should debounce. For
 
-    *           example, if this function should be tied to a Video.js player,
 
-    *           the player can be passed here. Alternatively, defaults to the
 
-    *           global `window` object.
 
-    *
 
-    * @return   {Function}
 
-    *           A debounced function.
 
-    */
 
-   const debounce = function (func, wait, immediate, context = window) {
 
-     let timeout;
 
-     const cancel = () => {
 
-       context.clearTimeout(timeout);
 
-       timeout = null;
 
-     };
 
-     /* eslint-disable consistent-this */
 
-     const debounced = function () {
 
-       const self = this;
 
-       const args = arguments;
 
-       let later = function () {
 
-         timeout = null;
 
-         later = null;
 
-         if (!immediate) {
 
-           func.apply(self, args);
 
-         }
 
-       };
 
-       if (!timeout && immediate) {
 
-         func.apply(self, args);
 
-       }
 
-       context.clearTimeout(timeout);
 
-       timeout = context.setTimeout(later, wait);
 
-     };
 
-     /* eslint-enable consistent-this */
 
-     debounced.cancel = cancel;
 
-     return debounced;
 
-   };
 
-   var Fn = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     UPDATE_REFRESH_INTERVAL: UPDATE_REFRESH_INTERVAL,
 
-     bind_: bind_,
 
-     throttle: throttle,
 
-     debounce: debounce
 
-   });
 
-   /**
 
-    * @file src/js/event-target.js
 
-    */
 
-   let EVENT_MAP;
 
-   /**
 
-    * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
 
-    * adds shorthand functions that wrap around lengthy functions. For example:
 
-    * the `on` function is a wrapper around `addEventListener`.
 
-    *
 
-    * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
 
-    * @class EventTarget
 
-    */
 
-   class EventTarget$2 {
 
-     /**
 
-      * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
 
-      * function that will get called when an event with a certain name gets triggered.
 
-      *
 
-      * @param {string|string[]} type
 
-      *        An event name or an array of event names.
 
-      *
 
-      * @param {Function} fn
 
-      *        The function to call with `EventTarget`s
 
-      */
 
-     on(type, fn) {
 
-       // Remove the addEventListener alias before calling Events.on
 
-       // so we don't get into an infinite type loop
 
-       const ael = this.addEventListener;
 
-       this.addEventListener = () => {};
 
-       on(this, type, fn);
 
-       this.addEventListener = ael;
 
-     }
 
-     /**
 
-      * Removes an `event listener` for a specific event from an instance of `EventTarget`.
 
-      * This makes it so that the `event listener` will no longer get called when the
 
-      * named event happens.
 
-      *
 
-      * @param {string|string[]} type
 
-      *        An event name or an array of event names.
 
-      *
 
-      * @param {Function} fn
 
-      *        The function to remove.
 
-      */
 
-     off(type, fn) {
 
-       off(this, type, fn);
 
-     }
 
-     /**
 
-      * This function will add an `event listener` that gets triggered only once. After the
 
-      * first trigger it will get removed. This is like adding an `event listener`
 
-      * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
 
-      *
 
-      * @param {string|string[]} type
 
-      *        An event name or an array of event names.
 
-      *
 
-      * @param {Function} fn
 
-      *        The function to be called once for each event name.
 
-      */
 
-     one(type, fn) {
 
-       // Remove the addEventListener aliasing Events.on
 
-       // so we don't get into an infinite type loop
 
-       const ael = this.addEventListener;
 
-       this.addEventListener = () => {};
 
-       one(this, type, fn);
 
-       this.addEventListener = ael;
 
-     }
 
-     /**
 
-      * This function will add an `event listener` that gets triggered only once and is
 
-      * removed from all events. This is like adding an array of `event listener`s
 
-      * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
 
-      * first time it is triggered.
 
-      *
 
-      * @param {string|string[]} type
 
-      *        An event name or an array of event names.
 
-      *
 
-      * @param {Function} fn
 
-      *        The function to be called once for each event name.
 
-      */
 
-     any(type, fn) {
 
-       // Remove the addEventListener aliasing Events.on
 
-       // so we don't get into an infinite type loop
 
-       const ael = this.addEventListener;
 
-       this.addEventListener = () => {};
 
-       any(this, type, fn);
 
-       this.addEventListener = ael;
 
-     }
 
-     /**
 
-      * This function causes an event to happen. This will then cause any `event listeners`
 
-      * that are waiting for that event, to get called. If there are no `event listeners`
 
-      * for an event then nothing will happen.
 
-      *
 
-      * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
 
-      * Trigger will also call the `on` + `uppercaseEventName` function.
 
-      *
 
-      * Example:
 
-      * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
 
-      * `onClick` if it exists.
 
-      *
 
-      * @param {string|EventTarget~Event|Object} event
 
-      *        The name of the event, an `Event`, or an object with a key of type set to
 
-      *        an event name.
 
-      */
 
-     trigger(event) {
 
-       const type = event.type || event;
 
-       // deprecation
 
-       // In a future version we should default target to `this`
 
-       // similar to how we default the target to `elem` in
 
-       // `Events.trigger`. Right now the default `target` will be
 
-       // `document` due to the `Event.fixEvent` call.
 
-       if (typeof event === 'string') {
 
-         event = {
 
-           type
 
-         };
 
-       }
 
-       event = fixEvent(event);
 
-       if (this.allowedEvents_[type] && this['on' + type]) {
 
-         this['on' + type](event);
 
-       }
 
-       trigger(this, event);
 
-     }
 
-     queueTrigger(event) {
 
-       // only set up EVENT_MAP if it'll be used
 
-       if (!EVENT_MAP) {
 
-         EVENT_MAP = new Map();
 
-       }
 
-       const type = event.type || event;
 
-       let map = EVENT_MAP.get(this);
 
-       if (!map) {
 
-         map = new Map();
 
-         EVENT_MAP.set(this, map);
 
-       }
 
-       const oldTimeout = map.get(type);
 
-       map.delete(type);
 
-       window.clearTimeout(oldTimeout);
 
-       const timeout = window.setTimeout(() => {
 
-         map.delete(type);
 
-         // if we cleared out all timeouts for the current target, delete its map
 
-         if (map.size === 0) {
 
-           map = null;
 
-           EVENT_MAP.delete(this);
 
-         }
 
-         this.trigger(event);
 
-       }, 0);
 
-       map.set(type, timeout);
 
-     }
 
-   }
 
-   /**
 
-    * A Custom DOM event.
 
-    *
 
-    * @typedef {CustomEvent} Event
 
-    * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
 
-    */
 
-   /**
 
-    * All event listeners should follow the following format.
 
-    *
 
-    * @callback EventTarget~EventListener
 
-    * @this {EventTarget}
 
-    *
 
-    * @param {Event} event
 
-    *        the event that triggered this function
 
-    *
 
-    * @param {Object} [hash]
 
-    *        hash of data sent during the event
 
-    */
 
-   /**
 
-    * An object containing event names as keys and booleans as values.
 
-    *
 
-    * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
 
-    *         will have extra functionality. See that function for more information.
 
-    *
 
-    * @property EventTarget.prototype.allowedEvents_
 
-    * @private
 
-    */
 
-   EventTarget$2.prototype.allowedEvents_ = {};
 
-   /**
 
-    * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
 
-    * the standard DOM API.
 
-    *
 
-    * @function
 
-    * @see {@link EventTarget#on}
 
-    */
 
-   EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on;
 
-   /**
 
-    * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
 
-    * the standard DOM API.
 
-    *
 
-    * @function
 
-    * @see {@link EventTarget#off}
 
-    */
 
-   EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off;
 
-   /**
 
-    * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
 
-    * the standard DOM API.
 
-    *
 
-    * @function
 
-    * @see {@link EventTarget#trigger}
 
-    */
 
-   EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger;
 
-   /**
 
-    * @file mixins/evented.js
 
-    * @module evented
 
-    */
 
-   const objName = obj => {
 
-     if (typeof obj.name === 'function') {
 
-       return obj.name();
 
-     }
 
-     if (typeof obj.name === 'string') {
 
-       return obj.name;
 
-     }
 
-     if (obj.name_) {
 
-       return obj.name_;
 
-     }
 
-     if (obj.constructor && obj.constructor.name) {
 
-       return obj.constructor.name;
 
-     }
 
-     return typeof obj;
 
-   };
 
-   /**
 
-    * Returns whether or not an object has had the evented mixin applied.
 
-    *
 
-    * @param  {Object} object
 
-    *         An object to test.
 
-    *
 
-    * @return {boolean}
 
-    *         Whether or not the object appears to be evented.
 
-    */
 
-   const isEvented = object => object instanceof EventTarget$2 || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(k => typeof object[k] === 'function');
 
-   /**
 
-    * Adds a callback to run after the evented mixin applied.
 
-    *
 
-    * @param  {Object} object
 
-    *         An object to Add
 
-    * @param  {Function} callback
 
-    *         The callback to run.
 
-    */
 
-   const addEventedCallback = (target, callback) => {
 
-     if (isEvented(target)) {
 
-       callback();
 
-     } else {
 
-       if (!target.eventedCallbacks) {
 
-         target.eventedCallbacks = [];
 
-       }
 
-       target.eventedCallbacks.push(callback);
 
-     }
 
-   };
 
-   /**
 
-    * Whether a value is a valid event type - non-empty string or array.
 
-    *
 
-    * @private
 
-    * @param  {string|Array} type
 
-    *         The type value to test.
 
-    *
 
-    * @return {boolean}
 
-    *         Whether or not the type is a valid event type.
 
-    */
 
-   const isValidEventType = type =>
 
-   // The regex here verifies that the `type` contains at least one non-
 
-   // whitespace character.
 
-   typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length;
 
-   /**
 
-    * Validates a value to determine if it is a valid event target. Throws if not.
 
-    *
 
-    * @private
 
-    * @throws {Error}
 
-    *         If the target does not appear to be a valid event target.
 
-    *
 
-    * @param  {Object} target
 
-    *         The object to test.
 
-    *
 
-    * @param  {Object} obj
 
-    *         The evented object we are validating for
 
-    *
 
-    * @param  {string} fnName
 
-    *         The name of the evented mixin function that called this.
 
-    */
 
-   const validateTarget = (target, obj, fnName) => {
 
-     if (!target || !target.nodeName && !isEvented(target)) {
 
-       throw new Error(`Invalid target for ${objName(obj)}#${fnName}; must be a DOM node or evented object.`);
 
-     }
 
-   };
 
-   /**
 
-    * Validates a value to determine if it is a valid event target. Throws if not.
 
-    *
 
-    * @private
 
-    * @throws {Error}
 
-    *         If the type does not appear to be a valid event type.
 
-    *
 
-    * @param  {string|Array} type
 
-    *         The type to test.
 
-    *
 
-    * @param  {Object} obj
 
-   *         The evented object we are validating for
 
-    *
 
-    * @param  {string} fnName
 
-    *         The name of the evented mixin function that called this.
 
-    */
 
-   const validateEventType = (type, obj, fnName) => {
 
-     if (!isValidEventType(type)) {
 
-       throw new Error(`Invalid event type for ${objName(obj)}#${fnName}; must be a non-empty string or array.`);
 
-     }
 
-   };
 
-   /**
 
-    * Validates a value to determine if it is a valid listener. Throws if not.
 
-    *
 
-    * @private
 
-    * @throws {Error}
 
-    *         If the listener is not a function.
 
-    *
 
-    * @param  {Function} listener
 
-    *         The listener to test.
 
-    *
 
-    * @param  {Object} obj
 
-    *         The evented object we are validating for
 
-    *
 
-    * @param  {string} fnName
 
-    *         The name of the evented mixin function that called this.
 
-    */
 
-   const validateListener = (listener, obj, fnName) => {
 
-     if (typeof listener !== 'function') {
 
-       throw new Error(`Invalid listener for ${objName(obj)}#${fnName}; must be a function.`);
 
-     }
 
-   };
 
-   /**
 
-    * Takes an array of arguments given to `on()` or `one()`, validates them, and
 
-    * normalizes them into an object.
 
-    *
 
-    * @private
 
-    * @param  {Object} self
 
-    *         The evented object on which `on()` or `one()` was called. This
 
-    *         object will be bound as the `this` value for the listener.
 
-    *
 
-    * @param  {Array} args
 
-    *         An array of arguments passed to `on()` or `one()`.
 
-    *
 
-    * @param  {string} fnName
 
-    *         The name of the evented mixin function that called this.
 
-    *
 
-    * @return {Object}
 
-    *         An object containing useful values for `on()` or `one()` calls.
 
-    */
 
-   const normalizeListenArgs = (self, args, fnName) => {
 
-     // If the number of arguments is less than 3, the target is always the
 
-     // evented object itself.
 
-     const isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
 
-     let target;
 
-     let type;
 
-     let listener;
 
-     if (isTargetingSelf) {
 
-       target = self.eventBusEl_;
 
-       // Deal with cases where we got 3 arguments, but we are still listening to
 
-       // the evented object itself.
 
-       if (args.length >= 3) {
 
-         args.shift();
 
-       }
 
-       [type, listener] = args;
 
-     } else {
 
-       [target, type, listener] = args;
 
-     }
 
-     validateTarget(target, self, fnName);
 
-     validateEventType(type, self, fnName);
 
-     validateListener(listener, self, fnName);
 
-     listener = bind_(self, listener);
 
-     return {
 
-       isTargetingSelf,
 
-       target,
 
-       type,
 
-       listener
 
-     };
 
-   };
 
-   /**
 
-    * Adds the listener to the event type(s) on the target, normalizing for
 
-    * the type of target.
 
-    *
 
-    * @private
 
-    * @param  {Element|Object} target
 
-    *         A DOM node or evented object.
 
-    *
 
-    * @param  {string} method
 
-    *         The event binding method to use ("on" or "one").
 
-    *
 
-    * @param  {string|Array} type
 
-    *         One or more event type(s).
 
-    *
 
-    * @param  {Function} listener
 
-    *         A listener function.
 
-    */
 
-   const listen = (target, method, type, listener) => {
 
-     validateTarget(target, target, method);
 
-     if (target.nodeName) {
 
-       Events[method](target, type, listener);
 
-     } else {
 
-       target[method](type, listener);
 
-     }
 
-   };
 
-   /**
 
-    * Contains methods that provide event capabilities to an object which is passed
 
-    * to {@link module:evented|evented}.
 
-    *
 
-    * @mixin EventedMixin
 
-    */
 
-   const EventedMixin = {
 
-     /**
 
-      * Add a listener to an event (or events) on this object or another evented
 
-      * object.
 
-      *
 
-      * @param  {string|Array|Element|Object} targetOrType
 
-      *         If this is a string or array, it represents the event type(s)
 
-      *         that will trigger the listener.
 
-      *
 
-      *         Another evented object can be passed here instead, which will
 
-      *         cause the listener to listen for events on _that_ object.
 
-      *
 
-      *         In either case, the listener's `this` value will be bound to
 
-      *         this object.
 
-      *
 
-      * @param  {string|Array|Function} typeOrListener
 
-      *         If the first argument was a string or array, this should be the
 
-      *         listener function. Otherwise, this is a string or array of event
 
-      *         type(s).
 
-      *
 
-      * @param  {Function} [listener]
 
-      *         If the first argument was another evented object, this will be
 
-      *         the listener function.
 
-      */
 
-     on(...args) {
 
-       const {
 
-         isTargetingSelf,
 
-         target,
 
-         type,
 
-         listener
 
-       } = normalizeListenArgs(this, args, 'on');
 
-       listen(target, 'on', type, listener);
 
-       // If this object is listening to another evented object.
 
-       if (!isTargetingSelf) {
 
-         // If this object is disposed, remove the listener.
 
-         const removeListenerOnDispose = () => this.off(target, type, listener);
 
-         // Use the same function ID as the listener so we can remove it later it
 
-         // using the ID of the original listener.
 
-         removeListenerOnDispose.guid = listener.guid;
 
-         // Add a listener to the target's dispose event as well. This ensures
 
-         // that if the target is disposed BEFORE this object, we remove the
 
-         // removal listener that was just added. Otherwise, we create a memory leak.
 
-         const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);
 
-         // Use the same function ID as the listener so we can remove it later
 
-         // it using the ID of the original listener.
 
-         removeRemoverOnTargetDispose.guid = listener.guid;
 
-         listen(this, 'on', 'dispose', removeListenerOnDispose);
 
-         listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
 
-       }
 
-     },
 
-     /**
 
-      * Add a listener to an event (or events) on this object or another evented
 
-      * object. The listener will be called once per event and then removed.
 
-      *
 
-      * @param  {string|Array|Element|Object} targetOrType
 
-      *         If this is a string or array, it represents the event type(s)
 
-      *         that will trigger the listener.
 
-      *
 
-      *         Another evented object can be passed here instead, which will
 
-      *         cause the listener to listen for events on _that_ object.
 
-      *
 
-      *         In either case, the listener's `this` value will be bound to
 
-      *         this object.
 
-      *
 
-      * @param  {string|Array|Function} typeOrListener
 
-      *         If the first argument was a string or array, this should be the
 
-      *         listener function. Otherwise, this is a string or array of event
 
-      *         type(s).
 
-      *
 
-      * @param  {Function} [listener]
 
-      *         If the first argument was another evented object, this will be
 
-      *         the listener function.
 
-      */
 
-     one(...args) {
 
-       const {
 
-         isTargetingSelf,
 
-         target,
 
-         type,
 
-         listener
 
-       } = normalizeListenArgs(this, args, 'one');
 
-       // Targeting this evented object.
 
-       if (isTargetingSelf) {
 
-         listen(target, 'one', type, listener);
 
-         // Targeting another evented object.
 
-       } else {
 
-         // TODO: This wrapper is incorrect! It should only
 
-         //       remove the wrapper for the event type that called it.
 
-         //       Instead all listeners are removed on the first trigger!
 
-         //       see https://github.com/videojs/video.js/issues/5962
 
-         const wrapper = (...largs) => {
 
-           this.off(target, type, wrapper);
 
-           listener.apply(null, largs);
 
-         };
 
-         // Use the same function ID as the listener so we can remove it later
 
-         // it using the ID of the original listener.
 
-         wrapper.guid = listener.guid;
 
-         listen(target, 'one', type, wrapper);
 
-       }
 
-     },
 
-     /**
 
-      * Add a listener to an event (or events) on this object or another evented
 
-      * object. The listener will only be called once for the first event that is triggered
 
-      * then removed.
 
-      *
 
-      * @param  {string|Array|Element|Object} targetOrType
 
-      *         If this is a string or array, it represents the event type(s)
 
-      *         that will trigger the listener.
 
-      *
 
-      *         Another evented object can be passed here instead, which will
 
-      *         cause the listener to listen for events on _that_ object.
 
-      *
 
-      *         In either case, the listener's `this` value will be bound to
 
-      *         this object.
 
-      *
 
-      * @param  {string|Array|Function} typeOrListener
 
-      *         If the first argument was a string or array, this should be the
 
-      *         listener function. Otherwise, this is a string or array of event
 
-      *         type(s).
 
-      *
 
-      * @param  {Function} [listener]
 
-      *         If the first argument was another evented object, this will be
 
-      *         the listener function.
 
-      */
 
-     any(...args) {
 
-       const {
 
-         isTargetingSelf,
 
-         target,
 
-         type,
 
-         listener
 
-       } = normalizeListenArgs(this, args, 'any');
 
-       // Targeting this evented object.
 
-       if (isTargetingSelf) {
 
-         listen(target, 'any', type, listener);
 
-         // Targeting another evented object.
 
-       } else {
 
-         const wrapper = (...largs) => {
 
-           this.off(target, type, wrapper);
 
-           listener.apply(null, largs);
 
-         };
 
-         // Use the same function ID as the listener so we can remove it later
 
-         // it using the ID of the original listener.
 
-         wrapper.guid = listener.guid;
 
-         listen(target, 'any', type, wrapper);
 
-       }
 
-     },
 
-     /**
 
-      * Removes listener(s) from event(s) on an evented object.
 
-      *
 
-      * @param  {string|Array|Element|Object} [targetOrType]
 
-      *         If this is a string or array, it represents the event type(s).
 
-      *
 
-      *         Another evented object can be passed here instead, in which case
 
-      *         ALL 3 arguments are _required_.
 
-      *
 
-      * @param  {string|Array|Function} [typeOrListener]
 
-      *         If the first argument was a string or array, this may be the
 
-      *         listener function. Otherwise, this is a string or array of event
 
-      *         type(s).
 
-      *
 
-      * @param  {Function} [listener]
 
-      *         If the first argument was another evented object, this will be
 
-      *         the listener function; otherwise, _all_ listeners bound to the
 
-      *         event type(s) will be removed.
 
-      */
 
-     off(targetOrType, typeOrListener, listener) {
 
-       // Targeting this evented object.
 
-       if (!targetOrType || isValidEventType(targetOrType)) {
 
-         off(this.eventBusEl_, targetOrType, typeOrListener);
 
-         // Targeting another evented object.
 
-       } else {
 
-         const target = targetOrType;
 
-         const type = typeOrListener;
 
-         // Fail fast and in a meaningful way!
 
-         validateTarget(target, this, 'off');
 
-         validateEventType(type, this, 'off');
 
-         validateListener(listener, this, 'off');
 
-         // Ensure there's at least a guid, even if the function hasn't been used
 
-         listener = bind_(this, listener);
 
-         // Remove the dispose listener on this evented object, which was given
 
-         // the same guid as the event listener in on().
 
-         this.off('dispose', listener);
 
-         if (target.nodeName) {
 
-           off(target, type, listener);
 
-           off(target, 'dispose', listener);
 
-         } else if (isEvented(target)) {
 
-           target.off(type, listener);
 
-           target.off('dispose', listener);
 
-         }
 
-       }
 
-     },
 
-     /**
 
-      * Fire an event on this evented object, causing its listeners to be called.
 
-      *
 
-      * @param   {string|Object} event
 
-      *          An event type or an object with a type property.
 
-      *
 
-      * @param   {Object} [hash]
 
-      *          An additional object to pass along to listeners.
 
-      *
 
-      * @return {boolean}
 
-      *          Whether or not the default behavior was prevented.
 
-      */
 
-     trigger(event, hash) {
 
-       validateTarget(this.eventBusEl_, this, 'trigger');
 
-       const type = event && typeof event !== 'string' ? event.type : event;
 
-       if (!isValidEventType(type)) {
 
-         throw new Error(`Invalid event type for ${objName(this)}#trigger; ` + 'must be a non-empty string or object with a type key that has a non-empty value.');
 
-       }
 
-       return trigger(this.eventBusEl_, event, hash);
 
-     }
 
-   };
 
-   /**
 
-    * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
 
-    *
 
-    * @param  {Object} target
 
-    *         The object to which to add event methods.
 
-    *
 
-    * @param  {Object} [options={}]
 
-    *         Options for customizing the mixin behavior.
 
-    *
 
-    * @param  {string} [options.eventBusKey]
 
-    *         By default, adds a `eventBusEl_` DOM element to the target object,
 
-    *         which is used as an event bus. If the target object already has a
 
-    *         DOM element that should be used, pass its key here.
 
-    *
 
-    * @return {Object}
 
-    *         The target object.
 
-    */
 
-   function evented(target, options = {}) {
 
-     const {
 
-       eventBusKey
 
-     } = options;
 
-     // Set or create the eventBusEl_.
 
-     if (eventBusKey) {
 
-       if (!target[eventBusKey].nodeName) {
 
-         throw new Error(`The eventBusKey "${eventBusKey}" does not refer to an element.`);
 
-       }
 
-       target.eventBusEl_ = target[eventBusKey];
 
-     } else {
 
-       target.eventBusEl_ = createEl('span', {
 
-         className: 'vjs-event-bus'
 
-       });
 
-     }
 
-     Object.assign(target, EventedMixin);
 
-     if (target.eventedCallbacks) {
 
-       target.eventedCallbacks.forEach(callback => {
 
-         callback();
 
-       });
 
-     }
 
-     // When any evented object is disposed, it removes all its listeners.
 
-     target.on('dispose', () => {
 
-       target.off();
 
-       [target, target.el_, target.eventBusEl_].forEach(function (val) {
 
-         if (val && DomData.has(val)) {
 
-           DomData.delete(val);
 
-         }
 
-       });
 
-       window.setTimeout(() => {
 
-         target.eventBusEl_ = null;
 
-       }, 0);
 
-     });
 
-     return target;
 
-   }
 
-   /**
 
-    * @file mixins/stateful.js
 
-    * @module stateful
 
-    */
 
-   /**
 
-    * Contains methods that provide statefulness to an object which is passed
 
-    * to {@link module:stateful}.
 
-    *
 
-    * @mixin StatefulMixin
 
-    */
 
-   const StatefulMixin = {
 
-     /**
 
-      * A hash containing arbitrary keys and values representing the state of
 
-      * the object.
 
-      *
 
-      * @type {Object}
 
-      */
 
-     state: {},
 
-     /**
 
-      * Set the state of an object by mutating its
 
-      * {@link module:stateful~StatefulMixin.state|state} object in place.
 
-      *
 
-      * @fires   module:stateful~StatefulMixin#statechanged
 
-      * @param   {Object|Function} stateUpdates
 
-      *          A new set of properties to shallow-merge into the plugin state.
 
-      *          Can be a plain object or a function returning a plain object.
 
-      *
 
-      * @return {Object|undefined}
 
-      *          An object containing changes that occurred. If no changes
 
-      *          occurred, returns `undefined`.
 
-      */
 
-     setState(stateUpdates) {
 
-       // Support providing the `stateUpdates` state as a function.
 
-       if (typeof stateUpdates === 'function') {
 
-         stateUpdates = stateUpdates();
 
-       }
 
-       let changes;
 
-       each(stateUpdates, (value, key) => {
 
-         // Record the change if the value is different from what's in the
 
-         // current state.
 
-         if (this.state[key] !== value) {
 
-           changes = changes || {};
 
-           changes[key] = {
 
-             from: this.state[key],
 
-             to: value
 
-           };
 
-         }
 
-         this.state[key] = value;
 
-       });
 
-       // Only trigger "statechange" if there were changes AND we have a trigger
 
-       // function. This allows us to not require that the target object be an
 
-       // evented object.
 
-       if (changes && isEvented(this)) {
 
-         /**
 
-          * An event triggered on an object that is both
 
-          * {@link module:stateful|stateful} and {@link module:evented|evented}
 
-          * indicating that its state has changed.
 
-          *
 
-          * @event    module:stateful~StatefulMixin#statechanged
 
-          * @type     {Object}
 
-          * @property {Object} changes
 
-          *           A hash containing the properties that were changed and
 
-          *           the values they were changed `from` and `to`.
 
-          */
 
-         this.trigger({
 
-           changes,
 
-           type: 'statechanged'
 
-         });
 
-       }
 
-       return changes;
 
-     }
 
-   };
 
-   /**
 
-    * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
 
-    * object.
 
-    *
 
-    * If the target object is {@link module:evented|evented} and has a
 
-    * `handleStateChanged` method, that method will be automatically bound to the
 
-    * `statechanged` event on itself.
 
-    *
 
-    * @param   {Object} target
 
-    *          The object to be made stateful.
 
-    *
 
-    * @param   {Object} [defaultState]
 
-    *          A default set of properties to populate the newly-stateful object's
 
-    *          `state` property.
 
-    *
 
-    * @return {Object}
 
-    *          Returns the `target`.
 
-    */
 
-   function stateful(target, defaultState) {
 
-     Object.assign(target, StatefulMixin);
 
-     // This happens after the mixing-in because we need to replace the `state`
 
-     // added in that step.
 
-     target.state = Object.assign({}, target.state, defaultState);
 
-     // Auto-bind the `handleStateChanged` method of the target object if it exists.
 
-     if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
 
-       target.on('statechanged', target.handleStateChanged);
 
-     }
 
-     return target;
 
-   }
 
-   /**
 
-    * @file str.js
 
-    * @module to-lower-case
 
-    */
 
-   /**
 
-    * Lowercase the first letter of a string.
 
-    *
 
-    * @param {string} string
 
-    *        String to be lowercased
 
-    *
 
-    * @return {string}
 
-    *         The string with a lowercased first letter
 
-    */
 
-   const toLowerCase = function (string) {
 
-     if (typeof string !== 'string') {
 
-       return string;
 
-     }
 
-     return string.replace(/./, w => w.toLowerCase());
 
-   };
 
-   /**
 
-    * Uppercase the first letter of a string.
 
-    *
 
-    * @param {string} string
 
-    *        String to be uppercased
 
-    *
 
-    * @return {string}
 
-    *         The string with an uppercased first letter
 
-    */
 
-   const toTitleCase$1 = function (string) {
 
-     if (typeof string !== 'string') {
 
-       return string;
 
-     }
 
-     return string.replace(/./, w => w.toUpperCase());
 
-   };
 
-   /**
 
-    * Compares the TitleCase versions of the two strings for equality.
 
-    *
 
-    * @param {string} str1
 
-    *        The first string to compare
 
-    *
 
-    * @param {string} str2
 
-    *        The second string to compare
 
-    *
 
-    * @return {boolean}
 
-    *         Whether the TitleCase versions of the strings are equal
 
-    */
 
-   const titleCaseEquals = function (str1, str2) {
 
-     return toTitleCase$1(str1) === toTitleCase$1(str2);
 
-   };
 
-   var Str = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     toLowerCase: toLowerCase,
 
-     toTitleCase: toTitleCase$1,
 
-     titleCaseEquals: titleCaseEquals
 
-   });
 
-   var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
 
-   function unwrapExports (x) {
 
-   	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
 
-   }
 
-   function createCommonjsModule(fn, module) {
 
-   	return module = { exports: {} }, fn(module, module.exports), module.exports;
 
-   }
 
-   var keycode = createCommonjsModule(function (module, exports) {
 
-     // Source: http://jsfiddle.net/vWx8V/
 
-     // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
 
-     /**
 
-      * Conenience method returns corresponding value for given keyName or keyCode.
 
-      *
 
-      * @param {Mixed} keyCode {Number} or keyName {String}
 
-      * @return {Mixed}
 
-      * @api public
 
-      */
 
-     function keyCode(searchInput) {
 
-       // Keyboard Events
 
-       if (searchInput && 'object' === typeof searchInput) {
 
-         var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
 
-         if (hasKeyCode) searchInput = hasKeyCode;
 
-       }
 
-       // Numbers
 
-       if ('number' === typeof searchInput) return names[searchInput];
 
-       // Everything else (cast to string)
 
-       var search = String(searchInput);
 
-       // check codes
 
-       var foundNamedKey = codes[search.toLowerCase()];
 
-       if (foundNamedKey) return foundNamedKey;
 
-       // check aliases
 
-       var foundNamedKey = aliases[search.toLowerCase()];
 
-       if (foundNamedKey) return foundNamedKey;
 
-       // weird character?
 
-       if (search.length === 1) return search.charCodeAt(0);
 
-       return undefined;
 
-     }
 
-     /**
 
-      * Compares a keyboard event with a given keyCode or keyName.
 
-      *
 
-      * @param {Event} event Keyboard event that should be tested
 
-      * @param {Mixed} keyCode {Number} or keyName {String}
 
-      * @return {Boolean}
 
-      * @api public
 
-      */
 
-     keyCode.isEventKey = function isEventKey(event, nameOrCode) {
 
-       if (event && 'object' === typeof event) {
 
-         var keyCode = event.which || event.keyCode || event.charCode;
 
-         if (keyCode === null || keyCode === undefined) {
 
-           return false;
 
-         }
 
-         if (typeof nameOrCode === 'string') {
 
-           // check codes
 
-           var foundNamedKey = codes[nameOrCode.toLowerCase()];
 
-           if (foundNamedKey) {
 
-             return foundNamedKey === keyCode;
 
-           }
 
-           // check aliases
 
-           var foundNamedKey = aliases[nameOrCode.toLowerCase()];
 
-           if (foundNamedKey) {
 
-             return foundNamedKey === keyCode;
 
-           }
 
-         } else if (typeof nameOrCode === 'number') {
 
-           return nameOrCode === keyCode;
 
-         }
 
-         return false;
 
-       }
 
-     };
 
-     exports = module.exports = keyCode;
 
-     /**
 
-      * Get by name
 
-      *
 
-      *   exports.code['enter'] // => 13
 
-      */
 
-     var codes = exports.code = exports.codes = {
 
-       'backspace': 8,
 
-       'tab': 9,
 
-       'enter': 13,
 
-       'shift': 16,
 
-       'ctrl': 17,
 
-       'alt': 18,
 
-       'pause/break': 19,
 
-       'caps lock': 20,
 
-       'esc': 27,
 
-       'space': 32,
 
-       'page up': 33,
 
-       'page down': 34,
 
-       'end': 35,
 
-       'home': 36,
 
-       'left': 37,
 
-       'up': 38,
 
-       'right': 39,
 
-       'down': 40,
 
-       'insert': 45,
 
-       'delete': 46,
 
-       'command': 91,
 
-       'left command': 91,
 
-       'right command': 93,
 
-       'numpad *': 106,
 
-       'numpad +': 107,
 
-       'numpad -': 109,
 
-       'numpad .': 110,
 
-       'numpad /': 111,
 
-       'num lock': 144,
 
-       'scroll lock': 145,
 
-       'my computer': 182,
 
-       'my calculator': 183,
 
-       ';': 186,
 
-       '=': 187,
 
-       ',': 188,
 
-       '-': 189,
 
-       '.': 190,
 
-       '/': 191,
 
-       '`': 192,
 
-       '[': 219,
 
-       '\\': 220,
 
-       ']': 221,
 
-       "'": 222
 
-     };
 
-     // Helper aliases
 
-     var aliases = exports.aliases = {
 
-       'windows': 91,
 
-       '⇧': 16,
 
-       '⌥': 18,
 
-       '⌃': 17,
 
-       '⌘': 91,
 
-       'ctl': 17,
 
-       'control': 17,
 
-       'option': 18,
 
-       'pause': 19,
 
-       'break': 19,
 
-       'caps': 20,
 
-       'return': 13,
 
-       'escape': 27,
 
-       'spc': 32,
 
-       'spacebar': 32,
 
-       'pgup': 33,
 
-       'pgdn': 34,
 
-       'ins': 45,
 
-       'del': 46,
 
-       'cmd': 91
 
-     };
 
-     /*!
 
-      * Programatically add the following
 
-      */
 
-     // lower case chars
 
-     for (i = 97; i < 123; i++) codes[String.fromCharCode(i)] = i - 32;
 
-     // numbers
 
-     for (var i = 48; i < 58; i++) codes[i - 48] = i;
 
-     // function keys
 
-     for (i = 1; i < 13; i++) codes['f' + i] = i + 111;
 
-     // numpad keys
 
-     for (i = 0; i < 10; i++) codes['numpad ' + i] = i + 96;
 
-     /**
 
-      * Get by code
 
-      *
 
-      *   exports.name[13] // => 'Enter'
 
-      */
 
-     var names = exports.names = exports.title = {}; // title for backward compat
 
-     // Create reverse mapping
 
-     for (i in codes) names[codes[i]] = i;
 
-     // Add aliases
 
-     for (var alias in aliases) {
 
-       codes[alias] = aliases[alias];
 
-     }
 
-   });
 
-   keycode.code;
 
-   keycode.codes;
 
-   keycode.aliases;
 
-   keycode.names;
 
-   keycode.title;
 
-   /**
 
-    * Player Component - Base class for all UI objects
 
-    *
 
-    * @file component.js
 
-    */
 
-   /**
 
-    * Base class for all UI Components.
 
-    * Components are UI objects which represent both a javascript object and an element
 
-    * in the DOM. They can be children of other components, and can have
 
-    * children themselves.
 
-    *
 
-    * Components can also use methods from {@link EventTarget}
 
-    */
 
-   class Component$1 {
 
-     /**
 
-      * A callback that is called when a component is ready. Does not have any
 
-      * parameters and any callback value will be ignored.
 
-      *
 
-      * @callback ReadyCallback
 
-      * @this Component
 
-      */
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of component options.
 
-      *
 
-      * @param {Object[]} [options.children]
 
-      *        An array of children objects to initialize this component with. Children objects have
 
-      *        a name property that will be used if more than one component of the same type needs to be
 
-      *        added.
 
-      *
 
-      * @param  {string} [options.className]
 
-      *         A class or space separated list of classes to add the component
 
-      *
 
-      * @param {ReadyCallback} [ready]
 
-      *        Function that gets called when the `Component` is ready.
 
-      */
 
-     constructor(player, options, ready) {
 
-       // The component might be the player itself and we can't pass `this` to super
 
-       if (!player && this.play) {
 
-         this.player_ = player = this; // eslint-disable-line
 
-       } else {
 
-         this.player_ = player;
 
-       }
 
-       this.isDisposed_ = false;
 
-       // Hold the reference to the parent component via `addChild` method
 
-       this.parentComponent_ = null;
 
-       // Make a copy of prototype.options_ to protect against overriding defaults
 
-       this.options_ = merge$2({}, this.options_);
 
-       // Updated options with supplied options
 
-       options = this.options_ = merge$2(this.options_, options);
 
-       // Get ID from options or options element if one is supplied
 
-       this.id_ = options.id || options.el && options.el.id;
 
-       // If there was no ID from the options, generate one
 
-       if (!this.id_) {
 
-         // Don't require the player ID function in the case of mock players
 
-         const id = player && player.id && player.id() || 'no_player';
 
-         this.id_ = `${id}_component_${newGUID()}`;
 
-       }
 
-       this.name_ = options.name || null;
 
-       // Create element if one wasn't provided in options
 
-       if (options.el) {
 
-         this.el_ = options.el;
 
-       } else if (options.createEl !== false) {
 
-         this.el_ = this.createEl();
 
-       }
 
-       if (options.className && this.el_) {
 
-         options.className.split(' ').forEach(c => this.addClass(c));
 
-       }
 
-       // Remove the placeholder event methods. If the component is evented, the
 
-       // real methods are added next
 
-       ['on', 'off', 'one', 'any', 'trigger'].forEach(fn => {
 
-         this[fn] = undefined;
 
-       });
 
-       // if evented is anything except false, we want to mixin in evented
 
-       if (options.evented !== false) {
 
-         // Make this an evented object and use `el_`, if available, as its event bus
 
-         evented(this, {
 
-           eventBusKey: this.el_ ? 'el_' : null
 
-         });
 
-         this.handleLanguagechange = this.handleLanguagechange.bind(this);
 
-         this.on(this.player_, 'languagechange', this.handleLanguagechange);
 
-       }
 
-       stateful(this, this.constructor.defaultState);
 
-       this.children_ = [];
 
-       this.childIndex_ = {};
 
-       this.childNameIndex_ = {};
 
-       this.setTimeoutIds_ = new Set();
 
-       this.setIntervalIds_ = new Set();
 
-       this.rafIds_ = new Set();
 
-       this.namedRafs_ = new Map();
 
-       this.clearingTimersOnDispose_ = false;
 
-       // Add any child components in options
 
-       if (options.initChildren !== false) {
 
-         this.initChildren();
 
-       }
 
-       // Don't want to trigger ready here or it will go before init is actually
 
-       // finished for all children that run this constructor
 
-       this.ready(ready);
 
-       if (options.reportTouchActivity !== false) {
 
-         this.enableTouchActivity();
 
-       }
 
-     }
 
-     // `on`, `off`, `one`, `any` and `trigger` are here so tsc includes them in definitions.
 
-     // They are replaced or removed in the constructor
 
-     /**
 
-      * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
 
-      * function that will get called when an event with a certain name gets triggered.
 
-      *
 
-      * @param {string|string[]} type
 
-      *        An event name or an array of event names.
 
-      *
 
-      * @param {Function} fn
 
-      *        The function to call with `EventTarget`s
 
-      */
 
-     on(type, fn) {}
 
-     /**
 
-      * Removes an `event listener` for a specific event from an instance of `EventTarget`.
 
-      * This makes it so that the `event listener` will no longer get called when the
 
-      * named event happens.
 
-      *
 
-      * @param {string|string[]} type
 
-      *        An event name or an array of event names.
 
-      *
 
-      * @param {Function} fn
 
-      *        The function to remove.
 
-      */
 
-     off(type, fn) {}
 
-     /**
 
-      * This function will add an `event listener` that gets triggered only once. After the
 
-      * first trigger it will get removed. This is like adding an `event listener`
 
-      * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
 
-      *
 
-      * @param {string|string[]} type
 
-      *        An event name or an array of event names.
 
-      *
 
-      * @param {Function} fn
 
-      *        The function to be called once for each event name.
 
-      */
 
-     one(type, fn) {}
 
-     /**
 
-      * This function will add an `event listener` that gets triggered only once and is
 
-      * removed from all events. This is like adding an array of `event listener`s
 
-      * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
 
-      * first time it is triggered.
 
-      *
 
-      * @param {string|string[]} type
 
-      *        An event name or an array of event names.
 
-      *
 
-      * @param {Function} fn
 
-      *        The function to be called once for each event name.
 
-      */
 
-     any(type, fn) {}
 
-     /**
 
-      * This function causes an event to happen. This will then cause any `event listeners`
 
-      * that are waiting for that event, to get called. If there are no `event listeners`
 
-      * for an event then nothing will happen.
 
-      *
 
-      * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
 
-      * Trigger will also call the `on` + `uppercaseEventName` function.
 
-      *
 
-      * Example:
 
-      * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
 
-      * `onClick` if it exists.
 
-      *
 
-      * @param {string|Event|Object} event
 
-      *        The name of the event, an `Event`, or an object with a key of type set to
 
-      *        an event name.
 
-      */
 
-     trigger(event) {}
 
-     /**
 
-      * Dispose of the `Component` and all child components.
 
-      *
 
-      * @fires Component#dispose
 
-      *
 
-      * @param {Object} options
 
-      * @param {Element} options.originalEl element with which to replace player element
 
-      */
 
-     dispose(options = {}) {
 
-       // Bail out if the component has already been disposed.
 
-       if (this.isDisposed_) {
 
-         return;
 
-       }
 
-       if (this.readyQueue_) {
 
-         this.readyQueue_.length = 0;
 
-       }
 
-       /**
 
-        * Triggered when a `Component` is disposed.
 
-        *
 
-        * @event Component#dispose
 
-        * @type {Event}
 
-        *
 
-        * @property {boolean} [bubbles=false]
 
-        *           set to false so that the dispose event does not
 
-        *           bubble up
 
-        */
 
-       this.trigger({
 
-         type: 'dispose',
 
-         bubbles: false
 
-       });
 
-       this.isDisposed_ = true;
 
-       // Dispose all children.
 
-       if (this.children_) {
 
-         for (let i = this.children_.length - 1; i >= 0; i--) {
 
-           if (this.children_[i].dispose) {
 
-             this.children_[i].dispose();
 
-           }
 
-         }
 
-       }
 
-       // Delete child references
 
-       this.children_ = null;
 
-       this.childIndex_ = null;
 
-       this.childNameIndex_ = null;
 
-       this.parentComponent_ = null;
 
-       if (this.el_) {
 
-         // Remove element from DOM
 
-         if (this.el_.parentNode) {
 
-           if (options.restoreEl) {
 
-             this.el_.parentNode.replaceChild(options.restoreEl, this.el_);
 
-           } else {
 
-             this.el_.parentNode.removeChild(this.el_);
 
-           }
 
-         }
 
-         this.el_ = null;
 
-       }
 
-       // remove reference to the player after disposing of the element
 
-       this.player_ = null;
 
-     }
 
-     /**
 
-      * Determine whether or not this component has been disposed.
 
-      *
 
-      * @return {boolean}
 
-      *         If the component has been disposed, will be `true`. Otherwise, `false`.
 
-      */
 
-     isDisposed() {
 
-       return Boolean(this.isDisposed_);
 
-     }
 
-     /**
 
-      * Return the {@link Player} that the `Component` has attached to.
 
-      *
 
-      * @return { import('./player').default }
 
-      *         The player that this `Component` has attached to.
 
-      */
 
-     player() {
 
-       return this.player_;
 
-     }
 
-     /**
 
-      * Deep merge of options objects with new options.
 
-      * > Note: When both `obj` and `options` contain properties whose values are objects.
 
-      *         The two properties get merged using {@link module:obj.merge}
 
-      *
 
-      * @param {Object} obj
 
-      *        The object that contains new options.
 
-      *
 
-      * @return {Object}
 
-      *         A new object of `this.options_` and `obj` merged together.
 
-      */
 
-     options(obj) {
 
-       if (!obj) {
 
-         return this.options_;
 
-       }
 
-       this.options_ = merge$2(this.options_, obj);
 
-       return this.options_;
 
-     }
 
-     /**
 
-      * Get the `Component`s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The DOM element for this `Component`.
 
-      */
 
-     el() {
 
-       return this.el_;
 
-     }
 
-     /**
 
-      * Create the `Component`s DOM element.
 
-      *
 
-      * @param {string} [tagName]
 
-      *        Element's DOM node type. e.g. 'div'
 
-      *
 
-      * @param {Object} [properties]
 
-      *        An object of properties that should be set.
 
-      *
 
-      * @param {Object} [attributes]
 
-      *        An object of attributes that should be set.
 
-      *
 
-      * @return {Element}
 
-      *         The element that gets created.
 
-      */
 
-     createEl(tagName, properties, attributes) {
 
-       return createEl(tagName, properties, attributes);
 
-     }
 
-     /**
 
-      * Localize a string given the string in english.
 
-      *
 
-      * If tokens are provided, it'll try and run a simple token replacement on the provided string.
 
-      * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
 
-      *
 
-      * If a `defaultValue` is provided, it'll use that over `string`,
 
-      * if a value isn't found in provided language files.
 
-      * This is useful if you want to have a descriptive key for token replacement
 
-      * but have a succinct localized string and not require `en.json` to be included.
 
-      *
 
-      * Currently, it is used for the progress bar timing.
 
-      * ```js
 
-      * {
 
-      *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
 
-      * }
 
-      * ```
 
-      * It is then used like so:
 
-      * ```js
 
-      * this.localize('progress bar timing: currentTime={1} duration{2}',
 
-      *               [this.player_.currentTime(), this.player_.duration()],
 
-      *               '{1} of {2}');
 
-      * ```
 
-      *
 
-      * Which outputs something like: `01:23 of 24:56`.
 
-      *
 
-      *
 
-      * @param {string} string
 
-      *        The string to localize and the key to lookup in the language files.
 
-      * @param {string[]} [tokens]
 
-      *        If the current item has token replacements, provide the tokens here.
 
-      * @param {string} [defaultValue]
 
-      *        Defaults to `string`. Can be a default value to use for token replacement
 
-      *        if the lookup key is needed to be separate.
 
-      *
 
-      * @return {string}
 
-      *         The localized string or if no localization exists the english string.
 
-      */
 
-     localize(string, tokens, defaultValue = string) {
 
-       const code = this.player_.language && this.player_.language();
 
-       const languages = this.player_.languages && this.player_.languages();
 
-       const language = languages && languages[code];
 
-       const primaryCode = code && code.split('-')[0];
 
-       const primaryLang = languages && languages[primaryCode];
 
-       let localizedString = defaultValue;
 
-       if (language && language[string]) {
 
-         localizedString = language[string];
 
-       } else if (primaryLang && primaryLang[string]) {
 
-         localizedString = primaryLang[string];
 
-       }
 
-       if (tokens) {
 
-         localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
 
-           const value = tokens[index - 1];
 
-           let ret = value;
 
-           if (typeof value === 'undefined') {
 
-             ret = match;
 
-           }
 
-           return ret;
 
-         });
 
-       }
 
-       return localizedString;
 
-     }
 
-     /**
 
-      * Handles language change for the player in components. Should be overridden by sub-components.
 
-      *
 
-      * @abstract
 
-      */
 
-     handleLanguagechange() {}
 
-     /**
 
-      * Return the `Component`s DOM element. This is where children get inserted.
 
-      * This will usually be the the same as the element returned in {@link Component#el}.
 
-      *
 
-      * @return {Element}
 
-      *         The content element for this `Component`.
 
-      */
 
-     contentEl() {
 
-       return this.contentEl_ || this.el_;
 
-     }
 
-     /**
 
-      * Get this `Component`s ID
 
-      *
 
-      * @return {string}
 
-      *         The id of this `Component`
 
-      */
 
-     id() {
 
-       return this.id_;
 
-     }
 
-     /**
 
-      * Get the `Component`s name. The name gets used to reference the `Component`
 
-      * and is set during registration.
 
-      *
 
-      * @return {string}
 
-      *         The name of this `Component`.
 
-      */
 
-     name() {
 
-       return this.name_;
 
-     }
 
-     /**
 
-      * Get an array of all child components
 
-      *
 
-      * @return {Array}
 
-      *         The children
 
-      */
 
-     children() {
 
-       return this.children_;
 
-     }
 
-     /**
 
-      * Returns the child `Component` with the given `id`.
 
-      *
 
-      * @param {string} id
 
-      *        The id of the child `Component` to get.
 
-      *
 
-      * @return {Component|undefined}
 
-      *         The child `Component` with the given `id` or undefined.
 
-      */
 
-     getChildById(id) {
 
-       return this.childIndex_[id];
 
-     }
 
-     /**
 
-      * Returns the child `Component` with the given `name`.
 
-      *
 
-      * @param {string} name
 
-      *        The name of the child `Component` to get.
 
-      *
 
-      * @return {Component|undefined}
 
-      *         The child `Component` with the given `name` or undefined.
 
-      */
 
-     getChild(name) {
 
-       if (!name) {
 
-         return;
 
-       }
 
-       return this.childNameIndex_[name];
 
-     }
 
-     /**
 
-      * Returns the descendant `Component` following the givent
 
-      * descendant `names`. For instance ['foo', 'bar', 'baz'] would
 
-      * try to get 'foo' on the current component, 'bar' on the 'foo'
 
-      * component and 'baz' on the 'bar' component and return undefined
 
-      * if any of those don't exist.
 
-      *
 
-      * @param {...string[]|...string} names
 
-      *        The name of the child `Component` to get.
 
-      *
 
-      * @return {Component|undefined}
 
-      *         The descendant `Component` following the given descendant
 
-      *         `names` or undefined.
 
-      */
 
-     getDescendant(...names) {
 
-       // flatten array argument into the main array
 
-       names = names.reduce((acc, n) => acc.concat(n), []);
 
-       let currentChild = this;
 
-       for (let i = 0; i < names.length; i++) {
 
-         currentChild = currentChild.getChild(names[i]);
 
-         if (!currentChild || !currentChild.getChild) {
 
-           return;
 
-         }
 
-       }
 
-       return currentChild;
 
-     }
 
-     /**
 
-      * Add a child `Component` inside the current `Component`.
 
-      *
 
-      *
 
-      * @param {string|Component} child
 
-      *        The name or instance of a child to add.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        The key/value store of options that will get passed to children of
 
-      *        the child.
 
-      *
 
-      * @param {number} [index=this.children_.length]
 
-      *        The index to attempt to add a child into.
 
-      *
 
-      * @return {Component}
 
-      *         The `Component` that gets added as a child. When using a string the
 
-      *         `Component` will get created by this process.
 
-      */
 
-     addChild(child, options = {}, index = this.children_.length) {
 
-       let component;
 
-       let componentName;
 
-       // If child is a string, create component with options
 
-       if (typeof child === 'string') {
 
-         componentName = toTitleCase$1(child);
 
-         const componentClassName = options.componentClass || componentName;
 
-         // Set name through options
 
-         options.name = componentName;
 
-         // Create a new object & element for this controls set
 
-         // If there's no .player_, this is a player
 
-         const ComponentClass = Component$1.getComponent(componentClassName);
 
-         if (!ComponentClass) {
 
-           throw new Error(`Component ${componentClassName} does not exist`);
 
-         }
 
-         // data stored directly on the videojs object may be
 
-         // misidentified as a component to retain
 
-         // backwards-compatibility with 4.x. check to make sure the
 
-         // component class can be instantiated.
 
-         if (typeof ComponentClass !== 'function') {
 
-           return null;
 
-         }
 
-         component = new ComponentClass(this.player_ || this, options);
 
-         // child is a component instance
 
-       } else {
 
-         component = child;
 
-       }
 
-       if (component.parentComponent_) {
 
-         component.parentComponent_.removeChild(component);
 
-       }
 
-       this.children_.splice(index, 0, component);
 
-       component.parentComponent_ = this;
 
-       if (typeof component.id === 'function') {
 
-         this.childIndex_[component.id()] = component;
 
-       }
 
-       // If a name wasn't used to create the component, check if we can use the
 
-       // name function of the component
 
-       componentName = componentName || component.name && toTitleCase$1(component.name());
 
-       if (componentName) {
 
-         this.childNameIndex_[componentName] = component;
 
-         this.childNameIndex_[toLowerCase(componentName)] = component;
 
-       }
 
-       // Add the UI object's element to the container div (box)
 
-       // Having an element is not required
 
-       if (typeof component.el === 'function' && component.el()) {
 
-         // If inserting before a component, insert before that component's element
 
-         let refNode = null;
 
-         if (this.children_[index + 1]) {
 
-           // Most children are components, but the video tech is an HTML element
 
-           if (this.children_[index + 1].el_) {
 
-             refNode = this.children_[index + 1].el_;
 
-           } else if (isEl(this.children_[index + 1])) {
 
-             refNode = this.children_[index + 1];
 
-           }
 
-         }
 
-         this.contentEl().insertBefore(component.el(), refNode);
 
-       }
 
-       // Return so it can stored on parent object if desired.
 
-       return component;
 
-     }
 
-     /**
 
-      * Remove a child `Component` from this `Component`s list of children. Also removes
 
-      * the child `Component`s element from this `Component`s element.
 
-      *
 
-      * @param {Component} component
 
-      *        The child `Component` to remove.
 
-      */
 
-     removeChild(component) {
 
-       if (typeof component === 'string') {
 
-         component = this.getChild(component);
 
-       }
 
-       if (!component || !this.children_) {
 
-         return;
 
-       }
 
-       let childFound = false;
 
-       for (let i = this.children_.length - 1; i >= 0; i--) {
 
-         if (this.children_[i] === component) {
 
-           childFound = true;
 
-           this.children_.splice(i, 1);
 
-           break;
 
-         }
 
-       }
 
-       if (!childFound) {
 
-         return;
 
-       }
 
-       component.parentComponent_ = null;
 
-       this.childIndex_[component.id()] = null;
 
-       this.childNameIndex_[toTitleCase$1(component.name())] = null;
 
-       this.childNameIndex_[toLowerCase(component.name())] = null;
 
-       const compEl = component.el();
 
-       if (compEl && compEl.parentNode === this.contentEl()) {
 
-         this.contentEl().removeChild(component.el());
 
-       }
 
-     }
 
-     /**
 
-      * Add and initialize default child `Component`s based upon options.
 
-      */
 
-     initChildren() {
 
-       const children = this.options_.children;
 
-       if (children) {
 
-         // `this` is `parent`
 
-         const parentOptions = this.options_;
 
-         const handleAdd = child => {
 
-           const name = child.name;
 
-           let opts = child.opts;
 
-           // Allow options for children to be set at the parent options
 
-           // e.g. videojs(id, { controlBar: false });
 
-           // instead of videojs(id, { children: { controlBar: false });
 
-           if (parentOptions[name] !== undefined) {
 
-             opts = parentOptions[name];
 
-           }
 
-           // Allow for disabling default components
 
-           // e.g. options['children']['posterImage'] = false
 
-           if (opts === false) {
 
-             return;
 
-           }
 
-           // Allow options to be passed as a simple boolean if no configuration
 
-           // is necessary.
 
-           if (opts === true) {
 
-             opts = {};
 
-           }
 
-           // We also want to pass the original player options
 
-           // to each component as well so they don't need to
 
-           // reach back into the player for options later.
 
-           opts.playerOptions = this.options_.playerOptions;
 
-           // Create and add the child component.
 
-           // Add a direct reference to the child by name on the parent instance.
 
-           // If two of the same component are used, different names should be supplied
 
-           // for each
 
-           const newChild = this.addChild(name, opts);
 
-           if (newChild) {
 
-             this[name] = newChild;
 
-           }
 
-         };
 
-         // Allow for an array of children details to passed in the options
 
-         let workingChildren;
 
-         const Tech = Component$1.getComponent('Tech');
 
-         if (Array.isArray(children)) {
 
-           workingChildren = children;
 
-         } else {
 
-           workingChildren = Object.keys(children);
 
-         }
 
-         workingChildren
 
-         // children that are in this.options_ but also in workingChildren  would
 
-         // give us extra children we do not want. So, we want to filter them out.
 
-         .concat(Object.keys(this.options_).filter(function (child) {
 
-           return !workingChildren.some(function (wchild) {
 
-             if (typeof wchild === 'string') {
 
-               return child === wchild;
 
-             }
 
-             return child === wchild.name;
 
-           });
 
-         })).map(child => {
 
-           let name;
 
-           let opts;
 
-           if (typeof child === 'string') {
 
-             name = child;
 
-             opts = children[name] || this.options_[name] || {};
 
-           } else {
 
-             name = child.name;
 
-             opts = child;
 
-           }
 
-           return {
 
-             name,
 
-             opts
 
-           };
 
-         }).filter(child => {
 
-           // we have to make sure that child.name isn't in the techOrder since
 
-           // techs are registered as Components but can't aren't compatible
 
-           // See https://github.com/videojs/video.js/issues/2772
 
-           const c = Component$1.getComponent(child.opts.componentClass || toTitleCase$1(child.name));
 
-           return c && !Tech.isTech(c);
 
-         }).forEach(handleAdd);
 
-       }
 
-     }
 
-     /**
 
-      * Builds the default DOM class name. Should be overridden by sub-components.
 
-      *
 
-      * @return {string}
 
-      *         The DOM class name for this object.
 
-      *
 
-      * @abstract
 
-      */
 
-     buildCSSClass() {
 
-       // Child classes can include a function that does:
 
-       // return 'CLASS NAME' + this._super();
 
-       return '';
 
-     }
 
-     /**
 
-      * Bind a listener to the component's ready state.
 
-      * Different from event listeners in that if the ready event has already happened
 
-      * it will trigger the function immediately.
 
-      *
 
-      * @param {ReadyCallback} fn
 
-      *        Function that gets called when the `Component` is ready.
 
-      *
 
-      * @return {Component}
 
-      *         Returns itself; method can be chained.
 
-      */
 
-     ready(fn, sync = false) {
 
-       if (!fn) {
 
-         return;
 
-       }
 
-       if (!this.isReady_) {
 
-         this.readyQueue_ = this.readyQueue_ || [];
 
-         this.readyQueue_.push(fn);
 
-         return;
 
-       }
 
-       if (sync) {
 
-         fn.call(this);
 
-       } else {
 
-         // Call the function asynchronously by default for consistency
 
-         this.setTimeout(fn, 1);
 
-       }
 
-     }
 
-     /**
 
-      * Trigger all the ready listeners for this `Component`.
 
-      *
 
-      * @fires Component#ready
 
-      */
 
-     triggerReady() {
 
-       this.isReady_ = true;
 
-       // Ensure ready is triggered asynchronously
 
-       this.setTimeout(function () {
 
-         const readyQueue = this.readyQueue_;
 
-         // Reset Ready Queue
 
-         this.readyQueue_ = [];
 
-         if (readyQueue && readyQueue.length > 0) {
 
-           readyQueue.forEach(function (fn) {
 
-             fn.call(this);
 
-           }, this);
 
-         }
 
-         // Allow for using event listeners also
 
-         /**
 
-          * Triggered when a `Component` is ready.
 
-          *
 
-          * @event Component#ready
 
-          * @type {Event}
 
-          */
 
-         this.trigger('ready');
 
-       }, 1);
 
-     }
 
-     /**
 
-      * Find a single DOM element matching a `selector`. This can be within the `Component`s
 
-      * `contentEl()` or another custom context.
 
-      *
 
-      * @param {string} selector
 
-      *        A valid CSS selector, which will be passed to `querySelector`.
 
-      *
 
-      * @param {Element|string} [context=this.contentEl()]
 
-      *        A DOM element within which to query. Can also be a selector string in
 
-      *        which case the first matching element will get used as context. If
 
-      *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
 
-      *        nothing it falls back to `document`.
 
-      *
 
-      * @return {Element|null}
 
-      *         the dom element that was found, or null
 
-      *
 
-      * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
 
-      */
 
-     $(selector, context) {
 
-       return $(selector, context || this.contentEl());
 
-     }
 
-     /**
 
-      * Finds all DOM element matching a `selector`. This can be within the `Component`s
 
-      * `contentEl()` or another custom context.
 
-      *
 
-      * @param {string} selector
 
-      *        A valid CSS selector, which will be passed to `querySelectorAll`.
 
-      *
 
-      * @param {Element|string} [context=this.contentEl()]
 
-      *        A DOM element within which to query. Can also be a selector string in
 
-      *        which case the first matching element will get used as context. If
 
-      *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
 
-      *        nothing it falls back to `document`.
 
-      *
 
-      * @return {NodeList}
 
-      *         a list of dom elements that were found
 
-      *
 
-      * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
 
-      */
 
-     $$(selector, context) {
 
-       return $$(selector, context || this.contentEl());
 
-     }
 
-     /**
 
-      * Check if a component's element has a CSS class name.
 
-      *
 
-      * @param {string} classToCheck
 
-      *        CSS class name to check.
 
-      *
 
-      * @return {boolean}
 
-      *         - True if the `Component` has the class.
 
-      *         - False if the `Component` does not have the class`
 
-      */
 
-     hasClass(classToCheck) {
 
-       return hasClass(this.el_, classToCheck);
 
-     }
 
-     /**
 
-      * Add a CSS class name to the `Component`s element.
 
-      *
 
-      * @param {...string} classesToAdd
 
-      *        One or more CSS class name to add.
 
-      */
 
-     addClass(...classesToAdd) {
 
-       addClass(this.el_, ...classesToAdd);
 
-     }
 
-     /**
 
-      * Remove a CSS class name from the `Component`s element.
 
-      *
 
-      * @param {...string} classesToRemove
 
-      *        One or more CSS class name to remove.
 
-      */
 
-     removeClass(...classesToRemove) {
 
-       removeClass(this.el_, ...classesToRemove);
 
-     }
 
-     /**
 
-      * Add or remove a CSS class name from the component's element.
 
-      * - `classToToggle` gets added when {@link Component#hasClass} would return false.
 
-      * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
 
-      *
 
-      * @param  {string} classToToggle
 
-      *         The class to add or remove based on (@link Component#hasClass}
 
-      *
 
-      * @param  {boolean|Dom~predicate} [predicate]
 
-      *         An {@link Dom~predicate} function or a boolean
 
-      */
 
-     toggleClass(classToToggle, predicate) {
 
-       toggleClass(this.el_, classToToggle, predicate);
 
-     }
 
-     /**
 
-      * Show the `Component`s element if it is hidden by removing the
 
-      * 'vjs-hidden' class name from it.
 
-      */
 
-     show() {
 
-       this.removeClass('vjs-hidden');
 
-     }
 
-     /**
 
-      * Hide the `Component`s element if it is currently showing by adding the
 
-      * 'vjs-hidden` class name to it.
 
-      */
 
-     hide() {
 
-       this.addClass('vjs-hidden');
 
-     }
 
-     /**
 
-      * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
 
-      * class name to it. Used during fadeIn/fadeOut.
 
-      *
 
-      * @private
 
-      */
 
-     lockShowing() {
 
-       this.addClass('vjs-lock-showing');
 
-     }
 
-     /**
 
-      * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
 
-      * class name from it. Used during fadeIn/fadeOut.
 
-      *
 
-      * @private
 
-      */
 
-     unlockShowing() {
 
-       this.removeClass('vjs-lock-showing');
 
-     }
 
-     /**
 
-      * Get the value of an attribute on the `Component`s element.
 
-      *
 
-      * @param {string} attribute
 
-      *        Name of the attribute to get the value from.
 
-      *
 
-      * @return {string|null}
 
-      *         - The value of the attribute that was asked for.
 
-      *         - Can be an empty string on some browsers if the attribute does not exist
 
-      *           or has no value
 
-      *         - Most browsers will return null if the attribute does not exist or has
 
-      *           no value.
 
-      *
 
-      * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
 
-      */
 
-     getAttribute(attribute) {
 
-       return getAttribute(this.el_, attribute);
 
-     }
 
-     /**
 
-      * Set the value of an attribute on the `Component`'s element
 
-      *
 
-      * @param {string} attribute
 
-      *        Name of the attribute to set.
 
-      *
 
-      * @param {string} value
 
-      *        Value to set the attribute to.
 
-      *
 
-      * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
 
-      */
 
-     setAttribute(attribute, value) {
 
-       setAttribute(this.el_, attribute, value);
 
-     }
 
-     /**
 
-      * Remove an attribute from the `Component`s element.
 
-      *
 
-      * @param {string} attribute
 
-      *        Name of the attribute to remove.
 
-      *
 
-      * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
 
-      */
 
-     removeAttribute(attribute) {
 
-       removeAttribute(this.el_, attribute);
 
-     }
 
-     /**
 
-      * Get or set the width of the component based upon the CSS styles.
 
-      * See {@link Component#dimension} for more detailed information.
 
-      *
 
-      * @param {number|string} [num]
 
-      *        The width that you want to set postfixed with '%', 'px' or nothing.
 
-      *
 
-      * @param {boolean} [skipListeners]
 
-      *        Skip the componentresize event trigger
 
-      *
 
-      * @return {number|string}
 
-      *         The width when getting, zero if there is no width. Can be a string
 
-      *           postpixed with '%' or 'px'.
 
-      */
 
-     width(num, skipListeners) {
 
-       return this.dimension('width', num, skipListeners);
 
-     }
 
-     /**
 
-      * Get or set the height of the component based upon the CSS styles.
 
-      * See {@link Component#dimension} for more detailed information.
 
-      *
 
-      * @param {number|string} [num]
 
-      *        The height that you want to set postfixed with '%', 'px' or nothing.
 
-      *
 
-      * @param {boolean} [skipListeners]
 
-      *        Skip the componentresize event trigger
 
-      *
 
-      * @return {number|string}
 
-      *         The width when getting, zero if there is no width. Can be a string
 
-      *         postpixed with '%' or 'px'.
 
-      */
 
-     height(num, skipListeners) {
 
-       return this.dimension('height', num, skipListeners);
 
-     }
 
-     /**
 
-      * Set both the width and height of the `Component` element at the same time.
 
-      *
 
-      * @param  {number|string} width
 
-      *         Width to set the `Component`s element to.
 
-      *
 
-      * @param  {number|string} height
 
-      *         Height to set the `Component`s element to.
 
-      */
 
-     dimensions(width, height) {
 
-       // Skip componentresize listeners on width for optimization
 
-       this.width(width, true);
 
-       this.height(height);
 
-     }
 
-     /**
 
-      * Get or set width or height of the `Component` element. This is the shared code
 
-      * for the {@link Component#width} and {@link Component#height}.
 
-      *
 
-      * Things to know:
 
-      * - If the width or height in an number this will return the number postfixed with 'px'.
 
-      * - If the width/height is a percent this will return the percent postfixed with '%'
 
-      * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
 
-      *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
 
-      *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
 
-      *   for more information
 
-      * - If you want the computed style of the component, use {@link Component#currentWidth}
 
-      *   and {@link {Component#currentHeight}
 
-      *
 
-      * @fires Component#componentresize
 
-      *
 
-      * @param {string} widthOrHeight
 
-      8        'width' or 'height'
 
-      *
 
-      * @param  {number|string} [num]
 
-      8         New dimension
 
-      *
 
-      * @param  {boolean} [skipListeners]
 
-      *         Skip componentresize event trigger
 
-      *
 
-      * @return {number}
 
-      *         The dimension when getting or 0 if unset
 
-      */
 
-     dimension(widthOrHeight, num, skipListeners) {
 
-       if (num !== undefined) {
 
-         // Set to zero if null or literally NaN (NaN !== NaN)
 
-         if (num === null || num !== num) {
 
-           num = 0;
 
-         }
 
-         // Check if using css width/height (% or px) and adjust
 
-         if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
 
-           this.el_.style[widthOrHeight] = num;
 
-         } else if (num === 'auto') {
 
-           this.el_.style[widthOrHeight] = '';
 
-         } else {
 
-           this.el_.style[widthOrHeight] = num + 'px';
 
-         }
 
-         // skipListeners allows us to avoid triggering the resize event when setting both width and height
 
-         if (!skipListeners) {
 
-           /**
 
-            * Triggered when a component is resized.
 
-            *
 
-            * @event Component#componentresize
 
-            * @type {Event}
 
-            */
 
-           this.trigger('componentresize');
 
-         }
 
-         return;
 
-       }
 
-       // Not setting a value, so getting it
 
-       // Make sure element exists
 
-       if (!this.el_) {
 
-         return 0;
 
-       }
 
-       // Get dimension value from style
 
-       const val = this.el_.style[widthOrHeight];
 
-       const pxIndex = val.indexOf('px');
 
-       if (pxIndex !== -1) {
 
-         // Return the pixel value with no 'px'
 
-         return parseInt(val.slice(0, pxIndex), 10);
 
-       }
 
-       // No px so using % or no style was set, so falling back to offsetWidth/height
 
-       // If component has display:none, offset will return 0
 
-       // TODO: handle display:none and no dimension style using px
 
-       return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10);
 
-     }
 
-     /**
 
-      * Get the computed width or the height of the component's element.
 
-      *
 
-      * Uses `window.getComputedStyle`.
 
-      *
 
-      * @param {string} widthOrHeight
 
-      *        A string containing 'width' or 'height'. Whichever one you want to get.
 
-      *
 
-      * @return {number}
 
-      *         The dimension that gets asked for or 0 if nothing was set
 
-      *         for that dimension.
 
-      */
 
-     currentDimension(widthOrHeight) {
 
-       let computedWidthOrHeight = 0;
 
-       if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
 
-         throw new Error('currentDimension only accepts width or height value');
 
-       }
 
-       computedWidthOrHeight = computedStyle(this.el_, widthOrHeight);
 
-       // remove 'px' from variable and parse as integer
 
-       computedWidthOrHeight = parseFloat(computedWidthOrHeight);
 
-       // if the computed value is still 0, it's possible that the browser is lying
 
-       // and we want to check the offset values.
 
-       // This code also runs wherever getComputedStyle doesn't exist.
 
-       if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
 
-         const rule = `offset${toTitleCase$1(widthOrHeight)}`;
 
-         computedWidthOrHeight = this.el_[rule];
 
-       }
 
-       return computedWidthOrHeight;
 
-     }
 
-     /**
 
-      * An object that contains width and height values of the `Component`s
 
-      * computed style. Uses `window.getComputedStyle`.
 
-      *
 
-      * @typedef {Object} Component~DimensionObject
 
-      *
 
-      * @property {number} width
 
-      *           The width of the `Component`s computed style.
 
-      *
 
-      * @property {number} height
 
-      *           The height of the `Component`s computed style.
 
-      */
 
-     /**
 
-      * Get an object that contains computed width and height values of the
 
-      * component's element.
 
-      *
 
-      * Uses `window.getComputedStyle`.
 
-      *
 
-      * @return {Component~DimensionObject}
 
-      *         The computed dimensions of the component's element.
 
-      */
 
-     currentDimensions() {
 
-       return {
 
-         width: this.currentDimension('width'),
 
-         height: this.currentDimension('height')
 
-       };
 
-     }
 
-     /**
 
-      * Get the computed width of the component's element.
 
-      *
 
-      * Uses `window.getComputedStyle`.
 
-      *
 
-      * @return {number}
 
-      *         The computed width of the component's element.
 
-      */
 
-     currentWidth() {
 
-       return this.currentDimension('width');
 
-     }
 
-     /**
 
-      * Get the computed height of the component's element.
 
-      *
 
-      * Uses `window.getComputedStyle`.
 
-      *
 
-      * @return {number}
 
-      *         The computed height of the component's element.
 
-      */
 
-     currentHeight() {
 
-       return this.currentDimension('height');
 
-     }
 
-     /**
 
-      * Set the focus to this component
 
-      */
 
-     focus() {
 
-       this.el_.focus();
 
-     }
 
-     /**
 
-      * Remove the focus from this component
 
-      */
 
-     blur() {
 
-       this.el_.blur();
 
-     }
 
-     /**
 
-      * When this Component receives a `keydown` event which it does not process,
 
-      *  it passes the event to the Player for handling.
 
-      *
 
-      * @param {KeyboardEvent} event
 
-      *        The `keydown` event that caused this function to be called.
 
-      */
 
-     handleKeyDown(event) {
 
-       if (this.player_) {
 
-         // We only stop propagation here because we want unhandled events to fall
 
-         // back to the browser. Exclude Tab for focus trapping.
 
-         if (!keycode.isEventKey(event, 'Tab')) {
 
-           event.stopPropagation();
 
-         }
 
-         this.player_.handleKeyDown(event);
 
-       }
 
-     }
 
-     /**
 
-      * Many components used to have a `handleKeyPress` method, which was poorly
 
-      * named because it listened to a `keydown` event. This method name now
 
-      * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
 
-      * will not see their method calls stop working.
 
-      *
 
-      * @param {Event} event
 
-      *        The event that caused this function to be called.
 
-      */
 
-     handleKeyPress(event) {
 
-       this.handleKeyDown(event);
 
-     }
 
-     /**
 
-      * Emit a 'tap' events when touch event support gets detected. This gets used to
 
-      * support toggling the controls through a tap on the video. They get enabled
 
-      * because every sub-component would have extra overhead otherwise.
 
-      *
 
-      * @private
 
-      * @fires Component#tap
 
-      * @listens Component#touchstart
 
-      * @listens Component#touchmove
 
-      * @listens Component#touchleave
 
-      * @listens Component#touchcancel
 
-      * @listens Component#touchend
 
-       */
 
-     emitTapEvents() {
 
-       // Track the start time so we can determine how long the touch lasted
 
-       let touchStart = 0;
 
-       let firstTouch = null;
 
-       // Maximum movement allowed during a touch event to still be considered a tap
 
-       // Other popular libs use anywhere from 2 (hammer.js) to 15,
 
-       // so 10 seems like a nice, round number.
 
-       const tapMovementThreshold = 10;
 
-       // The maximum length a touch can be while still being considered a tap
 
-       const touchTimeThreshold = 200;
 
-       let couldBeTap;
 
-       this.on('touchstart', function (event) {
 
-         // If more than one finger, don't consider treating this as a click
 
-         if (event.touches.length === 1) {
 
-           // Copy pageX/pageY from the object
 
-           firstTouch = {
 
-             pageX: event.touches[0].pageX,
 
-             pageY: event.touches[0].pageY
 
-           };
 
-           // Record start time so we can detect a tap vs. "touch and hold"
 
-           touchStart = window.performance.now();
 
-           // Reset couldBeTap tracking
 
-           couldBeTap = true;
 
-         }
 
-       });
 
-       this.on('touchmove', function (event) {
 
-         // If more than one finger, don't consider treating this as a click
 
-         if (event.touches.length > 1) {
 
-           couldBeTap = false;
 
-         } else if (firstTouch) {
 
-           // Some devices will throw touchmoves for all but the slightest of taps.
 
-           // So, if we moved only a small distance, this could still be a tap
 
-           const xdiff = event.touches[0].pageX - firstTouch.pageX;
 
-           const ydiff = event.touches[0].pageY - firstTouch.pageY;
 
-           const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
 
-           if (touchDistance > tapMovementThreshold) {
 
-             couldBeTap = false;
 
-           }
 
-         }
 
-       });
 
-       const noTap = function () {
 
-         couldBeTap = false;
 
-       };
 
-       // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
 
-       this.on('touchleave', noTap);
 
-       this.on('touchcancel', noTap);
 
-       // When the touch ends, measure how long it took and trigger the appropriate
 
-       // event
 
-       this.on('touchend', function (event) {
 
-         firstTouch = null;
 
-         // Proceed only if the touchmove/leave/cancel event didn't happen
 
-         if (couldBeTap === true) {
 
-           // Measure how long the touch lasted
 
-           const touchTime = window.performance.now() - touchStart;
 
-           // Make sure the touch was less than the threshold to be considered a tap
 
-           if (touchTime < touchTimeThreshold) {
 
-             // Don't let browser turn this into a click
 
-             event.preventDefault();
 
-             /**
 
-              * Triggered when a `Component` is tapped.
 
-              *
 
-              * @event Component#tap
 
-              * @type {MouseEvent}
 
-              */
 
-             this.trigger('tap');
 
-             // It may be good to copy the touchend event object and change the
 
-             // type to tap, if the other event properties aren't exact after
 
-             // Events.fixEvent runs (e.g. event.target)
 
-           }
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * This function reports user activity whenever touch events happen. This can get
 
-      * turned off by any sub-components that wants touch events to act another way.
 
-      *
 
-      * Report user touch activity when touch events occur. User activity gets used to
 
-      * determine when controls should show/hide. It is simple when it comes to mouse
 
-      * events, because any mouse event should show the controls. So we capture mouse
 
-      * events that bubble up to the player and report activity when that happens.
 
-      * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
 
-      * controls. So touch events can't help us at the player level either.
 
-      *
 
-      * User activity gets checked asynchronously. So what could happen is a tap event
 
-      * on the video turns the controls off. Then the `touchend` event bubbles up to
 
-      * the player. Which, if it reported user activity, would turn the controls right
 
-      * back on. We also don't want to completely block touch events from bubbling up.
 
-      * Furthermore a `touchmove` event and anything other than a tap, should not turn
 
-      * controls back on.
 
-      *
 
-      * @listens Component#touchstart
 
-      * @listens Component#touchmove
 
-      * @listens Component#touchend
 
-      * @listens Component#touchcancel
 
-      */
 
-     enableTouchActivity() {
 
-       // Don't continue if the root player doesn't support reporting user activity
 
-       if (!this.player() || !this.player().reportUserActivity) {
 
-         return;
 
-       }
 
-       // listener for reporting that the user is active
 
-       const report = bind_(this.player(), this.player().reportUserActivity);
 
-       let touchHolding;
 
-       this.on('touchstart', function () {
 
-         report();
 
-         // For as long as the they are touching the device or have their mouse down,
 
-         // we consider them active even if they're not moving their finger or mouse.
 
-         // So we want to continue to update that they are active
 
-         this.clearInterval(touchHolding);
 
-         // report at the same interval as activityCheck
 
-         touchHolding = this.setInterval(report, 250);
 
-       });
 
-       const touchEnd = function (event) {
 
-         report();
 
-         // stop the interval that maintains activity if the touch is holding
 
-         this.clearInterval(touchHolding);
 
-       };
 
-       this.on('touchmove', report);
 
-       this.on('touchend', touchEnd);
 
-       this.on('touchcancel', touchEnd);
 
-     }
 
-     /**
 
-      * A callback that has no parameters and is bound into `Component`s context.
 
-      *
 
-      * @callback Component~GenericCallback
 
-      * @this Component
 
-      */
 
-     /**
 
-      * Creates a function that runs after an `x` millisecond timeout. This function is a
 
-      * wrapper around `window.setTimeout`. There are a few reasons to use this one
 
-      * instead though:
 
-      * 1. It gets cleared via  {@link Component#clearTimeout} when
 
-      *    {@link Component#dispose} gets called.
 
-      * 2. The function callback will gets turned into a {@link Component~GenericCallback}
 
-      *
 
-      * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
 
-      *         will cause its dispose listener not to get cleaned up! Please use
 
-      *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
 
-      *
 
-      * @param {Component~GenericCallback} fn
 
-      *        The function that will be run after `timeout`.
 
-      *
 
-      * @param {number} timeout
 
-      *        Timeout in milliseconds to delay before executing the specified function.
 
-      *
 
-      * @return {number}
 
-      *         Returns a timeout ID that gets used to identify the timeout. It can also
 
-      *         get used in {@link Component#clearTimeout} to clear the timeout that
 
-      *         was set.
 
-      *
 
-      * @listens Component#dispose
 
-      * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
 
-      */
 
-     setTimeout(fn, timeout) {
 
-       // declare as variables so they are properly available in timeout function
 
-       // eslint-disable-next-line
 
-       var timeoutId;
 
-       fn = bind_(this, fn);
 
-       this.clearTimersOnDispose_();
 
-       timeoutId = window.setTimeout(() => {
 
-         if (this.setTimeoutIds_.has(timeoutId)) {
 
-           this.setTimeoutIds_.delete(timeoutId);
 
-         }
 
-         fn();
 
-       }, timeout);
 
-       this.setTimeoutIds_.add(timeoutId);
 
-       return timeoutId;
 
-     }
 
-     /**
 
-      * Clears a timeout that gets created via `window.setTimeout` or
 
-      * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
 
-      * use this function instead of `window.clearTimout`. If you don't your dispose
 
-      * listener will not get cleaned up until {@link Component#dispose}!
 
-      *
 
-      * @param {number} timeoutId
 
-      *        The id of the timeout to clear. The return value of
 
-      *        {@link Component#setTimeout} or `window.setTimeout`.
 
-      *
 
-      * @return {number}
 
-      *         Returns the timeout id that was cleared.
 
-      *
 
-      * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
 
-      */
 
-     clearTimeout(timeoutId) {
 
-       if (this.setTimeoutIds_.has(timeoutId)) {
 
-         this.setTimeoutIds_.delete(timeoutId);
 
-         window.clearTimeout(timeoutId);
 
-       }
 
-       return timeoutId;
 
-     }
 
-     /**
 
-      * Creates a function that gets run every `x` milliseconds. This function is a wrapper
 
-      * around `window.setInterval`. There are a few reasons to use this one instead though.
 
-      * 1. It gets cleared via  {@link Component#clearInterval} when
 
-      *    {@link Component#dispose} gets called.
 
-      * 2. The function callback will be a {@link Component~GenericCallback}
 
-      *
 
-      * @param {Component~GenericCallback} fn
 
-      *        The function to run every `x` seconds.
 
-      *
 
-      * @param {number} interval
 
-      *        Execute the specified function every `x` milliseconds.
 
-      *
 
-      * @return {number}
 
-      *         Returns an id that can be used to identify the interval. It can also be be used in
 
-      *         {@link Component#clearInterval} to clear the interval.
 
-      *
 
-      * @listens Component#dispose
 
-      * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
 
-      */
 
-     setInterval(fn, interval) {
 
-       fn = bind_(this, fn);
 
-       this.clearTimersOnDispose_();
 
-       const intervalId = window.setInterval(fn, interval);
 
-       this.setIntervalIds_.add(intervalId);
 
-       return intervalId;
 
-     }
 
-     /**
 
-      * Clears an interval that gets created via `window.setInterval` or
 
-      * {@link Component#setInterval}. If you set an interval via {@link Component#setInterval}
 
-      * use this function instead of `window.clearInterval`. If you don't your dispose
 
-      * listener will not get cleaned up until {@link Component#dispose}!
 
-      *
 
-      * @param {number} intervalId
 
-      *        The id of the interval to clear. The return value of
 
-      *        {@link Component#setInterval} or `window.setInterval`.
 
-      *
 
-      * @return {number}
 
-      *         Returns the interval id that was cleared.
 
-      *
 
-      * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
 
-      */
 
-     clearInterval(intervalId) {
 
-       if (this.setIntervalIds_.has(intervalId)) {
 
-         this.setIntervalIds_.delete(intervalId);
 
-         window.clearInterval(intervalId);
 
-       }
 
-       return intervalId;
 
-     }
 
-     /**
 
-      * Queues up a callback to be passed to requestAnimationFrame (rAF), but
 
-      * with a few extra bonuses:
 
-      *
 
-      * - Supports browsers that do not support rAF by falling back to
 
-      *   {@link Component#setTimeout}.
 
-      *
 
-      * - The callback is turned into a {@link Component~GenericCallback} (i.e.
 
-      *   bound to the component).
 
-      *
 
-      * - Automatic cancellation of the rAF callback is handled if the component
 
-      *   is disposed before it is called.
 
-      *
 
-      * @param  {Component~GenericCallback} fn
 
-      *         A function that will be bound to this component and executed just
 
-      *         before the browser's next repaint.
 
-      *
 
-      * @return {number}
 
-      *         Returns an rAF ID that gets used to identify the timeout. It can
 
-      *         also be used in {@link Component#cancelAnimationFrame} to cancel
 
-      *         the animation frame callback.
 
-      *
 
-      * @listens Component#dispose
 
-      * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
 
-      */
 
-     requestAnimationFrame(fn) {
 
-       this.clearTimersOnDispose_();
 
-       // declare as variables so they are properly available in rAF function
 
-       // eslint-disable-next-line
 
-       var id;
 
-       fn = bind_(this, fn);
 
-       id = window.requestAnimationFrame(() => {
 
-         if (this.rafIds_.has(id)) {
 
-           this.rafIds_.delete(id);
 
-         }
 
-         fn();
 
-       });
 
-       this.rafIds_.add(id);
 
-       return id;
 
-     }
 
-     /**
 
-      * Request an animation frame, but only one named animation
 
-      * frame will be queued. Another will never be added until
 
-      * the previous one finishes.
 
-      *
 
-      * @param {string} name
 
-      *        The name to give this requestAnimationFrame
 
-      *
 
-      * @param  {Component~GenericCallback} fn
 
-      *         A function that will be bound to this component and executed just
 
-      *         before the browser's next repaint.
 
-      */
 
-     requestNamedAnimationFrame(name, fn) {
 
-       if (this.namedRafs_.has(name)) {
 
-         return;
 
-       }
 
-       this.clearTimersOnDispose_();
 
-       fn = bind_(this, fn);
 
-       const id = this.requestAnimationFrame(() => {
 
-         fn();
 
-         if (this.namedRafs_.has(name)) {
 
-           this.namedRafs_.delete(name);
 
-         }
 
-       });
 
-       this.namedRafs_.set(name, id);
 
-       return name;
 
-     }
 
-     /**
 
-      * Cancels a current named animation frame if it exists.
 
-      *
 
-      * @param {string} name
 
-      *        The name of the requestAnimationFrame to cancel.
 
-      */
 
-     cancelNamedAnimationFrame(name) {
 
-       if (!this.namedRafs_.has(name)) {
 
-         return;
 
-       }
 
-       this.cancelAnimationFrame(this.namedRafs_.get(name));
 
-       this.namedRafs_.delete(name);
 
-     }
 
-     /**
 
-      * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
 
-      * (rAF).
 
-      *
 
-      * If you queue an rAF callback via {@link Component#requestAnimationFrame},
 
-      * use this function instead of `window.cancelAnimationFrame`. If you don't,
 
-      * your dispose listener will not get cleaned up until {@link Component#dispose}!
 
-      *
 
-      * @param {number} id
 
-      *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
 
-      *
 
-      * @return {number}
 
-      *         Returns the rAF ID that was cleared.
 
-      *
 
-      * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
 
-      */
 
-     cancelAnimationFrame(id) {
 
-       if (this.rafIds_.has(id)) {
 
-         this.rafIds_.delete(id);
 
-         window.cancelAnimationFrame(id);
 
-       }
 
-       return id;
 
-     }
 
-     /**
 
-      * A function to setup `requestAnimationFrame`, `setTimeout`,
 
-      * and `setInterval`, clearing on dispose.
 
-      *
 
-      * > Previously each timer added and removed dispose listeners on it's own.
 
-      * For better performance it was decided to batch them all, and use `Set`s
 
-      * to track outstanding timer ids.
 
-      *
 
-      * @private
 
-      */
 
-     clearTimersOnDispose_() {
 
-       if (this.clearingTimersOnDispose_) {
 
-         return;
 
-       }
 
-       this.clearingTimersOnDispose_ = true;
 
-       this.one('dispose', () => {
 
-         [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEach(([idName, cancelName]) => {
 
-           // for a `Set` key will actually be the value again
 
-           // so forEach((val, val) =>` but for maps we want to use
 
-           // the key.
 
-           this[idName].forEach((val, key) => this[cancelName](key));
 
-         });
 
-         this.clearingTimersOnDispose_ = false;
 
-       });
 
-     }
 
-     /**
 
-      * Register a `Component` with `videojs` given the name and the component.
 
-      *
 
-      * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
 
-      *         should be registered using {@link Tech.registerTech} or
 
-      *         {@link videojs:videojs.registerTech}.
 
-      *
 
-      * > NOTE: This function can also be seen on videojs as
 
-      *         {@link videojs:videojs.registerComponent}.
 
-      *
 
-      * @param {string} name
 
-      *        The name of the `Component` to register.
 
-      *
 
-      * @param {Component} ComponentToRegister
 
-      *        The `Component` class to register.
 
-      *
 
-      * @return {Component}
 
-      *         The `Component` that was registered.
 
-      */
 
-     static registerComponent(name, ComponentToRegister) {
 
-       if (typeof name !== 'string' || !name) {
 
-         throw new Error(`Illegal component name, "${name}"; must be a non-empty string.`);
 
-       }
 
-       const Tech = Component$1.getComponent('Tech');
 
-       // We need to make sure this check is only done if Tech has been registered.
 
-       const isTech = Tech && Tech.isTech(ComponentToRegister);
 
-       const isComp = Component$1 === ComponentToRegister || Component$1.prototype.isPrototypeOf(ComponentToRegister.prototype);
 
-       if (isTech || !isComp) {
 
-         let reason;
 
-         if (isTech) {
 
-           reason = 'techs must be registered using Tech.registerTech()';
 
-         } else {
 
-           reason = 'must be a Component subclass';
 
-         }
 
-         throw new Error(`Illegal component, "${name}"; ${reason}.`);
 
-       }
 
-       name = toTitleCase$1(name);
 
-       if (!Component$1.components_) {
 
-         Component$1.components_ = {};
 
-       }
 
-       const Player = Component$1.getComponent('Player');
 
-       if (name === 'Player' && Player && Player.players) {
 
-         const players = Player.players;
 
-         const playerNames = Object.keys(players);
 
-         // If we have players that were disposed, then their name will still be
 
-         // in Players.players. So, we must loop through and verify that the value
 
-         // for each item is not null. This allows registration of the Player component
 
-         // after all players have been disposed or before any were created.
 
-         if (players && playerNames.length > 0 && playerNames.map(pname => players[pname]).every(Boolean)) {
 
-           throw new Error('Can not register Player component after player has been created.');
 
-         }
 
-       }
 
-       Component$1.components_[name] = ComponentToRegister;
 
-       Component$1.components_[toLowerCase(name)] = ComponentToRegister;
 
-       return ComponentToRegister;
 
-     }
 
-     /**
 
-      * Get a `Component` based on the name it was registered with.
 
-      *
 
-      * @param {string} name
 
-      *        The Name of the component to get.
 
-      *
 
-      * @return {Component}
 
-      *         The `Component` that got registered under the given name.
 
-      */
 
-     static getComponent(name) {
 
-       if (!name || !Component$1.components_) {
 
-         return;
 
-       }
 
-       return Component$1.components_[name];
 
-     }
 
-   }
 
-   Component$1.registerComponent('Component', Component$1);
 
-   /**
 
-    * @file time.js
 
-    * @module time
 
-    */
 
-   /**
 
-    * Returns the time for the specified index at the start or end
 
-    * of a TimeRange object.
 
-    *
 
-    * @typedef    {Function} TimeRangeIndex
 
-    *
 
-    * @param      {number} [index=0]
 
-    *             The range number to return the time for.
 
-    *
 
-    * @return     {number}
 
-    *             The time offset at the specified index.
 
-    *
 
-    * @deprecated The index argument must be provided.
 
-    *             In the future, leaving it out will throw an error.
 
-    */
 
-   /**
 
-    * An object that contains ranges of time, which mimics {@link TimeRanges}.
 
-    *
 
-    * @typedef  {Object} TimeRange
 
-    *
 
-    * @property {number} length
 
-    *           The number of time ranges represented by this object.
 
-    *
 
-    * @property {module:time~TimeRangeIndex} start
 
-    *           Returns the time offset at which a specified time range begins.
 
-    *
 
-    * @property {module:time~TimeRangeIndex} end
 
-    *           Returns the time offset at which a specified time range ends.
 
-    *
 
-    * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
 
-    */
 
-   /**
 
-    * Check if any of the time ranges are over the maximum index.
 
-    *
 
-    * @private
 
-    * @param   {string} fnName
 
-    *          The function name to use for logging
 
-    *
 
-    * @param   {number} index
 
-    *          The index to check
 
-    *
 
-    * @param   {number} maxIndex
 
-    *          The maximum possible index
 
-    *
 
-    * @throws  {Error} if the timeRanges provided are over the maxIndex
 
-    */
 
-   function rangeCheck(fnName, index, maxIndex) {
 
-     if (typeof index !== 'number' || index < 0 || index > maxIndex) {
 
-       throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is non-numeric or out of bounds (0-${maxIndex}).`);
 
-     }
 
-   }
 
-   /**
 
-    * Get the time for the specified index at the start or end
 
-    * of a TimeRange object.
 
-    *
 
-    * @private
 
-    * @param      {string} fnName
 
-    *             The function name to use for logging
 
-    *
 
-    * @param      {string} valueIndex
 
-    *             The property that should be used to get the time. should be
 
-    *             'start' or 'end'
 
-    *
 
-    * @param      {Array} ranges
 
-    *             An array of time ranges
 
-    *
 
-    * @param      {Array} [rangeIndex=0]
 
-    *             The index to start the search at
 
-    *
 
-    * @return     {number}
 
-    *             The time that offset at the specified index.
 
-    *
 
-    * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
 
-    * @throws     {Error} if rangeIndex is more than the length of ranges
 
-    */
 
-   function getRange(fnName, valueIndex, ranges, rangeIndex) {
 
-     rangeCheck(fnName, rangeIndex, ranges.length - 1);
 
-     return ranges[rangeIndex][valueIndex];
 
-   }
 
-   /**
 
-    * Create a time range object given ranges of time.
 
-    *
 
-    * @private
 
-    * @param   {Array} [ranges]
 
-    *          An array of time ranges.
 
-    *
 
-    * @return  {TimeRange}
 
-    */
 
-   function createTimeRangesObj(ranges) {
 
-     let timeRangesObj;
 
-     if (ranges === undefined || ranges.length === 0) {
 
-       timeRangesObj = {
 
-         length: 0,
 
-         start() {
 
-           throw new Error('This TimeRanges object is empty');
 
-         },
 
-         end() {
 
-           throw new Error('This TimeRanges object is empty');
 
-         }
 
-       };
 
-     } else {
 
-       timeRangesObj = {
 
-         length: ranges.length,
 
-         start: getRange.bind(null, 'start', 0, ranges),
 
-         end: getRange.bind(null, 'end', 1, ranges)
 
-       };
 
-     }
 
-     if (window.Symbol && window.Symbol.iterator) {
 
-       timeRangesObj[window.Symbol.iterator] = () => (ranges || []).values();
 
-     }
 
-     return timeRangesObj;
 
-   }
 
-   /**
 
-    * Create a `TimeRange` object which mimics an
 
-    * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
 
-    *
 
-    * @param {number|Array[]} start
 
-    *        The start of a single range (a number) or an array of ranges (an
 
-    *        array of arrays of two numbers each).
 
-    *
 
-    * @param {number} end
 
-    *        The end of a single range. Cannot be used with the array form of
 
-    *        the `start` argument.
 
-    *
 
-    * @return {TimeRange}
 
-    */
 
-   function createTimeRanges$1(start, end) {
 
-     if (Array.isArray(start)) {
 
-       return createTimeRangesObj(start);
 
-     } else if (start === undefined || end === undefined) {
 
-       return createTimeRangesObj();
 
-     }
 
-     return createTimeRangesObj([[start, end]]);
 
-   }
 
-   /**
 
-    * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
 
-    * seconds) will force a number of leading zeros to cover the length of the
 
-    * guide.
 
-    *
 
-    * @private
 
-    * @param  {number} seconds
 
-    *         Number of seconds to be turned into a string
 
-    *
 
-    * @param  {number} guide
 
-    *         Number (in seconds) to model the string after
 
-    *
 
-    * @return {string}
 
-    *         Time formatted as H:MM:SS or M:SS
 
-    */
 
-   const defaultImplementation = function (seconds, guide) {
 
-     seconds = seconds < 0 ? 0 : seconds;
 
-     let s = Math.floor(seconds % 60);
 
-     let m = Math.floor(seconds / 60 % 60);
 
-     let h = Math.floor(seconds / 3600);
 
-     const gm = Math.floor(guide / 60 % 60);
 
-     const gh = Math.floor(guide / 3600);
 
-     // handle invalid times
 
-     if (isNaN(seconds) || seconds === Infinity) {
 
-       // '-' is false for all relational operators (e.g. <, >=) so this setting
 
-       // will add the minimum number of fields specified by the guide
 
-       h = m = s = '-';
 
-     }
 
-     // Check if we need to show hours
 
-     h = h > 0 || gh > 0 ? h + ':' : '';
 
-     // If hours are showing, we may need to add a leading zero.
 
-     // Always show at least one digit of minutes.
 
-     m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
 
-     // Check if leading zero is need for seconds
 
-     s = s < 10 ? '0' + s : s;
 
-     return h + m + s;
 
-   };
 
-   // Internal pointer to the current implementation.
 
-   let implementation = defaultImplementation;
 
-   /**
 
-    * Replaces the default formatTime implementation with a custom implementation.
 
-    *
 
-    * @param {Function} customImplementation
 
-    *        A function which will be used in place of the default formatTime
 
-    *        implementation. Will receive the current time in seconds and the
 
-    *        guide (in seconds) as arguments.
 
-    */
 
-   function setFormatTime(customImplementation) {
 
-     implementation = customImplementation;
 
-   }
 
-   /**
 
-    * Resets formatTime to the default implementation.
 
-    */
 
-   function resetFormatTime() {
 
-     implementation = defaultImplementation;
 
-   }
 
-   /**
 
-    * Delegates to either the default time formatting function or a custom
 
-    * function supplied via `setFormatTime`.
 
-    *
 
-    * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
 
-    * guide (in seconds) will force a number of leading zeros to cover the
 
-    * length of the guide.
 
-    *
 
-    * @example  formatTime(125, 600) === "02:05"
 
-    * @param    {number} seconds
 
-    *           Number of seconds to be turned into a string
 
-    *
 
-    * @param    {number} guide
 
-    *           Number (in seconds) to model the string after
 
-    *
 
-    * @return   {string}
 
-    *           Time formatted as H:MM:SS or M:SS
 
-    */
 
-   function formatTime(seconds, guide = seconds) {
 
-     return implementation(seconds, guide);
 
-   }
 
-   var Time = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     createTimeRanges: createTimeRanges$1,
 
-     createTimeRange: createTimeRanges$1,
 
-     setFormatTime: setFormatTime,
 
-     resetFormatTime: resetFormatTime,
 
-     formatTime: formatTime
 
-   });
 
-   /**
 
-    * @file buffer.js
 
-    * @module buffer
 
-    */
 
-   /**
 
-    * Compute the percentage of the media that has been buffered.
 
-    *
 
-    * @param { import('./time').TimeRange } buffered
 
-    *        The current `TimeRanges` object representing buffered time ranges
 
-    *
 
-    * @param {number} duration
 
-    *        Total duration of the media
 
-    *
 
-    * @return {number}
 
-    *         Percent buffered of the total duration in decimal form.
 
-    */
 
-   function bufferedPercent(buffered, duration) {
 
-     let bufferedDuration = 0;
 
-     let start;
 
-     let end;
 
-     if (!duration) {
 
-       return 0;
 
-     }
 
-     if (!buffered || !buffered.length) {
 
-       buffered = createTimeRanges$1(0, 0);
 
-     }
 
-     for (let i = 0; i < buffered.length; i++) {
 
-       start = buffered.start(i);
 
-       end = buffered.end(i);
 
-       // buffered end can be bigger than duration by a very small fraction
 
-       if (end > duration) {
 
-         end = duration;
 
-       }
 
-       bufferedDuration += end - start;
 
-     }
 
-     return bufferedDuration / duration;
 
-   }
 
-   /**
 
-    * @file media-error.js
 
-    */
 
-   /**
 
-    * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
 
-    *
 
-    * @param {number|string|Object|MediaError} value
 
-    *        This can be of multiple types:
 
-    *        - number: should be a standard error code
 
-    *        - string: an error message (the code will be 0)
 
-    *        - Object: arbitrary properties
 
-    *        - `MediaError` (native): used to populate a video.js `MediaError` object
 
-    *        - `MediaError` (video.js): will return itself if it's already a
 
-    *          video.js `MediaError` object.
 
-    *
 
-    * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
 
-    * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
 
-    *
 
-    * @class MediaError
 
-    */
 
-   function MediaError(value) {
 
-     // Allow redundant calls to this constructor to avoid having `instanceof`
 
-     // checks peppered around the code.
 
-     if (value instanceof MediaError) {
 
-       return value;
 
-     }
 
-     if (typeof value === 'number') {
 
-       this.code = value;
 
-     } else if (typeof value === 'string') {
 
-       // default code is zero, so this is a custom error
 
-       this.message = value;
 
-     } else if (isObject$1(value)) {
 
-       // We assign the `code` property manually because native `MediaError` objects
 
-       // do not expose it as an own/enumerable property of the object.
 
-       if (typeof value.code === 'number') {
 
-         this.code = value.code;
 
-       }
 
-       Object.assign(this, value);
 
-     }
 
-     if (!this.message) {
 
-       this.message = MediaError.defaultMessages[this.code] || '';
 
-     }
 
-   }
 
-   /**
 
-    * The error code that refers two one of the defined `MediaError` types
 
-    *
 
-    * @type {Number}
 
-    */
 
-   MediaError.prototype.code = 0;
 
-   /**
 
-    * An optional message that to show with the error. Message is not part of the HTML5
 
-    * video spec but allows for more informative custom errors.
 
-    *
 
-    * @type {String}
 
-    */
 
-   MediaError.prototype.message = '';
 
-   /**
 
-    * An optional status code that can be set by plugins to allow even more detail about
 
-    * the error. For example a plugin might provide a specific HTTP status code and an
 
-    * error message for that code. Then when the plugin gets that error this class will
 
-    * know how to display an error message for it. This allows a custom message to show
 
-    * up on the `Player` error overlay.
 
-    *
 
-    * @type {Array}
 
-    */
 
-   MediaError.prototype.status = null;
 
-   /**
 
-    * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
 
-    * specification listed under {@link MediaError} for more information.
 
-    *
 
-    * @enum {array}
 
-    * @readonly
 
-    * @property {string} 0 - MEDIA_ERR_CUSTOM
 
-    * @property {string} 1 - MEDIA_ERR_ABORTED
 
-    * @property {string} 2 - MEDIA_ERR_NETWORK
 
-    * @property {string} 3 - MEDIA_ERR_DECODE
 
-    * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
 
-    * @property {string} 5 - MEDIA_ERR_ENCRYPTED
 
-    */
 
-   MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
 
-   /**
 
-    * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
 
-    *
 
-    * @type {Array}
 
-    * @constant
 
-    */
 
-   MediaError.defaultMessages = {
 
-     1: 'You aborted the media playback',
 
-     2: 'A network error caused the media download to fail part-way.',
 
-     3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
 
-     4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
 
-     5: 'The media is encrypted and we do not have the keys to decrypt it.'
 
-   };
 
-   // Add types as properties on MediaError
 
-   // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
 
-   for (let errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
 
-     MediaError[MediaError.errorTypes[errNum]] = errNum;
 
-     // values should be accessible on both the class and instance
 
-     MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
 
-   }
 
-   var tuple = SafeParseTuple;
 
-   function SafeParseTuple(obj, reviver) {
 
-     var json;
 
-     var error = null;
 
-     try {
 
-       json = JSON.parse(obj, reviver);
 
-     } catch (err) {
 
-       error = err;
 
-     }
 
-     return [error, json];
 
-   }
 
-   /**
 
-    * Returns whether an object is `Promise`-like (i.e. has a `then` method).
 
-    *
 
-    * @param  {Object}  value
 
-    *         An object that may or may not be `Promise`-like.
 
-    *
 
-    * @return {boolean}
 
-    *         Whether or not the object is `Promise`-like.
 
-    */
 
-   function isPromise(value) {
 
-     return value !== undefined && value !== null && typeof value.then === 'function';
 
-   }
 
-   /**
 
-    * Silence a Promise-like object.
 
-    *
 
-    * This is useful for avoiding non-harmful, but potentially confusing "uncaught
 
-    * play promise" rejection error messages.
 
-    *
 
-    * @param  {Object} value
 
-    *         An object that may or may not be `Promise`-like.
 
-    */
 
-   function silencePromise(value) {
 
-     if (isPromise(value)) {
 
-       value.then(null, e => {});
 
-     }
 
-   }
 
-   /**
 
-    * @file text-track-list-converter.js Utilities for capturing text track state and
 
-    * re-creating tracks based on a capture.
 
-    *
 
-    * @module text-track-list-converter
 
-    */
 
-   /**
 
-    * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
 
-    * represents the {@link TextTrack}'s state.
 
-    *
 
-    * @param {TextTrack} track
 
-    *        The text track to query.
 
-    *
 
-    * @return {Object}
 
-    *         A serializable javascript representation of the TextTrack.
 
-    * @private
 
-    */
 
-   const trackToJson_ = function (track) {
 
-     const ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce((acc, prop, i) => {
 
-       if (track[prop]) {
 
-         acc[prop] = track[prop];
 
-       }
 
-       return acc;
 
-     }, {
 
-       cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
 
-         return {
 
-           startTime: cue.startTime,
 
-           endTime: cue.endTime,
 
-           text: cue.text,
 
-           id: cue.id
 
-         };
 
-       })
 
-     });
 
-     return ret;
 
-   };
 
-   /**
 
-    * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
 
-    * state of all {@link TextTrack}s currently configured. The return array is compatible with
 
-    * {@link text-track-list-converter:jsonToTextTracks}.
 
-    *
 
-    * @param { import('../tech/tech').default } tech
 
-    *        The tech object to query
 
-    *
 
-    * @return {Array}
 
-    *         A serializable javascript representation of the {@link Tech}s
 
-    *         {@link TextTrackList}.
 
-    */
 
-   const textTracksToJson = function (tech) {
 
-     const trackEls = tech.$$('track');
 
-     const trackObjs = Array.prototype.map.call(trackEls, t => t.track);
 
-     const tracks = Array.prototype.map.call(trackEls, function (trackEl) {
 
-       const json = trackToJson_(trackEl.track);
 
-       if (trackEl.src) {
 
-         json.src = trackEl.src;
 
-       }
 
-       return json;
 
-     });
 
-     return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
 
-       return trackObjs.indexOf(track) === -1;
 
-     }).map(trackToJson_));
 
-   };
 
-   /**
 
-    * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
 
-    * object {@link TextTrack} representations.
 
-    *
 
-    * @param {Array} json
 
-    *        An array of `TextTrack` representation objects, like those that would be
 
-    *        produced by `textTracksToJson`.
 
-    *
 
-    * @param {Tech} tech
 
-    *        The `Tech` to create the `TextTrack`s on.
 
-    */
 
-   const jsonToTextTracks = function (json, tech) {
 
-     json.forEach(function (track) {
 
-       const addedTrack = tech.addRemoteTextTrack(track).track;
 
-       if (!track.src && track.cues) {
 
-         track.cues.forEach(cue => addedTrack.addCue(cue));
 
-       }
 
-     });
 
-     return tech.textTracks();
 
-   };
 
-   var textTrackConverter = {
 
-     textTracksToJson,
 
-     jsonToTextTracks,
 
-     trackToJson_
 
-   };
 
-   /**
 
-    * @file modal-dialog.js
 
-    */
 
-   const MODAL_CLASS_NAME = 'vjs-modal-dialog';
 
-   /**
 
-    * The `ModalDialog` displays over the video and its controls, which blocks
 
-    * interaction with the player until it is closed.
 
-    *
 
-    * Modal dialogs include a "Close" button and will close when that button
 
-    * is activated - or when ESC is pressed anywhere.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class ModalDialog extends Component$1 {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @param { import('./utils/dom').ContentDescriptor} [options.content=undefined]
 
-      *        Provide customized content for this modal.
 
-      *
 
-      * @param {string} [options.description]
 
-      *        A text description for the modal, primarily for accessibility.
 
-      *
 
-      * @param {boolean} [options.fillAlways=false]
 
-      *        Normally, modals are automatically filled only the first time
 
-      *        they open. This tells the modal to refresh its content
 
-      *        every time it opens.
 
-      *
 
-      * @param {string} [options.label]
 
-      *        A text label for the modal, primarily for accessibility.
 
-      *
 
-      * @param {boolean} [options.pauseOnOpen=true]
 
-      *        If `true`, playback will will be paused if playing when
 
-      *        the modal opens, and resumed when it closes.
 
-      *
 
-      * @param {boolean} [options.temporary=true]
 
-      *        If `true`, the modal can only be opened once; it will be
 
-      *        disposed as soon as it's closed.
 
-      *
 
-      * @param {boolean} [options.uncloseable=false]
 
-      *        If `true`, the user will not be able to close the modal
 
-      *        through the UI in the normal ways. Programmatic closing is
 
-      *        still possible.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.handleKeyDown_ = e => this.handleKeyDown(e);
 
-       this.close_ = e => this.close(e);
 
-       this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false;
 
-       this.closeable(!this.options_.uncloseable);
 
-       this.content(this.options_.content);
 
-       // Make sure the contentEl is defined AFTER any children are initialized
 
-       // because we only want the contents of the modal in the contentEl
 
-       // (not the UI elements like the close button).
 
-       this.contentEl_ = createEl('div', {
 
-         className: `${MODAL_CLASS_NAME}-content`
 
-       }, {
 
-         role: 'document'
 
-       });
 
-       this.descEl_ = createEl('p', {
 
-         className: `${MODAL_CLASS_NAME}-description vjs-control-text`,
 
-         id: this.el().getAttribute('aria-describedby')
 
-       });
 
-       textContent(this.descEl_, this.description());
 
-       this.el_.appendChild(this.descEl_);
 
-       this.el_.appendChild(this.contentEl_);
 
-     }
 
-     /**
 
-      * Create the `ModalDialog`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The DOM element that gets created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: this.buildCSSClass(),
 
-         tabIndex: -1
 
-       }, {
 
-         'aria-describedby': `${this.id()}_description`,
 
-         'aria-hidden': 'true',
 
-         'aria-label': this.label(),
 
-         'role': 'dialog'
 
-       });
 
-     }
 
-     dispose() {
 
-       this.contentEl_ = null;
 
-       this.descEl_ = null;
 
-       this.previouslyActiveEl_ = null;
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `${MODAL_CLASS_NAME} vjs-hidden ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * Returns the label string for this modal. Primarily used for accessibility.
 
-      *
 
-      * @return {string}
 
-      *         the localized or raw label of this modal.
 
-      */
 
-     label() {
 
-       return this.localize(this.options_.label || 'Modal Window');
 
-     }
 
-     /**
 
-      * Returns the description string for this modal. Primarily used for
 
-      * accessibility.
 
-      *
 
-      * @return {string}
 
-      *         The localized or raw description of this modal.
 
-      */
 
-     description() {
 
-       let desc = this.options_.description || this.localize('This is a modal window.');
 
-       // Append a universal closeability message if the modal is closeable.
 
-       if (this.closeable()) {
 
-         desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
 
-       }
 
-       return desc;
 
-     }
 
-     /**
 
-      * Opens the modal.
 
-      *
 
-      * @fires ModalDialog#beforemodalopen
 
-      * @fires ModalDialog#modalopen
 
-      */
 
-     open() {
 
-       if (!this.opened_) {
 
-         const player = this.player();
 
-         /**
 
-           * Fired just before a `ModalDialog` is opened.
 
-           *
 
-           * @event ModalDialog#beforemodalopen
 
-           * @type {Event}
 
-           */
 
-         this.trigger('beforemodalopen');
 
-         this.opened_ = true;
 
-         // Fill content if the modal has never opened before and
 
-         // never been filled.
 
-         if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
 
-           this.fill();
 
-         }
 
-         // If the player was playing, pause it and take note of its previously
 
-         // playing state.
 
-         this.wasPlaying_ = !player.paused();
 
-         if (this.options_.pauseOnOpen && this.wasPlaying_) {
 
-           player.pause();
 
-         }
 
-         this.on('keydown', this.handleKeyDown_);
 
-         // Hide controls and note if they were enabled.
 
-         this.hadControls_ = player.controls();
 
-         player.controls(false);
 
-         this.show();
 
-         this.conditionalFocus_();
 
-         this.el().setAttribute('aria-hidden', 'false');
 
-         /**
 
-           * Fired just after a `ModalDialog` is opened.
 
-           *
 
-           * @event ModalDialog#modalopen
 
-           * @type {Event}
 
-           */
 
-         this.trigger('modalopen');
 
-         this.hasBeenOpened_ = true;
 
-       }
 
-     }
 
-     /**
 
-      * If the `ModalDialog` is currently open or closed.
 
-      *
 
-      * @param  {boolean} [value]
 
-      *         If given, it will open (`true`) or close (`false`) the modal.
 
-      *
 
-      * @return {boolean}
 
-      *         the current open state of the modaldialog
 
-      */
 
-     opened(value) {
 
-       if (typeof value === 'boolean') {
 
-         this[value ? 'open' : 'close']();
 
-       }
 
-       return this.opened_;
 
-     }
 
-     /**
 
-      * Closes the modal, does nothing if the `ModalDialog` is
 
-      * not open.
 
-      *
 
-      * @fires ModalDialog#beforemodalclose
 
-      * @fires ModalDialog#modalclose
 
-      */
 
-     close() {
 
-       if (!this.opened_) {
 
-         return;
 
-       }
 
-       const player = this.player();
 
-       /**
 
-         * Fired just before a `ModalDialog` is closed.
 
-         *
 
-         * @event ModalDialog#beforemodalclose
 
-         * @type {Event}
 
-         */
 
-       this.trigger('beforemodalclose');
 
-       this.opened_ = false;
 
-       if (this.wasPlaying_ && this.options_.pauseOnOpen) {
 
-         player.play();
 
-       }
 
-       this.off('keydown', this.handleKeyDown_);
 
-       if (this.hadControls_) {
 
-         player.controls(true);
 
-       }
 
-       this.hide();
 
-       this.el().setAttribute('aria-hidden', 'true');
 
-       /**
 
-         * Fired just after a `ModalDialog` is closed.
 
-         *
 
-         * @event ModalDialog#modalclose
 
-         * @type {Event}
 
-         */
 
-       this.trigger('modalclose');
 
-       this.conditionalBlur_();
 
-       if (this.options_.temporary) {
 
-         this.dispose();
 
-       }
 
-     }
 
-     /**
 
-      * Check to see if the `ModalDialog` is closeable via the UI.
 
-      *
 
-      * @param  {boolean} [value]
 
-      *         If given as a boolean, it will set the `closeable` option.
 
-      *
 
-      * @return {boolean}
 
-      *         Returns the final value of the closable option.
 
-      */
 
-     closeable(value) {
 
-       if (typeof value === 'boolean') {
 
-         const closeable = this.closeable_ = !!value;
 
-         let close = this.getChild('closeButton');
 
-         // If this is being made closeable and has no close button, add one.
 
-         if (closeable && !close) {
 
-           // The close button should be a child of the modal - not its
 
-           // content element, so temporarily change the content element.
 
-           const temp = this.contentEl_;
 
-           this.contentEl_ = this.el_;
 
-           close = this.addChild('closeButton', {
 
-             controlText: 'Close Modal Dialog'
 
-           });
 
-           this.contentEl_ = temp;
 
-           this.on(close, 'close', this.close_);
 
-         }
 
-         // If this is being made uncloseable and has a close button, remove it.
 
-         if (!closeable && close) {
 
-           this.off(close, 'close', this.close_);
 
-           this.removeChild(close);
 
-           close.dispose();
 
-         }
 
-       }
 
-       return this.closeable_;
 
-     }
 
-     /**
 
-      * Fill the modal's content element with the modal's "content" option.
 
-      * The content element will be emptied before this change takes place.
 
-      */
 
-     fill() {
 
-       this.fillWith(this.content());
 
-     }
 
-     /**
 
-      * Fill the modal's content element with arbitrary content.
 
-      * The content element will be emptied before this change takes place.
 
-      *
 
-      * @fires ModalDialog#beforemodalfill
 
-      * @fires ModalDialog#modalfill
 
-      *
 
-      * @param { import('./utils/dom').ContentDescriptor} [content]
 
-      *        The same rules apply to this as apply to the `content` option.
 
-      */
 
-     fillWith(content) {
 
-       const contentEl = this.contentEl();
 
-       const parentEl = contentEl.parentNode;
 
-       const nextSiblingEl = contentEl.nextSibling;
 
-       /**
 
-         * Fired just before a `ModalDialog` is filled with content.
 
-         *
 
-         * @event ModalDialog#beforemodalfill
 
-         * @type {Event}
 
-         */
 
-       this.trigger('beforemodalfill');
 
-       this.hasBeenFilled_ = true;
 
-       // Detach the content element from the DOM before performing
 
-       // manipulation to avoid modifying the live DOM multiple times.
 
-       parentEl.removeChild(contentEl);
 
-       this.empty();
 
-       insertContent(contentEl, content);
 
-       /**
 
-        * Fired just after a `ModalDialog` is filled with content.
 
-        *
 
-        * @event ModalDialog#modalfill
 
-        * @type {Event}
 
-        */
 
-       this.trigger('modalfill');
 
-       // Re-inject the re-filled content element.
 
-       if (nextSiblingEl) {
 
-         parentEl.insertBefore(contentEl, nextSiblingEl);
 
-       } else {
 
-         parentEl.appendChild(contentEl);
 
-       }
 
-       // make sure that the close button is last in the dialog DOM
 
-       const closeButton = this.getChild('closeButton');
 
-       if (closeButton) {
 
-         parentEl.appendChild(closeButton.el_);
 
-       }
 
-     }
 
-     /**
 
-      * Empties the content element. This happens anytime the modal is filled.
 
-      *
 
-      * @fires ModalDialog#beforemodalempty
 
-      * @fires ModalDialog#modalempty
 
-      */
 
-     empty() {
 
-       /**
 
-       * Fired just before a `ModalDialog` is emptied.
 
-       *
 
-       * @event ModalDialog#beforemodalempty
 
-       * @type {Event}
 
-       */
 
-       this.trigger('beforemodalempty');
 
-       emptyEl(this.contentEl());
 
-       /**
 
-       * Fired just after a `ModalDialog` is emptied.
 
-       *
 
-       * @event ModalDialog#modalempty
 
-       * @type {Event}
 
-       */
 
-       this.trigger('modalempty');
 
-     }
 
-     /**
 
-      * Gets or sets the modal content, which gets normalized before being
 
-      * rendered into the DOM.
 
-      *
 
-      * This does not update the DOM or fill the modal, but it is called during
 
-      * that process.
 
-      *
 
-      * @param  { import('./utils/dom').ContentDescriptor} [value]
 
-      *         If defined, sets the internal content value to be used on the
 
-      *         next call(s) to `fill`. This value is normalized before being
 
-      *         inserted. To "clear" the internal content value, pass `null`.
 
-      *
 
-      * @return { import('./utils/dom').ContentDescriptor}
 
-      *         The current content of the modal dialog
 
-      */
 
-     content(value) {
 
-       if (typeof value !== 'undefined') {
 
-         this.content_ = value;
 
-       }
 
-       return this.content_;
 
-     }
 
-     /**
 
-      * conditionally focus the modal dialog if focus was previously on the player.
 
-      *
 
-      * @private
 
-      */
 
-     conditionalFocus_() {
 
-       const activeEl = document.activeElement;
 
-       const playerEl = this.player_.el_;
 
-       this.previouslyActiveEl_ = null;
 
-       if (playerEl.contains(activeEl) || playerEl === activeEl) {
 
-         this.previouslyActiveEl_ = activeEl;
 
-         this.focus();
 
-       }
 
-     }
 
-     /**
 
-      * conditionally blur the element and refocus the last focused element
 
-      *
 
-      * @private
 
-      */
 
-     conditionalBlur_() {
 
-       if (this.previouslyActiveEl_) {
 
-         this.previouslyActiveEl_.focus();
 
-         this.previouslyActiveEl_ = null;
 
-       }
 
-     }
 
-     /**
 
-      * Keydown handler. Attached when modal is focused.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       // Do not allow keydowns to reach out of the modal dialog.
 
-       event.stopPropagation();
 
-       if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
 
-         event.preventDefault();
 
-         this.close();
 
-         return;
 
-       }
 
-       // exit early if it isn't a tab key
 
-       if (!keycode.isEventKey(event, 'Tab')) {
 
-         return;
 
-       }
 
-       const focusableEls = this.focusableEls_();
 
-       const activeEl = this.el_.querySelector(':focus');
 
-       let focusIndex;
 
-       for (let i = 0; i < focusableEls.length; i++) {
 
-         if (activeEl === focusableEls[i]) {
 
-           focusIndex = i;
 
-           break;
 
-         }
 
-       }
 
-       if (document.activeElement === this.el_) {
 
-         focusIndex = 0;
 
-       }
 
-       if (event.shiftKey && focusIndex === 0) {
 
-         focusableEls[focusableEls.length - 1].focus();
 
-         event.preventDefault();
 
-       } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
 
-         focusableEls[0].focus();
 
-         event.preventDefault();
 
-       }
 
-     }
 
-     /**
 
-      * get all focusable elements
 
-      *
 
-      * @private
 
-      */
 
-     focusableEls_() {
 
-       const allChildren = this.el_.querySelectorAll('*');
 
-       return Array.prototype.filter.call(allChildren, child => {
 
-         return (child instanceof window.HTMLAnchorElement || child instanceof window.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window.HTMLInputElement || child instanceof window.HTMLSelectElement || child instanceof window.HTMLTextAreaElement || child instanceof window.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window.HTMLIFrameElement || child instanceof window.HTMLObjectElement || child instanceof window.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
 
-       });
 
-     }
 
-   }
 
-   /**
 
-    * Default options for `ModalDialog` default options.
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   ModalDialog.prototype.options_ = {
 
-     pauseOnOpen: true,
 
-     temporary: true
 
-   };
 
-   Component$1.registerComponent('ModalDialog', ModalDialog);
 
-   /**
 
-    * @file track-list.js
 
-    */
 
-   /**
 
-    * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
 
-    * {@link VideoTrackList}
 
-    *
 
-    * @extends EventTarget
 
-    */
 
-   class TrackList extends EventTarget$2 {
 
-     /**
 
-      * Create an instance of this class
 
-      *
 
-      * @param { import('./track').default[] } tracks
 
-      *        A list of tracks to initialize the list with.
 
-      *
 
-      * @abstract
 
-      */
 
-     constructor(tracks = []) {
 
-       super();
 
-       this.tracks_ = [];
 
-       /**
 
-        * @memberof TrackList
 
-        * @member {number} length
 
-        *         The current number of `Track`s in the this Trackist.
 
-        * @instance
 
-        */
 
-       Object.defineProperty(this, 'length', {
 
-         get() {
 
-           return this.tracks_.length;
 
-         }
 
-       });
 
-       for (let i = 0; i < tracks.length; i++) {
 
-         this.addTrack(tracks[i]);
 
-       }
 
-     }
 
-     /**
 
-      * Add a {@link Track} to the `TrackList`
 
-      *
 
-      * @param { import('./track').default } track
 
-      *        The audio, video, or text track to add to the list.
 
-      *
 
-      * @fires TrackList#addtrack
 
-      */
 
-     addTrack(track) {
 
-       const index = this.tracks_.length;
 
-       if (!('' + index in this)) {
 
-         Object.defineProperty(this, index, {
 
-           get() {
 
-             return this.tracks_[index];
 
-           }
 
-         });
 
-       }
 
-       // Do not add duplicate tracks
 
-       if (this.tracks_.indexOf(track) === -1) {
 
-         this.tracks_.push(track);
 
-         /**
 
-          * Triggered when a track is added to a track list.
 
-          *
 
-          * @event TrackList#addtrack
 
-          * @type {Event}
 
-          * @property {Track} track
 
-          *           A reference to track that was added.
 
-          */
 
-         this.trigger({
 
-           track,
 
-           type: 'addtrack',
 
-           target: this
 
-         });
 
-       }
 
-       /**
 
-        * Triggered when a track label is changed.
 
-        *
 
-        * @event TrackList#addtrack
 
-        * @type {Event}
 
-        * @property {Track} track
 
-        *           A reference to track that was added.
 
-        */
 
-       track.labelchange_ = () => {
 
-         this.trigger({
 
-           track,
 
-           type: 'labelchange',
 
-           target: this
 
-         });
 
-       };
 
-       if (isEvented(track)) {
 
-         track.addEventListener('labelchange', track.labelchange_);
 
-       }
 
-     }
 
-     /**
 
-      * Remove a {@link Track} from the `TrackList`
 
-      *
 
-      * @param { import('./track').default } rtrack
 
-      *        The audio, video, or text track to remove from the list.
 
-      *
 
-      * @fires TrackList#removetrack
 
-      */
 
-     removeTrack(rtrack) {
 
-       let track;
 
-       for (let i = 0, l = this.length; i < l; i++) {
 
-         if (this[i] === rtrack) {
 
-           track = this[i];
 
-           if (track.off) {
 
-             track.off();
 
-           }
 
-           this.tracks_.splice(i, 1);
 
-           break;
 
-         }
 
-       }
 
-       if (!track) {
 
-         return;
 
-       }
 
-       /**
 
-        * Triggered when a track is removed from track list.
 
-        *
 
-        * @event TrackList#removetrack
 
-        * @type {Event}
 
-        * @property {Track} track
 
-        *           A reference to track that was removed.
 
-        */
 
-       this.trigger({
 
-         track,
 
-         type: 'removetrack',
 
-         target: this
 
-       });
 
-     }
 
-     /**
 
-      * Get a Track from the TrackList by a tracks id
 
-      *
 
-      * @param {string} id - the id of the track to get
 
-      * @method getTrackById
 
-      * @return { import('./track').default }
 
-      * @private
 
-      */
 
-     getTrackById(id) {
 
-       let result = null;
 
-       for (let i = 0, l = this.length; i < l; i++) {
 
-         const track = this[i];
 
-         if (track.id === id) {
 
-           result = track;
 
-           break;
 
-         }
 
-       }
 
-       return result;
 
-     }
 
-   }
 
-   /**
 
-    * Triggered when a different track is selected/enabled.
 
-    *
 
-    * @event TrackList#change
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Events that can be called with on + eventName. See {@link EventHandler}.
 
-    *
 
-    * @property {Object} TrackList#allowedEvents_
 
-    * @private
 
-    */
 
-   TrackList.prototype.allowedEvents_ = {
 
-     change: 'change',
 
-     addtrack: 'addtrack',
 
-     removetrack: 'removetrack',
 
-     labelchange: 'labelchange'
 
-   };
 
-   // emulate attribute EventHandler support to allow for feature detection
 
-   for (const event in TrackList.prototype.allowedEvents_) {
 
-     TrackList.prototype['on' + event] = null;
 
-   }
 
-   /**
 
-    * @file audio-track-list.js
 
-    */
 
-   /**
 
-    * Anywhere we call this function we diverge from the spec
 
-    * as we only support one enabled audiotrack at a time
 
-    *
 
-    * @param {AudioTrackList} list
 
-    *        list to work on
 
-    *
 
-    * @param { import('./audio-track').default } track
 
-    *        The track to skip
 
-    *
 
-    * @private
 
-    */
 
-   const disableOthers$1 = function (list, track) {
 
-     for (let i = 0; i < list.length; i++) {
 
-       if (!Object.keys(list[i]).length || track.id === list[i].id) {
 
-         continue;
 
-       }
 
-       // another audio track is enabled, disable it
 
-       list[i].enabled = false;
 
-     }
 
-   };
 
-   /**
 
-    * The current list of {@link AudioTrack} for a media file.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
 
-    * @extends TrackList
 
-    */
 
-   class AudioTrackList extends TrackList {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param {AudioTrack[]} [tracks=[]]
 
-      *        A list of `AudioTrack` to instantiate the list with.
 
-      */
 
-     constructor(tracks = []) {
 
-       // make sure only 1 track is enabled
 
-       // sorted from last index to first index
 
-       for (let i = tracks.length - 1; i >= 0; i--) {
 
-         if (tracks[i].enabled) {
 
-           disableOthers$1(tracks, tracks[i]);
 
-           break;
 
-         }
 
-       }
 
-       super(tracks);
 
-       this.changing_ = false;
 
-     }
 
-     /**
 
-      * Add an {@link AudioTrack} to the `AudioTrackList`.
 
-      *
 
-      * @param { import('./audio-track').default } track
 
-      *        The AudioTrack to add to the list
 
-      *
 
-      * @fires TrackList#addtrack
 
-      */
 
-     addTrack(track) {
 
-       if (track.enabled) {
 
-         disableOthers$1(this, track);
 
-       }
 
-       super.addTrack(track);
 
-       // native tracks don't have this
 
-       if (!track.addEventListener) {
 
-         return;
 
-       }
 
-       track.enabledChange_ = () => {
 
-         // when we are disabling other tracks (since we don't support
 
-         // more than one track at a time) we will set changing_
 
-         // to true so that we don't trigger additional change events
 
-         if (this.changing_) {
 
-           return;
 
-         }
 
-         this.changing_ = true;
 
-         disableOthers$1(this, track);
 
-         this.changing_ = false;
 
-         this.trigger('change');
 
-       };
 
-       /**
 
-        * @listens AudioTrack#enabledchange
 
-        * @fires TrackList#change
 
-        */
 
-       track.addEventListener('enabledchange', track.enabledChange_);
 
-     }
 
-     removeTrack(rtrack) {
 
-       super.removeTrack(rtrack);
 
-       if (rtrack.removeEventListener && rtrack.enabledChange_) {
 
-         rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
 
-         rtrack.enabledChange_ = null;
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * @file video-track-list.js
 
-    */
 
-   /**
 
-    * Un-select all other {@link VideoTrack}s that are selected.
 
-    *
 
-    * @param {VideoTrackList} list
 
-    *        list to work on
 
-    *
 
-    * @param { import('./video-track').default } track
 
-    *        The track to skip
 
-    *
 
-    * @private
 
-    */
 
-   const disableOthers = function (list, track) {
 
-     for (let i = 0; i < list.length; i++) {
 
-       if (!Object.keys(list[i]).length || track.id === list[i].id) {
 
-         continue;
 
-       }
 
-       // another video track is enabled, disable it
 
-       list[i].selected = false;
 
-     }
 
-   };
 
-   /**
 
-    * The current list of {@link VideoTrack} for a video.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
 
-    * @extends TrackList
 
-    */
 
-   class VideoTrackList extends TrackList {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param {VideoTrack[]} [tracks=[]]
 
-      *        A list of `VideoTrack` to instantiate the list with.
 
-      */
 
-     constructor(tracks = []) {
 
-       // make sure only 1 track is enabled
 
-       // sorted from last index to first index
 
-       for (let i = tracks.length - 1; i >= 0; i--) {
 
-         if (tracks[i].selected) {
 
-           disableOthers(tracks, tracks[i]);
 
-           break;
 
-         }
 
-       }
 
-       super(tracks);
 
-       this.changing_ = false;
 
-       /**
 
-        * @member {number} VideoTrackList#selectedIndex
 
-        *         The current index of the selected {@link VideoTrack`}.
 
-        */
 
-       Object.defineProperty(this, 'selectedIndex', {
 
-         get() {
 
-           for (let i = 0; i < this.length; i++) {
 
-             if (this[i].selected) {
 
-               return i;
 
-             }
 
-           }
 
-           return -1;
 
-         },
 
-         set() {}
 
-       });
 
-     }
 
-     /**
 
-      * Add a {@link VideoTrack} to the `VideoTrackList`.
 
-      *
 
-      * @param { import('./video-track').default } track
 
-      *        The VideoTrack to add to the list
 
-      *
 
-      * @fires TrackList#addtrack
 
-      */
 
-     addTrack(track) {
 
-       if (track.selected) {
 
-         disableOthers(this, track);
 
-       }
 
-       super.addTrack(track);
 
-       // native tracks don't have this
 
-       if (!track.addEventListener) {
 
-         return;
 
-       }
 
-       track.selectedChange_ = () => {
 
-         if (this.changing_) {
 
-           return;
 
-         }
 
-         this.changing_ = true;
 
-         disableOthers(this, track);
 
-         this.changing_ = false;
 
-         this.trigger('change');
 
-       };
 
-       /**
 
-        * @listens VideoTrack#selectedchange
 
-        * @fires TrackList#change
 
-        */
 
-       track.addEventListener('selectedchange', track.selectedChange_);
 
-     }
 
-     removeTrack(rtrack) {
 
-       super.removeTrack(rtrack);
 
-       if (rtrack.removeEventListener && rtrack.selectedChange_) {
 
-         rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
 
-         rtrack.selectedChange_ = null;
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * @file text-track-list.js
 
-    */
 
-   /**
 
-    * The current list of {@link TextTrack} for a media file.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
 
-    * @extends TrackList
 
-    */
 
-   class TextTrackList extends TrackList {
 
-     /**
 
-      * Add a {@link TextTrack} to the `TextTrackList`
 
-      *
 
-      * @param { import('./text-track').default } track
 
-      *        The text track to add to the list.
 
-      *
 
-      * @fires TrackList#addtrack
 
-      */
 
-     addTrack(track) {
 
-       super.addTrack(track);
 
-       if (!this.queueChange_) {
 
-         this.queueChange_ = () => this.queueTrigger('change');
 
-       }
 
-       if (!this.triggerSelectedlanguagechange) {
 
-         this.triggerSelectedlanguagechange_ = () => this.trigger('selectedlanguagechange');
 
-       }
 
-       /**
 
-        * @listens TextTrack#modechange
 
-        * @fires TrackList#change
 
-        */
 
-       track.addEventListener('modechange', this.queueChange_);
 
-       const nonLanguageTextTrackKind = ['metadata', 'chapters'];
 
-       if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
 
-         track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
 
-       }
 
-     }
 
-     removeTrack(rtrack) {
 
-       super.removeTrack(rtrack);
 
-       // manually remove the event handlers we added
 
-       if (rtrack.removeEventListener) {
 
-         if (this.queueChange_) {
 
-           rtrack.removeEventListener('modechange', this.queueChange_);
 
-         }
 
-         if (this.selectedlanguagechange_) {
 
-           rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
 
-         }
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * @file html-track-element-list.js
 
-    */
 
-   /**
 
-    * The current list of {@link HtmlTrackElement}s.
 
-    */
 
-   class HtmlTrackElementList {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param {HtmlTrackElement[]} [tracks=[]]
 
-      *        A list of `HtmlTrackElement` to instantiate the list with.
 
-      */
 
-     constructor(trackElements = []) {
 
-       this.trackElements_ = [];
 
-       /**
 
-        * @memberof HtmlTrackElementList
 
-        * @member {number} length
 
-        *         The current number of `Track`s in the this Trackist.
 
-        * @instance
 
-        */
 
-       Object.defineProperty(this, 'length', {
 
-         get() {
 
-           return this.trackElements_.length;
 
-         }
 
-       });
 
-       for (let i = 0, length = trackElements.length; i < length; i++) {
 
-         this.addTrackElement_(trackElements[i]);
 
-       }
 
-     }
 
-     /**
 
-      * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
 
-      *
 
-      * @param {HtmlTrackElement} trackElement
 
-      *        The track element to add to the list.
 
-      *
 
-      * @private
 
-      */
 
-     addTrackElement_(trackElement) {
 
-       const index = this.trackElements_.length;
 
-       if (!('' + index in this)) {
 
-         Object.defineProperty(this, index, {
 
-           get() {
 
-             return this.trackElements_[index];
 
-           }
 
-         });
 
-       }
 
-       // Do not add duplicate elements
 
-       if (this.trackElements_.indexOf(trackElement) === -1) {
 
-         this.trackElements_.push(trackElement);
 
-       }
 
-     }
 
-     /**
 
-      * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
 
-      * {@link TextTrack}.
 
-      *
 
-      * @param {TextTrack} track
 
-      *        The track associated with a track element.
 
-      *
 
-      * @return {HtmlTrackElement|undefined}
 
-      *         The track element that was found or undefined.
 
-      *
 
-      * @private
 
-      */
 
-     getTrackElementByTrack_(track) {
 
-       let trackElement_;
 
-       for (let i = 0, length = this.trackElements_.length; i < length; i++) {
 
-         if (track === this.trackElements_[i].track) {
 
-           trackElement_ = this.trackElements_[i];
 
-           break;
 
-         }
 
-       }
 
-       return trackElement_;
 
-     }
 
-     /**
 
-      * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
 
-      *
 
-      * @param {HtmlTrackElement} trackElement
 
-      *        The track element to remove from the list.
 
-      *
 
-      * @private
 
-      */
 
-     removeTrackElement_(trackElement) {
 
-       for (let i = 0, length = this.trackElements_.length; i < length; i++) {
 
-         if (trackElement === this.trackElements_[i]) {
 
-           if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
 
-             this.trackElements_[i].track.off();
 
-           }
 
-           if (typeof this.trackElements_[i].off === 'function') {
 
-             this.trackElements_[i].off();
 
-           }
 
-           this.trackElements_.splice(i, 1);
 
-           break;
 
-         }
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * @file text-track-cue-list.js
 
-    */
 
-   /**
 
-    * @typedef {Object} TextTrackCueList~TextTrackCue
 
-    *
 
-    * @property {string} id
 
-    *           The unique id for this text track cue
 
-    *
 
-    * @property {number} startTime
 
-    *           The start time for this text track cue
 
-    *
 
-    * @property {number} endTime
 
-    *           The end time for this text track cue
 
-    *
 
-    * @property {boolean} pauseOnExit
 
-    *           Pause when the end time is reached if true.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
 
-    */
 
-   /**
 
-    * A List of TextTrackCues.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
 
-    */
 
-   class TextTrackCueList {
 
-     /**
 
-      * Create an instance of this class..
 
-      *
 
-      * @param {Array} cues
 
-      *        A list of cues to be initialized with
 
-      */
 
-     constructor(cues) {
 
-       TextTrackCueList.prototype.setCues_.call(this, cues);
 
-       /**
 
-        * @memberof TextTrackCueList
 
-        * @member {number} length
 
-        *         The current number of `TextTrackCue`s in the TextTrackCueList.
 
-        * @instance
 
-        */
 
-       Object.defineProperty(this, 'length', {
 
-         get() {
 
-           return this.length_;
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * A setter for cues in this list. Creates getters
 
-      * an an index for the cues.
 
-      *
 
-      * @param {Array} cues
 
-      *        An array of cues to set
 
-      *
 
-      * @private
 
-      */
 
-     setCues_(cues) {
 
-       const oldLength = this.length || 0;
 
-       let i = 0;
 
-       const l = cues.length;
 
-       this.cues_ = cues;
 
-       this.length_ = cues.length;
 
-       const defineProp = function (index) {
 
-         if (!('' + index in this)) {
 
-           Object.defineProperty(this, '' + index, {
 
-             get() {
 
-               return this.cues_[index];
 
-             }
 
-           });
 
-         }
 
-       };
 
-       if (oldLength < l) {
 
-         i = oldLength;
 
-         for (; i < l; i++) {
 
-           defineProp.call(this, i);
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
 
-      *
 
-      * @param {string} id
 
-      *        The id of the cue that should be searched for.
 
-      *
 
-      * @return {TextTrackCueList~TextTrackCue|null}
 
-      *         A single cue or null if none was found.
 
-      */
 
-     getCueById(id) {
 
-       let result = null;
 
-       for (let i = 0, l = this.length; i < l; i++) {
 
-         const cue = this[i];
 
-         if (cue.id === id) {
 
-           result = cue;
 
-           break;
 
-         }
 
-       }
 
-       return result;
 
-     }
 
-   }
 
-   /**
 
-    * @file track-kinds.js
 
-    */
 
-   /**
 
-    * All possible `VideoTrackKind`s
 
-    *
 
-    * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
 
-    * @typedef VideoTrack~Kind
 
-    * @enum
 
-    */
 
-   const VideoTrackKind = {
 
-     alternative: 'alternative',
 
-     captions: 'captions',
 
-     main: 'main',
 
-     sign: 'sign',
 
-     subtitles: 'subtitles',
 
-     commentary: 'commentary'
 
-   };
 
-   /**
 
-    * All possible `AudioTrackKind`s
 
-    *
 
-    * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
 
-    * @typedef AudioTrack~Kind
 
-    * @enum
 
-    */
 
-   const AudioTrackKind = {
 
-     'alternative': 'alternative',
 
-     'descriptions': 'descriptions',
 
-     'main': 'main',
 
-     'main-desc': 'main-desc',
 
-     'translation': 'translation',
 
-     'commentary': 'commentary'
 
-   };
 
-   /**
 
-    * All possible `TextTrackKind`s
 
-    *
 
-    * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
 
-    * @typedef TextTrack~Kind
 
-    * @enum
 
-    */
 
-   const TextTrackKind = {
 
-     subtitles: 'subtitles',
 
-     captions: 'captions',
 
-     descriptions: 'descriptions',
 
-     chapters: 'chapters',
 
-     metadata: 'metadata'
 
-   };
 
-   /**
 
-    * All possible `TextTrackMode`s
 
-    *
 
-    * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
 
-    * @typedef TextTrack~Mode
 
-    * @enum
 
-    */
 
-   const TextTrackMode = {
 
-     disabled: 'disabled',
 
-     hidden: 'hidden',
 
-     showing: 'showing'
 
-   };
 
-   /**
 
-    * @file track.js
 
-    */
 
-   /**
 
-    * A Track class that contains all of the common functionality for {@link AudioTrack},
 
-    * {@link VideoTrack}, and {@link TextTrack}.
 
-    *
 
-    * > Note: This class should not be used directly
 
-    *
 
-    * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
 
-    * @extends EventTarget
 
-    * @abstract
 
-    */
 
-   class Track extends EventTarget$2 {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        Object of option names and values
 
-      *
 
-      * @param {string} [options.kind='']
 
-      *        A valid kind for the track type you are creating.
 
-      *
 
-      * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
 
-      *        A unique id for this AudioTrack.
 
-      *
 
-      * @param {string} [options.label='']
 
-      *        The menu label for this track.
 
-      *
 
-      * @param {string} [options.language='']
 
-      *        A valid two character language code.
 
-      *
 
-      * @abstract
 
-      */
 
-     constructor(options = {}) {
 
-       super();
 
-       const trackProps = {
 
-         id: options.id || 'vjs_track_' + newGUID(),
 
-         kind: options.kind || '',
 
-         language: options.language || ''
 
-       };
 
-       let label = options.label || '';
 
-       /**
 
-        * @memberof Track
 
-        * @member {string} id
 
-        *         The id of this track. Cannot be changed after creation.
 
-        * @instance
 
-        *
 
-        * @readonly
 
-        */
 
-       /**
 
-        * @memberof Track
 
-        * @member {string} kind
 
-        *         The kind of track that this is. Cannot be changed after creation.
 
-        * @instance
 
-        *
 
-        * @readonly
 
-        */
 
-       /**
 
-        * @memberof Track
 
-        * @member {string} language
 
-        *         The two letter language code for this track. Cannot be changed after
 
-        *         creation.
 
-        * @instance
 
-        *
 
-        * @readonly
 
-        */
 
-       for (const key in trackProps) {
 
-         Object.defineProperty(this, key, {
 
-           get() {
 
-             return trackProps[key];
 
-           },
 
-           set() {}
 
-         });
 
-       }
 
-       /**
 
-        * @memberof Track
 
-        * @member {string} label
 
-        *         The label of this track. Cannot be changed after creation.
 
-        * @instance
 
-        *
 
-        * @fires Track#labelchange
 
-        */
 
-       Object.defineProperty(this, 'label', {
 
-         get() {
 
-           return label;
 
-         },
 
-         set(newLabel) {
 
-           if (newLabel !== label) {
 
-             label = newLabel;
 
-             /**
 
-              * An event that fires when label changes on this track.
 
-              *
 
-              * > Note: This is not part of the spec!
 
-              *
 
-              * @event Track#labelchange
 
-              * @type {Event}
 
-              */
 
-             this.trigger('labelchange');
 
-           }
 
-         }
 
-       });
 
-     }
 
-   }
 
-   /**
 
-    * @file url.js
 
-    * @module url
 
-    */
 
-   /**
 
-    * @typedef {Object} url:URLObject
 
-    *
 
-    * @property {string} protocol
 
-    *           The protocol of the url that was parsed.
 
-    *
 
-    * @property {string} hostname
 
-    *           The hostname of the url that was parsed.
 
-    *
 
-    * @property {string} port
 
-    *           The port of the url that was parsed.
 
-    *
 
-    * @property {string} pathname
 
-    *           The pathname of the url that was parsed.
 
-    *
 
-    * @property {string} search
 
-    *           The search query of the url that was parsed.
 
-    *
 
-    * @property {string} hash
 
-    *           The hash of the url that was parsed.
 
-    *
 
-    * @property {string} host
 
-    *           The host of the url that was parsed.
 
-    */
 
-   /**
 
-    * Resolve and parse the elements of a URL.
 
-    *
 
-    * @function
 
-    * @param    {String} url
 
-    *           The url to parse
 
-    *
 
-    * @return   {url:URLObject}
 
-    *           An object of url details
 
-    */
 
-   const parseUrl = function (url) {
 
-     // This entire method can be replace with URL once we are able to drop IE11
 
-     const props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
 
-     // add the url to an anchor and let the browser parse the URL
 
-     const a = document.createElement('a');
 
-     a.href = url;
 
-     // Copy the specific URL properties to a new object
 
-     // This is also needed for IE because the anchor loses its
 
-     // properties when it's removed from the dom
 
-     const details = {};
 
-     for (let i = 0; i < props.length; i++) {
 
-       details[props[i]] = a[props[i]];
 
-     }
 
-     // IE adds the port to the host property unlike everyone else. If
 
-     // a port identifier is added for standard ports, strip it.
 
-     if (details.protocol === 'http:') {
 
-       details.host = details.host.replace(/:80$/, '');
 
-     }
 
-     if (details.protocol === 'https:') {
 
-       details.host = details.host.replace(/:443$/, '');
 
-     }
 
-     if (!details.protocol) {
 
-       details.protocol = window.location.protocol;
 
-     }
 
-     /* istanbul ignore if */
 
-     if (!details.host) {
 
-       details.host = window.location.host;
 
-     }
 
-     return details;
 
-   };
 
-   /**
 
-    * Get absolute version of relative URL.
 
-    *
 
-    * @function
 
-    * @param    {string} url
 
-    *           URL to make absolute
 
-    *
 
-    * @return   {string}
 
-    *           Absolute URL
 
-    *
 
-    * @see      http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
 
-    */
 
-   const getAbsoluteURL = function (url) {
 
-     // Check if absolute URL
 
-     if (!url.match(/^https?:\/\//)) {
 
-       // Add the url to an anchor and let the browser parse it to convert to an absolute url
 
-       const a = document.createElement('a');
 
-       a.href = url;
 
-       url = a.href;
 
-     }
 
-     return url;
 
-   };
 
-   /**
 
-    * Returns the extension of the passed file name. It will return an empty string
 
-    * if passed an invalid path.
 
-    *
 
-    * @function
 
-    * @param    {string} path
 
-    *           The fileName path like '/path/to/file.mp4'
 
-    *
 
-    * @return  {string}
 
-    *           The extension in lower case or an empty string if no
 
-    *           extension could be found.
 
-    */
 
-   const getFileExtension = function (path) {
 
-     if (typeof path === 'string') {
 
-       const splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/;
 
-       const pathParts = splitPathRe.exec(path);
 
-       if (pathParts) {
 
-         return pathParts.pop().toLowerCase();
 
-       }
 
-     }
 
-     return '';
 
-   };
 
-   /**
 
-    * Returns whether the url passed is a cross domain request or not.
 
-    *
 
-    * @function
 
-    * @param    {string} url
 
-    *           The url to check.
 
-    *
 
-    * @param    {Object} [winLoc]
 
-    *           the domain to check the url against, defaults to window.location
 
-    *
 
-    * @param    {string} [winLoc.protocol]
 
-    *           The window location protocol defaults to window.location.protocol
 
-    *
 
-    * @param    {string} [winLoc.host]
 
-    *           The window location host defaults to window.location.host
 
-    *
 
-    * @return   {boolean}
 
-    *           Whether it is a cross domain request or not.
 
-    */
 
-   const isCrossOrigin = function (url, winLoc = window.location) {
 
-     const urlInfo = parseUrl(url);
 
-     // IE8 protocol relative urls will return ':' for protocol
 
-     const srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol;
 
-     // Check if url is for another domain/origin
 
-     // IE8 doesn't know location.origin, so we won't rely on it here
 
-     const crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
 
-     return crossOrigin;
 
-   };
 
-   var Url = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     parseUrl: parseUrl,
 
-     getAbsoluteURL: getAbsoluteURL,
 
-     getFileExtension: getFileExtension,
 
-     isCrossOrigin: isCrossOrigin
 
-   });
 
-   var win;
 
-   if (typeof window !== "undefined") {
 
-     win = window;
 
-   } else if (typeof commonjsGlobal !== "undefined") {
 
-     win = commonjsGlobal;
 
-   } else if (typeof self !== "undefined") {
 
-     win = self;
 
-   } else {
 
-     win = {};
 
-   }
 
-   var window_1 = win;
 
-   var _extends_1 = createCommonjsModule(function (module) {
 
-     function _extends() {
 
-       module.exports = _extends = Object.assign ? Object.assign.bind() : function (target) {
 
-         for (var i = 1; i < arguments.length; i++) {
 
-           var source = arguments[i];
 
-           for (var key in source) {
 
-             if (Object.prototype.hasOwnProperty.call(source, key)) {
 
-               target[key] = source[key];
 
-             }
 
-           }
 
-         }
 
-         return target;
 
-       }, module.exports.__esModule = true, module.exports["default"] = module.exports;
 
-       return _extends.apply(this, arguments);
 
-     }
 
-     module.exports = _extends, module.exports.__esModule = true, module.exports["default"] = module.exports;
 
-   });
 
-   var _extends$1 = unwrapExports(_extends_1);
 
-   var isFunction_1 = isFunction;
 
-   var toString = Object.prototype.toString;
 
-   function isFunction(fn) {
 
-     if (!fn) {
 
-       return false;
 
-     }
 
-     var string = toString.call(fn);
 
-     return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && (
 
-     // IE8 and below
 
-     fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
 
-   }
 
-   var httpResponseHandler = function httpResponseHandler(callback, decodeResponseBody) {
 
-     if (decodeResponseBody === void 0) {
 
-       decodeResponseBody = false;
 
-     }
 
-     return function (err, response, responseBody) {
 
-       // if the XHR failed, return that error
 
-       if (err) {
 
-         callback(err);
 
-         return;
 
-       } // if the HTTP status code is 4xx or 5xx, the request also failed
 
-       if (response.statusCode >= 400 && response.statusCode <= 599) {
 
-         var cause = responseBody;
 
-         if (decodeResponseBody) {
 
-           if (window_1.TextDecoder) {
 
-             var charset = getCharset(response.headers && response.headers['content-type']);
 
-             try {
 
-               cause = new TextDecoder(charset).decode(responseBody);
 
-             } catch (e) {}
 
-           } else {
 
-             cause = String.fromCharCode.apply(null, new Uint8Array(responseBody));
 
-           }
 
-         }
 
-         callback({
 
-           cause: cause
 
-         });
 
-         return;
 
-       } // otherwise, request succeeded
 
-       callback(null, responseBody);
 
-     };
 
-   };
 
-   function getCharset(contentTypeHeader) {
 
-     if (contentTypeHeader === void 0) {
 
-       contentTypeHeader = '';
 
-     }
 
-     return contentTypeHeader.toLowerCase().split(';').reduce(function (charset, contentType) {
 
-       var _contentType$split = contentType.split('='),
 
-         type = _contentType$split[0],
 
-         value = _contentType$split[1];
 
-       if (type.trim() === 'charset') {
 
-         return value.trim();
 
-       }
 
-       return charset;
 
-     }, 'utf-8');
 
-   }
 
-   var httpHandler = httpResponseHandler;
 
-   createXHR.httpHandler = httpHandler;
 
-   /**
 
-    * @license
 
-    * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
 
-    * Copyright (c) 2014 David Björklund
 
-    * Available under the MIT license
 
-    * <https://github.com/kesla/parse-headers/blob/master/LICENCE>
 
-    */
 
-   var parseHeaders = function parseHeaders(headers) {
 
-     var result = {};
 
-     if (!headers) {
 
-       return result;
 
-     }
 
-     headers.trim().split('\n').forEach(function (row) {
 
-       var index = row.indexOf(':');
 
-       var key = row.slice(0, index).trim().toLowerCase();
 
-       var value = row.slice(index + 1).trim();
 
-       if (typeof result[key] === 'undefined') {
 
-         result[key] = value;
 
-       } else if (Array.isArray(result[key])) {
 
-         result[key].push(value);
 
-       } else {
 
-         result[key] = [result[key], value];
 
-       }
 
-     });
 
-     return result;
 
-   };
 
-   var lib = createXHR; // Allow use of default import syntax in TypeScript
 
-   var default_1 = createXHR;
 
-   createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop$1;
 
-   createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window_1.XDomainRequest;
 
-   forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) {
 
-     createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
 
-       options = initParams(uri, options, callback);
 
-       options.method = method.toUpperCase();
 
-       return _createXHR(options);
 
-     };
 
-   });
 
-   function forEachArray(array, iterator) {
 
-     for (var i = 0; i < array.length; i++) {
 
-       iterator(array[i]);
 
-     }
 
-   }
 
-   function isEmpty(obj) {
 
-     for (var i in obj) {
 
-       if (obj.hasOwnProperty(i)) return false;
 
-     }
 
-     return true;
 
-   }
 
-   function initParams(uri, options, callback) {
 
-     var params = uri;
 
-     if (isFunction_1(options)) {
 
-       callback = options;
 
-       if (typeof uri === "string") {
 
-         params = {
 
-           uri: uri
 
-         };
 
-       }
 
-     } else {
 
-       params = _extends_1({}, options, {
 
-         uri: uri
 
-       });
 
-     }
 
-     params.callback = callback;
 
-     return params;
 
-   }
 
-   function createXHR(uri, options, callback) {
 
-     options = initParams(uri, options, callback);
 
-     return _createXHR(options);
 
-   }
 
-   function _createXHR(options) {
 
-     if (typeof options.callback === "undefined") {
 
-       throw new Error("callback argument missing");
 
-     }
 
-     var called = false;
 
-     var callback = function cbOnce(err, response, body) {
 
-       if (!called) {
 
-         called = true;
 
-         options.callback(err, response, body);
 
-       }
 
-     };
 
-     function readystatechange() {
 
-       if (xhr.readyState === 4) {
 
-         setTimeout(loadFunc, 0);
 
-       }
 
-     }
 
-     function getBody() {
 
-       // Chrome with requestType=blob throws errors arround when even testing access to responseText
 
-       var body = undefined;
 
-       if (xhr.response) {
 
-         body = xhr.response;
 
-       } else {
 
-         body = xhr.responseText || getXml(xhr);
 
-       }
 
-       if (isJson) {
 
-         try {
 
-           body = JSON.parse(body);
 
-         } catch (e) {}
 
-       }
 
-       return body;
 
-     }
 
-     function errorFunc(evt) {
 
-       clearTimeout(timeoutTimer);
 
-       if (!(evt instanceof Error)) {
 
-         evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
 
-       }
 
-       evt.statusCode = 0;
 
-       return callback(evt, failureResponse);
 
-     } // will load the data & process the response in a special response object
 
-     function loadFunc() {
 
-       if (aborted) return;
 
-       var status;
 
-       clearTimeout(timeoutTimer);
 
-       if (options.useXDR && xhr.status === undefined) {
 
-         //IE8 CORS GET successful response doesn't have a status field, but body is fine
 
-         status = 200;
 
-       } else {
 
-         status = xhr.status === 1223 ? 204 : xhr.status;
 
-       }
 
-       var response = failureResponse;
 
-       var err = null;
 
-       if (status !== 0) {
 
-         response = {
 
-           body: getBody(),
 
-           statusCode: status,
 
-           method: method,
 
-           headers: {},
 
-           url: uri,
 
-           rawRequest: xhr
 
-         };
 
-         if (xhr.getAllResponseHeaders) {
 
-           //remember xhr can in fact be XDR for CORS in IE
 
-           response.headers = parseHeaders(xhr.getAllResponseHeaders());
 
-         }
 
-       } else {
 
-         err = new Error("Internal XMLHttpRequest Error");
 
-       }
 
-       return callback(err, response, response.body);
 
-     }
 
-     var xhr = options.xhr || null;
 
-     if (!xhr) {
 
-       if (options.cors || options.useXDR) {
 
-         xhr = new createXHR.XDomainRequest();
 
-       } else {
 
-         xhr = new createXHR.XMLHttpRequest();
 
-       }
 
-     }
 
-     var key;
 
-     var aborted;
 
-     var uri = xhr.url = options.uri || options.url;
 
-     var method = xhr.method = options.method || "GET";
 
-     var body = options.body || options.data;
 
-     var headers = xhr.headers = options.headers || {};
 
-     var sync = !!options.sync;
 
-     var isJson = false;
 
-     var timeoutTimer;
 
-     var failureResponse = {
 
-       body: undefined,
 
-       headers: {},
 
-       statusCode: 0,
 
-       method: method,
 
-       url: uri,
 
-       rawRequest: xhr
 
-     };
 
-     if ("json" in options && options.json !== false) {
 
-       isJson = true;
 
-       headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
 
-       if (method !== "GET" && method !== "HEAD") {
 
-         headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
 
-         body = JSON.stringify(options.json === true ? body : options.json);
 
-       }
 
-     }
 
-     xhr.onreadystatechange = readystatechange;
 
-     xhr.onload = loadFunc;
 
-     xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
 
-     xhr.onprogress = function () {// IE must die
 
-     };
 
-     xhr.onabort = function () {
 
-       aborted = true;
 
-     };
 
-     xhr.ontimeout = errorFunc;
 
-     xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
 
-     if (!sync) {
 
-       xhr.withCredentials = !!options.withCredentials;
 
-     } // Cannot set timeout with sync request
 
-     // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
 
-     // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
 
-     if (!sync && options.timeout > 0) {
 
-       timeoutTimer = setTimeout(function () {
 
-         if (aborted) return;
 
-         aborted = true; //IE9 may still call readystatechange
 
-         xhr.abort("timeout");
 
-         var e = new Error("XMLHttpRequest timeout");
 
-         e.code = "ETIMEDOUT";
 
-         errorFunc(e);
 
-       }, options.timeout);
 
-     }
 
-     if (xhr.setRequestHeader) {
 
-       for (key in headers) {
 
-         if (headers.hasOwnProperty(key)) {
 
-           xhr.setRequestHeader(key, headers[key]);
 
-         }
 
-       }
 
-     } else if (options.headers && !isEmpty(options.headers)) {
 
-       throw new Error("Headers cannot be set on an XDomainRequest object");
 
-     }
 
-     if ("responseType" in options) {
 
-       xhr.responseType = options.responseType;
 
-     }
 
-     if ("beforeSend" in options && typeof options.beforeSend === "function") {
 
-       options.beforeSend(xhr);
 
-     } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
 
-     // XMLHttpRequest spec says to pass null as body to indicate no body
 
-     // See https://github.com/naugtur/xhr/issues/100.
 
-     xhr.send(body || null);
 
-     return xhr;
 
-   }
 
-   function getXml(xhr) {
 
-     // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException"
 
-     // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML.
 
-     try {
 
-       if (xhr.responseType === "document") {
 
-         return xhr.responseXML;
 
-       }
 
-       var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
 
-       if (xhr.responseType === "" && !firefoxBugTakenEffect) {
 
-         return xhr.responseXML;
 
-       }
 
-     } catch (e) {}
 
-     return null;
 
-   }
 
-   function noop$1() {}
 
-   lib.default = default_1;
 
-   /**
 
-    * @file text-track.js
 
-    */
 
-   /**
 
-    * Takes a webvtt file contents and parses it into cues
 
-    *
 
-    * @param {string} srcContent
 
-    *        webVTT file contents
 
-    *
 
-    * @param {TextTrack} track
 
-    *        TextTrack to add cues to. Cues come from the srcContent.
 
-    *
 
-    * @private
 
-    */
 
-   const parseCues = function (srcContent, track) {
 
-     const parser = new window.WebVTT.Parser(window, window.vttjs, window.WebVTT.StringDecoder());
 
-     const errors = [];
 
-     parser.oncue = function (cue) {
 
-       track.addCue(cue);
 
-     };
 
-     parser.onparsingerror = function (error) {
 
-       errors.push(error);
 
-     };
 
-     parser.onflush = function () {
 
-       track.trigger({
 
-         type: 'loadeddata',
 
-         target: track
 
-       });
 
-     };
 
-     parser.parse(srcContent);
 
-     if (errors.length > 0) {
 
-       if (window.console && window.console.groupCollapsed) {
 
-         window.console.groupCollapsed(`Text Track parsing errors for ${track.src}`);
 
-       }
 
-       errors.forEach(error => log$1.error(error));
 
-       if (window.console && window.console.groupEnd) {
 
-         window.console.groupEnd();
 
-       }
 
-     }
 
-     parser.flush();
 
-   };
 
-   /**
 
-    * Load a `TextTrack` from a specified url.
 
-    *
 
-    * @param {string} src
 
-    *        Url to load track from.
 
-    *
 
-    * @param {TextTrack} track
 
-    *        Track to add cues to. Comes from the content at the end of `url`.
 
-    *
 
-    * @private
 
-    */
 
-   const loadTrack = function (src, track) {
 
-     const opts = {
 
-       uri: src
 
-     };
 
-     const crossOrigin = isCrossOrigin(src);
 
-     if (crossOrigin) {
 
-       opts.cors = crossOrigin;
 
-     }
 
-     const withCredentials = track.tech_.crossOrigin() === 'use-credentials';
 
-     if (withCredentials) {
 
-       opts.withCredentials = withCredentials;
 
-     }
 
-     lib(opts, bind_(this, function (err, response, responseBody) {
 
-       if (err) {
 
-         return log$1.error(err, response);
 
-       }
 
-       track.loaded_ = true;
 
-       // Make sure that vttjs has loaded, otherwise, wait till it finished loading
 
-       // NOTE: this is only used for the alt/video.novtt.js build
 
-       if (typeof window.WebVTT !== 'function') {
 
-         if (track.tech_) {
 
-           // to prevent use before define eslint error, we define loadHandler
 
-           // as a let here
 
-           track.tech_.any(['vttjsloaded', 'vttjserror'], event => {
 
-             if (event.type === 'vttjserror') {
 
-               log$1.error(`vttjs failed to load, stopping trying to process ${track.src}`);
 
-               return;
 
-             }
 
-             return parseCues(responseBody, track);
 
-           });
 
-         }
 
-       } else {
 
-         parseCues(responseBody, track);
 
-       }
 
-     }));
 
-   };
 
-   /**
 
-    * A representation of a single `TextTrack`.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
 
-    * @extends Track
 
-    */
 
-   class TextTrack extends Track {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param {Object} options={}
 
-      *        Object of option names and values
 
-      *
 
-      * @param { import('../tech/tech').default } options.tech
 
-      *        A reference to the tech that owns this TextTrack.
 
-      *
 
-      * @param {TextTrack~Kind} [options.kind='subtitles']
 
-      *        A valid text track kind.
 
-      *
 
-      * @param {TextTrack~Mode} [options.mode='disabled']
 
-      *        A valid text track mode.
 
-      *
 
-      * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
 
-      *        A unique id for this TextTrack.
 
-      *
 
-      * @param {string} [options.label='']
 
-      *        The menu label for this track.
 
-      *
 
-      * @param {string} [options.language='']
 
-      *        A valid two character language code.
 
-      *
 
-      * @param {string} [options.srclang='']
 
-      *        A valid two character language code. An alternative, but deprioritized
 
-      *        version of `options.language`
 
-      *
 
-      * @param {string} [options.src]
 
-      *        A url to TextTrack cues.
 
-      *
 
-      * @param {boolean} [options.default]
 
-      *        If this track should default to on or off.
 
-      */
 
-     constructor(options = {}) {
 
-       if (!options.tech) {
 
-         throw new Error('A tech was not provided.');
 
-       }
 
-       const settings = merge$2(options, {
 
-         kind: TextTrackKind[options.kind] || 'subtitles',
 
-         language: options.language || options.srclang || ''
 
-       });
 
-       let mode = TextTrackMode[settings.mode] || 'disabled';
 
-       const default_ = settings.default;
 
-       if (settings.kind === 'metadata' || settings.kind === 'chapters') {
 
-         mode = 'hidden';
 
-       }
 
-       super(settings);
 
-       this.tech_ = settings.tech;
 
-       this.cues_ = [];
 
-       this.activeCues_ = [];
 
-       this.preload_ = this.tech_.preloadTextTracks !== false;
 
-       const cues = new TextTrackCueList(this.cues_);
 
-       const activeCues = new TextTrackCueList(this.activeCues_);
 
-       let changed = false;
 
-       this.timeupdateHandler = bind_(this, function (event = {}) {
 
-         if (this.tech_.isDisposed()) {
 
-           return;
 
-         }
 
-         if (!this.tech_.isReady_) {
 
-           if (event.type !== 'timeupdate') {
 
-             this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
 
-           }
 
-           return;
 
-         }
 
-         // Accessing this.activeCues for the side-effects of updating itself
 
-         // due to its nature as a getter function. Do not remove or cues will
 
-         // stop updating!
 
-         // Use the setter to prevent deletion from uglify (pure_getters rule)
 
-         this.activeCues = this.activeCues;
 
-         if (changed) {
 
-           this.trigger('cuechange');
 
-           changed = false;
 
-         }
 
-         if (event.type !== 'timeupdate') {
 
-           this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
 
-         }
 
-       });
 
-       const disposeHandler = () => {
 
-         this.stopTracking();
 
-       };
 
-       this.tech_.one('dispose', disposeHandler);
 
-       if (mode !== 'disabled') {
 
-         this.startTracking();
 
-       }
 
-       Object.defineProperties(this, {
 
-         /**
 
-          * @memberof TextTrack
 
-          * @member {boolean} default
 
-          *         If this track was set to be on or off by default. Cannot be changed after
 
-          *         creation.
 
-          * @instance
 
-          *
 
-          * @readonly
 
-          */
 
-         default: {
 
-           get() {
 
-             return default_;
 
-           },
 
-           set() {}
 
-         },
 
-         /**
 
-          * @memberof TextTrack
 
-          * @member {string} mode
 
-          *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
 
-          *         not be set if setting to an invalid mode.
 
-          * @instance
 
-          *
 
-          * @fires TextTrack#modechange
 
-          */
 
-         mode: {
 
-           get() {
 
-             return mode;
 
-           },
 
-           set(newMode) {
 
-             if (!TextTrackMode[newMode]) {
 
-               return;
 
-             }
 
-             if (mode === newMode) {
 
-               return;
 
-             }
 
-             mode = newMode;
 
-             if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
 
-               // On-demand load.
 
-               loadTrack(this.src, this);
 
-             }
 
-             this.stopTracking();
 
-             if (mode !== 'disabled') {
 
-               this.startTracking();
 
-             }
 
-             /**
 
-              * An event that fires when mode changes on this track. This allows
 
-              * the TextTrackList that holds this track to act accordingly.
 
-              *
 
-              * > Note: This is not part of the spec!
 
-              *
 
-              * @event TextTrack#modechange
 
-              * @type {Event}
 
-              */
 
-             this.trigger('modechange');
 
-           }
 
-         },
 
-         /**
 
-          * @memberof TextTrack
 
-          * @member {TextTrackCueList} cues
 
-          *         The text track cue list for this TextTrack.
 
-          * @instance
 
-          */
 
-         cues: {
 
-           get() {
 
-             if (!this.loaded_) {
 
-               return null;
 
-             }
 
-             return cues;
 
-           },
 
-           set() {}
 
-         },
 
-         /**
 
-          * @memberof TextTrack
 
-          * @member {TextTrackCueList} activeCues
 
-          *         The list text track cues that are currently active for this TextTrack.
 
-          * @instance
 
-          */
 
-         activeCues: {
 
-           get() {
 
-             if (!this.loaded_) {
 
-               return null;
 
-             }
 
-             // nothing to do
 
-             if (this.cues.length === 0) {
 
-               return activeCues;
 
-             }
 
-             const ct = this.tech_.currentTime();
 
-             const active = [];
 
-             for (let i = 0, l = this.cues.length; i < l; i++) {
 
-               const cue = this.cues[i];
 
-               if (cue.startTime <= ct && cue.endTime >= ct) {
 
-                 active.push(cue);
 
-               }
 
-             }
 
-             changed = false;
 
-             if (active.length !== this.activeCues_.length) {
 
-               changed = true;
 
-             } else {
 
-               for (let i = 0; i < active.length; i++) {
 
-                 if (this.activeCues_.indexOf(active[i]) === -1) {
 
-                   changed = true;
 
-                 }
 
-               }
 
-             }
 
-             this.activeCues_ = active;
 
-             activeCues.setCues_(this.activeCues_);
 
-             return activeCues;
 
-           },
 
-           // /!\ Keep this setter empty (see the timeupdate handler above)
 
-           set() {}
 
-         }
 
-       });
 
-       if (settings.src) {
 
-         this.src = settings.src;
 
-         if (!this.preload_) {
 
-           // Tracks will load on-demand.
 
-           // Act like we're loaded for other purposes.
 
-           this.loaded_ = true;
 
-         }
 
-         if (this.preload_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
 
-           loadTrack(this.src, this);
 
-         }
 
-       } else {
 
-         this.loaded_ = true;
 
-       }
 
-     }
 
-     startTracking() {
 
-       // More precise cues based on requestVideoFrameCallback with a requestAnimationFram fallback
 
-       this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
 
-       // Also listen to timeupdate in case rVFC/rAF stops (window in background, audio in video el)
 
-       this.tech_.on('timeupdate', this.timeupdateHandler);
 
-     }
 
-     stopTracking() {
 
-       if (this.rvf_) {
 
-         this.tech_.cancelVideoFrameCallback(this.rvf_);
 
-         this.rvf_ = undefined;
 
-       }
 
-       this.tech_.off('timeupdate', this.timeupdateHandler);
 
-     }
 
-     /**
 
-      * Add a cue to the internal list of cues.
 
-      *
 
-      * @param {TextTrack~Cue} cue
 
-      *        The cue to add to our internal list
 
-      */
 
-     addCue(originalCue) {
 
-       let cue = originalCue;
 
-       if (window.vttjs && !(originalCue instanceof window.vttjs.VTTCue)) {
 
-         cue = new window.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
 
-         for (const prop in originalCue) {
 
-           if (!(prop in cue)) {
 
-             cue[prop] = originalCue[prop];
 
-           }
 
-         }
 
-         // make sure that `id` is copied over
 
-         cue.id = originalCue.id;
 
-         cue.originalCue_ = originalCue;
 
-       }
 
-       const tracks = this.tech_.textTracks();
 
-       for (let i = 0; i < tracks.length; i++) {
 
-         if (tracks[i] !== this) {
 
-           tracks[i].removeCue(cue);
 
-         }
 
-       }
 
-       this.cues_.push(cue);
 
-       this.cues.setCues_(this.cues_);
 
-     }
 
-     /**
 
-      * Remove a cue from our internal list
 
-      *
 
-      * @param {TextTrack~Cue} removeCue
 
-      *        The cue to remove from our internal list
 
-      */
 
-     removeCue(removeCue) {
 
-       let i = this.cues_.length;
 
-       while (i--) {
 
-         const cue = this.cues_[i];
 
-         if (cue === removeCue || cue.originalCue_ && cue.originalCue_ === removeCue) {
 
-           this.cues_.splice(i, 1);
 
-           this.cues.setCues_(this.cues_);
 
-           break;
 
-         }
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * cuechange - One or more cues in the track have become active or stopped being active.
 
-    */
 
-   TextTrack.prototype.allowedEvents_ = {
 
-     cuechange: 'cuechange'
 
-   };
 
-   /**
 
-    * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
 
-    * only one `AudioTrack` in the list will be enabled at a time.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
 
-    * @extends Track
 
-    */
 
-   class AudioTrack extends Track {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        Object of option names and values
 
-      *
 
-      * @param {AudioTrack~Kind} [options.kind='']
 
-      *        A valid audio track kind
 
-      *
 
-      * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
 
-      *        A unique id for this AudioTrack.
 
-      *
 
-      * @param {string} [options.label='']
 
-      *        The menu label for this track.
 
-      *
 
-      * @param {string} [options.language='']
 
-      *        A valid two character language code.
 
-      *
 
-      * @param {boolean} [options.enabled]
 
-      *        If this track is the one that is currently playing. If this track is part of
 
-      *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
 
-      */
 
-     constructor(options = {}) {
 
-       const settings = merge$2(options, {
 
-         kind: AudioTrackKind[options.kind] || ''
 
-       });
 
-       super(settings);
 
-       let enabled = false;
 
-       /**
 
-        * @memberof AudioTrack
 
-        * @member {boolean} enabled
 
-        *         If this `AudioTrack` is enabled or not. When setting this will
 
-        *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
 
-        * @instance
 
-        *
 
-        * @fires VideoTrack#selectedchange
 
-        */
 
-       Object.defineProperty(this, 'enabled', {
 
-         get() {
 
-           return enabled;
 
-         },
 
-         set(newEnabled) {
 
-           // an invalid or unchanged value
 
-           if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
 
-             return;
 
-           }
 
-           enabled = newEnabled;
 
-           /**
 
-            * An event that fires when enabled changes on this track. This allows
 
-            * the AudioTrackList that holds this track to act accordingly.
 
-            *
 
-            * > Note: This is not part of the spec! Native tracks will do
 
-            *         this internally without an event.
 
-            *
 
-            * @event AudioTrack#enabledchange
 
-            * @type {Event}
 
-            */
 
-           this.trigger('enabledchange');
 
-         }
 
-       });
 
-       // if the user sets this track to selected then
 
-       // set selected to that true value otherwise
 
-       // we keep it false
 
-       if (settings.enabled) {
 
-         this.enabled = settings.enabled;
 
-       }
 
-       this.loaded_ = true;
 
-     }
 
-   }
 
-   /**
 
-    * A representation of a single `VideoTrack`.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
 
-    * @extends Track
 
-    */
 
-   class VideoTrack extends Track {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        Object of option names and values
 
-      *
 
-      * @param {string} [options.kind='']
 
-      *        A valid {@link VideoTrack~Kind}
 
-      *
 
-      * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
 
-      *        A unique id for this AudioTrack.
 
-      *
 
-      * @param {string} [options.label='']
 
-      *        The menu label for this track.
 
-      *
 
-      * @param {string} [options.language='']
 
-      *        A valid two character language code.
 
-      *
 
-      * @param {boolean} [options.selected]
 
-      *        If this track is the one that is currently playing.
 
-      */
 
-     constructor(options = {}) {
 
-       const settings = merge$2(options, {
 
-         kind: VideoTrackKind[options.kind] || ''
 
-       });
 
-       super(settings);
 
-       let selected = false;
 
-       /**
 
-        * @memberof VideoTrack
 
-        * @member {boolean} selected
 
-        *         If this `VideoTrack` is selected or not. When setting this will
 
-        *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
 
-        * @instance
 
-        *
 
-        * @fires VideoTrack#selectedchange
 
-        */
 
-       Object.defineProperty(this, 'selected', {
 
-         get() {
 
-           return selected;
 
-         },
 
-         set(newSelected) {
 
-           // an invalid or unchanged value
 
-           if (typeof newSelected !== 'boolean' || newSelected === selected) {
 
-             return;
 
-           }
 
-           selected = newSelected;
 
-           /**
 
-            * An event that fires when selected changes on this track. This allows
 
-            * the VideoTrackList that holds this track to act accordingly.
 
-            *
 
-            * > Note: This is not part of the spec! Native tracks will do
 
-            *         this internally without an event.
 
-            *
 
-            * @event VideoTrack#selectedchange
 
-            * @type {Event}
 
-            */
 
-           this.trigger('selectedchange');
 
-         }
 
-       });
 
-       // if the user sets this track to selected then
 
-       // set selected to that true value otherwise
 
-       // we keep it false
 
-       if (settings.selected) {
 
-         this.selected = settings.selected;
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * @file html-track-element.js
 
-    */
 
-   /**
 
-    * A single track represented in the DOM.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
 
-    * @extends EventTarget
 
-    */
 
-   class HTMLTrackElement extends EventTarget$2 {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param {Object} options={}
 
-      *        Object of option names and values
 
-      *
 
-      * @param { import('../tech/tech').default } options.tech
 
-      *        A reference to the tech that owns this HTMLTrackElement.
 
-      *
 
-      * @param {TextTrack~Kind} [options.kind='subtitles']
 
-      *        A valid text track kind.
 
-      *
 
-      * @param {TextTrack~Mode} [options.mode='disabled']
 
-      *        A valid text track mode.
 
-      *
 
-      * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
 
-      *        A unique id for this TextTrack.
 
-      *
 
-      * @param {string} [options.label='']
 
-      *        The menu label for this track.
 
-      *
 
-      * @param {string} [options.language='']
 
-      *        A valid two character language code.
 
-      *
 
-      * @param {string} [options.srclang='']
 
-      *        A valid two character language code. An alternative, but deprioritized
 
-      *        version of `options.language`
 
-      *
 
-      * @param {string} [options.src]
 
-      *        A url to TextTrack cues.
 
-      *
 
-      * @param {boolean} [options.default]
 
-      *        If this track should default to on or off.
 
-      */
 
-     constructor(options = {}) {
 
-       super();
 
-       let readyState;
 
-       const track = new TextTrack(options);
 
-       this.kind = track.kind;
 
-       this.src = track.src;
 
-       this.srclang = track.language;
 
-       this.label = track.label;
 
-       this.default = track.default;
 
-       Object.defineProperties(this, {
 
-         /**
 
-          * @memberof HTMLTrackElement
 
-          * @member {HTMLTrackElement~ReadyState} readyState
 
-          *         The current ready state of the track element.
 
-          * @instance
 
-          */
 
-         readyState: {
 
-           get() {
 
-             return readyState;
 
-           }
 
-         },
 
-         /**
 
-          * @memberof HTMLTrackElement
 
-          * @member {TextTrack} track
 
-          *         The underlying TextTrack object.
 
-          * @instance
 
-          *
 
-          */
 
-         track: {
 
-           get() {
 
-             return track;
 
-           }
 
-         }
 
-       });
 
-       readyState = HTMLTrackElement.NONE;
 
-       /**
 
-        * @listens TextTrack#loadeddata
 
-        * @fires HTMLTrackElement#load
 
-        */
 
-       track.addEventListener('loadeddata', () => {
 
-         readyState = HTMLTrackElement.LOADED;
 
-         this.trigger({
 
-           type: 'load',
 
-           target: this
 
-         });
 
-       });
 
-     }
 
-   }
 
-   HTMLTrackElement.prototype.allowedEvents_ = {
 
-     load: 'load'
 
-   };
 
-   /**
 
-    * The text track not loaded state.
 
-    *
 
-    * @type {number}
 
-    * @static
 
-    */
 
-   HTMLTrackElement.NONE = 0;
 
-   /**
 
-    * The text track loading state.
 
-    *
 
-    * @type {number}
 
-    * @static
 
-    */
 
-   HTMLTrackElement.LOADING = 1;
 
-   /**
 
-    * The text track loaded state.
 
-    *
 
-    * @type {number}
 
-    * @static
 
-    */
 
-   HTMLTrackElement.LOADED = 2;
 
-   /**
 
-    * The text track failed to load state.
 
-    *
 
-    * @type {number}
 
-    * @static
 
-    */
 
-   HTMLTrackElement.ERROR = 3;
 
-   /*
 
-    * This file contains all track properties that are used in
 
-    * player.js, tech.js, html5.js and possibly other techs in the future.
 
-    */
 
-   const NORMAL = {
 
-     audio: {
 
-       ListClass: AudioTrackList,
 
-       TrackClass: AudioTrack,
 
-       capitalName: 'Audio'
 
-     },
 
-     video: {
 
-       ListClass: VideoTrackList,
 
-       TrackClass: VideoTrack,
 
-       capitalName: 'Video'
 
-     },
 
-     text: {
 
-       ListClass: TextTrackList,
 
-       TrackClass: TextTrack,
 
-       capitalName: 'Text'
 
-     }
 
-   };
 
-   Object.keys(NORMAL).forEach(function (type) {
 
-     NORMAL[type].getterName = `${type}Tracks`;
 
-     NORMAL[type].privateName = `${type}Tracks_`;
 
-   });
 
-   const REMOTE = {
 
-     remoteText: {
 
-       ListClass: TextTrackList,
 
-       TrackClass: TextTrack,
 
-       capitalName: 'RemoteText',
 
-       getterName: 'remoteTextTracks',
 
-       privateName: 'remoteTextTracks_'
 
-     },
 
-     remoteTextEl: {
 
-       ListClass: HtmlTrackElementList,
 
-       TrackClass: HTMLTrackElement,
 
-       capitalName: 'RemoteTextTrackEls',
 
-       getterName: 'remoteTextTrackEls',
 
-       privateName: 'remoteTextTrackEls_'
 
-     }
 
-   };
 
-   const ALL = Object.assign({}, NORMAL, REMOTE);
 
-   REMOTE.names = Object.keys(REMOTE);
 
-   NORMAL.names = Object.keys(NORMAL);
 
-   ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
 
-   var minDoc = {};
 
-   var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof window !== 'undefined' ? window : {};
 
-   var doccy;
 
-   if (typeof document !== 'undefined') {
 
-     doccy = document;
 
-   } else {
 
-     doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
 
-     if (!doccy) {
 
-       doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
 
-     }
 
-   }
 
-   var document_1 = doccy;
 
-   /**
 
-    * Copyright 2013 vtt.js Contributors
 
-    *
 
-    * Licensed under the Apache License, Version 2.0 (the "License");
 
-    * you may not use this file except in compliance with the License.
 
-    * You may obtain a copy of the License at
 
-    *
 
-    *   http://www.apache.org/licenses/LICENSE-2.0
 
-    *
 
-    * Unless required by applicable law or agreed to in writing, software
 
-    * distributed under the License is distributed on an "AS IS" BASIS,
 
-    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
-    * See the License for the specific language governing permissions and
 
-    * limitations under the License.
 
-    */
 
-   /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 
-   /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
-   var _objCreate = Object.create || function () {
 
-     function F() {}
 
-     return function (o) {
 
-       if (arguments.length !== 1) {
 
-         throw new Error('Object.create shim only accepts one parameter.');
 
-       }
 
-       F.prototype = o;
 
-       return new F();
 
-     };
 
-   }();
 
-   // Creates a new ParserError object from an errorData object. The errorData
 
-   // object should have default code and message properties. The default message
 
-   // property can be overriden by passing in a message parameter.
 
-   // See ParsingError.Errors below for acceptable errors.
 
-   function ParsingError(errorData, message) {
 
-     this.name = "ParsingError";
 
-     this.code = errorData.code;
 
-     this.message = message || errorData.message;
 
-   }
 
-   ParsingError.prototype = _objCreate(Error.prototype);
 
-   ParsingError.prototype.constructor = ParsingError;
 
-   // ParsingError metadata for acceptable ParsingErrors.
 
-   ParsingError.Errors = {
 
-     BadSignature: {
 
-       code: 0,
 
-       message: "Malformed WebVTT signature."
 
-     },
 
-     BadTimeStamp: {
 
-       code: 1,
 
-       message: "Malformed time stamp."
 
-     }
 
-   };
 
-   // Try to parse input as a time stamp.
 
-   function parseTimeStamp(input) {
 
-     function computeSeconds(h, m, s, f) {
 
-       return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
 
-     }
 
-     var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/);
 
-     if (!m) {
 
-       return null;
 
-     }
 
-     if (m[3]) {
 
-       // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
 
-       return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
 
-     } else if (m[1] > 59) {
 
-       // Timestamp takes the form of [hours]:[minutes].[milliseconds]
 
-       // First position is hours as it's over 59.
 
-       return computeSeconds(m[1], m[2], 0, m[4]);
 
-     } else {
 
-       // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
 
-       return computeSeconds(0, m[1], m[2], m[4]);
 
-     }
 
-   }
 
-   // A settings object holds key/value pairs and will ignore anything but the first
 
-   // assignment to a specific key.
 
-   function Settings() {
 
-     this.values = _objCreate(null);
 
-   }
 
-   Settings.prototype = {
 
-     // Only accept the first assignment to any key.
 
-     set: function (k, v) {
 
-       if (!this.get(k) && v !== "") {
 
-         this.values[k] = v;
 
-       }
 
-     },
 
-     // Return the value for a key, or a default value.
 
-     // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
 
-     // a number of possible default values as properties where 'defaultKey' is
 
-     // the key of the property that will be chosen; otherwise it's assumed to be
 
-     // a single value.
 
-     get: function (k, dflt, defaultKey) {
 
-       if (defaultKey) {
 
-         return this.has(k) ? this.values[k] : dflt[defaultKey];
 
-       }
 
-       return this.has(k) ? this.values[k] : dflt;
 
-     },
 
-     // Check whether we have a value for a key.
 
-     has: function (k) {
 
-       return k in this.values;
 
-     },
 
-     // Accept a setting if its one of the given alternatives.
 
-     alt: function (k, v, a) {
 
-       for (var n = 0; n < a.length; ++n) {
 
-         if (v === a[n]) {
 
-           this.set(k, v);
 
-           break;
 
-         }
 
-       }
 
-     },
 
-     // Accept a setting if its a valid (signed) integer.
 
-     integer: function (k, v) {
 
-       if (/^-?\d+$/.test(v)) {
 
-         // integer
 
-         this.set(k, parseInt(v, 10));
 
-       }
 
-     },
 
-     // Accept a setting if its a valid percentage.
 
-     percent: function (k, v) {
 
-       if (v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
 
-         v = parseFloat(v);
 
-         if (v >= 0 && v <= 100) {
 
-           this.set(k, v);
 
-           return true;
 
-         }
 
-       }
 
-       return false;
 
-     }
 
-   };
 
-   // Helper function to parse input into groups separated by 'groupDelim', and
 
-   // interprete each group as a key/value pair separated by 'keyValueDelim'.
 
-   function parseOptions(input, callback, keyValueDelim, groupDelim) {
 
-     var groups = groupDelim ? input.split(groupDelim) : [input];
 
-     for (var i in groups) {
 
-       if (typeof groups[i] !== "string") {
 
-         continue;
 
-       }
 
-       var kv = groups[i].split(keyValueDelim);
 
-       if (kv.length !== 2) {
 
-         continue;
 
-       }
 
-       var k = kv[0].trim();
 
-       var v = kv[1].trim();
 
-       callback(k, v);
 
-     }
 
-   }
 
-   function parseCue(input, cue, regionList) {
 
-     // Remember the original input if we need to throw an error.
 
-     var oInput = input;
 
-     // 4.1 WebVTT timestamp
 
-     function consumeTimeStamp() {
 
-       var ts = parseTimeStamp(input);
 
-       if (ts === null) {
 
-         throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
 
-       }
 
-       // Remove time stamp from input.
 
-       input = input.replace(/^[^\sa-zA-Z-]+/, "");
 
-       return ts;
 
-     }
 
-     // 4.4.2 WebVTT cue settings
 
-     function consumeCueSettings(input, cue) {
 
-       var settings = new Settings();
 
-       parseOptions(input, function (k, v) {
 
-         switch (k) {
 
-           case "region":
 
-             // Find the last region we parsed with the same region id.
 
-             for (var i = regionList.length - 1; i >= 0; i--) {
 
-               if (regionList[i].id === v) {
 
-                 settings.set(k, regionList[i].region);
 
-                 break;
 
-               }
 
-             }
 
-             break;
 
-           case "vertical":
 
-             settings.alt(k, v, ["rl", "lr"]);
 
-             break;
 
-           case "line":
 
-             var vals = v.split(","),
 
-               vals0 = vals[0];
 
-             settings.integer(k, vals0);
 
-             settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
 
-             settings.alt(k, vals0, ["auto"]);
 
-             if (vals.length === 2) {
 
-               settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
 
-             }
 
-             break;
 
-           case "position":
 
-             vals = v.split(",");
 
-             settings.percent(k, vals[0]);
 
-             if (vals.length === 2) {
 
-               settings.alt("positionAlign", vals[1], ["start", "center", "end"]);
 
-             }
 
-             break;
 
-           case "size":
 
-             settings.percent(k, v);
 
-             break;
 
-           case "align":
 
-             settings.alt(k, v, ["start", "center", "end", "left", "right"]);
 
-             break;
 
-         }
 
-       }, /:/, /\s/);
 
-       // Apply default values for any missing fields.
 
-       cue.region = settings.get("region", null);
 
-       cue.vertical = settings.get("vertical", "");
 
-       try {
 
-         cue.line = settings.get("line", "auto");
 
-       } catch (e) {}
 
-       cue.lineAlign = settings.get("lineAlign", "start");
 
-       cue.snapToLines = settings.get("snapToLines", true);
 
-       cue.size = settings.get("size", 100);
 
-       // Safari still uses the old middle value and won't accept center
 
-       try {
 
-         cue.align = settings.get("align", "center");
 
-       } catch (e) {
 
-         cue.align = settings.get("align", "middle");
 
-       }
 
-       try {
 
-         cue.position = settings.get("position", "auto");
 
-       } catch (e) {
 
-         cue.position = settings.get("position", {
 
-           start: 0,
 
-           left: 0,
 
-           center: 50,
 
-           middle: 50,
 
-           end: 100,
 
-           right: 100
 
-         }, cue.align);
 
-       }
 
-       cue.positionAlign = settings.get("positionAlign", {
 
-         start: "start",
 
-         left: "start",
 
-         center: "center",
 
-         middle: "center",
 
-         end: "end",
 
-         right: "end"
 
-       }, cue.align);
 
-     }
 
-     function skipWhitespace() {
 
-       input = input.replace(/^\s+/, "");
 
-     }
 
-     // 4.1 WebVTT cue timings.
 
-     skipWhitespace();
 
-     cue.startTime = consumeTimeStamp(); // (1) collect cue start time
 
-     skipWhitespace();
 
-     if (input.substr(0, 3) !== "-->") {
 
-       // (3) next characters must match "-->"
 
-       throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
 
-     }
 
-     input = input.substr(3);
 
-     skipWhitespace();
 
-     cue.endTime = consumeTimeStamp(); // (5) collect cue end time
 
-     // 4.1 WebVTT cue settings list.
 
-     skipWhitespace();
 
-     consumeCueSettings(input, cue);
 
-   }
 
-   // When evaluating this file as part of a Webpack bundle for server
 
-   // side rendering, `document` is an empty object.
 
-   var TEXTAREA_ELEMENT = document_1.createElement && document_1.createElement("textarea");
 
-   var TAG_NAME = {
 
-     c: "span",
 
-     i: "i",
 
-     b: "b",
 
-     u: "u",
 
-     ruby: "ruby",
 
-     rt: "rt",
 
-     v: "span",
 
-     lang: "span"
 
-   };
 
-   // 5.1 default text color
 
-   // 5.2 default text background color is equivalent to text color with bg_ prefix
 
-   var DEFAULT_COLOR_CLASS = {
 
-     white: 'rgba(255,255,255,1)',
 
-     lime: 'rgba(0,255,0,1)',
 
-     cyan: 'rgba(0,255,255,1)',
 
-     red: 'rgba(255,0,0,1)',
 
-     yellow: 'rgba(255,255,0,1)',
 
-     magenta: 'rgba(255,0,255,1)',
 
-     blue: 'rgba(0,0,255,1)',
 
-     black: 'rgba(0,0,0,1)'
 
-   };
 
-   var TAG_ANNOTATION = {
 
-     v: "title",
 
-     lang: "lang"
 
-   };
 
-   var NEEDS_PARENT = {
 
-     rt: "ruby"
 
-   };
 
-   // Parse content into a document fragment.
 
-   function parseContent(window, input) {
 
-     function nextToken() {
 
-       // Check for end-of-string.
 
-       if (!input) {
 
-         return null;
 
-       }
 
-       // Consume 'n' characters from the input.
 
-       function consume(result) {
 
-         input = input.substr(result.length);
 
-         return result;
 
-       }
 
-       var m = input.match(/^([^<]*)(<[^>]*>?)?/);
 
-       // If there is some text before the next tag, return it, otherwise return
 
-       // the tag.
 
-       return consume(m[1] ? m[1] : m[2]);
 
-     }
 
-     function unescape(s) {
 
-       TEXTAREA_ELEMENT.innerHTML = s;
 
-       s = TEXTAREA_ELEMENT.textContent;
 
-       TEXTAREA_ELEMENT.textContent = "";
 
-       return s;
 
-     }
 
-     function shouldAdd(current, element) {
 
-       return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
 
-     }
 
-     // Create an element for this tag.
 
-     function createElement(type, annotation) {
 
-       var tagName = TAG_NAME[type];
 
-       if (!tagName) {
 
-         return null;
 
-       }
 
-       var element = window.document.createElement(tagName);
 
-       var name = TAG_ANNOTATION[type];
 
-       if (name && annotation) {
 
-         element[name] = annotation.trim();
 
-       }
 
-       return element;
 
-     }
 
-     var rootDiv = window.document.createElement("div"),
 
-       current = rootDiv,
 
-       t,
 
-       tagStack = [];
 
-     while ((t = nextToken()) !== null) {
 
-       if (t[0] === '<') {
 
-         if (t[1] === "/") {
 
-           // If the closing tag matches, move back up to the parent node.
 
-           if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
 
-             tagStack.pop();
 
-             current = current.parentNode;
 
-           }
 
-           // Otherwise just ignore the end tag.
 
-           continue;
 
-         }
 
-         var ts = parseTimeStamp(t.substr(1, t.length - 2));
 
-         var node;
 
-         if (ts) {
 
-           // Timestamps are lead nodes as well.
 
-           node = window.document.createProcessingInstruction("timestamp", ts);
 
-           current.appendChild(node);
 
-           continue;
 
-         }
 
-         var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
 
-         // If we can't parse the tag, skip to the next tag.
 
-         if (!m) {
 
-           continue;
 
-         }
 
-         // Try to construct an element, and ignore the tag if we couldn't.
 
-         node = createElement(m[1], m[3]);
 
-         if (!node) {
 
-           continue;
 
-         }
 
-         // Determine if the tag should be added based on the context of where it
 
-         // is placed in the cuetext.
 
-         if (!shouldAdd(current, node)) {
 
-           continue;
 
-         }
 
-         // Set the class list (as a list of classes, separated by space).
 
-         if (m[2]) {
 
-           var classes = m[2].split('.');
 
-           classes.forEach(function (cl) {
 
-             var bgColor = /^bg_/.test(cl);
 
-             // slice out `bg_` if it's a background color
 
-             var colorName = bgColor ? cl.slice(3) : cl;
 
-             if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) {
 
-               var propName = bgColor ? 'background-color' : 'color';
 
-               var propValue = DEFAULT_COLOR_CLASS[colorName];
 
-               node.style[propName] = propValue;
 
-             }
 
-           });
 
-           node.className = classes.join(' ');
 
-         }
 
-         // Append the node to the current node, and enter the scope of the new
 
-         // node.
 
-         tagStack.push(m[1]);
 
-         current.appendChild(node);
 
-         current = node;
 
-         continue;
 
-       }
 
-       // Text nodes are leaf nodes.
 
-       current.appendChild(window.document.createTextNode(unescape(t)));
 
-     }
 
-     return rootDiv;
 
-   }
 
-   // This is a list of all the Unicode characters that have a strong
 
-   // right-to-left category. What this means is that these characters are
 
-   // written right-to-left for sure. It was generated by pulling all the strong
 
-   // right-to-left characters out of the Unicode data table. That table can
 
-   // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
 
-   var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]];
 
-   function isStrongRTLChar(charCode) {
 
-     for (var i = 0; i < strongRTLRanges.length; i++) {
 
-       var currentRange = strongRTLRanges[i];
 
-       if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
 
-         return true;
 
-       }
 
-     }
 
-     return false;
 
-   }
 
-   function determineBidi(cueDiv) {
 
-     var nodeStack = [],
 
-       text = "",
 
-       charCode;
 
-     if (!cueDiv || !cueDiv.childNodes) {
 
-       return "ltr";
 
-     }
 
-     function pushNodes(nodeStack, node) {
 
-       for (var i = node.childNodes.length - 1; i >= 0; i--) {
 
-         nodeStack.push(node.childNodes[i]);
 
-       }
 
-     }
 
-     function nextTextNode(nodeStack) {
 
-       if (!nodeStack || !nodeStack.length) {
 
-         return null;
 
-       }
 
-       var node = nodeStack.pop(),
 
-         text = node.textContent || node.innerText;
 
-       if (text) {
 
-         // TODO: This should match all unicode type B characters (paragraph
 
-         // separator characters). See issue #115.
 
-         var m = text.match(/^.*(\n|\r)/);
 
-         if (m) {
 
-           nodeStack.length = 0;
 
-           return m[0];
 
-         }
 
-         return text;
 
-       }
 
-       if (node.tagName === "ruby") {
 
-         return nextTextNode(nodeStack);
 
-       }
 
-       if (node.childNodes) {
 
-         pushNodes(nodeStack, node);
 
-         return nextTextNode(nodeStack);
 
-       }
 
-     }
 
-     pushNodes(nodeStack, cueDiv);
 
-     while (text = nextTextNode(nodeStack)) {
 
-       for (var i = 0; i < text.length; i++) {
 
-         charCode = text.charCodeAt(i);
 
-         if (isStrongRTLChar(charCode)) {
 
-           return "rtl";
 
-         }
 
-       }
 
-     }
 
-     return "ltr";
 
-   }
 
-   function computeLinePos(cue) {
 
-     if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
 
-       return cue.line;
 
-     }
 
-     if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
 
-       return -1;
 
-     }
 
-     var track = cue.track,
 
-       trackList = track.textTrackList,
 
-       count = 0;
 
-     for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
 
-       if (trackList[i].mode === "showing") {
 
-         count++;
 
-       }
 
-     }
 
-     return ++count * -1;
 
-   }
 
-   function StyleBox() {}
 
-   // Apply styles to a div. If there is no div passed then it defaults to the
 
-   // div on 'this'.
 
-   StyleBox.prototype.applyStyles = function (styles, div) {
 
-     div = div || this.div;
 
-     for (var prop in styles) {
 
-       if (styles.hasOwnProperty(prop)) {
 
-         div.style[prop] = styles[prop];
 
-       }
 
-     }
 
-   };
 
-   StyleBox.prototype.formatStyle = function (val, unit) {
 
-     return val === 0 ? 0 : val + unit;
 
-   };
 
-   // Constructs the computed display state of the cue (a div). Places the div
 
-   // into the overlay which should be a block level element (usually a div).
 
-   function CueStyleBox(window, cue, styleOptions) {
 
-     StyleBox.call(this);
 
-     this.cue = cue;
 
-     // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
 
-     // have inline positioning and will function as the cue background box.
 
-     this.cueDiv = parseContent(window, cue.text);
 
-     var styles = {
 
-       color: "rgba(255, 255, 255, 1)",
 
-       backgroundColor: "rgba(0, 0, 0, 0.8)",
 
-       position: "relative",
 
-       left: 0,
 
-       right: 0,
 
-       top: 0,
 
-       bottom: 0,
 
-       display: "inline",
 
-       writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
 
-       unicodeBidi: "plaintext"
 
-     };
 
-     this.applyStyles(styles, this.cueDiv);
 
-     // Create an absolutely positioned div that will be used to position the cue
 
-     // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
 
-     // mirrors of them except middle instead of center on Safari.
 
-     this.div = window.document.createElement("div");
 
-     styles = {
 
-       direction: determineBidi(this.cueDiv),
 
-       writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
 
-       unicodeBidi: "plaintext",
 
-       textAlign: cue.align === "middle" ? "center" : cue.align,
 
-       font: styleOptions.font,
 
-       whiteSpace: "pre-line",
 
-       position: "absolute"
 
-     };
 
-     this.applyStyles(styles);
 
-     this.div.appendChild(this.cueDiv);
 
-     // Calculate the distance from the reference edge of the viewport to the text
 
-     // position of the cue box. The reference edge will be resolved later when
 
-     // the box orientation styles are applied.
 
-     var textPos = 0;
 
-     switch (cue.positionAlign) {
 
-       case "start":
 
-         textPos = cue.position;
 
-         break;
 
-       case "center":
 
-         textPos = cue.position - cue.size / 2;
 
-         break;
 
-       case "end":
 
-         textPos = cue.position - cue.size;
 
-         break;
 
-     }
 
-     // Horizontal box orientation; textPos is the distance from the left edge of the
 
-     // area to the left edge of the box and cue.size is the distance extending to
 
-     // the right from there.
 
-     if (cue.vertical === "") {
 
-       this.applyStyles({
 
-         left: this.formatStyle(textPos, "%"),
 
-         width: this.formatStyle(cue.size, "%")
 
-       });
 
-       // Vertical box orientation; textPos is the distance from the top edge of the
 
-       // area to the top edge of the box and cue.size is the height extending
 
-       // downwards from there.
 
-     } else {
 
-       this.applyStyles({
 
-         top: this.formatStyle(textPos, "%"),
 
-         height: this.formatStyle(cue.size, "%")
 
-       });
 
-     }
 
-     this.move = function (box) {
 
-       this.applyStyles({
 
-         top: this.formatStyle(box.top, "px"),
 
-         bottom: this.formatStyle(box.bottom, "px"),
 
-         left: this.formatStyle(box.left, "px"),
 
-         right: this.formatStyle(box.right, "px"),
 
-         height: this.formatStyle(box.height, "px"),
 
-         width: this.formatStyle(box.width, "px")
 
-       });
 
-     };
 
-   }
 
-   CueStyleBox.prototype = _objCreate(StyleBox.prototype);
 
-   CueStyleBox.prototype.constructor = CueStyleBox;
 
-   // Represents the co-ordinates of an Element in a way that we can easily
 
-   // compute things with such as if it overlaps or intersects with another Element.
 
-   // Can initialize it with either a StyleBox or another BoxPosition.
 
-   function BoxPosition(obj) {
 
-     // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
 
-     // was passed in and we need to copy the results of 'getBoundingClientRect'
 
-     // as the object returned is readonly. All co-ordinate values are in reference
 
-     // to the viewport origin (top left).
 
-     var lh, height, width, top;
 
-     if (obj.div) {
 
-       height = obj.div.offsetHeight;
 
-       width = obj.div.offsetWidth;
 
-       top = obj.div.offsetTop;
 
-       var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
 
-       obj = obj.div.getBoundingClientRect();
 
-       // In certain cases the outter div will be slightly larger then the sum of
 
-       // the inner div's lines. This could be due to bold text, etc, on some platforms.
 
-       // In this case we should get the average line height and use that. This will
 
-       // result in the desired behaviour.
 
-       lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
 
-     }
 
-     this.left = obj.left;
 
-     this.right = obj.right;
 
-     this.top = obj.top || top;
 
-     this.height = obj.height || height;
 
-     this.bottom = obj.bottom || top + (obj.height || height);
 
-     this.width = obj.width || width;
 
-     this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
 
-   }
 
-   // Move the box along a particular axis. Optionally pass in an amount to move
 
-   // the box. If no amount is passed then the default is the line height of the
 
-   // box.
 
-   BoxPosition.prototype.move = function (axis, toMove) {
 
-     toMove = toMove !== undefined ? toMove : this.lineHeight;
 
-     switch (axis) {
 
-       case "+x":
 
-         this.left += toMove;
 
-         this.right += toMove;
 
-         break;
 
-       case "-x":
 
-         this.left -= toMove;
 
-         this.right -= toMove;
 
-         break;
 
-       case "+y":
 
-         this.top += toMove;
 
-         this.bottom += toMove;
 
-         break;
 
-       case "-y":
 
-         this.top -= toMove;
 
-         this.bottom -= toMove;
 
-         break;
 
-     }
 
-   };
 
-   // Check if this box overlaps another box, b2.
 
-   BoxPosition.prototype.overlaps = function (b2) {
 
-     return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
 
-   };
 
-   // Check if this box overlaps any other boxes in boxes.
 
-   BoxPosition.prototype.overlapsAny = function (boxes) {
 
-     for (var i = 0; i < boxes.length; i++) {
 
-       if (this.overlaps(boxes[i])) {
 
-         return true;
 
-       }
 
-     }
 
-     return false;
 
-   };
 
-   // Check if this box is within another box.
 
-   BoxPosition.prototype.within = function (container) {
 
-     return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
 
-   };
 
-   // Check if this box is entirely within the container or it is overlapping
 
-   // on the edge opposite of the axis direction passed. For example, if "+x" is
 
-   // passed and the box is overlapping on the left edge of the container, then
 
-   // return true.
 
-   BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
 
-     switch (axis) {
 
-       case "+x":
 
-         return this.left < container.left;
 
-       case "-x":
 
-         return this.right > container.right;
 
-       case "+y":
 
-         return this.top < container.top;
 
-       case "-y":
 
-         return this.bottom > container.bottom;
 
-     }
 
-   };
 
-   // Find the percentage of the area that this box is overlapping with another
 
-   // box.
 
-   BoxPosition.prototype.intersectPercentage = function (b2) {
 
-     var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
 
-       y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
 
-       intersectArea = x * y;
 
-     return intersectArea / (this.height * this.width);
 
-   };
 
-   // Convert the positions from this box to CSS compatible positions using
 
-   // the reference container's positions. This has to be done because this
 
-   // box's positions are in reference to the viewport origin, whereas, CSS
 
-   // values are in referecne to their respective edges.
 
-   BoxPosition.prototype.toCSSCompatValues = function (reference) {
 
-     return {
 
-       top: this.top - reference.top,
 
-       bottom: reference.bottom - this.bottom,
 
-       left: this.left - reference.left,
 
-       right: reference.right - this.right,
 
-       height: this.height,
 
-       width: this.width
 
-     };
 
-   };
 
-   // Get an object that represents the box's position without anything extra.
 
-   // Can pass a StyleBox, HTMLElement, or another BoxPositon.
 
-   BoxPosition.getSimpleBoxPosition = function (obj) {
 
-     var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
 
-     var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
 
-     var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
 
-     obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
 
-     var ret = {
 
-       left: obj.left,
 
-       right: obj.right,
 
-       top: obj.top || top,
 
-       height: obj.height || height,
 
-       bottom: obj.bottom || top + (obj.height || height),
 
-       width: obj.width || width
 
-     };
 
-     return ret;
 
-   };
 
-   // Move a StyleBox to its specified, or next best, position. The containerBox
 
-   // is the box that contains the StyleBox, such as a div. boxPositions are
 
-   // a list of other boxes that the styleBox can't overlap with.
 
-   function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
 
-     // Find the best position for a cue box, b, on the video. The axis parameter
 
-     // is a list of axis, the order of which, it will move the box along. For example:
 
-     // Passing ["+x", "-x"] will move the box first along the x axis in the positive
 
-     // direction. If it doesn't find a good position for it there it will then move
 
-     // it along the x axis in the negative direction.
 
-     function findBestPosition(b, axis) {
 
-       var bestPosition,
 
-         specifiedPosition = new BoxPosition(b),
 
-         percentage = 1; // Highest possible so the first thing we get is better.
 
-       for (var i = 0; i < axis.length; i++) {
 
-         while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
 
-           b.move(axis[i]);
 
-         }
 
-         // We found a spot where we aren't overlapping anything. This is our
 
-         // best position.
 
-         if (b.within(containerBox)) {
 
-           return b;
 
-         }
 
-         var p = b.intersectPercentage(containerBox);
 
-         // If we're outside the container box less then we were on our last try
 
-         // then remember this position as the best position.
 
-         if (percentage > p) {
 
-           bestPosition = new BoxPosition(b);
 
-           percentage = p;
 
-         }
 
-         // Reset the box position to the specified position.
 
-         b = new BoxPosition(specifiedPosition);
 
-       }
 
-       return bestPosition || specifiedPosition;
 
-     }
 
-     var boxPosition = new BoxPosition(styleBox),
 
-       cue = styleBox.cue,
 
-       linePos = computeLinePos(cue),
 
-       axis = [];
 
-     // If we have a line number to align the cue to.
 
-     if (cue.snapToLines) {
 
-       var size;
 
-       switch (cue.vertical) {
 
-         case "":
 
-           axis = ["+y", "-y"];
 
-           size = "height";
 
-           break;
 
-         case "rl":
 
-           axis = ["+x", "-x"];
 
-           size = "width";
 
-           break;
 
-         case "lr":
 
-           axis = ["-x", "+x"];
 
-           size = "width";
 
-           break;
 
-       }
 
-       var step = boxPosition.lineHeight,
 
-         position = step * Math.round(linePos),
 
-         maxPosition = containerBox[size] + step,
 
-         initialAxis = axis[0];
 
-       // If the specified intial position is greater then the max position then
 
-       // clamp the box to the amount of steps it would take for the box to
 
-       // reach the max position.
 
-       if (Math.abs(position) > maxPosition) {
 
-         position = position < 0 ? -1 : 1;
 
-         position *= Math.ceil(maxPosition / step) * step;
 
-       }
 
-       // If computed line position returns negative then line numbers are
 
-       // relative to the bottom of the video instead of the top. Therefore, we
 
-       // need to increase our initial position by the length or width of the
 
-       // video, depending on the writing direction, and reverse our axis directions.
 
-       if (linePos < 0) {
 
-         position += cue.vertical === "" ? containerBox.height : containerBox.width;
 
-         axis = axis.reverse();
 
-       }
 
-       // Move the box to the specified position. This may not be its best
 
-       // position.
 
-       boxPosition.move(initialAxis, position);
 
-     } else {
 
-       // If we have a percentage line value for the cue.
 
-       var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
 
-       switch (cue.lineAlign) {
 
-         case "center":
 
-           linePos -= calculatedPercentage / 2;
 
-           break;
 
-         case "end":
 
-           linePos -= calculatedPercentage;
 
-           break;
 
-       }
 
-       // Apply initial line position to the cue box.
 
-       switch (cue.vertical) {
 
-         case "":
 
-           styleBox.applyStyles({
 
-             top: styleBox.formatStyle(linePos, "%")
 
-           });
 
-           break;
 
-         case "rl":
 
-           styleBox.applyStyles({
 
-             left: styleBox.formatStyle(linePos, "%")
 
-           });
 
-           break;
 
-         case "lr":
 
-           styleBox.applyStyles({
 
-             right: styleBox.formatStyle(linePos, "%")
 
-           });
 
-           break;
 
-       }
 
-       axis = ["+y", "-x", "+x", "-y"];
 
-       // Get the box position again after we've applied the specified positioning
 
-       // to it.
 
-       boxPosition = new BoxPosition(styleBox);
 
-     }
 
-     var bestPosition = findBestPosition(boxPosition, axis);
 
-     styleBox.move(bestPosition.toCSSCompatValues(containerBox));
 
-   }
 
-   function WebVTT$1() {
 
-     // Nothing
 
-   }
 
-   // Helper to allow strings to be decoded instead of the default binary utf8 data.
 
-   WebVTT$1.StringDecoder = function () {
 
-     return {
 
-       decode: function (data) {
 
-         if (!data) {
 
-           return "";
 
-         }
 
-         if (typeof data !== "string") {
 
-           throw new Error("Error - expected string data.");
 
-         }
 
-         return decodeURIComponent(encodeURIComponent(data));
 
-       }
 
-     };
 
-   };
 
-   WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
 
-     if (!window || !cuetext) {
 
-       return null;
 
-     }
 
-     return parseContent(window, cuetext);
 
-   };
 
-   var FONT_SIZE_PERCENT = 0.05;
 
-   var FONT_STYLE = "sans-serif";
 
-   var CUE_BACKGROUND_PADDING = "1.5%";
 
-   // Runs the processing model over the cues and regions passed to it.
 
-   // @param overlay A block level element (usually a div) that the computed cues
 
-   //                and regions will be placed into.
 
-   WebVTT$1.processCues = function (window, cues, overlay) {
 
-     if (!window || !cues || !overlay) {
 
-       return null;
 
-     }
 
-     // Remove all previous children.
 
-     while (overlay.firstChild) {
 
-       overlay.removeChild(overlay.firstChild);
 
-     }
 
-     var paddedOverlay = window.document.createElement("div");
 
-     paddedOverlay.style.position = "absolute";
 
-     paddedOverlay.style.left = "0";
 
-     paddedOverlay.style.right = "0";
 
-     paddedOverlay.style.top = "0";
 
-     paddedOverlay.style.bottom = "0";
 
-     paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
 
-     overlay.appendChild(paddedOverlay);
 
-     // Determine if we need to compute the display states of the cues. This could
 
-     // be the case if a cue's state has been changed since the last computation or
 
-     // if it has not been computed yet.
 
-     function shouldCompute(cues) {
 
-       for (var i = 0; i < cues.length; i++) {
 
-         if (cues[i].hasBeenReset || !cues[i].displayState) {
 
-           return true;
 
-         }
 
-       }
 
-       return false;
 
-     }
 
-     // We don't need to recompute the cues' display states. Just reuse them.
 
-     if (!shouldCompute(cues)) {
 
-       for (var i = 0; i < cues.length; i++) {
 
-         paddedOverlay.appendChild(cues[i].displayState);
 
-       }
 
-       return;
 
-     }
 
-     var boxPositions = [],
 
-       containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
 
-       fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
 
-     var styleOptions = {
 
-       font: fontSize + "px " + FONT_STYLE
 
-     };
 
-     (function () {
 
-       var styleBox, cue;
 
-       for (var i = 0; i < cues.length; i++) {
 
-         cue = cues[i];
 
-         // Compute the intial position and styles of the cue div.
 
-         styleBox = new CueStyleBox(window, cue, styleOptions);
 
-         paddedOverlay.appendChild(styleBox.div);
 
-         // Move the cue div to it's correct line position.
 
-         moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
 
-         // Remember the computed div so that we don't have to recompute it later
 
-         // if we don't have too.
 
-         cue.displayState = styleBox.div;
 
-         boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
 
-       }
 
-     })();
 
-   };
 
-   WebVTT$1.Parser = function (window, vttjs, decoder) {
 
-     if (!decoder) {
 
-       decoder = vttjs;
 
-       vttjs = {};
 
-     }
 
-     if (!vttjs) {
 
-       vttjs = {};
 
-     }
 
-     this.window = window;
 
-     this.vttjs = vttjs;
 
-     this.state = "INITIAL";
 
-     this.buffer = "";
 
-     this.decoder = decoder || new TextDecoder("utf8");
 
-     this.regionList = [];
 
-   };
 
-   WebVTT$1.Parser.prototype = {
 
-     // If the error is a ParsingError then report it to the consumer if
 
-     // possible. If it's not a ParsingError then throw it like normal.
 
-     reportOrThrowError: function (e) {
 
-       if (e instanceof ParsingError) {
 
-         this.onparsingerror && this.onparsingerror(e);
 
-       } else {
 
-         throw e;
 
-       }
 
-     },
 
-     parse: function (data) {
 
-       var self = this;
 
-       // If there is no data then we won't decode it, but will just try to parse
 
-       // whatever is in buffer already. This may occur in circumstances, for
 
-       // example when flush() is called.
 
-       if (data) {
 
-         // Try to decode the data that we received.
 
-         self.buffer += self.decoder.decode(data, {
 
-           stream: true
 
-         });
 
-       }
 
-       function collectNextLine() {
 
-         var buffer = self.buffer;
 
-         var pos = 0;
 
-         while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
 
-           ++pos;
 
-         }
 
-         var line = buffer.substr(0, pos);
 
-         // Advance the buffer early in case we fail below.
 
-         if (buffer[pos] === '\r') {
 
-           ++pos;
 
-         }
 
-         if (buffer[pos] === '\n') {
 
-           ++pos;
 
-         }
 
-         self.buffer = buffer.substr(pos);
 
-         return line;
 
-       }
 
-       // 3.4 WebVTT region and WebVTT region settings syntax
 
-       function parseRegion(input) {
 
-         var settings = new Settings();
 
-         parseOptions(input, function (k, v) {
 
-           switch (k) {
 
-             case "id":
 
-               settings.set(k, v);
 
-               break;
 
-             case "width":
 
-               settings.percent(k, v);
 
-               break;
 
-             case "lines":
 
-               settings.integer(k, v);
 
-               break;
 
-             case "regionanchor":
 
-             case "viewportanchor":
 
-               var xy = v.split(',');
 
-               if (xy.length !== 2) {
 
-                 break;
 
-               }
 
-               // We have to make sure both x and y parse, so use a temporary
 
-               // settings object here.
 
-               var anchor = new Settings();
 
-               anchor.percent("x", xy[0]);
 
-               anchor.percent("y", xy[1]);
 
-               if (!anchor.has("x") || !anchor.has("y")) {
 
-                 break;
 
-               }
 
-               settings.set(k + "X", anchor.get("x"));
 
-               settings.set(k + "Y", anchor.get("y"));
 
-               break;
 
-             case "scroll":
 
-               settings.alt(k, v, ["up"]);
 
-               break;
 
-           }
 
-         }, /=/, /\s/);
 
-         // Create the region, using default values for any values that were not
 
-         // specified.
 
-         if (settings.has("id")) {
 
-           var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
 
-           region.width = settings.get("width", 100);
 
-           region.lines = settings.get("lines", 3);
 
-           region.regionAnchorX = settings.get("regionanchorX", 0);
 
-           region.regionAnchorY = settings.get("regionanchorY", 100);
 
-           region.viewportAnchorX = settings.get("viewportanchorX", 0);
 
-           region.viewportAnchorY = settings.get("viewportanchorY", 100);
 
-           region.scroll = settings.get("scroll", "");
 
-           // Register the region.
 
-           self.onregion && self.onregion(region);
 
-           // Remember the VTTRegion for later in case we parse any VTTCues that
 
-           // reference it.
 
-           self.regionList.push({
 
-             id: settings.get("id"),
 
-             region: region
 
-           });
 
-         }
 
-       }
 
-       // draft-pantos-http-live-streaming-20
 
-       // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
 
-       // 3.5 WebVTT
 
-       function parseTimestampMap(input) {
 
-         var settings = new Settings();
 
-         parseOptions(input, function (k, v) {
 
-           switch (k) {
 
-             case "MPEGT":
 
-               settings.integer(k + 'S', v);
 
-               break;
 
-             case "LOCA":
 
-               settings.set(k + 'L', parseTimeStamp(v));
 
-               break;
 
-           }
 
-         }, /[^\d]:/, /,/);
 
-         self.ontimestampmap && self.ontimestampmap({
 
-           "MPEGTS": settings.get("MPEGTS"),
 
-           "LOCAL": settings.get("LOCAL")
 
-         });
 
-       }
 
-       // 3.2 WebVTT metadata header syntax
 
-       function parseHeader(input) {
 
-         if (input.match(/X-TIMESTAMP-MAP/)) {
 
-           // This line contains HLS X-TIMESTAMP-MAP metadata
 
-           parseOptions(input, function (k, v) {
 
-             switch (k) {
 
-               case "X-TIMESTAMP-MAP":
 
-                 parseTimestampMap(v);
 
-                 break;
 
-             }
 
-           }, /=/);
 
-         } else {
 
-           parseOptions(input, function (k, v) {
 
-             switch (k) {
 
-               case "Region":
 
-                 // 3.3 WebVTT region metadata header syntax
 
-                 parseRegion(v);
 
-                 break;
 
-             }
 
-           }, /:/);
 
-         }
 
-       }
 
-       // 5.1 WebVTT file parsing.
 
-       try {
 
-         var line;
 
-         if (self.state === "INITIAL") {
 
-           // We can't start parsing until we have the first line.
 
-           if (!/\r\n|\n/.test(self.buffer)) {
 
-             return this;
 
-           }
 
-           line = collectNextLine();
 
-           var m = line.match(/^WEBVTT([ \t].*)?$/);
 
-           if (!m || !m[0]) {
 
-             throw new ParsingError(ParsingError.Errors.BadSignature);
 
-           }
 
-           self.state = "HEADER";
 
-         }
 
-         var alreadyCollectedLine = false;
 
-         while (self.buffer) {
 
-           // We can't parse a line until we have the full line.
 
-           if (!/\r\n|\n/.test(self.buffer)) {
 
-             return this;
 
-           }
 
-           if (!alreadyCollectedLine) {
 
-             line = collectNextLine();
 
-           } else {
 
-             alreadyCollectedLine = false;
 
-           }
 
-           switch (self.state) {
 
-             case "HEADER":
 
-               // 13-18 - Allow a header (metadata) under the WEBVTT line.
 
-               if (/:/.test(line)) {
 
-                 parseHeader(line);
 
-               } else if (!line) {
 
-                 // An empty line terminates the header and starts the body (cues).
 
-                 self.state = "ID";
 
-               }
 
-               continue;
 
-             case "NOTE":
 
-               // Ignore NOTE blocks.
 
-               if (!line) {
 
-                 self.state = "ID";
 
-               }
 
-               continue;
 
-             case "ID":
 
-               // Check for the start of NOTE blocks.
 
-               if (/^NOTE($|[ \t])/.test(line)) {
 
-                 self.state = "NOTE";
 
-                 break;
 
-               }
 
-               // 19-29 - Allow any number of line terminators, then initialize new cue values.
 
-               if (!line) {
 
-                 continue;
 
-               }
 
-               self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
 
-               // Safari still uses the old middle value and won't accept center
 
-               try {
 
-                 self.cue.align = "center";
 
-               } catch (e) {
 
-                 self.cue.align = "middle";
 
-               }
 
-               self.state = "CUE";
 
-               // 30-39 - Check if self line contains an optional identifier or timing data.
 
-               if (line.indexOf("-->") === -1) {
 
-                 self.cue.id = line;
 
-                 continue;
 
-               }
 
-             // Process line as start of a cue.
 
-             /*falls through*/
 
-             case "CUE":
 
-               // 40 - Collect cue timings and settings.
 
-               try {
 
-                 parseCue(line, self.cue, self.regionList);
 
-               } catch (e) {
 
-                 self.reportOrThrowError(e);
 
-                 // In case of an error ignore rest of the cue.
 
-                 self.cue = null;
 
-                 self.state = "BADCUE";
 
-                 continue;
 
-               }
 
-               self.state = "CUETEXT";
 
-               continue;
 
-             case "CUETEXT":
 
-               var hasSubstring = line.indexOf("-->") !== -1;
 
-               // 34 - If we have an empty line then report the cue.
 
-               // 35 - If we have the special substring '-->' then report the cue,
 
-               // but do not collect the line as we need to process the current
 
-               // one as a new cue.
 
-               if (!line || hasSubstring && (alreadyCollectedLine = true)) {
 
-                 // We are done parsing self cue.
 
-                 self.oncue && self.oncue(self.cue);
 
-                 self.cue = null;
 
-                 self.state = "ID";
 
-                 continue;
 
-               }
 
-               if (self.cue.text) {
 
-                 self.cue.text += "\n";
 
-               }
 
-               self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n');
 
-               continue;
 
-             case "BADCUE":
 
-               // BADCUE
 
-               // 54-62 - Collect and discard the remaining cue.
 
-               if (!line) {
 
-                 self.state = "ID";
 
-               }
 
-               continue;
 
-           }
 
-         }
 
-       } catch (e) {
 
-         self.reportOrThrowError(e);
 
-         // If we are currently parsing a cue, report what we have.
 
-         if (self.state === "CUETEXT" && self.cue && self.oncue) {
 
-           self.oncue(self.cue);
 
-         }
 
-         self.cue = null;
 
-         // Enter BADWEBVTT state if header was not parsed correctly otherwise
 
-         // another exception occurred so enter BADCUE state.
 
-         self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
 
-       }
 
-       return this;
 
-     },
 
-     flush: function () {
 
-       var self = this;
 
-       try {
 
-         // Finish decoding the stream.
 
-         self.buffer += self.decoder.decode();
 
-         // Synthesize the end of the current cue or region.
 
-         if (self.cue || self.state === "HEADER") {
 
-           self.buffer += "\n\n";
 
-           self.parse();
 
-         }
 
-         // If we've flushed, parsed, and we're still on the INITIAL state then
 
-         // that means we don't have enough of the stream to parse the first
 
-         // line.
 
-         if (self.state === "INITIAL") {
 
-           throw new ParsingError(ParsingError.Errors.BadSignature);
 
-         }
 
-       } catch (e) {
 
-         self.reportOrThrowError(e);
 
-       }
 
-       self.onflush && self.onflush();
 
-       return this;
 
-     }
 
-   };
 
-   var vtt = WebVTT$1;
 
-   /**
 
-    * Copyright 2013 vtt.js Contributors
 
-    *
 
-    * Licensed under the Apache License, Version 2.0 (the "License");
 
-    * you may not use this file except in compliance with the License.
 
-    * You may obtain a copy of the License at
 
-    *
 
-    *   http://www.apache.org/licenses/LICENSE-2.0
 
-    *
 
-    * Unless required by applicable law or agreed to in writing, software
 
-    * distributed under the License is distributed on an "AS IS" BASIS,
 
-    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
-    * See the License for the specific language governing permissions and
 
-    * limitations under the License.
 
-    */
 
-   var autoKeyword = "auto";
 
-   var directionSetting = {
 
-     "": 1,
 
-     "lr": 1,
 
-     "rl": 1
 
-   };
 
-   var alignSetting = {
 
-     "start": 1,
 
-     "center": 1,
 
-     "end": 1,
 
-     "left": 1,
 
-     "right": 1,
 
-     "auto": 1,
 
-     "line-left": 1,
 
-     "line-right": 1
 
-   };
 
-   function findDirectionSetting(value) {
 
-     if (typeof value !== "string") {
 
-       return false;
 
-     }
 
-     var dir = directionSetting[value.toLowerCase()];
 
-     return dir ? value.toLowerCase() : false;
 
-   }
 
-   function findAlignSetting(value) {
 
-     if (typeof value !== "string") {
 
-       return false;
 
-     }
 
-     var align = alignSetting[value.toLowerCase()];
 
-     return align ? value.toLowerCase() : false;
 
-   }
 
-   function VTTCue(startTime, endTime, text) {
 
-     /**
 
-      * Shim implementation specific properties. These properties are not in
 
-      * the spec.
 
-      */
 
-     // Lets us know when the VTTCue's data has changed in such a way that we need
 
-     // to recompute its display state. This lets us compute its display state
 
-     // lazily.
 
-     this.hasBeenReset = false;
 
-     /**
 
-      * VTTCue and TextTrackCue properties
 
-      * http://dev.w3.org/html5/webvtt/#vttcue-interface
 
-      */
 
-     var _id = "";
 
-     var _pauseOnExit = false;
 
-     var _startTime = startTime;
 
-     var _endTime = endTime;
 
-     var _text = text;
 
-     var _region = null;
 
-     var _vertical = "";
 
-     var _snapToLines = true;
 
-     var _line = "auto";
 
-     var _lineAlign = "start";
 
-     var _position = "auto";
 
-     var _positionAlign = "auto";
 
-     var _size = 100;
 
-     var _align = "center";
 
-     Object.defineProperties(this, {
 
-       "id": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _id;
 
-         },
 
-         set: function (value) {
 
-           _id = "" + value;
 
-         }
 
-       },
 
-       "pauseOnExit": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _pauseOnExit;
 
-         },
 
-         set: function (value) {
 
-           _pauseOnExit = !!value;
 
-         }
 
-       },
 
-       "startTime": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _startTime;
 
-         },
 
-         set: function (value) {
 
-           if (typeof value !== "number") {
 
-             throw new TypeError("Start time must be set to a number.");
 
-           }
 
-           _startTime = value;
 
-           this.hasBeenReset = true;
 
-         }
 
-       },
 
-       "endTime": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _endTime;
 
-         },
 
-         set: function (value) {
 
-           if (typeof value !== "number") {
 
-             throw new TypeError("End time must be set to a number.");
 
-           }
 
-           _endTime = value;
 
-           this.hasBeenReset = true;
 
-         }
 
-       },
 
-       "text": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _text;
 
-         },
 
-         set: function (value) {
 
-           _text = "" + value;
 
-           this.hasBeenReset = true;
 
-         }
 
-       },
 
-       "region": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _region;
 
-         },
 
-         set: function (value) {
 
-           _region = value;
 
-           this.hasBeenReset = true;
 
-         }
 
-       },
 
-       "vertical": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _vertical;
 
-         },
 
-         set: function (value) {
 
-           var setting = findDirectionSetting(value);
 
-           // Have to check for false because the setting an be an empty string.
 
-           if (setting === false) {
 
-             throw new SyntaxError("Vertical: an invalid or illegal direction string was specified.");
 
-           }
 
-           _vertical = setting;
 
-           this.hasBeenReset = true;
 
-         }
 
-       },
 
-       "snapToLines": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _snapToLines;
 
-         },
 
-         set: function (value) {
 
-           _snapToLines = !!value;
 
-           this.hasBeenReset = true;
 
-         }
 
-       },
 
-       "line": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _line;
 
-         },
 
-         set: function (value) {
 
-           if (typeof value !== "number" && value !== autoKeyword) {
 
-             throw new SyntaxError("Line: an invalid number or illegal string was specified.");
 
-           }
 
-           _line = value;
 
-           this.hasBeenReset = true;
 
-         }
 
-       },
 
-       "lineAlign": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _lineAlign;
 
-         },
 
-         set: function (value) {
 
-           var setting = findAlignSetting(value);
 
-           if (!setting) {
 
-             console.warn("lineAlign: an invalid or illegal string was specified.");
 
-           } else {
 
-             _lineAlign = setting;
 
-             this.hasBeenReset = true;
 
-           }
 
-         }
 
-       },
 
-       "position": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _position;
 
-         },
 
-         set: function (value) {
 
-           if (value < 0 || value > 100) {
 
-             throw new Error("Position must be between 0 and 100.");
 
-           }
 
-           _position = value;
 
-           this.hasBeenReset = true;
 
-         }
 
-       },
 
-       "positionAlign": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _positionAlign;
 
-         },
 
-         set: function (value) {
 
-           var setting = findAlignSetting(value);
 
-           if (!setting) {
 
-             console.warn("positionAlign: an invalid or illegal string was specified.");
 
-           } else {
 
-             _positionAlign = setting;
 
-             this.hasBeenReset = true;
 
-           }
 
-         }
 
-       },
 
-       "size": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _size;
 
-         },
 
-         set: function (value) {
 
-           if (value < 0 || value > 100) {
 
-             throw new Error("Size must be between 0 and 100.");
 
-           }
 
-           _size = value;
 
-           this.hasBeenReset = true;
 
-         }
 
-       },
 
-       "align": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _align;
 
-         },
 
-         set: function (value) {
 
-           var setting = findAlignSetting(value);
 
-           if (!setting) {
 
-             throw new SyntaxError("align: an invalid or illegal alignment string was specified.");
 
-           }
 
-           _align = setting;
 
-           this.hasBeenReset = true;
 
-         }
 
-       }
 
-     });
 
-     /**
 
-      * Other <track> spec defined properties
 
-      */
 
-     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
 
-     this.displayState = undefined;
 
-   }
 
-   /**
 
-    * VTTCue methods
 
-    */
 
-   VTTCue.prototype.getCueAsHTML = function () {
 
-     // Assume WebVTT.convertCueToDOMTree is on the global.
 
-     return WebVTT.convertCueToDOMTree(window, this.text);
 
-   };
 
-   var vttcue = VTTCue;
 
-   /**
 
-    * Copyright 2013 vtt.js Contributors
 
-    *
 
-    * Licensed under the Apache License, Version 2.0 (the "License");
 
-    * you may not use this file except in compliance with the License.
 
-    * You may obtain a copy of the License at
 
-    *
 
-    *   http://www.apache.org/licenses/LICENSE-2.0
 
-    *
 
-    * Unless required by applicable law or agreed to in writing, software
 
-    * distributed under the License is distributed on an "AS IS" BASIS,
 
-    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
-    * See the License for the specific language governing permissions and
 
-    * limitations under the License.
 
-    */
 
-   var scrollSetting = {
 
-     "": true,
 
-     "up": true
 
-   };
 
-   function findScrollSetting(value) {
 
-     if (typeof value !== "string") {
 
-       return false;
 
-     }
 
-     var scroll = scrollSetting[value.toLowerCase()];
 
-     return scroll ? value.toLowerCase() : false;
 
-   }
 
-   function isValidPercentValue(value) {
 
-     return typeof value === "number" && value >= 0 && value <= 100;
 
-   }
 
-   // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
 
-   function VTTRegion() {
 
-     var _width = 100;
 
-     var _lines = 3;
 
-     var _regionAnchorX = 0;
 
-     var _regionAnchorY = 100;
 
-     var _viewportAnchorX = 0;
 
-     var _viewportAnchorY = 100;
 
-     var _scroll = "";
 
-     Object.defineProperties(this, {
 
-       "width": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _width;
 
-         },
 
-         set: function (value) {
 
-           if (!isValidPercentValue(value)) {
 
-             throw new Error("Width must be between 0 and 100.");
 
-           }
 
-           _width = value;
 
-         }
 
-       },
 
-       "lines": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _lines;
 
-         },
 
-         set: function (value) {
 
-           if (typeof value !== "number") {
 
-             throw new TypeError("Lines must be set to a number.");
 
-           }
 
-           _lines = value;
 
-         }
 
-       },
 
-       "regionAnchorY": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _regionAnchorY;
 
-         },
 
-         set: function (value) {
 
-           if (!isValidPercentValue(value)) {
 
-             throw new Error("RegionAnchorX must be between 0 and 100.");
 
-           }
 
-           _regionAnchorY = value;
 
-         }
 
-       },
 
-       "regionAnchorX": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _regionAnchorX;
 
-         },
 
-         set: function (value) {
 
-           if (!isValidPercentValue(value)) {
 
-             throw new Error("RegionAnchorY must be between 0 and 100.");
 
-           }
 
-           _regionAnchorX = value;
 
-         }
 
-       },
 
-       "viewportAnchorY": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _viewportAnchorY;
 
-         },
 
-         set: function (value) {
 
-           if (!isValidPercentValue(value)) {
 
-             throw new Error("ViewportAnchorY must be between 0 and 100.");
 
-           }
 
-           _viewportAnchorY = value;
 
-         }
 
-       },
 
-       "viewportAnchorX": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _viewportAnchorX;
 
-         },
 
-         set: function (value) {
 
-           if (!isValidPercentValue(value)) {
 
-             throw new Error("ViewportAnchorX must be between 0 and 100.");
 
-           }
 
-           _viewportAnchorX = value;
 
-         }
 
-       },
 
-       "scroll": {
 
-         enumerable: true,
 
-         get: function () {
 
-           return _scroll;
 
-         },
 
-         set: function (value) {
 
-           var setting = findScrollSetting(value);
 
-           // Have to check for false as an empty string is a legal value.
 
-           if (setting === false) {
 
-             console.warn("Scroll: an invalid or illegal string was specified.");
 
-           } else {
 
-             _scroll = setting;
 
-           }
 
-         }
 
-       }
 
-     });
 
-   }
 
-   var vttregion = VTTRegion;
 
-   var browserIndex = createCommonjsModule(function (module) {
 
-     /**
 
-      * Copyright 2013 vtt.js Contributors
 
-      *
 
-      * Licensed under the Apache License, Version 2.0 (the "License");
 
-      * you may not use this file except in compliance with the License.
 
-      * You may obtain a copy of the License at
 
-      *
 
-      *   http://www.apache.org/licenses/LICENSE-2.0
 
-      *
 
-      * Unless required by applicable law or agreed to in writing, software
 
-      * distributed under the License is distributed on an "AS IS" BASIS,
 
-      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
-      * See the License for the specific language governing permissions and
 
-      * limitations under the License.
 
-      */
 
-     // Default exports for Node. Export the extended versions of VTTCue and
 
-     // VTTRegion in Node since we likely want the capability to convert back and
 
-     // forth between JSON. If we don't then it's not that big of a deal since we're
 
-     // off browser.
 
-     var vttjs = module.exports = {
 
-       WebVTT: vtt,
 
-       VTTCue: vttcue,
 
-       VTTRegion: vttregion
 
-     };
 
-     window_1.vttjs = vttjs;
 
-     window_1.WebVTT = vttjs.WebVTT;
 
-     var cueShim = vttjs.VTTCue;
 
-     var regionShim = vttjs.VTTRegion;
 
-     var nativeVTTCue = window_1.VTTCue;
 
-     var nativeVTTRegion = window_1.VTTRegion;
 
-     vttjs.shim = function () {
 
-       window_1.VTTCue = cueShim;
 
-       window_1.VTTRegion = regionShim;
 
-     };
 
-     vttjs.restore = function () {
 
-       window_1.VTTCue = nativeVTTCue;
 
-       window_1.VTTRegion = nativeVTTRegion;
 
-     };
 
-     if (!window_1.VTTCue) {
 
-       vttjs.shim();
 
-     }
 
-   });
 
-   browserIndex.WebVTT;
 
-   browserIndex.VTTCue;
 
-   browserIndex.VTTRegion;
 
-   /**
 
-    * @file tech.js
 
-    */
 
-   /**
 
-    * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
 
-    * that just contains the src url alone.
 
-    * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
 
-      * `var SourceString = 'http://example.com/some-video.mp4';`
 
-    *
 
-    * @typedef {Object|string} Tech~SourceObject
 
-    *
 
-    * @property {string} src
 
-    *           The url to the source
 
-    *
 
-    * @property {string} type
 
-    *           The mime type of the source
 
-    */
 
-   /**
 
-    * A function used by {@link Tech} to create a new {@link TextTrack}.
 
-    *
 
-    * @private
 
-    *
 
-    * @param {Tech} self
 
-    *        An instance of the Tech class.
 
-    *
 
-    * @param {string} kind
 
-    *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
 
-    *
 
-    * @param {string} [label]
 
-    *        Label to identify the text track
 
-    *
 
-    * @param {string} [language]
 
-    *        Two letter language abbreviation
 
-    *
 
-    * @param {Object} [options={}]
 
-    *        An object with additional text track options
 
-    *
 
-    * @return {TextTrack}
 
-    *          The text track that was created.
 
-    */
 
-   function createTrackHelper(self, kind, label, language, options = {}) {
 
-     const tracks = self.textTracks();
 
-     options.kind = kind;
 
-     if (label) {
 
-       options.label = label;
 
-     }
 
-     if (language) {
 
-       options.language = language;
 
-     }
 
-     options.tech = self;
 
-     const track = new ALL.text.TrackClass(options);
 
-     tracks.addTrack(track);
 
-     return track;
 
-   }
 
-   /**
 
-    * This is the base class for media playback technology controllers, such as
 
-    * {@link HTML5}
 
-    *
 
-    * @extends Component
 
-    */
 
-   class Tech extends Component$1 {
 
-     /**
 
-     * Create an instance of this Tech.
 
-     *
 
-     * @param {Object} [options]
 
-     *        The key/value store of player options.
 
-     *
 
-     * @param {Function} [ready]
 
-     *        Callback function to call when the `HTML5` Tech is ready.
 
-     */
 
-     constructor(options = {}, ready = function () {}) {
 
-       // we don't want the tech to report user activity automatically.
 
-       // This is done manually in addControlsListeners
 
-       options.reportTouchActivity = false;
 
-       super(null, options, ready);
 
-       this.onDurationChange_ = e => this.onDurationChange(e);
 
-       this.trackProgress_ = e => this.trackProgress(e);
 
-       this.trackCurrentTime_ = e => this.trackCurrentTime(e);
 
-       this.stopTrackingCurrentTime_ = e => this.stopTrackingCurrentTime(e);
 
-       this.disposeSourceHandler_ = e => this.disposeSourceHandler(e);
 
-       this.queuedHanders_ = new Set();
 
-       // keep track of whether the current source has played at all to
 
-       // implement a very limited played()
 
-       this.hasStarted_ = false;
 
-       this.on('playing', function () {
 
-         this.hasStarted_ = true;
 
-       });
 
-       this.on('loadstart', function () {
 
-         this.hasStarted_ = false;
 
-       });
 
-       ALL.names.forEach(name => {
 
-         const props = ALL[name];
 
-         if (options && options[props.getterName]) {
 
-           this[props.privateName] = options[props.getterName];
 
-         }
 
-       });
 
-       // Manually track progress in cases where the browser/tech doesn't report it.
 
-       if (!this.featuresProgressEvents) {
 
-         this.manualProgressOn();
 
-       }
 
-       // Manually track timeupdates in cases where the browser/tech doesn't report it.
 
-       if (!this.featuresTimeupdateEvents) {
 
-         this.manualTimeUpdatesOn();
 
-       }
 
-       ['Text', 'Audio', 'Video'].forEach(track => {
 
-         if (options[`native${track}Tracks`] === false) {
 
-           this[`featuresNative${track}Tracks`] = false;
 
-         }
 
-       });
 
-       if (options.nativeCaptions === false || options.nativeTextTracks === false) {
 
-         this.featuresNativeTextTracks = false;
 
-       } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
 
-         this.featuresNativeTextTracks = true;
 
-       }
 
-       if (!this.featuresNativeTextTracks) {
 
-         this.emulateTextTracks();
 
-       }
 
-       this.preloadTextTracks = options.preloadTextTracks !== false;
 
-       this.autoRemoteTextTracks_ = new ALL.text.ListClass();
 
-       this.initTrackListeners();
 
-       // Turn on component tap events only if not using native controls
 
-       if (!options.nativeControlsForTouch) {
 
-         this.emitTapEvents();
 
-       }
 
-       if (this.constructor) {
 
-         this.name_ = this.constructor.name || 'Unknown Tech';
 
-       }
 
-     }
 
-     /**
 
-      * A special function to trigger source set in a way that will allow player
 
-      * to re-trigger if the player or tech are not ready yet.
 
-      *
 
-      * @fires Tech#sourceset
 
-      * @param {string} src The source string at the time of the source changing.
 
-      */
 
-     triggerSourceset(src) {
 
-       if (!this.isReady_) {
 
-         // on initial ready we have to trigger source set
 
-         // 1ms after ready so that player can watch for it.
 
-         this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1));
 
-       }
 
-       /**
 
-        * Fired when the source is set on the tech causing the media element
 
-        * to reload.
 
-        *
 
-        * @see {@link Player#event:sourceset}
 
-        * @event Tech#sourceset
 
-        * @type {Event}
 
-        */
 
-       this.trigger({
 
-         src,
 
-         type: 'sourceset'
 
-       });
 
-     }
 
-     /* Fallbacks for unsupported event types
 
-     ================================================================================ */
 
-     /**
 
-      * Polyfill the `progress` event for browsers that don't support it natively.
 
-      *
 
-      * @see {@link Tech#trackProgress}
 
-      */
 
-     manualProgressOn() {
 
-       this.on('durationchange', this.onDurationChange_);
 
-       this.manualProgress = true;
 
-       // Trigger progress watching when a source begins loading
 
-       this.one('ready', this.trackProgress_);
 
-     }
 
-     /**
 
-      * Turn off the polyfill for `progress` events that was created in
 
-      * {@link Tech#manualProgressOn}
 
-      */
 
-     manualProgressOff() {
 
-       this.manualProgress = false;
 
-       this.stopTrackingProgress();
 
-       this.off('durationchange', this.onDurationChange_);
 
-     }
 
-     /**
 
-      * This is used to trigger a `progress` event when the buffered percent changes. It
 
-      * sets an interval function that will be called every 500 milliseconds to check if the
 
-      * buffer end percent has changed.
 
-      *
 
-      * > This function is called by {@link Tech#manualProgressOn}
 
-      *
 
-      * @param {Event} event
 
-      *        The `ready` event that caused this to run.
 
-      *
 
-      * @listens Tech#ready
 
-      * @fires Tech#progress
 
-      */
 
-     trackProgress(event) {
 
-       this.stopTrackingProgress();
 
-       this.progressInterval = this.setInterval(bind_(this, function () {
 
-         // Don't trigger unless buffered amount is greater than last time
 
-         const numBufferedPercent = this.bufferedPercent();
 
-         if (this.bufferedPercent_ !== numBufferedPercent) {
 
-           /**
 
-            * See {@link Player#progress}
 
-            *
 
-            * @event Tech#progress
 
-            * @type {Event}
 
-            */
 
-           this.trigger('progress');
 
-         }
 
-         this.bufferedPercent_ = numBufferedPercent;
 
-         if (numBufferedPercent === 1) {
 
-           this.stopTrackingProgress();
 
-         }
 
-       }), 500);
 
-     }
 
-     /**
 
-      * Update our internal duration on a `durationchange` event by calling
 
-      * {@link Tech#duration}.
 
-      *
 
-      * @param {Event} event
 
-      *        The `durationchange` event that caused this to run.
 
-      *
 
-      * @listens Tech#durationchange
 
-      */
 
-     onDurationChange(event) {
 
-       this.duration_ = this.duration();
 
-     }
 
-     /**
 
-      * Get and create a `TimeRange` object for buffering.
 
-      *
 
-      * @return { import('../utils/time').TimeRange }
 
-      *         The time range object that was created.
 
-      */
 
-     buffered() {
 
-       return createTimeRanges$1(0, 0);
 
-     }
 
-     /**
 
-      * Get the percentage of the current video that is currently buffered.
 
-      *
 
-      * @return {number}
 
-      *         A number from 0 to 1 that represents the decimal percentage of the
 
-      *         video that is buffered.
 
-      *
 
-      */
 
-     bufferedPercent() {
 
-       return bufferedPercent(this.buffered(), this.duration_);
 
-     }
 
-     /**
 
-      * Turn off the polyfill for `progress` events that was created in
 
-      * {@link Tech#manualProgressOn}
 
-      * Stop manually tracking progress events by clearing the interval that was set in
 
-      * {@link Tech#trackProgress}.
 
-      */
 
-     stopTrackingProgress() {
 
-       this.clearInterval(this.progressInterval);
 
-     }
 
-     /**
 
-      * Polyfill the `timeupdate` event for browsers that don't support it.
 
-      *
 
-      * @see {@link Tech#trackCurrentTime}
 
-      */
 
-     manualTimeUpdatesOn() {
 
-       this.manualTimeUpdates = true;
 
-       this.on('play', this.trackCurrentTime_);
 
-       this.on('pause', this.stopTrackingCurrentTime_);
 
-     }
 
-     /**
 
-      * Turn off the polyfill for `timeupdate` events that was created in
 
-      * {@link Tech#manualTimeUpdatesOn}
 
-      */
 
-     manualTimeUpdatesOff() {
 
-       this.manualTimeUpdates = false;
 
-       this.stopTrackingCurrentTime();
 
-       this.off('play', this.trackCurrentTime_);
 
-       this.off('pause', this.stopTrackingCurrentTime_);
 
-     }
 
-     /**
 
-      * Sets up an interval function to track current time and trigger `timeupdate` every
 
-      * 250 milliseconds.
 
-      *
 
-      * @listens Tech#play
 
-      * @triggers Tech#timeupdate
 
-      */
 
-     trackCurrentTime() {
 
-       if (this.currentTimeInterval) {
 
-         this.stopTrackingCurrentTime();
 
-       }
 
-       this.currentTimeInterval = this.setInterval(function () {
 
-         /**
 
-          * Triggered at an interval of 250ms to indicated that time is passing in the video.
 
-          *
 
-          * @event Tech#timeupdate
 
-          * @type {Event}
 
-          */
 
-         this.trigger({
 
-           type: 'timeupdate',
 
-           target: this,
 
-           manuallyTriggered: true
 
-         });
 
-         // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
 
-       }, 250);
 
-     }
 
-     /**
 
-      * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
 
-      * `timeupdate` event is no longer triggered.
 
-      *
 
-      * @listens {Tech#pause}
 
-      */
 
-     stopTrackingCurrentTime() {
 
-       this.clearInterval(this.currentTimeInterval);
 
-       // #1002 - if the video ends right before the next timeupdate would happen,
 
-       // the progress bar won't make it all the way to the end
 
-       this.trigger({
 
-         type: 'timeupdate',
 
-         target: this,
 
-         manuallyTriggered: true
 
-       });
 
-     }
 
-     /**
 
-      * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
 
-      * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
 
-      *
 
-      * @fires Component#dispose
 
-      */
 
-     dispose() {
 
-       // clear out all tracks because we can't reuse them between techs
 
-       this.clearTracks(NORMAL.names);
 
-       // Turn off any manual progress or timeupdate tracking
 
-       if (this.manualProgress) {
 
-         this.manualProgressOff();
 
-       }
 
-       if (this.manualTimeUpdates) {
 
-         this.manualTimeUpdatesOff();
 
-       }
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Clear out a single `TrackList` or an array of `TrackLists` given their names.
 
-      *
 
-      * > Note: Techs without source handlers should call this between sources for `video`
 
-      *         & `audio` tracks. You don't want to use them between tracks!
 
-      *
 
-      * @param {string[]|string} types
 
-      *        TrackList names to clear, valid names are `video`, `audio`, and
 
-      *        `text`.
 
-      */
 
-     clearTracks(types) {
 
-       types = [].concat(types);
 
-       // clear out all tracks because we can't reuse them between techs
 
-       types.forEach(type => {
 
-         const list = this[`${type}Tracks`]() || [];
 
-         let i = list.length;
 
-         while (i--) {
 
-           const track = list[i];
 
-           if (type === 'text') {
 
-             this.removeRemoteTextTrack(track);
 
-           }
 
-           list.removeTrack(track);
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * Remove any TextTracks added via addRemoteTextTrack that are
 
-      * flagged for automatic garbage collection
 
-      */
 
-     cleanupAutoTextTracks() {
 
-       const list = this.autoRemoteTextTracks_ || [];
 
-       let i = list.length;
 
-       while (i--) {
 
-         const track = list[i];
 
-         this.removeRemoteTextTrack(track);
 
-       }
 
-     }
 
-     /**
 
-      * Reset the tech, which will removes all sources and reset the internal readyState.
 
-      *
 
-      * @abstract
 
-      */
 
-     reset() {}
 
-     /**
 
-      * Get the value of `crossOrigin` from the tech.
 
-      *
 
-      * @abstract
 
-      *
 
-      * @see {Html5#crossOrigin}
 
-      */
 
-     crossOrigin() {}
 
-     /**
 
-      * Set the value of `crossOrigin` on the tech.
 
-      *
 
-      * @abstract
 
-      *
 
-      * @param {string} crossOrigin the crossOrigin value
 
-      * @see {Html5#setCrossOrigin}
 
-      */
 
-     setCrossOrigin() {}
 
-     /**
 
-      * Get or set an error on the Tech.
 
-      *
 
-      * @param {MediaError} [err]
 
-      *        Error to set on the Tech
 
-      *
 
-      * @return {MediaError|null}
 
-      *         The current error object on the tech, or null if there isn't one.
 
-      */
 
-     error(err) {
 
-       if (err !== undefined) {
 
-         this.error_ = new MediaError(err);
 
-         this.trigger('error');
 
-       }
 
-       return this.error_;
 
-     }
 
-     /**
 
-      * Returns the `TimeRange`s that have been played through for the current source.
 
-      *
 
-      * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
 
-      *         It only checks whether the source has played at all or not.
 
-      *
 
-      * @return {TimeRange}
 
-      *         - A single time range if this video has played
 
-      *         - An empty set of ranges if not.
 
-      */
 
-     played() {
 
-       if (this.hasStarted_) {
 
-         return createTimeRanges$1(0, 0);
 
-       }
 
-       return createTimeRanges$1();
 
-     }
 
-     /**
 
-      * Start playback
 
-      *
 
-      * @abstract
 
-      *
 
-      * @see {Html5#play}
 
-      */
 
-     play() {}
 
-     /**
 
-      * Set whether we are scrubbing or not
 
-      *
 
-      * @abstract
 
-      * @param {boolean} _isScrubbing
 
-      *                  - true for we are currently scrubbing
 
-      *                  - false for we are no longer scrubbing
 
-      *
 
-      * @see {Html5#setScrubbing}
 
-      */
 
-     setScrubbing(_isScrubbing) {}
 
-     /**
 
-      * Get whether we are scrubbing or not
 
-      *
 
-      * @abstract
 
-      *
 
-      * @see {Html5#scrubbing}
 
-      */
 
-     scrubbing() {}
 
-     /**
 
-      * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
 
-      * previously called.
 
-      *
 
-      * @param {number} _seconds
 
-      *        Set the current time of the media to this.
 
-      * @fires Tech#timeupdate
 
-      */
 
-     setCurrentTime(_seconds) {
 
-       // improve the accuracy of manual timeupdates
 
-       if (this.manualTimeUpdates) {
 
-         /**
 
-          * A manual `timeupdate` event.
 
-          *
 
-          * @event Tech#timeupdate
 
-          * @type {Event}
 
-          */
 
-         this.trigger({
 
-           type: 'timeupdate',
 
-           target: this,
 
-           manuallyTriggered: true
 
-         });
 
-       }
 
-     }
 
-     /**
 
-      * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
 
-      * {@link TextTrackList} events.
 
-      *
 
-      * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
 
-      *
 
-      * @fires Tech#audiotrackchange
 
-      * @fires Tech#videotrackchange
 
-      * @fires Tech#texttrackchange
 
-      */
 
-     initTrackListeners() {
 
-       /**
 
-         * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
 
-         *
 
-         * @event Tech#audiotrackchange
 
-         * @type {Event}
 
-         */
 
-       /**
 
-         * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
 
-         *
 
-         * @event Tech#videotrackchange
 
-         * @type {Event}
 
-         */
 
-       /**
 
-         * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
 
-         *
 
-         * @event Tech#texttrackchange
 
-         * @type {Event}
 
-         */
 
-       NORMAL.names.forEach(name => {
 
-         const props = NORMAL[name];
 
-         const trackListChanges = () => {
 
-           this.trigger(`${name}trackchange`);
 
-         };
 
-         const tracks = this[props.getterName]();
 
-         tracks.addEventListener('removetrack', trackListChanges);
 
-         tracks.addEventListener('addtrack', trackListChanges);
 
-         this.on('dispose', () => {
 
-           tracks.removeEventListener('removetrack', trackListChanges);
 
-           tracks.removeEventListener('addtrack', trackListChanges);
 
-         });
 
-       });
 
-     }
 
-     /**
 
-      * Emulate TextTracks using vtt.js if necessary
 
-      *
 
-      * @fires Tech#vttjsloaded
 
-      * @fires Tech#vttjserror
 
-      */
 
-     addWebVttScript_() {
 
-       if (window.WebVTT) {
 
-         return;
 
-       }
 
-       // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
 
-       // signals that the Tech is ready at which point Tech.el_ is part of the DOM
 
-       // before inserting the WebVTT script
 
-       if (document.body.contains(this.el())) {
 
-         // load via require if available and vtt.js script location was not passed in
 
-         // as an option. novtt builds will turn the above require call into an empty object
 
-         // which will cause this if check to always fail.
 
-         if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
 
-           this.trigger('vttjsloaded');
 
-           return;
 
-         }
 
-         // load vtt.js via the script location option or the cdn of no location was
 
-         // passed in
 
-         const script = document.createElement('script');
 
-         script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
 
-         script.onload = () => {
 
-           /**
 
-            * Fired when vtt.js is loaded.
 
-            *
 
-            * @event Tech#vttjsloaded
 
-            * @type {Event}
 
-            */
 
-           this.trigger('vttjsloaded');
 
-         };
 
-         script.onerror = () => {
 
-           /**
 
-            * Fired when vtt.js was not loaded due to an error
 
-            *
 
-            * @event Tech#vttjsloaded
 
-            * @type {Event}
 
-            */
 
-           this.trigger('vttjserror');
 
-         };
 
-         this.on('dispose', () => {
 
-           script.onload = null;
 
-           script.onerror = null;
 
-         });
 
-         // but have not loaded yet and we set it to true before the inject so that
 
-         // we don't overwrite the injected window.WebVTT if it loads right away
 
-         window.WebVTT = true;
 
-         this.el().parentNode.appendChild(script);
 
-       } else {
 
-         this.ready(this.addWebVttScript_);
 
-       }
 
-     }
 
-     /**
 
-      * Emulate texttracks
 
-      *
 
-      */
 
-     emulateTextTracks() {
 
-       const tracks = this.textTracks();
 
-       const remoteTracks = this.remoteTextTracks();
 
-       const handleAddTrack = e => tracks.addTrack(e.track);
 
-       const handleRemoveTrack = e => tracks.removeTrack(e.track);
 
-       remoteTracks.on('addtrack', handleAddTrack);
 
-       remoteTracks.on('removetrack', handleRemoveTrack);
 
-       this.addWebVttScript_();
 
-       const updateDisplay = () => this.trigger('texttrackchange');
 
-       const textTracksChanges = () => {
 
-         updateDisplay();
 
-         for (let i = 0; i < tracks.length; i++) {
 
-           const track = tracks[i];
 
-           track.removeEventListener('cuechange', updateDisplay);
 
-           if (track.mode === 'showing') {
 
-             track.addEventListener('cuechange', updateDisplay);
 
-           }
 
-         }
 
-       };
 
-       textTracksChanges();
 
-       tracks.addEventListener('change', textTracksChanges);
 
-       tracks.addEventListener('addtrack', textTracksChanges);
 
-       tracks.addEventListener('removetrack', textTracksChanges);
 
-       this.on('dispose', function () {
 
-         remoteTracks.off('addtrack', handleAddTrack);
 
-         remoteTracks.off('removetrack', handleRemoveTrack);
 
-         tracks.removeEventListener('change', textTracksChanges);
 
-         tracks.removeEventListener('addtrack', textTracksChanges);
 
-         tracks.removeEventListener('removetrack', textTracksChanges);
 
-         for (let i = 0; i < tracks.length; i++) {
 
-           const track = tracks[i];
 
-           track.removeEventListener('cuechange', updateDisplay);
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * Create and returns a remote {@link TextTrack} object.
 
-      *
 
-      * @param {string} kind
 
-      *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
 
-      *
 
-      * @param {string} [label]
 
-      *        Label to identify the text track
 
-      *
 
-      * @param {string} [language]
 
-      *        Two letter language abbreviation
 
-      *
 
-      * @return {TextTrack}
 
-      *         The TextTrack that gets created.
 
-      */
 
-     addTextTrack(kind, label, language) {
 
-       if (!kind) {
 
-         throw new Error('TextTrack kind is required but was not provided');
 
-       }
 
-       return createTrackHelper(this, kind, label, language);
 
-     }
 
-     /**
 
-      * Create an emulated TextTrack for use by addRemoteTextTrack
 
-      *
 
-      * This is intended to be overridden by classes that inherit from
 
-      * Tech in order to create native or custom TextTracks.
 
-      *
 
-      * @param {Object} options
 
-      *        The object should contain the options to initialize the TextTrack with.
 
-      *
 
-      * @param {string} [options.kind]
 
-      *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
 
-      *
 
-      * @param {string} [options.label].
 
-      *        Label to identify the text track
 
-      *
 
-      * @param {string} [options.language]
 
-      *        Two letter language abbreviation.
 
-      *
 
-      * @return {HTMLTrackElement}
 
-      *         The track element that gets created.
 
-      */
 
-     createRemoteTextTrack(options) {
 
-       const track = merge$2(options, {
 
-         tech: this
 
-       });
 
-       return new REMOTE.remoteTextEl.TrackClass(track);
 
-     }
 
-     /**
 
-      * Creates a remote text track object and returns an html track element.
 
-      *
 
-      * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
 
-      *
 
-      * @param {Object} options
 
-      *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
 
-      *
 
-      * @param {boolean} [manualCleanup=false]
 
-      *        - When false: the TextTrack will be automatically removed from the video
 
-      *          element whenever the source changes
 
-      *        - When True: The TextTrack will have to be cleaned up manually
 
-      *
 
-      * @return {HTMLTrackElement}
 
-      *         An Html Track Element.
 
-      *
 
-      */
 
-     addRemoteTextTrack(options = {}, manualCleanup) {
 
-       const htmlTrackElement = this.createRemoteTextTrack(options);
 
-       if (typeof manualCleanup !== 'boolean') {
 
-         manualCleanup = false;
 
-       }
 
-       // store HTMLTrackElement and TextTrack to remote list
 
-       this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
 
-       this.remoteTextTracks().addTrack(htmlTrackElement.track);
 
-       if (manualCleanup === false) {
 
-         // create the TextTrackList if it doesn't exist
 
-         this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
 
-       }
 
-       return htmlTrackElement;
 
-     }
 
-     /**
 
-      * Remove a remote text track from the remote `TextTrackList`.
 
-      *
 
-      * @param {TextTrack} track
 
-      *        `TextTrack` to remove from the `TextTrackList`
 
-      */
 
-     removeRemoteTextTrack(track) {
 
-       const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
 
-       // remove HTMLTrackElement and TextTrack from remote list
 
-       this.remoteTextTrackEls().removeTrackElement_(trackElement);
 
-       this.remoteTextTracks().removeTrack(track);
 
-       this.autoRemoteTextTracks_.removeTrack(track);
 
-     }
 
-     /**
 
-      * Gets available media playback quality metrics as specified by the W3C's Media
 
-      * Playback Quality API.
 
-      *
 
-      * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
 
-      *
 
-      * @return {Object}
 
-      *         An object with supported media playback quality metrics
 
-      *
 
-      * @abstract
 
-      */
 
-     getVideoPlaybackQuality() {
 
-       return {};
 
-     }
 
-     /**
 
-      * Attempt to create a floating video window always on top of other windows
 
-      * so that users may continue consuming media while they interact with other
 
-      * content sites, or applications on their device.
 
-      *
 
-      * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
 
-      *
 
-      * @return {Promise|undefined}
 
-      *         A promise with a Picture-in-Picture window if the browser supports
 
-      *         Promises (or one was passed in as an option). It returns undefined
 
-      *         otherwise.
 
-      *
 
-      * @abstract
 
-      */
 
-     requestPictureInPicture() {
 
-       return Promise.reject();
 
-     }
 
-     /**
 
-      * A method to check for the value of the 'disablePictureInPicture' <video> property.
 
-      * Defaults to true, as it should be considered disabled if the tech does not support pip
 
-      *
 
-      * @abstract
 
-      */
 
-     disablePictureInPicture() {
 
-       return true;
 
-     }
 
-     /**
 
-      * A method to set or unset the 'disablePictureInPicture' <video> property.
 
-      *
 
-      * @abstract
 
-      */
 
-     setDisablePictureInPicture() {}
 
-     /**
 
-      * A fallback implementation of requestVideoFrameCallback using requestAnimationFrame
 
-      *
 
-      * @param {function} cb
 
-      * @return {number} request id
 
-      */
 
-     requestVideoFrameCallback(cb) {
 
-       const id = newGUID();
 
-       if (!this.isReady_ || this.paused()) {
 
-         this.queuedHanders_.add(id);
 
-         this.one('playing', () => {
 
-           if (this.queuedHanders_.has(id)) {
 
-             this.queuedHanders_.delete(id);
 
-             cb();
 
-           }
 
-         });
 
-       } else {
 
-         this.requestNamedAnimationFrame(id, cb);
 
-       }
 
-       return id;
 
-     }
 
-     /**
 
-      * A fallback implementation of cancelVideoFrameCallback
 
-      *
 
-      * @param {number} id id of callback to be cancelled
 
-      */
 
-     cancelVideoFrameCallback(id) {
 
-       if (this.queuedHanders_.has(id)) {
 
-         this.queuedHanders_.delete(id);
 
-       } else {
 
-         this.cancelNamedAnimationFrame(id);
 
-       }
 
-     }
 
-     /**
 
-      * A method to set a poster from a `Tech`.
 
-      *
 
-      * @abstract
 
-      */
 
-     setPoster() {}
 
-     /**
 
-      * A method to check for the presence of the 'playsinline' <video> attribute.
 
-      *
 
-      * @abstract
 
-      */
 
-     playsinline() {}
 
-     /**
 
-      * A method to set or unset the 'playsinline' <video> attribute.
 
-      *
 
-      * @abstract
 
-      */
 
-     setPlaysinline() {}
 
-     /**
 
-      * Attempt to force override of native audio tracks.
 
-      *
 
-      * @param {boolean} override - If set to true native audio will be overridden,
 
-      * otherwise native audio will potentially be used.
 
-      *
 
-      * @abstract
 
-      */
 
-     overrideNativeAudioTracks(override) {}
 
-     /**
 
-      * Attempt to force override of native video tracks.
 
-      *
 
-      * @param {boolean} override - If set to true native video will be overridden,
 
-      * otherwise native video will potentially be used.
 
-      *
 
-      * @abstract
 
-      */
 
-     overrideNativeVideoTracks(override) {}
 
-     /**
 
-      * Check if the tech can support the given mime-type.
 
-      *
 
-      * The base tech does not support any type, but source handlers might
 
-      * overwrite this.
 
-      *
 
-      * @param  {string} _type
 
-      *         The mimetype to check for support
 
-      *
 
-      * @return {string}
 
-      *         'probably', 'maybe', or empty string
 
-      *
 
-      * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
 
-      *
 
-      * @abstract
 
-      */
 
-     canPlayType(_type) {
 
-       return '';
 
-     }
 
-     /**
 
-      * Check if the type is supported by this tech.
 
-      *
 
-      * The base tech does not support any type, but source handlers might
 
-      * overwrite this.
 
-      *
 
-      * @param {string} _type
 
-      *        The media type to check
 
-      * @return {string} Returns the native video element's response
 
-      */
 
-     static canPlayType(_type) {
 
-       return '';
 
-     }
 
-     /**
 
-      * Check if the tech can support the given source
 
-      *
 
-      * @param {Object} srcObj
 
-      *        The source object
 
-      * @param {Object} options
 
-      *        The options passed to the tech
 
-      * @return {string} 'probably', 'maybe', or '' (empty string)
 
-      */
 
-     static canPlaySource(srcObj, options) {
 
-       return Tech.canPlayType(srcObj.type);
 
-     }
 
-     /*
 
-      * Return whether the argument is a Tech or not.
 
-      * Can be passed either a Class like `Html5` or a instance like `player.tech_`
 
-      *
 
-      * @param {Object} component
 
-      *        The item to check
 
-      *
 
-      * @return {boolean}
 
-      *         Whether it is a tech or not
 
-      *         - True if it is a tech
 
-      *         - False if it is not
 
-      */
 
-     static isTech(component) {
 
-       return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
 
-     }
 
-     /**
 
-      * Registers a `Tech` into a shared list for videojs.
 
-      *
 
-      * @param {string} name
 
-      *        Name of the `Tech` to register.
 
-      *
 
-      * @param {Object} tech
 
-      *        The `Tech` class to register.
 
-      */
 
-     static registerTech(name, tech) {
 
-       if (!Tech.techs_) {
 
-         Tech.techs_ = {};
 
-       }
 
-       if (!Tech.isTech(tech)) {
 
-         throw new Error(`Tech ${name} must be a Tech`);
 
-       }
 
-       if (!Tech.canPlayType) {
 
-         throw new Error('Techs must have a static canPlayType method on them');
 
-       }
 
-       if (!Tech.canPlaySource) {
 
-         throw new Error('Techs must have a static canPlaySource method on them');
 
-       }
 
-       name = toTitleCase$1(name);
 
-       Tech.techs_[name] = tech;
 
-       Tech.techs_[toLowerCase(name)] = tech;
 
-       if (name !== 'Tech') {
 
-         // camel case the techName for use in techOrder
 
-         Tech.defaultTechOrder_.push(name);
 
-       }
 
-       return tech;
 
-     }
 
-     /**
 
-      * Get a `Tech` from the shared list by name.
 
-      *
 
-      * @param {string} name
 
-      *        `camelCase` or `TitleCase` name of the Tech to get
 
-      *
 
-      * @return {Tech|undefined}
 
-      *         The `Tech` or undefined if there was no tech with the name requested.
 
-      */
 
-     static getTech(name) {
 
-       if (!name) {
 
-         return;
 
-       }
 
-       if (Tech.techs_ && Tech.techs_[name]) {
 
-         return Tech.techs_[name];
 
-       }
 
-       name = toTitleCase$1(name);
 
-       if (window && window.videojs && window.videojs[name]) {
 
-         log$1.warn(`The ${name} tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)`);
 
-         return window.videojs[name];
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * Get the {@link VideoTrackList}
 
-    *
 
-    * @returns {VideoTrackList}
 
-    * @method Tech.prototype.videoTracks
 
-    */
 
-   /**
 
-    * Get the {@link AudioTrackList}
 
-    *
 
-    * @returns {AudioTrackList}
 
-    * @method Tech.prototype.audioTracks
 
-    */
 
-   /**
 
-    * Get the {@link TextTrackList}
 
-    *
 
-    * @returns {TextTrackList}
 
-    * @method Tech.prototype.textTracks
 
-    */
 
-   /**
 
-    * Get the remote element {@link TextTrackList}
 
-    *
 
-    * @returns {TextTrackList}
 
-    * @method Tech.prototype.remoteTextTracks
 
-    */
 
-   /**
 
-    * Get the remote element {@link HtmlTrackElementList}
 
-    *
 
-    * @returns {HtmlTrackElementList}
 
-    * @method Tech.prototype.remoteTextTrackEls
 
-    */
 
-   ALL.names.forEach(function (name) {
 
-     const props = ALL[name];
 
-     Tech.prototype[props.getterName] = function () {
 
-       this[props.privateName] = this[props.privateName] || new props.ListClass();
 
-       return this[props.privateName];
 
-     };
 
-   });
 
-   /**
 
-    * List of associated text tracks
 
-    *
 
-    * @type {TextTrackList}
 
-    * @private
 
-    * @property Tech#textTracks_
 
-    */
 
-   /**
 
-    * List of associated audio tracks.
 
-    *
 
-    * @type {AudioTrackList}
 
-    * @private
 
-    * @property Tech#audioTracks_
 
-    */
 
-   /**
 
-    * List of associated video tracks.
 
-    *
 
-    * @type {VideoTrackList}
 
-    * @private
 
-    * @property Tech#videoTracks_
 
-    */
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports volume control.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Tech.prototype.featuresVolumeControl = true;
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports muting volume.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Tech.prototype.featuresMuteControl = true;
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports fullscreen resize control.
 
-    * Resizing plugins using request fullscreen reloads the plugin
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Tech.prototype.featuresFullscreenResize = false;
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports changing the speed at which the video
 
-    * plays. Examples:
 
-    *   - Set player to play 2x (twice) as fast
 
-    *   - Set player to play 0.5x (half) as fast
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Tech.prototype.featuresPlaybackRate = false;
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports the `progress` event.
 
-    * This will be used to determine if {@link Tech#manualProgressOn} should be called.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Tech.prototype.featuresProgressEvents = false;
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports the `sourceset` event.
 
-    *
 
-    * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
 
-    * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
 
-    * a new source.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Tech.prototype.featuresSourceset = false;
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports the `timeupdate` event.
 
-    * This will be used to determine if {@link Tech#manualTimeUpdates} should be called.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Tech.prototype.featuresTimeupdateEvents = false;
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
 
-    * This will help us integrate with native `TextTrack`s if the browser supports them.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Tech.prototype.featuresNativeTextTracks = false;
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports `requestVideoFrameCallback`.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Tech.prototype.featuresVideoFrameCallback = false;
 
-   /**
 
-    * A functional mixin for techs that want to use the Source Handler pattern.
 
-    * Source handlers are scripts for handling specific formats.
 
-    * The source handler pattern is used for adaptive formats (HLS, DASH) that
 
-    * manually load video data and feed it into a Source Buffer (Media Source Extensions)
 
-    * Example: `Tech.withSourceHandlers.call(MyTech);`
 
-    *
 
-    * @param {Tech} _Tech
 
-    *        The tech to add source handler functions to.
 
-    *
 
-    * @mixes Tech~SourceHandlerAdditions
 
-    */
 
-   Tech.withSourceHandlers = function (_Tech) {
 
-     /**
 
-      * Register a source handler
 
-      *
 
-      * @param {Function} handler
 
-      *        The source handler class
 
-      *
 
-      * @param {number} [index]
 
-      *        Register it at the following index
 
-      */
 
-     _Tech.registerSourceHandler = function (handler, index) {
 
-       let handlers = _Tech.sourceHandlers;
 
-       if (!handlers) {
 
-         handlers = _Tech.sourceHandlers = [];
 
-       }
 
-       if (index === undefined) {
 
-         // add to the end of the list
 
-         index = handlers.length;
 
-       }
 
-       handlers.splice(index, 0, handler);
 
-     };
 
-     /**
 
-      * Check if the tech can support the given type. Also checks the
 
-      * Techs sourceHandlers.
 
-      *
 
-      * @param {string} type
 
-      *         The mimetype to check.
 
-      *
 
-      * @return {string}
 
-      *         'probably', 'maybe', or '' (empty string)
 
-      */
 
-     _Tech.canPlayType = function (type) {
 
-       const handlers = _Tech.sourceHandlers || [];
 
-       let can;
 
-       for (let i = 0; i < handlers.length; i++) {
 
-         can = handlers[i].canPlayType(type);
 
-         if (can) {
 
-           return can;
 
-         }
 
-       }
 
-       return '';
 
-     };
 
-     /**
 
-      * Returns the first source handler that supports the source.
 
-      *
 
-      * TODO: Answer question: should 'probably' be prioritized over 'maybe'
 
-      *
 
-      * @param {Tech~SourceObject} source
 
-      *        The source object
 
-      *
 
-      * @param {Object} options
 
-      *        The options passed to the tech
 
-      *
 
-      * @return {SourceHandler|null}
 
-      *          The first source handler that supports the source or null if
 
-      *          no SourceHandler supports the source
 
-      */
 
-     _Tech.selectSourceHandler = function (source, options) {
 
-       const handlers = _Tech.sourceHandlers || [];
 
-       let can;
 
-       for (let i = 0; i < handlers.length; i++) {
 
-         can = handlers[i].canHandleSource(source, options);
 
-         if (can) {
 
-           return handlers[i];
 
-         }
 
-       }
 
-       return null;
 
-     };
 
-     /**
 
-      * Check if the tech can support the given source.
 
-      *
 
-      * @param {Tech~SourceObject} srcObj
 
-      *        The source object
 
-      *
 
-      * @param {Object} options
 
-      *        The options passed to the tech
 
-      *
 
-      * @return {string}
 
-      *         'probably', 'maybe', or '' (empty string)
 
-      */
 
-     _Tech.canPlaySource = function (srcObj, options) {
 
-       const sh = _Tech.selectSourceHandler(srcObj, options);
 
-       if (sh) {
 
-         return sh.canHandleSource(srcObj, options);
 
-       }
 
-       return '';
 
-     };
 
-     /**
 
-      * When using a source handler, prefer its implementation of
 
-      * any function normally provided by the tech.
 
-      */
 
-     const deferrable = ['seekable', 'seeking', 'duration'];
 
-     /**
 
-      * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
 
-      * function if it exists, with a fallback to the Techs seekable function.
 
-      *
 
-      * @method _Tech.seekable
 
-      */
 
-     /**
 
-      * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
 
-      * function if it exists, otherwise it will fallback to the techs duration function.
 
-      *
 
-      * @method _Tech.duration
 
-      */
 
-     deferrable.forEach(function (fnName) {
 
-       const originalFn = this[fnName];
 
-       if (typeof originalFn !== 'function') {
 
-         return;
 
-       }
 
-       this[fnName] = function () {
 
-         if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
 
-           return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
 
-         }
 
-         return originalFn.apply(this, arguments);
 
-       };
 
-     }, _Tech.prototype);
 
-     /**
 
-      * Create a function for setting the source using a source object
 
-      * and source handlers.
 
-      * Should never be called unless a source handler was found.
 
-      *
 
-      * @param {Tech~SourceObject} source
 
-      *        A source object with src and type keys
 
-      */
 
-     _Tech.prototype.setSource = function (source) {
 
-       let sh = _Tech.selectSourceHandler(source, this.options_);
 
-       if (!sh) {
 
-         // Fall back to a native source handler when unsupported sources are
 
-         // deliberately set
 
-         if (_Tech.nativeSourceHandler) {
 
-           sh = _Tech.nativeSourceHandler;
 
-         } else {
 
-           log$1.error('No source handler found for the current source.');
 
-         }
 
-       }
 
-       // Dispose any existing source handler
 
-       this.disposeSourceHandler();
 
-       this.off('dispose', this.disposeSourceHandler_);
 
-       if (sh !== _Tech.nativeSourceHandler) {
 
-         this.currentSource_ = source;
 
-       }
 
-       this.sourceHandler_ = sh.handleSource(source, this, this.options_);
 
-       this.one('dispose', this.disposeSourceHandler_);
 
-     };
 
-     /**
 
-      * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
 
-      *
 
-      * @listens Tech#dispose
 
-      */
 
-     _Tech.prototype.disposeSourceHandler = function () {
 
-       // if we have a source and get another one
 
-       // then we are loading something new
 
-       // than clear all of our current tracks
 
-       if (this.currentSource_) {
 
-         this.clearTracks(['audio', 'video']);
 
-         this.currentSource_ = null;
 
-       }
 
-       // always clean up auto-text tracks
 
-       this.cleanupAutoTextTracks();
 
-       if (this.sourceHandler_) {
 
-         if (this.sourceHandler_.dispose) {
 
-           this.sourceHandler_.dispose();
 
-         }
 
-         this.sourceHandler_ = null;
 
-       }
 
-     };
 
-   };
 
-   // The base Tech class needs to be registered as a Component. It is the only
 
-   // Tech that can be registered as a Component.
 
-   Component$1.registerComponent('Tech', Tech);
 
-   Tech.registerTech('Tech', Tech);
 
-   /**
 
-    * A list of techs that should be added to techOrder on Players
 
-    *
 
-    * @private
 
-    */
 
-   Tech.defaultTechOrder_ = [];
 
-   /**
 
-    * @file middleware.js
 
-    * @module middleware
 
-    */
 
-   const middlewares = {};
 
-   const middlewareInstances = {};
 
-   const TERMINATOR = {};
 
-   /**
 
-    * A middleware object is a plain JavaScript object that has methods that
 
-    * match the {@link Tech} methods found in the lists of allowed
 
-    * {@link module:middleware.allowedGetters|getters},
 
-    * {@link module:middleware.allowedSetters|setters}, and
 
-    * {@link module:middleware.allowedMediators|mediators}.
 
-    *
 
-    * @typedef {Object} MiddlewareObject
 
-    */
 
-   /**
 
-    * A middleware factory function that should return a
 
-    * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
 
-    *
 
-    * This factory will be called for each player when needed, with the player
 
-    * passed in as an argument.
 
-    *
 
-    * @callback MiddlewareFactory
 
-    * @param { import('../player').default } player
 
-    *        A Video.js player.
 
-    */
 
-   /**
 
-    * Define a middleware that the player should use by way of a factory function
 
-    * that returns a middleware object.
 
-    *
 
-    * @param  {string} type
 
-    *         The MIME type to match or `"*"` for all MIME types.
 
-    *
 
-    * @param  {MiddlewareFactory} middleware
 
-    *         A middleware factory function that will be executed for
 
-    *         matching types.
 
-    */
 
-   function use(type, middleware) {
 
-     middlewares[type] = middlewares[type] || [];
 
-     middlewares[type].push(middleware);
 
-   }
 
-   /**
 
-    * Asynchronously sets a source using middleware by recursing through any
 
-    * matching middlewares and calling `setSource` on each, passing along the
 
-    * previous returned value each time.
 
-    *
 
-    * @param  { import('../player').default } player
 
-    *         A {@link Player} instance.
 
-    *
 
-    * @param  {Tech~SourceObject} src
 
-    *         A source object.
 
-    *
 
-    * @param  {Function}
 
-    *         The next middleware to run.
 
-    */
 
-   function setSource(player, src, next) {
 
-     player.setTimeout(() => setSourceHelper(src, middlewares[src.type], next, player), 1);
 
-   }
 
-   /**
 
-    * When the tech is set, passes the tech to each middleware's `setTech` method.
 
-    *
 
-    * @param {Object[]} middleware
 
-    *        An array of middleware instances.
 
-    *
 
-    * @param { import('../tech/tech').default } tech
 
-    *        A Video.js tech.
 
-    */
 
-   function setTech(middleware, tech) {
 
-     middleware.forEach(mw => mw.setTech && mw.setTech(tech));
 
-   }
 
-   /**
 
-    * Calls a getter on the tech first, through each middleware
 
-    * from right to left to the player.
 
-    *
 
-    * @param  {Object[]} middleware
 
-    *         An array of middleware instances.
 
-    *
 
-    * @param  { import('../tech/tech').default } tech
 
-    *         The current tech.
 
-    *
 
-    * @param  {string} method
 
-    *         A method name.
 
-    *
 
-    * @return {*}
 
-    *         The final value from the tech after middleware has intercepted it.
 
-    */
 
-   function get(middleware, tech, method) {
 
-     return middleware.reduceRight(middlewareIterator(method), tech[method]());
 
-   }
 
-   /**
 
-    * Takes the argument given to the player and calls the setter method on each
 
-    * middleware from left to right to the tech.
 
-    *
 
-    * @param  {Object[]} middleware
 
-    *         An array of middleware instances.
 
-    *
 
-    * @param  { import('../tech/tech').default } tech
 
-    *         The current tech.
 
-    *
 
-    * @param  {string} method
 
-    *         A method name.
 
-    *
 
-    * @param  {*} arg
 
-    *         The value to set on the tech.
 
-    *
 
-    * @return {*}
 
-    *         The return value of the `method` of the `tech`.
 
-    */
 
-   function set(middleware, tech, method, arg) {
 
-     return tech[method](middleware.reduce(middlewareIterator(method), arg));
 
-   }
 
-   /**
 
-    * Takes the argument given to the player and calls the `call` version of the
 
-    * method on each middleware from left to right.
 
-    *
 
-    * Then, call the passed in method on the tech and return the result unchanged
 
-    * back to the player, through middleware, this time from right to left.
 
-    *
 
-    * @param  {Object[]} middleware
 
-    *         An array of middleware instances.
 
-    *
 
-    * @param  { import('../tech/tech').default } tech
 
-    *         The current tech.
 
-    *
 
-    * @param  {string} method
 
-    *         A method name.
 
-    *
 
-    * @param  {*} arg
 
-    *         The value to set on the tech.
 
-    *
 
-    * @return {*}
 
-    *         The return value of the `method` of the `tech`, regardless of the
 
-    *         return values of middlewares.
 
-    */
 
-   function mediate(middleware, tech, method, arg = null) {
 
-     const callMethod = 'call' + toTitleCase$1(method);
 
-     const middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
 
-     const terminated = middlewareValue === TERMINATOR;
 
-     // deprecated. The `null` return value should instead return TERMINATOR to
 
-     // prevent confusion if a techs method actually returns null.
 
-     const returnValue = terminated ? null : tech[method](middlewareValue);
 
-     executeRight(middleware, method, returnValue, terminated);
 
-     return returnValue;
 
-   }
 
-   /**
 
-    * Enumeration of allowed getters where the keys are method names.
 
-    *
 
-    * @type {Object}
 
-    */
 
-   const allowedGetters = {
 
-     buffered: 1,
 
-     currentTime: 1,
 
-     duration: 1,
 
-     muted: 1,
 
-     played: 1,
 
-     paused: 1,
 
-     seekable: 1,
 
-     volume: 1,
 
-     ended: 1
 
-   };
 
-   /**
 
-    * Enumeration of allowed setters where the keys are method names.
 
-    *
 
-    * @type {Object}
 
-    */
 
-   const allowedSetters = {
 
-     setCurrentTime: 1,
 
-     setMuted: 1,
 
-     setVolume: 1
 
-   };
 
-   /**
 
-    * Enumeration of allowed mediators where the keys are method names.
 
-    *
 
-    * @type {Object}
 
-    */
 
-   const allowedMediators = {
 
-     play: 1,
 
-     pause: 1
 
-   };
 
-   function middlewareIterator(method) {
 
-     return (value, mw) => {
 
-       // if the previous middleware terminated, pass along the termination
 
-       if (value === TERMINATOR) {
 
-         return TERMINATOR;
 
-       }
 
-       if (mw[method]) {
 
-         return mw[method](value);
 
-       }
 
-       return value;
 
-     };
 
-   }
 
-   function executeRight(mws, method, value, terminated) {
 
-     for (let i = mws.length - 1; i >= 0; i--) {
 
-       const mw = mws[i];
 
-       if (mw[method]) {
 
-         mw[method](terminated, value);
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * Clear the middleware cache for a player.
 
-    *
 
-    * @param  { import('../player').default } player
 
-    *         A {@link Player} instance.
 
-    */
 
-   function clearCacheForPlayer(player) {
 
-     middlewareInstances[player.id()] = null;
 
-   }
 
-   /**
 
-    * {
 
-    *  [playerId]: [[mwFactory, mwInstance], ...]
 
-    * }
 
-    *
 
-    * @private
 
-    */
 
-   function getOrCreateFactory(player, mwFactory) {
 
-     const mws = middlewareInstances[player.id()];
 
-     let mw = null;
 
-     if (mws === undefined || mws === null) {
 
-       mw = mwFactory(player);
 
-       middlewareInstances[player.id()] = [[mwFactory, mw]];
 
-       return mw;
 
-     }
 
-     for (let i = 0; i < mws.length; i++) {
 
-       const [mwf, mwi] = mws[i];
 
-       if (mwf !== mwFactory) {
 
-         continue;
 
-       }
 
-       mw = mwi;
 
-     }
 
-     if (mw === null) {
 
-       mw = mwFactory(player);
 
-       mws.push([mwFactory, mw]);
 
-     }
 
-     return mw;
 
-   }
 
-   function setSourceHelper(src = {}, middleware = [], next, player, acc = [], lastRun = false) {
 
-     const [mwFactory, ...mwrest] = middleware;
 
-     // if mwFactory is a string, then we're at a fork in the road
 
-     if (typeof mwFactory === 'string') {
 
-       setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
 
-       // if we have an mwFactory, call it with the player to get the mw,
 
-       // then call the mw's setSource method
 
-     } else if (mwFactory) {
 
-       const mw = getOrCreateFactory(player, mwFactory);
 
-       // if setSource isn't present, implicitly select this middleware
 
-       if (!mw.setSource) {
 
-         acc.push(mw);
 
-         return setSourceHelper(src, mwrest, next, player, acc, lastRun);
 
-       }
 
-       mw.setSource(Object.assign({}, src), function (err, _src) {
 
-         // something happened, try the next middleware on the current level
 
-         // make sure to use the old src
 
-         if (err) {
 
-           return setSourceHelper(src, mwrest, next, player, acc, lastRun);
 
-         }
 
-         // we've succeeded, now we need to go deeper
 
-         acc.push(mw);
 
-         // if it's the same type, continue down the current chain
 
-         // otherwise, we want to go down the new chain
 
-         setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
 
-       });
 
-     } else if (mwrest.length) {
 
-       setSourceHelper(src, mwrest, next, player, acc, lastRun);
 
-     } else if (lastRun) {
 
-       next(src, acc);
 
-     } else {
 
-       setSourceHelper(src, middlewares['*'], next, player, acc, true);
 
-     }
 
-   }
 
-   /**
 
-    * Mimetypes
 
-    *
 
-    * @see https://www.iana.org/assignments/media-types/media-types.xhtml
 
-    * @typedef Mimetypes~Kind
 
-    * @enum
 
-    */
 
-   const MimetypesKind = {
 
-     opus: 'video/ogg',
 
-     ogv: 'video/ogg',
 
-     mp4: 'video/mp4',
 
-     mov: 'video/mp4',
 
-     m4v: 'video/mp4',
 
-     mkv: 'video/x-matroska',
 
-     m4a: 'audio/mp4',
 
-     mp3: 'audio/mpeg',
 
-     aac: 'audio/aac',
 
-     caf: 'audio/x-caf',
 
-     flac: 'audio/flac',
 
-     oga: 'audio/ogg',
 
-     wav: 'audio/wav',
 
-     m3u8: 'application/x-mpegURL',
 
-     mpd: 'application/dash+xml',
 
-     jpg: 'image/jpeg',
 
-     jpeg: 'image/jpeg',
 
-     gif: 'image/gif',
 
-     png: 'image/png',
 
-     svg: 'image/svg+xml',
 
-     webp: 'image/webp'
 
-   };
 
-   /**
 
-    * Get the mimetype of a given src url if possible
 
-    *
 
-    * @param {string} src
 
-    *        The url to the src
 
-    *
 
-    * @return {string}
 
-    *         return the mimetype if it was known or empty string otherwise
 
-    */
 
-   const getMimetype = function (src = '') {
 
-     const ext = getFileExtension(src);
 
-     const mimetype = MimetypesKind[ext.toLowerCase()];
 
-     return mimetype || '';
 
-   };
 
-   /**
 
-    * Find the mime type of a given source string if possible. Uses the player
 
-    * source cache.
 
-    *
 
-    * @param { import('../player').default } player
 
-    *        The player object
 
-    *
 
-    * @param {string} src
 
-    *        The source string
 
-    *
 
-    * @return {string}
 
-    *         The type that was found
 
-    */
 
-   const findMimetype = (player, src) => {
 
-     if (!src) {
 
-       return '';
 
-     }
 
-     // 1. check for the type in the `source` cache
 
-     if (player.cache_.source.src === src && player.cache_.source.type) {
 
-       return player.cache_.source.type;
 
-     }
 
-     // 2. see if we have this source in our `currentSources` cache
 
-     const matchingSources = player.cache_.sources.filter(s => s.src === src);
 
-     if (matchingSources.length) {
 
-       return matchingSources[0].type;
 
-     }
 
-     // 3. look for the src url in source elements and use the type there
 
-     const sources = player.$$('source');
 
-     for (let i = 0; i < sources.length; i++) {
 
-       const s = sources[i];
 
-       if (s.type && s.src && s.src === src) {
 
-         return s.type;
 
-       }
 
-     }
 
-     // 4. finally fallback to our list of mime types based on src url extension
 
-     return getMimetype(src);
 
-   };
 
-   /**
 
-    * @module filter-source
 
-    */
 
-   /**
 
-    * Filter out single bad source objects or multiple source objects in an
 
-    * array. Also flattens nested source object arrays into a 1 dimensional
 
-    * array of source objects.
 
-    *
 
-    * @param {Tech~SourceObject|Tech~SourceObject[]} src
 
-    *        The src object to filter
 
-    *
 
-    * @return {Tech~SourceObject[]}
 
-    *         An array of sourceobjects containing only valid sources
 
-    *
 
-    * @private
 
-    */
 
-   const filterSource = function (src) {
 
-     // traverse array
 
-     if (Array.isArray(src)) {
 
-       let newsrc = [];
 
-       src.forEach(function (srcobj) {
 
-         srcobj = filterSource(srcobj);
 
-         if (Array.isArray(srcobj)) {
 
-           newsrc = newsrc.concat(srcobj);
 
-         } else if (isObject$1(srcobj)) {
 
-           newsrc.push(srcobj);
 
-         }
 
-       });
 
-       src = newsrc;
 
-     } else if (typeof src === 'string' && src.trim()) {
 
-       // convert string into object
 
-       src = [fixSource({
 
-         src
 
-       })];
 
-     } else if (isObject$1(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
 
-       // src is already valid
 
-       src = [fixSource(src)];
 
-     } else {
 
-       // invalid source, turn it into an empty array
 
-       src = [];
 
-     }
 
-     return src;
 
-   };
 
-   /**
 
-    * Checks src mimetype, adding it when possible
 
-    *
 
-    * @param {Tech~SourceObject} src
 
-    *        The src object to check
 
-    * @return {Tech~SourceObject}
 
-    *        src Object with known type
 
-    */
 
-   function fixSource(src) {
 
-     if (!src.type) {
 
-       const mimetype = getMimetype(src.src);
 
-       if (mimetype) {
 
-         src.type = mimetype;
 
-       }
 
-     }
 
-     return src;
 
-   }
 
-   /**
 
-    * @file loader.js
 
-    */
 
-   /**
 
-    * The `MediaLoader` is the `Component` that decides which playback technology to load
 
-    * when a player is initialized.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class MediaLoader extends Component$1 {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param { import('../player').default } player
 
-      *        The `Player` that this class should attach to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @param {Function} [ready]
 
-      *        The function that is run when this component is ready.
 
-      */
 
-     constructor(player, options, ready) {
 
-       // MediaLoader has no element
 
-       const options_ = merge$2({
 
-         createEl: false
 
-       }, options);
 
-       super(player, options_, ready);
 
-       // If there are no sources when the player is initialized,
 
-       // load the first supported playback technology.
 
-       if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
 
-         for (let i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
 
-           const techName = toTitleCase$1(j[i]);
 
-           let tech = Tech.getTech(techName);
 
-           // Support old behavior of techs being registered as components.
 
-           // Remove once that deprecated behavior is removed.
 
-           if (!techName) {
 
-             tech = Component$1.getComponent(techName);
 
-           }
 
-           // Check if the browser supports this technology
 
-           if (tech && tech.isSupported()) {
 
-             player.loadTech_(techName);
 
-             break;
 
-           }
 
-         }
 
-       } else {
 
-         // Loop through playback technologies (e.g. HTML5) and check for support.
 
-         // Then load the best source.
 
-         // A few assumptions here:
 
-         //   All playback technologies respect preload false.
 
-         player.src(options.playerOptions.sources);
 
-       }
 
-     }
 
-   }
 
-   Component$1.registerComponent('MediaLoader', MediaLoader);
 
-   /**
 
-    * @file clickable-component.js
 
-    */
 
-   /**
 
-    * Component which is clickable or keyboard actionable, but is not a
 
-    * native HTML button.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class ClickableComponent extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param  { import('./player').default } player
 
-      *         The `Player` that this class should be attached to.
 
-      *
 
-      * @param  {Object} [options]
 
-      *         The key/value store of component options.
 
-      *
 
-      * @param  {function} [options.clickHandler]
 
-      *         The function to call when the button is clicked / activated
 
-      *
 
-      * @param  {string} [options.controlText]
 
-      *         The text to set on the button
 
-      *
 
-      * @param  {string} [options.className]
 
-      *         A class or space separated list of classes to add the component
 
-      *
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       if (this.options_.controlText) {
 
-         this.controlText(this.options_.controlText);
 
-       }
 
-       this.handleMouseOver_ = e => this.handleMouseOver(e);
 
-       this.handleMouseOut_ = e => this.handleMouseOut(e);
 
-       this.handleClick_ = e => this.handleClick(e);
 
-       this.handleKeyDown_ = e => this.handleKeyDown(e);
 
-       this.emitTapEvents();
 
-       this.enable();
 
-     }
 
-     /**
 
-      * Create the `ClickableComponent`s DOM element.
 
-      *
 
-      * @param {string} [tag=div]
 
-      *        The element's node type.
 
-      *
 
-      * @param {Object} [props={}]
 
-      *        An object of properties that should be set on the element.
 
-      *
 
-      * @param {Object} [attributes={}]
 
-      *        An object of attributes that should be set on the element.
 
-      *
 
-      * @return {Element}
 
-      *         The element that gets created.
 
-      */
 
-     createEl(tag = 'div', props = {}, attributes = {}) {
 
-       props = Object.assign({
 
-         className: this.buildCSSClass(),
 
-         tabIndex: 0
 
-       }, props);
 
-       if (tag === 'button') {
 
-         log$1.error(`Creating a ClickableComponent with an HTML element of ${tag} is not supported; use a Button instead.`);
 
-       }
 
-       // Add ARIA attributes for clickable element which is not a native HTML button
 
-       attributes = Object.assign({
 
-         role: 'button'
 
-       }, attributes);
 
-       this.tabIndex_ = props.tabIndex;
 
-       const el = createEl(tag, props, attributes);
 
-       el.appendChild(createEl('span', {
 
-         className: 'vjs-icon-placeholder'
 
-       }, {
 
-         'aria-hidden': true
 
-       }));
 
-       this.createControlTextEl(el);
 
-       return el;
 
-     }
 
-     dispose() {
 
-       // remove controlTextEl_ on dispose
 
-       this.controlTextEl_ = null;
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Create a control text element on this `ClickableComponent`
 
-      *
 
-      * @param {Element} [el]
 
-      *        Parent element for the control text.
 
-      *
 
-      * @return {Element}
 
-      *         The control text element that gets created.
 
-      */
 
-     createControlTextEl(el) {
 
-       this.controlTextEl_ = createEl('span', {
 
-         className: 'vjs-control-text'
 
-       }, {
 
-         // let the screen reader user know that the text of the element may change
 
-         'aria-live': 'polite'
 
-       });
 
-       if (el) {
 
-         el.appendChild(this.controlTextEl_);
 
-       }
 
-       this.controlText(this.controlText_, el);
 
-       return this.controlTextEl_;
 
-     }
 
-     /**
 
-      * Get or set the localize text to use for the controls on the `ClickableComponent`.
 
-      *
 
-      * @param {string} [text]
 
-      *        Control text for element.
 
-      *
 
-      * @param {Element} [el=this.el()]
 
-      *        Element to set the title on.
 
-      *
 
-      * @return {string}
 
-      *         - The control text when getting
 
-      */
 
-     controlText(text, el = this.el()) {
 
-       if (text === undefined) {
 
-         return this.controlText_ || 'Need Text';
 
-       }
 
-       const localizedText = this.localize(text);
 
-       /** @protected */
 
-       this.controlText_ = text;
 
-       textContent(this.controlTextEl_, localizedText);
 
-       if (!this.nonIconControl && !this.player_.options_.noUITitleAttributes) {
 
-         // Set title attribute if only an icon is shown
 
-         el.setAttribute('title', localizedText);
 
-       }
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-control vjs-button ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * Enable this `ClickableComponent`
 
-      */
 
-     enable() {
 
-       if (!this.enabled_) {
 
-         this.enabled_ = true;
 
-         this.removeClass('vjs-disabled');
 
-         this.el_.setAttribute('aria-disabled', 'false');
 
-         if (typeof this.tabIndex_ !== 'undefined') {
 
-           this.el_.setAttribute('tabIndex', this.tabIndex_);
 
-         }
 
-         this.on(['tap', 'click'], this.handleClick_);
 
-         this.on('keydown', this.handleKeyDown_);
 
-       }
 
-     }
 
-     /**
 
-      * Disable this `ClickableComponent`
 
-      */
 
-     disable() {
 
-       this.enabled_ = false;
 
-       this.addClass('vjs-disabled');
 
-       this.el_.setAttribute('aria-disabled', 'true');
 
-       if (typeof this.tabIndex_ !== 'undefined') {
 
-         this.el_.removeAttribute('tabIndex');
 
-       }
 
-       this.off('mouseover', this.handleMouseOver_);
 
-       this.off('mouseout', this.handleMouseOut_);
 
-       this.off(['tap', 'click'], this.handleClick_);
 
-       this.off('keydown', this.handleKeyDown_);
 
-     }
 
-     /**
 
-      * Handles language change in ClickableComponent for the player in components
 
-      *
 
-      *
 
-      */
 
-     handleLanguagechange() {
 
-       this.controlText(this.controlText_);
 
-     }
 
-     /**
 
-      * Event handler that is called when a `ClickableComponent` receives a
 
-      * `click` or `tap` event.
 
-      *
 
-      * @param {Event} event
 
-      *        The `tap` or `click` event that caused this function to be called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      * @abstract
 
-      */
 
-     handleClick(event) {
 
-       if (this.options_.clickHandler) {
 
-         this.options_.clickHandler.call(this, arguments);
 
-       }
 
-     }
 
-     /**
 
-      * Event handler that is called when a `ClickableComponent` receives a
 
-      * `keydown` event.
 
-      *
 
-      * By default, if the key is Space or Enter, it will trigger a `click` event.
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown` event that caused this function to be called.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       // Support Space or Enter key operation to fire a click event. Also,
 
-       // prevent the event from propagating through the DOM and triggering
 
-       // Player hotkeys.
 
-       if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.trigger('click');
 
-       } else {
 
-         // Pass keypress handling up for unsupported keys
 
-         super.handleKeyDown(event);
 
-       }
 
-     }
 
-   }
 
-   Component$1.registerComponent('ClickableComponent', ClickableComponent);
 
-   /**
 
-    * @file poster-image.js
 
-    */
 
-   /**
 
-    * A `ClickableComponent` that handles showing the poster image for the player.
 
-    *
 
-    * @extends ClickableComponent
 
-    */
 
-   class PosterImage extends ClickableComponent {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should attach to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.update();
 
-       this.update_ = e => this.update(e);
 
-       player.on('posterchange', this.update_);
 
-     }
 
-     /**
 
-      * Clean up and dispose of the `PosterImage`.
 
-      */
 
-     dispose() {
 
-       this.player().off('posterchange', this.update_);
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Create the `PosterImage`s DOM element.
 
-      *
 
-      * @return {Element}
 
-      *         The element that gets created.
 
-      */
 
-     createEl() {
 
-       // The el is an empty div to keep position in the DOM
 
-       // A picture and img el will be inserted when a source is set
 
-       return createEl('div', {
 
-         className: 'vjs-poster'
 
-       });
 
-     }
 
-     /**
 
-      * Get or set the `PosterImage`'s crossOrigin option.
 
-      *
 
-      * @param {string|null} [value]
 
-      *        The value to set the crossOrigin to. If an argument is
 
-      *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
 
-      *
 
-      * @return {string|null}
 
-      *         - The current crossOrigin value of the `Player` when getting.
 
-      *         - undefined when setting
 
-      */
 
-     crossOrigin(value) {
 
-       // `null` can be set to unset a value
 
-       if (typeof value === 'undefined') {
 
-         if (this.$('img')) {
 
-           // If the poster's element exists, give its value
 
-           return this.$('img').crossOrigin;
 
-         } else if (this.player_.tech_ && this.player_.tech_.isReady_) {
 
-           // If not but the tech is ready, query the tech
 
-           return this.player_.crossOrigin();
 
-         }
 
-         // Otherwise check options as the  poster is usually set before the state of crossorigin
 
-         // can be retrieved by the getter
 
-         return this.player_.options_.crossOrigin || this.player_.options_.crossorigin || null;
 
-       }
 
-       if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
 
-         this.player_.log.warn(`crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`);
 
-         return;
 
-       }
 
-       if (this.$('img')) {
 
-         this.$('img').crossOrigin = value;
 
-       }
 
-       return;
 
-     }
 
-     /**
 
-      * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
 
-      *
 
-      * @listens Player#posterchange
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `Player#posterchange` event that triggered this function.
 
-      */
 
-     update(event) {
 
-       const url = this.player().poster();
 
-       this.setSrc(url);
 
-       // If there's no poster source we should display:none on this component
 
-       // so it's not still clickable or right-clickable
 
-       if (url) {
 
-         this.show();
 
-       } else {
 
-         this.hide();
 
-       }
 
-     }
 
-     /**
 
-      * Set the source of the `PosterImage` depending on the display method. (Re)creates
 
-      * the inner picture and img elementss when needed.
 
-      *
 
-      * @param {string} [url]
 
-      *        The URL to the source for the `PosterImage`. If not specified or falsy,
 
-      *        any source and ant inner picture/img are removed.
 
-      */
 
-     setSrc(url) {
 
-       if (!url) {
 
-         this.el_.textContent = '';
 
-         return;
 
-       }
 
-       if (!this.$('img')) {
 
-         this.el_.appendChild(createEl('picture', {
 
-           className: 'vjs-poster',
 
-           // Don't want poster to be tabbable.
 
-           tabIndex: -1
 
-         }, {}, createEl('img', {
 
-           loading: 'lazy',
 
-           crossOrigin: this.crossOrigin()
 
-         }, {
 
-           alt: ''
 
-         })));
 
-       }
 
-       this.$('img').src = url;
 
-     }
 
-     /**
 
-      * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
 
-      * {@link ClickableComponent#handleClick} for instances where this will be triggered.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      * @listens keydown
 
-      *
 
-      * @param {Event} event
 
-      +        The `click`, `tap` or `keydown` event that caused this function to be called.
 
-      */
 
-     handleClick(event) {
 
-       // We don't want a click to trigger playback when controls are disabled
 
-       if (!this.player_.controls()) {
 
-         return;
 
-       }
 
-       if (this.player_.tech(true)) {
 
-         this.player_.tech(true).focus();
 
-       }
 
-       if (this.player_.paused()) {
 
-         silencePromise(this.player_.play());
 
-       } else {
 
-         this.player_.pause();
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * Get or set the `PosterImage`'s crossorigin option. For the HTML5 player, this
 
-    * sets the `crossOrigin` property on the `<img>` tag to control the CORS
 
-    * behavior.
 
-    *
 
-    * @param {string|null} [value]
 
-    *        The value to set the `PosterImages`'s crossorigin to. If an argument is
 
-    *        given, must be one of `anonymous` or `use-credentials`.
 
-    *
 
-    * @return {string|null|undefined}
 
-    *         - The current crossorigin value of the `Player` when getting.
 
-    *         - undefined when setting
 
-    */
 
-   PosterImage.prototype.crossorigin = PosterImage.prototype.crossOrigin;
 
-   Component$1.registerComponent('PosterImage', PosterImage);
 
-   /**
 
-    * @file text-track-display.js
 
-    */
 
-   const darkGray = '#222';
 
-   const lightGray = '#ccc';
 
-   const fontMap = {
 
-     monospace: 'monospace',
 
-     sansSerif: 'sans-serif',
 
-     serif: 'serif',
 
-     monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
 
-     monospaceSerif: '"Courier New", monospace',
 
-     proportionalSansSerif: 'sans-serif',
 
-     proportionalSerif: 'serif',
 
-     casual: '"Comic Sans MS", Impact, fantasy',
 
-     script: '"Monotype Corsiva", cursive',
 
-     smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
 
-   };
 
-   /**
 
-    * Construct an rgba color from a given hex color code.
 
-    *
 
-    * @param {number} color
 
-    *        Hex number for color, like #f0e or #f604e2.
 
-    *
 
-    * @param {number} opacity
 
-    *        Value for opacity, 0.0 - 1.0.
 
-    *
 
-    * @return {string}
 
-    *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
 
-    */
 
-   function constructColor(color, opacity) {
 
-     let hex;
 
-     if (color.length === 4) {
 
-       // color looks like "#f0e"
 
-       hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
 
-     } else if (color.length === 7) {
 
-       // color looks like "#f604e2"
 
-       hex = color.slice(1);
 
-     } else {
 
-       throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
 
-     }
 
-     return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
 
-   }
 
-   /**
 
-    * Try to update the style of a DOM element. Some style changes will throw an error,
 
-    * particularly in IE8. Those should be noops.
 
-    *
 
-    * @param {Element} el
 
-    *        The DOM element to be styled.
 
-    *
 
-    * @param {string} style
 
-    *        The CSS property on the element that should be styled.
 
-    *
 
-    * @param {string} rule
 
-    *        The style rule that should be applied to the property.
 
-    *
 
-    * @private
 
-    */
 
-   function tryUpdateStyle(el, style, rule) {
 
-     try {
 
-       el.style[style] = rule;
 
-     } catch (e) {
 
-       // Satisfies linter.
 
-       return;
 
-     }
 
-   }
 
-   /**
 
-    * The component for displaying text track cues.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class TextTrackDisplay extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @param {Function} [ready]
 
-      *        The function to call when `TextTrackDisplay` is ready.
 
-      */
 
-     constructor(player, options, ready) {
 
-       super(player, options, ready);
 
-       const updateDisplayHandler = e => this.updateDisplay(e);
 
-       player.on('loadstart', e => this.toggleDisplay(e));
 
-       player.on('texttrackchange', updateDisplayHandler);
 
-       player.on('loadedmetadata', e => this.preselectTrack(e));
 
-       // This used to be called during player init, but was causing an error
 
-       // if a track should show by default and the display hadn't loaded yet.
 
-       // Should probably be moved to an external track loader when we support
 
-       // tracks that don't need a display.
 
-       player.ready(bind_(this, function () {
 
-         if (player.tech_ && player.tech_.featuresNativeTextTracks) {
 
-           this.hide();
 
-           return;
 
-         }
 
-         player.on('fullscreenchange', updateDisplayHandler);
 
-         player.on('playerresize', updateDisplayHandler);
 
-         const screenOrientation = window.screen.orientation || window;
 
-         const changeOrientationEvent = window.screen.orientation ? 'change' : 'orientationchange';
 
-         screenOrientation.addEventListener(changeOrientationEvent, updateDisplayHandler);
 
-         player.on('dispose', () => screenOrientation.removeEventListener(changeOrientationEvent, updateDisplayHandler));
 
-         const tracks = this.options_.playerOptions.tracks || [];
 
-         for (let i = 0; i < tracks.length; i++) {
 
-           this.player_.addRemoteTextTrack(tracks[i], true);
 
-         }
 
-         this.preselectTrack();
 
-       }));
 
-     }
 
-     /**
 
-     * Preselect a track following this precedence:
 
-     * - matches the previously selected {@link TextTrack}'s language and kind
 
-     * - matches the previously selected {@link TextTrack}'s language only
 
-     * - is the first default captions track
 
-     * - is the first default descriptions track
 
-     *
 
-     * @listens Player#loadstart
 
-     */
 
-     preselectTrack() {
 
-       const modes = {
 
-         captions: 1,
 
-         subtitles: 1
 
-       };
 
-       const trackList = this.player_.textTracks();
 
-       const userPref = this.player_.cache_.selectedLanguage;
 
-       let firstDesc;
 
-       let firstCaptions;
 
-       let preferredTrack;
 
-       for (let i = 0; i < trackList.length; i++) {
 
-         const track = trackList[i];
 
-         if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
 
-           // Always choose the track that matches both language and kind
 
-           if (track.kind === userPref.kind) {
 
-             preferredTrack = track;
 
-             // or choose the first track that matches language
 
-           } else if (!preferredTrack) {
 
-             preferredTrack = track;
 
-           }
 
-           // clear everything if offTextTrackMenuItem was clicked
 
-         } else if (userPref && !userPref.enabled) {
 
-           preferredTrack = null;
 
-           firstDesc = null;
 
-           firstCaptions = null;
 
-         } else if (track.default) {
 
-           if (track.kind === 'descriptions' && !firstDesc) {
 
-             firstDesc = track;
 
-           } else if (track.kind in modes && !firstCaptions) {
 
-             firstCaptions = track;
 
-           }
 
-         }
 
-       }
 
-       // The preferredTrack matches the user preference and takes
 
-       // precedence over all the other tracks.
 
-       // So, display the preferredTrack before the first default track
 
-       // and the subtitles/captions track before the descriptions track
 
-       if (preferredTrack) {
 
-         preferredTrack.mode = 'showing';
 
-       } else if (firstCaptions) {
 
-         firstCaptions.mode = 'showing';
 
-       } else if (firstDesc) {
 
-         firstDesc.mode = 'showing';
 
-       }
 
-     }
 
-     /**
 
-      * Turn display of {@link TextTrack}'s from the current state into the other state.
 
-      * There are only two states:
 
-      * - 'shown'
 
-      * - 'hidden'
 
-      *
 
-      * @listens Player#loadstart
 
-      */
 
-     toggleDisplay() {
 
-       if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
 
-         this.hide();
 
-       } else {
 
-         this.show();
 
-       }
 
-     }
 
-     /**
 
-      * Create the {@link Component}'s DOM element.
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-text-track-display'
 
-       }, {
 
-         'translate': 'yes',
 
-         'aria-live': 'off',
 
-         'aria-atomic': 'true'
 
-       });
 
-     }
 
-     /**
 
-      * Clear all displayed {@link TextTrack}s.
 
-      */
 
-     clearDisplay() {
 
-       if (typeof window.WebVTT === 'function') {
 
-         window.WebVTT.processCues(window, [], this.el_);
 
-       }
 
-     }
 
-     /**
 
-      * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
 
-      * a {@link Player#fullscreenchange} is fired.
 
-      *
 
-      * @listens Player#texttrackchange
 
-      * @listens Player#fullscreenchange
 
-      */
 
-     updateDisplay() {
 
-       const tracks = this.player_.textTracks();
 
-       const allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
 
-       this.clearDisplay();
 
-       if (allowMultipleShowingTracks) {
 
-         const showingTracks = [];
 
-         for (let i = 0; i < tracks.length; ++i) {
 
-           const track = tracks[i];
 
-           if (track.mode !== 'showing') {
 
-             continue;
 
-           }
 
-           showingTracks.push(track);
 
-         }
 
-         this.updateForTrack(showingTracks);
 
-         return;
 
-       }
 
-       //  Track display prioritization model: if multiple tracks are 'showing',
 
-       //  display the first 'subtitles' or 'captions' track which is 'showing',
 
-       //  otherwise display the first 'descriptions' track which is 'showing'
 
-       let descriptionsTrack = null;
 
-       let captionsSubtitlesTrack = null;
 
-       let i = tracks.length;
 
-       while (i--) {
 
-         const track = tracks[i];
 
-         if (track.mode === 'showing') {
 
-           if (track.kind === 'descriptions') {
 
-             descriptionsTrack = track;
 
-           } else {
 
-             captionsSubtitlesTrack = track;
 
-           }
 
-         }
 
-       }
 
-       if (captionsSubtitlesTrack) {
 
-         if (this.getAttribute('aria-live') !== 'off') {
 
-           this.setAttribute('aria-live', 'off');
 
-         }
 
-         this.updateForTrack(captionsSubtitlesTrack);
 
-       } else if (descriptionsTrack) {
 
-         if (this.getAttribute('aria-live') !== 'assertive') {
 
-           this.setAttribute('aria-live', 'assertive');
 
-         }
 
-         this.updateForTrack(descriptionsTrack);
 
-       }
 
-     }
 
-     /**
 
-      * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
 
-      *
 
-      * @param {TextTrack} track
 
-      *        Text track object containing active cues to style.
 
-      */
 
-     updateDisplayState(track) {
 
-       const overrides = this.player_.textTrackSettings.getValues();
 
-       const cues = track.activeCues;
 
-       let i = cues.length;
 
-       while (i--) {
 
-         const cue = cues[i];
 
-         if (!cue) {
 
-           continue;
 
-         }
 
-         const cueDiv = cue.displayState;
 
-         if (overrides.color) {
 
-           cueDiv.firstChild.style.color = overrides.color;
 
-         }
 
-         if (overrides.textOpacity) {
 
-           tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
 
-         }
 
-         if (overrides.backgroundColor) {
 
-           cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
 
-         }
 
-         if (overrides.backgroundOpacity) {
 
-           tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
 
-         }
 
-         if (overrides.windowColor) {
 
-           if (overrides.windowOpacity) {
 
-             tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
 
-           } else {
 
-             cueDiv.style.backgroundColor = overrides.windowColor;
 
-           }
 
-         }
 
-         if (overrides.edgeStyle) {
 
-           if (overrides.edgeStyle === 'dropshadow') {
 
-             cueDiv.firstChild.style.textShadow = `2px 2px 3px ${darkGray}, 2px 2px 4px ${darkGray}, 2px 2px 5px ${darkGray}`;
 
-           } else if (overrides.edgeStyle === 'raised') {
 
-             cueDiv.firstChild.style.textShadow = `1px 1px ${darkGray}, 2px 2px ${darkGray}, 3px 3px ${darkGray}`;
 
-           } else if (overrides.edgeStyle === 'depressed') {
 
-             cueDiv.firstChild.style.textShadow = `1px 1px ${lightGray}, 0 1px ${lightGray}, -1px -1px ${darkGray}, 0 -1px ${darkGray}`;
 
-           } else if (overrides.edgeStyle === 'uniform') {
 
-             cueDiv.firstChild.style.textShadow = `0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}`;
 
-           }
 
-         }
 
-         if (overrides.fontPercent && overrides.fontPercent !== 1) {
 
-           const fontSize = window.parseFloat(cueDiv.style.fontSize);
 
-           cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
 
-           cueDiv.style.height = 'auto';
 
-           cueDiv.style.top = 'auto';
 
-         }
 
-         if (overrides.fontFamily && overrides.fontFamily !== 'default') {
 
-           if (overrides.fontFamily === 'small-caps') {
 
-             cueDiv.firstChild.style.fontVariant = 'small-caps';
 
-           } else {
 
-             cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
 
-           }
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
 
-      *
 
-      * @param {TextTrack|TextTrack[]} tracks
 
-      *        Text track object or text track array to be added to the list.
 
-      */
 
-     updateForTrack(tracks) {
 
-       if (!Array.isArray(tracks)) {
 
-         tracks = [tracks];
 
-       }
 
-       if (typeof window.WebVTT !== 'function' || tracks.every(track => {
 
-         return !track.activeCues;
 
-       })) {
 
-         return;
 
-       }
 
-       const cues = [];
 
-       // push all active track cues
 
-       for (let i = 0; i < tracks.length; ++i) {
 
-         const track = tracks[i];
 
-         for (let j = 0; j < track.activeCues.length; ++j) {
 
-           cues.push(track.activeCues[j]);
 
-         }
 
-       }
 
-       // removes all cues before it processes new ones
 
-       window.WebVTT.processCues(window, cues, this.el_);
 
-       // add unique class to each language text track & add settings styling if necessary
 
-       for (let i = 0; i < tracks.length; ++i) {
 
-         const track = tracks[i];
 
-         for (let j = 0; j < track.activeCues.length; ++j) {
 
-           const cueEl = track.activeCues[j].displayState;
 
-           addClass(cueEl, 'vjs-text-track-cue', 'vjs-text-track-cue-' + (track.language ? track.language : i));
 
-           if (track.language) {
 
-             setAttribute(cueEl, 'lang', track.language);
 
-           }
 
-         }
 
-         if (this.player_.textTrackSettings) {
 
-           this.updateDisplayState(track);
 
-         }
 
-       }
 
-     }
 
-   }
 
-   Component$1.registerComponent('TextTrackDisplay', TextTrackDisplay);
 
-   /**
 
-    * @file loading-spinner.js
 
-    */
 
-   /**
 
-    * A loading spinner for use during waiting/loading events.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class LoadingSpinner extends Component$1 {
 
-     /**
 
-      * Create the `LoadingSpinner`s DOM element.
 
-      *
 
-      * @return {Element}
 
-      *         The dom element that gets created.
 
-      */
 
-     createEl() {
 
-       const isAudio = this.player_.isAudio();
 
-       const playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
 
-       const controlText = createEl('span', {
 
-         className: 'vjs-control-text',
 
-         textContent: this.localize('{1} is loading.', [playerType])
 
-       });
 
-       const el = super.createEl('div', {
 
-         className: 'vjs-loading-spinner',
 
-         dir: 'ltr'
 
-       });
 
-       el.appendChild(controlText);
 
-       return el;
 
-     }
 
-     /**
 
-      * Update control text on languagechange
 
-      */
 
-     handleLanguagechange() {
 
-       this.$('.vjs-control-text').textContent = this.localize('{1} is loading.', [this.player_.isAudio() ? 'Audio Player' : 'Video Player']);
 
-     }
 
-   }
 
-   Component$1.registerComponent('LoadingSpinner', LoadingSpinner);
 
-   /**
 
-    * @file button.js
 
-    */
 
-   /**
 
-    * Base class for all buttons.
 
-    *
 
-    * @extends ClickableComponent
 
-    */
 
-   class Button extends ClickableComponent {
 
-     /**
 
-      * Create the `Button`s DOM element.
 
-      *
 
-      * @param {string} [tag="button"]
 
-      *        The element's node type. This argument is IGNORED: no matter what
 
-      *        is passed, it will always create a `button` element.
 
-      *
 
-      * @param {Object} [props={}]
 
-      *        An object of properties that should be set on the element.
 
-      *
 
-      * @param {Object} [attributes={}]
 
-      *        An object of attributes that should be set on the element.
 
-      *
 
-      * @return {Element}
 
-      *         The element that gets created.
 
-      */
 
-     createEl(tag, props = {}, attributes = {}) {
 
-       tag = 'button';
 
-       props = Object.assign({
 
-         className: this.buildCSSClass()
 
-       }, props);
 
-       // Add attributes for button element
 
-       attributes = Object.assign({
 
-         // Necessary since the default button type is "submit"
 
-         type: 'button'
 
-       }, attributes);
 
-       const el = createEl(tag, props, attributes);
 
-       el.appendChild(createEl('span', {
 
-         className: 'vjs-icon-placeholder'
 
-       }, {
 
-         'aria-hidden': true
 
-       }));
 
-       this.createControlTextEl(el);
 
-       return el;
 
-     }
 
-     /**
 
-      * Add a child `Component` inside of this `Button`.
 
-      *
 
-      * @param {string|Component} child
 
-      *        The name or instance of a child to add.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        The key/value store of options that will get passed to children of
 
-      *        the child.
 
-      *
 
-      * @return {Component}
 
-      *         The `Component` that gets added as a child. When using a string the
 
-      *         `Component` will get created by this process.
 
-      *
 
-      * @deprecated since version 5
 
-      */
 
-     addChild(child, options = {}) {
 
-       const className = this.constructor.name;
 
-       log$1.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
 
-       return Component$1.prototype.addChild.call(this, child, options);
 
-     }
 
-     /**
 
-      * Enable the `Button` element so that it can be activated or clicked. Use this with
 
-      * {@link Button#disable}.
 
-      */
 
-     enable() {
 
-       super.enable();
 
-       this.el_.removeAttribute('disabled');
 
-     }
 
-     /**
 
-      * Disable the `Button` element so that it cannot be activated or clicked. Use this with
 
-      * {@link Button#enable}.
 
-      */
 
-     disable() {
 
-       super.disable();
 
-       this.el_.setAttribute('disabled', 'disabled');
 
-     }
 
-     /**
 
-      * This gets called when a `Button` has focus and `keydown` is triggered via a key
 
-      * press.
 
-      *
 
-      * @param {Event} event
 
-      *        The event that caused this function to get called.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       // Ignore Space or Enter key operation, which is handled by the browser for
 
-       // a button - though not for its super class, ClickableComponent. Also,
 
-       // prevent the event from propagating through the DOM and triggering Player
 
-       // hotkeys. We do not preventDefault here because we _want_ the browser to
 
-       // handle it.
 
-       if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
 
-         event.stopPropagation();
 
-         return;
 
-       }
 
-       // Pass keypress handling up for unsupported keys
 
-       super.handleKeyDown(event);
 
-     }
 
-   }
 
-   Component$1.registerComponent('Button', Button);
 
-   /**
 
-    * @file big-play-button.js
 
-    */
 
-   /**
 
-    * The initial play button that shows before the video has played. The hiding of the
 
-    * `BigPlayButton` get done via CSS and `Player` states.
 
-    *
 
-    * @extends Button
 
-    */
 
-   class BigPlayButton extends Button {
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.mouseused_ = false;
 
-       this.on('mousedown', e => this.handleMouseDown(e));
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
 
-      */
 
-     buildCSSClass() {
 
-       return 'vjs-big-play-button';
 
-     }
 
-     /**
 
-      * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
 
-      * for more detailed information on what a click can be.
 
-      *
 
-      * @param {KeyboardEvent} event
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       const playPromise = this.player_.play();
 
-       // exit early if clicked via the mouse
 
-       if (this.mouseused_ && event.clientX && event.clientY) {
 
-         silencePromise(playPromise);
 
-         if (this.player_.tech(true)) {
 
-           this.player_.tech(true).focus();
 
-         }
 
-         return;
 
-       }
 
-       const cb = this.player_.getChild('controlBar');
 
-       const playToggle = cb && cb.getChild('playToggle');
 
-       if (!playToggle) {
 
-         this.player_.tech(true).focus();
 
-         return;
 
-       }
 
-       const playFocus = () => playToggle.focus();
 
-       if (isPromise(playPromise)) {
 
-         playPromise.then(playFocus, () => {});
 
-       } else {
 
-         this.setTimeout(playFocus, 1);
 
-       }
 
-     }
 
-     handleKeyDown(event) {
 
-       this.mouseused_ = false;
 
-       super.handleKeyDown(event);
 
-     }
 
-     handleMouseDown(event) {
 
-       this.mouseused_ = true;
 
-     }
 
-   }
 
-   /**
 
-    * The text that should display over the `BigPlayButton`s controls. Added to for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   BigPlayButton.prototype.controlText_ = 'Play Video';
 
-   Component$1.registerComponent('BigPlayButton', BigPlayButton);
 
-   /**
 
-    * @file close-button.js
 
-    */
 
-   /**
 
-    * The `CloseButton` is a `{@link Button}` that fires a `close` event when
 
-    * it gets clicked.
 
-    *
 
-    * @extends Button
 
-    */
 
-   class CloseButton extends Button {
 
-     /**
 
-     * Creates an instance of the this class.
 
-     *
 
-     * @param  { import('./player').default } player
 
-     *         The `Player` that this class should be attached to.
 
-     *
 
-     * @param  {Object} [options]
 
-     *         The key/value store of player options.
 
-     */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.controlText(options && options.controlText || this.localize('Close'));
 
-     }
 
-     /**
 
-     * Builds the default DOM `className`.
 
-     *
 
-     * @return {string}
 
-     *         The DOM `className` for this object.
 
-     */
 
-     buildCSSClass() {
 
-       return `vjs-close-button ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * This gets called when a `CloseButton` gets clicked. See
 
-      * {@link ClickableComponent#handleClick} for more information on when
 
-      * this will be triggered
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      * @fires CloseButton#close
 
-      */
 
-     handleClick(event) {
 
-       /**
 
-        * Triggered when the a `CloseButton` is clicked.
 
-        *
 
-        * @event CloseButton#close
 
-        * @type {Event}
 
-        *
 
-        * @property {boolean} [bubbles=false]
 
-        *           set to false so that the close event does not
 
-        *           bubble up to parents if there is no listener
 
-        */
 
-       this.trigger({
 
-         type: 'close',
 
-         bubbles: false
 
-       });
 
-     }
 
-     /**
 
-      * Event handler that is called when a `CloseButton` receives a
 
-      * `keydown` event.
 
-      *
 
-      * By default, if the key is Esc, it will trigger a `click` event.
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown` event that caused this function to be called.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       // Esc button will trigger `click` event
 
-       if (keycode.isEventKey(event, 'Esc')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.trigger('click');
 
-       } else {
 
-         // Pass keypress handling up for unsupported keys
 
-         super.handleKeyDown(event);
 
-       }
 
-     }
 
-   }
 
-   Component$1.registerComponent('CloseButton', CloseButton);
 
-   /**
 
-    * @file play-toggle.js
 
-    */
 
-   /**
 
-    * Button to toggle between play and pause.
 
-    *
 
-    * @extends Button
 
-    */
 
-   class PlayToggle extends Button {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options = {}) {
 
-       super(player, options);
 
-       // show or hide replay icon
 
-       options.replay = options.replay === undefined || options.replay;
 
-       this.on(player, 'play', e => this.handlePlay(e));
 
-       this.on(player, 'pause', e => this.handlePause(e));
 
-       if (options.replay) {
 
-         this.on(player, 'ended', e => this.handleEnded(e));
 
-       }
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-play-control ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * This gets called when an `PlayToggle` is "clicked". See
 
-      * {@link ClickableComponent} for more detailed information on what a click can be.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       if (this.player_.paused()) {
 
-         silencePromise(this.player_.play());
 
-       } else {
 
-         this.player_.pause();
 
-       }
 
-     }
 
-     /**
 
-      * This gets called once after the video has ended and the user seeks so that
 
-      * we can change the replay button back to a play button.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The event that caused this function to run.
 
-      *
 
-      * @listens Player#seeked
 
-      */
 
-     handleSeeked(event) {
 
-       this.removeClass('vjs-ended');
 
-       if (this.player_.paused()) {
 
-         this.handlePause(event);
 
-       } else {
 
-         this.handlePlay(event);
 
-       }
 
-     }
 
-     /**
 
-      * Add the vjs-playing class to the element so it can change appearance.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The event that caused this function to run.
 
-      *
 
-      * @listens Player#play
 
-      */
 
-     handlePlay(event) {
 
-       this.removeClass('vjs-ended', 'vjs-paused');
 
-       this.addClass('vjs-playing');
 
-       // change the button text to "Pause"
 
-       this.controlText('Pause');
 
-     }
 
-     /**
 
-      * Add the vjs-paused class to the element so it can change appearance.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The event that caused this function to run.
 
-      *
 
-      * @listens Player#pause
 
-      */
 
-     handlePause(event) {
 
-       this.removeClass('vjs-playing');
 
-       this.addClass('vjs-paused');
 
-       // change the button text to "Play"
 
-       this.controlText('Play');
 
-     }
 
-     /**
 
-      * Add the vjs-ended class to the element so it can change appearance
 
-      *
 
-      * @param {Event} [event]
 
-      *        The event that caused this function to run.
 
-      *
 
-      * @listens Player#ended
 
-      */
 
-     handleEnded(event) {
 
-       this.removeClass('vjs-playing');
 
-       this.addClass('vjs-ended');
 
-       // change the button text to "Replay"
 
-       this.controlText('Replay');
 
-       // on the next seek remove the replay button
 
-       this.one(this.player_, 'seeked', e => this.handleSeeked(e));
 
-     }
 
-   }
 
-   /**
 
-    * The text that should display over the `PlayToggle`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   PlayToggle.prototype.controlText_ = 'Play';
 
-   Component$1.registerComponent('PlayToggle', PlayToggle);
 
-   /**
 
-    * @file time-display.js
 
-    */
 
-   /**
 
-    * Displays time information about the video
 
-    *
 
-    * @extends Component
 
-    */
 
-   class TimeDisplay extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.on(player, ['timeupdate', 'ended'], e => this.updateContent(e));
 
-       this.updateTextNode_();
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       const className = this.buildCSSClass();
 
-       const el = super.createEl('div', {
 
-         className: `${className} vjs-time-control vjs-control`
 
-       });
 
-       const span = createEl('span', {
 
-         className: 'vjs-control-text',
 
-         textContent: `${this.localize(this.labelText_)}\u00a0`
 
-       }, {
 
-         role: 'presentation'
 
-       });
 
-       el.appendChild(span);
 
-       this.contentEl_ = createEl('span', {
 
-         className: `${className}-display`
 
-       }, {
 
-         // span elements have no implicit role, but some screen readers (notably VoiceOver)
 
-         // treat them as a break between items in the DOM when using arrow keys
 
-         // (or left-to-right swipes on iOS) to read contents of a page. Using
 
-         // role='presentation' causes VoiceOver to NOT treat this span as a break.
 
-         role: 'presentation'
 
-       });
 
-       el.appendChild(this.contentEl_);
 
-       return el;
 
-     }
 
-     dispose() {
 
-       this.contentEl_ = null;
 
-       this.textNode_ = null;
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Updates the time display text node with a new time
 
-      *
 
-      * @param {number} [time=0] the time to update to
 
-      *
 
-      * @private
 
-      */
 
-     updateTextNode_(time = 0) {
 
-       time = formatTime(time);
 
-       if (this.formattedTime_ === time) {
 
-         return;
 
-       }
 
-       this.formattedTime_ = time;
 
-       this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', () => {
 
-         if (!this.contentEl_) {
 
-           return;
 
-         }
 
-         let oldNode = this.textNode_;
 
-         if (oldNode && this.contentEl_.firstChild !== oldNode) {
 
-           oldNode = null;
 
-           log$1.warn('TimeDisplay#updateTextnode_: Prevented replacement of text node element since it was no longer a child of this node. Appending a new node instead.');
 
-         }
 
-         this.textNode_ = document.createTextNode(this.formattedTime_);
 
-         if (!this.textNode_) {
 
-           return;
 
-         }
 
-         if (oldNode) {
 
-           this.contentEl_.replaceChild(this.textNode_, oldNode);
 
-         } else {
 
-           this.contentEl_.appendChild(this.textNode_);
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * To be filled out in the child class, should update the displayed time
 
-      * in accordance with the fact that the current time has changed.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `timeupdate`  event that caused this to run.
 
-      *
 
-      * @listens Player#timeupdate
 
-      */
 
-     updateContent(event) {}
 
-   }
 
-   /**
 
-    * The text that is added to the `TimeDisplay` for screen reader users.
 
-    *
 
-    * @type {string}
 
-    * @private
 
-    */
 
-   TimeDisplay.prototype.labelText_ = 'Time';
 
-   /**
 
-    * The text that should display over the `TimeDisplay`s controls. Added to for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    *
 
-    * @deprecated in v7; controlText_ is not used in non-active display Components
 
-    */
 
-   TimeDisplay.prototype.controlText_ = 'Time';
 
-   Component$1.registerComponent('TimeDisplay', TimeDisplay);
 
-   /**
 
-    * @file current-time-display.js
 
-    */
 
-   /**
 
-    * Displays the current time
 
-    *
 
-    * @extends Component
 
-    */
 
-   class CurrentTimeDisplay extends TimeDisplay {
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return 'vjs-current-time';
 
-     }
 
-     /**
 
-      * Update current time display
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `timeupdate` event that caused this function to run.
 
-      *
 
-      * @listens Player#timeupdate
 
-      */
 
-     updateContent(event) {
 
-       // Allows for smooth scrubbing, when player can't keep up.
 
-       let time;
 
-       if (this.player_.ended()) {
 
-         time = this.player_.duration();
 
-       } else {
 
-         time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
 
-       }
 
-       this.updateTextNode_(time);
 
-     }
 
-   }
 
-   /**
 
-    * The text that is added to the `CurrentTimeDisplay` for screen reader users.
 
-    *
 
-    * @type {string}
 
-    * @private
 
-    */
 
-   CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
 
-   /**
 
-    * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    *
 
-    * @deprecated in v7; controlText_ is not used in non-active display Components
 
-    */
 
-   CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
 
-   Component$1.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
 
-   /**
 
-    * @file duration-display.js
 
-    */
 
-   /**
 
-    * Displays the duration
 
-    *
 
-    * @extends Component
 
-    */
 
-   class DurationDisplay extends TimeDisplay {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       const updateContent = e => this.updateContent(e);
 
-       // we do not want to/need to throttle duration changes,
 
-       // as they should always display the changed duration as
 
-       // it has changed
 
-       this.on(player, 'durationchange', updateContent);
 
-       // Listen to loadstart because the player duration is reset when a new media element is loaded,
 
-       // but the durationchange on the user agent will not fire.
 
-       // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
 
-       this.on(player, 'loadstart', updateContent);
 
-       // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
 
-       // listeners could have broken dependent applications/libraries. These
 
-       // can likely be removed for 7.0.
 
-       this.on(player, 'loadedmetadata', updateContent);
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return 'vjs-duration';
 
-     }
 
-     /**
 
-      * Update duration time display.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
 
-      *        this function to be called.
 
-      *
 
-      * @listens Player#durationchange
 
-      * @listens Player#timeupdate
 
-      * @listens Player#loadedmetadata
 
-      */
 
-     updateContent(event) {
 
-       const duration = this.player_.duration();
 
-       this.updateTextNode_(duration);
 
-     }
 
-   }
 
-   /**
 
-    * The text that is added to the `DurationDisplay` for screen reader users.
 
-    *
 
-    * @type {string}
 
-    * @private
 
-    */
 
-   DurationDisplay.prototype.labelText_ = 'Duration';
 
-   /**
 
-    * The text that should display over the `DurationDisplay`s controls. Added to for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    *
 
-    * @deprecated in v7; controlText_ is not used in non-active display Components
 
-    */
 
-   DurationDisplay.prototype.controlText_ = 'Duration';
 
-   Component$1.registerComponent('DurationDisplay', DurationDisplay);
 
-   /**
 
-    * @file time-divider.js
 
-    */
 
-   /**
 
-    * The separator between the current time and duration.
 
-    * Can be hidden if it's not needed in the design.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class TimeDivider extends Component$1 {
 
-     /**
 
-      * Create the component's DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       const el = super.createEl('div', {
 
-         className: 'vjs-time-control vjs-time-divider'
 
-       }, {
 
-         // this element and its contents can be hidden from assistive techs since
 
-         // it is made extraneous by the announcement of the control text
 
-         // for the current time and duration displays
 
-         'aria-hidden': true
 
-       });
 
-       const div = super.createEl('div');
 
-       const span = super.createEl('span', {
 
-         textContent: '/'
 
-       });
 
-       div.appendChild(span);
 
-       el.appendChild(div);
 
-       return el;
 
-     }
 
-   }
 
-   Component$1.registerComponent('TimeDivider', TimeDivider);
 
-   /**
 
-    * @file remaining-time-display.js
 
-    */
 
-   /**
 
-    * Displays the time left in the video
 
-    *
 
-    * @extends Component
 
-    */
 
-   class RemainingTimeDisplay extends TimeDisplay {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.on(player, 'durationchange', e => this.updateContent(e));
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return 'vjs-remaining-time';
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element with the "minus" character prepend to the time
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       const el = super.createEl();
 
-       if (this.options_.displayNegative !== false) {
 
-         el.insertBefore(createEl('span', {}, {
 
-           'aria-hidden': true
 
-         }, '-'), this.contentEl_);
 
-       }
 
-       return el;
 
-     }
 
-     /**
 
-      * Update remaining time display.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `timeupdate` or `durationchange` event that caused this to run.
 
-      *
 
-      * @listens Player#timeupdate
 
-      * @listens Player#durationchange
 
-      */
 
-     updateContent(event) {
 
-       if (typeof this.player_.duration() !== 'number') {
 
-         return;
 
-       }
 
-       let time;
 
-       // @deprecated We should only use remainingTimeDisplay
 
-       // as of video.js 7
 
-       if (this.player_.ended()) {
 
-         time = 0;
 
-       } else if (this.player_.remainingTimeDisplay) {
 
-         time = this.player_.remainingTimeDisplay();
 
-       } else {
 
-         time = this.player_.remainingTime();
 
-       }
 
-       this.updateTextNode_(time);
 
-     }
 
-   }
 
-   /**
 
-    * The text that is added to the `RemainingTimeDisplay` for screen reader users.
 
-    *
 
-    * @type {string}
 
-    * @private
 
-    */
 
-   RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
 
-   /**
 
-    * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    *
 
-    * @deprecated in v7; controlText_ is not used in non-active display Components
 
-    */
 
-   RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
 
-   Component$1.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
 
-   /**
 
-    * @file live-display.js
 
-    */
 
-   // TODO - Future make it click to snap to live
 
-   /**
 
-    * Displays the live indicator when duration is Infinity.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class LiveDisplay extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.updateShowing();
 
-       this.on(this.player(), 'durationchange', e => this.updateShowing(e));
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       const el = super.createEl('div', {
 
-         className: 'vjs-live-control vjs-control'
 
-       });
 
-       this.contentEl_ = createEl('div', {
 
-         className: 'vjs-live-display'
 
-       }, {
 
-         'aria-live': 'off'
 
-       });
 
-       this.contentEl_.appendChild(createEl('span', {
 
-         className: 'vjs-control-text',
 
-         textContent: `${this.localize('Stream Type')}\u00a0`
 
-       }));
 
-       this.contentEl_.appendChild(document.createTextNode(this.localize('LIVE')));
 
-       el.appendChild(this.contentEl_);
 
-       return el;
 
-     }
 
-     dispose() {
 
-       this.contentEl_ = null;
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
 
-      * it accordingly
 
-      *
 
-      * @param {Event} [event]
 
-      *        The {@link Player#durationchange} event that caused this function to run.
 
-      *
 
-      * @listens Player#durationchange
 
-      */
 
-     updateShowing(event) {
 
-       if (this.player().duration() === Infinity) {
 
-         this.show();
 
-       } else {
 
-         this.hide();
 
-       }
 
-     }
 
-   }
 
-   Component$1.registerComponent('LiveDisplay', LiveDisplay);
 
-   /**
 
-    * @file seek-to-live.js
 
-    */
 
-   /**
 
-    * Displays the live indicator when duration is Infinity.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class SeekToLive extends Button {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.updateLiveEdgeStatus();
 
-       if (this.player_.liveTracker) {
 
-         this.updateLiveEdgeStatusHandler_ = e => this.updateLiveEdgeStatus(e);
 
-         this.on(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
 
-       }
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       const el = super.createEl('button', {
 
-         className: 'vjs-seek-to-live-control vjs-control'
 
-       });
 
-       this.textEl_ = createEl('span', {
 
-         className: 'vjs-seek-to-live-text',
 
-         textContent: this.localize('LIVE')
 
-       }, {
 
-         'aria-hidden': 'true'
 
-       });
 
-       el.appendChild(this.textEl_);
 
-       return el;
 
-     }
 
-     /**
 
-      * Update the state of this button if we are at the live edge
 
-      * or not
 
-      */
 
-     updateLiveEdgeStatus() {
 
-       // default to live edge
 
-       if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
 
-         this.setAttribute('aria-disabled', true);
 
-         this.addClass('vjs-at-live-edge');
 
-         this.controlText('Seek to live, currently playing live');
 
-       } else {
 
-         this.setAttribute('aria-disabled', false);
 
-         this.removeClass('vjs-at-live-edge');
 
-         this.controlText('Seek to live, currently behind live');
 
-       }
 
-     }
 
-     /**
 
-      * On click bring us as near to the live point as possible.
 
-      * This requires that we wait for the next `live-seekable-change`
 
-      * event which will happen every segment length seconds.
 
-      */
 
-     handleClick() {
 
-       this.player_.liveTracker.seekToLiveEdge();
 
-     }
 
-     /**
 
-      * Dispose of the element and stop tracking
 
-      */
 
-     dispose() {
 
-       if (this.player_.liveTracker) {
 
-         this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
 
-       }
 
-       this.textEl_ = null;
 
-       super.dispose();
 
-     }
 
-   }
 
-   /**
 
-    * The text that should display over the `SeekToLive`s control. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
 
-   Component$1.registerComponent('SeekToLive', SeekToLive);
 
-   /**
 
-    * @file num.js
 
-    * @module num
 
-    */
 
-   /**
 
-    * Keep a number between a min and a max value
 
-    *
 
-    * @param {number} number
 
-    *        The number to clamp
 
-    *
 
-    * @param {number} min
 
-    *        The minimum value
 
-    * @param {number} max
 
-    *        The maximum value
 
-    *
 
-    * @return {number}
 
-    *         the clamped number
 
-    */
 
-   function clamp(number, min, max) {
 
-     number = Number(number);
 
-     return Math.min(max, Math.max(min, isNaN(number) ? min : number));
 
-   }
 
-   var Num = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     clamp: clamp
 
-   });
 
-   /**
 
-    * @file slider.js
 
-    */
 
-   /**
 
-    * The base functionality for a slider. Can be vertical or horizontal.
 
-    * For instance the volume bar or the seek bar on a video is a slider.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class Slider extends Component$1 {
 
-     /**
 
-     * Create an instance of this class
 
-     *
 
-     * @param { import('../player').default } player
 
-     *        The `Player` that this class should be attached to.
 
-     *
 
-     * @param {Object} [options]
 
-     *        The key/value store of player options.
 
-     */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.handleMouseDown_ = e => this.handleMouseDown(e);
 
-       this.handleMouseUp_ = e => this.handleMouseUp(e);
 
-       this.handleKeyDown_ = e => this.handleKeyDown(e);
 
-       this.handleClick_ = e => this.handleClick(e);
 
-       this.handleMouseMove_ = e => this.handleMouseMove(e);
 
-       this.update_ = e => this.update(e);
 
-       // Set property names to bar to match with the child Slider class is looking for
 
-       this.bar = this.getChild(this.options_.barName);
 
-       // Set a horizontal or vertical class on the slider depending on the slider type
 
-       this.vertical(!!this.options_.vertical);
 
-       this.enable();
 
-     }
 
-     /**
 
-      * Are controls are currently enabled for this slider or not.
 
-      *
 
-      * @return {boolean}
 
-      *         true if controls are enabled, false otherwise
 
-      */
 
-     enabled() {
 
-       return this.enabled_;
 
-     }
 
-     /**
 
-      * Enable controls for this slider if they are disabled
 
-      */
 
-     enable() {
 
-       if (this.enabled()) {
 
-         return;
 
-       }
 
-       this.on('mousedown', this.handleMouseDown_);
 
-       this.on('touchstart', this.handleMouseDown_);
 
-       this.on('keydown', this.handleKeyDown_);
 
-       this.on('click', this.handleClick_);
 
-       // TODO: deprecated, controlsvisible does not seem to be fired
 
-       this.on(this.player_, 'controlsvisible', this.update);
 
-       if (this.playerEvent) {
 
-         this.on(this.player_, this.playerEvent, this.update);
 
-       }
 
-       this.removeClass('disabled');
 
-       this.setAttribute('tabindex', 0);
 
-       this.enabled_ = true;
 
-     }
 
-     /**
 
-      * Disable controls for this slider if they are enabled
 
-      */
 
-     disable() {
 
-       if (!this.enabled()) {
 
-         return;
 
-       }
 
-       const doc = this.bar.el_.ownerDocument;
 
-       this.off('mousedown', this.handleMouseDown_);
 
-       this.off('touchstart', this.handleMouseDown_);
 
-       this.off('keydown', this.handleKeyDown_);
 
-       this.off('click', this.handleClick_);
 
-       this.off(this.player_, 'controlsvisible', this.update_);
 
-       this.off(doc, 'mousemove', this.handleMouseMove_);
 
-       this.off(doc, 'mouseup', this.handleMouseUp_);
 
-       this.off(doc, 'touchmove', this.handleMouseMove_);
 
-       this.off(doc, 'touchend', this.handleMouseUp_);
 
-       this.removeAttribute('tabindex');
 
-       this.addClass('disabled');
 
-       if (this.playerEvent) {
 
-         this.off(this.player_, this.playerEvent, this.update);
 
-       }
 
-       this.enabled_ = false;
 
-     }
 
-     /**
 
-      * Create the `Slider`s DOM element.
 
-      *
 
-      * @param {string} type
 
-      *        Type of element to create.
 
-      *
 
-      * @param {Object} [props={}]
 
-      *        List of properties in Object form.
 
-      *
 
-      * @param {Object} [attributes={}]
 
-      *        list of attributes in Object form.
 
-      *
 
-      * @return {Element}
 
-      *         The element that gets created.
 
-      */
 
-     createEl(type, props = {}, attributes = {}) {
 
-       // Add the slider element class to all sub classes
 
-       props.className = props.className + ' vjs-slider';
 
-       props = Object.assign({
 
-         tabIndex: 0
 
-       }, props);
 
-       attributes = Object.assign({
 
-         'role': 'slider',
 
-         'aria-valuenow': 0,
 
-         'aria-valuemin': 0,
 
-         'aria-valuemax': 100
 
-       }, attributes);
 
-       return super.createEl(type, props, attributes);
 
-     }
 
-     /**
 
-      * Handle `mousedown` or `touchstart` events on the `Slider`.
 
-      *
 
-      * @param {MouseEvent} event
 
-      *        `mousedown` or `touchstart` event that triggered this function
 
-      *
 
-      * @listens mousedown
 
-      * @listens touchstart
 
-      * @fires Slider#slideractive
 
-      */
 
-     handleMouseDown(event) {
 
-       const doc = this.bar.el_.ownerDocument;
 
-       if (event.type === 'mousedown') {
 
-         event.preventDefault();
 
-       }
 
-       // Do not call preventDefault() on touchstart in Chrome
 
-       // to avoid console warnings. Use a 'touch-action: none' style
 
-       // instead to prevent unintended scrolling.
 
-       // https://developers.google.com/web/updates/2017/01/scrolling-intervention
 
-       if (event.type === 'touchstart' && !IS_CHROME) {
 
-         event.preventDefault();
 
-       }
 
-       blockTextSelection();
 
-       this.addClass('vjs-sliding');
 
-       /**
 
-        * Triggered when the slider is in an active state
 
-        *
 
-        * @event Slider#slideractive
 
-        * @type {MouseEvent}
 
-        */
 
-       this.trigger('slideractive');
 
-       this.on(doc, 'mousemove', this.handleMouseMove_);
 
-       this.on(doc, 'mouseup', this.handleMouseUp_);
 
-       this.on(doc, 'touchmove', this.handleMouseMove_);
 
-       this.on(doc, 'touchend', this.handleMouseUp_);
 
-       this.handleMouseMove(event, true);
 
-     }
 
-     /**
 
-      * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
 
-      * The `mousemove` and `touchmove` events will only only trigger this function during
 
-      * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
 
-      * {@link Slider#handleMouseUp}.
 
-      *
 
-      * @param {MouseEvent} event
 
-      *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
 
-      *        this function
 
-      * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false.
 
-      *
 
-      * @listens mousemove
 
-      * @listens touchmove
 
-      */
 
-     handleMouseMove(event) {}
 
-     /**
 
-      * Handle `mouseup` or `touchend` events on the `Slider`.
 
-      *
 
-      * @param {MouseEvent} event
 
-      *        `mouseup` or `touchend` event that triggered this function.
 
-      *
 
-      * @listens touchend
 
-      * @listens mouseup
 
-      * @fires Slider#sliderinactive
 
-      */
 
-     handleMouseUp(event) {
 
-       const doc = this.bar.el_.ownerDocument;
 
-       unblockTextSelection();
 
-       this.removeClass('vjs-sliding');
 
-       /**
 
-        * Triggered when the slider is no longer in an active state.
 
-        *
 
-        * @event Slider#sliderinactive
 
-        * @type {Event}
 
-        */
 
-       this.trigger('sliderinactive');
 
-       this.off(doc, 'mousemove', this.handleMouseMove_);
 
-       this.off(doc, 'mouseup', this.handleMouseUp_);
 
-       this.off(doc, 'touchmove', this.handleMouseMove_);
 
-       this.off(doc, 'touchend', this.handleMouseUp_);
 
-       this.update();
 
-     }
 
-     /**
 
-      * Update the progress bar of the `Slider`.
 
-      *
 
-      * @return {number}
 
-      *          The percentage of progress the progress bar represents as a
 
-      *          number from 0 to 1.
 
-      */
 
-     update() {
 
-       // In VolumeBar init we have a setTimeout for update that pops and update
 
-       // to the end of the execution stack. The player is destroyed before then
 
-       // update will cause an error
 
-       // If there's no bar...
 
-       if (!this.el_ || !this.bar) {
 
-         return;
 
-       }
 
-       // clamp progress between 0 and 1
 
-       // and only round to four decimal places, as we round to two below
 
-       const progress = this.getProgress();
 
-       if (progress === this.progress_) {
 
-         return progress;
 
-       }
 
-       this.progress_ = progress;
 
-       this.requestNamedAnimationFrame('Slider#update', () => {
 
-         // Set the new bar width or height
 
-         const sizeKey = this.vertical() ? 'height' : 'width';
 
-         // Convert to a percentage for css value
 
-         this.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
 
-       });
 
-       return progress;
 
-     }
 
-     /**
 
-      * Get the percentage of the bar that should be filled
 
-      * but clamped and rounded.
 
-      *
 
-      * @return {number}
 
-      *         percentage filled that the slider is
 
-      */
 
-     getProgress() {
 
-       return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
 
-     }
 
-     /**
 
-      * Calculate distance for slider
 
-      *
 
-      * @param {Event} event
 
-      *        The event that caused this function to run.
 
-      *
 
-      * @return {number}
 
-      *         The current position of the Slider.
 
-      *         - position.x for vertical `Slider`s
 
-      *         - position.y for horizontal `Slider`s
 
-      */
 
-     calculateDistance(event) {
 
-       const position = getPointerPosition(this.el_, event);
 
-       if (this.vertical()) {
 
-         return position.y;
 
-       }
 
-       return position.x;
 
-     }
 
-     /**
 
-      * Handle a `keydown` event on the `Slider`. Watches for left, right, up, and down
 
-      * arrow keys. This function will only be called when the slider has focus. See
 
-      * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
 
-      *
 
-      * @param {KeyboardEvent} event
 
-      *        the `keydown` event that caused this function to run.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       // Left and Down Arrows
 
-       if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.stepBack();
 
-         // Up and Right Arrows
 
-       } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.stepForward();
 
-       } else {
 
-         // Pass keydown handling up for unsupported keys
 
-         super.handleKeyDown(event);
 
-       }
 
-     }
 
-     /**
 
-      * Listener for click events on slider, used to prevent clicks
 
-      *   from bubbling up to parent elements like button menus.
 
-      *
 
-      * @param {Object} event
 
-      *        Event that caused this object to run
 
-      */
 
-     handleClick(event) {
 
-       event.stopPropagation();
 
-       event.preventDefault();
 
-     }
 
-     /**
 
-      * Get/set if slider is horizontal for vertical
 
-      *
 
-      * @param {boolean} [bool]
 
-      *        - true if slider is vertical,
 
-      *        - false is horizontal
 
-      *
 
-      * @return {boolean}
 
-      *         - true if slider is vertical, and getting
 
-      *         - false if the slider is horizontal, and getting
 
-      */
 
-     vertical(bool) {
 
-       if (bool === undefined) {
 
-         return this.vertical_ || false;
 
-       }
 
-       this.vertical_ = !!bool;
 
-       if (this.vertical_) {
 
-         this.addClass('vjs-slider-vertical');
 
-       } else {
 
-         this.addClass('vjs-slider-horizontal');
 
-       }
 
-     }
 
-   }
 
-   Component$1.registerComponent('Slider', Slider);
 
-   /**
 
-    * @file load-progress-bar.js
 
-    */
 
-   // get the percent width of a time compared to the total end
 
-   const percentify = (time, end) => clamp(time / end * 100, 0, 100).toFixed(2) + '%';
 
-   /**
 
-    * Shows loading progress
 
-    *
 
-    * @extends Component
 
-    */
 
-   class LoadProgressBar extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.partEls_ = [];
 
-       this.on(player, 'progress', e => this.update(e));
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       const el = super.createEl('div', {
 
-         className: 'vjs-load-progress'
 
-       });
 
-       const wrapper = createEl('span', {
 
-         className: 'vjs-control-text'
 
-       });
 
-       const loadedText = createEl('span', {
 
-         textContent: this.localize('Loaded')
 
-       });
 
-       const separator = document.createTextNode(': ');
 
-       this.percentageEl_ = createEl('span', {
 
-         className: 'vjs-control-text-loaded-percentage',
 
-         textContent: '0%'
 
-       });
 
-       el.appendChild(wrapper);
 
-       wrapper.appendChild(loadedText);
 
-       wrapper.appendChild(separator);
 
-       wrapper.appendChild(this.percentageEl_);
 
-       return el;
 
-     }
 
-     dispose() {
 
-       this.partEls_ = null;
 
-       this.percentageEl_ = null;
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Update progress bar
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `progress` event that caused this function to run.
 
-      *
 
-      * @listens Player#progress
 
-      */
 
-     update(event) {
 
-       this.requestNamedAnimationFrame('LoadProgressBar#update', () => {
 
-         const liveTracker = this.player_.liveTracker;
 
-         const buffered = this.player_.buffered();
 
-         const duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
 
-         const bufferedEnd = this.player_.bufferedEnd();
 
-         const children = this.partEls_;
 
-         const percent = percentify(bufferedEnd, duration);
 
-         if (this.percent_ !== percent) {
 
-           // update the width of the progress bar
 
-           this.el_.style.width = percent;
 
-           // update the control-text
 
-           textContent(this.percentageEl_, percent);
 
-           this.percent_ = percent;
 
-         }
 
-         // add child elements to represent the individual buffered time ranges
 
-         for (let i = 0; i < buffered.length; i++) {
 
-           const start = buffered.start(i);
 
-           const end = buffered.end(i);
 
-           let part = children[i];
 
-           if (!part) {
 
-             part = this.el_.appendChild(createEl());
 
-             children[i] = part;
 
-           }
 
-           //  only update if changed
 
-           if (part.dataset.start === start && part.dataset.end === end) {
 
-             continue;
 
-           }
 
-           part.dataset.start = start;
 
-           part.dataset.end = end;
 
-           // set the percent based on the width of the progress bar (bufferedEnd)
 
-           part.style.left = percentify(start, bufferedEnd);
 
-           part.style.width = percentify(end - start, bufferedEnd);
 
-         }
 
-         // remove unused buffered range elements
 
-         for (let i = children.length; i > buffered.length; i--) {
 
-           this.el_.removeChild(children[i - 1]);
 
-         }
 
-         children.length = buffered.length;
 
-       });
 
-     }
 
-   }
 
-   Component$1.registerComponent('LoadProgressBar', LoadProgressBar);
 
-   /**
 
-    * @file time-tooltip.js
 
-    */
 
-   /**
 
-    * Time tooltips display a time above the progress bar.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class TimeTooltip extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The {@link Player} that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
 
-     }
 
-     /**
 
-      * Create the time tooltip DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-time-tooltip'
 
-       }, {
 
-         'aria-hidden': 'true'
 
-       });
 
-     }
 
-     /**
 
-      * Updates the position of the time tooltip relative to the `SeekBar`.
 
-      *
 
-      * @param {Object} seekBarRect
 
-      *        The `ClientRect` for the {@link SeekBar} element.
 
-      *
 
-      * @param {number} seekBarPoint
 
-      *        A number from 0 to 1, representing a horizontal reference point
 
-      *        from the left edge of the {@link SeekBar}
 
-      */
 
-     update(seekBarRect, seekBarPoint, content) {
 
-       const tooltipRect = findPosition(this.el_);
 
-       const playerRect = getBoundingClientRect(this.player_.el());
 
-       const seekBarPointPx = seekBarRect.width * seekBarPoint;
 
-       // do nothing if either rect isn't available
 
-       // for example, if the player isn't in the DOM for testing
 
-       if (!playerRect || !tooltipRect) {
 
-         return;
 
-       }
 
-       // This is the space left of the `seekBarPoint` available within the bounds
 
-       // of the player. We calculate any gap between the left edge of the player
 
-       // and the left edge of the `SeekBar` and add the number of pixels in the
 
-       // `SeekBar` before hitting the `seekBarPoint`
 
-       const spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx;
 
-       // This is the space right of the `seekBarPoint` available within the bounds
 
-       // of the player. We calculate the number of pixels from the `seekBarPoint`
 
-       // to the right edge of the `SeekBar` and add to that any gap between the
 
-       // right edge of the `SeekBar` and the player.
 
-       const spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right);
 
-       // This is the number of pixels by which the tooltip will need to be pulled
 
-       // further to the right to center it over the `seekBarPoint`.
 
-       let pullTooltipBy = tooltipRect.width / 2;
 
-       // Adjust the `pullTooltipBy` distance to the left or right depending on
 
-       // the results of the space calculations above.
 
-       if (spaceLeftOfPoint < pullTooltipBy) {
 
-         pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
 
-       } else if (spaceRightOfPoint < pullTooltipBy) {
 
-         pullTooltipBy = spaceRightOfPoint;
 
-       }
 
-       // Due to the imprecision of decimal/ratio based calculations and varying
 
-       // rounding behaviors, there are cases where the spacing adjustment is off
 
-       // by a pixel or two. This adds insurance to these calculations.
 
-       if (pullTooltipBy < 0) {
 
-         pullTooltipBy = 0;
 
-       } else if (pullTooltipBy > tooltipRect.width) {
 
-         pullTooltipBy = tooltipRect.width;
 
-       }
 
-       // prevent small width fluctuations within 0.4px from
 
-       // changing the value below.
 
-       // This really helps for live to prevent the play
 
-       // progress time tooltip from jittering
 
-       pullTooltipBy = Math.round(pullTooltipBy);
 
-       this.el_.style.right = `-${pullTooltipBy}px`;
 
-       this.write(content);
 
-     }
 
-     /**
 
-      * Write the time to the tooltip DOM element.
 
-      *
 
-      * @param {string} content
 
-      *        The formatted time for the tooltip.
 
-      */
 
-     write(content) {
 
-       textContent(this.el_, content);
 
-     }
 
-     /**
 
-      * Updates the position of the time tooltip relative to the `SeekBar`.
 
-      *
 
-      * @param {Object} seekBarRect
 
-      *        The `ClientRect` for the {@link SeekBar} element.
 
-      *
 
-      * @param {number} seekBarPoint
 
-      *        A number from 0 to 1, representing a horizontal reference point
 
-      *        from the left edge of the {@link SeekBar}
 
-      *
 
-      * @param {number} time
 
-      *        The time to update the tooltip to, not used during live playback
 
-      *
 
-      * @param {Function} cb
 
-      *        A function that will be called during the request animation frame
 
-      *        for tooltips that need to do additional animations from the default
 
-      */
 
-     updateTime(seekBarRect, seekBarPoint, time, cb) {
 
-       this.requestNamedAnimationFrame('TimeTooltip#updateTime', () => {
 
-         let content;
 
-         const duration = this.player_.duration();
 
-         if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
 
-           const liveWindow = this.player_.liveTracker.liveWindow();
 
-           const secondsBehind = liveWindow - seekBarPoint * liveWindow;
 
-           content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
 
-         } else {
 
-           content = formatTime(time, duration);
 
-         }
 
-         this.update(seekBarRect, seekBarPoint, content);
 
-         if (cb) {
 
-           cb();
 
-         }
 
-       });
 
-     }
 
-   }
 
-   Component$1.registerComponent('TimeTooltip', TimeTooltip);
 
-   /**
 
-    * @file play-progress-bar.js
 
-    */
 
-   /**
 
-    * Used by {@link SeekBar} to display media playback progress as part of the
 
-    * {@link ProgressControl}.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class PlayProgressBar extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The {@link Player} that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
 
-     }
 
-     /**
 
-      * Create the the DOM element for this class.
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-play-progress vjs-slider-bar'
 
-       }, {
 
-         'aria-hidden': 'true'
 
-       });
 
-     }
 
-     /**
 
-      * Enqueues updates to its own DOM as well as the DOM of its
 
-      * {@link TimeTooltip} child.
 
-      *
 
-      * @param {Object} seekBarRect
 
-      *        The `ClientRect` for the {@link SeekBar} element.
 
-      *
 
-      * @param {number} seekBarPoint
 
-      *        A number from 0 to 1, representing a horizontal reference point
 
-      *        from the left edge of the {@link SeekBar}
 
-      */
 
-     update(seekBarRect, seekBarPoint) {
 
-       const timeTooltip = this.getChild('timeTooltip');
 
-       if (!timeTooltip) {
 
-         return;
 
-       }
 
-       const time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
 
-       timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
 
-     }
 
-   }
 
-   /**
 
-    * Default options for {@link PlayProgressBar}.
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   PlayProgressBar.prototype.options_ = {
 
-     children: []
 
-   };
 
-   // Time tooltips should not be added to a player on mobile devices
 
-   if (!IS_IOS && !IS_ANDROID) {
 
-     PlayProgressBar.prototype.options_.children.push('timeTooltip');
 
-   }
 
-   Component$1.registerComponent('PlayProgressBar', PlayProgressBar);
 
-   /**
 
-    * @file mouse-time-display.js
 
-    */
 
-   /**
 
-    * The {@link MouseTimeDisplay} component tracks mouse movement over the
 
-    * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
 
-    * indicating the time which is represented by a given point in the
 
-    * {@link ProgressControl}.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class MouseTimeDisplay extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The {@link Player} that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
 
-     }
 
-     /**
 
-      * Create the DOM element for this class.
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-mouse-display'
 
-       });
 
-     }
 
-     /**
 
-      * Enqueues updates to its own DOM as well as the DOM of its
 
-      * {@link TimeTooltip} child.
 
-      *
 
-      * @param {Object} seekBarRect
 
-      *        The `ClientRect` for the {@link SeekBar} element.
 
-      *
 
-      * @param {number} seekBarPoint
 
-      *        A number from 0 to 1, representing a horizontal reference point
 
-      *        from the left edge of the {@link SeekBar}
 
-      */
 
-     update(seekBarRect, seekBarPoint) {
 
-       const time = seekBarPoint * this.player_.duration();
 
-       this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, () => {
 
-         this.el_.style.left = `${seekBarRect.width * seekBarPoint}px`;
 
-       });
 
-     }
 
-   }
 
-   /**
 
-    * Default options for `MouseTimeDisplay`
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   MouseTimeDisplay.prototype.options_ = {
 
-     children: ['timeTooltip']
 
-   };
 
-   Component$1.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
 
-   /**
 
-    * @file seek-bar.js
 
-    */
 
-   // The number of seconds the `step*` functions move the timeline.
 
-   const STEP_SECONDS = 5;
 
-   // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
 
-   const PAGE_KEY_MULTIPLIER = 12;
 
-   /**
 
-    * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
 
-    * as its `bar`.
 
-    *
 
-    * @extends Slider
 
-    */
 
-   class SeekBar extends Slider {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.setEventHandlers_();
 
-     }
 
-     /**
 
-      * Sets the event handlers
 
-      *
 
-      * @private
 
-      */
 
-     setEventHandlers_() {
 
-       this.update_ = bind_(this, this.update);
 
-       this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
 
-       this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
 
-       if (this.player_.liveTracker) {
 
-         this.on(this.player_.liveTracker, 'liveedgechange', this.update);
 
-       }
 
-       // when playing, let's ensure we smoothly update the play progress bar
 
-       // via an interval
 
-       this.updateInterval = null;
 
-       this.enableIntervalHandler_ = e => this.enableInterval_(e);
 
-       this.disableIntervalHandler_ = e => this.disableInterval_(e);
 
-       this.on(this.player_, ['playing'], this.enableIntervalHandler_);
 
-       this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
 
-       // we don't need to update the play progress if the document is hidden,
 
-       // also, this causes the CPU to spike and eventually crash the page on IE11.
 
-       if ('hidden' in document && 'visibilityState' in document) {
 
-         this.on(document, 'visibilitychange', this.toggleVisibility_);
 
-       }
 
-     }
 
-     toggleVisibility_(e) {
 
-       if (document.visibilityState === 'hidden') {
 
-         this.cancelNamedAnimationFrame('SeekBar#update');
 
-         this.cancelNamedAnimationFrame('Slider#update');
 
-         this.disableInterval_(e);
 
-       } else {
 
-         if (!this.player_.ended() && !this.player_.paused()) {
 
-           this.enableInterval_();
 
-         }
 
-         // we just switched back to the page and someone may be looking, so, update ASAP
 
-         this.update();
 
-       }
 
-     }
 
-     enableInterval_() {
 
-       if (this.updateInterval) {
 
-         return;
 
-       }
 
-       this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
 
-     }
 
-     disableInterval_(e) {
 
-       if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') {
 
-         return;
 
-       }
 
-       if (!this.updateInterval) {
 
-         return;
 
-       }
 
-       this.clearInterval(this.updateInterval);
 
-       this.updateInterval = null;
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-progress-holder'
 
-       }, {
 
-         'aria-label': this.localize('Progress Bar')
 
-       });
 
-     }
 
-     /**
 
-      * This function updates the play progress bar and accessibility
 
-      * attributes to whatever is passed in.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `timeupdate` or `ended` event that caused this to run.
 
-      *
 
-      * @listens Player#timeupdate
 
-      *
 
-      * @return {number}
 
-      *          The current percent at a number from 0-1
 
-      */
 
-     update(event) {
 
-       // ignore updates while the tab is hidden
 
-       if (document.visibilityState === 'hidden') {
 
-         return;
 
-       }
 
-       const percent = super.update();
 
-       this.requestNamedAnimationFrame('SeekBar#update', () => {
 
-         const currentTime = this.player_.ended() ? this.player_.duration() : this.getCurrentTime_();
 
-         const liveTracker = this.player_.liveTracker;
 
-         let duration = this.player_.duration();
 
-         if (liveTracker && liveTracker.isLive()) {
 
-           duration = this.player_.liveTracker.liveCurrentTime();
 
-         }
 
-         if (this.percent_ !== percent) {
 
-           // machine readable value of progress bar (percentage complete)
 
-           this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
 
-           this.percent_ = percent;
 
-         }
 
-         if (this.currentTime_ !== currentTime || this.duration_ !== duration) {
 
-           // human readable value of progress bar (time complete)
 
-           this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
 
-           this.currentTime_ = currentTime;
 
-           this.duration_ = duration;
 
-         }
 
-         // update the progress bar time tooltip with the current time
 
-         if (this.bar) {
 
-           this.bar.update(getBoundingClientRect(this.el()), this.getProgress());
 
-         }
 
-       });
 
-       return percent;
 
-     }
 
-     /**
 
-      * Prevent liveThreshold from causing seeks to seem like they
 
-      * are not happening from a user perspective.
 
-      *
 
-      * @param {number} ct
 
-      *        current time to seek to
 
-      */
 
-     userSeek_(ct) {
 
-       if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
 
-         this.player_.liveTracker.nextSeekedFromUser();
 
-       }
 
-       this.player_.currentTime(ct);
 
-     }
 
-     /**
 
-      * Get the value of current time but allows for smooth scrubbing,
 
-      * when player can't keep up.
 
-      *
 
-      * @return {number}
 
-      *         The current time value to display
 
-      *
 
-      * @private
 
-      */
 
-     getCurrentTime_() {
 
-       return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
 
-     }
 
-     /**
 
-      * Get the percentage of media played so far.
 
-      *
 
-      * @return {number}
 
-      *         The percentage of media played so far (0 to 1).
 
-      */
 
-     getPercent() {
 
-       const currentTime = this.getCurrentTime_();
 
-       let percent;
 
-       const liveTracker = this.player_.liveTracker;
 
-       if (liveTracker && liveTracker.isLive()) {
 
-         percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow();
 
-         // prevent the percent from changing at the live edge
 
-         if (liveTracker.atLiveEdge()) {
 
-           percent = 1;
 
-         }
 
-       } else {
 
-         percent = currentTime / this.player_.duration();
 
-       }
 
-       return percent;
 
-     }
 
-     /**
 
-      * Handle mouse down on seek bar
 
-      *
 
-      * @param {MouseEvent} event
 
-      *        The `mousedown` event that caused this to run.
 
-      *
 
-      * @listens mousedown
 
-      */
 
-     handleMouseDown(event) {
 
-       if (!isSingleLeftClick(event)) {
 
-         return;
 
-       }
 
-       // Stop event propagation to prevent double fire in progress-control.js
 
-       event.stopPropagation();
 
-       this.videoWasPlaying = !this.player_.paused();
 
-       this.player_.pause();
 
-       super.handleMouseDown(event);
 
-     }
 
-     /**
 
-      * Handle mouse move on seek bar
 
-      *
 
-      * @param {MouseEvent} event
 
-      *        The `mousemove` event that caused this to run.
 
-      * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false
 
-      *
 
-      * @listens mousemove
 
-      */
 
-     handleMouseMove(event, mouseDown = false) {
 
-       if (!isSingleLeftClick(event)) {
 
-         return;
 
-       }
 
-       if (!mouseDown && !this.player_.scrubbing()) {
 
-         this.player_.scrubbing(true);
 
-       }
 
-       let newTime;
 
-       const distance = this.calculateDistance(event);
 
-       const liveTracker = this.player_.liveTracker;
 
-       if (!liveTracker || !liveTracker.isLive()) {
 
-         newTime = distance * this.player_.duration();
 
-         // Don't let video end while scrubbing.
 
-         if (newTime === this.player_.duration()) {
 
-           newTime = newTime - 0.1;
 
-         }
 
-       } else {
 
-         if (distance >= 0.99) {
 
-           liveTracker.seekToLiveEdge();
 
-           return;
 
-         }
 
-         const seekableStart = liveTracker.seekableStart();
 
-         const seekableEnd = liveTracker.liveCurrentTime();
 
-         newTime = seekableStart + distance * liveTracker.liveWindow();
 
-         // Don't let video end while scrubbing.
 
-         if (newTime >= seekableEnd) {
 
-           newTime = seekableEnd;
 
-         }
 
-         // Compensate for precision differences so that currentTime is not less
 
-         // than seekable start
 
-         if (newTime <= seekableStart) {
 
-           newTime = seekableStart + 0.1;
 
-         }
 
-         // On android seekableEnd can be Infinity sometimes,
 
-         // this will cause newTime to be Infinity, which is
 
-         // not a valid currentTime.
 
-         if (newTime === Infinity) {
 
-           return;
 
-         }
 
-       }
 
-       // Set new time (tell player to seek to new time)
 
-       this.userSeek_(newTime);
 
-     }
 
-     enable() {
 
-       super.enable();
 
-       const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
 
-       if (!mouseTimeDisplay) {
 
-         return;
 
-       }
 
-       mouseTimeDisplay.show();
 
-     }
 
-     disable() {
 
-       super.disable();
 
-       const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
 
-       if (!mouseTimeDisplay) {
 
-         return;
 
-       }
 
-       mouseTimeDisplay.hide();
 
-     }
 
-     /**
 
-      * Handle mouse up on seek bar
 
-      *
 
-      * @param {MouseEvent} event
 
-      *        The `mouseup` event that caused this to run.
 
-      *
 
-      * @listens mouseup
 
-      */
 
-     handleMouseUp(event) {
 
-       super.handleMouseUp(event);
 
-       // Stop event propagation to prevent double fire in progress-control.js
 
-       if (event) {
 
-         event.stopPropagation();
 
-       }
 
-       this.player_.scrubbing(false);
 
-       /**
 
-        * Trigger timeupdate because we're done seeking and the time has changed.
 
-        * This is particularly useful for if the player is paused to time the time displays.
 
-        *
 
-        * @event Tech#timeupdate
 
-        * @type {Event}
 
-        */
 
-       this.player_.trigger({
 
-         type: 'timeupdate',
 
-         target: this,
 
-         manuallyTriggered: true
 
-       });
 
-       if (this.videoWasPlaying) {
 
-         silencePromise(this.player_.play());
 
-       } else {
 
-         // We're done seeking and the time has changed.
 
-         // If the player is paused, make sure we display the correct time on the seek bar.
 
-         this.update_();
 
-       }
 
-     }
 
-     /**
 
-      * Move more quickly fast forward for keyboard-only users
 
-      */
 
-     stepForward() {
 
-       this.userSeek_(this.player_.currentTime() + STEP_SECONDS);
 
-     }
 
-     /**
 
-      * Move more quickly rewind for keyboard-only users
 
-      */
 
-     stepBack() {
 
-       this.userSeek_(this.player_.currentTime() - STEP_SECONDS);
 
-     }
 
-     /**
 
-      * Toggles the playback state of the player
 
-      * This gets called when enter or space is used on the seekbar
 
-      *
 
-      * @param {KeyboardEvent} event
 
-      *        The `keydown` event that caused this function to be called
 
-      *
 
-      */
 
-     handleAction(event) {
 
-       if (this.player_.paused()) {
 
-         this.player_.play();
 
-       } else {
 
-         this.player_.pause();
 
-       }
 
-     }
 
-     /**
 
-      * Called when this SeekBar has focus and a key gets pressed down.
 
-      * Supports the following keys:
 
-      *
 
-      *   Space or Enter key fire a click event
 
-      *   Home key moves to start of the timeline
 
-      *   End key moves to end of the timeline
 
-      *   Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
 
-      *   PageDown key moves back a larger step than ArrowDown
 
-      *   PageUp key moves forward a large step
 
-      *
 
-      * @param {KeyboardEvent} event
 
-      *        The `keydown` event that caused this function to be called.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       const liveTracker = this.player_.liveTracker;
 
-       if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.handleAction(event);
 
-       } else if (keycode.isEventKey(event, 'Home')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.userSeek_(0);
 
-       } else if (keycode.isEventKey(event, 'End')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         if (liveTracker && liveTracker.isLive()) {
 
-           this.userSeek_(liveTracker.liveCurrentTime());
 
-         } else {
 
-           this.userSeek_(this.player_.duration());
 
-         }
 
-       } else if (/^[0-9]$/.test(keycode(event))) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         const gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
 
-         if (liveTracker && liveTracker.isLive()) {
 
-           this.userSeek_(liveTracker.seekableStart() + liveTracker.liveWindow() * gotoFraction);
 
-         } else {
 
-           this.userSeek_(this.player_.duration() * gotoFraction);
 
-         }
 
-       } else if (keycode.isEventKey(event, 'PgDn')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.userSeek_(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
 
-       } else if (keycode.isEventKey(event, 'PgUp')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.userSeek_(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
 
-       } else {
 
-         // Pass keydown handling up for unsupported keys
 
-         super.handleKeyDown(event);
 
-       }
 
-     }
 
-     dispose() {
 
-       this.disableInterval_();
 
-       this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
 
-       if (this.player_.liveTracker) {
 
-         this.off(this.player_.liveTracker, 'liveedgechange', this.update);
 
-       }
 
-       this.off(this.player_, ['playing'], this.enableIntervalHandler_);
 
-       this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
 
-       // we don't need to update the play progress if the document is hidden,
 
-       // also, this causes the CPU to spike and eventually crash the page on IE11.
 
-       if ('hidden' in document && 'visibilityState' in document) {
 
-         this.off(document, 'visibilitychange', this.toggleVisibility_);
 
-       }
 
-       super.dispose();
 
-     }
 
-   }
 
-   /**
 
-    * Default options for the `SeekBar`
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   SeekBar.prototype.options_ = {
 
-     children: ['loadProgressBar', 'playProgressBar'],
 
-     barName: 'playProgressBar'
 
-   };
 
-   // MouseTimeDisplay tooltips should not be added to a player on mobile devices
 
-   if (!IS_IOS && !IS_ANDROID) {
 
-     SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
 
-   }
 
-   Component$1.registerComponent('SeekBar', SeekBar);
 
-   /**
 
-    * @file progress-control.js
 
-    */
 
-   /**
 
-    * The Progress Control component contains the seek bar, load progress,
 
-    * and play progress.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class ProgressControl extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.handleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
 
-       this.throttledHandleMouseSeek = throttle(bind_(this, this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
 
-       this.handleMouseUpHandler_ = e => this.handleMouseUp(e);
 
-       this.handleMouseDownHandler_ = e => this.handleMouseDown(e);
 
-       this.enable();
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-progress-control vjs-control'
 
-       });
 
-     }
 
-     /**
 
-      * When the mouse moves over the `ProgressControl`, the pointer position
 
-      * gets passed down to the `MouseTimeDisplay` component.
 
-      *
 
-      * @param {Event} event
 
-      *        The `mousemove` event that caused this function to run.
 
-      *
 
-      * @listen mousemove
 
-      */
 
-     handleMouseMove(event) {
 
-       const seekBar = this.getChild('seekBar');
 
-       if (!seekBar) {
 
-         return;
 
-       }
 
-       const playProgressBar = seekBar.getChild('playProgressBar');
 
-       const mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
 
-       if (!playProgressBar && !mouseTimeDisplay) {
 
-         return;
 
-       }
 
-       const seekBarEl = seekBar.el();
 
-       const seekBarRect = findPosition(seekBarEl);
 
-       let seekBarPoint = getPointerPosition(seekBarEl, event).x;
 
-       // The default skin has a gap on either side of the `SeekBar`. This means
 
-       // that it's possible to trigger this behavior outside the boundaries of
 
-       // the `SeekBar`. This ensures we stay within it at all times.
 
-       seekBarPoint = clamp(seekBarPoint, 0, 1);
 
-       if (mouseTimeDisplay) {
 
-         mouseTimeDisplay.update(seekBarRect, seekBarPoint);
 
-       }
 
-       if (playProgressBar) {
 
-         playProgressBar.update(seekBarRect, seekBar.getProgress());
 
-       }
 
-     }
 
-     /**
 
-      * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
 
-      *
 
-      * @method ProgressControl#throttledHandleMouseSeek
 
-      * @param {Event} event
 
-      *        The `mousemove` event that caused this function to run.
 
-      *
 
-      * @listen mousemove
 
-      * @listen touchmove
 
-      */
 
-     /**
 
-      * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
 
-      *
 
-      * @param {Event} event
 
-      *        `mousedown` or `touchstart` event that triggered this function
 
-      *
 
-      * @listens mousemove
 
-      * @listens touchmove
 
-      */
 
-     handleMouseSeek(event) {
 
-       const seekBar = this.getChild('seekBar');
 
-       if (seekBar) {
 
-         seekBar.handleMouseMove(event);
 
-       }
 
-     }
 
-     /**
 
-      * Are controls are currently enabled for this progress control.
 
-      *
 
-      * @return {boolean}
 
-      *         true if controls are enabled, false otherwise
 
-      */
 
-     enabled() {
 
-       return this.enabled_;
 
-     }
 
-     /**
 
-      * Disable all controls on the progress control and its children
 
-      */
 
-     disable() {
 
-       this.children().forEach(child => child.disable && child.disable());
 
-       if (!this.enabled()) {
 
-         return;
 
-       }
 
-       this.off(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
 
-       this.off(this.el_, 'mousemove', this.handleMouseMove);
 
-       this.removeListenersAddedOnMousedownAndTouchstart();
 
-       this.addClass('disabled');
 
-       this.enabled_ = false;
 
-       // Restore normal playback state if controls are disabled while scrubbing
 
-       if (this.player_.scrubbing()) {
 
-         const seekBar = this.getChild('seekBar');
 
-         this.player_.scrubbing(false);
 
-         if (seekBar.videoWasPlaying) {
 
-           silencePromise(this.player_.play());
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Enable all controls on the progress control and its children
 
-      */
 
-     enable() {
 
-       this.children().forEach(child => child.enable && child.enable());
 
-       if (this.enabled()) {
 
-         return;
 
-       }
 
-       this.on(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
 
-       this.on(this.el_, 'mousemove', this.handleMouseMove);
 
-       this.removeClass('disabled');
 
-       this.enabled_ = true;
 
-     }
 
-     /**
 
-      * Cleanup listeners after the user finishes interacting with the progress controls
 
-      */
 
-     removeListenersAddedOnMousedownAndTouchstart() {
 
-       const doc = this.el_.ownerDocument;
 
-       this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
 
-       this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
 
-       this.off(doc, 'mouseup', this.handleMouseUpHandler_);
 
-       this.off(doc, 'touchend', this.handleMouseUpHandler_);
 
-     }
 
-     /**
 
-      * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
 
-      *
 
-      * @param {Event} event
 
-      *        `mousedown` or `touchstart` event that triggered this function
 
-      *
 
-      * @listens mousedown
 
-      * @listens touchstart
 
-      */
 
-     handleMouseDown(event) {
 
-       const doc = this.el_.ownerDocument;
 
-       const seekBar = this.getChild('seekBar');
 
-       if (seekBar) {
 
-         seekBar.handleMouseDown(event);
 
-       }
 
-       this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
 
-       this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
 
-       this.on(doc, 'mouseup', this.handleMouseUpHandler_);
 
-       this.on(doc, 'touchend', this.handleMouseUpHandler_);
 
-     }
 
-     /**
 
-      * Handle `mouseup` or `touchend` events on the `ProgressControl`.
 
-      *
 
-      * @param {Event} event
 
-      *        `mouseup` or `touchend` event that triggered this function.
 
-      *
 
-      * @listens touchend
 
-      * @listens mouseup
 
-      */
 
-     handleMouseUp(event) {
 
-       const seekBar = this.getChild('seekBar');
 
-       if (seekBar) {
 
-         seekBar.handleMouseUp(event);
 
-       }
 
-       this.removeListenersAddedOnMousedownAndTouchstart();
 
-     }
 
-   }
 
-   /**
 
-    * Default options for `ProgressControl`
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   ProgressControl.prototype.options_ = {
 
-     children: ['seekBar']
 
-   };
 
-   Component$1.registerComponent('ProgressControl', ProgressControl);
 
-   /**
 
-    * @file picture-in-picture-toggle.js
 
-    */
 
-   /**
 
-    * Toggle Picture-in-Picture mode
 
-    *
 
-    * @extends Button
 
-    */
 
-   class PictureInPictureToggle extends Button {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @listens Player#enterpictureinpicture
 
-      * @listens Player#leavepictureinpicture
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], e => this.handlePictureInPictureChange(e));
 
-       this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], e => this.handlePictureInPictureEnabledChange(e));
 
-       this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () => {
 
-         // This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
 
-         const isSourceAudio = player.currentType().substring(0, 5) === 'audio';
 
-         if (isSourceAudio || player.audioPosterMode() || player.audioOnlyMode()) {
 
-           if (player.isInPictureInPicture()) {
 
-             player.exitPictureInPicture();
 
-           }
 
-           this.hide();
 
-         } else {
 
-           this.show();
 
-         }
 
-       });
 
-       // TODO: Deactivate button on player emptied event.
 
-       this.disable();
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-picture-in-picture-control ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * Enables or disables button based on availability of a Picture-In-Picture mode.
 
-      *
 
-      * Enabled if
 
-      * - `player.options().enableDocumentPictureInPicture` is true and
 
-      *   window.documentPictureInPicture is available; or
 
-      * - `player.disablePictureInPicture()` is false and
 
-      *   element.requestPictureInPicture is available
 
-      */
 
-     handlePictureInPictureEnabledChange() {
 
-       if (document.pictureInPictureEnabled && this.player_.disablePictureInPicture() === false || this.player_.options_.enableDocumentPictureInPicture && 'documentPictureInPicture' in window) {
 
-         this.enable();
 
-       } else {
 
-         this.disable();
 
-       }
 
-     }
 
-     /**
 
-      * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens Player#enterpictureinpicture
 
-      * @listens Player#leavepictureinpicture
 
-      */
 
-     handlePictureInPictureChange(event) {
 
-       if (this.player_.isInPictureInPicture()) {
 
-         this.controlText('Exit Picture-in-Picture');
 
-       } else {
 
-         this.controlText('Picture-in-Picture');
 
-       }
 
-       this.handlePictureInPictureEnabledChange();
 
-     }
 
-     /**
 
-      * This gets called when an `PictureInPictureToggle` is "clicked". See
 
-      * {@link ClickableComponent} for more detailed information on what a click can be.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       if (!this.player_.isInPictureInPicture()) {
 
-         this.player_.requestPictureInPicture();
 
-       } else {
 
-         this.player_.exitPictureInPicture();
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
 
-   Component$1.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
 
-   /**
 
-    * @file fullscreen-toggle.js
 
-    */
 
-   /**
 
-    * Toggle fullscreen video
 
-    *
 
-    * @extends Button
 
-    */
 
-   class FullscreenToggle extends Button {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.on(player, 'fullscreenchange', e => this.handleFullscreenChange(e));
 
-       if (document[player.fsApi_.fullscreenEnabled] === false) {
 
-         this.disable();
 
-       }
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-fullscreen-control ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * Handles fullscreenchange on the player and change control text accordingly.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The {@link Player#fullscreenchange} event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens Player#fullscreenchange
 
-      */
 
-     handleFullscreenChange(event) {
 
-       if (this.player_.isFullscreen()) {
 
-         this.controlText('Exit Fullscreen');
 
-       } else {
 
-         this.controlText('Fullscreen');
 
-       }
 
-     }
 
-     /**
 
-      * This gets called when an `FullscreenToggle` is "clicked". See
 
-      * {@link ClickableComponent} for more detailed information on what a click can be.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       if (!this.player_.isFullscreen()) {
 
-         this.player_.requestFullscreen();
 
-       } else {
 
-         this.player_.exitFullscreen();
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * The text that should display over the `FullscreenToggle`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   FullscreenToggle.prototype.controlText_ = 'Fullscreen';
 
-   Component$1.registerComponent('FullscreenToggle', FullscreenToggle);
 
-   /**
 
-    * Check if volume control is supported and if it isn't hide the
 
-    * `Component` that was passed  using the `vjs-hidden` class.
 
-    *
 
-    * @param { import('../../component').default } self
 
-    *        The component that should be hidden if volume is unsupported
 
-    *
 
-    * @param { import('../../player').default } player
 
-    *        A reference to the player
 
-    *
 
-    * @private
 
-    */
 
-   const checkVolumeSupport = function (self, player) {
 
-     // hide volume controls when they're not supported by the current tech
 
-     if (player.tech_ && !player.tech_.featuresVolumeControl) {
 
-       self.addClass('vjs-hidden');
 
-     }
 
-     self.on(player, 'loadstart', function () {
 
-       if (!player.tech_.featuresVolumeControl) {
 
-         self.addClass('vjs-hidden');
 
-       } else {
 
-         self.removeClass('vjs-hidden');
 
-       }
 
-     });
 
-   };
 
-   /**
 
-    * @file volume-level.js
 
-    */
 
-   /**
 
-    * Shows volume level
 
-    *
 
-    * @extends Component
 
-    */
 
-   class VolumeLevel extends Component$1 {
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       const el = super.createEl('div', {
 
-         className: 'vjs-volume-level'
 
-       });
 
-       el.appendChild(super.createEl('span', {
 
-         className: 'vjs-control-text'
 
-       }));
 
-       return el;
 
-     }
 
-   }
 
-   Component$1.registerComponent('VolumeLevel', VolumeLevel);
 
-   /**
 
-    * @file volume-level-tooltip.js
 
-    */
 
-   /**
 
-    * Volume level tooltips display a volume above or side by side the volume bar.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class VolumeLevelTooltip extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The {@link Player} that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
 
-     }
 
-     /**
 
-      * Create the volume tooltip DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-volume-tooltip'
 
-       }, {
 
-         'aria-hidden': 'true'
 
-       });
 
-     }
 
-     /**
 
-      * Updates the position of the tooltip relative to the `VolumeBar` and
 
-      * its content text.
 
-      *
 
-      * @param {Object} rangeBarRect
 
-      *        The `ClientRect` for the {@link VolumeBar} element.
 
-      *
 
-      * @param {number} rangeBarPoint
 
-      *        A number from 0 to 1, representing a horizontal/vertical reference point
 
-      *        from the left edge of the {@link VolumeBar}
 
-      *
 
-      * @param {boolean} vertical
 
-      *        Referees to the Volume control position
 
-      *        in the control bar{@link VolumeControl}
 
-      *
 
-      */
 
-     update(rangeBarRect, rangeBarPoint, vertical, content) {
 
-       if (!vertical) {
 
-         const tooltipRect = getBoundingClientRect(this.el_);
 
-         const playerRect = getBoundingClientRect(this.player_.el());
 
-         const volumeBarPointPx = rangeBarRect.width * rangeBarPoint;
 
-         if (!playerRect || !tooltipRect) {
 
-           return;
 
-         }
 
-         const spaceLeftOfPoint = rangeBarRect.left - playerRect.left + volumeBarPointPx;
 
-         const spaceRightOfPoint = rangeBarRect.width - volumeBarPointPx + (playerRect.right - rangeBarRect.right);
 
-         let pullTooltipBy = tooltipRect.width / 2;
 
-         if (spaceLeftOfPoint < pullTooltipBy) {
 
-           pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
 
-         } else if (spaceRightOfPoint < pullTooltipBy) {
 
-           pullTooltipBy = spaceRightOfPoint;
 
-         }
 
-         if (pullTooltipBy < 0) {
 
-           pullTooltipBy = 0;
 
-         } else if (pullTooltipBy > tooltipRect.width) {
 
-           pullTooltipBy = tooltipRect.width;
 
-         }
 
-         this.el_.style.right = `-${pullTooltipBy}px`;
 
-       }
 
-       this.write(`${content}%`);
 
-     }
 
-     /**
 
-      * Write the volume to the tooltip DOM element.
 
-      *
 
-      * @param {string} content
 
-      *        The formatted volume for the tooltip.
 
-      */
 
-     write(content) {
 
-       textContent(this.el_, content);
 
-     }
 
-     /**
 
-      * Updates the position of the volume tooltip relative to the `VolumeBar`.
 
-      *
 
-      * @param {Object} rangeBarRect
 
-      *        The `ClientRect` for the {@link VolumeBar} element.
 
-      *
 
-      * @param {number} rangeBarPoint
 
-      *        A number from 0 to 1, representing a horizontal/vertical reference point
 
-      *        from the left edge of the {@link VolumeBar}
 
-      *
 
-      * @param {boolean} vertical
 
-      *        Referees to the Volume control position
 
-      *        in the control bar{@link VolumeControl}
 
-      *
 
-      * @param {number} volume
 
-      *        The volume level to update the tooltip to
 
-      *
 
-      * @param {Function} cb
 
-      *        A function that will be called during the request animation frame
 
-      *        for tooltips that need to do additional animations from the default
 
-      */
 
-     updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, cb) {
 
-       this.requestNamedAnimationFrame('VolumeLevelTooltip#updateVolume', () => {
 
-         this.update(rangeBarRect, rangeBarPoint, vertical, volume.toFixed(0));
 
-         if (cb) {
 
-           cb();
 
-         }
 
-       });
 
-     }
 
-   }
 
-   Component$1.registerComponent('VolumeLevelTooltip', VolumeLevelTooltip);
 
-   /**
 
-    * @file mouse-volume-level-display.js
 
-    */
 
-   /**
 
-    * The {@link MouseVolumeLevelDisplay} component tracks mouse movement over the
 
-    * {@link VolumeControl}. It displays an indicator and a {@link VolumeLevelTooltip}
 
-    * indicating the volume level which is represented by a given point in the
 
-    * {@link VolumeBar}.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class MouseVolumeLevelDisplay extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The {@link Player} that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
 
-     }
 
-     /**
 
-      * Create the DOM element for this class.
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-mouse-display'
 
-       });
 
-     }
 
-     /**
 
-      * Enquires updates to its own DOM as well as the DOM of its
 
-      * {@link VolumeLevelTooltip} child.
 
-      *
 
-      * @param {Object} rangeBarRect
 
-      *        The `ClientRect` for the {@link VolumeBar} element.
 
-      *
 
-      * @param {number} rangeBarPoint
 
-      *        A number from 0 to 1, representing a horizontal/vertical reference point
 
-      *        from the left edge of the {@link VolumeBar}
 
-      *
 
-      * @param {boolean} vertical
 
-      *        Referees to the Volume control position
 
-      *        in the control bar{@link VolumeControl}
 
-      *
 
-      */
 
-     update(rangeBarRect, rangeBarPoint, vertical) {
 
-       const volume = 100 * rangeBarPoint;
 
-       this.getChild('volumeLevelTooltip').updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, () => {
 
-         if (vertical) {
 
-           this.el_.style.bottom = `${rangeBarRect.height * rangeBarPoint}px`;
 
-         } else {
 
-           this.el_.style.left = `${rangeBarRect.width * rangeBarPoint}px`;
 
-         }
 
-       });
 
-     }
 
-   }
 
-   /**
 
-    * Default options for `MouseVolumeLevelDisplay`
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   MouseVolumeLevelDisplay.prototype.options_ = {
 
-     children: ['volumeLevelTooltip']
 
-   };
 
-   Component$1.registerComponent('MouseVolumeLevelDisplay', MouseVolumeLevelDisplay);
 
-   /**
 
-    * @file volume-bar.js
 
-    */
 
-   /**
 
-    * The bar that contains the volume level and can be clicked on to adjust the level
 
-    *
 
-    * @extends Slider
 
-    */
 
-   class VolumeBar extends Slider {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.on('slideractive', e => this.updateLastVolume_(e));
 
-       this.on(player, 'volumechange', e => this.updateARIAAttributes(e));
 
-       player.ready(() => this.updateARIAAttributes());
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-volume-bar vjs-slider-bar'
 
-       }, {
 
-         'aria-label': this.localize('Volume Level'),
 
-         'aria-live': 'polite'
 
-       });
 
-     }
 
-     /**
 
-      * Handle mouse down on volume bar
 
-      *
 
-      * @param {Event} event
 
-      *        The `mousedown` event that caused this to run.
 
-      *
 
-      * @listens mousedown
 
-      */
 
-     handleMouseDown(event) {
 
-       if (!isSingleLeftClick(event)) {
 
-         return;
 
-       }
 
-       super.handleMouseDown(event);
 
-     }
 
-     /**
 
-      * Handle movement events on the {@link VolumeMenuButton}.
 
-      *
 
-      * @param {Event} event
 
-      *        The event that caused this function to run.
 
-      *
 
-      * @listens mousemove
 
-      */
 
-     handleMouseMove(event) {
 
-       const mouseVolumeLevelDisplay = this.getChild('mouseVolumeLevelDisplay');
 
-       if (mouseVolumeLevelDisplay) {
 
-         const volumeBarEl = this.el();
 
-         const volumeBarRect = getBoundingClientRect(volumeBarEl);
 
-         const vertical = this.vertical();
 
-         let volumeBarPoint = getPointerPosition(volumeBarEl, event);
 
-         volumeBarPoint = vertical ? volumeBarPoint.y : volumeBarPoint.x;
 
-         // The default skin has a gap on either side of the `VolumeBar`. This means
 
-         // that it's possible to trigger this behavior outside the boundaries of
 
-         // the `VolumeBar`. This ensures we stay within it at all times.
 
-         volumeBarPoint = clamp(volumeBarPoint, 0, 1);
 
-         mouseVolumeLevelDisplay.update(volumeBarRect, volumeBarPoint, vertical);
 
-       }
 
-       if (!isSingleLeftClick(event)) {
 
-         return;
 
-       }
 
-       this.checkMuted();
 
-       this.player_.volume(this.calculateDistance(event));
 
-     }
 
-     /**
 
-      * If the player is muted unmute it.
 
-      */
 
-     checkMuted() {
 
-       if (this.player_.muted()) {
 
-         this.player_.muted(false);
 
-       }
 
-     }
 
-     /**
 
-      * Get percent of volume level
 
-      *
 
-      * @return {number}
 
-      *         Volume level percent as a decimal number.
 
-      */
 
-     getPercent() {
 
-       if (this.player_.muted()) {
 
-         return 0;
 
-       }
 
-       return this.player_.volume();
 
-     }
 
-     /**
 
-      * Increase volume level for keyboard users
 
-      */
 
-     stepForward() {
 
-       this.checkMuted();
 
-       this.player_.volume(this.player_.volume() + 0.1);
 
-     }
 
-     /**
 
-      * Decrease volume level for keyboard users
 
-      */
 
-     stepBack() {
 
-       this.checkMuted();
 
-       this.player_.volume(this.player_.volume() - 0.1);
 
-     }
 
-     /**
 
-      * Update ARIA accessibility attributes
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `volumechange` event that caused this function to run.
 
-      *
 
-      * @listens Player#volumechange
 
-      */
 
-     updateARIAAttributes(event) {
 
-       const ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
 
-       this.el_.setAttribute('aria-valuenow', ariaValue);
 
-       this.el_.setAttribute('aria-valuetext', ariaValue + '%');
 
-     }
 
-     /**
 
-      * Returns the current value of the player volume as a percentage
 
-      *
 
-      * @private
 
-      */
 
-     volumeAsPercentage_() {
 
-       return Math.round(this.player_.volume() * 100);
 
-     }
 
-     /**
 
-      * When user starts dragging the VolumeBar, store the volume and listen for
 
-      * the end of the drag. When the drag ends, if the volume was set to zero,
 
-      * set lastVolume to the stored volume.
 
-      *
 
-      * @listens slideractive
 
-      * @private
 
-      */
 
-     updateLastVolume_() {
 
-       const volumeBeforeDrag = this.player_.volume();
 
-       this.one('sliderinactive', () => {
 
-         if (this.player_.volume() === 0) {
 
-           this.player_.lastVolume_(volumeBeforeDrag);
 
-         }
 
-       });
 
-     }
 
-   }
 
-   /**
 
-    * Default options for the `VolumeBar`
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   VolumeBar.prototype.options_ = {
 
-     children: ['volumeLevel'],
 
-     barName: 'volumeLevel'
 
-   };
 
-   // MouseVolumeLevelDisplay tooltip should not be added to a player on mobile devices
 
-   if (!IS_IOS && !IS_ANDROID) {
 
-     VolumeBar.prototype.options_.children.splice(0, 0, 'mouseVolumeLevelDisplay');
 
-   }
 
-   /**
 
-    * Call the update event for this Slider when this event happens on the player.
 
-    *
 
-    * @type {string}
 
-    */
 
-   VolumeBar.prototype.playerEvent = 'volumechange';
 
-   Component$1.registerComponent('VolumeBar', VolumeBar);
 
-   /**
 
-    * @file volume-control.js
 
-    */
 
-   /**
 
-    * The component for controlling the volume level
 
-    *
 
-    * @extends Component
 
-    */
 
-   class VolumeControl extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options = {}) {
 
-       options.vertical = options.vertical || false;
 
-       // Pass the vertical option down to the VolumeBar if
 
-       // the VolumeBar is turned on.
 
-       if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
 
-         options.volumeBar = options.volumeBar || {};
 
-         options.volumeBar.vertical = options.vertical;
 
-       }
 
-       super(player, options);
 
-       // hide this control if volume support is missing
 
-       checkVolumeSupport(this, player);
 
-       this.throttledHandleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
 
-       this.handleMouseUpHandler_ = e => this.handleMouseUp(e);
 
-       this.on('mousedown', e => this.handleMouseDown(e));
 
-       this.on('touchstart', e => this.handleMouseDown(e));
 
-       this.on('mousemove', e => this.handleMouseMove(e));
 
-       // while the slider is active (the mouse has been pressed down and
 
-       // is dragging) or in focus we do not want to hide the VolumeBar
 
-       this.on(this.volumeBar, ['focus', 'slideractive'], () => {
 
-         this.volumeBar.addClass('vjs-slider-active');
 
-         this.addClass('vjs-slider-active');
 
-         this.trigger('slideractive');
 
-       });
 
-       this.on(this.volumeBar, ['blur', 'sliderinactive'], () => {
 
-         this.volumeBar.removeClass('vjs-slider-active');
 
-         this.removeClass('vjs-slider-active');
 
-         this.trigger('sliderinactive');
 
-       });
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       let orientationClass = 'vjs-volume-horizontal';
 
-       if (this.options_.vertical) {
 
-         orientationClass = 'vjs-volume-vertical';
 
-       }
 
-       return super.createEl('div', {
 
-         className: `vjs-volume-control vjs-control ${orientationClass}`
 
-       });
 
-     }
 
-     /**
 
-      * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
 
-      *
 
-      * @param {Event} event
 
-      *        `mousedown` or `touchstart` event that triggered this function
 
-      *
 
-      * @listens mousedown
 
-      * @listens touchstart
 
-      */
 
-     handleMouseDown(event) {
 
-       const doc = this.el_.ownerDocument;
 
-       this.on(doc, 'mousemove', this.throttledHandleMouseMove);
 
-       this.on(doc, 'touchmove', this.throttledHandleMouseMove);
 
-       this.on(doc, 'mouseup', this.handleMouseUpHandler_);
 
-       this.on(doc, 'touchend', this.handleMouseUpHandler_);
 
-     }
 
-     /**
 
-      * Handle `mouseup` or `touchend` events on the `VolumeControl`.
 
-      *
 
-      * @param {Event} event
 
-      *        `mouseup` or `touchend` event that triggered this function.
 
-      *
 
-      * @listens touchend
 
-      * @listens mouseup
 
-      */
 
-     handleMouseUp(event) {
 
-       const doc = this.el_.ownerDocument;
 
-       this.off(doc, 'mousemove', this.throttledHandleMouseMove);
 
-       this.off(doc, 'touchmove', this.throttledHandleMouseMove);
 
-       this.off(doc, 'mouseup', this.handleMouseUpHandler_);
 
-       this.off(doc, 'touchend', this.handleMouseUpHandler_);
 
-     }
 
-     /**
 
-      * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
 
-      *
 
-      * @param {Event} event
 
-      *        `mousedown` or `touchstart` event that triggered this function
 
-      *
 
-      * @listens mousedown
 
-      * @listens touchstart
 
-      */
 
-     handleMouseMove(event) {
 
-       this.volumeBar.handleMouseMove(event);
 
-     }
 
-   }
 
-   /**
 
-    * Default options for the `VolumeControl`
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   VolumeControl.prototype.options_ = {
 
-     children: ['volumeBar']
 
-   };
 
-   Component$1.registerComponent('VolumeControl', VolumeControl);
 
-   /**
 
-    * Check if muting volume is supported and if it isn't hide the mute toggle
 
-    * button.
 
-    *
 
-    * @param { import('../../component').default } self
 
-    *        A reference to the mute toggle button
 
-    *
 
-    * @param { import('../../player').default } player
 
-    *        A reference to the player
 
-    *
 
-    * @private
 
-    */
 
-   const checkMuteSupport = function (self, player) {
 
-     // hide mute toggle button if it's not supported by the current tech
 
-     if (player.tech_ && !player.tech_.featuresMuteControl) {
 
-       self.addClass('vjs-hidden');
 
-     }
 
-     self.on(player, 'loadstart', function () {
 
-       if (!player.tech_.featuresMuteControl) {
 
-         self.addClass('vjs-hidden');
 
-       } else {
 
-         self.removeClass('vjs-hidden');
 
-       }
 
-     });
 
-   };
 
-   /**
 
-    * @file mute-toggle.js
 
-    */
 
-   /**
 
-    * A button component for muting the audio.
 
-    *
 
-    * @extends Button
 
-    */
 
-   class MuteToggle extends Button {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       // hide this control if volume support is missing
 
-       checkMuteSupport(this, player);
 
-       this.on(player, ['loadstart', 'volumechange'], e => this.update(e));
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-mute-control ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * This gets called when an `MuteToggle` is "clicked". See
 
-      * {@link ClickableComponent} for more detailed information on what a click can be.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       const vol = this.player_.volume();
 
-       const lastVolume = this.player_.lastVolume_();
 
-       if (vol === 0) {
 
-         const volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
 
-         this.player_.volume(volumeToSet);
 
-         this.player_.muted(false);
 
-       } else {
 
-         this.player_.muted(this.player_.muted() ? false : true);
 
-       }
 
-     }
 
-     /**
 
-      * Update the `MuteToggle` button based on the state of `volume` and `muted`
 
-      * on the player.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The {@link Player#loadstart} event if this function was called
 
-      *        through an event.
 
-      *
 
-      * @listens Player#loadstart
 
-      * @listens Player#volumechange
 
-      */
 
-     update(event) {
 
-       this.updateIcon_();
 
-       this.updateControlText_();
 
-     }
 
-     /**
 
-      * Update the appearance of the `MuteToggle` icon.
 
-      *
 
-      * Possible states (given `level` variable below):
 
-      * - 0: crossed out
 
-      * - 1: zero bars of volume
 
-      * - 2: one bar of volume
 
-      * - 3: two bars of volume
 
-      *
 
-      * @private
 
-      */
 
-     updateIcon_() {
 
-       const vol = this.player_.volume();
 
-       let level = 3;
 
-       // in iOS when a player is loaded with muted attribute
 
-       // and volume is changed with a native mute button
 
-       // we want to make sure muted state is updated
 
-       if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
 
-         this.player_.muted(this.player_.tech_.el_.muted);
 
-       }
 
-       if (vol === 0 || this.player_.muted()) {
 
-         level = 0;
 
-       } else if (vol < 0.33) {
 
-         level = 1;
 
-       } else if (vol < 0.67) {
 
-         level = 2;
 
-       }
 
-       removeClass(this.el_, [0, 1, 2, 3].reduce((str, i) => str + `${i ? ' ' : ''}vjs-vol-${i}`, ''));
 
-       addClass(this.el_, `vjs-vol-${level}`);
 
-     }
 
-     /**
 
-      * If `muted` has changed on the player, update the control text
 
-      * (`title` attribute on `vjs-mute-control` element and content of
 
-      * `vjs-control-text` element).
 
-      *
 
-      * @private
 
-      */
 
-     updateControlText_() {
 
-       const soundOff = this.player_.muted() || this.player_.volume() === 0;
 
-       const text = soundOff ? 'Unmute' : 'Mute';
 
-       if (this.controlText() !== text) {
 
-         this.controlText(text);
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * The text that should display over the `MuteToggle`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   MuteToggle.prototype.controlText_ = 'Mute';
 
-   Component$1.registerComponent('MuteToggle', MuteToggle);
 
-   /**
 
-    * @file volume-control.js
 
-    */
 
-   /**
 
-    * A Component to contain the MuteToggle and VolumeControl so that
 
-    * they can work together.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class VolumePanel extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options = {}) {
 
-       if (typeof options.inline !== 'undefined') {
 
-         options.inline = options.inline;
 
-       } else {
 
-         options.inline = true;
 
-       }
 
-       // pass the inline option down to the VolumeControl as vertical if
 
-       // the VolumeControl is on.
 
-       if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
 
-         options.volumeControl = options.volumeControl || {};
 
-         options.volumeControl.vertical = !options.inline;
 
-       }
 
-       super(player, options);
 
-       // this handler is used by mouse handler methods below
 
-       this.handleKeyPressHandler_ = e => this.handleKeyPress(e);
 
-       this.on(player, ['loadstart'], e => this.volumePanelState_(e));
 
-       this.on(this.muteToggle, 'keyup', e => this.handleKeyPress(e));
 
-       this.on(this.volumeControl, 'keyup', e => this.handleVolumeControlKeyUp(e));
 
-       this.on('keydown', e => this.handleKeyPress(e));
 
-       this.on('mouseover', e => this.handleMouseOver(e));
 
-       this.on('mouseout', e => this.handleMouseOut(e));
 
-       // while the slider is active (the mouse has been pressed down and
 
-       // is dragging) we do not want to hide the VolumeBar
 
-       this.on(this.volumeControl, ['slideractive'], this.sliderActive_);
 
-       this.on(this.volumeControl, ['sliderinactive'], this.sliderInactive_);
 
-     }
 
-     /**
 
-      * Add vjs-slider-active class to the VolumePanel
 
-      *
 
-      * @listens VolumeControl#slideractive
 
-      * @private
 
-      */
 
-     sliderActive_() {
 
-       this.addClass('vjs-slider-active');
 
-     }
 
-     /**
 
-      * Removes vjs-slider-active class to the VolumePanel
 
-      *
 
-      * @listens VolumeControl#sliderinactive
 
-      * @private
 
-      */
 
-     sliderInactive_() {
 
-       this.removeClass('vjs-slider-active');
 
-     }
 
-     /**
 
-      * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
 
-      * depending on MuteToggle and VolumeControl state
 
-      *
 
-      * @listens Player#loadstart
 
-      * @private
 
-      */
 
-     volumePanelState_() {
 
-       // hide volume panel if neither volume control or mute toggle
 
-       // are displayed
 
-       if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
 
-         this.addClass('vjs-hidden');
 
-       }
 
-       // if only mute toggle is visible we don't want
 
-       // volume panel expanding when hovered or active
 
-       if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
 
-         this.addClass('vjs-mute-toggle-only');
 
-       }
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       let orientationClass = 'vjs-volume-panel-horizontal';
 
-       if (!this.options_.inline) {
 
-         orientationClass = 'vjs-volume-panel-vertical';
 
-       }
 
-       return super.createEl('div', {
 
-         className: `vjs-volume-panel vjs-control ${orientationClass}`
 
-       });
 
-     }
 
-     /**
 
-      * Dispose of the `volume-panel` and all child components.
 
-      */
 
-     dispose() {
 
-       this.handleMouseOut();
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes
 
-      * the volume panel and sets focus on `MuteToggle`.
 
-      *
 
-      * @param {Event} event
 
-      *        The `keyup` event that caused this function to be called.
 
-      *
 
-      * @listens keyup
 
-      */
 
-     handleVolumeControlKeyUp(event) {
 
-       if (keycode.isEventKey(event, 'Esc')) {
 
-         this.muteToggle.focus();
 
-       }
 
-     }
 
-     /**
 
-      * This gets called when a `VolumePanel` gains hover via a `mouseover` event.
 
-      * Turns on listening for `mouseover` event. When they happen it
 
-      * calls `this.handleMouseOver`.
 
-      *
 
-      * @param {Event} event
 
-      *        The `mouseover` event that caused this function to be called.
 
-      *
 
-      * @listens mouseover
 
-      */
 
-     handleMouseOver(event) {
 
-       this.addClass('vjs-hover');
 
-       on(document, 'keyup', this.handleKeyPressHandler_);
 
-     }
 
-     /**
 
-      * This gets called when a `VolumePanel` gains hover via a `mouseout` event.
 
-      * Turns on listening for `mouseout` event. When they happen it
 
-      * calls `this.handleMouseOut`.
 
-      *
 
-      * @param {Event} event
 
-      *        The `mouseout` event that caused this function to be called.
 
-      *
 
-      * @listens mouseout
 
-      */
 
-     handleMouseOut(event) {
 
-       this.removeClass('vjs-hover');
 
-       off(document, 'keyup', this.handleKeyPressHandler_);
 
-     }
 
-     /**
 
-      * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`,
 
-      * looking for ESC, which hides the `VolumeControl`.
 
-      *
 
-      * @param {Event} event
 
-      *        The keypress that triggered this event.
 
-      *
 
-      * @listens keydown | keyup
 
-      */
 
-     handleKeyPress(event) {
 
-       if (keycode.isEventKey(event, 'Esc')) {
 
-         this.handleMouseOut();
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * Default options for the `VolumeControl`
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   VolumePanel.prototype.options_ = {
 
-     children: ['muteToggle', 'volumeControl']
 
-   };
 
-   Component$1.registerComponent('VolumePanel', VolumePanel);
 
-   /**
 
-    * Button to skip forward a configurable amount of time
 
-    * through a video. Renders in the control bar.
 
-    *
 
-    * e.g. options: {controlBar: {skipButtons: forward: 5}}
 
-    *
 
-    * @extends Button
 
-    */
 
-   class SkipForward extends Button {
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.validOptions = [5, 10, 30];
 
-       this.skipTime = this.getSkipForwardTime();
 
-       if (this.skipTime && this.validOptions.includes(this.skipTime)) {
 
-         this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime]));
 
-         this.show();
 
-       } else {
 
-         this.hide();
 
-       }
 
-     }
 
-     getSkipForwardTime() {
 
-       const playerOptions = this.options_.playerOptions;
 
-       return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.forward;
 
-     }
 
-     buildCSSClass() {
 
-       return `vjs-skip-forward-${this.getSkipForwardTime()} ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * On click, skips forward in the duration/seekable range by a configurable amount of seconds.
 
-      * If the time left in the duration/seekable range is less than the configured 'skip forward' time,
 
-      * skips to end of duration/seekable range.
 
-      *
 
-      * Handle a click on a `SkipForward` button
 
-      *
 
-      * @param {EventTarget~Event} event
 
-      *        The `click` event that caused this function
 
-      *        to be called
 
-      */
 
-     handleClick(event) {
 
-       const currentVideoTime = this.player_.currentTime();
 
-       const liveTracker = this.player_.liveTracker;
 
-       const duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
 
-       let newTime;
 
-       if (currentVideoTime + this.skipTime <= duration) {
 
-         newTime = currentVideoTime + this.skipTime;
 
-       } else {
 
-         newTime = duration;
 
-       }
 
-       this.player_.currentTime(newTime);
 
-     }
 
-     /**
 
-      * Update control text on languagechange
 
-      */
 
-     handleLanguagechange() {
 
-       this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime]));
 
-     }
 
-   }
 
-   Component$1.registerComponent('SkipForward', SkipForward);
 
-   /**
 
-    * Button to skip backward a configurable amount of time
 
-    * through a video. Renders in the control bar.
 
-    *
 
-    *  * e.g. options: {controlBar: {skipButtons: backward: 5}}
 
-    *
 
-    * @extends Button
 
-    */
 
-   class SkipBackward extends Button {
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.validOptions = [5, 10, 30];
 
-       this.skipTime = this.getSkipBackwardTime();
 
-       if (this.skipTime && this.validOptions.includes(this.skipTime)) {
 
-         this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime]));
 
-         this.show();
 
-       } else {
 
-         this.hide();
 
-       }
 
-     }
 
-     getSkipBackwardTime() {
 
-       const playerOptions = this.options_.playerOptions;
 
-       return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.backward;
 
-     }
 
-     buildCSSClass() {
 
-       return `vjs-skip-backward-${this.getSkipBackwardTime()} ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * On click, skips backward in the video by a configurable amount of seconds.
 
-      * If the current time in the video is less than the configured 'skip backward' time,
 
-      * skips to beginning of video or seekable range.
 
-      *
 
-      * Handle a click on a `SkipBackward` button
 
-      *
 
-      * @param {EventTarget~Event} event
 
-      *        The `click` event that caused this function
 
-      *        to be called
 
-      */
 
-     handleClick(event) {
 
-       const currentVideoTime = this.player_.currentTime();
 
-       const liveTracker = this.player_.liveTracker;
 
-       const seekableStart = liveTracker && liveTracker.isLive() && liveTracker.seekableStart();
 
-       let newTime;
 
-       if (seekableStart && currentVideoTime - this.skipTime <= seekableStart) {
 
-         newTime = seekableStart;
 
-       } else if (currentVideoTime >= this.skipTime) {
 
-         newTime = currentVideoTime - this.skipTime;
 
-       } else {
 
-         newTime = 0;
 
-       }
 
-       this.player_.currentTime(newTime);
 
-     }
 
-     /**
 
-      * Update control text on languagechange
 
-      */
 
-     handleLanguagechange() {
 
-       this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime]));
 
-     }
 
-   }
 
-   SkipBackward.prototype.controlText_ = 'Skip Backward';
 
-   Component$1.registerComponent('SkipBackward', SkipBackward);
 
-   /**
 
-    * @file menu.js
 
-    */
 
-   /**
 
-    * The Menu component is used to build popup menus, including subtitle and
 
-    * captions selection menus.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class Menu extends Component$1 {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param { import('../player').default } player
 
-      *        the player that this component should attach to
 
-      *
 
-      * @param {Object} [options]
 
-      *        Object of option names and values
 
-      *
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       if (options) {
 
-         this.menuButton_ = options.menuButton;
 
-       }
 
-       this.focusedChild_ = -1;
 
-       this.on('keydown', e => this.handleKeyDown(e));
 
-       // All the menu item instances share the same blur handler provided by the menu container.
 
-       this.boundHandleBlur_ = e => this.handleBlur(e);
 
-       this.boundHandleTapClick_ = e => this.handleTapClick(e);
 
-     }
 
-     /**
 
-      * Add event listeners to the {@link MenuItem}.
 
-      *
 
-      * @param {Object} component
 
-      *        The instance of the `MenuItem` to add listeners to.
 
-      *
 
-      */
 
-     addEventListenerForItem(component) {
 
-       if (!(component instanceof Component$1)) {
 
-         return;
 
-       }
 
-       this.on(component, 'blur', this.boundHandleBlur_);
 
-       this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
 
-     }
 
-     /**
 
-      * Remove event listeners from the {@link MenuItem}.
 
-      *
 
-      * @param {Object} component
 
-      *        The instance of the `MenuItem` to remove listeners.
 
-      *
 
-      */
 
-     removeEventListenerForItem(component) {
 
-       if (!(component instanceof Component$1)) {
 
-         return;
 
-       }
 
-       this.off(component, 'blur', this.boundHandleBlur_);
 
-       this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
 
-     }
 
-     /**
 
-      * This method will be called indirectly when the component has been added
 
-      * before the component adds to the new menu instance by `addItem`.
 
-      * In this case, the original menu instance will remove the component
 
-      * by calling `removeChild`.
 
-      *
 
-      * @param {Object} component
 
-      *        The instance of the `MenuItem`
 
-      */
 
-     removeChild(component) {
 
-       if (typeof component === 'string') {
 
-         component = this.getChild(component);
 
-       }
 
-       this.removeEventListenerForItem(component);
 
-       super.removeChild(component);
 
-     }
 
-     /**
 
-      * Add a {@link MenuItem} to the menu.
 
-      *
 
-      * @param {Object|string} component
 
-      *        The name or instance of the `MenuItem` to add.
 
-      *
 
-      */
 
-     addItem(component) {
 
-       const childComponent = this.addChild(component);
 
-       if (childComponent) {
 
-         this.addEventListenerForItem(childComponent);
 
-       }
 
-     }
 
-     /**
 
-      * Create the `Menu`s DOM element.
 
-      *
 
-      * @return {Element}
 
-      *         the element that was created
 
-      */
 
-     createEl() {
 
-       const contentElType = this.options_.contentElType || 'ul';
 
-       this.contentEl_ = createEl(contentElType, {
 
-         className: 'vjs-menu-content'
 
-       });
 
-       this.contentEl_.setAttribute('role', 'menu');
 
-       const el = super.createEl('div', {
 
-         append: this.contentEl_,
 
-         className: 'vjs-menu'
 
-       });
 
-       el.appendChild(this.contentEl_);
 
-       // Prevent clicks from bubbling up. Needed for Menu Buttons,
 
-       // where a click on the parent is significant
 
-       on(el, 'click', function (event) {
 
-         event.preventDefault();
 
-         event.stopImmediatePropagation();
 
-       });
 
-       return el;
 
-     }
 
-     dispose() {
 
-       this.contentEl_ = null;
 
-       this.boundHandleBlur_ = null;
 
-       this.boundHandleTapClick_ = null;
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Called when a `MenuItem` loses focus.
 
-      *
 
-      * @param {Event} event
 
-      *        The `blur` event that caused this function to be called.
 
-      *
 
-      * @listens blur
 
-      */
 
-     handleBlur(event) {
 
-       const relatedTarget = event.relatedTarget || document.activeElement;
 
-       // Close menu popup when a user clicks outside the menu
 
-       if (!this.children().some(element => {
 
-         return element.el() === relatedTarget;
 
-       })) {
 
-         const btn = this.menuButton_;
 
-         if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
 
-           btn.unpressButton();
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Called when a `MenuItem` gets clicked or tapped.
 
-      *
 
-      * @param {Event} event
 
-      *        The `click` or `tap` event that caused this function to be called.
 
-      *
 
-      * @listens click,tap
 
-      */
 
-     handleTapClick(event) {
 
-       // Unpress the associated MenuButton, and move focus back to it
 
-       if (this.menuButton_) {
 
-         this.menuButton_.unpressButton();
 
-         const childComponents = this.children();
 
-         if (!Array.isArray(childComponents)) {
 
-           return;
 
-         }
 
-         const foundComponent = childComponents.filter(component => component.el() === event.target)[0];
 
-         if (!foundComponent) {
 
-           return;
 
-         }
 
-         // don't focus menu button if item is a caption settings item
 
-         // because focus will move elsewhere
 
-         if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
 
-           this.menuButton_.focus();
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Handle a `keydown` event on this menu. This listener is added in the constructor.
 
-      *
 
-      * @param {Event} event
 
-      *        A `keydown` event that happened on the menu.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       // Left and Down Arrows
 
-       if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.stepForward();
 
-         // Up and Right Arrows
 
-       } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         this.stepBack();
 
-       }
 
-     }
 
-     /**
 
-      * Move to next (lower) menu item for keyboard users.
 
-      */
 
-     stepForward() {
 
-       let stepChild = 0;
 
-       if (this.focusedChild_ !== undefined) {
 
-         stepChild = this.focusedChild_ + 1;
 
-       }
 
-       this.focus(stepChild);
 
-     }
 
-     /**
 
-      * Move to previous (higher) menu item for keyboard users.
 
-      */
 
-     stepBack() {
 
-       let stepChild = 0;
 
-       if (this.focusedChild_ !== undefined) {
 
-         stepChild = this.focusedChild_ - 1;
 
-       }
 
-       this.focus(stepChild);
 
-     }
 
-     /**
 
-      * Set focus on a {@link MenuItem} in the `Menu`.
 
-      *
 
-      * @param {Object|string} [item=0]
 
-      *        Index of child item set focus on.
 
-      */
 
-     focus(item = 0) {
 
-       const children = this.children().slice();
 
-       const haveTitle = children.length && children[0].hasClass('vjs-menu-title');
 
-       if (haveTitle) {
 
-         children.shift();
 
-       }
 
-       if (children.length > 0) {
 
-         if (item < 0) {
 
-           item = 0;
 
-         } else if (item >= children.length) {
 
-           item = children.length - 1;
 
-         }
 
-         this.focusedChild_ = item;
 
-         children[item].el_.focus();
 
-       }
 
-     }
 
-   }
 
-   Component$1.registerComponent('Menu', Menu);
 
-   /**
 
-    * @file menu-button.js
 
-    */
 
-   /**
 
-    * A `MenuButton` class for any popup {@link Menu}.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class MenuButton extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options = {}) {
 
-       super(player, options);
 
-       this.menuButton_ = new Button(player, options);
 
-       this.menuButton_.controlText(this.controlText_);
 
-       this.menuButton_.el_.setAttribute('aria-haspopup', 'true');
 
-       // Add buildCSSClass values to the button, not the wrapper
 
-       const buttonClass = Button.prototype.buildCSSClass();
 
-       this.menuButton_.el_.className = this.buildCSSClass() + ' ' + buttonClass;
 
-       this.menuButton_.removeClass('vjs-control');
 
-       this.addChild(this.menuButton_);
 
-       this.update();
 
-       this.enabled_ = true;
 
-       const handleClick = e => this.handleClick(e);
 
-       this.handleMenuKeyUp_ = e => this.handleMenuKeyUp(e);
 
-       this.on(this.menuButton_, 'tap', handleClick);
 
-       this.on(this.menuButton_, 'click', handleClick);
 
-       this.on(this.menuButton_, 'keydown', e => this.handleKeyDown(e));
 
-       this.on(this.menuButton_, 'mouseenter', () => {
 
-         this.addClass('vjs-hover');
 
-         this.menu.show();
 
-         on(document, 'keyup', this.handleMenuKeyUp_);
 
-       });
 
-       this.on('mouseleave', e => this.handleMouseLeave(e));
 
-       this.on('keydown', e => this.handleSubmenuKeyDown(e));
 
-     }
 
-     /**
 
-      * Update the menu based on the current state of its items.
 
-      */
 
-     update() {
 
-       const menu = this.createMenu();
 
-       if (this.menu) {
 
-         this.menu.dispose();
 
-         this.removeChild(this.menu);
 
-       }
 
-       this.menu = menu;
 
-       this.addChild(menu);
 
-       /**
 
-        * Track the state of the menu button
 
-        *
 
-        * @type {Boolean}
 
-        * @private
 
-        */
 
-       this.buttonPressed_ = false;
 
-       this.menuButton_.el_.setAttribute('aria-expanded', 'false');
 
-       if (this.items && this.items.length <= this.hideThreshold_) {
 
-         this.hide();
 
-         this.menu.contentEl_.removeAttribute('role');
 
-       } else {
 
-         this.show();
 
-         this.menu.contentEl_.setAttribute('role', 'menu');
 
-       }
 
-     }
 
-     /**
 
-      * Create the menu and add all items to it.
 
-      *
 
-      * @return {Menu}
 
-      *         The constructed menu
 
-      */
 
-     createMenu() {
 
-       const menu = new Menu(this.player_, {
 
-         menuButton: this
 
-       });
 
-       /**
 
-        * Hide the menu if the number of items is less than or equal to this threshold. This defaults
 
-        * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
 
-        * it here because every time we run `createMenu` we need to reset the value.
 
-        *
 
-        * @protected
 
-        * @type {Number}
 
-        */
 
-       this.hideThreshold_ = 0;
 
-       // Add a title list item to the top
 
-       if (this.options_.title) {
 
-         const titleEl = createEl('li', {
 
-           className: 'vjs-menu-title',
 
-           textContent: toTitleCase$1(this.options_.title),
 
-           tabIndex: -1
 
-         });
 
-         const titleComponent = new Component$1(this.player_, {
 
-           el: titleEl
 
-         });
 
-         menu.addItem(titleComponent);
 
-       }
 
-       this.items = this.createItems();
 
-       if (this.items) {
 
-         // Add menu items to the menu
 
-         for (let i = 0; i < this.items.length; i++) {
 
-           menu.addItem(this.items[i]);
 
-         }
 
-       }
 
-       return menu;
 
-     }
 
-     /**
 
-      * Create the list of menu items. Specific to each subclass.
 
-      *
 
-      * @abstract
 
-      */
 
-     createItems() {}
 
-     /**
 
-      * Create the `MenuButtons`s DOM element.
 
-      *
 
-      * @return {Element}
 
-      *         The element that gets created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: this.buildWrapperCSSClass()
 
-       }, {});
 
-     }
 
-     /**
 
-      * Allow sub components to stack CSS class names for the wrapper element
 
-      *
 
-      * @return {string}
 
-      *         The constructed wrapper DOM `className`
 
-      */
 
-     buildWrapperCSSClass() {
 
-       let menuButtonClass = 'vjs-menu-button';
 
-       // If the inline option is passed, we want to use different styles altogether.
 
-       if (this.options_.inline === true) {
 
-         menuButtonClass += '-inline';
 
-       } else {
 
-         menuButtonClass += '-popup';
 
-       }
 
-       // TODO: Fix the CSS so that this isn't necessary
 
-       const buttonClass = Button.prototype.buildCSSClass();
 
-       return `vjs-menu-button ${menuButtonClass} ${buttonClass} ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       let menuButtonClass = 'vjs-menu-button';
 
-       // If the inline option is passed, we want to use different styles altogether.
 
-       if (this.options_.inline === true) {
 
-         menuButtonClass += '-inline';
 
-       } else {
 
-         menuButtonClass += '-popup';
 
-       }
 
-       return `vjs-menu-button ${menuButtonClass} ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * Get or set the localized control text that will be used for accessibility.
 
-      *
 
-      * > NOTE: This will come from the internal `menuButton_` element.
 
-      *
 
-      * @param {string} [text]
 
-      *        Control text for element.
 
-      *
 
-      * @param {Element} [el=this.menuButton_.el()]
 
-      *        Element to set the title on.
 
-      *
 
-      * @return {string}
 
-      *         - The control text when getting
 
-      */
 
-     controlText(text, el = this.menuButton_.el()) {
 
-       return this.menuButton_.controlText(text, el);
 
-     }
 
-     /**
 
-      * Dispose of the `menu-button` and all child components.
 
-      */
 
-     dispose() {
 
-       this.handleMouseLeave();
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Handle a click on a `MenuButton`.
 
-      * See {@link ClickableComponent#handleClick} for instances where this is called.
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       if (this.buttonPressed_) {
 
-         this.unpressButton();
 
-       } else {
 
-         this.pressButton();
 
-       }
 
-     }
 
-     /**
 
-      * Handle `mouseleave` for `MenuButton`.
 
-      *
 
-      * @param {Event} event
 
-      *        The `mouseleave` event that caused this function to be called.
 
-      *
 
-      * @listens mouseleave
 
-      */
 
-     handleMouseLeave(event) {
 
-       this.removeClass('vjs-hover');
 
-       off(document, 'keyup', this.handleMenuKeyUp_);
 
-     }
 
-     /**
 
-      * Set the focus to the actual button, not to this element
 
-      */
 
-     focus() {
 
-       this.menuButton_.focus();
 
-     }
 
-     /**
 
-      * Remove the focus from the actual button, not this element
 
-      */
 
-     blur() {
 
-       this.menuButton_.blur();
 
-     }
 
-     /**
 
-      * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
 
-      * {@link ClickableComponent#handleKeyDown} for instances where this is called.
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown` event that caused this function to be called.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       // Escape or Tab unpress the 'button'
 
-       if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
 
-         if (this.buttonPressed_) {
 
-           this.unpressButton();
 
-         }
 
-         // Don't preventDefault for Tab key - we still want to lose focus
 
-         if (!keycode.isEventKey(event, 'Tab')) {
 
-           event.preventDefault();
 
-           // Set focus back to the menu button's button
 
-           this.menuButton_.focus();
 
-         }
 
-         // Up Arrow or Down Arrow also 'press' the button to open the menu
 
-       } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
 
-         if (!this.buttonPressed_) {
 
-           event.preventDefault();
 
-           this.pressButton();
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Handle a `keyup` event on a `MenuButton`. The listener for this is added in
 
-      * the constructor.
 
-      *
 
-      * @param {Event} event
 
-      *        Key press event
 
-      *
 
-      * @listens keyup
 
-      */
 
-     handleMenuKeyUp(event) {
 
-       // Escape hides popup menu
 
-       if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
 
-         this.removeClass('vjs-hover');
 
-       }
 
-     }
 
-     /**
 
-      * This method name now delegates to `handleSubmenuKeyDown`. This means
 
-      * anyone calling `handleSubmenuKeyPress` will not see their method calls
 
-      * stop working.
 
-      *
 
-      * @param {Event} event
 
-      *        The event that caused this function to be called.
 
-      */
 
-     handleSubmenuKeyPress(event) {
 
-       this.handleSubmenuKeyDown(event);
 
-     }
 
-     /**
 
-      * Handle a `keydown` event on a sub-menu. The listener for this is added in
 
-      * the constructor.
 
-      *
 
-      * @param {Event} event
 
-      *        Key press event
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleSubmenuKeyDown(event) {
 
-       // Escape or Tab unpress the 'button'
 
-       if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
 
-         if (this.buttonPressed_) {
 
-           this.unpressButton();
 
-         }
 
-         // Don't preventDefault for Tab key - we still want to lose focus
 
-         if (!keycode.isEventKey(event, 'Tab')) {
 
-           event.preventDefault();
 
-           // Set focus back to the menu button's button
 
-           this.menuButton_.focus();
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Put the current `MenuButton` into a pressed state.
 
-      */
 
-     pressButton() {
 
-       if (this.enabled_) {
 
-         this.buttonPressed_ = true;
 
-         this.menu.show();
 
-         this.menu.lockShowing();
 
-         this.menuButton_.el_.setAttribute('aria-expanded', 'true');
 
-         // set the focus into the submenu, except on iOS where it is resulting in
 
-         // undesired scrolling behavior when the player is in an iframe
 
-         if (IS_IOS && isInFrame()) {
 
-           // Return early so that the menu isn't focused
 
-           return;
 
-         }
 
-         this.menu.focus();
 
-       }
 
-     }
 
-     /**
 
-      * Take the current `MenuButton` out of a pressed state.
 
-      */
 
-     unpressButton() {
 
-       if (this.enabled_) {
 
-         this.buttonPressed_ = false;
 
-         this.menu.unlockShowing();
 
-         this.menu.hide();
 
-         this.menuButton_.el_.setAttribute('aria-expanded', 'false');
 
-       }
 
-     }
 
-     /**
 
-      * Disable the `MenuButton`. Don't allow it to be clicked.
 
-      */
 
-     disable() {
 
-       this.unpressButton();
 
-       this.enabled_ = false;
 
-       this.addClass('vjs-disabled');
 
-       this.menuButton_.disable();
 
-     }
 
-     /**
 
-      * Enable the `MenuButton`. Allow it to be clicked.
 
-      */
 
-     enable() {
 
-       this.enabled_ = true;
 
-       this.removeClass('vjs-disabled');
 
-       this.menuButton_.enable();
 
-     }
 
-   }
 
-   Component$1.registerComponent('MenuButton', MenuButton);
 
-   /**
 
-    * @file track-button.js
 
-    */
 
-   /**
 
-    * The base class for buttons that toggle specific  track types (e.g. subtitles).
 
-    *
 
-    * @extends MenuButton
 
-    */
 
-   class TrackButton extends MenuButton {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       const tracks = options.tracks;
 
-       super(player, options);
 
-       if (this.items.length <= 1) {
 
-         this.hide();
 
-       }
 
-       if (!tracks) {
 
-         return;
 
-       }
 
-       const updateHandler = bind_(this, this.update);
 
-       tracks.addEventListener('removetrack', updateHandler);
 
-       tracks.addEventListener('addtrack', updateHandler);
 
-       tracks.addEventListener('labelchange', updateHandler);
 
-       this.player_.on('ready', updateHandler);
 
-       this.player_.on('dispose', function () {
 
-         tracks.removeEventListener('removetrack', updateHandler);
 
-         tracks.removeEventListener('addtrack', updateHandler);
 
-         tracks.removeEventListener('labelchange', updateHandler);
 
-       });
 
-     }
 
-   }
 
-   Component$1.registerComponent('TrackButton', TrackButton);
 
-   /**
 
-    * @file menu-keys.js
 
-    */
 
-   /**
 
-     * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
 
-     * Note that 'Enter' and 'Space' are not included here (otherwise they would
 
-     * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
 
-    *
 
-     * @typedef MenuKeys
 
-     * @array
 
-     */
 
-   const MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
 
-   /**
 
-    * @file menu-item.js
 
-    */
 
-   /**
 
-    * The component for a menu item. `<li>`
 
-    *
 
-    * @extends ClickableComponent
 
-    */
 
-   class MenuItem extends ClickableComponent {
 
-     /**
 
-      * Creates an instance of the this class.
 
-      *
 
-      * @param { import('../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        The key/value store of player options.
 
-      *
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.selectable = options.selectable;
 
-       this.isSelected_ = options.selected || false;
 
-       this.multiSelectable = options.multiSelectable;
 
-       this.selected(this.isSelected_);
 
-       if (this.selectable) {
 
-         if (this.multiSelectable) {
 
-           this.el_.setAttribute('role', 'menuitemcheckbox');
 
-         } else {
 
-           this.el_.setAttribute('role', 'menuitemradio');
 
-         }
 
-       } else {
 
-         this.el_.setAttribute('role', 'menuitem');
 
-       }
 
-     }
 
-     /**
 
-      * Create the `MenuItem's DOM element
 
-      *
 
-      * @param {string} [type=li]
 
-      *        Element's node type, not actually used, always set to `li`.
 
-      *
 
-      * @param {Object} [props={}]
 
-      *        An object of properties that should be set on the element
 
-      *
 
-      * @param {Object} [attrs={}]
 
-      *        An object of attributes that should be set on the element
 
-      *
 
-      * @return {Element}
 
-      *         The element that gets created.
 
-      */
 
-     createEl(type, props, attrs) {
 
-       // The control is textual, not just an icon
 
-       this.nonIconControl = true;
 
-       const el = super.createEl('li', Object.assign({
 
-         className: 'vjs-menu-item',
 
-         tabIndex: -1
 
-       }, props), attrs);
 
-       // swap icon with menu item text.
 
-       el.replaceChild(createEl('span', {
 
-         className: 'vjs-menu-item-text',
 
-         textContent: this.localize(this.options_.label)
 
-       }), el.querySelector('.vjs-icon-placeholder'));
 
-       return el;
 
-     }
 
-     /**
 
-      * Ignore keys which are used by the menu, but pass any other ones up. See
 
-      * {@link ClickableComponent#handleKeyDown} for instances where this is called.
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown` event that caused this function to be called.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       if (!MenuKeys.some(key => keycode.isEventKey(event, key))) {
 
-         // Pass keydown handling up for unused keys
 
-         super.handleKeyDown(event);
 
-       }
 
-     }
 
-     /**
 
-      * Any click on a `MenuItem` puts it into the selected state.
 
-      * See {@link ClickableComponent#handleClick} for instances where this is called.
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       this.selected(true);
 
-     }
 
-     /**
 
-      * Set the state for this menu item as selected or not.
 
-      *
 
-      * @param {boolean} selected
 
-      *        if the menu item is selected or not
 
-      */
 
-     selected(selected) {
 
-       if (this.selectable) {
 
-         if (selected) {
 
-           this.addClass('vjs-selected');
 
-           this.el_.setAttribute('aria-checked', 'true');
 
-           // aria-checked isn't fully supported by browsers/screen readers,
 
-           // so indicate selected state to screen reader in the control text.
 
-           this.controlText(', selected');
 
-           this.isSelected_ = true;
 
-         } else {
 
-           this.removeClass('vjs-selected');
 
-           this.el_.setAttribute('aria-checked', 'false');
 
-           // Indicate un-selected state to screen reader
 
-           this.controlText('');
 
-           this.isSelected_ = false;
 
-         }
 
-       }
 
-     }
 
-   }
 
-   Component$1.registerComponent('MenuItem', MenuItem);
 
-   /**
 
-    * @file text-track-menu-item.js
 
-    */
 
-   /**
 
-    * The specific menu item type for selecting a language within a text track kind
 
-    *
 
-    * @extends MenuItem
 
-    */
 
-   class TextTrackMenuItem extends MenuItem {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       const track = options.track;
 
-       const tracks = player.textTracks();
 
-       // Modify options for parent MenuItem class's init.
 
-       options.label = track.label || track.language || 'Unknown';
 
-       options.selected = track.mode === 'showing';
 
-       super(player, options);
 
-       this.track = track;
 
-       // Determine the relevant kind(s) of tracks for this component and filter
 
-       // out empty kinds.
 
-       this.kinds = (options.kinds || [options.kind || this.track.kind]).filter(Boolean);
 
-       const changeHandler = (...args) => {
 
-         this.handleTracksChange.apply(this, args);
 
-       };
 
-       const selectedLanguageChangeHandler = (...args) => {
 
-         this.handleSelectedLanguageChange.apply(this, args);
 
-       };
 
-       player.on(['loadstart', 'texttrackchange'], changeHandler);
 
-       tracks.addEventListener('change', changeHandler);
 
-       tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
 
-       this.on('dispose', function () {
 
-         player.off(['loadstart', 'texttrackchange'], changeHandler);
 
-         tracks.removeEventListener('change', changeHandler);
 
-         tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
 
-       });
 
-       // iOS7 doesn't dispatch change events to TextTrackLists when an
 
-       // associated track's mode changes. Without something like
 
-       // Object.observe() (also not present on iOS7), it's not
 
-       // possible to detect changes to the mode attribute and polyfill
 
-       // the change event. As a poor substitute, we manually dispatch
 
-       // change events whenever the controls modify the mode.
 
-       if (tracks.onchange === undefined) {
 
-         let event;
 
-         this.on(['tap', 'click'], function () {
 
-           if (typeof window.Event !== 'object') {
 
-             // Android 2.3 throws an Illegal Constructor error for window.Event
 
-             try {
 
-               event = new window.Event('change');
 
-             } catch (err) {
 
-               // continue regardless of error
 
-             }
 
-           }
 
-           if (!event) {
 
-             event = document.createEvent('Event');
 
-             event.initEvent('change', true, true);
 
-           }
 
-           tracks.dispatchEvent(event);
 
-         });
 
-       }
 
-       // set the default state based on current tracks
 
-       this.handleTracksChange();
 
-     }
 
-     /**
 
-      * This gets called when an `TextTrackMenuItem` is "clicked". See
 
-      * {@link ClickableComponent} for more detailed information on what a click can be.
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       const referenceTrack = this.track;
 
-       const tracks = this.player_.textTracks();
 
-       super.handleClick(event);
 
-       if (!tracks) {
 
-         return;
 
-       }
 
-       for (let i = 0; i < tracks.length; i++) {
 
-         const track = tracks[i];
 
-         // If the track from the text tracks list is not of the right kind,
 
-         // skip it. We do not want to affect tracks of incompatible kind(s).
 
-         if (this.kinds.indexOf(track.kind) === -1) {
 
-           continue;
 
-         }
 
-         // If this text track is the component's track and it is not showing,
 
-         // set it to showing.
 
-         if (track === referenceTrack) {
 
-           if (track.mode !== 'showing') {
 
-             track.mode = 'showing';
 
-           }
 
-           // If this text track is not the component's track and it is not
 
-           // disabled, set it to disabled.
 
-         } else if (track.mode !== 'disabled') {
 
-           track.mode = 'disabled';
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Handle text track list change
 
-      *
 
-      * @param {Event} event
 
-      *        The `change` event that caused this function to be called.
 
-      *
 
-      * @listens TextTrackList#change
 
-      */
 
-     handleTracksChange(event) {
 
-       const shouldBeSelected = this.track.mode === 'showing';
 
-       // Prevent redundant selected() calls because they may cause
 
-       // screen readers to read the appended control text unnecessarily
 
-       if (shouldBeSelected !== this.isSelected_) {
 
-         this.selected(shouldBeSelected);
 
-       }
 
-     }
 
-     handleSelectedLanguageChange(event) {
 
-       if (this.track.mode === 'showing') {
 
-         const selectedLanguage = this.player_.cache_.selectedLanguage;
 
-         // Don't replace the kind of track across the same language
 
-         if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
 
-           return;
 
-         }
 
-         this.player_.cache_.selectedLanguage = {
 
-           enabled: true,
 
-           language: this.track.language,
 
-           kind: this.track.kind
 
-         };
 
-       }
 
-     }
 
-     dispose() {
 
-       // remove reference to track object on dispose
 
-       this.track = null;
 
-       super.dispose();
 
-     }
 
-   }
 
-   Component$1.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
 
-   /**
 
-    * @file off-text-track-menu-item.js
 
-    */
 
-   /**
 
-    * A special menu item for turning of a specific type of text track
 
-    *
 
-    * @extends TextTrackMenuItem
 
-    */
 
-   class OffTextTrackMenuItem extends TextTrackMenuItem {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       // Create pseudo track info
 
-       // Requires options['kind']
 
-       options.track = {
 
-         player,
 
-         // it is no longer necessary to store `kind` or `kinds` on the track itself
 
-         // since they are now stored in the `kinds` property of all instances of
 
-         // TextTrackMenuItem, but this will remain for backwards compatibility
 
-         kind: options.kind,
 
-         kinds: options.kinds,
 
-         default: false,
 
-         mode: 'disabled'
 
-       };
 
-       if (!options.kinds) {
 
-         options.kinds = [options.kind];
 
-       }
 
-       if (options.label) {
 
-         options.track.label = options.label;
 
-       } else {
 
-         options.track.label = options.kinds.join(' and ') + ' off';
 
-       }
 
-       // MenuItem is selectable
 
-       options.selectable = true;
 
-       // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
 
-       options.multiSelectable = false;
 
-       super(player, options);
 
-     }
 
-     /**
 
-      * Handle text track change
 
-      *
 
-      * @param {Event} event
 
-      *        The event that caused this function to run
 
-      */
 
-     handleTracksChange(event) {
 
-       const tracks = this.player().textTracks();
 
-       let shouldBeSelected = true;
 
-       for (let i = 0, l = tracks.length; i < l; i++) {
 
-         const track = tracks[i];
 
-         if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
 
-           shouldBeSelected = false;
 
-           break;
 
-         }
 
-       }
 
-       // Prevent redundant selected() calls because they may cause
 
-       // screen readers to read the appended control text unnecessarily
 
-       if (shouldBeSelected !== this.isSelected_) {
 
-         this.selected(shouldBeSelected);
 
-       }
 
-     }
 
-     handleSelectedLanguageChange(event) {
 
-       const tracks = this.player().textTracks();
 
-       let allHidden = true;
 
-       for (let i = 0, l = tracks.length; i < l; i++) {
 
-         const track = tracks[i];
 
-         if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
 
-           allHidden = false;
 
-           break;
 
-         }
 
-       }
 
-       if (allHidden) {
 
-         this.player_.cache_.selectedLanguage = {
 
-           enabled: false
 
-         };
 
-       }
 
-     }
 
-     /**
 
-      * Update control text and label on languagechange
 
-      */
 
-     handleLanguagechange() {
 
-       this.$('.vjs-menu-item-text').textContent = this.player_.localize(this.options_.label);
 
-       super.handleLanguagechange();
 
-     }
 
-   }
 
-   Component$1.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
 
-   /**
 
-    * @file text-track-button.js
 
-    */
 
-   /**
 
-    * The base class for buttons that toggle specific text track types (e.g. subtitles)
 
-    *
 
-    * @extends MenuButton
 
-    */
 
-   class TextTrackButton extends TrackButton {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options = {}) {
 
-       options.tracks = player.textTracks();
 
-       super(player, options);
 
-     }
 
-     /**
 
-      * Create a menu item for each text track
 
-      *
 
-      * @param {TextTrackMenuItem[]} [items=[]]
 
-      *        Existing array of items to use during creation
 
-      *
 
-      * @return {TextTrackMenuItem[]}
 
-      *         Array of menu items that were created
 
-      */
 
-     createItems(items = [], TrackMenuItem = TextTrackMenuItem) {
 
-       // Label is an override for the [track] off label
 
-       // USed to localise captions/subtitles
 
-       let label;
 
-       if (this.label_) {
 
-         label = `${this.label_} off`;
 
-       }
 
-       // Add an OFF menu item to turn all tracks off
 
-       items.push(new OffTextTrackMenuItem(this.player_, {
 
-         kinds: this.kinds_,
 
-         kind: this.kind_,
 
-         label
 
-       }));
 
-       this.hideThreshold_ += 1;
 
-       const tracks = this.player_.textTracks();
 
-       if (!Array.isArray(this.kinds_)) {
 
-         this.kinds_ = [this.kind_];
 
-       }
 
-       for (let i = 0; i < tracks.length; i++) {
 
-         const track = tracks[i];
 
-         // only add tracks that are of an appropriate kind and have a label
 
-         if (this.kinds_.indexOf(track.kind) > -1) {
 
-           const item = new TrackMenuItem(this.player_, {
 
-             track,
 
-             kinds: this.kinds_,
 
-             kind: this.kind_,
 
-             // MenuItem is selectable
 
-             selectable: true,
 
-             // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
 
-             multiSelectable: false
 
-           });
 
-           item.addClass(`vjs-${track.kind}-menu-item`);
 
-           items.push(item);
 
-         }
 
-       }
 
-       return items;
 
-     }
 
-   }
 
-   Component$1.registerComponent('TextTrackButton', TextTrackButton);
 
-   /**
 
-    * @file chapters-track-menu-item.js
 
-    */
 
-   /**
 
-    * The chapter track menu item
 
-    *
 
-    * @extends MenuItem
 
-    */
 
-   class ChaptersTrackMenuItem extends MenuItem {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       const track = options.track;
 
-       const cue = options.cue;
 
-       const currentTime = player.currentTime();
 
-       // Modify options for parent MenuItem class's init.
 
-       options.selectable = true;
 
-       options.multiSelectable = false;
 
-       options.label = cue.text;
 
-       options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
 
-       super(player, options);
 
-       this.track = track;
 
-       this.cue = cue;
 
-     }
 
-     /**
 
-      * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
 
-      * {@link ClickableComponent} for more detailed information on what a click can be.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       super.handleClick();
 
-       this.player_.currentTime(this.cue.startTime);
 
-     }
 
-   }
 
-   Component$1.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
 
-   /**
 
-    * @file chapters-button.js
 
-    */
 
-   /**
 
-    * The button component for toggling and selecting chapters
 
-    * Chapters act much differently than other text tracks
 
-    * Cues are navigation vs. other tracks of alternative languages
 
-    *
 
-    * @extends TextTrackButton
 
-    */
 
-   class ChaptersButton extends TextTrackButton {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @param {Function} [ready]
 
-      *        The function to call when this function is ready.
 
-      */
 
-     constructor(player, options, ready) {
 
-       super(player, options, ready);
 
-       this.selectCurrentItem_ = () => {
 
-         this.items.forEach(item => {
 
-           item.selected(this.track_.activeCues[0] === item.cue);
 
-         });
 
-       };
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-chapters-button ${super.buildCSSClass()}`;
 
-     }
 
-     buildWrapperCSSClass() {
 
-       return `vjs-chapters-button ${super.buildWrapperCSSClass()}`;
 
-     }
 
-     /**
 
-      * Update the menu based on the current state of its items.
 
-      *
 
-      * @param {Event} [event]
 
-      *        An event that triggered this function to run.
 
-      *
 
-      * @listens TextTrackList#addtrack
 
-      * @listens TextTrackList#removetrack
 
-      * @listens TextTrackList#change
 
-      */
 
-     update(event) {
 
-       if (event && event.track && event.track.kind !== 'chapters') {
 
-         return;
 
-       }
 
-       const track = this.findChaptersTrack();
 
-       if (track !== this.track_) {
 
-         this.setTrack(track);
 
-         super.update();
 
-       } else if (!this.items || track && track.cues && track.cues.length !== this.items.length) {
 
-         // Update the menu initially or if the number of cues has changed since set
 
-         super.update();
 
-       }
 
-     }
 
-     /**
 
-      * Set the currently selected track for the chapters button.
 
-      *
 
-      * @param {TextTrack} track
 
-      *        The new track to select. Nothing will change if this is the currently selected
 
-      *        track.
 
-      */
 
-     setTrack(track) {
 
-       if (this.track_ === track) {
 
-         return;
 
-       }
 
-       if (!this.updateHandler_) {
 
-         this.updateHandler_ = this.update.bind(this);
 
-       }
 
-       // here this.track_ refers to the old track instance
 
-       if (this.track_) {
 
-         const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
 
-         if (remoteTextTrackEl) {
 
-           remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
 
-         }
 
-         this.track_.removeEventListener('cuechange', this.selectCurrentItem_);
 
-         this.track_ = null;
 
-       }
 
-       this.track_ = track;
 
-       // here this.track_ refers to the new track instance
 
-       if (this.track_) {
 
-         this.track_.mode = 'hidden';
 
-         const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
 
-         if (remoteTextTrackEl) {
 
-           remoteTextTrackEl.addEventListener('load', this.updateHandler_);
 
-         }
 
-         this.track_.addEventListener('cuechange', this.selectCurrentItem_);
 
-       }
 
-     }
 
-     /**
 
-      * Find the track object that is currently in use by this ChaptersButton
 
-      *
 
-      * @return {TextTrack|undefined}
 
-      *         The current track or undefined if none was found.
 
-      */
 
-     findChaptersTrack() {
 
-       const tracks = this.player_.textTracks() || [];
 
-       for (let i = tracks.length - 1; i >= 0; i--) {
 
-         // We will always choose the last track as our chaptersTrack
 
-         const track = tracks[i];
 
-         if (track.kind === this.kind_) {
 
-           return track;
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Get the caption for the ChaptersButton based on the track label. This will also
 
-      * use the current tracks localized kind as a fallback if a label does not exist.
 
-      *
 
-      * @return {string}
 
-      *         The tracks current label or the localized track kind.
 
-      */
 
-     getMenuCaption() {
 
-       if (this.track_ && this.track_.label) {
 
-         return this.track_.label;
 
-       }
 
-       return this.localize(toTitleCase$1(this.kind_));
 
-     }
 
-     /**
 
-      * Create menu from chapter track
 
-      *
 
-      * @return { import('../../menu/menu').default }
 
-      *         New menu for the chapter buttons
 
-      */
 
-     createMenu() {
 
-       this.options_.title = this.getMenuCaption();
 
-       return super.createMenu();
 
-     }
 
-     /**
 
-      * Create a menu item for each text track
 
-      *
 
-      * @return  { import('./text-track-menu-item').default[] }
 
-      *         Array of menu items
 
-      */
 
-     createItems() {
 
-       const items = [];
 
-       if (!this.track_) {
 
-         return items;
 
-       }
 
-       const cues = this.track_.cues;
 
-       if (!cues) {
 
-         return items;
 
-       }
 
-       for (let i = 0, l = cues.length; i < l; i++) {
 
-         const cue = cues[i];
 
-         const mi = new ChaptersTrackMenuItem(this.player_, {
 
-           track: this.track_,
 
-           cue
 
-         });
 
-         items.push(mi);
 
-       }
 
-       return items;
 
-     }
 
-   }
 
-   /**
 
-    * `kind` of TextTrack to look for to associate it with this menu.
 
-    *
 
-    * @type {string}
 
-    * @private
 
-    */
 
-   ChaptersButton.prototype.kind_ = 'chapters';
 
-   /**
 
-    * The text that should display over the `ChaptersButton`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   ChaptersButton.prototype.controlText_ = 'Chapters';
 
-   Component$1.registerComponent('ChaptersButton', ChaptersButton);
 
-   /**
 
-    * @file descriptions-button.js
 
-    */
 
-   /**
 
-    * The button component for toggling and selecting descriptions
 
-    *
 
-    * @extends TextTrackButton
 
-    */
 
-   class DescriptionsButton extends TextTrackButton {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @param {Function} [ready]
 
-      *        The function to call when this component is ready.
 
-      */
 
-     constructor(player, options, ready) {
 
-       super(player, options, ready);
 
-       const tracks = player.textTracks();
 
-       const changeHandler = bind_(this, this.handleTracksChange);
 
-       tracks.addEventListener('change', changeHandler);
 
-       this.on('dispose', function () {
 
-         tracks.removeEventListener('change', changeHandler);
 
-       });
 
-     }
 
-     /**
 
-      * Handle text track change
 
-      *
 
-      * @param {Event} event
 
-      *        The event that caused this function to run
 
-      *
 
-      * @listens TextTrackList#change
 
-      */
 
-     handleTracksChange(event) {
 
-       const tracks = this.player().textTracks();
 
-       let disabled = false;
 
-       // Check whether a track of a different kind is showing
 
-       for (let i = 0, l = tracks.length; i < l; i++) {
 
-         const track = tracks[i];
 
-         if (track.kind !== this.kind_ && track.mode === 'showing') {
 
-           disabled = true;
 
-           break;
 
-         }
 
-       }
 
-       // If another track is showing, disable this menu button
 
-       if (disabled) {
 
-         this.disable();
 
-       } else {
 
-         this.enable();
 
-       }
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-descriptions-button ${super.buildCSSClass()}`;
 
-     }
 
-     buildWrapperCSSClass() {
 
-       return `vjs-descriptions-button ${super.buildWrapperCSSClass()}`;
 
-     }
 
-   }
 
-   /**
 
-    * `kind` of TextTrack to look for to associate it with this menu.
 
-    *
 
-    * @type {string}
 
-    * @private
 
-    */
 
-   DescriptionsButton.prototype.kind_ = 'descriptions';
 
-   /**
 
-    * The text that should display over the `DescriptionsButton`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   DescriptionsButton.prototype.controlText_ = 'Descriptions';
 
-   Component$1.registerComponent('DescriptionsButton', DescriptionsButton);
 
-   /**
 
-    * @file subtitles-button.js
 
-    */
 
-   /**
 
-    * The button component for toggling and selecting subtitles
 
-    *
 
-    * @extends TextTrackButton
 
-    */
 
-   class SubtitlesButton extends TextTrackButton {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @param {Function} [ready]
 
-      *        The function to call when this component is ready.
 
-      */
 
-     constructor(player, options, ready) {
 
-       super(player, options, ready);
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-subtitles-button ${super.buildCSSClass()}`;
 
-     }
 
-     buildWrapperCSSClass() {
 
-       return `vjs-subtitles-button ${super.buildWrapperCSSClass()}`;
 
-     }
 
-   }
 
-   /**
 
-    * `kind` of TextTrack to look for to associate it with this menu.
 
-    *
 
-    * @type {string}
 
-    * @private
 
-    */
 
-   SubtitlesButton.prototype.kind_ = 'subtitles';
 
-   /**
 
-    * The text that should display over the `SubtitlesButton`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   SubtitlesButton.prototype.controlText_ = 'Subtitles';
 
-   Component$1.registerComponent('SubtitlesButton', SubtitlesButton);
 
-   /**
 
-    * @file caption-settings-menu-item.js
 
-    */
 
-   /**
 
-    * The menu item for caption track settings menu
 
-    *
 
-    * @extends TextTrackMenuItem
 
-    */
 
-   class CaptionSettingsMenuItem extends TextTrackMenuItem {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       options.track = {
 
-         player,
 
-         kind: options.kind,
 
-         label: options.kind + ' settings',
 
-         selectable: false,
 
-         default: false,
 
-         mode: 'disabled'
 
-       };
 
-       // CaptionSettingsMenuItem has no concept of 'selected'
 
-       options.selectable = false;
 
-       options.name = 'CaptionSettingsMenuItem';
 
-       super(player, options);
 
-       this.addClass('vjs-texttrack-settings');
 
-       this.controlText(', opens ' + options.kind + ' settings dialog');
 
-     }
 
-     /**
 
-      * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
 
-      * {@link ClickableComponent} for more detailed information on what a click can be.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       this.player().getChild('textTrackSettings').open();
 
-     }
 
-     /**
 
-      * Update control text and label on languagechange
 
-      */
 
-     handleLanguagechange() {
 
-       this.$('.vjs-menu-item-text').textContent = this.player_.localize(this.options_.kind + ' settings');
 
-       super.handleLanguagechange();
 
-     }
 
-   }
 
-   Component$1.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
 
-   /**
 
-    * @file captions-button.js
 
-    */
 
-   /**
 
-    * The button component for toggling and selecting captions
 
-    *
 
-    * @extends TextTrackButton
 
-    */
 
-   class CaptionsButton extends TextTrackButton {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @param {Function} [ready]
 
-      *        The function to call when this component is ready.
 
-      */
 
-     constructor(player, options, ready) {
 
-       super(player, options, ready);
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-captions-button ${super.buildCSSClass()}`;
 
-     }
 
-     buildWrapperCSSClass() {
 
-       return `vjs-captions-button ${super.buildWrapperCSSClass()}`;
 
-     }
 
-     /**
 
-      * Create caption menu items
 
-      *
 
-      * @return {CaptionSettingsMenuItem[]}
 
-      *         The array of current menu items.
 
-      */
 
-     createItems() {
 
-       const items = [];
 
-       if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
 
-         items.push(new CaptionSettingsMenuItem(this.player_, {
 
-           kind: this.kind_
 
-         }));
 
-         this.hideThreshold_ += 1;
 
-       }
 
-       return super.createItems(items);
 
-     }
 
-   }
 
-   /**
 
-    * `kind` of TextTrack to look for to associate it with this menu.
 
-    *
 
-    * @type {string}
 
-    * @private
 
-    */
 
-   CaptionsButton.prototype.kind_ = 'captions';
 
-   /**
 
-    * The text that should display over the `CaptionsButton`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   CaptionsButton.prototype.controlText_ = 'Captions';
 
-   Component$1.registerComponent('CaptionsButton', CaptionsButton);
 
-   /**
 
-    * @file subs-caps-menu-item.js
 
-    */
 
-   /**
 
-    * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
 
-    * in the SubsCapsMenu.
 
-    *
 
-    * @extends TextTrackMenuItem
 
-    */
 
-   class SubsCapsMenuItem extends TextTrackMenuItem {
 
-     createEl(type, props, attrs) {
 
-       const el = super.createEl(type, props, attrs);
 
-       const parentSpan = el.querySelector('.vjs-menu-item-text');
 
-       if (this.options_.track.kind === 'captions') {
 
-         parentSpan.appendChild(createEl('span', {
 
-           className: 'vjs-icon-placeholder'
 
-         }, {
 
-           'aria-hidden': true
 
-         }));
 
-         parentSpan.appendChild(createEl('span', {
 
-           className: 'vjs-control-text',
 
-           // space added as the text will visually flow with the
 
-           // label
 
-           textContent: ` ${this.localize('Captions')}`
 
-         }));
 
-       }
 
-       return el;
 
-     }
 
-   }
 
-   Component$1.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
 
-   /**
 
-    * @file sub-caps-button.js
 
-    */
 
-   /**
 
-    * The button component for toggling and selecting captions and/or subtitles
 
-    *
 
-    * @extends TextTrackButton
 
-    */
 
-   class SubsCapsButton extends TextTrackButton {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @param {Function} [ready]
 
-      *        The function to call when this component is ready.
 
-      */
 
-     constructor(player, options = {}) {
 
-       super(player, options);
 
-       // Although North America uses "captions" in most cases for
 
-       // "captions and subtitles" other locales use "subtitles"
 
-       this.label_ = 'subtitles';
 
-       if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(this.player_.language_) > -1) {
 
-         this.label_ = 'captions';
 
-       }
 
-       this.menuButton_.controlText(toTitleCase$1(this.label_));
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-subs-caps-button ${super.buildCSSClass()}`;
 
-     }
 
-     buildWrapperCSSClass() {
 
-       return `vjs-subs-caps-button ${super.buildWrapperCSSClass()}`;
 
-     }
 
-     /**
 
-      * Create caption/subtitles menu items
 
-      *
 
-      * @return {CaptionSettingsMenuItem[]}
 
-      *         The array of current menu items.
 
-      */
 
-     createItems() {
 
-       let items = [];
 
-       if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
 
-         items.push(new CaptionSettingsMenuItem(this.player_, {
 
-           kind: this.label_
 
-         }));
 
-         this.hideThreshold_ += 1;
 
-       }
 
-       items = super.createItems(items, SubsCapsMenuItem);
 
-       return items;
 
-     }
 
-   }
 
-   /**
 
-    * `kind`s of TextTrack to look for to associate it with this menu.
 
-    *
 
-    * @type {array}
 
-    * @private
 
-    */
 
-   SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
 
-   /**
 
-    * The text that should display over the `SubsCapsButton`s controls.
 
-    *
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   SubsCapsButton.prototype.controlText_ = 'Subtitles';
 
-   Component$1.registerComponent('SubsCapsButton', SubsCapsButton);
 
-   /**
 
-    * @file audio-track-menu-item.js
 
-    */
 
-   /**
 
-    * An {@link AudioTrack} {@link MenuItem}
 
-    *
 
-    * @extends MenuItem
 
-    */
 
-   class AudioTrackMenuItem extends MenuItem {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       const track = options.track;
 
-       const tracks = player.audioTracks();
 
-       // Modify options for parent MenuItem class's init.
 
-       options.label = track.label || track.language || 'Unknown';
 
-       options.selected = track.enabled;
 
-       super(player, options);
 
-       this.track = track;
 
-       this.addClass(`vjs-${track.kind}-menu-item`);
 
-       const changeHandler = (...args) => {
 
-         this.handleTracksChange.apply(this, args);
 
-       };
 
-       tracks.addEventListener('change', changeHandler);
 
-       this.on('dispose', () => {
 
-         tracks.removeEventListener('change', changeHandler);
 
-       });
 
-     }
 
-     createEl(type, props, attrs) {
 
-       const el = super.createEl(type, props, attrs);
 
-       const parentSpan = el.querySelector('.vjs-menu-item-text');
 
-       if (this.options_.track.kind === 'main-desc') {
 
-         parentSpan.appendChild(createEl('span', {
 
-           className: 'vjs-icon-placeholder'
 
-         }, {
 
-           'aria-hidden': true
 
-         }));
 
-         parentSpan.appendChild(createEl('span', {
 
-           className: 'vjs-control-text',
 
-           textContent: ' ' + this.localize('Descriptions')
 
-         }));
 
-       }
 
-       return el;
 
-     }
 
-     /**
 
-      * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
 
-      * for more detailed information on what a click can be.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       super.handleClick(event);
 
-       // the audio track list will automatically toggle other tracks
 
-       // off for us.
 
-       this.track.enabled = true;
 
-       // when native audio tracks are used, we want to make sure that other tracks are turned off
 
-       if (this.player_.tech_.featuresNativeAudioTracks) {
 
-         const tracks = this.player_.audioTracks();
 
-         for (let i = 0; i < tracks.length; i++) {
 
-           const track = tracks[i];
 
-           // skip the current track since we enabled it above
 
-           if (track === this.track) {
 
-             continue;
 
-           }
 
-           track.enabled = track === this.track;
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Handle any {@link AudioTrack} change.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The {@link AudioTrackList#change} event that caused this to run.
 
-      *
 
-      * @listens AudioTrackList#change
 
-      */
 
-     handleTracksChange(event) {
 
-       this.selected(this.track.enabled);
 
-     }
 
-   }
 
-   Component$1.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
 
-   /**
 
-    * @file audio-track-button.js
 
-    */
 
-   /**
 
-    * The base class for buttons that toggle specific {@link AudioTrack} types.
 
-    *
 
-    * @extends TrackButton
 
-    */
 
-   class AudioTrackButton extends TrackButton {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param {Player} player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options={}]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options = {}) {
 
-       options.tracks = player.audioTracks();
 
-       super(player, options);
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-audio-button ${super.buildCSSClass()}`;
 
-     }
 
-     buildWrapperCSSClass() {
 
-       return `vjs-audio-button ${super.buildWrapperCSSClass()}`;
 
-     }
 
-     /**
 
-      * Create a menu item for each audio track
 
-      *
 
-      * @param {AudioTrackMenuItem[]} [items=[]]
 
-      *        An array of existing menu items to use.
 
-      *
 
-      * @return {AudioTrackMenuItem[]}
 
-      *         An array of menu items
 
-      */
 
-     createItems(items = []) {
 
-       // if there's only one audio track, there no point in showing it
 
-       this.hideThreshold_ = 1;
 
-       const tracks = this.player_.audioTracks();
 
-       for (let i = 0; i < tracks.length; i++) {
 
-         const track = tracks[i];
 
-         items.push(new AudioTrackMenuItem(this.player_, {
 
-           track,
 
-           // MenuItem is selectable
 
-           selectable: true,
 
-           // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
 
-           multiSelectable: false
 
-         }));
 
-       }
 
-       return items;
 
-     }
 
-   }
 
-   /**
 
-    * The text that should display over the `AudioTrackButton`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   AudioTrackButton.prototype.controlText_ = 'Audio Track';
 
-   Component$1.registerComponent('AudioTrackButton', AudioTrackButton);
 
-   /**
 
-    * @file playback-rate-menu-item.js
 
-    */
 
-   /**
 
-    * The specific menu item type for selecting a playback rate.
 
-    *
 
-    * @extends MenuItem
 
-    */
 
-   class PlaybackRateMenuItem extends MenuItem {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       const label = options.rate;
 
-       const rate = parseFloat(label, 10);
 
-       // Modify options for parent MenuItem class's init.
 
-       options.label = label;
 
-       options.selected = rate === player.playbackRate();
 
-       options.selectable = true;
 
-       options.multiSelectable = false;
 
-       super(player, options);
 
-       this.label = label;
 
-       this.rate = rate;
 
-       this.on(player, 'ratechange', e => this.update(e));
 
-     }
 
-     /**
 
-      * This gets called when an `PlaybackRateMenuItem` is "clicked". See
 
-      * {@link ClickableComponent} for more detailed information on what a click can be.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `keydown`, `tap`, or `click` event that caused this function to be
 
-      *        called.
 
-      *
 
-      * @listens tap
 
-      * @listens click
 
-      */
 
-     handleClick(event) {
 
-       super.handleClick();
 
-       this.player().playbackRate(this.rate);
 
-     }
 
-     /**
 
-      * Update the PlaybackRateMenuItem when the playbackrate changes.
 
-      *
 
-      * @param {Event} [event]
 
-      *        The `ratechange` event that caused this function to run.
 
-      *
 
-      * @listens Player#ratechange
 
-      */
 
-     update(event) {
 
-       this.selected(this.player().playbackRate() === this.rate);
 
-     }
 
-   }
 
-   /**
 
-    * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @private
 
-    */
 
-   PlaybackRateMenuItem.prototype.contentElType = 'button';
 
-   Component$1.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
 
-   /**
 
-    * @file playback-rate-menu-button.js
 
-    */
 
-   /**
 
-    * The component for controlling the playback rate.
 
-    *
 
-    * @extends MenuButton
 
-    */
 
-   class PlaybackRateMenuButton extends MenuButton {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../../player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.menuButton_.el_.setAttribute('aria-describedby', this.labelElId_);
 
-       this.updateVisibility();
 
-       this.updateLabel();
 
-       this.on(player, 'loadstart', e => this.updateVisibility(e));
 
-       this.on(player, 'ratechange', e => this.updateLabel(e));
 
-       this.on(player, 'playbackrateschange', e => this.handlePlaybackRateschange(e));
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       const el = super.createEl();
 
-       this.labelElId_ = 'vjs-playback-rate-value-label-' + this.id_;
 
-       this.labelEl_ = createEl('div', {
 
-         className: 'vjs-playback-rate-value',
 
-         id: this.labelElId_,
 
-         textContent: '1x'
 
-       });
 
-       el.appendChild(this.labelEl_);
 
-       return el;
 
-     }
 
-     dispose() {
 
-       this.labelEl_ = null;
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-playback-rate ${super.buildCSSClass()}`;
 
-     }
 
-     buildWrapperCSSClass() {
 
-       return `vjs-playback-rate ${super.buildWrapperCSSClass()}`;
 
-     }
 
-     /**
 
-      * Create the list of menu items. Specific to each subclass.
 
-      *
 
-      */
 
-     createItems() {
 
-       const rates = this.playbackRates();
 
-       const items = [];
 
-       for (let i = rates.length - 1; i >= 0; i--) {
 
-         items.push(new PlaybackRateMenuItem(this.player(), {
 
-           rate: rates[i] + 'x'
 
-         }));
 
-       }
 
-       return items;
 
-     }
 
-     /**
 
-      * On playbackrateschange, update the menu to account for the new items.
 
-      *
 
-      * @listens Player#playbackrateschange
 
-      */
 
-     handlePlaybackRateschange(event) {
 
-       this.update();
 
-     }
 
-     /**
 
-      * Get possible playback rates
 
-      *
 
-      * @return {Array}
 
-      *         All possible playback rates
 
-      */
 
-     playbackRates() {
 
-       const player = this.player();
 
-       return player.playbackRates && player.playbackRates() || [];
 
-     }
 
-     /**
 
-      * Get whether playback rates is supported by the tech
 
-      * and an array of playback rates exists
 
-      *
 
-      * @return {boolean}
 
-      *         Whether changing playback rate is supported
 
-      */
 
-     playbackRateSupported() {
 
-       return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
 
-     }
 
-     /**
 
-      * Hide playback rate controls when they're no playback rate options to select
 
-      *
 
-      * @param {Event} [event]
 
-      *        The event that caused this function to run.
 
-      *
 
-      * @listens Player#loadstart
 
-      */
 
-     updateVisibility(event) {
 
-       if (this.playbackRateSupported()) {
 
-         this.removeClass('vjs-hidden');
 
-       } else {
 
-         this.addClass('vjs-hidden');
 
-       }
 
-     }
 
-     /**
 
-      * Update button label when rate changed
 
-      *
 
-      * @param {Event} [event]
 
-      *        The event that caused this function to run.
 
-      *
 
-      * @listens Player#ratechange
 
-      */
 
-     updateLabel(event) {
 
-       if (this.playbackRateSupported()) {
 
-         this.labelEl_.textContent = this.player().playbackRate() + 'x';
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * The text that should display over the `PlaybackRateMenuButton`s controls.
 
-    *
 
-    * Added for localization.
 
-    *
 
-    * @type {string}
 
-    * @protected
 
-    */
 
-   PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
 
-   Component$1.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
 
-   /**
 
-    * @file spacer.js
 
-    */
 
-   /**
 
-    * Just an empty spacer element that can be used as an append point for plugins, etc.
 
-    * Also can be used to create space between elements when necessary.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class Spacer extends Component$1 {
 
-     /**
 
-     * Builds the default DOM `className`.
 
-     *
 
-     * @return {string}
 
-     *         The DOM `className` for this object.
 
-     */
 
-     buildCSSClass() {
 
-       return `vjs-spacer ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl(tag = 'div', props = {}, attributes = {}) {
 
-       if (!props.className) {
 
-         props.className = this.buildCSSClass();
 
-       }
 
-       return super.createEl(tag, props, attributes);
 
-     }
 
-   }
 
-   Component$1.registerComponent('Spacer', Spacer);
 
-   /**
 
-    * @file custom-control-spacer.js
 
-    */
 
-   /**
 
-    * Spacer specifically meant to be used as an insertion point for new plugins, etc.
 
-    *
 
-    * @extends Spacer
 
-    */
 
-   class CustomControlSpacer extends Spacer {
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-custom-control-spacer ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: this.buildCSSClass(),
 
-         // No-flex/table-cell mode requires there be some content
 
-         // in the cell to fill the remaining space of the table.
 
-         textContent: '\u00a0'
 
-       });
 
-     }
 
-   }
 
-   Component$1.registerComponent('CustomControlSpacer', CustomControlSpacer);
 
-   /**
 
-    * @file control-bar.js
 
-    */
 
-   /**
 
-    * Container of main controls.
 
-    *
 
-    * @extends Component
 
-    */
 
-   class ControlBar extends Component$1 {
 
-     /**
 
-      * Create the `Component`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       return super.createEl('div', {
 
-         className: 'vjs-control-bar',
 
-         dir: 'ltr'
 
-       });
 
-     }
 
-   }
 
-   /**
 
-    * Default options for `ControlBar`
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   ControlBar.prototype.options_ = {
 
-     children: ['playToggle', 'skipBackward', 'skipForward', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
 
-   };
 
-   if ('exitPictureInPicture' in document) {
 
-     ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle');
 
-   }
 
-   Component$1.registerComponent('ControlBar', ControlBar);
 
-   /**
 
-    * @file error-display.js
 
-    */
 
-   /**
 
-    * A display that indicates an error has occurred. This means that the video
 
-    * is unplayable.
 
-    *
 
-    * @extends ModalDialog
 
-    */
 
-   class ErrorDisplay extends ModalDialog {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param  { import('./player').default } player
 
-      *         The `Player` that this class should be attached to.
 
-      *
 
-      * @param  {Object} [options]
 
-      *         The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.on(player, 'error', e => this.open(e));
 
-     }
 
-     /**
 
-      * Builds the default DOM `className`.
 
-      *
 
-      * @return {string}
 
-      *         The DOM `className` for this object.
 
-      *
 
-      * @deprecated Since version 5.
 
-      */
 
-     buildCSSClass() {
 
-       return `vjs-error-display ${super.buildCSSClass()}`;
 
-     }
 
-     /**
 
-      * Gets the localized error message based on the `Player`s error.
 
-      *
 
-      * @return {string}
 
-      *         The `Player`s error message localized or an empty string.
 
-      */
 
-     content() {
 
-       const error = this.player().error();
 
-       return error ? this.localize(error.message) : '';
 
-     }
 
-   }
 
-   /**
 
-    * The default options for an `ErrorDisplay`.
 
-    *
 
-    * @private
 
-    */
 
-   ErrorDisplay.prototype.options_ = Object.assign({}, ModalDialog.prototype.options_, {
 
-     pauseOnOpen: false,
 
-     fillAlways: true,
 
-     temporary: false,
 
-     uncloseable: true
 
-   });
 
-   Component$1.registerComponent('ErrorDisplay', ErrorDisplay);
 
-   /**
 
-    * @file text-track-settings.js
 
-    */
 
-   const LOCAL_STORAGE_KEY$1 = 'vjs-text-track-settings';
 
-   const COLOR_BLACK = ['#000', 'Black'];
 
-   const COLOR_BLUE = ['#00F', 'Blue'];
 
-   const COLOR_CYAN = ['#0FF', 'Cyan'];
 
-   const COLOR_GREEN = ['#0F0', 'Green'];
 
-   const COLOR_MAGENTA = ['#F0F', 'Magenta'];
 
-   const COLOR_RED = ['#F00', 'Red'];
 
-   const COLOR_WHITE = ['#FFF', 'White'];
 
-   const COLOR_YELLOW = ['#FF0', 'Yellow'];
 
-   const OPACITY_OPAQUE = ['1', 'Opaque'];
 
-   const OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
 
-   const OPACITY_TRANS = ['0', 'Transparent'];
 
-   // Configuration for the various <select> elements in the DOM of this component.
 
-   //
 
-   // Possible keys include:
 
-   //
 
-   // `default`:
 
-   //   The default option index. Only needs to be provided if not zero.
 
-   // `parser`:
 
-   //   A function which is used to parse the value from the selected option in
 
-   //   a customized way.
 
-   // `selector`:
 
-   //   The selector used to find the associated <select> element.
 
-   const selectConfigs = {
 
-     backgroundColor: {
 
-       selector: '.vjs-bg-color > select',
 
-       id: 'captions-background-color-%s',
 
-       label: 'Color',
 
-       options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
 
-     },
 
-     backgroundOpacity: {
 
-       selector: '.vjs-bg-opacity > select',
 
-       id: 'captions-background-opacity-%s',
 
-       label: 'Opacity',
 
-       options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
 
-     },
 
-     color: {
 
-       selector: '.vjs-text-color > select',
 
-       id: 'captions-foreground-color-%s',
 
-       label: 'Color',
 
-       options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
 
-     },
 
-     edgeStyle: {
 
-       selector: '.vjs-edge-style > select',
 
-       id: '%s',
 
-       label: 'Text Edge Style',
 
-       options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
 
-     },
 
-     fontFamily: {
 
-       selector: '.vjs-font-family > select',
 
-       id: 'captions-font-family-%s',
 
-       label: 'Font Family',
 
-       options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
 
-     },
 
-     fontPercent: {
 
-       selector: '.vjs-font-percent > select',
 
-       id: 'captions-font-size-%s',
 
-       label: 'Font Size',
 
-       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%']],
 
-       default: 2,
 
-       parser: v => v === '1.00' ? null : Number(v)
 
-     },
 
-     textOpacity: {
 
-       selector: '.vjs-text-opacity > select',
 
-       id: 'captions-foreground-opacity-%s',
 
-       label: 'Opacity',
 
-       options: [OPACITY_OPAQUE, OPACITY_SEMI]
 
-     },
 
-     // Options for this object are defined below.
 
-     windowColor: {
 
-       selector: '.vjs-window-color > select',
 
-       id: 'captions-window-color-%s',
 
-       label: 'Color'
 
-     },
 
-     // Options for this object are defined below.
 
-     windowOpacity: {
 
-       selector: '.vjs-window-opacity > select',
 
-       id: 'captions-window-opacity-%s',
 
-       label: 'Opacity',
 
-       options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
 
-     }
 
-   };
 
-   selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
 
-   /**
 
-    * Get the actual value of an option.
 
-    *
 
-    * @param  {string} value
 
-    *         The value to get
 
-    *
 
-    * @param  {Function} [parser]
 
-    *         Optional function to adjust the value.
 
-    *
 
-    * @return {*}
 
-    *         - Will be `undefined` if no value exists
 
-    *         - Will be `undefined` if the given value is "none".
 
-    *         - Will be the actual value otherwise.
 
-    *
 
-    * @private
 
-    */
 
-   function parseOptionValue(value, parser) {
 
-     if (parser) {
 
-       value = parser(value);
 
-     }
 
-     if (value && value !== 'none') {
 
-       return value;
 
-     }
 
-   }
 
-   /**
 
-    * Gets the value of the selected <option> element within a <select> element.
 
-    *
 
-    * @param  {Element} el
 
-    *         the element to look in
 
-    *
 
-    * @param  {Function} [parser]
 
-    *         Optional function to adjust the value.
 
-    *
 
-    * @return {*}
 
-    *         - Will be `undefined` if no value exists
 
-    *         - Will be `undefined` if the given value is "none".
 
-    *         - Will be the actual value otherwise.
 
-    *
 
-    * @private
 
-    */
 
-   function getSelectedOptionValue(el, parser) {
 
-     const value = el.options[el.options.selectedIndex].value;
 
-     return parseOptionValue(value, parser);
 
-   }
 
-   /**
 
-    * Sets the selected <option> element within a <select> element based on a
 
-    * given value.
 
-    *
 
-    * @param {Element} el
 
-    *        The element to look in.
 
-    *
 
-    * @param {string} value
 
-    *        the property to look on.
 
-    *
 
-    * @param {Function} [parser]
 
-    *        Optional function to adjust the value before comparing.
 
-    *
 
-    * @private
 
-    */
 
-   function setSelectedOption(el, value, parser) {
 
-     if (!value) {
 
-       return;
 
-     }
 
-     for (let i = 0; i < el.options.length; i++) {
 
-       if (parseOptionValue(el.options[i].value, parser) === value) {
 
-         el.selectedIndex = i;
 
-         break;
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * Manipulate Text Tracks settings.
 
-    *
 
-    * @extends ModalDialog
 
-    */
 
-   class TextTrackSettings extends ModalDialog {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('../player').default } player
 
-      *         The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *         The key/value store of player options.
 
-      */
 
-     constructor(player, options) {
 
-       options.temporary = false;
 
-       super(player, options);
 
-       this.updateDisplay = this.updateDisplay.bind(this);
 
-       // fill the modal and pretend we have opened it
 
-       this.fill();
 
-       this.hasBeenOpened_ = this.hasBeenFilled_ = true;
 
-       this.endDialog = createEl('p', {
 
-         className: 'vjs-control-text',
 
-         textContent: this.localize('End of dialog window.')
 
-       });
 
-       this.el().appendChild(this.endDialog);
 
-       this.setDefaults();
 
-       // Grab `persistTextTrackSettings` from the player options if not passed in child options
 
-       if (options.persistTextTrackSettings === undefined) {
 
-         this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
 
-       }
 
-       this.on(this.$('.vjs-done-button'), 'click', () => {
 
-         this.saveSettings();
 
-         this.close();
 
-       });
 
-       this.on(this.$('.vjs-default-button'), 'click', () => {
 
-         this.setDefaults();
 
-         this.updateDisplay();
 
-       });
 
-       each(selectConfigs, config => {
 
-         this.on(this.$(config.selector), 'change', this.updateDisplay);
 
-       });
 
-       if (this.options_.persistTextTrackSettings) {
 
-         this.restoreSettings();
 
-       }
 
-     }
 
-     dispose() {
 
-       this.endDialog = null;
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Create a <select> element with configured options.
 
-      *
 
-      * @param {string} key
 
-      *        Configuration key to use during creation.
 
-      *
 
-      * @return {string}
 
-      *         An HTML string.
 
-      *
 
-      * @private
 
-      */
 
-     createElSelect_(key, legendId = '', type = 'label') {
 
-       const config = selectConfigs[key];
 
-       const id = config.id.replace('%s', this.id_);
 
-       const selectLabelledbyIds = [legendId, id].join(' ').trim();
 
-       return [`<${type} id="${id}" class="${type === 'label' ? 'vjs-label' : ''}">`, this.localize(config.label), `</${type}>`, `<select aria-labelledby="${selectLabelledbyIds}">`].concat(config.options.map(o => {
 
-         const optionId = id + '-' + o[1].replace(/\W+/g, '');
 
-         return [`<option id="${optionId}" value="${o[0]}" `, `aria-labelledby="${selectLabelledbyIds} ${optionId}">`, this.localize(o[1]), '</option>'].join('');
 
-       })).concat('</select>').join('');
 
-     }
 
-     /**
 
-      * Create foreground color element for the component
 
-      *
 
-      * @return {string}
 
-      *         An HTML string.
 
-      *
 
-      * @private
 
-      */
 
-     createElFgColor_() {
 
-       const legendId = `captions-text-legend-${this.id_}`;
 
-       return ['<fieldset class="vjs-fg vjs-track-setting">', `<legend id="${legendId}">`, this.localize('Text'), '</legend>', '<span class="vjs-text-color">', this.createElSelect_('color', legendId), '</span>', '<span class="vjs-text-opacity vjs-opacity">', this.createElSelect_('textOpacity', legendId), '</span>', '</fieldset>'].join('');
 
-     }
 
-     /**
 
-      * Create background color element for the component
 
-      *
 
-      * @return {string}
 
-      *         An HTML string.
 
-      *
 
-      * @private
 
-      */
 
-     createElBgColor_() {
 
-       const legendId = `captions-background-${this.id_}`;
 
-       return ['<fieldset class="vjs-bg vjs-track-setting">', `<legend id="${legendId}">`, this.localize('Text Background'), '</legend>', '<span class="vjs-bg-color">', this.createElSelect_('backgroundColor', legendId), '</span>', '<span class="vjs-bg-opacity vjs-opacity">', this.createElSelect_('backgroundOpacity', legendId), '</span>', '</fieldset>'].join('');
 
-     }
 
-     /**
 
-      * Create window color element for the component
 
-      *
 
-      * @return {string}
 
-      *         An HTML string.
 
-      *
 
-      * @private
 
-      */
 
-     createElWinColor_() {
 
-       const legendId = `captions-window-${this.id_}`;
 
-       return ['<fieldset class="vjs-window vjs-track-setting">', `<legend id="${legendId}">`, this.localize('Caption Area Background'), '</legend>', '<span class="vjs-window-color">', this.createElSelect_('windowColor', legendId), '</span>', '<span class="vjs-window-opacity vjs-opacity">', this.createElSelect_('windowOpacity', legendId), '</span>', '</fieldset>'].join('');
 
-     }
 
-     /**
 
-      * Create color elements for the component
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created
 
-      *
 
-      * @private
 
-      */
 
-     createElColors_() {
 
-       return createEl('div', {
 
-         className: 'vjs-track-settings-colors',
 
-         innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
 
-       });
 
-     }
 
-     /**
 
-      * Create font elements for the component
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      *
 
-      * @private
 
-      */
 
-     createElFont_() {
 
-       return createEl('div', {
 
-         className: 'vjs-track-settings-font',
 
-         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('')
 
-       });
 
-     }
 
-     /**
 
-      * Create controls for the component
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      *
 
-      * @private
 
-      */
 
-     createElControls_() {
 
-       const defaultsDescription = this.localize('restore all settings to the default values');
 
-       return createEl('div', {
 
-         className: 'vjs-track-settings-controls',
 
-         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('')
 
-       });
 
-     }
 
-     content() {
 
-       return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
 
-     }
 
-     label() {
 
-       return this.localize('Caption Settings Dialog');
 
-     }
 
-     description() {
 
-       return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
 
-     }
 
-     buildCSSClass() {
 
-       return super.buildCSSClass() + ' vjs-text-track-settings';
 
-     }
 
-     /**
 
-      * Gets an object of text track settings (or null).
 
-      *
 
-      * @return {Object}
 
-      *         An object with config values parsed from the DOM or localStorage.
 
-      */
 
-     getValues() {
 
-       return reduce(selectConfigs, (accum, config, key) => {
 
-         const value = getSelectedOptionValue(this.$(config.selector), config.parser);
 
-         if (value !== undefined) {
 
-           accum[key] = value;
 
-         }
 
-         return accum;
 
-       }, {});
 
-     }
 
-     /**
 
-      * Sets text track settings from an object of values.
 
-      *
 
-      * @param {Object} values
 
-      *        An object with config values parsed from the DOM or localStorage.
 
-      */
 
-     setValues(values) {
 
-       each(selectConfigs, (config, key) => {
 
-         setSelectedOption(this.$(config.selector), values[key], config.parser);
 
-       });
 
-     }
 
-     /**
 
-      * Sets all `<select>` elements to their default values.
 
-      */
 
-     setDefaults() {
 
-       each(selectConfigs, config => {
 
-         const index = config.hasOwnProperty('default') ? config.default : 0;
 
-         this.$(config.selector).selectedIndex = index;
 
-       });
 
-     }
 
-     /**
 
-      * Restore texttrack settings from localStorage
 
-      */
 
-     restoreSettings() {
 
-       let values;
 
-       try {
 
-         values = JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_KEY$1));
 
-       } catch (err) {
 
-         log$1.warn(err);
 
-       }
 
-       if (values) {
 
-         this.setValues(values);
 
-       }
 
-     }
 
-     /**
 
-      * Save text track settings to localStorage
 
-      */
 
-     saveSettings() {
 
-       if (!this.options_.persistTextTrackSettings) {
 
-         return;
 
-       }
 
-       const values = this.getValues();
 
-       try {
 
-         if (Object.keys(values).length) {
 
-           window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(values));
 
-         } else {
 
-           window.localStorage.removeItem(LOCAL_STORAGE_KEY$1);
 
-         }
 
-       } catch (err) {
 
-         log$1.warn(err);
 
-       }
 
-     }
 
-     /**
 
-      * Update display of text track settings
 
-      */
 
-     updateDisplay() {
 
-       const ttDisplay = this.player_.getChild('textTrackDisplay');
 
-       if (ttDisplay) {
 
-         ttDisplay.updateDisplay();
 
-       }
 
-     }
 
-     /**
 
-      * conditionally blur the element and refocus the captions button
 
-      *
 
-      * @private
 
-      */
 
-     conditionalBlur_() {
 
-       this.previouslyActiveEl_ = null;
 
-       const cb = this.player_.controlBar;
 
-       const subsCapsBtn = cb && cb.subsCapsButton;
 
-       const ccBtn = cb && cb.captionsButton;
 
-       if (subsCapsBtn) {
 
-         subsCapsBtn.focus();
 
-       } else if (ccBtn) {
 
-         ccBtn.focus();
 
-       }
 
-     }
 
-     /**
 
-      * Repopulate dialog with new localizations on languagechange
 
-      */
 
-     handleLanguagechange() {
 
-       this.fill();
 
-     }
 
-   }
 
-   Component$1.registerComponent('TextTrackSettings', TextTrackSettings);
 
-   /**
 
-    * @file resize-manager.js
 
-    */
 
-   /**
 
-    * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
 
-    *
 
-    * 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}.
 
-    *
 
-    * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
 
-    * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
 
-    *
 
-    * @example <caption>How to disable the resize manager</caption>
 
-    * const player = videojs('#vid', {
 
-    *   resizeManager: false
 
-    * });
 
-    *
 
-    * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
 
-    *
 
-    * @extends Component
 
-    */
 
-   class ResizeManager extends Component$1 {
 
-     /**
 
-      * Create the ResizeManager.
 
-      *
 
-      * @param {Object} player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of ResizeManager options.
 
-      *
 
-      * @param {Object} [options.ResizeObserver]
 
-      *        A polyfill for ResizeObserver can be passed in here.
 
-      *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
 
-      */
 
-     constructor(player, options) {
 
-       let RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window.ResizeObserver;
 
-       // if `null` was passed, we want to disable the ResizeObserver
 
-       if (options.ResizeObserver === null) {
 
-         RESIZE_OBSERVER_AVAILABLE = false;
 
-       }
 
-       // Only create an element when ResizeObserver isn't available
 
-       const options_ = merge$2({
 
-         createEl: !RESIZE_OBSERVER_AVAILABLE,
 
-         reportTouchActivity: false
 
-       }, options);
 
-       super(player, options_);
 
-       this.ResizeObserver = options.ResizeObserver || window.ResizeObserver;
 
-       this.loadListener_ = null;
 
-       this.resizeObserver_ = null;
 
-       this.debouncedHandler_ = debounce(() => {
 
-         this.resizeHandler();
 
-       }, 100, false, this);
 
-       if (RESIZE_OBSERVER_AVAILABLE) {
 
-         this.resizeObserver_ = new this.ResizeObserver(this.debouncedHandler_);
 
-         this.resizeObserver_.observe(player.el());
 
-       } else {
 
-         this.loadListener_ = () => {
 
-           if (!this.el_ || !this.el_.contentWindow) {
 
-             return;
 
-           }
 
-           const debouncedHandler_ = this.debouncedHandler_;
 
-           let unloadListener_ = this.unloadListener_ = function () {
 
-             off(this, 'resize', debouncedHandler_);
 
-             off(this, 'unload', unloadListener_);
 
-             unloadListener_ = null;
 
-           };
 
-           // safari and edge can unload the iframe before resizemanager dispose
 
-           // we have to dispose of event handlers correctly before that happens
 
-           on(this.el_.contentWindow, 'unload', unloadListener_);
 
-           on(this.el_.contentWindow, 'resize', debouncedHandler_);
 
-         };
 
-         this.one('load', this.loadListener_);
 
-       }
 
-     }
 
-     createEl() {
 
-       return super.createEl('iframe', {
 
-         className: 'vjs-resize-manager',
 
-         tabIndex: -1,
 
-         title: this.localize('No content')
 
-       }, {
 
-         'aria-hidden': 'true'
 
-       });
 
-     }
 
-     /**
 
-      * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
 
-      *
 
-      * @fires Player#playerresize
 
-      */
 
-     resizeHandler() {
 
-       /**
 
-        * Called when the player size has changed
 
-        *
 
-        * @event Player#playerresize
 
-        * @type {Event}
 
-        */
 
-       // make sure player is still around to trigger
 
-       // prevents this from causing an error after dispose
 
-       if (!this.player_ || !this.player_.trigger) {
 
-         return;
 
-       }
 
-       this.player_.trigger('playerresize');
 
-     }
 
-     dispose() {
 
-       if (this.debouncedHandler_) {
 
-         this.debouncedHandler_.cancel();
 
-       }
 
-       if (this.resizeObserver_) {
 
-         if (this.player_.el()) {
 
-           this.resizeObserver_.unobserve(this.player_.el());
 
-         }
 
-         this.resizeObserver_.disconnect();
 
-       }
 
-       if (this.loadListener_) {
 
-         this.off('load', this.loadListener_);
 
-       }
 
-       if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
 
-         this.unloadListener_.call(this.el_.contentWindow);
 
-       }
 
-       this.ResizeObserver = null;
 
-       this.resizeObserver = null;
 
-       this.debouncedHandler_ = null;
 
-       this.loadListener_ = null;
 
-       super.dispose();
 
-     }
 
-   }
 
-   Component$1.registerComponent('ResizeManager', ResizeManager);
 
-   const defaults = {
 
-     trackingThreshold: 20,
 
-     liveTolerance: 15
 
-   };
 
-   /*
 
-     track when we are at the live edge, and other helpers for live playback */
 
-   /**
 
-    * A class for checking live current time and determining when the player
 
-    * is at or behind the live edge.
 
-    */
 
-   class LiveTracker extends Component$1 {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * @param { import('./player').default } player
 
-      *        The `Player` that this class should be attached to.
 
-      *
 
-      * @param {Object} [options]
 
-      *        The key/value store of player options.
 
-      *
 
-      * @param {number} [options.trackingThreshold=20]
 
-      *        Number of seconds of live window (seekableEnd - seekableStart) that
 
-      *        media needs to have before the liveui will be shown.
 
-      *
 
-      * @param {number} [options.liveTolerance=15]
 
-      *        Number of seconds behind live that we have to be
 
-      *        before we will be considered non-live. Note that this will only
 
-      *        be used when playing at the live edge. This allows large seekable end
 
-      *        changes to not effect whether we are live or not.
 
-      */
 
-     constructor(player, options) {
 
-       // LiveTracker does not need an element
 
-       const options_ = merge$2(defaults, options, {
 
-         createEl: false
 
-       });
 
-       super(player, options_);
 
-       this.trackLiveHandler_ = () => this.trackLive_();
 
-       this.handlePlay_ = e => this.handlePlay(e);
 
-       this.handleFirstTimeupdate_ = e => this.handleFirstTimeupdate(e);
 
-       this.handleSeeked_ = e => this.handleSeeked(e);
 
-       this.seekToLiveEdge_ = e => this.seekToLiveEdge(e);
 
-       this.reset_();
 
-       this.on(this.player_, 'durationchange', e => this.handleDurationchange(e));
 
-       // we should try to toggle tracking on canplay as native playback engines, like Safari
 
-       // may not have the proper values for things like seekableEnd until then
 
-       this.on(this.player_, 'canplay', () => this.toggleTracking());
 
-     }
 
-     /**
 
-      * all the functionality for tracking when seek end changes
 
-      * and for tracking how far past seek end we should be
 
-      */
 
-     trackLive_() {
 
-       const seekable = this.player_.seekable();
 
-       // skip undefined seekable
 
-       if (!seekable || !seekable.length) {
 
-         return;
 
-       }
 
-       const newTime = Number(window.performance.now().toFixed(4));
 
-       const deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000;
 
-       this.lastTime_ = newTime;
 
-       this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
 
-       const liveCurrentTime = this.liveCurrentTime();
 
-       const currentTime = this.player_.currentTime();
 
-       // we are behind live if any are true
 
-       // 1. the player is paused
 
-       // 2. the user seeked to a location 2 seconds away from live
 
-       // 3. the difference between live and current time is greater
 
-       //    liveTolerance which defaults to 15s
 
-       let isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance;
 
-       // we cannot be behind if
 
-       // 1. until we have not seen a timeupdate yet
 
-       // 2. liveCurrentTime is Infinity, which happens on Android and Native Safari
 
-       if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
 
-         isBehind = false;
 
-       }
 
-       if (isBehind !== this.behindLiveEdge_) {
 
-         this.behindLiveEdge_ = isBehind;
 
-         this.trigger('liveedgechange');
 
-       }
 
-     }
 
-     /**
 
-      * handle a durationchange event on the player
 
-      * and start/stop tracking accordingly.
 
-      */
 
-     handleDurationchange() {
 
-       this.toggleTracking();
 
-     }
 
-     /**
 
-      * start/stop tracking
 
-      */
 
-     toggleTracking() {
 
-       if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
 
-         if (this.player_.options_.liveui) {
 
-           this.player_.addClass('vjs-liveui');
 
-         }
 
-         this.startTracking();
 
-       } else {
 
-         this.player_.removeClass('vjs-liveui');
 
-         this.stopTracking();
 
-       }
 
-     }
 
-     /**
 
-      * start tracking live playback
 
-      */
 
-     startTracking() {
 
-       if (this.isTracking()) {
 
-         return;
 
-       }
 
-       // If we haven't seen a timeupdate, we need to check whether playback
 
-       // began before this component started tracking. This can happen commonly
 
-       // when using autoplay.
 
-       if (!this.timeupdateSeen_) {
 
-         this.timeupdateSeen_ = this.player_.hasStarted();
 
-       }
 
-       this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, UPDATE_REFRESH_INTERVAL);
 
-       this.trackLive_();
 
-       this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_);
 
-       if (!this.timeupdateSeen_) {
 
-         this.one(this.player_, 'play', this.handlePlay_);
 
-         this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
 
-       } else {
 
-         this.on(this.player_, 'seeked', this.handleSeeked_);
 
-       }
 
-     }
 
-     /**
 
-      * handle the first timeupdate on the player if it wasn't already playing
 
-      * when live tracker started tracking.
 
-      */
 
-     handleFirstTimeupdate() {
 
-       this.timeupdateSeen_ = true;
 
-       this.on(this.player_, 'seeked', this.handleSeeked_);
 
-     }
 
-     /**
 
-      * Keep track of what time a seek starts, and listen for seeked
 
-      * to find where a seek ends.
 
-      */
 
-     handleSeeked() {
 
-       const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
 
-       this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2;
 
-       this.nextSeekedFromUser_ = false;
 
-       this.trackLive_();
 
-     }
 
-     /**
 
-      * handle the first play on the player, and make sure that we seek
 
-      * right to the live edge.
 
-      */
 
-     handlePlay() {
 
-       this.one(this.player_, 'timeupdate', this.seekToLiveEdge_);
 
-     }
 
-     /**
 
-      * Stop tracking, and set all internal variables to
 
-      * their initial value.
 
-      */
 
-     reset_() {
 
-       this.lastTime_ = -1;
 
-       this.pastSeekEnd_ = 0;
 
-       this.lastSeekEnd_ = -1;
 
-       this.behindLiveEdge_ = true;
 
-       this.timeupdateSeen_ = false;
 
-       this.seekedBehindLive_ = false;
 
-       this.nextSeekedFromUser_ = false;
 
-       this.clearInterval(this.trackingInterval_);
 
-       this.trackingInterval_ = null;
 
-       this.off(this.player_, ['play', 'pause'], this.trackLiveHandler_);
 
-       this.off(this.player_, 'seeked', this.handleSeeked_);
 
-       this.off(this.player_, 'play', this.handlePlay_);
 
-       this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
 
-       this.off(this.player_, 'timeupdate', this.seekToLiveEdge_);
 
-     }
 
-     /**
 
-      * The next seeked event is from the user. Meaning that any seek
 
-      * > 2s behind live will be considered behind live for real and
 
-      * liveTolerance will be ignored.
 
-      */
 
-     nextSeekedFromUser() {
 
-       this.nextSeekedFromUser_ = true;
 
-     }
 
-     /**
 
-      * stop tracking live playback
 
-      */
 
-     stopTracking() {
 
-       if (!this.isTracking()) {
 
-         return;
 
-       }
 
-       this.reset_();
 
-       this.trigger('liveedgechange');
 
-     }
 
-     /**
 
-      * A helper to get the player seekable end
 
-      * so that we don't have to null check everywhere
 
-      *
 
-      * @return {number}
 
-      *         The furthest seekable end or Infinity.
 
-      */
 
-     seekableEnd() {
 
-       const seekable = this.player_.seekable();
 
-       const seekableEnds = [];
 
-       let i = seekable ? seekable.length : 0;
 
-       while (i--) {
 
-         seekableEnds.push(seekable.end(i));
 
-       }
 
-       // grab the furthest seekable end after sorting, or if there are none
 
-       // default to Infinity
 
-       return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
 
-     }
 
-     /**
 
-      * A helper to get the player seekable start
 
-      * so that we don't have to null check everywhere
 
-      *
 
-      * @return {number}
 
-      *         The earliest seekable start or 0.
 
-      */
 
-     seekableStart() {
 
-       const seekable = this.player_.seekable();
 
-       const seekableStarts = [];
 
-       let i = seekable ? seekable.length : 0;
 
-       while (i--) {
 
-         seekableStarts.push(seekable.start(i));
 
-       }
 
-       // grab the first seekable start after sorting, or if there are none
 
-       // default to 0
 
-       return seekableStarts.length ? seekableStarts.sort()[0] : 0;
 
-     }
 
-     /**
 
-      * Get the live time window aka
 
-      * the amount of time between seekable start and
 
-      * live current time.
 
-      *
 
-      * @return {number}
 
-      *         The amount of seconds that are seekable in
 
-      *         the live video.
 
-      */
 
-     liveWindow() {
 
-       const liveCurrentTime = this.liveCurrentTime();
 
-       // if liveCurrenTime is Infinity then we don't have a liveWindow at all
 
-       if (liveCurrentTime === Infinity) {
 
-         return 0;
 
-       }
 
-       return liveCurrentTime - this.seekableStart();
 
-     }
 
-     /**
 
-      * Determines if the player is live, only checks if this component
 
-      * is tracking live playback or not
 
-      *
 
-      * @return {boolean}
 
-      *         Whether liveTracker is tracking
 
-      */
 
-     isLive() {
 
-       return this.isTracking();
 
-     }
 
-     /**
 
-      * Determines if currentTime is at the live edge and won't fall behind
 
-      * on each seekableendchange
 
-      *
 
-      * @return {boolean}
 
-      *         Whether playback is at the live edge
 
-      */
 
-     atLiveEdge() {
 
-       return !this.behindLiveEdge();
 
-     }
 
-     /**
 
-      * get what we expect the live current time to be
 
-      *
 
-      * @return {number}
 
-      *         The expected live current time
 
-      */
 
-     liveCurrentTime() {
 
-       return this.pastSeekEnd() + this.seekableEnd();
 
-     }
 
-     /**
 
-      * The number of seconds that have occurred after seekable end
 
-      * changed. This will be reset to 0 once seekable end changes.
 
-      *
 
-      * @return {number}
 
-      *         Seconds past the current seekable end
 
-      */
 
-     pastSeekEnd() {
 
-       const seekableEnd = this.seekableEnd();
 
-       if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
 
-         this.pastSeekEnd_ = 0;
 
-       }
 
-       this.lastSeekEnd_ = seekableEnd;
 
-       return this.pastSeekEnd_;
 
-     }
 
-     /**
 
-      * If we are currently behind the live edge, aka currentTime will be
 
-      * behind on a seekableendchange
 
-      *
 
-      * @return {boolean}
 
-      *         If we are behind the live edge
 
-      */
 
-     behindLiveEdge() {
 
-       return this.behindLiveEdge_;
 
-     }
 
-     /**
 
-      * Whether live tracker is currently tracking or not.
 
-      */
 
-     isTracking() {
 
-       return typeof this.trackingInterval_ === 'number';
 
-     }
 
-     /**
 
-      * Seek to the live edge if we are behind the live edge
 
-      */
 
-     seekToLiveEdge() {
 
-       this.seekedBehindLive_ = false;
 
-       if (this.atLiveEdge()) {
 
-         return;
 
-       }
 
-       this.nextSeekedFromUser_ = false;
 
-       this.player_.currentTime(this.liveCurrentTime());
 
-     }
 
-     /**
 
-      * Dispose of liveTracker
 
-      */
 
-     dispose() {
 
-       this.stopTracking();
 
-       super.dispose();
 
-     }
 
-   }
 
-   Component$1.registerComponent('LiveTracker', LiveTracker);
 
-   /**
 
-    * Displays an element over the player which contains an optional title and
 
-    * description for the current content.
 
-    *
 
-    * Much of the code for this component originated in the now obsolete
 
-    * videojs-dock plugin: https://github.com/brightcove/videojs-dock/
 
-    *
 
-    * @extends Component
 
-    */
 
-   class TitleBar extends Component$1 {
 
-     constructor(player, options) {
 
-       super(player, options);
 
-       this.on('statechanged', e => this.updateDom_());
 
-       this.updateDom_();
 
-     }
 
-     /**
 
-      * Create the `TitleBar`'s DOM element
 
-      *
 
-      * @return {Element}
 
-      *         The element that was created.
 
-      */
 
-     createEl() {
 
-       this.els = {
 
-         title: createEl('div', {
 
-           className: 'vjs-title-bar-title',
 
-           id: `vjs-title-bar-title-${newGUID()}`
 
-         }),
 
-         description: createEl('div', {
 
-           className: 'vjs-title-bar-description',
 
-           id: `vjs-title-bar-description-${newGUID()}`
 
-         })
 
-       };
 
-       return createEl('div', {
 
-         className: 'vjs-title-bar'
 
-       }, {}, Object.values(this.els));
 
-     }
 
-     /**
 
-      * Updates the DOM based on the component's state object.
 
-      */
 
-     updateDom_() {
 
-       const tech = this.player_.tech_;
 
-       const techEl = tech && tech.el_;
 
-       const techAriaAttrs = {
 
-         title: 'aria-labelledby',
 
-         description: 'aria-describedby'
 
-       };
 
-       ['title', 'description'].forEach(k => {
 
-         const value = this.state[k];
 
-         const el = this.els[k];
 
-         const techAriaAttr = techAriaAttrs[k];
 
-         emptyEl(el);
 
-         if (value) {
 
-           textContent(el, value);
 
-         }
 
-         // If there is a tech element available, update its ARIA attributes
 
-         // according to whether a title and/or description have been provided.
 
-         if (techEl) {
 
-           techEl.removeAttribute(techAriaAttr);
 
-           if (value) {
 
-             techEl.setAttribute(techAriaAttr, el.id);
 
-           }
 
-         }
 
-       });
 
-       if (this.state.title || this.state.description) {
 
-         this.show();
 
-       } else {
 
-         this.hide();
 
-       }
 
-     }
 
-     /**
 
-      * Update the contents of the title bar component with new title and
 
-      * description text.
 
-      *
 
-      * If both title and description are missing, the title bar will be hidden.
 
-      *
 
-      * If either title or description are present, the title bar will be visible.
 
-      *
 
-      * NOTE: Any previously set value will be preserved. To unset a previously
 
-      * set value, you must pass an empty string or null.
 
-      *
 
-      * For example:
 
-      *
 
-      * ```
 
-      * update({title: 'foo', description: 'bar'}) // title: 'foo', description: 'bar'
 
-      * update({description: 'bar2'}) // title: 'foo', description: 'bar2'
 
-      * update({title: ''}) // title: '', description: 'bar2'
 
-      * update({title: 'foo', description: null}) // title: 'foo', description: null
 
-      * ```
 
-      *
 
-      * @param  {Object} [options={}]
 
-      *         An options object. When empty, the title bar will be hidden.
 
-      *
 
-      * @param  {string} [options.title]
 
-      *         A title to display in the title bar.
 
-      *
 
-      * @param  {string} [options.description]
 
-      *         A description to display in the title bar.
 
-      */
 
-     update(options) {
 
-       this.setState(options);
 
-     }
 
-     /**
 
-      * Dispose the component.
 
-      */
 
-     dispose() {
 
-       const tech = this.player_.tech_;
 
-       const techEl = tech && tech.el_;
 
-       if (techEl) {
 
-         techEl.removeAttribute('aria-labelledby');
 
-         techEl.removeAttribute('aria-describedby');
 
-       }
 
-       super.dispose();
 
-       this.els = null;
 
-     }
 
-   }
 
-   Component$1.registerComponent('TitleBar', TitleBar);
 
-   /**
 
-    * This function is used to fire a sourceset when there is something
 
-    * similar to `mediaEl.load()` being called. It will try to find the source via
 
-    * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
 
-    * with the source that was found or empty string if we cannot know. If it cannot
 
-    * find a source then `sourceset` will not be fired.
 
-    *
 
-    * @param { import('./html5').default } tech
 
-    *        The tech object that sourceset was setup on
 
-    *
 
-    * @return {boolean}
 
-    *         returns false if the sourceset was not fired and true otherwise.
 
-    */
 
-   const sourcesetLoad = tech => {
 
-     const el = tech.el();
 
-     // if `el.src` is set, that source will be loaded.
 
-     if (el.hasAttribute('src')) {
 
-       tech.triggerSourceset(el.src);
 
-       return true;
 
-     }
 
-     /**
 
-      * Since there isn't a src property on the media element, source elements will be used for
 
-      * implementing the source selection algorithm. This happens asynchronously and
 
-      * for most cases were there is more than one source we cannot tell what source will
 
-      * be loaded, without re-implementing the source selection algorithm. At this time we are not
 
-      * going to do that. There are three special cases that we do handle here though:
 
-      *
 
-      * 1. If there are no sources, do not fire `sourceset`.
 
-      * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
 
-      * 3. If there is more than one `<source>` but all of them have the same `src` url.
 
-      *    That will be our src.
 
-      */
 
-     const sources = tech.$$('source');
 
-     const srcUrls = [];
 
-     let src = '';
 
-     // if there are no sources, do not fire sourceset
 
-     if (!sources.length) {
 
-       return false;
 
-     }
 
-     // only count valid/non-duplicate source elements
 
-     for (let i = 0; i < sources.length; i++) {
 
-       const url = sources[i].src;
 
-       if (url && srcUrls.indexOf(url) === -1) {
 
-         srcUrls.push(url);
 
-       }
 
-     }
 
-     // there were no valid sources
 
-     if (!srcUrls.length) {
 
-       return false;
 
-     }
 
-     // there is only one valid source element url
 
-     // use that
 
-     if (srcUrls.length === 1) {
 
-       src = srcUrls[0];
 
-     }
 
-     tech.triggerSourceset(src);
 
-     return true;
 
-   };
 
-   /**
 
-    * our implementation of an `innerHTML` descriptor for browsers
 
-    * that do not have one.
 
-    */
 
-   const innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
 
-     get() {
 
-       return this.cloneNode(true).innerHTML;
 
-     },
 
-     set(v) {
 
-       // make a dummy node to use innerHTML on
 
-       const dummy = document.createElement(this.nodeName.toLowerCase());
 
-       // set innerHTML to the value provided
 
-       dummy.innerHTML = v;
 
-       // make a document fragment to hold the nodes from dummy
 
-       const docFrag = document.createDocumentFragment();
 
-       // copy all of the nodes created by the innerHTML on dummy
 
-       // to the document fragment
 
-       while (dummy.childNodes.length) {
 
-         docFrag.appendChild(dummy.childNodes[0]);
 
-       }
 
-       // remove content
 
-       this.innerText = '';
 
-       // now we add all of that html in one by appending the
 
-       // document fragment. This is how innerHTML does it.
 
-       window.Element.prototype.appendChild.call(this, docFrag);
 
-       // then return the result that innerHTML's setter would
 
-       return this.innerHTML;
 
-     }
 
-   });
 
-   /**
 
-    * Get a property descriptor given a list of priorities and the
 
-    * property to get.
 
-    */
 
-   const getDescriptor = (priority, prop) => {
 
-     let descriptor = {};
 
-     for (let i = 0; i < priority.length; i++) {
 
-       descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
 
-       if (descriptor && descriptor.set && descriptor.get) {
 
-         break;
 
-       }
 
-     }
 
-     descriptor.enumerable = true;
 
-     descriptor.configurable = true;
 
-     return descriptor;
 
-   };
 
-   const getInnerHTMLDescriptor = tech => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, window.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
 
-   /**
 
-    * Patches browser internal functions so that we can tell synchronously
 
-    * if a `<source>` was appended to the media element. For some reason this
 
-    * causes a `sourceset` if the the media element is ready and has no source.
 
-    * This happens when:
 
-    * - The page has just loaded and the media element does not have a source.
 
-    * - The media element was emptied of all sources, then `load()` was called.
 
-    *
 
-    * It does this by patching the following functions/properties when they are supported:
 
-    *
 
-    * - `append()` - can be used to add a `<source>` element to the media element
 
-    * - `appendChild()` - can be used to add a `<source>` element to the media element
 
-    * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
 
-    * - `innerHTML` -  can be used to add a `<source>` element to the media element
 
-    *
 
-    * @param {Html5} tech
 
-    *        The tech object that sourceset is being setup on.
 
-    */
 
-   const firstSourceWatch = function (tech) {
 
-     const el = tech.el();
 
-     // make sure firstSourceWatch isn't setup twice.
 
-     if (el.resetSourceWatch_) {
 
-       return;
 
-     }
 
-     const old = {};
 
-     const innerDescriptor = getInnerHTMLDescriptor(tech);
 
-     const appendWrapper = appendFn => (...args) => {
 
-       const retval = appendFn.apply(el, args);
 
-       sourcesetLoad(tech);
 
-       return retval;
 
-     };
 
-     ['append', 'appendChild', 'insertAdjacentHTML'].forEach(k => {
 
-       if (!el[k]) {
 
-         return;
 
-       }
 
-       // store the old function
 
-       old[k] = el[k];
 
-       // call the old function with a sourceset if a source
 
-       // was loaded
 
-       el[k] = appendWrapper(old[k]);
 
-     });
 
-     Object.defineProperty(el, 'innerHTML', merge$2(innerDescriptor, {
 
-       set: appendWrapper(innerDescriptor.set)
 
-     }));
 
-     el.resetSourceWatch_ = () => {
 
-       el.resetSourceWatch_ = null;
 
-       Object.keys(old).forEach(k => {
 
-         el[k] = old[k];
 
-       });
 
-       Object.defineProperty(el, 'innerHTML', innerDescriptor);
 
-     };
 
-     // on the first sourceset, we need to revert our changes
 
-     tech.one('sourceset', el.resetSourceWatch_);
 
-   };
 
-   /**
 
-    * our implementation of a `src` descriptor for browsers
 
-    * that do not have one
 
-    */
 
-   const srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
 
-     get() {
 
-       if (this.hasAttribute('src')) {
 
-         return getAbsoluteURL(window.Element.prototype.getAttribute.call(this, 'src'));
 
-       }
 
-       return '';
 
-     },
 
-     set(v) {
 
-       window.Element.prototype.setAttribute.call(this, 'src', v);
 
-       return v;
 
-     }
 
-   });
 
-   const getSrcDescriptor = tech => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
 
-   /**
 
-    * setup `sourceset` handling on the `Html5` tech. This function
 
-    * patches the following element properties/functions:
 
-    *
 
-    * - `src` - to determine when `src` is set
 
-    * - `setAttribute()` - to determine when `src` is set
 
-    * - `load()` - this re-triggers the source selection algorithm, and can
 
-    *              cause a sourceset.
 
-    *
 
-    * If there is no source when we are adding `sourceset` support or during a `load()`
 
-    * we also patch the functions listed in `firstSourceWatch`.
 
-    *
 
-    * @param {Html5} tech
 
-    *        The tech to patch
 
-    */
 
-   const setupSourceset = function (tech) {
 
-     if (!tech.featuresSourceset) {
 
-       return;
 
-     }
 
-     const el = tech.el();
 
-     // make sure sourceset isn't setup twice.
 
-     if (el.resetSourceset_) {
 
-       return;
 
-     }
 
-     const srcDescriptor = getSrcDescriptor(tech);
 
-     const oldSetAttribute = el.setAttribute;
 
-     const oldLoad = el.load;
 
-     Object.defineProperty(el, 'src', merge$2(srcDescriptor, {
 
-       set: v => {
 
-         const retval = srcDescriptor.set.call(el, v);
 
-         // we use the getter here to get the actual value set on src
 
-         tech.triggerSourceset(el.src);
 
-         return retval;
 
-       }
 
-     }));
 
-     el.setAttribute = (n, v) => {
 
-       const retval = oldSetAttribute.call(el, n, v);
 
-       if (/src/i.test(n)) {
 
-         tech.triggerSourceset(el.src);
 
-       }
 
-       return retval;
 
-     };
 
-     el.load = () => {
 
-       const retval = oldLoad.call(el);
 
-       // if load was called, but there was no source to fire
 
-       // sourceset on. We have to watch for a source append
 
-       // as that can trigger a `sourceset` when the media element
 
-       // has no source
 
-       if (!sourcesetLoad(tech)) {
 
-         tech.triggerSourceset('');
 
-         firstSourceWatch(tech);
 
-       }
 
-       return retval;
 
-     };
 
-     if (el.currentSrc) {
 
-       tech.triggerSourceset(el.currentSrc);
 
-     } else if (!sourcesetLoad(tech)) {
 
-       firstSourceWatch(tech);
 
-     }
 
-     el.resetSourceset_ = () => {
 
-       el.resetSourceset_ = null;
 
-       el.load = oldLoad;
 
-       el.setAttribute = oldSetAttribute;
 
-       Object.defineProperty(el, 'src', srcDescriptor);
 
-       if (el.resetSourceWatch_) {
 
-         el.resetSourceWatch_();
 
-       }
 
-     };
 
-   };
 
-   /**
 
-    * @file html5.js
 
-    */
 
-   /**
 
-    * HTML5 Media Controller - Wrapper for HTML5 Media API
 
-    *
 
-    * @mixes Tech~SourceHandlerAdditions
 
-    * @extends Tech
 
-    */
 
-   class Html5 extends Tech {
 
-     /**
 
-     * Create an instance of this Tech.
 
-     *
 
-     * @param {Object} [options]
 
-     *        The key/value store of player options.
 
-     *
 
-     * @param {Function} [ready]
 
-     *        Callback function to call when the `HTML5` Tech is ready.
 
-     */
 
-     constructor(options, ready) {
 
-       super(options, ready);
 
-       const source = options.source;
 
-       let crossoriginTracks = false;
 
-       this.featuresVideoFrameCallback = this.featuresVideoFrameCallback && this.el_.tagName === 'VIDEO';
 
-       // Set the source if one is provided
 
-       // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
 
-       // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
 
-       // anyway so the error gets fired.
 
-       if (source && (this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
 
-         this.setSource(source);
 
-       } else {
 
-         this.handleLateInit_(this.el_);
 
-       }
 
-       // setup sourceset after late sourceset/init
 
-       if (options.enableSourceset) {
 
-         this.setupSourcesetHandling_();
 
-       }
 
-       this.isScrubbing_ = false;
 
-       if (this.el_.hasChildNodes()) {
 
-         const nodes = this.el_.childNodes;
 
-         let nodesLength = nodes.length;
 
-         const removeNodes = [];
 
-         while (nodesLength--) {
 
-           const node = nodes[nodesLength];
 
-           const nodeName = node.nodeName.toLowerCase();
 
-           if (nodeName === 'track') {
 
-             if (!this.featuresNativeTextTracks) {
 
-               // Empty video tag tracks so the built-in player doesn't use them also.
 
-               // This may not be fast enough to stop HTML5 browsers from reading the tags
 
-               // so we'll need to turn off any default tracks if we're manually doing
 
-               // captions and subtitles. videoElement.textTracks
 
-               removeNodes.push(node);
 
-             } else {
 
-               // store HTMLTrackElement and TextTrack to remote list
 
-               this.remoteTextTrackEls().addTrackElement_(node);
 
-               this.remoteTextTracks().addTrack(node.track);
 
-               this.textTracks().addTrack(node.track);
 
-               if (!crossoriginTracks && !this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
 
-                 crossoriginTracks = true;
 
-               }
 
-             }
 
-           }
 
-         }
 
-         for (let i = 0; i < removeNodes.length; i++) {
 
-           this.el_.removeChild(removeNodes[i]);
 
-         }
 
-       }
 
-       this.proxyNativeTracks_();
 
-       if (this.featuresNativeTextTracks && crossoriginTracks) {
 
-         log$1.warn('Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n' + 'This may prevent text tracks from loading.');
 
-       }
 
-       // prevent iOS Safari from disabling metadata text tracks during native playback
 
-       this.restoreMetadataTracksInIOSNativePlayer_();
 
-       // Determine if native controls should be used
 
-       // Our goal should be to get the custom controls on mobile solid everywhere
 
-       // so we can remove this all together. Right now this will block custom
 
-       // controls on touch enabled laptops like the Chrome Pixel
 
-       if ((TOUCH_ENABLED || IS_IPHONE) && options.nativeControlsForTouch === true) {
 
-         this.setControls(true);
 
-       }
 
-       // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
 
-       // into a `fullscreenchange` event
 
-       this.proxyWebkitFullscreen_();
 
-       this.triggerReady();
 
-     }
 
-     /**
 
-      * Dispose of `HTML5` media element and remove all tracks.
 
-      */
 
-     dispose() {
 
-       if (this.el_ && this.el_.resetSourceset_) {
 
-         this.el_.resetSourceset_();
 
-       }
 
-       Html5.disposeMediaElement(this.el_);
 
-       this.options_ = null;
 
-       // tech will handle clearing of the emulated track list
 
-       super.dispose();
 
-     }
 
-     /**
 
-      * Modify the media element so that we can detect when
 
-      * the source is changed. Fires `sourceset` just after the source has changed
 
-      */
 
-     setupSourcesetHandling_() {
 
-       setupSourceset(this);
 
-     }
 
-     /**
 
-      * When a captions track is enabled in the iOS Safari native player, all other
 
-      * tracks are disabled (including metadata tracks), which nulls all of their
 
-      * associated cue points. This will restore metadata tracks to their pre-fullscreen
 
-      * state in those cases so that cue points are not needlessly lost.
 
-      *
 
-      * @private
 
-      */
 
-     restoreMetadataTracksInIOSNativePlayer_() {
 
-       const textTracks = this.textTracks();
 
-       let metadataTracksPreFullscreenState;
 
-       // captures a snapshot of every metadata track's current state
 
-       const takeMetadataTrackSnapshot = () => {
 
-         metadataTracksPreFullscreenState = [];
 
-         for (let i = 0; i < textTracks.length; i++) {
 
-           const track = textTracks[i];
 
-           if (track.kind === 'metadata') {
 
-             metadataTracksPreFullscreenState.push({
 
-               track,
 
-               storedMode: track.mode
 
-             });
 
-           }
 
-         }
 
-       };
 
-       // snapshot each metadata track's initial state, and update the snapshot
 
-       // each time there is a track 'change' event
 
-       takeMetadataTrackSnapshot();
 
-       textTracks.addEventListener('change', takeMetadataTrackSnapshot);
 
-       this.on('dispose', () => textTracks.removeEventListener('change', takeMetadataTrackSnapshot));
 
-       const restoreTrackMode = () => {
 
-         for (let i = 0; i < metadataTracksPreFullscreenState.length; i++) {
 
-           const storedTrack = metadataTracksPreFullscreenState[i];
 
-           if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
 
-             storedTrack.track.mode = storedTrack.storedMode;
 
-           }
 
-         }
 
-         // we only want this handler to be executed on the first 'change' event
 
-         textTracks.removeEventListener('change', restoreTrackMode);
 
-       };
 
-       // when we enter fullscreen playback, stop updating the snapshot and
 
-       // restore all track modes to their pre-fullscreen state
 
-       this.on('webkitbeginfullscreen', () => {
 
-         textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
 
-         // remove the listener before adding it just in case it wasn't previously removed
 
-         textTracks.removeEventListener('change', restoreTrackMode);
 
-         textTracks.addEventListener('change', restoreTrackMode);
 
-       });
 
-       // start updating the snapshot again after leaving fullscreen
 
-       this.on('webkitendfullscreen', () => {
 
-         // remove the listener before adding it just in case it wasn't previously removed
 
-         textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
 
-         textTracks.addEventListener('change', takeMetadataTrackSnapshot);
 
-         // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
 
-         textTracks.removeEventListener('change', restoreTrackMode);
 
-       });
 
-     }
 
-     /**
 
-      * Attempt to force override of tracks for the given type
 
-      *
 
-      * @param {string} type - Track type to override, possible values include 'Audio',
 
-      * 'Video', and 'Text'.
 
-      * @param {boolean} override - If set to true native audio/video will be overridden,
 
-      * otherwise native audio/video will potentially be used.
 
-      * @private
 
-      */
 
-     overrideNative_(type, override) {
 
-       // If there is no behavioral change don't add/remove listeners
 
-       if (override !== this[`featuresNative${type}Tracks`]) {
 
-         return;
 
-       }
 
-       const lowerCaseType = type.toLowerCase();
 
-       if (this[`${lowerCaseType}TracksListeners_`]) {
 
-         Object.keys(this[`${lowerCaseType}TracksListeners_`]).forEach(eventName => {
 
-           const elTracks = this.el()[`${lowerCaseType}Tracks`];
 
-           elTracks.removeEventListener(eventName, this[`${lowerCaseType}TracksListeners_`][eventName]);
 
-         });
 
-       }
 
-       this[`featuresNative${type}Tracks`] = !override;
 
-       this[`${lowerCaseType}TracksListeners_`] = null;
 
-       this.proxyNativeTracksForType_(lowerCaseType);
 
-     }
 
-     /**
 
-      * Attempt to force override of native audio tracks.
 
-      *
 
-      * @param {boolean} override - If set to true native audio will be overridden,
 
-      * otherwise native audio will potentially be used.
 
-      */
 
-     overrideNativeAudioTracks(override) {
 
-       this.overrideNative_('Audio', override);
 
-     }
 
-     /**
 
-      * Attempt to force override of native video tracks.
 
-      *
 
-      * @param {boolean} override - If set to true native video will be overridden,
 
-      * otherwise native video will potentially be used.
 
-      */
 
-     overrideNativeVideoTracks(override) {
 
-       this.overrideNative_('Video', override);
 
-     }
 
-     /**
 
-      * Proxy native track list events for the given type to our track
 
-      * lists if the browser we are playing in supports that type of track list.
 
-      *
 
-      * @param {string} name - Track type; values include 'audio', 'video', and 'text'
 
-      * @private
 
-      */
 
-     proxyNativeTracksForType_(name) {
 
-       const props = NORMAL[name];
 
-       const elTracks = this.el()[props.getterName];
 
-       const techTracks = this[props.getterName]();
 
-       if (!this[`featuresNative${props.capitalName}Tracks`] || !elTracks || !elTracks.addEventListener) {
 
-         return;
 
-       }
 
-       const listeners = {
 
-         change: e => {
 
-           const event = {
 
-             type: 'change',
 
-             target: techTracks,
 
-             currentTarget: techTracks,
 
-             srcElement: techTracks
 
-           };
 
-           techTracks.trigger(event);
 
-           // if we are a text track change event, we should also notify the
 
-           // remote text track list. This can potentially cause a false positive
 
-           // if we were to get a change event on a non-remote track and
 
-           // we triggered the event on the remote text track list which doesn't
 
-           // contain that track. However, best practices mean looping through the
 
-           // list of tracks and searching for the appropriate mode value, so,
 
-           // this shouldn't pose an issue
 
-           if (name === 'text') {
 
-             this[REMOTE.remoteText.getterName]().trigger(event);
 
-           }
 
-         },
 
-         addtrack(e) {
 
-           techTracks.addTrack(e.track);
 
-         },
 
-         removetrack(e) {
 
-           techTracks.removeTrack(e.track);
 
-         }
 
-       };
 
-       const removeOldTracks = function () {
 
-         const removeTracks = [];
 
-         for (let i = 0; i < techTracks.length; i++) {
 
-           let found = false;
 
-           for (let j = 0; j < elTracks.length; j++) {
 
-             if (elTracks[j] === techTracks[i]) {
 
-               found = true;
 
-               break;
 
-             }
 
-           }
 
-           if (!found) {
 
-             removeTracks.push(techTracks[i]);
 
-           }
 
-         }
 
-         while (removeTracks.length) {
 
-           techTracks.removeTrack(removeTracks.shift());
 
-         }
 
-       };
 
-       this[props.getterName + 'Listeners_'] = listeners;
 
-       Object.keys(listeners).forEach(eventName => {
 
-         const listener = listeners[eventName];
 
-         elTracks.addEventListener(eventName, listener);
 
-         this.on('dispose', e => elTracks.removeEventListener(eventName, listener));
 
-       });
 
-       // Remove (native) tracks that are not used anymore
 
-       this.on('loadstart', removeOldTracks);
 
-       this.on('dispose', e => this.off('loadstart', removeOldTracks));
 
-     }
 
-     /**
 
-      * Proxy all native track list events to our track lists if the browser we are playing
 
-      * in supports that type of track list.
 
-      *
 
-      * @private
 
-      */
 
-     proxyNativeTracks_() {
 
-       NORMAL.names.forEach(name => {
 
-         this.proxyNativeTracksForType_(name);
 
-       });
 
-     }
 
-     /**
 
-      * Create the `Html5` Tech's DOM element.
 
-      *
 
-      * @return {Element}
 
-      *         The element that gets created.
 
-      */
 
-     createEl() {
 
-       let el = this.options_.tag;
 
-       // Check if this browser supports moving the element into the box.
 
-       // On the iPhone video will break if you move the element,
 
-       // So we have to create a brand new element.
 
-       // If we ingested the player div, we do not need to move the media element.
 
-       if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
 
-         // If the original tag is still there, clone and remove it.
 
-         if (el) {
 
-           const clone = el.cloneNode(true);
 
-           if (el.parentNode) {
 
-             el.parentNode.insertBefore(clone, el);
 
-           }
 
-           Html5.disposeMediaElement(el);
 
-           el = clone;
 
-         } else {
 
-           el = document.createElement('video');
 
-           // determine if native controls should be used
 
-           const tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
 
-           const attributes = merge$2({}, tagAttributes);
 
-           if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
 
-             delete attributes.controls;
 
-           }
 
-           setAttributes(el, Object.assign(attributes, {
 
-             id: this.options_.techId,
 
-             class: 'vjs-tech'
 
-           }));
 
-         }
 
-         el.playerId = this.options_.playerId;
 
-       }
 
-       if (typeof this.options_.preload !== 'undefined') {
 
-         setAttribute(el, 'preload', this.options_.preload);
 
-       }
 
-       if (this.options_.disablePictureInPicture !== undefined) {
 
-         el.disablePictureInPicture = this.options_.disablePictureInPicture;
 
-       }
 
-       // Update specific tag settings, in case they were overridden
 
-       // `autoplay` has to be *last* so that `muted` and `playsinline` are present
 
-       // when iOS/Safari or other browsers attempt to autoplay.
 
-       const settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
 
-       for (let i = 0; i < settingsAttrs.length; i++) {
 
-         const attr = settingsAttrs[i];
 
-         const value = this.options_[attr];
 
-         if (typeof value !== 'undefined') {
 
-           if (value) {
 
-             setAttribute(el, attr, attr);
 
-           } else {
 
-             removeAttribute(el, attr);
 
-           }
 
-           el[attr] = value;
 
-         }
 
-       }
 
-       return el;
 
-     }
 
-     /**
 
-      * This will be triggered if the loadstart event has already fired, before videojs was
 
-      * ready. Two known examples of when this can happen are:
 
-      * 1. If we're loading the playback object after it has started loading
 
-      * 2. The media is already playing the (often with autoplay on) then
 
-      *
 
-      * This function will fire another loadstart so that videojs can catchup.
 
-      *
 
-      * @fires Tech#loadstart
 
-      *
 
-      * @return {undefined}
 
-      *         returns nothing.
 
-      */
 
-     handleLateInit_(el) {
 
-       if (el.networkState === 0 || el.networkState === 3) {
 
-         // The video element hasn't started loading the source yet
 
-         // or didn't find a source
 
-         return;
 
-       }
 
-       if (el.readyState === 0) {
 
-         // NetworkState is set synchronously BUT loadstart is fired at the
 
-         // end of the current stack, usually before setInterval(fn, 0).
 
-         // So at this point we know loadstart may have already fired or is
 
-         // about to fire, and either way the player hasn't seen it yet.
 
-         // We don't want to fire loadstart prematurely here and cause a
 
-         // double loadstart so we'll wait and see if it happens between now
 
-         // and the next loop, and fire it if not.
 
-         // HOWEVER, we also want to make sure it fires before loadedmetadata
 
-         // which could also happen between now and the next loop, so we'll
 
-         // watch for that also.
 
-         let loadstartFired = false;
 
-         const setLoadstartFired = function () {
 
-           loadstartFired = true;
 
-         };
 
-         this.on('loadstart', setLoadstartFired);
 
-         const triggerLoadstart = function () {
 
-           // We did miss the original loadstart. Make sure the player
 
-           // sees loadstart before loadedmetadata
 
-           if (!loadstartFired) {
 
-             this.trigger('loadstart');
 
-           }
 
-         };
 
-         this.on('loadedmetadata', triggerLoadstart);
 
-         this.ready(function () {
 
-           this.off('loadstart', setLoadstartFired);
 
-           this.off('loadedmetadata', triggerLoadstart);
 
-           if (!loadstartFired) {
 
-             // We did miss the original native loadstart. Fire it now.
 
-             this.trigger('loadstart');
 
-           }
 
-         });
 
-         return;
 
-       }
 
-       // From here on we know that loadstart already fired and we missed it.
 
-       // The other readyState events aren't as much of a problem if we double
 
-       // them, so not going to go to as much trouble as loadstart to prevent
 
-       // that unless we find reason to.
 
-       const eventsToTrigger = ['loadstart'];
 
-       // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
 
-       eventsToTrigger.push('loadedmetadata');
 
-       // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
 
-       if (el.readyState >= 2) {
 
-         eventsToTrigger.push('loadeddata');
 
-       }
 
-       // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
 
-       if (el.readyState >= 3) {
 
-         eventsToTrigger.push('canplay');
 
-       }
 
-       // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
 
-       if (el.readyState >= 4) {
 
-         eventsToTrigger.push('canplaythrough');
 
-       }
 
-       // We still need to give the player time to add event listeners
 
-       this.ready(function () {
 
-         eventsToTrigger.forEach(function (type) {
 
-           this.trigger(type);
 
-         }, this);
 
-       });
 
-     }
 
-     /**
 
-      * Set whether we are scrubbing or not.
 
-      * This is used to decide whether we should use `fastSeek` or not.
 
-      * `fastSeek` is used to provide trick play on Safari browsers.
 
-      *
 
-      * @param {boolean} isScrubbing
 
-      *                  - true for we are currently scrubbing
 
-      *                  - false for we are no longer scrubbing
 
-      */
 
-     setScrubbing(isScrubbing) {
 
-       this.isScrubbing_ = isScrubbing;
 
-     }
 
-     /**
 
-      * Get whether we are scrubbing or not.
 
-      *
 
-      * @return {boolean} isScrubbing
 
-      *                  - true for we are currently scrubbing
 
-      *                  - false for we are no longer scrubbing
 
-      */
 
-     scrubbing() {
 
-       return this.isScrubbing_;
 
-     }
 
-     /**
 
-      * Set current time for the `HTML5` tech.
 
-      *
 
-      * @param {number} seconds
 
-      *        Set the current time of the media to this.
 
-      */
 
-     setCurrentTime(seconds) {
 
-       try {
 
-         if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) {
 
-           this.el_.fastSeek(seconds);
 
-         } else {
 
-           this.el_.currentTime = seconds;
 
-         }
 
-       } catch (e) {
 
-         log$1(e, 'Video is not ready. (Video.js)');
 
-         // this.warning(VideoJS.warnings.videoNotReady);
 
-       }
 
-     }
 
-     /**
 
-      * Get the current duration of the HTML5 media element.
 
-      *
 
-      * @return {number}
 
-      *         The duration of the media or 0 if there is no duration.
 
-      */
 
-     duration() {
 
-       // Android Chrome will report duration as Infinity for VOD HLS until after
 
-       // playback has started, which triggers the live display erroneously.
 
-       // Return NaN if playback has not started and trigger a durationupdate once
 
-       // the duration can be reliably known.
 
-       if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
 
-         // Wait for the first `timeupdate` with currentTime > 0 - there may be
 
-         // several with 0
 
-         const checkProgress = () => {
 
-           if (this.el_.currentTime > 0) {
 
-             // Trigger durationchange for genuinely live video
 
-             if (this.el_.duration === Infinity) {
 
-               this.trigger('durationchange');
 
-             }
 
-             this.off('timeupdate', checkProgress);
 
-           }
 
-         };
 
-         this.on('timeupdate', checkProgress);
 
-         return NaN;
 
-       }
 
-       return this.el_.duration || NaN;
 
-     }
 
-     /**
 
-      * Get the current width of the HTML5 media element.
 
-      *
 
-      * @return {number}
 
-      *         The width of the HTML5 media element.
 
-      */
 
-     width() {
 
-       return this.el_.offsetWidth;
 
-     }
 
-     /**
 
-      * Get the current height of the HTML5 media element.
 
-      *
 
-      * @return {number}
 
-      *         The height of the HTML5 media element.
 
-      */
 
-     height() {
 
-       return this.el_.offsetHeight;
 
-     }
 
-     /**
 
-      * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
 
-      * `fullscreenchange` event.
 
-      *
 
-      * @private
 
-      * @fires fullscreenchange
 
-      * @listens webkitendfullscreen
 
-      * @listens webkitbeginfullscreen
 
-      * @listens webkitbeginfullscreen
 
-      */
 
-     proxyWebkitFullscreen_() {
 
-       if (!('webkitDisplayingFullscreen' in this.el_)) {
 
-         return;
 
-       }
 
-       const endFn = function () {
 
-         this.trigger('fullscreenchange', {
 
-           isFullscreen: false
 
-         });
 
-         // Safari will sometimes set controls on the videoelement when existing fullscreen.
 
-         if (this.el_.controls && !this.options_.nativeControlsForTouch && this.controls()) {
 
-           this.el_.controls = false;
 
-         }
 
-       };
 
-       const beginFn = function () {
 
-         if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
 
-           this.one('webkitendfullscreen', endFn);
 
-           this.trigger('fullscreenchange', {
 
-             isFullscreen: true,
 
-             // set a flag in case another tech triggers fullscreenchange
 
-             nativeIOSFullscreen: true
 
-           });
 
-         }
 
-       };
 
-       this.on('webkitbeginfullscreen', beginFn);
 
-       this.on('dispose', () => {
 
-         this.off('webkitbeginfullscreen', beginFn);
 
-         this.off('webkitendfullscreen', endFn);
 
-       });
 
-     }
 
-     /**
 
-      * Check if fullscreen is supported on the video el.
 
-      *
 
-      * @return {boolean}
 
-      *         - True if fullscreen is supported.
 
-      *         - False if fullscreen is not supported.
 
-      */
 
-     supportsFullScreen() {
 
-       return typeof this.el_.webkitEnterFullScreen === 'function';
 
-     }
 
-     /**
 
-      * Request that the `HTML5` Tech enter fullscreen.
 
-      */
 
-     enterFullScreen() {
 
-       const video = this.el_;
 
-       if (video.paused && video.networkState <= video.HAVE_METADATA) {
 
-         // attempt to prime the video element for programmatic access
 
-         // this isn't necessary on the desktop but shouldn't hurt
 
-         silencePromise(this.el_.play());
 
-         // playing and pausing synchronously during the transition to fullscreen
 
-         // can get iOS ~6.1 devices into a play/pause loop
 
-         this.setTimeout(function () {
 
-           video.pause();
 
-           try {
 
-             video.webkitEnterFullScreen();
 
-           } catch (e) {
 
-             this.trigger('fullscreenerror', e);
 
-           }
 
-         }, 0);
 
-       } else {
 
-         try {
 
-           video.webkitEnterFullScreen();
 
-         } catch (e) {
 
-           this.trigger('fullscreenerror', e);
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Request that the `HTML5` Tech exit fullscreen.
 
-      */
 
-     exitFullScreen() {
 
-       if (!this.el_.webkitDisplayingFullscreen) {
 
-         this.trigger('fullscreenerror', new Error('The video is not fullscreen'));
 
-         return;
 
-       }
 
-       this.el_.webkitExitFullScreen();
 
-     }
 
-     /**
 
-      * Create a floating video window always on top of other windows so that users may
 
-      * continue consuming media while they interact with other content sites, or
 
-      * applications on their device.
 
-      *
 
-      * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
 
-      *
 
-      * @return {Promise}
 
-      *         A promise with a Picture-in-Picture window.
 
-      */
 
-     requestPictureInPicture() {
 
-       return this.el_.requestPictureInPicture();
 
-     }
 
-     /**
 
-      * Native requestVideoFrameCallback if supported by browser/tech, or fallback
 
-      * Don't use rVCF on Safari when DRM is playing, as it doesn't fire
 
-      * Needs to be checked later than the constructor
 
-      * This will be a false positive for clear sources loaded after a Fairplay source
 
-      *
 
-      * @param {function} cb function to call
 
-      * @return {number} id of request
 
-      */
 
-     requestVideoFrameCallback(cb) {
 
-       if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
 
-         return this.el_.requestVideoFrameCallback(cb);
 
-       }
 
-       return super.requestVideoFrameCallback(cb);
 
-     }
 
-     /**
 
-      * Native or fallback requestVideoFrameCallback
 
-      *
 
-      * @param {number} id request id to cancel
 
-      */
 
-     cancelVideoFrameCallback(id) {
 
-       if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
 
-         this.el_.cancelVideoFrameCallback(id);
 
-       } else {
 
-         super.cancelVideoFrameCallback(id);
 
-       }
 
-     }
 
-     /**
 
-      * A getter/setter for the `Html5` Tech's source object.
 
-      * > Note: Please use {@link Html5#setSource}
 
-      *
 
-      * @param {Tech~SourceObject} [src]
 
-      *        The source object you want to set on the `HTML5` techs element.
 
-      *
 
-      * @return {Tech~SourceObject|undefined}
 
-      *         - The current source object when a source is not passed in.
 
-      *         - undefined when setting
 
-      *
 
-      * @deprecated Since version 5.
 
-      */
 
-     src(src) {
 
-       if (src === undefined) {
 
-         return this.el_.src;
 
-       }
 
-       // Setting src through `src` instead of `setSrc` will be deprecated
 
-       this.setSrc(src);
 
-     }
 
-     /**
 
-      * Reset the tech by removing all sources and then calling
 
-      * {@link Html5.resetMediaElement}.
 
-      */
 
-     reset() {
 
-       Html5.resetMediaElement(this.el_);
 
-     }
 
-     /**
 
-      * Get the current source on the `HTML5` Tech. Falls back to returning the source from
 
-      * the HTML5 media element.
 
-      *
 
-      * @return {Tech~SourceObject}
 
-      *         The current source object from the HTML5 tech. With a fallback to the
 
-      *         elements source.
 
-      */
 
-     currentSrc() {
 
-       if (this.currentSource_) {
 
-         return this.currentSource_.src;
 
-       }
 
-       return this.el_.currentSrc;
 
-     }
 
-     /**
 
-      * Set controls attribute for the HTML5 media Element.
 
-      *
 
-      * @param {string} val
 
-      *        Value to set the controls attribute to
 
-      */
 
-     setControls(val) {
 
-       this.el_.controls = !!val;
 
-     }
 
-     /**
 
-      * Create and returns a remote {@link TextTrack} object.
 
-      *
 
-      * @param {string} kind
 
-      *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
 
-      *
 
-      * @param {string} [label]
 
-      *        Label to identify the text track
 
-      *
 
-      * @param {string} [language]
 
-      *        Two letter language abbreviation
 
-      *
 
-      * @return {TextTrack}
 
-      *         The TextTrack that gets created.
 
-      */
 
-     addTextTrack(kind, label, language) {
 
-       if (!this.featuresNativeTextTracks) {
 
-         return super.addTextTrack(kind, label, language);
 
-       }
 
-       return this.el_.addTextTrack(kind, label, language);
 
-     }
 
-     /**
 
-      * Creates either native TextTrack or an emulated TextTrack depending
 
-      * on the value of `featuresNativeTextTracks`
 
-      *
 
-      * @param {Object} options
 
-      *        The object should contain the options to initialize the TextTrack with.
 
-      *
 
-      * @param {string} [options.kind]
 
-      *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
 
-      *
 
-      * @param {string} [options.label]
 
-      *        Label to identify the text track
 
-      *
 
-      * @param {string} [options.language]
 
-      *        Two letter language abbreviation.
 
-      *
 
-      * @param {boolean} [options.default]
 
-      *        Default this track to on.
 
-      *
 
-      * @param {string} [options.id]
 
-      *        The internal id to assign this track.
 
-      *
 
-      * @param {string} [options.src]
 
-      *        A source url for the track.
 
-      *
 
-      * @return {HTMLTrackElement}
 
-      *         The track element that gets created.
 
-      */
 
-     createRemoteTextTrack(options) {
 
-       if (!this.featuresNativeTextTracks) {
 
-         return super.createRemoteTextTrack(options);
 
-       }
 
-       const htmlTrackElement = document.createElement('track');
 
-       if (options.kind) {
 
-         htmlTrackElement.kind = options.kind;
 
-       }
 
-       if (options.label) {
 
-         htmlTrackElement.label = options.label;
 
-       }
 
-       if (options.language || options.srclang) {
 
-         htmlTrackElement.srclang = options.language || options.srclang;
 
-       }
 
-       if (options.default) {
 
-         htmlTrackElement.default = options.default;
 
-       }
 
-       if (options.id) {
 
-         htmlTrackElement.id = options.id;
 
-       }
 
-       if (options.src) {
 
-         htmlTrackElement.src = options.src;
 
-       }
 
-       return htmlTrackElement;
 
-     }
 
-     /**
 
-      * Creates a remote text track object and returns an html track element.
 
-      *
 
-      * @param {Object} options The object should contain values for
 
-      * kind, language, label, and src (location of the WebVTT file)
 
-      * @param {boolean} [manualCleanup=false] if set to true, the TextTrack
 
-      * will not be removed from the TextTrackList and HtmlTrackElementList
 
-      * after a source change
 
-      * @return {HTMLTrackElement} An Html Track Element.
 
-      * This can be an emulated {@link HTMLTrackElement} or a native one.
 
-      *
 
-      */
 
-     addRemoteTextTrack(options, manualCleanup) {
 
-       const htmlTrackElement = super.addRemoteTextTrack(options, manualCleanup);
 
-       if (this.featuresNativeTextTracks) {
 
-         this.el().appendChild(htmlTrackElement);
 
-       }
 
-       return htmlTrackElement;
 
-     }
 
-     /**
 
-      * Remove remote `TextTrack` from `TextTrackList` object
 
-      *
 
-      * @param {TextTrack} track
 
-      *        `TextTrack` object to remove
 
-      */
 
-     removeRemoteTextTrack(track) {
 
-       super.removeRemoteTextTrack(track);
 
-       if (this.featuresNativeTextTracks) {
 
-         const tracks = this.$$('track');
 
-         let i = tracks.length;
 
-         while (i--) {
 
-           if (track === tracks[i] || track === tracks[i].track) {
 
-             this.el().removeChild(tracks[i]);
 
-           }
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Gets available media playback quality metrics as specified by the W3C's Media
 
-      * Playback Quality API.
 
-      *
 
-      * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
 
-      *
 
-      * @return {Object}
 
-      *         An object with supported media playback quality metrics
 
-      */
 
-     getVideoPlaybackQuality() {
 
-       if (typeof this.el().getVideoPlaybackQuality === 'function') {
 
-         return this.el().getVideoPlaybackQuality();
 
-       }
 
-       const videoPlaybackQuality = {};
 
-       if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
 
-         videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
 
-         videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
 
-       }
 
-       if (window.performance) {
 
-         videoPlaybackQuality.creationTime = window.performance.now();
 
-       }
 
-       return videoPlaybackQuality;
 
-     }
 
-   }
 
-   /* HTML5 Support Testing ---------------------------------------------------- */
 
-   /**
 
-    * Element for testing browser HTML5 media capabilities
 
-    *
 
-    * @type {Element}
 
-    * @constant
 
-    * @private
 
-    */
 
-   defineLazyProperty(Html5, 'TEST_VID', function () {
 
-     if (!isReal()) {
 
-       return;
 
-     }
 
-     const video = document.createElement('video');
 
-     const track = document.createElement('track');
 
-     track.kind = 'captions';
 
-     track.srclang = 'en';
 
-     track.label = 'English';
 
-     video.appendChild(track);
 
-     return video;
 
-   });
 
-   /**
 
-    * Check if HTML5 media is supported by this browser/device.
 
-    *
 
-    * @return {boolean}
 
-    *         - True if HTML5 media is supported.
 
-    *         - False if HTML5 media is not supported.
 
-    */
 
-   Html5.isSupported = function () {
 
-     // IE with no Media Player is a LIAR! (#984)
 
-     try {
 
-       Html5.TEST_VID.volume = 0.5;
 
-     } catch (e) {
 
-       return false;
 
-     }
 
-     return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
 
-   };
 
-   /**
 
-    * Check if the tech can support the given type
 
-    *
 
-    * @param {string} type
 
-    *        The mimetype to check
 
-    * @return {string} 'probably', 'maybe', or '' (empty string)
 
-    */
 
-   Html5.canPlayType = function (type) {
 
-     return Html5.TEST_VID.canPlayType(type);
 
-   };
 
-   /**
 
-    * Check if the tech can support the given source
 
-    *
 
-    * @param {Object} srcObj
 
-    *        The source object
 
-    * @param {Object} options
 
-    *        The options passed to the tech
 
-    * @return {string} 'probably', 'maybe', or '' (empty string)
 
-    */
 
-   Html5.canPlaySource = function (srcObj, options) {
 
-     return Html5.canPlayType(srcObj.type);
 
-   };
 
-   /**
 
-    * Check if the volume can be changed in this browser/device.
 
-    * Volume cannot be changed in a lot of mobile devices.
 
-    * Specifically, it can't be changed from 1 on iOS.
 
-    *
 
-    * @return {boolean}
 
-    *         - True if volume can be controlled
 
-    *         - False otherwise
 
-    */
 
-   Html5.canControlVolume = function () {
 
-     // IE will error if Windows Media Player not installed #3315
 
-     try {
 
-       const volume = Html5.TEST_VID.volume;
 
-       Html5.TEST_VID.volume = volume / 2 + 0.1;
 
-       const canControl = volume !== Html5.TEST_VID.volume;
 
-       // With the introduction of iOS 15, there are cases where the volume is read as
 
-       // changed but reverts back to its original state at the start of the next tick.
 
-       // To determine whether volume can be controlled on iOS,
 
-       // a timeout is set and the volume is checked asynchronously.
 
-       // Since `features` doesn't currently work asynchronously, the value is manually set.
 
-       if (canControl && IS_IOS) {
 
-         window.setTimeout(() => {
 
-           if (Html5 && Html5.prototype) {
 
-             Html5.prototype.featuresVolumeControl = volume !== Html5.TEST_VID.volume;
 
-           }
 
-         });
 
-         // default iOS to false, which will be updated in the timeout above.
 
-         return false;
 
-       }
 
-       return canControl;
 
-     } catch (e) {
 
-       return false;
 
-     }
 
-   };
 
-   /**
 
-    * Check if the volume can be muted in this browser/device.
 
-    * Some devices, e.g. iOS, don't allow changing volume
 
-    * but permits muting/unmuting.
 
-    *
 
-    * @return {boolean}
 
-    *      - True if volume can be muted
 
-    *      - False otherwise
 
-    */
 
-   Html5.canMuteVolume = function () {
 
-     try {
 
-       const muted = Html5.TEST_VID.muted;
 
-       // in some versions of iOS muted property doesn't always
 
-       // work, so we want to set both property and attribute
 
-       Html5.TEST_VID.muted = !muted;
 
-       if (Html5.TEST_VID.muted) {
 
-         setAttribute(Html5.TEST_VID, 'muted', 'muted');
 
-       } else {
 
-         removeAttribute(Html5.TEST_VID, 'muted', 'muted');
 
-       }
 
-       return muted !== Html5.TEST_VID.muted;
 
-     } catch (e) {
 
-       return false;
 
-     }
 
-   };
 
-   /**
 
-    * Check if the playback rate can be changed in this browser/device.
 
-    *
 
-    * @return {boolean}
 
-    *         - True if playback rate can be controlled
 
-    *         - False otherwise
 
-    */
 
-   Html5.canControlPlaybackRate = function () {
 
-     // Playback rate API is implemented in Android Chrome, but doesn't do anything
 
-     // https://github.com/videojs/video.js/issues/3180
 
-     if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
 
-       return false;
 
-     }
 
-     // IE will error if Windows Media Player not installed #3315
 
-     try {
 
-       const playbackRate = Html5.TEST_VID.playbackRate;
 
-       Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
 
-       return playbackRate !== Html5.TEST_VID.playbackRate;
 
-     } catch (e) {
 
-       return false;
 
-     }
 
-   };
 
-   /**
 
-    * Check if we can override a video/audio elements attributes, with
 
-    * Object.defineProperty.
 
-    *
 
-    * @return {boolean}
 
-    *         - True if builtin attributes can be overridden
 
-    *         - False otherwise
 
-    */
 
-   Html5.canOverrideAttributes = function () {
 
-     // if we cannot overwrite the src/innerHTML property, there is no support
 
-     // iOS 7 safari for instance cannot do this.
 
-     try {
 
-       const noop = () => {};
 
-       Object.defineProperty(document.createElement('video'), 'src', {
 
-         get: noop,
 
-         set: noop
 
-       });
 
-       Object.defineProperty(document.createElement('audio'), 'src', {
 
-         get: noop,
 
-         set: noop
 
-       });
 
-       Object.defineProperty(document.createElement('video'), 'innerHTML', {
 
-         get: noop,
 
-         set: noop
 
-       });
 
-       Object.defineProperty(document.createElement('audio'), 'innerHTML', {
 
-         get: noop,
 
-         set: noop
 
-       });
 
-     } catch (e) {
 
-       return false;
 
-     }
 
-     return true;
 
-   };
 
-   /**
 
-    * Check to see if native `TextTrack`s are supported by this browser/device.
 
-    *
 
-    * @return {boolean}
 
-    *         - True if native `TextTrack`s are supported.
 
-    *         - False otherwise
 
-    */
 
-   Html5.supportsNativeTextTracks = function () {
 
-     return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
 
-   };
 
-   /**
 
-    * Check to see if native `VideoTrack`s are supported by this browser/device
 
-    *
 
-    * @return {boolean}
 
-    *        - True if native `VideoTrack`s are supported.
 
-    *        - False otherwise
 
-    */
 
-   Html5.supportsNativeVideoTracks = function () {
 
-     return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
 
-   };
 
-   /**
 
-    * Check to see if native `AudioTrack`s are supported by this browser/device
 
-    *
 
-    * @return {boolean}
 
-    *        - True if native `AudioTrack`s are supported.
 
-    *        - False otherwise
 
-    */
 
-   Html5.supportsNativeAudioTracks = function () {
 
-     return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
 
-   };
 
-   /**
 
-    * An array of events available on the Html5 tech.
 
-    *
 
-    * @private
 
-    * @type {Array}
 
-    */
 
-   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'];
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports volume control.
 
-    *
 
-    * @type {boolean}
 
-    * @default {@link Html5.canControlVolume}
 
-    */
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports muting volume.
 
-    *
 
-    * @type {boolean}
 
-    * @default {@link Html5.canMuteVolume}
 
-    */
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports changing the speed at which the media
 
-    * plays. Examples:
 
-    *   - Set player to play 2x (twice) as fast
 
-    *   - Set player to play 0.5x (half) as fast
 
-    *
 
-    * @type {boolean}
 
-    * @default {@link Html5.canControlPlaybackRate}
 
-    */
 
-   /**
 
-    * Boolean indicating whether the `Tech` supports the `sourceset` event.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   /**
 
-    * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
 
-    *
 
-    * @type {boolean}
 
-    * @default {@link Html5.supportsNativeTextTracks}
 
-    */
 
-   /**
 
-    * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
 
-    *
 
-    * @type {boolean}
 
-    * @default {@link Html5.supportsNativeVideoTracks}
 
-    */
 
-   /**
 
-    * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
 
-    *
 
-    * @type {boolean}
 
-    * @default {@link Html5.supportsNativeAudioTracks}
 
-    */
 
-   [['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function ([key, fn]) {
 
-     defineLazyProperty(Html5.prototype, key, () => Html5[fn](), true);
 
-   });
 
-   Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
 
-   /**
 
-    * Boolean indicating whether the `HTML5` tech currently supports the media element
 
-    * moving in the DOM. iOS breaks if you move the media element, so this is set this to
 
-    * false there. Everywhere else this should be true.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Html5.prototype.movingMediaElementInDOM = !IS_IOS;
 
-   // TODO: Previous comment: No longer appears to be used. Can probably be removed.
 
-   //       Is this true?
 
-   /**
 
-    * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
 
-    * when going into fullscreen.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Html5.prototype.featuresFullscreenResize = true;
 
-   /**
 
-    * Boolean indicating whether the `HTML5` tech currently supports the progress event.
 
-    * If this is false, manual `progress` events will be triggered instead.
 
-    *
 
-    * @type {boolean}
 
-    * @default
 
-    */
 
-   Html5.prototype.featuresProgressEvents = true;
 
-   /**
 
-    * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
 
-    * If this is false, manual `timeupdate` events will be triggered instead.
 
-    *
 
-    * @default
 
-    */
 
-   Html5.prototype.featuresTimeupdateEvents = true;
 
-   /**
 
-    * Whether the HTML5 el supports `requestVideoFrameCallback`
 
-    *
 
-    * @type {boolean}
 
-    */
 
-   Html5.prototype.featuresVideoFrameCallback = !!(Html5.TEST_VID && Html5.TEST_VID.requestVideoFrameCallback);
 
-   Html5.disposeMediaElement = function (el) {
 
-     if (!el) {
 
-       return;
 
-     }
 
-     if (el.parentNode) {
 
-       el.parentNode.removeChild(el);
 
-     }
 
-     // remove any child track or source nodes to prevent their loading
 
-     while (el.hasChildNodes()) {
 
-       el.removeChild(el.firstChild);
 
-     }
 
-     // remove any src reference. not setting `src=''` because that causes a warning
 
-     // in firefox
 
-     el.removeAttribute('src');
 
-     // force the media element to update its loading state by calling load()
 
-     // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
 
-     if (typeof el.load === 'function') {
 
-       // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
 
-       (function () {
 
-         try {
 
-           el.load();
 
-         } catch (e) {
 
-           // not supported
 
-         }
 
-       })();
 
-     }
 
-   };
 
-   Html5.resetMediaElement = function (el) {
 
-     if (!el) {
 
-       return;
 
-     }
 
-     const sources = el.querySelectorAll('source');
 
-     let i = sources.length;
 
-     while (i--) {
 
-       el.removeChild(sources[i]);
 
-     }
 
-     // remove any src reference.
 
-     // not setting `src=''` because that throws an error
 
-     el.removeAttribute('src');
 
-     if (typeof el.load === 'function') {
 
-       // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
 
-       (function () {
 
-         try {
 
-           el.load();
 
-         } catch (e) {
 
-           // satisfy linter
 
-         }
 
-       })();
 
-     }
 
-   };
 
-   /* Native HTML5 element property wrapping ----------------------------------- */
 
-   // Wrap native boolean attributes with getters that check both property and attribute
 
-   // The list is as followed:
 
-   // muted, defaultMuted, autoplay, controls, loop, playsinline
 
-   [
 
-   /**
 
-    * Get the value of `muted` from the media element. `muted` indicates
 
-    * that the volume for the media should be set to silent. This does not actually change
 
-    * the `volume` attribute.
 
-    *
 
-    * @method Html5#muted
 
-    * @return {boolean}
 
-    *         - True if the value of `volume` should be ignored and the audio set to silent.
 
-    *         - False if the value of `volume` should be used.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
 
-    */
 
-   'muted',
 
-   /**
 
-    * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
 
-    * whether the media should start muted or not. Only changes the default state of the
 
-    * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
 
-    * current state.
 
-    *
 
-    * @method Html5#defaultMuted
 
-    * @return {boolean}
 
-    *         - The value of `defaultMuted` from the media element.
 
-    *         - True indicates that the media should start muted.
 
-    *         - False indicates that the media should not start muted
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
 
-    */
 
-   'defaultMuted',
 
-   /**
 
-    * Get the value of `autoplay` from the media element. `autoplay` indicates
 
-    * that the media should start to play as soon as the page is ready.
 
-    *
 
-    * @method Html5#autoplay
 
-    * @return {boolean}
 
-    *         - The value of `autoplay` from the media element.
 
-    *         - True indicates that the media should start as soon as the page loads.
 
-    *         - False indicates that the media should not start as soon as the page loads.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
 
-    */
 
-   'autoplay',
 
-   /**
 
-    * Get the value of `controls` from the media element. `controls` indicates
 
-    * whether the native media controls should be shown or hidden.
 
-    *
 
-    * @method Html5#controls
 
-    * @return {boolean}
 
-    *         - The value of `controls` from the media element.
 
-    *         - True indicates that native controls should be showing.
 
-    *         - False indicates that native controls should be hidden.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
 
-    */
 
-   'controls',
 
-   /**
 
-    * Get the value of `loop` from the media element. `loop` indicates
 
-    * that the media should return to the start of the media and continue playing once
 
-    * it reaches the end.
 
-    *
 
-    * @method Html5#loop
 
-    * @return {boolean}
 
-    *         - The value of `loop` from the media element.
 
-    *         - True indicates that playback should seek back to start once
 
-    *           the end of a media is reached.
 
-    *         - False indicates that playback should not loop back to the start when the
 
-    *           end of the media is reached.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
 
-    */
 
-   'loop',
 
-   /**
 
-    * Get the value of `playsinline` from the media element. `playsinline` indicates
 
-    * to the browser that non-fullscreen playback is preferred when fullscreen
 
-    * playback is the native default, such as in iOS Safari.
 
-    *
 
-    * @method Html5#playsinline
 
-    * @return {boolean}
 
-    *         - The value of `playsinline` from the media element.
 
-    *         - True indicates that the media should play inline.
 
-    *         - False indicates that the media should not play inline.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
 
-    */
 
-   'playsinline'].forEach(function (prop) {
 
-     Html5.prototype[prop] = function () {
 
-       return this.el_[prop] || this.el_.hasAttribute(prop);
 
-     };
 
-   });
 
-   // Wrap native boolean attributes with setters that set both property and attribute
 
-   // The list is as followed:
 
-   // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
 
-   // setControls is special-cased above
 
-   [
 
-   /**
 
-    * Set the value of `muted` on the media element. `muted` indicates that the current
 
-    * audio level should be silent.
 
-    *
 
-    * @method Html5#setMuted
 
-    * @param {boolean} muted
 
-    *        - True if the audio should be set to silent
 
-    *        - False otherwise
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
 
-    */
 
-   'muted',
 
-   /**
 
-    * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
 
-    * audio level should be silent, but will only effect the muted level on initial playback..
 
-    *
 
-    * @method Html5.prototype.setDefaultMuted
 
-    * @param {boolean} defaultMuted
 
-    *        - True if the audio should be set to silent
 
-    *        - False otherwise
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
 
-    */
 
-   'defaultMuted',
 
-   /**
 
-    * Set the value of `autoplay` on the media element. `autoplay` indicates
 
-    * that the media should start to play as soon as the page is ready.
 
-    *
 
-    * @method Html5#setAutoplay
 
-    * @param {boolean} autoplay
 
-    *         - True indicates that the media should start as soon as the page loads.
 
-    *         - False indicates that the media should not start as soon as the page loads.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
 
-    */
 
-   'autoplay',
 
-   /**
 
-    * Set the value of `loop` on the media element. `loop` indicates
 
-    * that the media should return to the start of the media and continue playing once
 
-    * it reaches the end.
 
-    *
 
-    * @method Html5#setLoop
 
-    * @param {boolean} loop
 
-    *         - True indicates that playback should seek back to start once
 
-    *           the end of a media is reached.
 
-    *         - False indicates that playback should not loop back to the start when the
 
-    *           end of the media is reached.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
 
-    */
 
-   'loop',
 
-   /**
 
-    * Set the value of `playsinline` from the media element. `playsinline` indicates
 
-    * to the browser that non-fullscreen playback is preferred when fullscreen
 
-    * playback is the native default, such as in iOS Safari.
 
-    *
 
-    * @method Html5#setPlaysinline
 
-    * @param {boolean} playsinline
 
-    *         - True indicates that the media should play inline.
 
-    *         - False indicates that the media should not play inline.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
 
-    */
 
-   'playsinline'].forEach(function (prop) {
 
-     Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
 
-       this.el_[prop] = v;
 
-       if (v) {
 
-         this.el_.setAttribute(prop, prop);
 
-       } else {
 
-         this.el_.removeAttribute(prop);
 
-       }
 
-     };
 
-   });
 
-   // Wrap native properties with a getter
 
-   // The list is as followed
 
-   // paused, currentTime, buffered, volume, poster, preload, error, seeking
 
-   // seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
 
-   // played, networkState, readyState, videoWidth, videoHeight, crossOrigin
 
-   [
 
-   /**
 
-    * Get the value of `paused` from the media element. `paused` indicates whether the media element
 
-    * is currently paused or not.
 
-    *
 
-    * @method Html5#paused
 
-    * @return {boolean}
 
-    *         The value of `paused` from the media element.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
 
-    */
 
-   'paused',
 
-   /**
 
-    * Get the value of `currentTime` from the media element. `currentTime` indicates
 
-    * the current second that the media is at in playback.
 
-    *
 
-    * @method Html5#currentTime
 
-    * @return {number}
 
-    *         The value of `currentTime` from the media element.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
 
-    */
 
-   'currentTime',
 
-   /**
 
-    * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
 
-    * object that represents the parts of the media that are already downloaded and
 
-    * available for playback.
 
-    *
 
-    * @method Html5#buffered
 
-    * @return {TimeRange}
 
-    *         The value of `buffered` from the media element.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
 
-    */
 
-   'buffered',
 
-   /**
 
-    * Get the value of `volume` from the media element. `volume` indicates
 
-    * the current playback volume of audio for a media. `volume` will be a value from 0
 
-    * (silent) to 1 (loudest and default).
 
-    *
 
-    * @method Html5#volume
 
-    * @return {number}
 
-    *         The value of `volume` from the media element. Value will be between 0-1.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
 
-    */
 
-   'volume',
 
-   /**
 
-    * Get the value of `poster` from the media element. `poster` indicates
 
-    * that the url of an image file that can/will be shown when no media data is available.
 
-    *
 
-    * @method Html5#poster
 
-    * @return {string}
 
-    *         The value of `poster` from the media element. Value will be a url to an
 
-    *         image.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
 
-    */
 
-   'poster',
 
-   /**
 
-    * Get the value of `preload` from the media element. `preload` indicates
 
-    * what should download before the media is interacted with. It can have the following
 
-    * values:
 
-    * - none: nothing should be downloaded
 
-    * - metadata: poster and the first few frames of the media may be downloaded to get
 
-    *   media dimensions and other metadata
 
-    * - auto: allow the media and metadata for the media to be downloaded before
 
-    *    interaction
 
-    *
 
-    * @method Html5#preload
 
-    * @return {string}
 
-    *         The value of `preload` from the media element. Will be 'none', 'metadata',
 
-    *         or 'auto'.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
 
-    */
 
-   'preload',
 
-   /**
 
-    * Get the value of the `error` from the media element. `error` indicates any
 
-    * MediaError that may have occurred during playback. If error returns null there is no
 
-    * current error.
 
-    *
 
-    * @method Html5#error
 
-    * @return {MediaError|null}
 
-    *         The value of `error` from the media element. Will be `MediaError` if there
 
-    *         is a current error and null otherwise.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
 
-    */
 
-   'error',
 
-   /**
 
-    * Get the value of `seeking` from the media element. `seeking` indicates whether the
 
-    * media is currently seeking to a new position or not.
 
-    *
 
-    * @method Html5#seeking
 
-    * @return {boolean}
 
-    *         - The value of `seeking` from the media element.
 
-    *         - True indicates that the media is currently seeking to a new position.
 
-    *         - False indicates that the media is not seeking to a new position at this time.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
 
-    */
 
-   'seeking',
 
-   /**
 
-    * Get the value of `seekable` from the media element. `seekable` returns a
 
-    * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
 
-    *
 
-    * @method Html5#seekable
 
-    * @return {TimeRange}
 
-    *         The value of `seekable` from the media element. A `TimeRange` object
 
-    *         indicating the current ranges of time that can be seeked to.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
 
-    */
 
-   'seekable',
 
-   /**
 
-    * Get the value of `ended` from the media element. `ended` indicates whether
 
-    * the media has reached the end or not.
 
-    *
 
-    * @method Html5#ended
 
-    * @return {boolean}
 
-    *         - The value of `ended` from the media element.
 
-    *         - True indicates that the media has ended.
 
-    *         - False indicates that the media has not ended.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
 
-    */
 
-   'ended',
 
-   /**
 
-    * Get the value of `playbackRate` from the media element. `playbackRate` indicates
 
-    * the rate at which the media is currently playing back. Examples:
 
-    *   - if playbackRate is set to 2, media will play twice as fast.
 
-    *   - if playbackRate is set to 0.5, media will play half as fast.
 
-    *
 
-    * @method Html5#playbackRate
 
-    * @return {number}
 
-    *         The value of `playbackRate` from the media element. A number indicating
 
-    *         the current playback speed of the media, where 1 is normal speed.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
 
-    */
 
-   'playbackRate',
 
-   /**
 
-    * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
 
-    * the rate at which the media is currently playing back. This value will not indicate the current
 
-    * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
 
-    *
 
-    * Examples:
 
-    *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
 
-    *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
 
-    *
 
-    * @method Html5.prototype.defaultPlaybackRate
 
-    * @return {number}
 
-    *         The value of `defaultPlaybackRate` from the media element. A number indicating
 
-    *         the current playback speed of the media, where 1 is normal speed.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
 
-    */
 
-   'defaultPlaybackRate',
 
-   /**
 
-    * Get the value of 'disablePictureInPicture' from the video element.
 
-    *
 
-    * @method Html5#disablePictureInPicture
 
-    * @return {boolean} value
 
-    *         - The value of `disablePictureInPicture` from the video element.
 
-    *         - True indicates that the video can't be played in Picture-In-Picture mode
 
-    *         - False indicates that the video can be played in Picture-In-Picture mode
 
-    *
 
-    * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
 
-    */
 
-   'disablePictureInPicture',
 
-   /**
 
-    * Get the value of `played` from the media element. `played` returns a `TimeRange`
 
-    * object representing points in the media timeline that have been played.
 
-    *
 
-    * @method Html5#played
 
-    * @return {TimeRange}
 
-    *         The value of `played` from the media element. A `TimeRange` object indicating
 
-    *         the ranges of time that have been played.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
 
-    */
 
-   'played',
 
-   /**
 
-    * Get the value of `networkState` from the media element. `networkState` indicates
 
-    * the current network state. It returns an enumeration from the following list:
 
-    * - 0: NETWORK_EMPTY
 
-    * - 1: NETWORK_IDLE
 
-    * - 2: NETWORK_LOADING
 
-    * - 3: NETWORK_NO_SOURCE
 
-    *
 
-    * @method Html5#networkState
 
-    * @return {number}
 
-    *         The value of `networkState` from the media element. This will be a number
 
-    *         from the list in the description.
 
-    *
 
-    * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
 
-    */
 
-   'networkState',
 
-   /**
 
-    * Get the value of `readyState` from the media element. `readyState` indicates
 
-    * the current state of the media element. It returns an enumeration from the
 
-    * following list:
 
-    * - 0: HAVE_NOTHING
 
-    * - 1: HAVE_METADATA
 
-    * - 2: HAVE_CURRENT_DATA
 
-    * - 3: HAVE_FUTURE_DATA
 
-    * - 4: HAVE_ENOUGH_DATA
 
-    *
 
-    * @method Html5#readyState
 
-    * @return {number}
 
-    *         The value of `readyState` from the media element. This will be a number
 
-    *         from the list in the description.
 
-    *
 
-    * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
 
-    */
 
-   'readyState',
 
-   /**
 
-    * Get the value of `videoWidth` from the video element. `videoWidth` indicates
 
-    * the current width of the video in css pixels.
 
-    *
 
-    * @method Html5#videoWidth
 
-    * @return {number}
 
-    *         The value of `videoWidth` from the video element. This will be a number
 
-    *         in css pixels.
 
-    *
 
-    * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
 
-    */
 
-   'videoWidth',
 
-   /**
 
-    * Get the value of `videoHeight` from the video element. `videoHeight` indicates
 
-    * the current height of the video in css pixels.
 
-    *
 
-    * @method Html5#videoHeight
 
-    * @return {number}
 
-    *         The value of `videoHeight` from the video element. This will be a number
 
-    *         in css pixels.
 
-    *
 
-    * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
 
-    */
 
-   'videoHeight',
 
-   /**
 
-    * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
 
-    * to the browser that should sent the cookies along with the requests for the
 
-    * different assets/playlists
 
-    *
 
-    * @method Html5#crossOrigin
 
-    * @return {string}
 
-    *         - anonymous indicates that the media should not sent cookies.
 
-    *         - use-credentials indicates that the media should sent cookies along the requests.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
 
-    */
 
-   'crossOrigin'].forEach(function (prop) {
 
-     Html5.prototype[prop] = function () {
 
-       return this.el_[prop];
 
-     };
 
-   });
 
-   // Wrap native properties with a setter in this format:
 
-   // set + toTitleCase(name)
 
-   // The list is as follows:
 
-   // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
 
-   // setDisablePictureInPicture, setCrossOrigin
 
-   [
 
-   /**
 
-    * Set the value of `volume` on the media element. `volume` indicates the current
 
-    * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
 
-    * so on.
 
-    *
 
-    * @method Html5#setVolume
 
-    * @param {number} percentAsDecimal
 
-    *        The volume percent as a decimal. Valid range is from 0-1.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
 
-    */
 
-   'volume',
 
-   /**
 
-    * Set the value of `src` on the media element. `src` indicates the current
 
-    * {@link Tech~SourceObject} for the media.
 
-    *
 
-    * @method Html5#setSrc
 
-    * @param {Tech~SourceObject} src
 
-    *        The source object to set as the current source.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
 
-    */
 
-   'src',
 
-   /**
 
-    * Set the value of `poster` on the media element. `poster` is the url to
 
-    * an image file that can/will be shown when no media data is available.
 
-    *
 
-    * @method Html5#setPoster
 
-    * @param {string} poster
 
-    *        The url to an image that should be used as the `poster` for the media
 
-    *        element.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
 
-    */
 
-   'poster',
 
-   /**
 
-    * Set the value of `preload` on the media element. `preload` indicates
 
-    * what should download before the media is interacted with. It can have the following
 
-    * values:
 
-    * - none: nothing should be downloaded
 
-    * - metadata: poster and the first few frames of the media may be downloaded to get
 
-    *   media dimensions and other metadata
 
-    * - auto: allow the media and metadata for the media to be downloaded before
 
-    *    interaction
 
-    *
 
-    * @method Html5#setPreload
 
-    * @param {string} preload
 
-    *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
 
-    *         or 'auto'.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
 
-    */
 
-   'preload',
 
-   /**
 
-    * Set the value of `playbackRate` on the media element. `playbackRate` indicates
 
-    * the rate at which the media should play back. Examples:
 
-    *   - if playbackRate is set to 2, media will play twice as fast.
 
-    *   - if playbackRate is set to 0.5, media will play half as fast.
 
-    *
 
-    * @method Html5#setPlaybackRate
 
-    * @return {number}
 
-    *         The value of `playbackRate` from the media element. A number indicating
 
-    *         the current playback speed of the media, where 1 is normal speed.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
 
-    */
 
-   'playbackRate',
 
-   /**
 
-    * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
 
-    * the rate at which the media should play back upon initial startup. Changing this value
 
-    * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
 
-    *
 
-    * Example Values:
 
-    *   - if playbackRate is set to 2, media will play twice as fast.
 
-    *   - if playbackRate is set to 0.5, media will play half as fast.
 
-    *
 
-    * @method Html5.prototype.setDefaultPlaybackRate
 
-    * @return {number}
 
-    *         The value of `defaultPlaybackRate` from the media element. A number indicating
 
-    *         the current playback speed of the media, where 1 is normal speed.
 
-    *
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
 
-    */
 
-   'defaultPlaybackRate',
 
-   /**
 
-    * Prevents the browser from suggesting a Picture-in-Picture context menu
 
-    * or to request Picture-in-Picture automatically in some cases.
 
-    *
 
-    * @method Html5#setDisablePictureInPicture
 
-    * @param {boolean} value
 
-    *         The true value will disable Picture-in-Picture mode.
 
-    *
 
-    * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
 
-    */
 
-   'disablePictureInPicture',
 
-   /**
 
-    * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
 
-    * to the browser that should sent the cookies along with the requests for the
 
-    * different assets/playlists
 
-    *
 
-    * @method Html5#setCrossOrigin
 
-    * @param {string} crossOrigin
 
-    *         - anonymous indicates that the media should not sent cookies.
 
-    *         - use-credentials indicates that the media should sent cookies along the requests.
 
-    *
 
-    * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
 
-    */
 
-   'crossOrigin'].forEach(function (prop) {
 
-     Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
 
-       this.el_[prop] = v;
 
-     };
 
-   });
 
-   // wrap native functions with a function
 
-   // The list is as follows:
 
-   // pause, load, play
 
-   [
 
-   /**
 
-    * A wrapper around the media elements `pause` function. This will call the `HTML5`
 
-    * media elements `pause` function.
 
-    *
 
-    * @method Html5#pause
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
 
-    */
 
-   'pause',
 
-   /**
 
-    * A wrapper around the media elements `load` function. This will call the `HTML5`s
 
-    * media element `load` function.
 
-    *
 
-    * @method Html5#load
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
 
-    */
 
-   'load',
 
-   /**
 
-    * A wrapper around the media elements `play` function. This will call the `HTML5`s
 
-    * media element `play` function.
 
-    *
 
-    * @method Html5#play
 
-    * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
 
-    */
 
-   'play'].forEach(function (prop) {
 
-     Html5.prototype[prop] = function () {
 
-       return this.el_[prop]();
 
-     };
 
-   });
 
-   Tech.withSourceHandlers(Html5);
 
-   /**
 
-    * Native source handler for Html5, simply passes the source to the media element.
 
-    *
 
-    * @property {Tech~SourceObject} source
 
-    *        The source object
 
-    *
 
-    * @property {Html5} tech
 
-    *        The instance of the HTML5 tech.
 
-    */
 
-   Html5.nativeSourceHandler = {};
 
-   /**
 
-    * Check if the media element can play the given mime type.
 
-    *
 
-    * @param {string} type
 
-    *        The mimetype to check
 
-    *
 
-    * @return {string}
 
-    *         'probably', 'maybe', or '' (empty string)
 
-    */
 
-   Html5.nativeSourceHandler.canPlayType = function (type) {
 
-     // IE without MediaPlayer throws an error (#519)
 
-     try {
 
-       return Html5.TEST_VID.canPlayType(type);
 
-     } catch (e) {
 
-       return '';
 
-     }
 
-   };
 
-   /**
 
-    * Check if the media element can handle a source natively.
 
-    *
 
-    * @param {Tech~SourceObject} source
 
-    *         The source object
 
-    *
 
-    * @param {Object} [options]
 
-    *         Options to be passed to the tech.
 
-    *
 
-    * @return {string}
 
-    *         'probably', 'maybe', or '' (empty string).
 
-    */
 
-   Html5.nativeSourceHandler.canHandleSource = function (source, options) {
 
-     // If a type was provided we should rely on that
 
-     if (source.type) {
 
-       return Html5.nativeSourceHandler.canPlayType(source.type);
 
-       // If no type, fall back to checking 'video/[EXTENSION]'
 
-     } else if (source.src) {
 
-       const ext = getFileExtension(source.src);
 
-       return Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
 
-     }
 
-     return '';
 
-   };
 
-   /**
 
-    * Pass the source to the native media element.
 
-    *
 
-    * @param {Tech~SourceObject} source
 
-    *        The source object
 
-    *
 
-    * @param {Html5} tech
 
-    *        The instance of the Html5 tech
 
-    *
 
-    * @param {Object} [options]
 
-    *        The options to pass to the source
 
-    */
 
-   Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
 
-     tech.setSrc(source.src);
 
-   };
 
-   /**
 
-    * A noop for the native dispose function, as cleanup is not needed.
 
-    */
 
-   Html5.nativeSourceHandler.dispose = function () {};
 
-   // Register the native source handler
 
-   Html5.registerSourceHandler(Html5.nativeSourceHandler);
 
-   Tech.registerTech('Html5', Html5);
 
-   /**
 
-    * @file player.js
 
-    */
 
-   // The following tech events are simply re-triggered
 
-   // on the player when they happen
 
-   const TECH_EVENTS_RETRIGGER = [
 
-   /**
 
-    * Fired while the user agent is downloading media data.
 
-    *
 
-    * @event Player#progress
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Retrigger the `progress` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechProgress_
 
-    * @fires Player#progress
 
-    * @listens Tech#progress
 
-    */
 
-   'progress',
 
-   /**
 
-    * Fires when the loading of an audio/video is aborted.
 
-    *
 
-    * @event Player#abort
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Retrigger the `abort` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechAbort_
 
-    * @fires Player#abort
 
-    * @listens Tech#abort
 
-    */
 
-   'abort',
 
-   /**
 
-    * Fires when the browser is intentionally not getting media data.
 
-    *
 
-    * @event Player#suspend
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Retrigger the `suspend` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechSuspend_
 
-    * @fires Player#suspend
 
-    * @listens Tech#suspend
 
-    */
 
-   'suspend',
 
-   /**
 
-    * Fires when the current playlist is empty.
 
-    *
 
-    * @event Player#emptied
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Retrigger the `emptied` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechEmptied_
 
-    * @fires Player#emptied
 
-    * @listens Tech#emptied
 
-    */
 
-   'emptied',
 
-   /**
 
-    * Fires when the browser is trying to get media data, but data is not available.
 
-    *
 
-    * @event Player#stalled
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Retrigger the `stalled` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechStalled_
 
-    * @fires Player#stalled
 
-    * @listens Tech#stalled
 
-    */
 
-   'stalled',
 
-   /**
 
-    * Fires when the browser has loaded meta data for the audio/video.
 
-    *
 
-    * @event Player#loadedmetadata
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Retrigger the `loadedmetadata` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechLoadedmetadata_
 
-    * @fires Player#loadedmetadata
 
-    * @listens Tech#loadedmetadata
 
-    */
 
-   'loadedmetadata',
 
-   /**
 
-    * Fires when the browser has loaded the current frame of the audio/video.
 
-    *
 
-    * @event Player#loadeddata
 
-    * @type {event}
 
-    */
 
-   /**
 
-    * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechLoaddeddata_
 
-    * @fires Player#loadeddata
 
-    * @listens Tech#loadeddata
 
-    */
 
-   'loadeddata',
 
-   /**
 
-    * Fires when the current playback position has changed.
 
-    *
 
-    * @event Player#timeupdate
 
-    * @type {event}
 
-    */
 
-   /**
 
-    * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechTimeUpdate_
 
-    * @fires Player#timeupdate
 
-    * @listens Tech#timeupdate
 
-    */
 
-   'timeupdate',
 
-   /**
 
-    * Fires when the video's intrinsic dimensions change
 
-    *
 
-    * @event Player#resize
 
-    * @type {event}
 
-    */
 
-   /**
 
-    * Retrigger the `resize` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechResize_
 
-    * @fires Player#resize
 
-    * @listens Tech#resize
 
-    */
 
-   'resize',
 
-   /**
 
-    * Fires when the volume has been changed
 
-    *
 
-    * @event Player#volumechange
 
-    * @type {event}
 
-    */
 
-   /**
 
-    * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechVolumechange_
 
-    * @fires Player#volumechange
 
-    * @listens Tech#volumechange
 
-    */
 
-   'volumechange',
 
-   /**
 
-    * Fires when the text track has been changed
 
-    *
 
-    * @event Player#texttrackchange
 
-    * @type {event}
 
-    */
 
-   /**
 
-    * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
 
-    *
 
-    * @private
 
-    * @method Player#handleTechTexttrackchange_
 
-    * @fires Player#texttrackchange
 
-    * @listens Tech#texttrackchange
 
-    */
 
-   'texttrackchange'];
 
-   // events to queue when playback rate is zero
 
-   // this is a hash for the sole purpose of mapping non-camel-cased event names
 
-   // to camel-cased function names
 
-   const TECH_EVENTS_QUEUE = {
 
-     canplay: 'CanPlay',
 
-     canplaythrough: 'CanPlayThrough',
 
-     playing: 'Playing',
 
-     seeked: 'Seeked'
 
-   };
 
-   const BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
 
-   const BREAKPOINT_CLASSES = {};
 
-   // grep: vjs-layout-tiny
 
-   // grep: vjs-layout-x-small
 
-   // grep: vjs-layout-small
 
-   // grep: vjs-layout-medium
 
-   // grep: vjs-layout-large
 
-   // grep: vjs-layout-x-large
 
-   // grep: vjs-layout-huge
 
-   BREAKPOINT_ORDER.forEach(k => {
 
-     const v = k.charAt(0) === 'x' ? `x-${k.substring(1)}` : k;
 
-     BREAKPOINT_CLASSES[k] = `vjs-layout-${v}`;
 
-   });
 
-   const DEFAULT_BREAKPOINTS = {
 
-     tiny: 210,
 
-     xsmall: 320,
 
-     small: 425,
 
-     medium: 768,
 
-     large: 1440,
 
-     xlarge: 2560,
 
-     huge: Infinity
 
-   };
 
-   /**
 
-    * An instance of the `Player` class is created when any of the Video.js setup methods
 
-    * are used to initialize a video.
 
-    *
 
-    * After an instance has been created it can be accessed globally in three ways:
 
-    * 1. By calling `videojs.getPlayer('example_video_1');`
 
-    * 2. By calling `videojs('example_video_1');` (not recomended)
 
-    * 2. By using it directly via  `videojs.players.example_video_1;`
 
-    *
 
-    * @extends Component
 
-    * @global
 
-    */
 
-   class Player extends Component$1 {
 
-     /**
 
-      * Create an instance of this class.
 
-      *
 
-      * @param {Element} tag
 
-      *        The original video DOM element used for configuring options.
 
-      *
 
-      * @param {Object} [options]
 
-      *        Object of option names and values.
 
-      *
 
-      * @param {Function} [ready]
 
-      *        Ready callback function.
 
-      */
 
-     constructor(tag, options, ready) {
 
-       // Make sure tag ID exists
 
-       tag.id = tag.id || options.id || `vjs_video_${newGUID()}`;
 
-       // Set Options
 
-       // The options argument overrides options set in the video tag
 
-       // which overrides globally set options.
 
-       // This latter part coincides with the load order
 
-       // (tag must exist before Player)
 
-       options = Object.assign(Player.getTagSettings(tag), options);
 
-       // Delay the initialization of children because we need to set up
 
-       // player properties first, and can't use `this` before `super()`
 
-       options.initChildren = false;
 
-       // Same with creating the element
 
-       options.createEl = false;
 
-       // don't auto mixin the evented mixin
 
-       options.evented = false;
 
-       // we don't want the player to report touch activity on itself
 
-       // see enableTouchActivity in Component
 
-       options.reportTouchActivity = false;
 
-       // If language is not set, get the closest lang attribute
 
-       if (!options.language) {
 
-         const closest = tag.closest('[lang]');
 
-         if (closest) {
 
-           options.language = closest.getAttribute('lang');
 
-         }
 
-       }
 
-       // Run base component initializing with new options
 
-       super(null, options, ready);
 
-       // Create bound methods for document listeners.
 
-       this.boundDocumentFullscreenChange_ = e => this.documentFullscreenChange_(e);
 
-       this.boundFullWindowOnEscKey_ = e => this.fullWindowOnEscKey(e);
 
-       this.boundUpdateStyleEl_ = e => this.updateStyleEl_(e);
 
-       this.boundApplyInitTime_ = e => this.applyInitTime_(e);
 
-       this.boundUpdateCurrentBreakpoint_ = e => this.updateCurrentBreakpoint_(e);
 
-       this.boundHandleTechClick_ = e => this.handleTechClick_(e);
 
-       this.boundHandleTechDoubleClick_ = e => this.handleTechDoubleClick_(e);
 
-       this.boundHandleTechTouchStart_ = e => this.handleTechTouchStart_(e);
 
-       this.boundHandleTechTouchMove_ = e => this.handleTechTouchMove_(e);
 
-       this.boundHandleTechTouchEnd_ = e => this.handleTechTouchEnd_(e);
 
-       this.boundHandleTechTap_ = e => this.handleTechTap_(e);
 
-       // default isFullscreen_ to false
 
-       this.isFullscreen_ = false;
 
-       // create logger
 
-       this.log = createLogger(this.id_);
 
-       // Hold our own reference to fullscreen api so it can be mocked in tests
 
-       this.fsApi_ = FullscreenApi;
 
-       // Tracks when a tech changes the poster
 
-       this.isPosterFromTech_ = false;
 
-       // Holds callback info that gets queued when playback rate is zero
 
-       // and a seek is happening
 
-       this.queuedCallbacks_ = [];
 
-       // Turn off API access because we're loading a new tech that might load asynchronously
 
-       this.isReady_ = false;
 
-       // Init state hasStarted_
 
-       this.hasStarted_ = false;
 
-       // Init state userActive_
 
-       this.userActive_ = false;
 
-       // Init debugEnabled_
 
-       this.debugEnabled_ = false;
 
-       // Init state audioOnlyMode_
 
-       this.audioOnlyMode_ = false;
 
-       // Init state audioPosterMode_
 
-       this.audioPosterMode_ = false;
 
-       // Init state audioOnlyCache_
 
-       this.audioOnlyCache_ = {
 
-         playerHeight: null,
 
-         hiddenChildren: []
 
-       };
 
-       // if the global option object was accidentally blown away by
 
-       // someone, bail early with an informative error
 
-       if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) {
 
-         throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
 
-       }
 
-       // Store the original tag used to set options
 
-       this.tag = tag;
 
-       // Store the tag attributes used to restore html5 element
 
-       this.tagAttributes = tag && getAttributes(tag);
 
-       // Update current language
 
-       this.language(this.options_.language);
 
-       // Update Supported Languages
 
-       if (options.languages) {
 
-         // Normalise player option languages to lowercase
 
-         const languagesToLower = {};
 
-         Object.getOwnPropertyNames(options.languages).forEach(function (name) {
 
-           languagesToLower[name.toLowerCase()] = options.languages[name];
 
-         });
 
-         this.languages_ = languagesToLower;
 
-       } else {
 
-         this.languages_ = Player.prototype.options_.languages;
 
-       }
 
-       this.resetCache_();
 
-       // Set poster
 
-       this.poster_ = options.poster || '';
 
-       // Set controls
 
-       this.controls_ = !!options.controls;
 
-       // Original tag settings stored in options
 
-       // now remove immediately so native controls don't flash.
 
-       // May be turned back on by HTML5 tech if nativeControlsForTouch is true
 
-       tag.controls = false;
 
-       tag.removeAttribute('controls');
 
-       this.changingSrc_ = false;
 
-       this.playCallbacks_ = [];
 
-       this.playTerminatedQueue_ = [];
 
-       // the attribute overrides the option
 
-       if (tag.hasAttribute('autoplay')) {
 
-         this.autoplay(true);
 
-       } else {
 
-         // otherwise use the setter to validate and
 
-         // set the correct value.
 
-         this.autoplay(this.options_.autoplay);
 
-       }
 
-       // check plugins
 
-       if (options.plugins) {
 
-         Object.keys(options.plugins).forEach(name => {
 
-           if (typeof this[name] !== 'function') {
 
-             throw new Error(`plugin "${name}" does not exist`);
 
-           }
 
-         });
 
-       }
 
-       /*
 
-        * Store the internal state of scrubbing
 
-        *
 
-        * @private
 
-        * @return {Boolean} True if the user is scrubbing
 
-        */
 
-       this.scrubbing_ = false;
 
-       this.el_ = this.createEl();
 
-       // Make this an evented object and use `el_` as its event bus.
 
-       evented(this, {
 
-         eventBusKey: 'el_'
 
-       });
 
-       // listen to document and player fullscreenchange handlers so we receive those events
 
-       // before a user can receive them so we can update isFullscreen appropriately.
 
-       // make sure that we listen to fullscreenchange events before everything else to make sure that
 
-       // our isFullscreen method is updated properly for internal components as well as external.
 
-       if (this.fsApi_.requestFullscreen) {
 
-         on(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
 
-         this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
 
-       }
 
-       if (this.fluid_) {
 
-         this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
 
-       }
 
-       // We also want to pass the original player options to each component and plugin
 
-       // as well so they don't need to reach back into the player for options later.
 
-       // We also need to do another copy of this.options_ so we don't end up with
 
-       // an infinite loop.
 
-       const playerOptionsCopy = merge$2(this.options_);
 
-       // Load plugins
 
-       if (options.plugins) {
 
-         Object.keys(options.plugins).forEach(name => {
 
-           this[name](options.plugins[name]);
 
-         });
 
-       }
 
-       // Enable debug mode to fire debugon event for all plugins.
 
-       if (options.debug) {
 
-         this.debug(true);
 
-       }
 
-       this.options_.playerOptions = playerOptionsCopy;
 
-       this.middleware_ = [];
 
-       this.playbackRates(options.playbackRates);
 
-       this.initChildren();
 
-       // Set isAudio based on whether or not an audio tag was used
 
-       this.isAudio(tag.nodeName.toLowerCase() === 'audio');
 
-       // Update controls className. Can't do this when the controls are initially
 
-       // set because the element doesn't exist yet.
 
-       if (this.controls()) {
 
-         this.addClass('vjs-controls-enabled');
 
-       } else {
 
-         this.addClass('vjs-controls-disabled');
 
-       }
 
-       // Set ARIA label and region role depending on player type
 
-       this.el_.setAttribute('role', 'region');
 
-       if (this.isAudio()) {
 
-         this.el_.setAttribute('aria-label', this.localize('Audio Player'));
 
-       } else {
 
-         this.el_.setAttribute('aria-label', this.localize('Video Player'));
 
-       }
 
-       if (this.isAudio()) {
 
-         this.addClass('vjs-audio');
 
-       }
 
-       // TODO: Make this smarter. Toggle user state between touching/mousing
 
-       // using events, since devices can have both touch and mouse events.
 
-       // TODO: Make this check be performed again when the window switches between monitors
 
-       // (See https://github.com/videojs/video.js/issues/5683)
 
-       if (TOUCH_ENABLED) {
 
-         this.addClass('vjs-touch-enabled');
 
-       }
 
-       // iOS Safari has broken hover handling
 
-       if (!IS_IOS) {
 
-         this.addClass('vjs-workinghover');
 
-       }
 
-       // Make player easily findable by ID
 
-       Player.players[this.id_] = this;
 
-       // Add a major version class to aid css in plugins
 
-       const majorVersion = version$5.split('.')[0];
 
-       this.addClass(`vjs-v${majorVersion}`);
 
-       // When the player is first initialized, trigger activity so components
 
-       // like the control bar show themselves if needed
 
-       this.userActive(true);
 
-       this.reportUserActivity();
 
-       this.one('play', e => this.listenForUserActivity_(e));
 
-       this.on('keydown', e => this.handleKeyDown(e));
 
-       this.on('languagechange', e => this.handleLanguagechange(e));
 
-       this.breakpoints(this.options_.breakpoints);
 
-       this.responsive(this.options_.responsive);
 
-       // Calling both the audio mode methods after the player is fully
 
-       // setup to be able to listen to the events triggered by them
 
-       this.on('ready', () => {
 
-         // Calling the audioPosterMode method first so that
 
-         // the audioOnlyMode can take precedence when both options are set to true
 
-         this.audioPosterMode(this.options_.audioPosterMode);
 
-         this.audioOnlyMode(this.options_.audioOnlyMode);
 
-       });
 
-     }
 
-     /**
 
-      * Destroys the video player and does any necessary cleanup.
 
-      *
 
-      * This is especially helpful if you are dynamically adding and removing videos
 
-      * to/from the DOM.
 
-      *
 
-      * @fires Player#dispose
 
-      */
 
-     dispose() {
 
-       /**
 
-        * Called when the player is being disposed of.
 
-        *
 
-        * @event Player#dispose
 
-        * @type {Event}
 
-        */
 
-       this.trigger('dispose');
 
-       // prevent dispose from being called twice
 
-       this.off('dispose');
 
-       // Make sure all player-specific document listeners are unbound. This is
 
-       off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
 
-       off(document, 'keydown', this.boundFullWindowOnEscKey_);
 
-       if (this.styleEl_ && this.styleEl_.parentNode) {
 
-         this.styleEl_.parentNode.removeChild(this.styleEl_);
 
-         this.styleEl_ = null;
 
-       }
 
-       // Kill reference to this player
 
-       Player.players[this.id_] = null;
 
-       if (this.tag && this.tag.player) {
 
-         this.tag.player = null;
 
-       }
 
-       if (this.el_ && this.el_.player) {
 
-         this.el_.player = null;
 
-       }
 
-       if (this.tech_) {
 
-         this.tech_.dispose();
 
-         this.isPosterFromTech_ = false;
 
-         this.poster_ = '';
 
-       }
 
-       if (this.playerElIngest_) {
 
-         this.playerElIngest_ = null;
 
-       }
 
-       if (this.tag) {
 
-         this.tag = null;
 
-       }
 
-       clearCacheForPlayer(this);
 
-       // remove all event handlers for track lists
 
-       // all tracks and track listeners are removed on
 
-       // tech dispose
 
-       ALL.names.forEach(name => {
 
-         const props = ALL[name];
 
-         const list = this[props.getterName]();
 
-         // if it is not a native list
 
-         // we have to manually remove event listeners
 
-         if (list && list.off) {
 
-           list.off();
 
-         }
 
-       });
 
-       // the actual .el_ is removed here, or replaced if
 
-       super.dispose({
 
-         restoreEl: this.options_.restoreEl
 
-       });
 
-     }
 
-     /**
 
-      * Create the `Player`'s DOM element.
 
-      *
 
-      * @return {Element}
 
-      *         The DOM element that gets created.
 
-      */
 
-     createEl() {
 
-       let tag = this.tag;
 
-       let el;
 
-       let playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
 
-       const divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
 
-       if (playerElIngest) {
 
-         el = this.el_ = tag.parentNode;
 
-       } else if (!divEmbed) {
 
-         el = this.el_ = super.createEl('div');
 
-       }
 
-       // Copy over all the attributes from the tag, including ID and class
 
-       // ID will now reference player box, not the video tag
 
-       const attrs = getAttributes(tag);
 
-       if (divEmbed) {
 
-         el = this.el_ = tag;
 
-         tag = this.tag = document.createElement('video');
 
-         while (el.children.length) {
 
-           tag.appendChild(el.firstChild);
 
-         }
 
-         if (!hasClass(el, 'video-js')) {
 
-           addClass(el, 'video-js');
 
-         }
 
-         el.appendChild(tag);
 
-         playerElIngest = this.playerElIngest_ = el;
 
-         // move properties over from our custom `video-js` element
 
-         // to our new `video` element. This will move things like
 
-         // `src` or `controls` that were set via js before the player
 
-         // was initialized.
 
-         Object.keys(el).forEach(k => {
 
-           try {
 
-             tag[k] = el[k];
 
-           } catch (e) {
 
-             // we got a a property like outerHTML which we can't actually copy, ignore it
 
-           }
 
-         });
 
-       }
 
-       // set tabindex to -1 to remove the video element from the focus order
 
-       tag.setAttribute('tabindex', '-1');
 
-       attrs.tabindex = '-1';
 
-       // Workaround for #4583 on Chrome (on Windows) with JAWS.
 
-       // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
 
-       // Note that we can't detect if JAWS is being used, but this ARIA attribute
 
-       // doesn't change behavior of Chrome if JAWS is not being used
 
-       if (IS_CHROME && IS_WINDOWS) {
 
-         tag.setAttribute('role', 'application');
 
-         attrs.role = 'application';
 
-       }
 
-       // Remove width/height attrs from tag so CSS can make it 100% width/height
 
-       tag.removeAttribute('width');
 
-       tag.removeAttribute('height');
 
-       if ('width' in attrs) {
 
-         delete attrs.width;
 
-       }
 
-       if ('height' in attrs) {
 
-         delete attrs.height;
 
-       }
 
-       Object.getOwnPropertyNames(attrs).forEach(function (attr) {
 
-         // don't copy over the class attribute to the player element when we're in a div embed
 
-         // the class is already set up properly in the divEmbed case
 
-         // and we want to make sure that the `video-js` class doesn't get lost
 
-         if (!(divEmbed && attr === 'class')) {
 
-           el.setAttribute(attr, attrs[attr]);
 
-         }
 
-         if (divEmbed) {
 
-           tag.setAttribute(attr, attrs[attr]);
 
-         }
 
-       });
 
-       // Update tag id/class for use as HTML5 playback tech
 
-       // Might think we should do this after embedding in container so .vjs-tech class
 
-       // doesn't flash 100% width/height, but class only applies with .video-js parent
 
-       tag.playerId = tag.id;
 
-       tag.id += '_html5_api';
 
-       tag.className = 'vjs-tech';
 
-       // Make player findable on elements
 
-       tag.player = el.player = this;
 
-       // Default state of video is paused
 
-       this.addClass('vjs-paused');
 
-       // Add a style element in the player that we'll use to set the width/height
 
-       // of the player in a way that's still overridable by CSS, just like the
 
-       // video element
 
-       if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
 
-         this.styleEl_ = createStyleElement('vjs-styles-dimensions');
 
-         const defaultsStyleEl = $('.vjs-styles-defaults');
 
-         const head = $('head');
 
-         head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
 
-       }
 
-       this.fill_ = false;
 
-       this.fluid_ = false;
 
-       // Pass in the width/height/aspectRatio options which will update the style el
 
-       this.width(this.options_.width);
 
-       this.height(this.options_.height);
 
-       this.fill(this.options_.fill);
 
-       this.fluid(this.options_.fluid);
 
-       this.aspectRatio(this.options_.aspectRatio);
 
-       // support both crossOrigin and crossorigin to reduce confusion and issues around the name
 
-       this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin);
 
-       // Hide any links within the video/audio tag,
 
-       // because IE doesn't hide them completely from screen readers.
 
-       const links = tag.getElementsByTagName('a');
 
-       for (let i = 0; i < links.length; i++) {
 
-         const linkEl = links.item(i);
 
-         addClass(linkEl, 'vjs-hidden');
 
-         linkEl.setAttribute('hidden', 'hidden');
 
-       }
 
-       // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
 
-       // keep track of the original for later so we can know if the source originally failed
 
-       tag.initNetworkState_ = tag.networkState;
 
-       // Wrap video tag in div (el/box) container
 
-       if (tag.parentNode && !playerElIngest) {
 
-         tag.parentNode.insertBefore(el, tag);
 
-       }
 
-       // insert the tag as the first child of the player element
 
-       // then manually add it to the children array so that this.addChild
 
-       // will work properly for other components
 
-       //
 
-       // Breaks iPhone, fixed in HTML5 setup.
 
-       prependTo(tag, el);
 
-       this.children_.unshift(tag);
 
-       // Set lang attr on player to ensure CSS :lang() in consistent with player
 
-       // if it's been set to something different to the doc
 
-       this.el_.setAttribute('lang', this.language_);
 
-       this.el_.setAttribute('translate', 'no');
 
-       this.el_ = el;
 
-       return el;
 
-     }
 
-     /**
 
-      * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this
 
-      * sets the `crossOrigin` property on the `<video>` tag to control the CORS
 
-      * behavior.
 
-      *
 
-      * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
 
-      *
 
-      * @param {string|null} [value]
 
-      *        The value to set the `Player`'s crossOrigin to. If an argument is
 
-      *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
 
-      *
 
-      * @return {string|null|undefined}
 
-      *         - The current crossOrigin value of the `Player` when getting.
 
-      *         - undefined when setting
 
-      */
 
-     crossOrigin(value) {
 
-       // `null` can be set to unset a value
 
-       if (typeof value === 'undefined') {
 
-         return this.techGet_('crossOrigin');
 
-       }
 
-       if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
 
-         log$1.warn(`crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`);
 
-         return;
 
-       }
 
-       this.techCall_('setCrossOrigin', value);
 
-       if (this.posterImage) {
 
-         this.posterImage.crossOrigin(value);
 
-       }
 
-       return;
 
-     }
 
-     /**
 
-      * A getter/setter for the `Player`'s width. Returns the player's configured value.
 
-      * To get the current width use `currentWidth()`.
 
-      *
 
-      * @param {number} [value]
 
-      *        The value to set the `Player`'s width to.
 
-      *
 
-      * @return {number}
 
-      *         The current width of the `Player` when getting.
 
-      */
 
-     width(value) {
 
-       return this.dimension('width', value);
 
-     }
 
-     /**
 
-      * A getter/setter for the `Player`'s height. Returns the player's configured value.
 
-      * To get the current height use `currentheight()`.
 
-      *
 
-      * @param {number} [value]
 
-      *        The value to set the `Player`'s height to.
 
-      *
 
-      * @return {number}
 
-      *         The current height of the `Player` when getting.
 
-      */
 
-     height(value) {
 
-       return this.dimension('height', value);
 
-     }
 
-     /**
 
-      * A getter/setter for the `Player`'s width & height.
 
-      *
 
-      * @param {string} dimension
 
-      *        This string can be:
 
-      *        - 'width'
 
-      *        - 'height'
 
-      *
 
-      * @param {number} [value]
 
-      *        Value for dimension specified in the first argument.
 
-      *
 
-      * @return {number}
 
-      *         The dimension arguments value when getting (width/height).
 
-      */
 
-     dimension(dimension, value) {
 
-       const privDimension = dimension + '_';
 
-       if (value === undefined) {
 
-         return this[privDimension] || 0;
 
-       }
 
-       if (value === '' || value === 'auto') {
 
-         // If an empty string is given, reset the dimension to be automatic
 
-         this[privDimension] = undefined;
 
-         this.updateStyleEl_();
 
-         return;
 
-       }
 
-       const parsedVal = parseFloat(value);
 
-       if (isNaN(parsedVal)) {
 
-         log$1.error(`Improper value "${value}" supplied for for ${dimension}`);
 
-         return;
 
-       }
 
-       this[privDimension] = parsedVal;
 
-       this.updateStyleEl_();
 
-     }
 
-     /**
 
-      * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
 
-      *
 
-      * Turning this on will turn off fill mode.
 
-      *
 
-      * @param {boolean} [bool]
 
-      *        - A value of true adds the class.
 
-      *        - A value of false removes the class.
 
-      *        - No value will be a getter.
 
-      *
 
-      * @return {boolean|undefined}
 
-      *         - The value of fluid when getting.
 
-      *         - `undefined` when setting.
 
-      */
 
-     fluid(bool) {
 
-       if (bool === undefined) {
 
-         return !!this.fluid_;
 
-       }
 
-       this.fluid_ = !!bool;
 
-       if (isEvented(this)) {
 
-         this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
 
-       }
 
-       if (bool) {
 
-         this.addClass('vjs-fluid');
 
-         this.fill(false);
 
-         addEventedCallback(this, () => {
 
-           this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
 
-         });
 
-       } else {
 
-         this.removeClass('vjs-fluid');
 
-       }
 
-       this.updateStyleEl_();
 
-     }
 
-     /**
 
-      * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
 
-      *
 
-      * Turning this on will turn off fluid mode.
 
-      *
 
-      * @param {boolean} [bool]
 
-      *        - A value of true adds the class.
 
-      *        - A value of false removes the class.
 
-      *        - No value will be a getter.
 
-      *
 
-      * @return {boolean|undefined}
 
-      *         - The value of fluid when getting.
 
-      *         - `undefined` when setting.
 
-      */
 
-     fill(bool) {
 
-       if (bool === undefined) {
 
-         return !!this.fill_;
 
-       }
 
-       this.fill_ = !!bool;
 
-       if (bool) {
 
-         this.addClass('vjs-fill');
 
-         this.fluid(false);
 
-       } else {
 
-         this.removeClass('vjs-fill');
 
-       }
 
-     }
 
-     /**
 
-      * Get/Set the aspect ratio
 
-      *
 
-      * @param {string} [ratio]
 
-      *        Aspect ratio for player
 
-      *
 
-      * @return {string|undefined}
 
-      *         returns the current aspect ratio when getting
 
-      */
 
-     /**
 
-      * A getter/setter for the `Player`'s aspect ratio.
 
-      *
 
-      * @param {string} [ratio]
 
-      *        The value to set the `Player`'s aspect ratio to.
 
-      *
 
-      * @return {string|undefined}
 
-      *         - The current aspect ratio of the `Player` when getting.
 
-      *         - undefined when setting
 
-      */
 
-     aspectRatio(ratio) {
 
-       if (ratio === undefined) {
 
-         return this.aspectRatio_;
 
-       }
 
-       // Check for width:height format
 
-       if (!/^\d+\:\d+$/.test(ratio)) {
 
-         throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
 
-       }
 
-       this.aspectRatio_ = ratio;
 
-       // We're assuming if you set an aspect ratio you want fluid mode,
 
-       // because in fixed mode you could calculate width and height yourself.
 
-       this.fluid(true);
 
-       this.updateStyleEl_();
 
-     }
 
-     /**
 
-      * Update styles of the `Player` element (height, width and aspect ratio).
 
-      *
 
-      * @private
 
-      * @listens Tech#loadedmetadata
 
-      */
 
-     updateStyleEl_() {
 
-       if (window.VIDEOJS_NO_DYNAMIC_STYLE === true) {
 
-         const width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
 
-         const height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
 
-         const techEl = this.tech_ && this.tech_.el();
 
-         if (techEl) {
 
-           if (width >= 0) {
 
-             techEl.width = width;
 
-           }
 
-           if (height >= 0) {
 
-             techEl.height = height;
 
-           }
 
-         }
 
-         return;
 
-       }
 
-       let width;
 
-       let height;
 
-       let aspectRatio;
 
-       let idClass;
 
-       // The aspect ratio is either used directly or to calculate width and height.
 
-       if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
 
-         // Use any aspectRatio that's been specifically set
 
-         aspectRatio = this.aspectRatio_;
 
-       } else if (this.videoWidth() > 0) {
 
-         // Otherwise try to get the aspect ratio from the video metadata
 
-         aspectRatio = this.videoWidth() + ':' + this.videoHeight();
 
-       } else {
 
-         // Or use a default. The video element's is 2:1, but 16:9 is more common.
 
-         aspectRatio = '16:9';
 
-       }
 
-       // Get the ratio as a decimal we can use to calculate dimensions
 
-       const ratioParts = aspectRatio.split(':');
 
-       const ratioMultiplier = ratioParts[1] / ratioParts[0];
 
-       if (this.width_ !== undefined) {
 
-         // Use any width that's been specifically set
 
-         width = this.width_;
 
-       } else if (this.height_ !== undefined) {
 
-         // Or calculate the width from the aspect ratio if a height has been set
 
-         width = this.height_ / ratioMultiplier;
 
-       } else {
 
-         // Or use the video's metadata, or use the video el's default of 300
 
-         width = this.videoWidth() || 300;
 
-       }
 
-       if (this.height_ !== undefined) {
 
-         // Use any height that's been specifically set
 
-         height = this.height_;
 
-       } else {
 
-         // Otherwise calculate the height from the ratio and the width
 
-         height = width * ratioMultiplier;
 
-       }
 
-       // Ensure the CSS class is valid by starting with an alpha character
 
-       if (/^[^a-zA-Z]/.test(this.id())) {
 
-         idClass = 'dimensions-' + this.id();
 
-       } else {
 
-         idClass = this.id() + '-dimensions';
 
-       }
 
-       // Ensure the right class is still on the player for the style element
 
-       this.addClass(idClass);
 
-       setTextContent(this.styleEl_, `
 
-       .${idClass} {
 
-         width: ${width}px;
 
-         height: ${height}px;
 
-       }
 
-       .${idClass}.vjs-fluid:not(.vjs-audio-only-mode) {
 
-         padding-top: ${ratioMultiplier * 100}%;
 
-       }
 
-     `);
 
-     }
 
-     /**
 
-      * Load/Create an instance of playback {@link Tech} including element
 
-      * and API methods. Then append the `Tech` element in `Player` as a child.
 
-      *
 
-      * @param {string} techName
 
-      *        name of the playback technology
 
-      *
 
-      * @param {string} source
 
-      *        video source
 
-      *
 
-      * @private
 
-      */
 
-     loadTech_(techName, source) {
 
-       // Pause and remove current playback technology
 
-       if (this.tech_) {
 
-         this.unloadTech_();
 
-       }
 
-       const titleTechName = toTitleCase$1(techName);
 
-       const camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
 
-       // get rid of the HTML5 video tag as soon as we are using another tech
 
-       if (titleTechName !== 'Html5' && this.tag) {
 
-         Tech.getTech('Html5').disposeMediaElement(this.tag);
 
-         this.tag.player = null;
 
-         this.tag = null;
 
-       }
 
-       this.techName_ = titleTechName;
 
-       // Turn off API access because we're loading a new tech that might load asynchronously
 
-       this.isReady_ = false;
 
-       let autoplay = this.autoplay();
 
-       // if autoplay is a string (or `true` with normalizeAutoplay: true) we pass false to the tech
 
-       // because the player is going to handle autoplay on `loadstart`
 
-       if (typeof this.autoplay() === 'string' || this.autoplay() === true && this.options_.normalizeAutoplay) {
 
-         autoplay = false;
 
-       }
 
-       // Grab tech-specific options from player options and add source and parent element to use.
 
-       const techOptions = {
 
-         source,
 
-         autoplay,
 
-         'nativeControlsForTouch': this.options_.nativeControlsForTouch,
 
-         'playerId': this.id(),
 
-         'techId': `${this.id()}_${camelTechName}_api`,
 
-         'playsinline': this.options_.playsinline,
 
-         'preload': this.options_.preload,
 
-         'loop': this.options_.loop,
 
-         'disablePictureInPicture': this.options_.disablePictureInPicture,
 
-         'muted': this.options_.muted,
 
-         'poster': this.poster(),
 
-         'language': this.language(),
 
-         'playerElIngest': this.playerElIngest_ || false,
 
-         'vtt.js': this.options_['vtt.js'],
 
-         'canOverridePoster': !!this.options_.techCanOverridePoster,
 
-         'enableSourceset': this.options_.enableSourceset
 
-       };
 
-       ALL.names.forEach(name => {
 
-         const props = ALL[name];
 
-         techOptions[props.getterName] = this[props.privateName];
 
-       });
 
-       Object.assign(techOptions, this.options_[titleTechName]);
 
-       Object.assign(techOptions, this.options_[camelTechName]);
 
-       Object.assign(techOptions, this.options_[techName.toLowerCase()]);
 
-       if (this.tag) {
 
-         techOptions.tag = this.tag;
 
-       }
 
-       if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
 
-         techOptions.startTime = this.cache_.currentTime;
 
-       }
 
-       // Initialize tech instance
 
-       const TechClass = Tech.getTech(techName);
 
-       if (!TechClass) {
 
-         throw new Error(`No Tech named '${titleTechName}' exists! '${titleTechName}' should be registered using videojs.registerTech()'`);
 
-       }
 
-       this.tech_ = new TechClass(techOptions);
 
-       // player.triggerReady is always async, so don't need this to be async
 
-       this.tech_.ready(bind_(this, this.handleTechReady_), true);
 
-       textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
 
-       // Listen to all HTML5-defined events and trigger them on the player
 
-       TECH_EVENTS_RETRIGGER.forEach(event => {
 
-         this.on(this.tech_, event, e => this[`handleTech${toTitleCase$1(event)}_`](e));
 
-       });
 
-       Object.keys(TECH_EVENTS_QUEUE).forEach(event => {
 
-         this.on(this.tech_, event, eventObj => {
 
-           if (this.tech_.playbackRate() === 0 && this.tech_.seeking()) {
 
-             this.queuedCallbacks_.push({
 
-               callback: this[`handleTech${TECH_EVENTS_QUEUE[event]}_`].bind(this),
 
-               event: eventObj
 
-             });
 
-             return;
 
-           }
 
-           this[`handleTech${TECH_EVENTS_QUEUE[event]}_`](eventObj);
 
-         });
 
-       });
 
-       this.on(this.tech_, 'loadstart', e => this.handleTechLoadStart_(e));
 
-       this.on(this.tech_, 'sourceset', e => this.handleTechSourceset_(e));
 
-       this.on(this.tech_, 'waiting', e => this.handleTechWaiting_(e));
 
-       this.on(this.tech_, 'ended', e => this.handleTechEnded_(e));
 
-       this.on(this.tech_, 'seeking', e => this.handleTechSeeking_(e));
 
-       this.on(this.tech_, 'play', e => this.handleTechPlay_(e));
 
-       this.on(this.tech_, 'pause', e => this.handleTechPause_(e));
 
-       this.on(this.tech_, 'durationchange', e => this.handleTechDurationChange_(e));
 
-       this.on(this.tech_, 'fullscreenchange', (e, data) => this.handleTechFullscreenChange_(e, data));
 
-       this.on(this.tech_, 'fullscreenerror', (e, err) => this.handleTechFullscreenError_(e, err));
 
-       this.on(this.tech_, 'enterpictureinpicture', e => this.handleTechEnterPictureInPicture_(e));
 
-       this.on(this.tech_, 'leavepictureinpicture', e => this.handleTechLeavePictureInPicture_(e));
 
-       this.on(this.tech_, 'error', e => this.handleTechError_(e));
 
-       this.on(this.tech_, 'posterchange', e => this.handleTechPosterChange_(e));
 
-       this.on(this.tech_, 'textdata', e => this.handleTechTextData_(e));
 
-       this.on(this.tech_, 'ratechange', e => this.handleTechRateChange_(e));
 
-       this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_);
 
-       this.usingNativeControls(this.techGet_('controls'));
 
-       if (this.controls() && !this.usingNativeControls()) {
 
-         this.addTechControlsListeners_();
 
-       }
 
-       // Add the tech element in the DOM if it was not already there
 
-       // Make sure to not insert the original video element if using Html5
 
-       if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
 
-         prependTo(this.tech_.el(), this.el());
 
-       }
 
-       // Get rid of the original video tag reference after the first tech is loaded
 
-       if (this.tag) {
 
-         this.tag.player = null;
 
-         this.tag = null;
 
-       }
 
-     }
 
-     /**
 
-      * Unload and dispose of the current playback {@link Tech}.
 
-      *
 
-      * @private
 
-      */
 
-     unloadTech_() {
 
-       // Save the current text tracks so that we can reuse the same text tracks with the next tech
 
-       ALL.names.forEach(name => {
 
-         const props = ALL[name];
 
-         this[props.privateName] = this[props.getterName]();
 
-       });
 
-       this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
 
-       this.isReady_ = false;
 
-       this.tech_.dispose();
 
-       this.tech_ = false;
 
-       if (this.isPosterFromTech_) {
 
-         this.poster_ = '';
 
-         this.trigger('posterchange');
 
-       }
 
-       this.isPosterFromTech_ = false;
 
-     }
 
-     /**
 
-      * Return a reference to the current {@link Tech}.
 
-      * It will print a warning by default about the danger of using the tech directly
 
-      * but any argument that is passed in will silence the warning.
 
-      *
 
-      * @param {*} [safety]
 
-      *        Anything passed in to silence the warning
 
-      *
 
-      * @return {Tech}
 
-      *         The Tech
 
-      */
 
-     tech(safety) {
 
-       if (safety === undefined) {
 
-         log$1.warn('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');
 
-       }
 
-       return this.tech_;
 
-     }
 
-     /**
 
-      * Set up click and touch listeners for the playback element
 
-      *
 
-      * - On desktops: a click on the video itself will toggle playback
 
-      * - On mobile devices: a click on the video toggles controls
 
-      *   which is done by toggling the user state between active and
 
-      *   inactive
 
-      * - A tap can signal that a user has become active or has become inactive
 
-      *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
 
-      *   quick tap should hide them again (signaling the user is in an inactive
 
-      *   viewing state)
 
-      * - In addition to this, we still want the user to be considered inactive after
 
-      *   a few seconds of inactivity.
 
-      *
 
-      * > Note: the only part of iOS interaction we can't mimic with this setup
 
-      * is a touch and hold on the video element counting as activity in order to
 
-      * keep the controls showing, but that shouldn't be an issue. A touch and hold
 
-      * on any controls will still keep the user active
 
-      *
 
-      * @private
 
-      */
 
-     addTechControlsListeners_() {
 
-       // Make sure to remove all the previous listeners in case we are called multiple times.
 
-       this.removeTechControlsListeners_();
 
-       this.on(this.tech_, 'click', this.boundHandleTechClick_);
 
-       this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
 
-       // If the controls were hidden we don't want that to change without a tap event
 
-       // so we'll check if the controls were already showing before reporting user
 
-       // activity
 
-       this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
 
-       this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
 
-       this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
 
-       // The tap listener needs to come after the touchend listener because the tap
 
-       // listener cancels out any reportedUserActivity when setting userActive(false)
 
-       this.on(this.tech_, 'tap', this.boundHandleTechTap_);
 
-     }
 
-     /**
 
-      * Remove the listeners used for click and tap controls. This is needed for
 
-      * toggling to controls disabled, where a tap/touch should do nothing.
 
-      *
 
-      * @private
 
-      */
 
-     removeTechControlsListeners_() {
 
-       // We don't want to just use `this.off()` because there might be other needed
 
-       // listeners added by techs that extend this.
 
-       this.off(this.tech_, 'tap', this.boundHandleTechTap_);
 
-       this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
 
-       this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
 
-       this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
 
-       this.off(this.tech_, 'click', this.boundHandleTechClick_);
 
-       this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
 
-     }
 
-     /**
 
-      * Player waits for the tech to be ready
 
-      *
 
-      * @private
 
-      */
 
-     handleTechReady_() {
 
-       this.triggerReady();
 
-       // Keep the same volume as before
 
-       if (this.cache_.volume) {
 
-         this.techCall_('setVolume', this.cache_.volume);
 
-       }
 
-       // Look if the tech found a higher resolution poster while loading
 
-       this.handleTechPosterChange_();
 
-       // Update the duration if available
 
-       this.handleTechDurationChange_();
 
-     }
 
-     /**
 
-      * Retrigger the `loadstart` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#loadstart
 
-      * @listens Tech#loadstart
 
-      * @private
 
-      */
 
-     handleTechLoadStart_() {
 
-       // TODO: Update to use `emptied` event instead. See #1277.
 
-       this.removeClass('vjs-ended', 'vjs-seeking');
 
-       // reset the error state
 
-       this.error(null);
 
-       // Update the duration
 
-       this.handleTechDurationChange_();
 
-       if (!this.paused()) {
 
-         /**
 
-          * Fired when the user agent begins looking for media data
 
-          *
 
-          * @event Player#loadstart
 
-          * @type {Event}
 
-          */
 
-         this.trigger('loadstart');
 
-       } else {
 
-         // reset the hasStarted state
 
-         this.hasStarted(false);
 
-         this.trigger('loadstart');
 
-       }
 
-       // autoplay happens after loadstart for the browser,
 
-       // so we mimic that behavior
 
-       this.manualAutoplay_(this.autoplay() === true && this.options_.normalizeAutoplay ? 'play' : this.autoplay());
 
-     }
 
-     /**
 
-      * Handle autoplay string values, rather than the typical boolean
 
-      * values that should be handled by the tech. Note that this is not
 
-      * part of any specification. Valid values and what they do can be
 
-      * found on the autoplay getter at Player#autoplay()
 
-      */
 
-     manualAutoplay_(type) {
 
-       if (!this.tech_ || typeof type !== 'string') {
 
-         return;
 
-       }
 
-       // Save original muted() value, set muted to true, and attempt to play().
 
-       // On promise rejection, restore muted from saved value
 
-       const resolveMuted = () => {
 
-         const previouslyMuted = this.muted();
 
-         this.muted(true);
 
-         const restoreMuted = () => {
 
-           this.muted(previouslyMuted);
 
-         };
 
-         // restore muted on play terminatation
 
-         this.playTerminatedQueue_.push(restoreMuted);
 
-         const mutedPromise = this.play();
 
-         if (!isPromise(mutedPromise)) {
 
-           return;
 
-         }
 
-         return mutedPromise.catch(err => {
 
-           restoreMuted();
 
-           throw new Error(`Rejection at manualAutoplay. Restoring muted value. ${err ? err : ''}`);
 
-         });
 
-       };
 
-       let promise;
 
-       // if muted defaults to true
 
-       // the only thing we can do is call play
 
-       if (type === 'any' && !this.muted()) {
 
-         promise = this.play();
 
-         if (isPromise(promise)) {
 
-           promise = promise.catch(resolveMuted);
 
-         }
 
-       } else if (type === 'muted' && !this.muted()) {
 
-         promise = resolveMuted();
 
-       } else {
 
-         promise = this.play();
 
-       }
 
-       if (!isPromise(promise)) {
 
-         return;
 
-       }
 
-       return promise.then(() => {
 
-         this.trigger({
 
-           type: 'autoplay-success',
 
-           autoplay: type
 
-         });
 
-       }).catch(() => {
 
-         this.trigger({
 
-           type: 'autoplay-failure',
 
-           autoplay: type
 
-         });
 
-       });
 
-     }
 
-     /**
 
-      * Update the internal source caches so that we return the correct source from
 
-      * `src()`, `currentSource()`, and `currentSources()`.
 
-      *
 
-      * > Note: `currentSources` will not be updated if the source that is passed in exists
 
-      *         in the current `currentSources` cache.
 
-      *
 
-      *
 
-      * @param {Tech~SourceObject} srcObj
 
-      *        A string or object source to update our caches to.
 
-      */
 
-     updateSourceCaches_(srcObj = '') {
 
-       let src = srcObj;
 
-       let type = '';
 
-       if (typeof src !== 'string') {
 
-         src = srcObj.src;
 
-         type = srcObj.type;
 
-       }
 
-       // make sure all the caches are set to default values
 
-       // to prevent null checking
 
-       this.cache_.source = this.cache_.source || {};
 
-       this.cache_.sources = this.cache_.sources || [];
 
-       // try to get the type of the src that was passed in
 
-       if (src && !type) {
 
-         type = findMimetype(this, src);
 
-       }
 
-       // update `currentSource` cache always
 
-       this.cache_.source = merge$2({}, srcObj, {
 
-         src,
 
-         type
 
-       });
 
-       const matchingSources = this.cache_.sources.filter(s => s.src && s.src === src);
 
-       const sourceElSources = [];
 
-       const sourceEls = this.$$('source');
 
-       const matchingSourceEls = [];
 
-       for (let i = 0; i < sourceEls.length; i++) {
 
-         const sourceObj = getAttributes(sourceEls[i]);
 
-         sourceElSources.push(sourceObj);
 
-         if (sourceObj.src && sourceObj.src === src) {
 
-           matchingSourceEls.push(sourceObj.src);
 
-         }
 
-       }
 
-       // if we have matching source els but not matching sources
 
-       // the current source cache is not up to date
 
-       if (matchingSourceEls.length && !matchingSources.length) {
 
-         this.cache_.sources = sourceElSources;
 
-         // if we don't have matching source or source els set the
 
-         // sources cache to the `currentSource` cache
 
-       } else if (!matchingSources.length) {
 
-         this.cache_.sources = [this.cache_.source];
 
-       }
 
-       // update the tech `src` cache
 
-       this.cache_.src = src;
 
-     }
 
-     /**
 
-      * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
 
-      * causing the media element to reload.
 
-      *
 
-      * It will fire for the initial source and each subsequent source.
 
-      * This event is a custom event from Video.js and is triggered by the {@link Tech}.
 
-      *
 
-      * The event object for this event contains a `src` property that will contain the source
 
-      * that was available when the event was triggered. This is generally only necessary if Video.js
 
-      * is switching techs while the source was being changed.
 
-      *
 
-      * It is also fired when `load` is called on the player (or media element)
 
-      * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
 
-      * says that the resource selection algorithm needs to be aborted and restarted.
 
-      * In this case, it is very likely that the `src` property will be set to the
 
-      * empty string `""` to indicate we do not know what the source will be but
 
-      * that it is changing.
 
-      *
 
-      * *This event is currently still experimental and may change in minor releases.*
 
-      * __To use this, pass `enableSourceset` option to the player.__
 
-      *
 
-      * @event Player#sourceset
 
-      * @type {Event}
 
-      * @prop {string} src
 
-      *                The source url available when the `sourceset` was triggered.
 
-      *                It will be an empty string if we cannot know what the source is
 
-      *                but know that the source will change.
 
-      */
 
-     /**
 
-      * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#sourceset
 
-      * @listens Tech#sourceset
 
-      * @private
 
-      */
 
-     handleTechSourceset_(event) {
 
-       // only update the source cache when the source
 
-       // was not updated using the player api
 
-       if (!this.changingSrc_) {
 
-         let updateSourceCaches = src => this.updateSourceCaches_(src);
 
-         const playerSrc = this.currentSource().src;
 
-         const eventSrc = event.src;
 
-         // if we have a playerSrc that is not a blob, and a tech src that is a blob
 
-         if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
 
-           // if both the tech source and the player source were updated we assume
 
-           // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
 
-           if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
 
-             updateSourceCaches = () => {};
 
-           }
 
-         }
 
-         // update the source to the initial source right away
 
-         // in some cases this will be empty string
 
-         updateSourceCaches(eventSrc);
 
-         // if the `sourceset` `src` was an empty string
 
-         // wait for a `loadstart` to update the cache to `currentSrc`.
 
-         // If a sourceset happens before a `loadstart`, we reset the state
 
-         if (!event.src) {
 
-           this.tech_.any(['sourceset', 'loadstart'], e => {
 
-             // if a sourceset happens before a `loadstart` there
 
-             // is nothing to do as this `handleTechSourceset_`
 
-             // will be called again and this will be handled there.
 
-             if (e.type === 'sourceset') {
 
-               return;
 
-             }
 
-             const techSrc = this.techGet('currentSrc');
 
-             this.lastSource_.tech = techSrc;
 
-             this.updateSourceCaches_(techSrc);
 
-           });
 
-         }
 
-       }
 
-       this.lastSource_ = {
 
-         player: this.currentSource().src,
 
-         tech: event.src
 
-       };
 
-       this.trigger({
 
-         src: event.src,
 
-         type: 'sourceset'
 
-       });
 
-     }
 
-     /**
 
-      * Add/remove the vjs-has-started class
 
-      *
 
-      *
 
-      * @param {boolean} request
 
-      *        - true: adds the class
 
-      *        - false: remove the class
 
-      *
 
-      * @return {boolean}
 
-      *         the boolean value of hasStarted_
 
-      */
 
-     hasStarted(request) {
 
-       if (request === undefined) {
 
-         // act as getter, if we have no request to change
 
-         return this.hasStarted_;
 
-       }
 
-       if (request === this.hasStarted_) {
 
-         return;
 
-       }
 
-       this.hasStarted_ = request;
 
-       if (this.hasStarted_) {
 
-         this.addClass('vjs-has-started');
 
-       } else {
 
-         this.removeClass('vjs-has-started');
 
-       }
 
-     }
 
-     /**
 
-      * Fired whenever the media begins or resumes playback
 
-      *
 
-      * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
 
-      * @fires Player#play
 
-      * @listens Tech#play
 
-      * @private
 
-      */
 
-     handleTechPlay_() {
 
-       this.removeClass('vjs-ended', 'vjs-paused');
 
-       this.addClass('vjs-playing');
 
-       // hide the poster when the user hits play
 
-       this.hasStarted(true);
 
-       /**
 
-        * Triggered whenever an {@link Tech#play} event happens. Indicates that
 
-        * playback has started or resumed.
 
-        *
 
-        * @event Player#play
 
-        * @type {Event}
 
-        */
 
-       this.trigger('play');
 
-     }
 
-     /**
 
-      * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
 
-      *
 
-      * If there were any events queued while the playback rate was zero, fire
 
-      * those events now.
 
-      *
 
-      * @private
 
-      * @method Player#handleTechRateChange_
 
-      * @fires Player#ratechange
 
-      * @listens Tech#ratechange
 
-      */
 
-     handleTechRateChange_() {
 
-       if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
 
-         this.queuedCallbacks_.forEach(queued => queued.callback(queued.event));
 
-         this.queuedCallbacks_ = [];
 
-       }
 
-       this.cache_.lastPlaybackRate = this.tech_.playbackRate();
 
-       /**
 
-        * Fires when the playing speed of the audio/video is changed
 
-        *
 
-        * @event Player#ratechange
 
-        * @type {event}
 
-        */
 
-       this.trigger('ratechange');
 
-     }
 
-     /**
 
-      * Retrigger the `waiting` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#waiting
 
-      * @listens Tech#waiting
 
-      * @private
 
-      */
 
-     handleTechWaiting_() {
 
-       this.addClass('vjs-waiting');
 
-       /**
 
-        * A readyState change on the DOM element has caused playback to stop.
 
-        *
 
-        * @event Player#waiting
 
-        * @type {Event}
 
-        */
 
-       this.trigger('waiting');
 
-       // Browsers may emit a timeupdate event after a waiting event. In order to prevent
 
-       // premature removal of the waiting class, wait for the time to change.
 
-       const timeWhenWaiting = this.currentTime();
 
-       const timeUpdateListener = () => {
 
-         if (timeWhenWaiting !== this.currentTime()) {
 
-           this.removeClass('vjs-waiting');
 
-           this.off('timeupdate', timeUpdateListener);
 
-         }
 
-       };
 
-       this.on('timeupdate', timeUpdateListener);
 
-     }
 
-     /**
 
-      * Retrigger the `canplay` event that was triggered by the {@link Tech}.
 
-      * > Note: This is not consistent between browsers. See #1351
 
-      *
 
-      * @fires Player#canplay
 
-      * @listens Tech#canplay
 
-      * @private
 
-      */
 
-     handleTechCanPlay_() {
 
-       this.removeClass('vjs-waiting');
 
-       /**
 
-        * The media has a readyState of HAVE_FUTURE_DATA or greater.
 
-        *
 
-        * @event Player#canplay
 
-        * @type {Event}
 
-        */
 
-       this.trigger('canplay');
 
-     }
 
-     /**
 
-      * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#canplaythrough
 
-      * @listens Tech#canplaythrough
 
-      * @private
 
-      */
 
-     handleTechCanPlayThrough_() {
 
-       this.removeClass('vjs-waiting');
 
-       /**
 
-        * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
 
-        * entire media file can be played without buffering.
 
-        *
 
-        * @event Player#canplaythrough
 
-        * @type {Event}
 
-        */
 
-       this.trigger('canplaythrough');
 
-     }
 
-     /**
 
-      * Retrigger the `playing` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#playing
 
-      * @listens Tech#playing
 
-      * @private
 
-      */
 
-     handleTechPlaying_() {
 
-       this.removeClass('vjs-waiting');
 
-       /**
 
-        * The media is no longer blocked from playback, and has started playing.
 
-        *
 
-        * @event Player#playing
 
-        * @type {Event}
 
-        */
 
-       this.trigger('playing');
 
-     }
 
-     /**
 
-      * Retrigger the `seeking` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#seeking
 
-      * @listens Tech#seeking
 
-      * @private
 
-      */
 
-     handleTechSeeking_() {
 
-       this.addClass('vjs-seeking');
 
-       /**
 
-        * Fired whenever the player is jumping to a new time
 
-        *
 
-        * @event Player#seeking
 
-        * @type {Event}
 
-        */
 
-       this.trigger('seeking');
 
-     }
 
-     /**
 
-      * Retrigger the `seeked` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#seeked
 
-      * @listens Tech#seeked
 
-      * @private
 
-      */
 
-     handleTechSeeked_() {
 
-       this.removeClass('vjs-seeking', 'vjs-ended');
 
-       /**
 
-        * Fired when the player has finished jumping to a new time
 
-        *
 
-        * @event Player#seeked
 
-        * @type {Event}
 
-        */
 
-       this.trigger('seeked');
 
-     }
 
-     /**
 
-      * Retrigger the `pause` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#pause
 
-      * @listens Tech#pause
 
-      * @private
 
-      */
 
-     handleTechPause_() {
 
-       this.removeClass('vjs-playing');
 
-       this.addClass('vjs-paused');
 
-       /**
 
-        * Fired whenever the media has been paused
 
-        *
 
-        * @event Player#pause
 
-        * @type {Event}
 
-        */
 
-       this.trigger('pause');
 
-     }
 
-     /**
 
-      * Retrigger the `ended` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#ended
 
-      * @listens Tech#ended
 
-      * @private
 
-      */
 
-     handleTechEnded_() {
 
-       this.addClass('vjs-ended');
 
-       this.removeClass('vjs-waiting');
 
-       if (this.options_.loop) {
 
-         this.currentTime(0);
 
-         this.play();
 
-       } else if (!this.paused()) {
 
-         this.pause();
 
-       }
 
-       /**
 
-        * Fired when the end of the media resource is reached (currentTime == duration)
 
-        *
 
-        * @event Player#ended
 
-        * @type {Event}
 
-        */
 
-       this.trigger('ended');
 
-     }
 
-     /**
 
-      * Fired when the duration of the media resource is first known or changed
 
-      *
 
-      * @listens Tech#durationchange
 
-      * @private
 
-      */
 
-     handleTechDurationChange_() {
 
-       this.duration(this.techGet_('duration'));
 
-     }
 
-     /**
 
-      * Handle a click on the media element to play/pause
 
-      *
 
-      * @param {Event} event
 
-      *        the event that caused this function to trigger
 
-      *
 
-      * @listens Tech#click
 
-      * @private
 
-      */
 
-     handleTechClick_(event) {
 
-       // When controls are disabled a click should not toggle playback because
 
-       // the click is considered a control
 
-       if (!this.controls_) {
 
-         return;
 
-       }
 
-       if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.click === undefined || this.options_.userActions.click !== false) {
 
-         if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.click === 'function') {
 
-           this.options_.userActions.click.call(this, event);
 
-         } else if (this.paused()) {
 
-           silencePromise(this.play());
 
-         } else {
 
-           this.pause();
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Handle a double-click on the media element to enter/exit fullscreen
 
-      *
 
-      * @param {Event} event
 
-      *        the event that caused this function to trigger
 
-      *
 
-      * @listens Tech#dblclick
 
-      * @private
 
-      */
 
-     handleTechDoubleClick_(event) {
 
-       if (!this.controls_) {
 
-         return;
 
-       }
 
-       // we do not want to toggle fullscreen state
 
-       // when double-clicking inside a control bar or a modal
 
-       const inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), el => el.contains(event.target));
 
-       if (!inAllowedEls) {
 
-         /*
 
-          * options.userActions.doubleClick
 
-          *
 
-          * If `undefined` or `true`, double-click toggles fullscreen if controls are present
 
-          * Set to `false` to disable double-click handling
 
-          * Set to a function to substitute an external double-click handler
 
-          */
 
-         if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
 
-           if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
 
-             this.options_.userActions.doubleClick.call(this, event);
 
-           } else if (this.isFullscreen()) {
 
-             this.exitFullscreen();
 
-           } else {
 
-             this.requestFullscreen();
 
-           }
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Handle a tap on the media element. It will toggle the user
 
-      * activity state, which hides and shows the controls.
 
-      *
 
-      * @listens Tech#tap
 
-      * @private
 
-      */
 
-     handleTechTap_() {
 
-       this.userActive(!this.userActive());
 
-     }
 
-     /**
 
-      * Handle touch to start
 
-      *
 
-      * @listens Tech#touchstart
 
-      * @private
 
-      */
 
-     handleTechTouchStart_() {
 
-       this.userWasActive = this.userActive();
 
-     }
 
-     /**
 
-      * Handle touch to move
 
-      *
 
-      * @listens Tech#touchmove
 
-      * @private
 
-      */
 
-     handleTechTouchMove_() {
 
-       if (this.userWasActive) {
 
-         this.reportUserActivity();
 
-       }
 
-     }
 
-     /**
 
-      * Handle touch to end
 
-      *
 
-      * @param {Event} event
 
-      *        the touchend event that triggered
 
-      *        this function
 
-      *
 
-      * @listens Tech#touchend
 
-      * @private
 
-      */
 
-     handleTechTouchEnd_(event) {
 
-       // Stop the mouse events from also happening
 
-       if (event.cancelable) {
 
-         event.preventDefault();
 
-       }
 
-     }
 
-     /**
 
-      * @private
 
-      */
 
-     toggleFullscreenClass_() {
 
-       if (this.isFullscreen()) {
 
-         this.addClass('vjs-fullscreen');
 
-       } else {
 
-         this.removeClass('vjs-fullscreen');
 
-       }
 
-     }
 
-     /**
 
-      * when the document fschange event triggers it calls this
 
-      */
 
-     documentFullscreenChange_(e) {
 
-       const targetPlayer = e.target.player;
 
-       // if another player was fullscreen
 
-       // do a null check for targetPlayer because older firefox's would put document as e.target
 
-       if (targetPlayer && targetPlayer !== this) {
 
-         return;
 
-       }
 
-       const el = this.el();
 
-       let isFs = document[this.fsApi_.fullscreenElement] === el;
 
-       if (!isFs && el.matches) {
 
-         isFs = el.matches(':' + this.fsApi_.fullscreen);
 
-       } else if (!isFs && el.msMatchesSelector) {
 
-         isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen);
 
-       }
 
-       this.isFullscreen(isFs);
 
-     }
 
-     /**
 
-      * Handle Tech Fullscreen Change
 
-      *
 
-      * @param {Event} event
 
-      *        the fullscreenchange event that triggered this function
 
-      *
 
-      * @param {Object} data
 
-      *        the data that was sent with the event
 
-      *
 
-      * @private
 
-      * @listens Tech#fullscreenchange
 
-      * @fires Player#fullscreenchange
 
-      */
 
-     handleTechFullscreenChange_(event, data) {
 
-       if (data) {
 
-         if (data.nativeIOSFullscreen) {
 
-           this.addClass('vjs-ios-native-fs');
 
-           this.tech_.one('webkitendfullscreen', () => {
 
-             this.removeClass('vjs-ios-native-fs');
 
-           });
 
-         }
 
-         this.isFullscreen(data.isFullscreen);
 
-       }
 
-     }
 
-     handleTechFullscreenError_(event, err) {
 
-       this.trigger('fullscreenerror', err);
 
-     }
 
-     /**
 
-      * @private
 
-      */
 
-     togglePictureInPictureClass_() {
 
-       if (this.isInPictureInPicture()) {
 
-         this.addClass('vjs-picture-in-picture');
 
-       } else {
 
-         this.removeClass('vjs-picture-in-picture');
 
-       }
 
-     }
 
-     /**
 
-      * Handle Tech Enter Picture-in-Picture.
 
-      *
 
-      * @param {Event} event
 
-      *        the enterpictureinpicture event that triggered this function
 
-      *
 
-      * @private
 
-      * @listens Tech#enterpictureinpicture
 
-      */
 
-     handleTechEnterPictureInPicture_(event) {
 
-       this.isInPictureInPicture(true);
 
-     }
 
-     /**
 
-      * Handle Tech Leave Picture-in-Picture.
 
-      *
 
-      * @param {Event} event
 
-      *        the leavepictureinpicture event that triggered this function
 
-      *
 
-      * @private
 
-      * @listens Tech#leavepictureinpicture
 
-      */
 
-     handleTechLeavePictureInPicture_(event) {
 
-       this.isInPictureInPicture(false);
 
-     }
 
-     /**
 
-      * Fires when an error occurred during the loading of an audio/video.
 
-      *
 
-      * @private
 
-      * @listens Tech#error
 
-      */
 
-     handleTechError_() {
 
-       const error = this.tech_.error();
 
-       this.error(error);
 
-     }
 
-     /**
 
-      * Retrigger the `textdata` event that was triggered by the {@link Tech}.
 
-      *
 
-      * @fires Player#textdata
 
-      * @listens Tech#textdata
 
-      * @private
 
-      */
 
-     handleTechTextData_() {
 
-       let data = null;
 
-       if (arguments.length > 1) {
 
-         data = arguments[1];
 
-       }
 
-       /**
 
-        * Fires when we get a textdata event from tech
 
-        *
 
-        * @event Player#textdata
 
-        * @type {Event}
 
-        */
 
-       this.trigger('textdata', data);
 
-     }
 
-     /**
 
-      * Get object for cached values.
 
-      *
 
-      * @return {Object}
 
-      *         get the current object cache
 
-      */
 
-     getCache() {
 
-       return this.cache_;
 
-     }
 
-     /**
 
-      * Resets the internal cache object.
 
-      *
 
-      * Using this function outside the player constructor or reset method may
 
-      * have unintended side-effects.
 
-      *
 
-      * @private
 
-      */
 
-     resetCache_() {
 
-       this.cache_ = {
 
-         // Right now, the currentTime is not _really_ cached because it is always
 
-         // retrieved from the tech (see: currentTime). However, for completeness,
 
-         // we set it to zero here to ensure that if we do start actually caching
 
-         // it, we reset it along with everything else.
 
-         currentTime: 0,
 
-         initTime: 0,
 
-         inactivityTimeout: this.options_.inactivityTimeout,
 
-         duration: NaN,
 
-         lastVolume: 1,
 
-         lastPlaybackRate: this.defaultPlaybackRate(),
 
-         media: null,
 
-         src: '',
 
-         source: {},
 
-         sources: [],
 
-         playbackRates: [],
 
-         volume: 1
 
-       };
 
-     }
 
-     /**
 
-      * Pass values to the playback tech
 
-      *
 
-      * @param {string} [method]
 
-      *        the method to call
 
-      *
 
-      * @param {Object} arg
 
-      *        the argument to pass
 
-      *
 
-      * @private
 
-      */
 
-     techCall_(method, arg) {
 
-       // If it's not ready yet, call method when it is
 
-       this.ready(function () {
 
-         if (method in allowedSetters) {
 
-           return set(this.middleware_, this.tech_, method, arg);
 
-         } else if (method in allowedMediators) {
 
-           return mediate(this.middleware_, this.tech_, method, arg);
 
-         }
 
-         try {
 
-           if (this.tech_) {
 
-             this.tech_[method](arg);
 
-           }
 
-         } catch (e) {
 
-           log$1(e);
 
-           throw e;
 
-         }
 
-       }, true);
 
-     }
 
-     /**
 
-      * Mediate attempt to call playback tech method
 
-      * and return the value of the method called.
 
-      *
 
-      * @param {string} method
 
-      *        Tech method
 
-      *
 
-      * @return {*}
 
-      *         Value returned by the tech method called, undefined if tech
 
-      *         is not ready or tech method is not present
 
-      *
 
-      * @private
 
-      */
 
-     techGet_(method) {
 
-       if (!this.tech_ || !this.tech_.isReady_) {
 
-         return;
 
-       }
 
-       if (method in allowedGetters) {
 
-         return get(this.middleware_, this.tech_, method);
 
-       } else if (method in allowedMediators) {
 
-         return mediate(this.middleware_, this.tech_, method);
 
-       }
 
-       // Log error when playback tech object is present but method
 
-       // is undefined or unavailable
 
-       try {
 
-         return this.tech_[method]();
 
-       } catch (e) {
 
-         // When building additional tech libs, an expected method may not be defined yet
 
-         if (this.tech_[method] === undefined) {
 
-           log$1(`Video.js: ${method} method not defined for ${this.techName_} playback technology.`, e);
 
-           throw e;
 
-         }
 
-         // When a method isn't available on the object it throws a TypeError
 
-         if (e.name === 'TypeError') {
 
-           log$1(`Video.js: ${method} unavailable on ${this.techName_} playback technology element.`, e);
 
-           this.tech_.isReady_ = false;
 
-           throw e;
 
-         }
 
-         // If error unknown, just log and throw
 
-         log$1(e);
 
-         throw e;
 
-       }
 
-     }
 
-     /**
 
-      * Attempt to begin playback at the first opportunity.
 
-      *
 
-      * @return {Promise|undefined}
 
-      *         Returns a promise if the browser supports Promises (or one
 
-      *         was passed in as an option). This promise will be resolved on
 
-      *         the return value of play. If this is undefined it will fulfill the
 
-      *         promise chain otherwise the promise chain will be fulfilled when
 
-      *         the promise from play is fulfilled.
 
-      */
 
-     play() {
 
-       return new Promise(resolve => {
 
-         this.play_(resolve);
 
-       });
 
-     }
 
-     /**
 
-      * The actual logic for play, takes a callback that will be resolved on the
 
-      * return value of play. This allows us to resolve to the play promise if there
 
-      * is one on modern browsers.
 
-      *
 
-      * @private
 
-      * @param {Function} [callback]
 
-      *        The callback that should be called when the techs play is actually called
 
-      */
 
-     play_(callback = silencePromise) {
 
-       this.playCallbacks_.push(callback);
 
-       const isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc()));
 
-       const isSafariOrIOS = Boolean(IS_ANY_SAFARI || IS_IOS);
 
-       // treat calls to play_ somewhat like the `one` event function
 
-       if (this.waitToPlay_) {
 
-         this.off(['ready', 'loadstart'], this.waitToPlay_);
 
-         this.waitToPlay_ = null;
 
-       }
 
-       // if the player/tech is not ready or the src itself is not ready
 
-       // queue up a call to play on `ready` or `loadstart`
 
-       if (!this.isReady_ || !isSrcReady) {
 
-         this.waitToPlay_ = e => {
 
-           this.play_();
 
-         };
 
-         this.one(['ready', 'loadstart'], this.waitToPlay_);
 
-         // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
 
-         // in that case, we need to prime the video element by calling load so it'll be ready in time
 
-         if (!isSrcReady && isSafariOrIOS) {
 
-           this.load();
 
-         }
 
-         return;
 
-       }
 
-       // If the player/tech is ready and we have a source, we can attempt playback.
 
-       const val = this.techGet_('play');
 
-       // For native playback, reset the progress bar if we get a play call from a replay.
 
-       const isNativeReplay = isSafariOrIOS && this.hasClass('vjs-ended');
 
-       if (isNativeReplay) {
 
-         this.resetProgressBar_();
 
-       }
 
-       // play was terminated if the returned value is null
 
-       if (val === null) {
 
-         this.runPlayTerminatedQueue_();
 
-       } else {
 
-         this.runPlayCallbacks_(val);
 
-       }
 
-     }
 
-     /**
 
-      * These functions will be run when if play is terminated. If play
 
-      * runPlayCallbacks_ is run these function will not be run. This allows us
 
-      * to differentiate between a terminated play and an actual call to play.
 
-      */
 
-     runPlayTerminatedQueue_() {
 
-       const queue = this.playTerminatedQueue_.slice(0);
 
-       this.playTerminatedQueue_ = [];
 
-       queue.forEach(function (q) {
 
-         q();
 
-       });
 
-     }
 
-     /**
 
-      * When a callback to play is delayed we have to run these
 
-      * callbacks when play is actually called on the tech. This function
 
-      * runs the callbacks that were delayed and accepts the return value
 
-      * from the tech.
 
-      *
 
-      * @param {undefined|Promise} val
 
-      *        The return value from the tech.
 
-      */
 
-     runPlayCallbacks_(val) {
 
-       const callbacks = this.playCallbacks_.slice(0);
 
-       this.playCallbacks_ = [];
 
-       // clear play terminatedQueue since we finished a real play
 
-       this.playTerminatedQueue_ = [];
 
-       callbacks.forEach(function (cb) {
 
-         cb(val);
 
-       });
 
-     }
 
-     /**
 
-      * Pause the video playback
 
-      *
 
-      * @return {Player}
 
-      *         A reference to the player object this function was called on
 
-      */
 
-     pause() {
 
-       this.techCall_('pause');
 
-     }
 
-     /**
 
-      * Check if the player is paused or has yet to play
 
-      *
 
-      * @return {boolean}
 
-      *         - false: if the media is currently playing
 
-      *         - true: if media is not currently playing
 
-      */
 
-     paused() {
 
-       // The initial state of paused should be true (in Safari it's actually false)
 
-       return this.techGet_('paused') === false ? false : true;
 
-     }
 
-     /**
 
-      * Get a TimeRange object representing the current ranges of time that the user
 
-      * has played.
 
-      *
 
-      * @return { import('./utils/time').TimeRange }
 
-      *         A time range object that represents all the increments of time that have
 
-      *         been played.
 
-      */
 
-     played() {
 
-       return this.techGet_('played') || createTimeRanges$1(0, 0);
 
-     }
 
-     /**
 
-      * Returns whether or not the user is "scrubbing". Scrubbing is
 
-      * when the user has clicked the progress bar handle and is
 
-      * dragging it along the progress bar.
 
-      *
 
-      * @param {boolean} [isScrubbing]
 
-      *        whether the user is or is not scrubbing
 
-      *
 
-      * @return {boolean}
 
-      *         The value of scrubbing when getting
 
-      */
 
-     scrubbing(isScrubbing) {
 
-       if (typeof isScrubbing === 'undefined') {
 
-         return this.scrubbing_;
 
-       }
 
-       this.scrubbing_ = !!isScrubbing;
 
-       this.techCall_('setScrubbing', this.scrubbing_);
 
-       if (isScrubbing) {
 
-         this.addClass('vjs-scrubbing');
 
-       } else {
 
-         this.removeClass('vjs-scrubbing');
 
-       }
 
-     }
 
-     /**
 
-      * Get or set the current time (in seconds)
 
-      *
 
-      * @param {number|string} [seconds]
 
-      *        The time to seek to in seconds
 
-      *
 
-      * @return {number}
 
-      *         - the current time in seconds when getting
 
-      */
 
-     currentTime(seconds) {
 
-       if (typeof seconds !== 'undefined') {
 
-         if (seconds < 0) {
 
-           seconds = 0;
 
-         }
 
-         if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) {
 
-           this.cache_.initTime = seconds;
 
-           this.off('canplay', this.boundApplyInitTime_);
 
-           this.one('canplay', this.boundApplyInitTime_);
 
-           return;
 
-         }
 
-         this.techCall_('setCurrentTime', seconds);
 
-         this.cache_.initTime = 0;
 
-         return;
 
-       }
 
-       // cache last currentTime and return. default to 0 seconds
 
-       //
 
-       // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
 
-       // currentTime when scrubbing, but may not provide much performance benefit after all.
 
-       // Should be tested. Also something has to read the actual current time or the cache will
 
-       // never get updated.
 
-       this.cache_.currentTime = this.techGet_('currentTime') || 0;
 
-       return this.cache_.currentTime;
 
-     }
 
-     /**
 
-      * Apply the value of initTime stored in cache as currentTime.
 
-      *
 
-      * @private
 
-      */
 
-     applyInitTime_() {
 
-       this.currentTime(this.cache_.initTime);
 
-     }
 
-     /**
 
-      * Normally gets the length in time of the video in seconds;
 
-      * in all but the rarest use cases an argument will NOT be passed to the method
 
-      *
 
-      * > **NOTE**: The video must have started loading before the duration can be
 
-      * known, and depending on preload behaviour may not be known until the video starts
 
-      * playing.
 
-      *
 
-      * @fires Player#durationchange
 
-      *
 
-      * @param {number} [seconds]
 
-      *        The duration of the video to set in seconds
 
-      *
 
-      * @return {number}
 
-      *         - The duration of the video in seconds when getting
 
-      */
 
-     duration(seconds) {
 
-       if (seconds === undefined) {
 
-         // return NaN if the duration is not known
 
-         return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
 
-       }
 
-       seconds = parseFloat(seconds);
 
-       // Standardize on Infinity for signaling video is live
 
-       if (seconds < 0) {
 
-         seconds = Infinity;
 
-       }
 
-       if (seconds !== this.cache_.duration) {
 
-         // Cache the last set value for optimized scrubbing
 
-         this.cache_.duration = seconds;
 
-         if (seconds === Infinity) {
 
-           this.addClass('vjs-live');
 
-         } else {
 
-           this.removeClass('vjs-live');
 
-         }
 
-         if (!isNaN(seconds)) {
 
-           // Do not fire durationchange unless the duration value is known.
 
-           // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
 
-           /**
 
-            * @event Player#durationchange
 
-            * @type {Event}
 
-            */
 
-           this.trigger('durationchange');
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Calculates how much time is left in the video. Not part
 
-      * of the native video API.
 
-      *
 
-      * @return {number}
 
-      *         The time remaining in seconds
 
-      */
 
-     remainingTime() {
 
-       return this.duration() - this.currentTime();
 
-     }
 
-     /**
 
-      * A remaining time function that is intended to be used when
 
-      * the time is to be displayed directly to the user.
 
-      *
 
-      * @return {number}
 
-      *         The rounded time remaining in seconds
 
-      */
 
-     remainingTimeDisplay() {
 
-       return Math.floor(this.duration()) - Math.floor(this.currentTime());
 
-     }
 
-     //
 
-     // Kind of like an array of portions of the video that have been downloaded.
 
-     /**
 
-      * Get a TimeRange object with an array of the times of the video
 
-      * that have been downloaded. If you just want the percent of the
 
-      * video that's been downloaded, use bufferedPercent.
 
-      *
 
-      * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
 
-      *
 
-      * @return { import('./utils/time').TimeRange }
 
-      *         A mock {@link TimeRanges} object (following HTML spec)
 
-      */
 
-     buffered() {
 
-       let buffered = this.techGet_('buffered');
 
-       if (!buffered || !buffered.length) {
 
-         buffered = createTimeRanges$1(0, 0);
 
-       }
 
-       return buffered;
 
-     }
 
-     /**
 
-      * Get the percent (as a decimal) of the video that's been downloaded.
 
-      * This method is not a part of the native HTML video API.
 
-      *
 
-      * @return {number}
 
-      *         A decimal between 0 and 1 representing the percent
 
-      *         that is buffered 0 being 0% and 1 being 100%
 
-      */
 
-     bufferedPercent() {
 
-       return bufferedPercent(this.buffered(), this.duration());
 
-     }
 
-     /**
 
-      * Get the ending time of the last buffered time range
 
-      * This is used in the progress bar to encapsulate all time ranges.
 
-      *
 
-      * @return {number}
 
-      *         The end of the last buffered time range
 
-      */
 
-     bufferedEnd() {
 
-       const buffered = this.buffered();
 
-       const duration = this.duration();
 
-       let end = buffered.end(buffered.length - 1);
 
-       if (end > duration) {
 
-         end = duration;
 
-       }
 
-       return end;
 
-     }
 
-     /**
 
-      * Get or set the current volume of the media
 
-      *
 
-      * @param  {number} [percentAsDecimal]
 
-      *         The new volume as a decimal percent:
 
-      *         - 0 is muted/0%/off
 
-      *         - 1.0 is 100%/full
 
-      *         - 0.5 is half volume or 50%
 
-      *
 
-      * @return {number}
 
-      *         The current volume as a percent when getting
 
-      */
 
-     volume(percentAsDecimal) {
 
-       let vol;
 
-       if (percentAsDecimal !== undefined) {
 
-         // Force value to between 0 and 1
 
-         vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
 
-         this.cache_.volume = vol;
 
-         this.techCall_('setVolume', vol);
 
-         if (vol > 0) {
 
-           this.lastVolume_(vol);
 
-         }
 
-         return;
 
-       }
 
-       // Default to 1 when returning current volume.
 
-       vol = parseFloat(this.techGet_('volume'));
 
-       return isNaN(vol) ? 1 : vol;
 
-     }
 
-     /**
 
-      * Get the current muted state, or turn mute on or off
 
-      *
 
-      * @param {boolean} [muted]
 
-      *        - true to mute
 
-      *        - false to unmute
 
-      *
 
-      * @return {boolean}
 
-      *         - true if mute is on and getting
 
-      *         - false if mute is off and getting
 
-      */
 
-     muted(muted) {
 
-       if (muted !== undefined) {
 
-         this.techCall_('setMuted', muted);
 
-         return;
 
-       }
 
-       return this.techGet_('muted') || false;
 
-     }
 
-     /**
 
-      * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
 
-      * indicates the state of muted on initial playback.
 
-      *
 
-      * ```js
 
-      *   var myPlayer = videojs('some-player-id');
 
-      *
 
-      *   myPlayer.src("http://www.example.com/path/to/video.mp4");
 
-      *
 
-      *   // get, should be false
 
-      *   console.log(myPlayer.defaultMuted());
 
-      *   // set to true
 
-      *   myPlayer.defaultMuted(true);
 
-      *   // get should be true
 
-      *   console.log(myPlayer.defaultMuted());
 
-      * ```
 
-      *
 
-      * @param {boolean} [defaultMuted]
 
-      *        - true to mute
 
-      *        - false to unmute
 
-      *
 
-      * @return {boolean|Player}
 
-      *         - true if defaultMuted is on and getting
 
-      *         - false if defaultMuted is off and getting
 
-      *         - A reference to the current player when setting
 
-      */
 
-     defaultMuted(defaultMuted) {
 
-       if (defaultMuted !== undefined) {
 
-         return this.techCall_('setDefaultMuted', defaultMuted);
 
-       }
 
-       return this.techGet_('defaultMuted') || false;
 
-     }
 
-     /**
 
-      * Get the last volume, or set it
 
-      *
 
-      * @param  {number} [percentAsDecimal]
 
-      *         The new last volume as a decimal percent:
 
-      *         - 0 is muted/0%/off
 
-      *         - 1.0 is 100%/full
 
-      *         - 0.5 is half volume or 50%
 
-      *
 
-      * @return {number}
 
-      *         the current value of lastVolume as a percent when getting
 
-      *
 
-      * @private
 
-      */
 
-     lastVolume_(percentAsDecimal) {
 
-       if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
 
-         this.cache_.lastVolume = percentAsDecimal;
 
-         return;
 
-       }
 
-       return this.cache_.lastVolume;
 
-     }
 
-     /**
 
-      * Check if current tech can support native fullscreen
 
-      * (e.g. with built in controls like iOS)
 
-      *
 
-      * @return {boolean}
 
-      *         if native fullscreen is supported
 
-      */
 
-     supportsFullScreen() {
 
-       return this.techGet_('supportsFullScreen') || false;
 
-     }
 
-     /**
 
-      * Check if the player is in fullscreen mode or tell the player that it
 
-      * is or is not in fullscreen mode.
 
-      *
 
-      * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
 
-      * property and instead document.fullscreenElement is used. But isFullscreen is
 
-      * still a valuable property for internal player workings.
 
-      *
 
-      * @param  {boolean} [isFS]
 
-      *         Set the players current fullscreen state
 
-      *
 
-      * @return {boolean}
 
-      *         - true if fullscreen is on and getting
 
-      *         - false if fullscreen is off and getting
 
-      */
 
-     isFullscreen(isFS) {
 
-       if (isFS !== undefined) {
 
-         const oldValue = this.isFullscreen_;
 
-         this.isFullscreen_ = Boolean(isFS);
 
-         // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange
 
-         // this is the only place where we trigger fullscreenchange events for older browsers
 
-         // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well
 
-         if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
 
-           /**
 
-              * @event Player#fullscreenchange
 
-              * @type {Event}
 
-              */
 
-           this.trigger('fullscreenchange');
 
-         }
 
-         this.toggleFullscreenClass_();
 
-         return;
 
-       }
 
-       return this.isFullscreen_;
 
-     }
 
-     /**
 
-      * Increase the size of the video to full screen
 
-      * In some browsers, full screen is not supported natively, so it enters
 
-      * "full window mode", where the video fills the browser window.
 
-      * In browsers and devices that support native full screen, sometimes the
 
-      * browser's default controls will be shown, and not the Video.js custom skin.
 
-      * This includes most mobile devices (iOS, Android) and older versions of
 
-      * Safari.
 
-      *
 
-      * @param  {Object} [fullscreenOptions]
 
-      *         Override the player fullscreen options
 
-      *
 
-      * @fires Player#fullscreenchange
 
-      */
 
-     requestFullscreen(fullscreenOptions) {
 
-       if (this.isInPictureInPicture()) {
 
-         this.exitPictureInPicture();
 
-       }
 
-       const self = this;
 
-       return new Promise((resolve, reject) => {
 
-         function offHandler() {
 
-           self.off('fullscreenerror', errorHandler);
 
-           self.off('fullscreenchange', changeHandler);
 
-         }
 
-         function changeHandler() {
 
-           offHandler();
 
-           resolve();
 
-         }
 
-         function errorHandler(e, err) {
 
-           offHandler();
 
-           reject(err);
 
-         }
 
-         self.one('fullscreenchange', changeHandler);
 
-         self.one('fullscreenerror', errorHandler);
 
-         const promise = self.requestFullscreenHelper_(fullscreenOptions);
 
-         if (promise) {
 
-           promise.then(offHandler, offHandler);
 
-           promise.then(resolve, reject);
 
-         }
 
-       });
 
-     }
 
-     requestFullscreenHelper_(fullscreenOptions) {
 
-       let fsOptions;
 
-       // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
 
-       // Use defaults or player configured option unless passed directly to this method.
 
-       if (!this.fsApi_.prefixed) {
 
-         fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
 
-         if (fullscreenOptions !== undefined) {
 
-           fsOptions = fullscreenOptions;
 
-         }
 
-       }
 
-       // This method works as follows:
 
-       // 1. if a fullscreen api is available, use it
 
-       //   1. call requestFullscreen with potential options
 
-       //   2. if we got a promise from above, use it to update isFullscreen()
 
-       // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it.
 
-       //   This is particularly used for iPhone, older iPads, and non-safari browser on iOS.
 
-       // 3. otherwise, use "fullWindow" mode
 
-       if (this.fsApi_.requestFullscreen) {
 
-         const promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
 
-         // Even on browsers with promise support this may not return a promise
 
-         if (promise) {
 
-           promise.then(() => this.isFullscreen(true), () => this.isFullscreen(false));
 
-         }
 
-         return promise;
 
-       } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
 
-         // we can't take the video.js controls fullscreen but we can go fullscreen
 
-         // with native controls
 
-         this.techCall_('enterFullScreen');
 
-       } else {
 
-         // fullscreen isn't supported so we'll just stretch the video element to
 
-         // fill the viewport
 
-         this.enterFullWindow();
 
-       }
 
-     }
 
-     /**
 
-      * Return the video to its normal size after having been in full screen mode
 
-      *
 
-      * @fires Player#fullscreenchange
 
-      */
 
-     exitFullscreen() {
 
-       const self = this;
 
-       return new Promise((resolve, reject) => {
 
-         function offHandler() {
 
-           self.off('fullscreenerror', errorHandler);
 
-           self.off('fullscreenchange', changeHandler);
 
-         }
 
-         function changeHandler() {
 
-           offHandler();
 
-           resolve();
 
-         }
 
-         function errorHandler(e, err) {
 
-           offHandler();
 
-           reject(err);
 
-         }
 
-         self.one('fullscreenchange', changeHandler);
 
-         self.one('fullscreenerror', errorHandler);
 
-         const promise = self.exitFullscreenHelper_();
 
-         if (promise) {
 
-           promise.then(offHandler, offHandler);
 
-           // map the promise to our resolve/reject methods
 
-           promise.then(resolve, reject);
 
-         }
 
-       });
 
-     }
 
-     exitFullscreenHelper_() {
 
-       if (this.fsApi_.requestFullscreen) {
 
-         const promise = document[this.fsApi_.exitFullscreen]();
 
-         // Even on browsers with promise support this may not return a promise
 
-         if (promise) {
 
-           // we're splitting the promise here, so, we want to catch the
 
-           // potential error so that this chain doesn't have unhandled errors
 
-           silencePromise(promise.then(() => this.isFullscreen(false)));
 
-         }
 
-         return promise;
 
-       } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
 
-         this.techCall_('exitFullScreen');
 
-       } else {
 
-         this.exitFullWindow();
 
-       }
 
-     }
 
-     /**
 
-      * When fullscreen isn't supported we can stretch the
 
-      * video container to as wide as the browser will let us.
 
-      *
 
-      * @fires Player#enterFullWindow
 
-      */
 
-     enterFullWindow() {
 
-       this.isFullscreen(true);
 
-       this.isFullWindow = true;
 
-       // Storing original doc overflow value to return to when fullscreen is off
 
-       this.docOrigOverflow = document.documentElement.style.overflow;
 
-       // Add listener for esc key to exit fullscreen
 
-       on(document, 'keydown', this.boundFullWindowOnEscKey_);
 
-       // Hide any scroll bars
 
-       document.documentElement.style.overflow = 'hidden';
 
-       // Apply fullscreen styles
 
-       addClass(document.body, 'vjs-full-window');
 
-       /**
 
-        * @event Player#enterFullWindow
 
-        * @type {Event}
 
-        */
 
-       this.trigger('enterFullWindow');
 
-     }
 
-     /**
 
-      * Check for call to either exit full window or
 
-      * full screen on ESC key
 
-      *
 
-      * @param {string} event
 
-      *        Event to check for key press
 
-      */
 
-     fullWindowOnEscKey(event) {
 
-       if (keycode.isEventKey(event, 'Esc')) {
 
-         if (this.isFullscreen() === true) {
 
-           if (!this.isFullWindow) {
 
-             this.exitFullscreen();
 
-           } else {
 
-             this.exitFullWindow();
 
-           }
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Exit full window
 
-      *
 
-      * @fires Player#exitFullWindow
 
-      */
 
-     exitFullWindow() {
 
-       this.isFullscreen(false);
 
-       this.isFullWindow = false;
 
-       off(document, 'keydown', this.boundFullWindowOnEscKey_);
 
-       // Unhide scroll bars.
 
-       document.documentElement.style.overflow = this.docOrigOverflow;
 
-       // Remove fullscreen styles
 
-       removeClass(document.body, 'vjs-full-window');
 
-       // Resize the box, controller, and poster to original sizes
 
-       // this.positionAll();
 
-       /**
 
-        * @event Player#exitFullWindow
 
-        * @type {Event}
 
-        */
 
-       this.trigger('exitFullWindow');
 
-     }
 
-     /**
 
-      * Disable Picture-in-Picture mode.
 
-      *
 
-      * @param {boolean} value
 
-      *                  - true will disable Picture-in-Picture mode
 
-      *                  - false will enable Picture-in-Picture mode
 
-      */
 
-     disablePictureInPicture(value) {
 
-       if (value === undefined) {
 
-         return this.techGet_('disablePictureInPicture');
 
-       }
 
-       this.techCall_('setDisablePictureInPicture', value);
 
-       this.options_.disablePictureInPicture = value;
 
-       this.trigger('disablepictureinpicturechanged');
 
-     }
 
-     /**
 
-      * Check if the player is in Picture-in-Picture mode or tell the player that it
 
-      * is or is not in Picture-in-Picture mode.
 
-      *
 
-      * @param  {boolean} [isPiP]
 
-      *         Set the players current Picture-in-Picture state
 
-      *
 
-      * @return {boolean}
 
-      *         - true if Picture-in-Picture is on and getting
 
-      *         - false if Picture-in-Picture is off and getting
 
-      */
 
-     isInPictureInPicture(isPiP) {
 
-       if (isPiP !== undefined) {
 
-         this.isInPictureInPicture_ = !!isPiP;
 
-         this.togglePictureInPictureClass_();
 
-         return;
 
-       }
 
-       return !!this.isInPictureInPicture_;
 
-     }
 
-     /**
 
-      * Create a floating video window always on top of other windows so that users may
 
-      * continue consuming media while they interact with other content sites, or
 
-      * applications on their device.
 
-      *
 
-      * This can use document picture-in-picture or element picture in picture
 
-      *
 
-      * Set `enableDocumentPictureInPicture` to `true` to use docPiP on a supported browser
 
-      * Else set `disablePictureInPicture` to `false` to disable elPiP on a supported browser
 
-      *
 
-      *
 
-      * @see [Spec]{@link https://w3c.github.io/picture-in-picture/}
 
-      * @see [Spec]{@link https://wicg.github.io/document-picture-in-picture/}
 
-      *
 
-      * @fires Player#enterpictureinpicture
 
-      *
 
-      * @return {Promise}
 
-      *         A promise with a Picture-in-Picture window.
 
-      */
 
-     requestPictureInPicture() {
 
-       if (this.options_.enableDocumentPictureInPicture && window.documentPictureInPicture) {
 
-         const pipContainer = document.createElement(this.el().tagName);
 
-         pipContainer.classList = this.el().classList;
 
-         pipContainer.classList.add('vjs-pip-container');
 
-         if (this.posterImage) {
 
-           pipContainer.appendChild(this.posterImage.el().cloneNode(true));
 
-         }
 
-         if (this.titleBar) {
 
-           pipContainer.appendChild(this.titleBar.el().cloneNode(true));
 
-         }
 
-         pipContainer.appendChild(createEl('p', {
 
-           className: 'vjs-pip-text'
 
-         }, {}, this.localize('Playing in picture-in-picture')));
 
-         return window.documentPictureInPicture.requestWindow({
 
-           // The aspect ratio won't be correct, Chrome bug https://crbug.com/1407629
 
-           initialAspectRatio: this.videoWidth() / this.videoHeight(),
 
-           copyStyleSheets: true
 
-         }).then(pipWindow => {
 
-           this.el_.parentNode.insertBefore(pipContainer, this.el_);
 
-           pipWindow.document.body.append(this.el_);
 
-           pipWindow.document.body.classList.add('vjs-pip-window');
 
-           this.player_.isInPictureInPicture(true);
 
-           this.player_.trigger('enterpictureinpicture');
 
-           // Listen for the PiP closing event to move the video back.
 
-           pipWindow.addEventListener('unload', event => {
 
-             const pipVideo = event.target.querySelector('.video-js');
 
-             pipContainer.replaceWith(pipVideo);
 
-             this.player_.isInPictureInPicture(false);
 
-             this.player_.trigger('leavepictureinpicture');
 
-           });
 
-           return pipWindow;
 
-         });
 
-       }
 
-       if ('pictureInPictureEnabled' in document && this.disablePictureInPicture() === false) {
 
-         /**
 
-          * This event fires when the player enters picture in picture mode
 
-          *
 
-          * @event Player#enterpictureinpicture
 
-          * @type {Event}
 
-          */
 
-         return this.techGet_('requestPictureInPicture');
 
-       }
 
-       return Promise.reject('No PiP mode is available');
 
-     }
 
-     /**
 
-      * Exit Picture-in-Picture mode.
 
-      *
 
-      * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
 
-      *
 
-      * @fires Player#leavepictureinpicture
 
-      *
 
-      * @return {Promise}
 
-      *         A promise.
 
-      */
 
-     exitPictureInPicture() {
 
-       if (window.documentPictureInPicture && window.documentPictureInPicture.window) {
 
-         // With documentPictureInPicture, Player#leavepictureinpicture is fired in the unload handler
 
-         window.documentPictureInPicture.window.close();
 
-         return Promise.resolve();
 
-       }
 
-       if ('pictureInPictureEnabled' in document) {
 
-         /**
 
-          * This event fires when the player leaves picture in picture mode
 
-          *
 
-          * @event Player#leavepictureinpicture
 
-          * @type {Event}
 
-          */
 
-         return document.exitPictureInPicture();
 
-       }
 
-     }
 
-     /**
 
-      * Called when this Player has focus and a key gets pressed down, or when
 
-      * any Component of this player receives a key press that it doesn't handle.
 
-      * This allows player-wide hotkeys (either as defined below, or optionally
 
-      * by an external function).
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown` event that caused this function to be called.
 
-      *
 
-      * @listens keydown
 
-      */
 
-     handleKeyDown(event) {
 
-       const {
 
-         userActions
 
-       } = this.options_;
 
-       // Bail out if hotkeys are not configured.
 
-       if (!userActions || !userActions.hotkeys) {
 
-         return;
 
-       }
 
-       // Function that determines whether or not to exclude an element from
 
-       // hotkeys handling.
 
-       const excludeElement = el => {
 
-         const tagName = el.tagName.toLowerCase();
 
-         // The first and easiest test is for `contenteditable` elements.
 
-         if (el.isContentEditable) {
 
-           return true;
 
-         }
 
-         // Inputs matching these types will still trigger hotkey handling as
 
-         // they are not text inputs.
 
-         const allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
 
-         if (tagName === 'input') {
 
-           return allowedInputTypes.indexOf(el.type) === -1;
 
-         }
 
-         // The final test is by tag name. These tags will be excluded entirely.
 
-         const excludedTags = ['textarea'];
 
-         return excludedTags.indexOf(tagName) !== -1;
 
-       };
 
-       // Bail out if the user is focused on an interactive form element.
 
-       if (excludeElement(this.el_.ownerDocument.activeElement)) {
 
-         return;
 
-       }
 
-       if (typeof userActions.hotkeys === 'function') {
 
-         userActions.hotkeys.call(this, event);
 
-       } else {
 
-         this.handleHotkeys(event);
 
-       }
 
-     }
 
-     /**
 
-      * Called when this Player receives a hotkey keydown event.
 
-      * Supported player-wide hotkeys are:
 
-      *
 
-      *   f          - toggle fullscreen
 
-      *   m          - toggle mute
 
-      *   k or Space - toggle play/pause
 
-      *
 
-      * @param {Event} event
 
-      *        The `keydown` event that caused this function to be called.
 
-      */
 
-     handleHotkeys(event) {
 
-       const hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {};
 
-       // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
 
-       const {
 
-         fullscreenKey = keydownEvent => keycode.isEventKey(keydownEvent, 'f'),
 
-         muteKey = keydownEvent => keycode.isEventKey(keydownEvent, 'm'),
 
-         playPauseKey = keydownEvent => keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space')
 
-       } = hotkeys;
 
-       if (fullscreenKey.call(this, event)) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         const FSToggle = Component$1.getComponent('FullscreenToggle');
 
-         if (document[this.fsApi_.fullscreenEnabled] !== false) {
 
-           FSToggle.prototype.handleClick.call(this, event);
 
-         }
 
-       } else if (muteKey.call(this, event)) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         const MuteToggle = Component$1.getComponent('MuteToggle');
 
-         MuteToggle.prototype.handleClick.call(this, event);
 
-       } else if (playPauseKey.call(this, event)) {
 
-         event.preventDefault();
 
-         event.stopPropagation();
 
-         const PlayToggle = Component$1.getComponent('PlayToggle');
 
-         PlayToggle.prototype.handleClick.call(this, event);
 
-       }
 
-     }
 
-     /**
 
-      * Check whether the player can play a given mimetype
 
-      *
 
-      * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
 
-      *
 
-      * @param {string} type
 
-      *        The mimetype to check
 
-      *
 
-      * @return {string}
 
-      *         'probably', 'maybe', or '' (empty string)
 
-      */
 
-     canPlayType(type) {
 
-       let can;
 
-       // Loop through each playback technology in the options order
 
-       for (let i = 0, j = this.options_.techOrder; i < j.length; i++) {
 
-         const techName = j[i];
 
-         let tech = Tech.getTech(techName);
 
-         // Support old behavior of techs being registered as components.
 
-         // Remove once that deprecated behavior is removed.
 
-         if (!tech) {
 
-           tech = Component$1.getComponent(techName);
 
-         }
 
-         // Check if the current tech is defined before continuing
 
-         if (!tech) {
 
-           log$1.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
 
-           continue;
 
-         }
 
-         // Check if the browser supports this technology
 
-         if (tech.isSupported()) {
 
-           can = tech.canPlayType(type);
 
-           if (can) {
 
-             return can;
 
-           }
 
-         }
 
-       }
 
-       return '';
 
-     }
 
-     /**
 
-      * Select source based on tech-order or source-order
 
-      * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
 
-      * defaults to tech-order selection
 
-      *
 
-      * @param {Array} sources
 
-      *        The sources for a media asset
 
-      *
 
-      * @return {Object|boolean}
 
-      *         Object of source and tech order or false
 
-      */
 
-     selectSource(sources) {
 
-       // Get only the techs specified in `techOrder` that exist and are supported by the
 
-       // current platform
 
-       const techs = this.options_.techOrder.map(techName => {
 
-         return [techName, Tech.getTech(techName)];
 
-       }).filter(([techName, tech]) => {
 
-         // Check if the current tech is defined before continuing
 
-         if (tech) {
 
-           // Check if the browser supports this technology
 
-           return tech.isSupported();
 
-         }
 
-         log$1.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
 
-         return false;
 
-       });
 
-       // Iterate over each `innerArray` element once per `outerArray` element and execute
 
-       // `tester` with both. If `tester` returns a non-falsy value, exit early and return
 
-       // that value.
 
-       const findFirstPassingTechSourcePair = function (outerArray, innerArray, tester) {
 
-         let found;
 
-         outerArray.some(outerChoice => {
 
-           return innerArray.some(innerChoice => {
 
-             found = tester(outerChoice, innerChoice);
 
-             if (found) {
 
-               return true;
 
-             }
 
-           });
 
-         });
 
-         return found;
 
-       };
 
-       let foundSourceAndTech;
 
-       const flip = fn => (a, b) => fn(b, a);
 
-       const finder = ([techName, tech], source) => {
 
-         if (tech.canPlaySource(source, this.options_[techName.toLowerCase()])) {
 
-           return {
 
-             source,
 
-             tech: techName
 
-           };
 
-         }
 
-       };
 
-       // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
 
-       // to select from them based on their priority.
 
-       if (this.options_.sourceOrder) {
 
-         // Source-first ordering
 
-         foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
 
-       } else {
 
-         // Tech-first ordering
 
-         foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
 
-       }
 
-       return foundSourceAndTech || false;
 
-     }
 
-     /**
 
-      * Executes source setting and getting logic
 
-      *
 
-      * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
 
-      *        A SourceObject, an array of SourceObjects, or a string referencing
 
-      *        a URL to a media source. It is _highly recommended_ that an object
 
-      *        or array of objects is used here, so that source selection
 
-      *        algorithms can take the `type` into account.
 
-      *
 
-      *        If not provided, this method acts as a getter.
 
-      * @param {boolean} isRetry
 
-      *        Indicates whether this is being called internally as a result of a retry
 
-      *
 
-      * @return {string|undefined}
 
-      *         If the `source` argument is missing, returns the current source
 
-      *         URL. Otherwise, returns nothing/undefined.
 
-      */
 
-     handleSrc_(source, isRetry) {
 
-       // getter usage
 
-       if (typeof source === 'undefined') {
 
-         return this.cache_.src || '';
 
-       }
 
-       // Reset retry behavior for new source
 
-       if (this.resetRetryOnError_) {
 
-         this.resetRetryOnError_();
 
-       }
 
-       // filter out invalid sources and turn our source into
 
-       // an array of source objects
 
-       const sources = filterSource(source);
 
-       // if a source was passed in then it is invalid because
 
-       // it was filtered to a zero length Array. So we have to
 
-       // show an error
 
-       if (!sources.length) {
 
-         this.setTimeout(function () {
 
-           this.error({
 
-             code: 4,
 
-             message: this.options_.notSupportedMessage
 
-           });
 
-         }, 0);
 
-         return;
 
-       }
 
-       // initial sources
 
-       this.changingSrc_ = true;
 
-       // Only update the cached source list if we are not retrying a new source after error,
 
-       // since in that case we want to include the failed source(s) in the cache
 
-       if (!isRetry) {
 
-         this.cache_.sources = sources;
 
-       }
 
-       this.updateSourceCaches_(sources[0]);
 
-       // middlewareSource is the source after it has been changed by middleware
 
-       setSource(this, sources[0], (middlewareSource, mws) => {
 
-         this.middleware_ = mws;
 
-         // since sourceSet is async we have to update the cache again after we select a source since
 
-         // the source that is selected could be out of order from the cache update above this callback.
 
-         if (!isRetry) {
 
-           this.cache_.sources = sources;
 
-         }
 
-         this.updateSourceCaches_(middlewareSource);
 
-         const err = this.src_(middlewareSource);
 
-         if (err) {
 
-           if (sources.length > 1) {
 
-             return this.handleSrc_(sources.slice(1));
 
-           }
 
-           this.changingSrc_ = false;
 
-           // We need to wrap this in a timeout to give folks a chance to add error event handlers
 
-           this.setTimeout(function () {
 
-             this.error({
 
-               code: 4,
 
-               message: this.options_.notSupportedMessage
 
-             });
 
-           }, 0);
 
-           // we could not find an appropriate tech, but let's still notify the delegate that this is it
 
-           // this needs a better comment about why this is needed
 
-           this.triggerReady();
 
-           return;
 
-         }
 
-         setTech(mws, this.tech_);
 
-       });
 
-       // Try another available source if this one fails before playback.
 
-       if (sources.length > 1) {
 
-         const retry = () => {
 
-           // Remove the error modal
 
-           this.error(null);
 
-           this.handleSrc_(sources.slice(1), true);
 
-         };
 
-         const stopListeningForErrors = () => {
 
-           this.off('error', retry);
 
-         };
 
-         this.one('error', retry);
 
-         this.one('playing', stopListeningForErrors);
 
-         this.resetRetryOnError_ = () => {
 
-           this.off('error', retry);
 
-           this.off('playing', stopListeningForErrors);
 
-         };
 
-       }
 
-     }
 
-     /**
 
-      * Get or set the video source.
 
-      *
 
-      * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
 
-      *        A SourceObject, an array of SourceObjects, or a string referencing
 
-      *        a URL to a media source. It is _highly recommended_ that an object
 
-      *        or array of objects is used here, so that source selection
 
-      *        algorithms can take the `type` into account.
 
-      *
 
-      *        If not provided, this method acts as a getter.
 
-      *
 
-      * @return {string|undefined}
 
-      *         If the `source` argument is missing, returns the current source
 
-      *         URL. Otherwise, returns nothing/undefined.
 
-      */
 
-     src(source) {
 
-       return this.handleSrc_(source, false);
 
-     }
 
-     /**
 
-      * Set the source object on the tech, returns a boolean that indicates whether
 
-      * there is a tech that can play the source or not
 
-      *
 
-      * @param {Tech~SourceObject} source
 
-      *        The source object to set on the Tech
 
-      *
 
-      * @return {boolean}
 
-      *         - True if there is no Tech to playback this source
 
-      *         - False otherwise
 
-      *
 
-      * @private
 
-      */
 
-     src_(source) {
 
-       const sourceTech = this.selectSource([source]);
 
-       if (!sourceTech) {
 
-         return true;
 
-       }
 
-       if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
 
-         this.changingSrc_ = true;
 
-         // load this technology with the chosen source
 
-         this.loadTech_(sourceTech.tech, sourceTech.source);
 
-         this.tech_.ready(() => {
 
-           this.changingSrc_ = false;
 
-         });
 
-         return false;
 
-       }
 
-       // wait until the tech is ready to set the source
 
-       // and set it synchronously if possible (#2326)
 
-       this.ready(function () {
 
-         // The setSource tech method was added with source handlers
 
-         // so older techs won't support it
 
-         // We need to check the direct prototype for the case where subclasses
 
-         // of the tech do not support source handlers
 
-         if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
 
-           this.techCall_('setSource', source);
 
-         } else {
 
-           this.techCall_('src', source.src);
 
-         }
 
-         this.changingSrc_ = false;
 
-       }, true);
 
-       return false;
 
-     }
 
-     /**
 
-      * Begin loading the src data.
 
-      */
 
-     load() {
 
-       this.techCall_('load');
 
-     }
 
-     /**
 
-      * Reset the player. Loads the first tech in the techOrder,
 
-      * removes all the text tracks in the existing `tech`,
 
-      * and calls `reset` on the `tech`.
 
-      */
 
-     reset() {
 
-       if (this.paused()) {
 
-         this.doReset_();
 
-       } else {
 
-         const playPromise = this.play();
 
-         silencePromise(playPromise.then(() => this.doReset_()));
 
-       }
 
-     }
 
-     doReset_() {
 
-       if (this.tech_) {
 
-         this.tech_.clearTracks('text');
 
-       }
 
-       this.resetCache_();
 
-       this.poster('');
 
-       this.loadTech_(this.options_.techOrder[0], null);
 
-       this.techCall_('reset');
 
-       this.resetControlBarUI_();
 
-       if (isEvented(this)) {
 
-         this.trigger('playerreset');
 
-       }
 
-     }
 
-     /**
 
-      * Reset Control Bar's UI by calling sub-methods that reset
 
-      * all of Control Bar's components
 
-      */
 
-     resetControlBarUI_() {
 
-       this.resetProgressBar_();
 
-       this.resetPlaybackRate_();
 
-       this.resetVolumeBar_();
 
-     }
 
-     /**
 
-      * Reset tech's progress so progress bar is reset in the UI
 
-      */
 
-     resetProgressBar_() {
 
-       this.currentTime(0);
 
-       const {
 
-         currentTimeDisplay,
 
-         durationDisplay,
 
-         progressControl,
 
-         remainingTimeDisplay
 
-       } = this.controlBar || {};
 
-       const {
 
-         seekBar
 
-       } = progressControl || {};
 
-       if (currentTimeDisplay) {
 
-         currentTimeDisplay.updateContent();
 
-       }
 
-       if (durationDisplay) {
 
-         durationDisplay.updateContent();
 
-       }
 
-       if (remainingTimeDisplay) {
 
-         remainingTimeDisplay.updateContent();
 
-       }
 
-       if (seekBar) {
 
-         seekBar.update();
 
-         if (seekBar.loadProgressBar) {
 
-           seekBar.loadProgressBar.update();
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Reset Playback ratio
 
-      */
 
-     resetPlaybackRate_() {
 
-       this.playbackRate(this.defaultPlaybackRate());
 
-       this.handleTechRateChange_();
 
-     }
 
-     /**
 
-      * Reset Volume bar
 
-      */
 
-     resetVolumeBar_() {
 
-       this.volume(1.0);
 
-       this.trigger('volumechange');
 
-     }
 
-     /**
 
-      * Returns all of the current source objects.
 
-      *
 
-      * @return {Tech~SourceObject[]}
 
-      *         The current source objects
 
-      */
 
-     currentSources() {
 
-       const source = this.currentSource();
 
-       const sources = [];
 
-       // assume `{}` or `{ src }`
 
-       if (Object.keys(source).length !== 0) {
 
-         sources.push(source);
 
-       }
 
-       return this.cache_.sources || sources;
 
-     }
 
-     /**
 
-      * Returns the current source object.
 
-      *
 
-      * @return {Tech~SourceObject}
 
-      *         The current source object
 
-      */
 
-     currentSource() {
 
-       return this.cache_.source || {};
 
-     }
 
-     /**
 
-      * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
 
-      * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
 
-      *
 
-      * @return {string}
 
-      *         The current source
 
-      */
 
-     currentSrc() {
 
-       return this.currentSource() && this.currentSource().src || '';
 
-     }
 
-     /**
 
-      * Get the current source type e.g. video/mp4
 
-      * This can allow you rebuild the current source object so that you could load the same
 
-      * source and tech later
 
-      *
 
-      * @return {string}
 
-      *         The source MIME type
 
-      */
 
-     currentType() {
 
-       return this.currentSource() && this.currentSource().type || '';
 
-     }
 
-     /**
 
-      * Get or set the preload attribute
 
-      *
 
-      * @param {boolean} [value]
 
-      *        - true means that we should preload
 
-      *        - false means that we should not preload
 
-      *
 
-      * @return {string}
 
-      *         The preload attribute value when getting
 
-      */
 
-     preload(value) {
 
-       if (value !== undefined) {
 
-         this.techCall_('setPreload', value);
 
-         this.options_.preload = value;
 
-         return;
 
-       }
 
-       return this.techGet_('preload');
 
-     }
 
-     /**
 
-      * Get or set the autoplay option. When this is a boolean it will
 
-      * modify the attribute on the tech. When this is a string the attribute on
 
-      * the tech will be removed and `Player` will handle autoplay on loadstarts.
 
-      *
 
-      * @param {boolean|string} [value]
 
-      *        - true: autoplay using the browser behavior
 
-      *        - false: do not autoplay
 
-      *        - 'play': call play() on every loadstart
 
-      *        - 'muted': call muted() then play() on every loadstart
 
-      *        - 'any': call play() on every loadstart. if that fails call muted() then play().
 
-      *        - *: values other than those listed here will be set `autoplay` to true
 
-      *
 
-      * @return {boolean|string}
 
-      *         The current value of autoplay when getting
 
-      */
 
-     autoplay(value) {
 
-       // getter usage
 
-       if (value === undefined) {
 
-         return this.options_.autoplay || false;
 
-       }
 
-       let techAutoplay;
 
-       // if the value is a valid string set it to that, or normalize `true` to 'play', if need be
 
-       if (typeof value === 'string' && /(any|play|muted)/.test(value) || value === true && this.options_.normalizeAutoplay) {
 
-         this.options_.autoplay = value;
 
-         this.manualAutoplay_(typeof value === 'string' ? value : 'play');
 
-         techAutoplay = false;
 
-         // any falsy value sets autoplay to false in the browser,
 
-         // lets do the same
 
-       } else if (!value) {
 
-         this.options_.autoplay = false;
 
-         // any other value (ie truthy) sets autoplay to true
 
-       } else {
 
-         this.options_.autoplay = true;
 
-       }
 
-       techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay;
 
-       // if we don't have a tech then we do not queue up
 
-       // a setAutoplay call on tech ready. We do this because the
 
-       // autoplay option will be passed in the constructor and we
 
-       // do not need to set it twice
 
-       if (this.tech_) {
 
-         this.techCall_('setAutoplay', techAutoplay);
 
-       }
 
-     }
 
-     /**
 
-      * Set or unset the playsinline attribute.
 
-      * Playsinline tells the browser that non-fullscreen playback is preferred.
 
-      *
 
-      * @param {boolean} [value]
 
-      *        - true means that we should try to play inline by default
 
-      *        - false means that we should use the browser's default playback mode,
 
-      *          which in most cases is inline. iOS Safari is a notable exception
 
-      *          and plays fullscreen by default.
 
-      *
 
-      * @return {string|Player}
 
-      *         - the current value of playsinline
 
-      *         - the player when setting
 
-      *
 
-      * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
 
-      */
 
-     playsinline(value) {
 
-       if (value !== undefined) {
 
-         this.techCall_('setPlaysinline', value);
 
-         this.options_.playsinline = value;
 
-         return this;
 
-       }
 
-       return this.techGet_('playsinline');
 
-     }
 
-     /**
 
-      * Get or set the loop attribute on the video element.
 
-      *
 
-      * @param {boolean} [value]
 
-      *        - true means that we should loop the video
 
-      *        - false means that we should not loop the video
 
-      *
 
-      * @return {boolean}
 
-      *         The current value of loop when getting
 
-      */
 
-     loop(value) {
 
-       if (value !== undefined) {
 
-         this.techCall_('setLoop', value);
 
-         this.options_.loop = value;
 
-         return;
 
-       }
 
-       return this.techGet_('loop');
 
-     }
 
-     /**
 
-      * Get or set the poster image source url
 
-      *
 
-      * @fires Player#posterchange
 
-      *
 
-      * @param {string} [src]
 
-      *        Poster image source URL
 
-      *
 
-      * @return {string}
 
-      *         The current value of poster when getting
 
-      */
 
-     poster(src) {
 
-       if (src === undefined) {
 
-         return this.poster_;
 
-       }
 
-       // The correct way to remove a poster is to set as an empty string
 
-       // other falsey values will throw errors
 
-       if (!src) {
 
-         src = '';
 
-       }
 
-       if (src === this.poster_) {
 
-         return;
 
-       }
 
-       // update the internal poster variable
 
-       this.poster_ = src;
 
-       // update the tech's poster
 
-       this.techCall_('setPoster', src);
 
-       this.isPosterFromTech_ = false;
 
-       // alert components that the poster has been set
 
-       /**
 
-        * This event fires when the poster image is changed on the player.
 
-        *
 
-        * @event Player#posterchange
 
-        * @type {Event}
 
-        */
 
-       this.trigger('posterchange');
 
-     }
 
-     /**
 
-      * Some techs (e.g. YouTube) can provide a poster source in an
 
-      * asynchronous way. We want the poster component to use this
 
-      * poster source so that it covers up the tech's controls.
 
-      * (YouTube's play button). However we only want to use this
 
-      * source if the player user hasn't set a poster through
 
-      * the normal APIs.
 
-      *
 
-      * @fires Player#posterchange
 
-      * @listens Tech#posterchange
 
-      * @private
 
-      */
 
-     handleTechPosterChange_() {
 
-       if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
 
-         const newPoster = this.tech_.poster() || '';
 
-         if (newPoster !== this.poster_) {
 
-           this.poster_ = newPoster;
 
-           this.isPosterFromTech_ = true;
 
-           // Let components know the poster has changed
 
-           this.trigger('posterchange');
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Get or set whether or not the controls are showing.
 
-      *
 
-      * @fires Player#controlsenabled
 
-      *
 
-      * @param {boolean} [bool]
 
-      *        - true to turn controls on
 
-      *        - false to turn controls off
 
-      *
 
-      * @return {boolean}
 
-      *         The current value of controls when getting
 
-      */
 
-     controls(bool) {
 
-       if (bool === undefined) {
 
-         return !!this.controls_;
 
-       }
 
-       bool = !!bool;
 
-       // Don't trigger a change event unless it actually changed
 
-       if (this.controls_ === bool) {
 
-         return;
 
-       }
 
-       this.controls_ = bool;
 
-       if (this.usingNativeControls()) {
 
-         this.techCall_('setControls', bool);
 
-       }
 
-       if (this.controls_) {
 
-         this.removeClass('vjs-controls-disabled');
 
-         this.addClass('vjs-controls-enabled');
 
-         /**
 
-          * @event Player#controlsenabled
 
-          * @type {Event}
 
-          */
 
-         this.trigger('controlsenabled');
 
-         if (!this.usingNativeControls()) {
 
-           this.addTechControlsListeners_();
 
-         }
 
-       } else {
 
-         this.removeClass('vjs-controls-enabled');
 
-         this.addClass('vjs-controls-disabled');
 
-         /**
 
-          * @event Player#controlsdisabled
 
-          * @type {Event}
 
-          */
 
-         this.trigger('controlsdisabled');
 
-         if (!this.usingNativeControls()) {
 
-           this.removeTechControlsListeners_();
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Toggle native controls on/off. Native controls are the controls built into
 
-      * devices (e.g. default iPhone controls) or other techs
 
-      * (e.g. Vimeo Controls)
 
-      * **This should only be set by the current tech, because only the tech knows
 
-      * if it can support native controls**
 
-      *
 
-      * @fires Player#usingnativecontrols
 
-      * @fires Player#usingcustomcontrols
 
-      *
 
-      * @param {boolean} [bool]
 
-      *        - true to turn native controls on
 
-      *        - false to turn native controls off
 
-      *
 
-      * @return {boolean}
 
-      *         The current value of native controls when getting
 
-      */
 
-     usingNativeControls(bool) {
 
-       if (bool === undefined) {
 
-         return !!this.usingNativeControls_;
 
-       }
 
-       bool = !!bool;
 
-       // Don't trigger a change event unless it actually changed
 
-       if (this.usingNativeControls_ === bool) {
 
-         return;
 
-       }
 
-       this.usingNativeControls_ = bool;
 
-       if (this.usingNativeControls_) {
 
-         this.addClass('vjs-using-native-controls');
 
-         /**
 
-          * player is using the native device controls
 
-          *
 
-          * @event Player#usingnativecontrols
 
-          * @type {Event}
 
-          */
 
-         this.trigger('usingnativecontrols');
 
-       } else {
 
-         this.removeClass('vjs-using-native-controls');
 
-         /**
 
-          * player is using the custom HTML controls
 
-          *
 
-          * @event Player#usingcustomcontrols
 
-          * @type {Event}
 
-          */
 
-         this.trigger('usingcustomcontrols');
 
-       }
 
-     }
 
-     /**
 
-      * Set or get the current MediaError
 
-      *
 
-      * @fires Player#error
 
-      *
 
-      * @param  {MediaError|string|number} [err]
 
-      *         A MediaError or a string/number to be turned
 
-      *         into a MediaError
 
-      *
 
-      * @return {MediaError|null}
 
-      *         The current MediaError when getting (or null)
 
-      */
 
-     error(err) {
 
-       if (err === undefined) {
 
-         return this.error_ || null;
 
-       }
 
-       // allow hooks to modify error object
 
-       hooks('beforeerror').forEach(hookFunction => {
 
-         const newErr = hookFunction(this, err);
 
-         if (!(isObject$1(newErr) && !Array.isArray(newErr) || typeof newErr === 'string' || typeof newErr === 'number' || newErr === null)) {
 
-           this.log.error('please return a value that MediaError expects in beforeerror hooks');
 
-           return;
 
-         }
 
-         err = newErr;
 
-       });
 
-       // Suppress the first error message for no compatible source until
 
-       // user interaction
 
-       if (this.options_.suppressNotSupportedError && err && err.code === 4) {
 
-         const triggerSuppressedError = function () {
 
-           this.error(err);
 
-         };
 
-         this.options_.suppressNotSupportedError = false;
 
-         this.any(['click', 'touchstart'], triggerSuppressedError);
 
-         this.one('loadstart', function () {
 
-           this.off(['click', 'touchstart'], triggerSuppressedError);
 
-         });
 
-         return;
 
-       }
 
-       // restoring to default
 
-       if (err === null) {
 
-         this.error_ = err;
 
-         this.removeClass('vjs-error');
 
-         if (this.errorDisplay) {
 
-           this.errorDisplay.close();
 
-         }
 
-         return;
 
-       }
 
-       this.error_ = new MediaError(err);
 
-       // add the vjs-error classname to the player
 
-       this.addClass('vjs-error');
 
-       // log the name of the error type and any message
 
-       // IE11 logs "[object object]" and required you to expand message to see error object
 
-       log$1.error(`(CODE:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`, this.error_.message, this.error_);
 
-       /**
 
-        * @event Player#error
 
-        * @type {Event}
 
-        */
 
-       this.trigger('error');
 
-       // notify hooks of the per player error
 
-       hooks('error').forEach(hookFunction => hookFunction(this, this.error_));
 
-       return;
 
-     }
 
-     /**
 
-      * Report user activity
 
-      *
 
-      * @param {Object} event
 
-      *        Event object
 
-      */
 
-     reportUserActivity(event) {
 
-       this.userActivity_ = true;
 
-     }
 
-     /**
 
-      * Get/set if user is active
 
-      *
 
-      * @fires Player#useractive
 
-      * @fires Player#userinactive
 
-      *
 
-      * @param {boolean} [bool]
 
-      *        - true if the user is active
 
-      *        - false if the user is inactive
 
-      *
 
-      * @return {boolean}
 
-      *         The current value of userActive when getting
 
-      */
 
-     userActive(bool) {
 
-       if (bool === undefined) {
 
-         return this.userActive_;
 
-       }
 
-       bool = !!bool;
 
-       if (bool === this.userActive_) {
 
-         return;
 
-       }
 
-       this.userActive_ = bool;
 
-       if (this.userActive_) {
 
-         this.userActivity_ = true;
 
-         this.removeClass('vjs-user-inactive');
 
-         this.addClass('vjs-user-active');
 
-         /**
 
-          * @event Player#useractive
 
-          * @type {Event}
 
-          */
 
-         this.trigger('useractive');
 
-         return;
 
-       }
 
-       // Chrome/Safari/IE have bugs where when you change the cursor it can
 
-       // trigger a mousemove event. This causes an issue when you're hiding
 
-       // the cursor when the user is inactive, and a mousemove signals user
 
-       // activity. Making it impossible to go into inactive mode. Specifically
 
-       // this happens in fullscreen when we really need to hide the cursor.
 
-       //
 
-       // When this gets resolved in ALL browsers it can be removed
 
-       // https://code.google.com/p/chromium/issues/detail?id=103041
 
-       if (this.tech_) {
 
-         this.tech_.one('mousemove', function (e) {
 
-           e.stopPropagation();
 
-           e.preventDefault();
 
-         });
 
-       }
 
-       this.userActivity_ = false;
 
-       this.removeClass('vjs-user-active');
 
-       this.addClass('vjs-user-inactive');
 
-       /**
 
-        * @event Player#userinactive
 
-        * @type {Event}
 
-        */
 
-       this.trigger('userinactive');
 
-     }
 
-     /**
 
-      * Listen for user activity based on timeout value
 
-      *
 
-      * @private
 
-      */
 
-     listenForUserActivity_() {
 
-       let mouseInProgress;
 
-       let lastMoveX;
 
-       let lastMoveY;
 
-       const handleActivity = bind_(this, this.reportUserActivity);
 
-       const handleMouseMove = function (e) {
 
-         // #1068 - Prevent mousemove spamming
 
-         // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
 
-         if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
 
-           lastMoveX = e.screenX;
 
-           lastMoveY = e.screenY;
 
-           handleActivity();
 
-         }
 
-       };
 
-       const handleMouseDown = function () {
 
-         handleActivity();
 
-         // For as long as the they are touching the device or have their mouse down,
 
-         // we consider them active even if they're not moving their finger or mouse.
 
-         // So we want to continue to update that they are active
 
-         this.clearInterval(mouseInProgress);
 
-         // Setting userActivity=true now and setting the interval to the same time
 
-         // as the activityCheck interval (250) should ensure we never miss the
 
-         // next activityCheck
 
-         mouseInProgress = this.setInterval(handleActivity, 250);
 
-       };
 
-       const handleMouseUpAndMouseLeave = function (event) {
 
-         handleActivity();
 
-         // Stop the interval that maintains activity if the mouse/touch is down
 
-         this.clearInterval(mouseInProgress);
 
-       };
 
-       // Any mouse movement will be considered user activity
 
-       this.on('mousedown', handleMouseDown);
 
-       this.on('mousemove', handleMouseMove);
 
-       this.on('mouseup', handleMouseUpAndMouseLeave);
 
-       this.on('mouseleave', handleMouseUpAndMouseLeave);
 
-       const controlBar = this.getChild('controlBar');
 
-       // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
 
-       // controlBar would no longer be hidden by default timeout.
 
-       if (controlBar && !IS_IOS && !IS_ANDROID) {
 
-         controlBar.on('mouseenter', function (event) {
 
-           if (this.player().options_.inactivityTimeout !== 0) {
 
-             this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
 
-           }
 
-           this.player().options_.inactivityTimeout = 0;
 
-         });
 
-         controlBar.on('mouseleave', function (event) {
 
-           this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
 
-         });
 
-       }
 
-       // Listen for keyboard navigation
 
-       // Shouldn't need to use inProgress interval because of key repeat
 
-       this.on('keydown', handleActivity);
 
-       this.on('keyup', handleActivity);
 
-       // Run an interval every 250 milliseconds instead of stuffing everything into
 
-       // the mousemove/touchmove function itself, to prevent performance degradation.
 
-       // `this.reportUserActivity` simply sets this.userActivity_ to true, which
 
-       // then gets picked up by this loop
 
-       // http://ejohn.org/blog/learning-from-twitter/
 
-       let inactivityTimeout;
 
-       this.setInterval(function () {
 
-         // Check to see if mouse/touch activity has happened
 
-         if (!this.userActivity_) {
 
-           return;
 
-         }
 
-         // Reset the activity tracker
 
-         this.userActivity_ = false;
 
-         // If the user state was inactive, set the state to active
 
-         this.userActive(true);
 
-         // Clear any existing inactivity timeout to start the timer over
 
-         this.clearTimeout(inactivityTimeout);
 
-         const timeout = this.options_.inactivityTimeout;
 
-         if (timeout <= 0) {
 
-           return;
 
-         }
 
-         // In <timeout> milliseconds, if no more activity has occurred the
 
-         // user will be considered inactive
 
-         inactivityTimeout = this.setTimeout(function () {
 
-           // Protect against the case where the inactivityTimeout can trigger just
 
-           // before the next user activity is picked up by the activity check loop
 
-           // causing a flicker
 
-           if (!this.userActivity_) {
 
-             this.userActive(false);
 
-           }
 
-         }, timeout);
 
-       }, 250);
 
-     }
 
-     /**
 
-      * Gets or sets the current playback rate. A playback rate of
 
-      * 1.0 represents normal speed and 0.5 would indicate half-speed
 
-      * playback, for instance.
 
-      *
 
-      * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
 
-      *
 
-      * @param {number} [rate]
 
-      *       New playback rate to set.
 
-      *
 
-      * @return {number}
 
-      *         The current playback rate when getting or 1.0
 
-      */
 
-     playbackRate(rate) {
 
-       if (rate !== undefined) {
 
-         // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
 
-         // that is registered above
 
-         this.techCall_('setPlaybackRate', rate);
 
-         return;
 
-       }
 
-       if (this.tech_ && this.tech_.featuresPlaybackRate) {
 
-         return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
 
-       }
 
-       return 1.0;
 
-     }
 
-     /**
 
-      * Gets or sets the current default playback rate. A default playback rate of
 
-      * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
 
-      * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
 
-      * not the current playbackRate.
 
-      *
 
-      * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
 
-      *
 
-      * @param {number} [rate]
 
-      *       New default playback rate to set.
 
-      *
 
-      * @return {number|Player}
 
-      *         - The default playback rate when getting or 1.0
 
-      *         - the player when setting
 
-      */
 
-     defaultPlaybackRate(rate) {
 
-       if (rate !== undefined) {
 
-         return this.techCall_('setDefaultPlaybackRate', rate);
 
-       }
 
-       if (this.tech_ && this.tech_.featuresPlaybackRate) {
 
-         return this.techGet_('defaultPlaybackRate');
 
-       }
 
-       return 1.0;
 
-     }
 
-     /**
 
-      * Gets or sets the audio flag
 
-      *
 
-      * @param {boolean} bool
 
-      *        - true signals that this is an audio player
 
-      *        - false signals that this is not an audio player
 
-      *
 
-      * @return {boolean}
 
-      *         The current value of isAudio when getting
 
-      */
 
-     isAudio(bool) {
 
-       if (bool !== undefined) {
 
-         this.isAudio_ = !!bool;
 
-         return;
 
-       }
 
-       return !!this.isAudio_;
 
-     }
 
-     enableAudioOnlyUI_() {
 
-       // Update styling immediately to show the control bar so we can get its height
 
-       this.addClass('vjs-audio-only-mode');
 
-       const playerChildren = this.children();
 
-       const controlBar = this.getChild('ControlBar');
 
-       const controlBarHeight = controlBar && controlBar.currentHeight();
 
-       // Hide all player components except the control bar. Control bar components
 
-       // needed only for video are hidden with CSS
 
-       playerChildren.forEach(child => {
 
-         if (child === controlBar) {
 
-           return;
 
-         }
 
-         if (child.el_ && !child.hasClass('vjs-hidden')) {
 
-           child.hide();
 
-           this.audioOnlyCache_.hiddenChildren.push(child);
 
-         }
 
-       });
 
-       this.audioOnlyCache_.playerHeight = this.currentHeight();
 
-       // Set the player height the same as the control bar
 
-       this.height(controlBarHeight);
 
-       this.trigger('audioonlymodechange');
 
-     }
 
-     disableAudioOnlyUI_() {
 
-       this.removeClass('vjs-audio-only-mode');
 
-       // Show player components that were previously hidden
 
-       this.audioOnlyCache_.hiddenChildren.forEach(child => child.show());
 
-       // Reset player height
 
-       this.height(this.audioOnlyCache_.playerHeight);
 
-       this.trigger('audioonlymodechange');
 
-     }
 
-     /**
 
-      * Get the current audioOnlyMode state or set audioOnlyMode to true or false.
 
-      *
 
-      * Setting this to `true` will hide all player components except the control bar,
 
-      * as well as control bar components needed only for video.
 
-      *
 
-      * @param {boolean} [value]
 
-      *         The value to set audioOnlyMode to.
 
-      *
 
-      * @return {Promise|boolean}
 
-      *        A Promise is returned when setting the state, and a boolean when getting
 
-      *        the present state
 
-      */
 
-     audioOnlyMode(value) {
 
-       if (typeof value !== 'boolean' || value === this.audioOnlyMode_) {
 
-         return this.audioOnlyMode_;
 
-       }
 
-       this.audioOnlyMode_ = value;
 
-       // Enable Audio Only Mode
 
-       if (value) {
 
-         const exitPromises = [];
 
-         // Fullscreen and PiP are not supported in audioOnlyMode, so exit if we need to.
 
-         if (this.isInPictureInPicture()) {
 
-           exitPromises.push(this.exitPictureInPicture());
 
-         }
 
-         if (this.isFullscreen()) {
 
-           exitPromises.push(this.exitFullscreen());
 
-         }
 
-         if (this.audioPosterMode()) {
 
-           exitPromises.push(this.audioPosterMode(false));
 
-         }
 
-         return Promise.all(exitPromises).then(() => this.enableAudioOnlyUI_());
 
-       }
 
-       // Disable Audio Only Mode
 
-       return Promise.resolve().then(() => this.disableAudioOnlyUI_());
 
-     }
 
-     enablePosterModeUI_() {
 
-       // Hide the video element and show the poster image to enable posterModeUI
 
-       const tech = this.tech_ && this.tech_;
 
-       tech.hide();
 
-       this.addClass('vjs-audio-poster-mode');
 
-       this.trigger('audiopostermodechange');
 
-     }
 
-     disablePosterModeUI_() {
 
-       // Show the video element and hide the poster image to disable posterModeUI
 
-       const tech = this.tech_ && this.tech_;
 
-       tech.show();
 
-       this.removeClass('vjs-audio-poster-mode');
 
-       this.trigger('audiopostermodechange');
 
-     }
 
-     /**
 
-      * Get the current audioPosterMode state or set audioPosterMode to true or false
 
-      *
 
-      * @param {boolean} [value]
 
-      *         The value to set audioPosterMode to.
 
-      *
 
-      * @return {Promise|boolean}
 
-      *         A Promise is returned when setting the state, and a boolean when getting
 
-      *        the present state
 
-      */
 
-     audioPosterMode(value) {
 
-       if (typeof value !== 'boolean' || value === this.audioPosterMode_) {
 
-         return this.audioPosterMode_;
 
-       }
 
-       this.audioPosterMode_ = value;
 
-       if (value) {
 
-         if (this.audioOnlyMode()) {
 
-           const audioOnlyModePromise = this.audioOnlyMode(false);
 
-           return audioOnlyModePromise.then(() => {
 
-             // enable audio poster mode after audio only mode is disabled
 
-             this.enablePosterModeUI_();
 
-           });
 
-         }
 
-         return Promise.resolve().then(() => {
 
-           // enable audio poster mode
 
-           this.enablePosterModeUI_();
 
-         });
 
-       }
 
-       return Promise.resolve().then(() => {
 
-         // disable audio poster mode
 
-         this.disablePosterModeUI_();
 
-       });
 
-     }
 
-     /**
 
-      * A helper method for adding a {@link TextTrack} to our
 
-      * {@link TextTrackList}.
 
-      *
 
-      * In addition to the W3C settings we allow adding additional info through options.
 
-      *
 
-      * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
 
-      *
 
-      * @param {string} [kind]
 
-      *        the kind of TextTrack you are adding
 
-      *
 
-      * @param {string} [label]
 
-      *        the label to give the TextTrack label
 
-      *
 
-      * @param {string} [language]
 
-      *        the language to set on the TextTrack
 
-      *
 
-      * @return {TextTrack|undefined}
 
-      *         the TextTrack that was added or undefined
 
-      *         if there is no tech
 
-      */
 
-     addTextTrack(kind, label, language) {
 
-       if (this.tech_) {
 
-         return this.tech_.addTextTrack(kind, label, language);
 
-       }
 
-     }
 
-     /**
 
-      * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}.
 
-      *
 
-      * @param {Object} options
 
-      *        Options to pass to {@link HTMLTrackElement} during creation. See
 
-      *        {@link HTMLTrackElement} for object properties that you should use.
 
-      *
 
-      * @param {boolean} [manualCleanup=false] if set to true, the TextTrack will not be removed
 
-      *                                        from the TextTrackList and HtmlTrackElementList
 
-      *                                        after a source change
 
-      *
 
-      * @return { import('./tracks/html-track-element').default }
 
-      *         the HTMLTrackElement that was created and added
 
-      *         to the HtmlTrackElementList and the remote
 
-      *         TextTrackList
 
-      *
 
-      */
 
-     addRemoteTextTrack(options, manualCleanup) {
 
-       if (this.tech_) {
 
-         return this.tech_.addRemoteTextTrack(options, manualCleanup);
 
-       }
 
-     }
 
-     /**
 
-      * Remove a remote {@link TextTrack} from the respective
 
-      * {@link TextTrackList} and {@link HtmlTrackElementList}.
 
-      *
 
-      * @param {Object} track
 
-      *        Remote {@link TextTrack} to remove
 
-      *
 
-      * @return {undefined}
 
-      *         does not return anything
 
-      */
 
-     removeRemoteTextTrack(obj = {}) {
 
-       let {
 
-         track
 
-       } = obj;
 
-       if (!track) {
 
-         track = obj;
 
-       }
 
-       // destructure the input into an object with a track argument, defaulting to arguments[0]
 
-       // default the whole argument to an empty object if nothing was passed in
 
-       if (this.tech_) {
 
-         return this.tech_.removeRemoteTextTrack(track);
 
-       }
 
-     }
 
-     /**
 
-      * Gets available media playback quality metrics as specified by the W3C's Media
 
-      * Playback Quality API.
 
-      *
 
-      * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
 
-      *
 
-      * @return {Object|undefined}
 
-      *         An object with supported media playback quality metrics or undefined if there
 
-      *         is no tech or the tech does not support it.
 
-      */
 
-     getVideoPlaybackQuality() {
 
-       return this.techGet_('getVideoPlaybackQuality');
 
-     }
 
-     /**
 
-      * Get video width
 
-      *
 
-      * @return {number}
 
-      *         current video width
 
-      */
 
-     videoWidth() {
 
-       return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
 
-     }
 
-     /**
 
-      * Get video height
 
-      *
 
-      * @return {number}
 
-      *         current video height
 
-      */
 
-     videoHeight() {
 
-       return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
 
-     }
 
-     /**
 
-      * The player's language code.
 
-      *
 
-      * Changing the language will trigger
 
-      * [languagechange]{@link Player#event:languagechange}
 
-      * which Components can use to update control text.
 
-      * ClickableComponent will update its control text by default on
 
-      * [languagechange]{@link Player#event:languagechange}.
 
-      *
 
-      * @fires Player#languagechange
 
-      *
 
-      * @param {string} [code]
 
-      *        the language code to set the player to
 
-      *
 
-      * @return {string}
 
-      *         The current language code when getting
 
-      */
 
-     language(code) {
 
-       if (code === undefined) {
 
-         return this.language_;
 
-       }
 
-       if (this.language_ !== String(code).toLowerCase()) {
 
-         this.language_ = String(code).toLowerCase();
 
-         // during first init, it's possible some things won't be evented
 
-         if (isEvented(this)) {
 
-           /**
 
-           * fires when the player language change
 
-           *
 
-           * @event Player#languagechange
 
-           * @type {Event}
 
-           */
 
-           this.trigger('languagechange');
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Get the player's language dictionary
 
-      * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
 
-      * Languages specified directly in the player options have precedence
 
-      *
 
-      * @return {Array}
 
-      *         An array of of supported languages
 
-      */
 
-     languages() {
 
-       return merge$2(Player.prototype.options_.languages, this.languages_);
 
-     }
 
-     /**
 
-      * returns a JavaScript object representing the current track
 
-      * information. **DOES not return it as JSON**
 
-      *
 
-      * @return {Object}
 
-      *         Object representing the current of track info
 
-      */
 
-     toJSON() {
 
-       const options = merge$2(this.options_);
 
-       const tracks = options.tracks;
 
-       options.tracks = [];
 
-       for (let i = 0; i < tracks.length; i++) {
 
-         let track = tracks[i];
 
-         // deep merge tracks and null out player so no circular references
 
-         track = merge$2(track);
 
-         track.player = undefined;
 
-         options.tracks[i] = track;
 
-       }
 
-       return options;
 
-     }
 
-     /**
 
-      * Creates a simple modal dialog (an instance of the {@link ModalDialog}
 
-      * component) that immediately overlays the player with arbitrary
 
-      * content and removes itself when closed.
 
-      *
 
-      * @param {string|Function|Element|Array|null} content
 
-      *        Same as {@link ModalDialog#content}'s param of the same name.
 
-      *        The most straight-forward usage is to provide a string or DOM
 
-      *        element.
 
-      *
 
-      * @param {Object} [options]
 
-      *        Extra options which will be passed on to the {@link ModalDialog}.
 
-      *
 
-      * @return {ModalDialog}
 
-      *         the {@link ModalDialog} that was created
 
-      */
 
-     createModal(content, options) {
 
-       options = options || {};
 
-       options.content = content || '';
 
-       const modal = new ModalDialog(this, options);
 
-       this.addChild(modal);
 
-       modal.on('dispose', () => {
 
-         this.removeChild(modal);
 
-       });
 
-       modal.open();
 
-       return modal;
 
-     }
 
-     /**
 
-      * Change breakpoint classes when the player resizes.
 
-      *
 
-      * @private
 
-      */
 
-     updateCurrentBreakpoint_() {
 
-       if (!this.responsive()) {
 
-         return;
 
-       }
 
-       const currentBreakpoint = this.currentBreakpoint();
 
-       const currentWidth = this.currentWidth();
 
-       for (let i = 0; i < BREAKPOINT_ORDER.length; i++) {
 
-         const candidateBreakpoint = BREAKPOINT_ORDER[i];
 
-         const maxWidth = this.breakpoints_[candidateBreakpoint];
 
-         if (currentWidth <= maxWidth) {
 
-           // The current breakpoint did not change, nothing to do.
 
-           if (currentBreakpoint === candidateBreakpoint) {
 
-             return;
 
-           }
 
-           // Only remove a class if there is a current breakpoint.
 
-           if (currentBreakpoint) {
 
-             this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
 
-           }
 
-           this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
 
-           this.breakpoint_ = candidateBreakpoint;
 
-           break;
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Removes the current breakpoint.
 
-      *
 
-      * @private
 
-      */
 
-     removeCurrentBreakpoint_() {
 
-       const className = this.currentBreakpointClass();
 
-       this.breakpoint_ = '';
 
-       if (className) {
 
-         this.removeClass(className);
 
-       }
 
-     }
 
-     /**
 
-      * Get or set breakpoints on the player.
 
-      *
 
-      * Calling this method with an object or `true` will remove any previous
 
-      * custom breakpoints and start from the defaults again.
 
-      *
 
-      * @param  {Object|boolean} [breakpoints]
 
-      *         If an object is given, it can be used to provide custom
 
-      *         breakpoints. If `true` is given, will set default breakpoints.
 
-      *         If this argument is not given, will simply return the current
 
-      *         breakpoints.
 
-      *
 
-      * @param  {number} [breakpoints.tiny]
 
-      *         The maximum width for the "vjs-layout-tiny" class.
 
-      *
 
-      * @param  {number} [breakpoints.xsmall]
 
-      *         The maximum width for the "vjs-layout-x-small" class.
 
-      *
 
-      * @param  {number} [breakpoints.small]
 
-      *         The maximum width for the "vjs-layout-small" class.
 
-      *
 
-      * @param  {number} [breakpoints.medium]
 
-      *         The maximum width for the "vjs-layout-medium" class.
 
-      *
 
-      * @param  {number} [breakpoints.large]
 
-      *         The maximum width for the "vjs-layout-large" class.
 
-      *
 
-      * @param  {number} [breakpoints.xlarge]
 
-      *         The maximum width for the "vjs-layout-x-large" class.
 
-      *
 
-      * @param  {number} [breakpoints.huge]
 
-      *         The maximum width for the "vjs-layout-huge" class.
 
-      *
 
-      * @return {Object}
 
-      *         An object mapping breakpoint names to maximum width values.
 
-      */
 
-     breakpoints(breakpoints) {
 
-       // Used as a getter.
 
-       if (breakpoints === undefined) {
 
-         return Object.assign(this.breakpoints_);
 
-       }
 
-       this.breakpoint_ = '';
 
-       this.breakpoints_ = Object.assign({}, DEFAULT_BREAKPOINTS, breakpoints);
 
-       // When breakpoint definitions change, we need to update the currently
 
-       // selected breakpoint.
 
-       this.updateCurrentBreakpoint_();
 
-       // Clone the breakpoints before returning.
 
-       return Object.assign(this.breakpoints_);
 
-     }
 
-     /**
 
-      * Get or set a flag indicating whether or not this player should adjust
 
-      * its UI based on its dimensions.
 
-      *
 
-      * @param  {boolean} value
 
-      *         Should be `true` if the player should adjust its UI based on its
 
-      *         dimensions; otherwise, should be `false`.
 
-      *
 
-      * @return {boolean}
 
-      *         Will be `true` if this player should adjust its UI based on its
 
-      *         dimensions; otherwise, will be `false`.
 
-      */
 
-     responsive(value) {
 
-       // Used as a getter.
 
-       if (value === undefined) {
 
-         return this.responsive_;
 
-       }
 
-       value = Boolean(value);
 
-       const current = this.responsive_;
 
-       // Nothing changed.
 
-       if (value === current) {
 
-         return;
 
-       }
 
-       // The value actually changed, set it.
 
-       this.responsive_ = value;
 
-       // Start listening for breakpoints and set the initial breakpoint if the
 
-       // player is now responsive.
 
-       if (value) {
 
-         this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
 
-         this.updateCurrentBreakpoint_();
 
-         // Stop listening for breakpoints if the player is no longer responsive.
 
-       } else {
 
-         this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
 
-         this.removeCurrentBreakpoint_();
 
-       }
 
-       return value;
 
-     }
 
-     /**
 
-      * Get current breakpoint name, if any.
 
-      *
 
-      * @return {string}
 
-      *         If there is currently a breakpoint set, returns a the key from the
 
-      *         breakpoints object matching it. Otherwise, returns an empty string.
 
-      */
 
-     currentBreakpoint() {
 
-       return this.breakpoint_;
 
-     }
 
-     /**
 
-      * Get the current breakpoint class name.
 
-      *
 
-      * @return {string}
 
-      *         The matching class name (e.g. `"vjs-layout-tiny"` or
 
-      *         `"vjs-layout-large"`) for the current breakpoint. Empty string if
 
-      *         there is no current breakpoint.
 
-      */
 
-     currentBreakpointClass() {
 
-       return BREAKPOINT_CLASSES[this.breakpoint_] || '';
 
-     }
 
-     /**
 
-      * An object that describes a single piece of media.
 
-      *
 
-      * Properties that are not part of this type description will be retained; so,
 
-      * this can be viewed as a generic metadata storage mechanism as well.
 
-      *
 
-      * @see      {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
 
-      * @typedef  {Object} Player~MediaObject
 
-      *
 
-      * @property {string} [album]
 
-      *           Unused, except if this object is passed to the `MediaSession`
 
-      *           API.
 
-      *
 
-      * @property {string} [artist]
 
-      *           Unused, except if this object is passed to the `MediaSession`
 
-      *           API.
 
-      *
 
-      * @property {Object[]} [artwork]
 
-      *           Unused, except if this object is passed to the `MediaSession`
 
-      *           API. If not specified, will be populated via the `poster`, if
 
-      *           available.
 
-      *
 
-      * @property {string} [poster]
 
-      *           URL to an image that will display before playback.
 
-      *
 
-      * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
 
-      *           A single source object, an array of source objects, or a string
 
-      *           referencing a URL to a media source. It is _highly recommended_
 
-      *           that an object or array of objects is used here, so that source
 
-      *           selection algorithms can take the `type` into account.
 
-      *
 
-      * @property {string} [title]
 
-      *           Unused, except if this object is passed to the `MediaSession`
 
-      *           API.
 
-      *
 
-      * @property {Object[]} [textTracks]
 
-      *           An array of objects to be used to create text tracks, following
 
-      *           the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
 
-      *           For ease of removal, these will be created as "remote" text
 
-      *           tracks and set to automatically clean up on source changes.
 
-      *
 
-      *           These objects may have properties like `src`, `kind`, `label`,
 
-      *           and `language`, see {@link Tech#createRemoteTextTrack}.
 
-      */
 
-     /**
 
-      * Populate the player using a {@link Player~MediaObject|MediaObject}.
 
-      *
 
-      * @param  {Player~MediaObject} media
 
-      *         A media object.
 
-      *
 
-      * @param  {Function} ready
 
-      *         A callback to be called when the player is ready.
 
-      */
 
-     loadMedia(media, ready) {
 
-       if (!media || typeof media !== 'object') {
 
-         return;
 
-       }
 
-       this.reset();
 
-       // Clone the media object so it cannot be mutated from outside.
 
-       this.cache_.media = merge$2(media);
 
-       const {
 
-         artist,
 
-         artwork,
 
-         description,
 
-         poster,
 
-         src,
 
-         textTracks,
 
-         title
 
-       } = this.cache_.media;
 
-       // If `artwork` is not given, create it using `poster`.
 
-       if (!artwork && poster) {
 
-         this.cache_.media.artwork = [{
 
-           src: poster,
 
-           type: getMimetype(poster)
 
-         }];
 
-       }
 
-       if (src) {
 
-         this.src(src);
 
-       }
 
-       if (poster) {
 
-         this.poster(poster);
 
-       }
 
-       if (Array.isArray(textTracks)) {
 
-         textTracks.forEach(tt => this.addRemoteTextTrack(tt, false));
 
-       }
 
-       if (this.titleBar) {
 
-         this.titleBar.update({
 
-           title,
 
-           description: description || artist || ''
 
-         });
 
-       }
 
-       this.ready(ready);
 
-     }
 
-     /**
 
-      * Get a clone of the current {@link Player~MediaObject} for this player.
 
-      *
 
-      * If the `loadMedia` method has not been used, will attempt to return a
 
-      * {@link Player~MediaObject} based on the current state of the player.
 
-      *
 
-      * @return {Player~MediaObject}
 
-      */
 
-     getMedia() {
 
-       if (!this.cache_.media) {
 
-         const poster = this.poster();
 
-         const src = this.currentSources();
 
-         const textTracks = Array.prototype.map.call(this.remoteTextTracks(), tt => ({
 
-           kind: tt.kind,
 
-           label: tt.label,
 
-           language: tt.language,
 
-           src: tt.src
 
-         }));
 
-         const media = {
 
-           src,
 
-           textTracks
 
-         };
 
-         if (poster) {
 
-           media.poster = poster;
 
-           media.artwork = [{
 
-             src: media.poster,
 
-             type: getMimetype(media.poster)
 
-           }];
 
-         }
 
-         return media;
 
-       }
 
-       return merge$2(this.cache_.media);
 
-     }
 
-     /**
 
-      * Gets tag settings
 
-      *
 
-      * @param {Element} tag
 
-      *        The player tag
 
-      *
 
-      * @return {Object}
 
-      *         An object containing all of the settings
 
-      *         for a player tag
 
-      */
 
-     static getTagSettings(tag) {
 
-       const baseOptions = {
 
-         sources: [],
 
-         tracks: []
 
-       };
 
-       const tagOptions = getAttributes(tag);
 
-       const dataSetup = tagOptions['data-setup'];
 
-       if (hasClass(tag, 'vjs-fill')) {
 
-         tagOptions.fill = true;
 
-       }
 
-       if (hasClass(tag, 'vjs-fluid')) {
 
-         tagOptions.fluid = true;
 
-       }
 
-       // Check if data-setup attr exists.
 
-       if (dataSetup !== null) {
 
-         // Parse options JSON
 
-         // If empty string, make it a parsable json object.
 
-         const [err, data] = tuple(dataSetup || '{}');
 
-         if (err) {
 
-           log$1.error(err);
 
-         }
 
-         Object.assign(tagOptions, data);
 
-       }
 
-       Object.assign(baseOptions, tagOptions);
 
-       // Get tag children settings
 
-       if (tag.hasChildNodes()) {
 
-         const children = tag.childNodes;
 
-         for (let i = 0, j = children.length; i < j; i++) {
 
-           const child = children[i];
 
-           // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
 
-           const childName = child.nodeName.toLowerCase();
 
-           if (childName === 'source') {
 
-             baseOptions.sources.push(getAttributes(child));
 
-           } else if (childName === 'track') {
 
-             baseOptions.tracks.push(getAttributes(child));
 
-           }
 
-         }
 
-       }
 
-       return baseOptions;
 
-     }
 
-     /**
 
-      * Set debug mode to enable/disable logs at info level.
 
-      *
 
-      * @param {boolean} enabled
 
-      * @fires Player#debugon
 
-      * @fires Player#debugoff
 
-      */
 
-     debug(enabled) {
 
-       if (enabled === undefined) {
 
-         return this.debugEnabled_;
 
-       }
 
-       if (enabled) {
 
-         this.trigger('debugon');
 
-         this.previousLogLevel_ = this.log.level;
 
-         this.log.level('debug');
 
-         this.debugEnabled_ = true;
 
-       } else {
 
-         this.trigger('debugoff');
 
-         this.log.level(this.previousLogLevel_);
 
-         this.previousLogLevel_ = undefined;
 
-         this.debugEnabled_ = false;
 
-       }
 
-     }
 
-     /**
 
-      * Set or get current playback rates.
 
-      * Takes an array and updates the playback rates menu with the new items.
 
-      * Pass in an empty array to hide the menu.
 
-      * Values other than arrays are ignored.
 
-      *
 
-      * @fires Player#playbackrateschange
 
-      * @param {number[]} newRates
 
-      *                   The new rates that the playback rates menu should update to.
 
-      *                   An empty array will hide the menu
 
-      * @return {number[]} When used as a getter will return the current playback rates
 
-      */
 
-     playbackRates(newRates) {
 
-       if (newRates === undefined) {
 
-         return this.cache_.playbackRates;
 
-       }
 
-       // ignore any value that isn't an array
 
-       if (!Array.isArray(newRates)) {
 
-         return;
 
-       }
 
-       // ignore any arrays that don't only contain numbers
 
-       if (!newRates.every(rate => typeof rate === 'number')) {
 
-         return;
 
-       }
 
-       this.cache_.playbackRates = newRates;
 
-       /**
 
-       * fires when the playback rates in a player are changed
 
-       *
 
-       * @event Player#playbackrateschange
 
-       * @type {Event}
 
-       */
 
-       this.trigger('playbackrateschange');
 
-     }
 
-   }
 
-   /**
 
-    * Get the {@link VideoTrackList}
 
-    *
 
-    * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
 
-    *
 
-    * @return {VideoTrackList}
 
-    *         the current video track list
 
-    *
 
-    * @method Player.prototype.videoTracks
 
-    */
 
-   /**
 
-    * Get the {@link AudioTrackList}
 
-    *
 
-    * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
 
-    *
 
-    * @return {AudioTrackList}
 
-    *         the current audio track list
 
-    *
 
-    * @method Player.prototype.audioTracks
 
-    */
 
-   /**
 
-    * Get the {@link TextTrackList}
 
-    *
 
-    * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
 
-    *
 
-    * @return {TextTrackList}
 
-    *         the current text track list
 
-    *
 
-    * @method Player.prototype.textTracks
 
-    */
 
-   /**
 
-    * Get the remote {@link TextTrackList}
 
-    *
 
-    * @return {TextTrackList}
 
-    *         The current remote text track list
 
-    *
 
-    * @method Player.prototype.remoteTextTracks
 
-    */
 
-   /**
 
-    * Get the remote {@link HtmlTrackElementList} tracks.
 
-    *
 
-    * @return {HtmlTrackElementList}
 
-    *         The current remote text track element list
 
-    *
 
-    * @method Player.prototype.remoteTextTrackEls
 
-    */
 
-   ALL.names.forEach(function (name) {
 
-     const props = ALL[name];
 
-     Player.prototype[props.getterName] = function () {
 
-       if (this.tech_) {
 
-         return this.tech_[props.getterName]();
 
-       }
 
-       // if we have not yet loadTech_, we create {video,audio,text}Tracks_
 
-       // these will be passed to the tech during loading
 
-       this[props.privateName] = this[props.privateName] || new props.ListClass();
 
-       return this[props.privateName];
 
-     };
 
-   });
 
-   /**
 
-    * Get or set the `Player`'s crossorigin option. For the HTML5 player, this
 
-    * sets the `crossOrigin` property on the `<video>` tag to control the CORS
 
-    * behavior.
 
-    *
 
-    * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
 
-    *
 
-    * @param {string} [value]
 
-    *        The value to set the `Player`'s crossorigin to. If an argument is
 
-    *        given, must be one of `anonymous` or `use-credentials`.
 
-    *
 
-    * @return {string|undefined}
 
-    *         - The current crossorigin value of the `Player` when getting.
 
-    *         - undefined when setting
 
-    */
 
-   Player.prototype.crossorigin = Player.prototype.crossOrigin;
 
-   /**
 
-    * Global enumeration of players.
 
-    *
 
-    * The keys are the player IDs and the values are either the {@link Player}
 
-    * instance or `null` for disposed players.
 
-    *
 
-    * @type {Object}
 
-    */
 
-   Player.players = {};
 
-   const navigator = window.navigator;
 
-   /*
 
-    * Player instance options, surfaced using options
 
-    * options = Player.prototype.options_
 
-    * Make changes in options, not here.
 
-    *
 
-    * @type {Object}
 
-    * @private
 
-    */
 
-   Player.prototype.options_ = {
 
-     // Default order of fallback technology
 
-     techOrder: Tech.defaultTechOrder_,
 
-     html5: {},
 
-     // enable sourceset by default
 
-     enableSourceset: true,
 
-     // default inactivity timeout
 
-     inactivityTimeout: 2000,
 
-     // default playback rates
 
-     playbackRates: [],
 
-     // Add playback rate selection by adding rates
 
-     // 'playbackRates': [0.5, 1, 1.5, 2],
 
-     liveui: false,
 
-     // Included control sets
 
-     children: ['mediaLoader', 'posterImage', 'titleBar', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
 
-     language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
 
-     // locales and their language translations
 
-     languages: {},
 
-     // Default message to show when a video cannot be played.
 
-     notSupportedMessage: 'No compatible source was found for this media.',
 
-     normalizeAutoplay: false,
 
-     fullscreen: {
 
-       options: {
 
-         navigationUI: 'hide'
 
-       }
 
-     },
 
-     breakpoints: {},
 
-     responsive: false,
 
-     audioOnlyMode: false,
 
-     audioPosterMode: false
 
-   };
 
-   [
 
-   /**
 
-    * Returns whether or not the player is in the "ended" state.
 
-    *
 
-    * @return {Boolean} True if the player is in the ended state, false if not.
 
-    * @method Player#ended
 
-    */
 
-   'ended',
 
-   /**
 
-    * Returns whether or not the player is in the "seeking" state.
 
-    *
 
-    * @return {Boolean} True if the player is in the seeking state, false if not.
 
-    * @method Player#seeking
 
-    */
 
-   'seeking',
 
-   /**
 
-    * Returns the TimeRanges of the media that are currently available
 
-    * for seeking to.
 
-    *
 
-    * @return {TimeRanges} the seekable intervals of the media timeline
 
-    * @method Player#seekable
 
-    */
 
-   'seekable',
 
-   /**
 
-    * Returns the current state of network activity for the element, from
 
-    * the codes in the list below.
 
-    * - NETWORK_EMPTY (numeric value 0)
 
-    *   The element has not yet been initialised. All attributes are in
 
-    *   their initial states.
 
-    * - NETWORK_IDLE (numeric value 1)
 
-    *   The element's resource selection algorithm is active and has
 
-    *   selected a resource, but it is not actually using the network at
 
-    *   this time.
 
-    * - NETWORK_LOADING (numeric value 2)
 
-    *   The user agent is actively trying to download data.
 
-    * - NETWORK_NO_SOURCE (numeric value 3)
 
-    *   The element's resource selection algorithm is active, but it has
 
-    *   not yet found a resource to use.
 
-    *
 
-    * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
 
-    * @return {number} the current network activity state
 
-    * @method Player#networkState
 
-    */
 
-   'networkState',
 
-   /**
 
-    * Returns a value that expresses the current state of the element
 
-    * with respect to rendering the current playback position, from the
 
-    * codes in the list below.
 
-    * - HAVE_NOTHING (numeric value 0)
 
-    *   No information regarding the media resource is available.
 
-    * - HAVE_METADATA (numeric value 1)
 
-    *   Enough of the resource has been obtained that the duration of the
 
-    *   resource is available.
 
-    * - HAVE_CURRENT_DATA (numeric value 2)
 
-    *   Data for the immediate current playback position is available.
 
-    * - HAVE_FUTURE_DATA (numeric value 3)
 
-    *   Data for the immediate current playback position is available, as
 
-    *   well as enough data for the user agent to advance the current
 
-    *   playback position in the direction of playback.
 
-    * - HAVE_ENOUGH_DATA (numeric value 4)
 
-    *   The user agent estimates that enough data is available for
 
-    *   playback to proceed uninterrupted.
 
-    *
 
-    * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
 
-    * @return {number} the current playback rendering state
 
-    * @method Player#readyState
 
-    */
 
-   'readyState'].forEach(function (fn) {
 
-     Player.prototype[fn] = function () {
 
-       return this.techGet_(fn);
 
-     };
 
-   });
 
-   TECH_EVENTS_RETRIGGER.forEach(function (event) {
 
-     Player.prototype[`handleTech${toTitleCase$1(event)}_`] = function () {
 
-       return this.trigger(event);
 
-     };
 
-   });
 
-   /**
 
-    * Fired when the player has initial duration and dimension information
 
-    *
 
-    * @event Player#loadedmetadata
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Fired when the player has downloaded data at the current playback position
 
-    *
 
-    * @event Player#loadeddata
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Fired when the current playback position has changed *
 
-    * During playback this is fired every 15-250 milliseconds, depending on the
 
-    * playback technology in use.
 
-    *
 
-    * @event Player#timeupdate
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Fired when the volume changes
 
-    *
 
-    * @event Player#volumechange
 
-    * @type {Event}
 
-    */
 
-   /**
 
-    * Reports whether or not a player has a plugin available.
 
-    *
 
-    * This does not report whether or not the plugin has ever been initialized
 
-    * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
 
-    *
 
-    * @method Player#hasPlugin
 
-    * @param  {string}  name
 
-    *         The name of a plugin.
 
-    *
 
-    * @return {boolean}
 
-    *         Whether or not this player has the requested plugin available.
 
-    */
 
-   /**
 
-    * Reports whether or not a player is using a plugin by name.
 
-    *
 
-    * For basic plugins, this only reports whether the plugin has _ever_ been
 
-    * initialized on this player.
 
-    *
 
-    * @method Player#usingPlugin
 
-    * @param  {string} name
 
-    *         The name of a plugin.
 
-    *
 
-    * @return {boolean}
 
-    *         Whether or not this player is using the requested plugin.
 
-    */
 
-   Component$1.registerComponent('Player', Player);
 
-   /**
 
-    * @file plugin.js
 
-    */
 
-   /**
 
-    * The base plugin name.
 
-    *
 
-    * @private
 
-    * @constant
 
-    * @type {string}
 
-    */
 
-   const BASE_PLUGIN_NAME = 'plugin';
 
-   /**
 
-    * The key on which a player's active plugins cache is stored.
 
-    *
 
-    * @private
 
-    * @constant
 
-    * @type     {string}
 
-    */
 
-   const PLUGIN_CACHE_KEY = 'activePlugins_';
 
-   /**
 
-    * Stores registered plugins in a private space.
 
-    *
 
-    * @private
 
-    * @type    {Object}
 
-    */
 
-   const pluginStorage = {};
 
-   /**
 
-    * Reports whether or not a plugin has been registered.
 
-    *
 
-    * @private
 
-    * @param   {string} name
 
-    *          The name of a plugin.
 
-    *
 
-    * @return {boolean}
 
-    *          Whether or not the plugin has been registered.
 
-    */
 
-   const pluginExists = name => pluginStorage.hasOwnProperty(name);
 
-   /**
 
-    * Get a single registered plugin by name.
 
-    *
 
-    * @private
 
-    * @param   {string} name
 
-    *          The name of a plugin.
 
-    *
 
-    * @return {typeof Plugin|Function|undefined}
 
-    *          The plugin (or undefined).
 
-    */
 
-   const getPlugin = name => pluginExists(name) ? pluginStorage[name] : undefined;
 
-   /**
 
-    * Marks a plugin as "active" on a player.
 
-    *
 
-    * Also, ensures that the player has an object for tracking active plugins.
 
-    *
 
-    * @private
 
-    * @param   {Player} player
 
-    *          A Video.js player instance.
 
-    *
 
-    * @param   {string} name
 
-    *          The name of a plugin.
 
-    */
 
-   const markPluginAsActive = (player, name) => {
 
-     player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
 
-     player[PLUGIN_CACHE_KEY][name] = true;
 
-   };
 
-   /**
 
-    * Triggers a pair of plugin setup events.
 
-    *
 
-    * @private
 
-    * @param  {Player} player
 
-    *         A Video.js player instance.
 
-    *
 
-    * @param  {Plugin~PluginEventHash} hash
 
-    *         A plugin event hash.
 
-    *
 
-    * @param  {boolean} [before]
 
-    *         If true, prefixes the event name with "before". In other words,
 
-    *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
 
-    */
 
-   const triggerSetupEvent = (player, hash, before) => {
 
-     const eventName = (before ? 'before' : '') + 'pluginsetup';
 
-     player.trigger(eventName, hash);
 
-     player.trigger(eventName + ':' + hash.name, hash);
 
-   };
 
-   /**
 
-    * Takes a basic plugin function and returns a wrapper function which marks
 
-    * on the player that the plugin has been activated.
 
-    *
 
-    * @private
 
-    * @param   {string} name
 
-    *          The name of the plugin.
 
-    *
 
-    * @param   {Function} plugin
 
-    *          The basic plugin.
 
-    *
 
-    * @return {Function}
 
-    *          A wrapper function for the given plugin.
 
-    */
 
-   const createBasicPlugin = function (name, plugin) {
 
-     const basicPluginWrapper = function () {
 
-       // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
 
-       // regardless, but we want the hash to be consistent with the hash provided
 
-       // for advanced plugins.
 
-       //
 
-       // The only potentially counter-intuitive thing here is the `instance` in
 
-       // the "pluginsetup" event is the value returned by the `plugin` function.
 
-       triggerSetupEvent(this, {
 
-         name,
 
-         plugin,
 
-         instance: null
 
-       }, true);
 
-       const instance = plugin.apply(this, arguments);
 
-       markPluginAsActive(this, name);
 
-       triggerSetupEvent(this, {
 
-         name,
 
-         plugin,
 
-         instance
 
-       });
 
-       return instance;
 
-     };
 
-     Object.keys(plugin).forEach(function (prop) {
 
-       basicPluginWrapper[prop] = plugin[prop];
 
-     });
 
-     return basicPluginWrapper;
 
-   };
 
-   /**
 
-    * Takes a plugin sub-class and returns a factory function for generating
 
-    * instances of it.
 
-    *
 
-    * This factory function will replace itself with an instance of the requested
 
-    * sub-class of Plugin.
 
-    *
 
-    * @private
 
-    * @param   {string} name
 
-    *          The name of the plugin.
 
-    *
 
-    * @param   {Plugin} PluginSubClass
 
-    *          The advanced plugin.
 
-    *
 
-    * @return {Function}
 
-    */
 
-   const createPluginFactory = (name, PluginSubClass) => {
 
-     // Add a `name` property to the plugin prototype so that each plugin can
 
-     // refer to itself by name.
 
-     PluginSubClass.prototype.name = name;
 
-     return function (...args) {
 
-       triggerSetupEvent(this, {
 
-         name,
 
-         plugin: PluginSubClass,
 
-         instance: null
 
-       }, true);
 
-       const instance = new PluginSubClass(...[this, ...args]);
 
-       // The plugin is replaced by a function that returns the current instance.
 
-       this[name] = () => instance;
 
-       triggerSetupEvent(this, instance.getEventHash());
 
-       return instance;
 
-     };
 
-   };
 
-   /**
 
-    * Parent class for all advanced plugins.
 
-    *
 
-    * @mixes   module:evented~EventedMixin
 
-    * @mixes   module:stateful~StatefulMixin
 
-    * @fires   Player#beforepluginsetup
 
-    * @fires   Player#beforepluginsetup:$name
 
-    * @fires   Player#pluginsetup
 
-    * @fires   Player#pluginsetup:$name
 
-    * @listens Player#dispose
 
-    * @throws  {Error}
 
-    *          If attempting to instantiate the base {@link Plugin} class
 
-    *          directly instead of via a sub-class.
 
-    */
 
-   class Plugin {
 
-     /**
 
-      * Creates an instance of this class.
 
-      *
 
-      * Sub-classes should call `super` to ensure plugins are properly initialized.
 
-      *
 
-      * @param {Player} player
 
-      *        A Video.js player instance.
 
-      */
 
-     constructor(player) {
 
-       if (this.constructor === Plugin) {
 
-         throw new Error('Plugin must be sub-classed; not directly instantiated.');
 
-       }
 
-       this.player = player;
 
-       if (!this.log) {
 
-         this.log = this.player.log.createLogger(this.name);
 
-       }
 
-       // Make this object evented, but remove the added `trigger` method so we
 
-       // use the prototype version instead.
 
-       evented(this);
 
-       delete this.trigger;
 
-       stateful(this, this.constructor.defaultState);
 
-       markPluginAsActive(player, this.name);
 
-       // Auto-bind the dispose method so we can use it as a listener and unbind
 
-       // it later easily.
 
-       this.dispose = this.dispose.bind(this);
 
-       // If the player is disposed, dispose the plugin.
 
-       player.on('dispose', this.dispose);
 
-     }
 
-     /**
 
-      * Get the version of the plugin that was set on <pluginName>.VERSION
 
-      */
 
-     version() {
 
-       return this.constructor.VERSION;
 
-     }
 
-     /**
 
-      * Each event triggered by plugins includes a hash of additional data with
 
-      * conventional properties.
 
-      *
 
-      * This returns that object or mutates an existing hash.
 
-      *
 
-      * @param   {Object} [hash={}]
 
-      *          An object to be used as event an event hash.
 
-      *
 
-      * @return {Plugin~PluginEventHash}
 
-      *          An event hash object with provided properties mixed-in.
 
-      */
 
-     getEventHash(hash = {}) {
 
-       hash.name = this.name;
 
-       hash.plugin = this.constructor;
 
-       hash.instance = this;
 
-       return hash;
 
-     }
 
-     /**
 
-      * Triggers an event on the plugin object and overrides
 
-      * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
 
-      *
 
-      * @param   {string|Object} event
 
-      *          An event type or an object with a type property.
 
-      *
 
-      * @param   {Object} [hash={}]
 
-      *          Additional data hash to merge with a
 
-      *          {@link Plugin~PluginEventHash|PluginEventHash}.
 
-      *
 
-      * @return {boolean}
 
-      *          Whether or not default was prevented.
 
-      */
 
-     trigger(event, hash = {}) {
 
-       return trigger(this.eventBusEl_, event, this.getEventHash(hash));
 
-     }
 
-     /**
 
-      * Handles "statechanged" events on the plugin. No-op by default, override by
 
-      * subclassing.
 
-      *
 
-      * @abstract
 
-      * @param    {Event} e
 
-      *           An event object provided by a "statechanged" event.
 
-      *
 
-      * @param    {Object} e.changes
 
-      *           An object describing changes that occurred with the "statechanged"
 
-      *           event.
 
-      */
 
-     handleStateChanged(e) {}
 
-     /**
 
-      * Disposes a plugin.
 
-      *
 
-      * Subclasses can override this if they want, but for the sake of safety,
 
-      * it's probably best to subscribe the "dispose" event.
 
-      *
 
-      * @fires Plugin#dispose
 
-      */
 
-     dispose() {
 
-       const {
 
-         name,
 
-         player
 
-       } = this;
 
-       /**
 
-        * Signals that a advanced plugin is about to be disposed.
 
-        *
 
-        * @event Plugin#dispose
 
-        * @type  {Event}
 
-        */
 
-       this.trigger('dispose');
 
-       this.off();
 
-       player.off('dispose', this.dispose);
 
-       // Eliminate any possible sources of leaking memory by clearing up
 
-       // references between the player and the plugin instance and nulling out
 
-       // the plugin's state and replacing methods with a function that throws.
 
-       player[PLUGIN_CACHE_KEY][name] = false;
 
-       this.player = this.state = null;
 
-       // Finally, replace the plugin name on the player with a new factory
 
-       // function, so that the plugin is ready to be set up again.
 
-       player[name] = createPluginFactory(name, pluginStorage[name]);
 
-     }
 
-     /**
 
-      * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
 
-      *
 
-      * @param   {string|Function} plugin
 
-      *          If a string, matches the name of a plugin. If a function, will be
 
-      *          tested directly.
 
-      *
 
-      * @return {boolean}
 
-      *          Whether or not a plugin is a basic plugin.
 
-      */
 
-     static isBasic(plugin) {
 
-       const p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
 
-       return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
 
-     }
 
-     /**
 
-      * Register a Video.js plugin.
 
-      *
 
-      * @param   {string} name
 
-      *          The name of the plugin to be registered. Must be a string and
 
-      *          must not match an existing plugin or a method on the `Player`
 
-      *          prototype.
 
-      *
 
-      * @param   {typeof Plugin|Function} plugin
 
-      *          A sub-class of `Plugin` or a function for basic plugins.
 
-      *
 
-      * @return {typeof Plugin|Function}
 
-      *          For advanced plugins, a factory function for that plugin. For
 
-      *          basic plugins, a wrapper function that initializes the plugin.
 
-      */
 
-     static registerPlugin(name, plugin) {
 
-       if (typeof name !== 'string') {
 
-         throw new Error(`Illegal plugin name, "${name}", must be a string, was ${typeof name}.`);
 
-       }
 
-       if (pluginExists(name)) {
 
-         log$1.warn(`A plugin named "${name}" already exists. You may want to avoid re-registering plugins!`);
 
-       } else if (Player.prototype.hasOwnProperty(name)) {
 
-         throw new Error(`Illegal plugin name, "${name}", cannot share a name with an existing player method!`);
 
-       }
 
-       if (typeof plugin !== 'function') {
 
-         throw new Error(`Illegal plugin for "${name}", must be a function, was ${typeof plugin}.`);
 
-       }
 
-       pluginStorage[name] = plugin;
 
-       // Add a player prototype method for all sub-classed plugins (but not for
 
-       // the base Plugin class).
 
-       if (name !== BASE_PLUGIN_NAME) {
 
-         if (Plugin.isBasic(plugin)) {
 
-           Player.prototype[name] = createBasicPlugin(name, plugin);
 
-         } else {
 
-           Player.prototype[name] = createPluginFactory(name, plugin);
 
-         }
 
-       }
 
-       return plugin;
 
-     }
 
-     /**
 
-      * De-register a Video.js plugin.
 
-      *
 
-      * @param  {string} name
 
-      *         The name of the plugin to be de-registered. Must be a string that
 
-      *         matches an existing plugin.
 
-      *
 
-      * @throws {Error}
 
-      *         If an attempt is made to de-register the base plugin.
 
-      */
 
-     static deregisterPlugin(name) {
 
-       if (name === BASE_PLUGIN_NAME) {
 
-         throw new Error('Cannot de-register base plugin.');
 
-       }
 
-       if (pluginExists(name)) {
 
-         delete pluginStorage[name];
 
-         delete Player.prototype[name];
 
-       }
 
-     }
 
-     /**
 
-      * Gets an object containing multiple Video.js plugins.
 
-      *
 
-      * @param   {Array} [names]
 
-      *          If provided, should be an array of plugin names. Defaults to _all_
 
-      *          plugin names.
 
-      *
 
-      * @return {Object|undefined}
 
-      *          An object containing plugin(s) associated with their name(s) or
 
-      *          `undefined` if no matching plugins exist).
 
-      */
 
-     static getPlugins(names = Object.keys(pluginStorage)) {
 
-       let result;
 
-       names.forEach(name => {
 
-         const plugin = getPlugin(name);
 
-         if (plugin) {
 
-           result = result || {};
 
-           result[name] = plugin;
 
-         }
 
-       });
 
-       return result;
 
-     }
 
-     /**
 
-      * Gets a plugin's version, if available
 
-      *
 
-      * @param   {string} name
 
-      *          The name of a plugin.
 
-      *
 
-      * @return {string}
 
-      *          The plugin's version or an empty string.
 
-      */
 
-     static getPluginVersion(name) {
 
-       const plugin = getPlugin(name);
 
-       return plugin && plugin.VERSION || '';
 
-     }
 
-   }
 
-   /**
 
-    * Gets a plugin by name if it exists.
 
-    *
 
-    * @static
 
-    * @method   getPlugin
 
-    * @memberOf Plugin
 
-    * @param    {string} name
 
-    *           The name of a plugin.
 
-    *
 
-    * @returns  {typeof Plugin|Function|undefined}
 
-    *           The plugin (or `undefined`).
 
-    */
 
-   Plugin.getPlugin = getPlugin;
 
-   /**
 
-    * The name of the base plugin class as it is registered.
 
-    *
 
-    * @type {string}
 
-    */
 
-   Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
 
-   Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
 
-   /**
 
-    * Documented in player.js
 
-    *
 
-    * @ignore
 
-    */
 
-   Player.prototype.usingPlugin = function (name) {
 
-     return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
 
-   };
 
-   /**
 
-    * Documented in player.js
 
-    *
 
-    * @ignore
 
-    */
 
-   Player.prototype.hasPlugin = function (name) {
 
-     return !!pluginExists(name);
 
-   };
 
-   /**
 
-    * Signals that a plugin is about to be set up on a player.
 
-    *
 
-    * @event    Player#beforepluginsetup
 
-    * @type     {Plugin~PluginEventHash}
 
-    */
 
-   /**
 
-    * Signals that a plugin is about to be set up on a player - by name. The name
 
-    * is the name of the plugin.
 
-    *
 
-    * @event    Player#beforepluginsetup:$name
 
-    * @type     {Plugin~PluginEventHash}
 
-    */
 
-   /**
 
-    * Signals that a plugin has just been set up on a player.
 
-    *
 
-    * @event    Player#pluginsetup
 
-    * @type     {Plugin~PluginEventHash}
 
-    */
 
-   /**
 
-    * Signals that a plugin has just been set up on a player - by name. The name
 
-    * is the name of the plugin.
 
-    *
 
-    * @event    Player#pluginsetup:$name
 
-    * @type     {Plugin~PluginEventHash}
 
-    */
 
-   /**
 
-    * @typedef  {Object} Plugin~PluginEventHash
 
-    *
 
-    * @property {string} instance
 
-    *           For basic plugins, the return value of the plugin function. For
 
-    *           advanced plugins, the plugin instance on which the event is fired.
 
-    *
 
-    * @property {string} name
 
-    *           The name of the plugin.
 
-    *
 
-    * @property {string} plugin
 
-    *           For basic plugins, the plugin function. For advanced plugins, the
 
-    *           plugin class/constructor.
 
-    */
 
-   /**
 
-    * @file deprecate.js
 
-    * @module deprecate
 
-    */
 
-   /**
 
-    * Decorate a function with a deprecation message the first time it is called.
 
-    *
 
-    * @param  {string}   message
 
-    *         A deprecation message to log the first time the returned function
 
-    *         is called.
 
-    *
 
-    * @param  {Function} fn
 
-    *         The function to be deprecated.
 
-    *
 
-    * @return {Function}
 
-    *         A wrapper function that will log a deprecation warning the first
 
-    *         time it is called. The return value will be the return value of
 
-    *         the wrapped function.
 
-    */
 
-   function deprecate(message, fn) {
 
-     let warned = false;
 
-     return function (...args) {
 
-       if (!warned) {
 
-         log$1.warn(message);
 
-       }
 
-       warned = true;
 
-       return fn.apply(this, args);
 
-     };
 
-   }
 
-   /**
 
-    * Internal function used to mark a function as deprecated in the next major
 
-    * version with consistent messaging.
 
-    *
 
-    * @param  {number}   major   The major version where it will be removed
 
-    * @param  {string}   oldName The old function name
 
-    * @param  {string}   newName The new function name
 
-    * @param  {Function} fn      The function to deprecate
 
-    * @return {Function}         The decorated function
 
-    */
 
-   function deprecateForMajor(major, oldName, newName, fn) {
 
-     return deprecate(`${oldName} is deprecated and will be removed in ${major}.0; please use ${newName} instead.`, fn);
 
-   }
 
-   /**
 
-    * @file video.js
 
-    * @module videojs
 
-    */
 
-   /**
 
-    * Normalize an `id` value by trimming off a leading `#`
 
-    *
 
-    * @private
 
-    * @param   {string} id
 
-    *          A string, maybe with a leading `#`.
 
-    *
 
-    * @return {string}
 
-    *          The string, without any leading `#`.
 
-    */
 
-   const normalizeId = id => id.indexOf('#') === 0 ? id.slice(1) : id;
 
-   /**
 
-    * A callback that is called when a component is ready. Does not have any
 
-    * parameters and any callback value will be ignored. See: {@link Component~ReadyCallback}
 
-    *
 
-    * @callback ReadyCallback
 
-    */
 
-   /**
 
-    * The `videojs()` function doubles as the main function for users to create a
 
-    * {@link Player} instance as well as the main library namespace.
 
-    *
 
-    * It can also be used as a getter for a pre-existing {@link Player} instance.
 
-    * However, we _strongly_ recommend using `videojs.getPlayer()` for this
 
-    * purpose because it avoids any potential for unintended initialization.
 
-    *
 
-    * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
 
-    * of our JSDoc template, we cannot properly document this as both a function
 
-    * and a namespace, so its function signature is documented here.
 
-    *
 
-    * #### Arguments
 
-    * ##### id
 
-    * string|Element, **required**
 
-    *
 
-    * Video element or video element ID.
 
-    *
 
-    * ##### options
 
-    * Object, optional
 
-    *
 
-    * Options object for providing settings.
 
-    * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
 
-    *
 
-    * ##### ready
 
-    * {@link Component~ReadyCallback}, optional
 
-    *
 
-    * A function to be called when the {@link Player} and {@link Tech} are ready.
 
-    *
 
-    * #### Return Value
 
-    *
 
-    * The `videojs()` function returns a {@link Player} instance.
 
-    *
 
-    * @namespace
 
-    *
 
-    * @borrows AudioTrack as AudioTrack
 
-    * @borrows Component.getComponent as getComponent
 
-    * @borrows module:events.on as on
 
-    * @borrows module:events.one as one
 
-    * @borrows module:events.off as off
 
-    * @borrows module:events.trigger as trigger
 
-    * @borrows EventTarget as EventTarget
 
-    * @borrows module:middleware.use as use
 
-    * @borrows Player.players as players
 
-    * @borrows Plugin.registerPlugin as registerPlugin
 
-    * @borrows Plugin.deregisterPlugin as deregisterPlugin
 
-    * @borrows Plugin.getPlugins as getPlugins
 
-    * @borrows Plugin.getPlugin as getPlugin
 
-    * @borrows Plugin.getPluginVersion as getPluginVersion
 
-    * @borrows Tech.getTech as getTech
 
-    * @borrows Tech.registerTech as registerTech
 
-    * @borrows TextTrack as TextTrack
 
-    * @borrows VideoTrack as VideoTrack
 
-    *
 
-    * @param  {string|Element} id
 
-    *         Video element or video element ID.
 
-    *
 
-    * @param  {Object} [options]
 
-    *         Options object for providing settings.
 
-    *         See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
 
-    *
 
-    * @param  {ReadyCallback} [ready]
 
-    *         A function to be called when the {@link Player} and {@link Tech} are
 
-    *         ready.
 
-    *
 
-    * @return {Player}
 
-    *         The `videojs()` function returns a {@link Player|Player} instance.
 
-    */
 
-   function videojs(id, options, ready) {
 
-     let player = videojs.getPlayer(id);
 
-     if (player) {
 
-       if (options) {
 
-         log$1.warn(`Player "${id}" is already initialised. Options will not be applied.`);
 
-       }
 
-       if (ready) {
 
-         player.ready(ready);
 
-       }
 
-       return player;
 
-     }
 
-     const el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
 
-     if (!isEl(el)) {
 
-       throw new TypeError('The element or ID supplied is not valid. (videojs)');
 
-     }
 
-     // document.body.contains(el) will only check if el is contained within that one document.
 
-     // This causes problems for elements in iframes.
 
-     // Instead, use the element's ownerDocument instead of the global document.
 
-     // This will make sure that the element is indeed in the dom of that document.
 
-     // Additionally, check that the document in question has a default view.
 
-     // If the document is no longer attached to the dom, the defaultView of the document will be null.
 
-     if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
 
-       log$1.warn('The element supplied is not included in the DOM');
 
-     }
 
-     options = options || {};
 
-     // Store a copy of the el before modification, if it is to be restored in destroy()
 
-     // If div ingest, store the parent div
 
-     if (options.restoreEl === true) {
 
-       options.restoreEl = (el.parentNode && el.parentNode.hasAttribute('data-vjs-player') ? el.parentNode : el).cloneNode(true);
 
-     }
 
-     hooks('beforesetup').forEach(hookFunction => {
 
-       const opts = hookFunction(el, merge$2(options));
 
-       if (!isObject$1(opts) || Array.isArray(opts)) {
 
-         log$1.error('please return an object in beforesetup hooks');
 
-         return;
 
-       }
 
-       options = merge$2(options, opts);
 
-     });
 
-     // We get the current "Player" component here in case an integration has
 
-     // replaced it with a custom player.
 
-     const PlayerComponent = Component$1.getComponent('Player');
 
-     player = new PlayerComponent(el, options, ready);
 
-     hooks('setup').forEach(hookFunction => hookFunction(player));
 
-     return player;
 
-   }
 
-   videojs.hooks_ = hooks_;
 
-   videojs.hooks = hooks;
 
-   videojs.hook = hook;
 
-   videojs.hookOnce = hookOnce;
 
-   videojs.removeHook = removeHook;
 
-   // Add default styles
 
-   if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
 
-     let style = $('.vjs-styles-defaults');
 
-     if (!style) {
 
-       style = createStyleElement('vjs-styles-defaults');
 
-       const head = $('head');
 
-       if (head) {
 
-         head.insertBefore(style, head.firstChild);
 
-       }
 
-       setTextContent(style, `
 
-       .video-js {
 
-         width: 300px;
 
-         height: 150px;
 
-       }
 
-       .vjs-fluid:not(.vjs-audio-only-mode) {
 
-         padding-top: 56.25%
 
-       }
 
-     `);
 
-     }
 
-   }
 
-   // Run Auto-load players
 
-   // You have to wait at least once in case this script is loaded after your
 
-   // video in the DOM (weird behavior only with minified version)
 
-   autoSetupTimeout(1, videojs);
 
-   /**
 
-    * Current Video.js version. Follows [semantic versioning](https://semver.org/).
 
-    *
 
-    * @type {string}
 
-    */
 
-   videojs.VERSION = version$5;
 
-   /**
 
-    * The global options object. These are the settings that take effect
 
-    * if no overrides are specified when the player is created.
 
-    *
 
-    * @type {Object}
 
-    */
 
-   videojs.options = Player.prototype.options_;
 
-   /**
 
-    * Get an object with the currently created players, keyed by player ID
 
-    *
 
-    * @return {Object}
 
-    *         The created players
 
-    */
 
-   videojs.getPlayers = () => Player.players;
 
-   /**
 
-    * Get a single player based on an ID or DOM element.
 
-    *
 
-    * This is useful if you want to check if an element or ID has an associated
 
-    * Video.js player, but not create one if it doesn't.
 
-    *
 
-    * @param   {string|Element} id
 
-    *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
 
-    *          or a string matching the `id` of such an element.
 
-    *
 
-    * @return {Player|undefined}
 
-    *          A player instance or `undefined` if there is no player instance
 
-    *          matching the argument.
 
-    */
 
-   videojs.getPlayer = id => {
 
-     const players = Player.players;
 
-     let tag;
 
-     if (typeof id === 'string') {
 
-       const nId = normalizeId(id);
 
-       const player = players[nId];
 
-       if (player) {
 
-         return player;
 
-       }
 
-       tag = $('#' + nId);
 
-     } else {
 
-       tag = id;
 
-     }
 
-     if (isEl(tag)) {
 
-       const {
 
-         player,
 
-         playerId
 
-       } = tag;
 
-       // Element may have a `player` property referring to an already created
 
-       // player instance. If so, return that.
 
-       if (player || players[playerId]) {
 
-         return player || players[playerId];
 
-       }
 
-     }
 
-   };
 
-   /**
 
-    * Returns an array of all current players.
 
-    *
 
-    * @return {Array}
 
-    *         An array of all players. The array will be in the order that
 
-    *         `Object.keys` provides, which could potentially vary between
 
-    *         JavaScript engines.
 
-    *
 
-    */
 
-   videojs.getAllPlayers = () =>
 
-   // Disposed players leave a key with a `null` value, so we need to make sure
 
-   // we filter those out.
 
-   Object.keys(Player.players).map(k => Player.players[k]).filter(Boolean);
 
-   videojs.players = Player.players;
 
-   videojs.getComponent = Component$1.getComponent;
 
-   /**
 
-    * Register a component so it can referred to by name. Used when adding to other
 
-    * components, either through addChild `component.addChild('myComponent')` or through
 
-    * default children options  `{ children: ['myComponent'] }`.
 
-    *
 
-    * > NOTE: You could also just initialize the component before adding.
 
-    * `component.addChild(new MyComponent());`
 
-    *
 
-    * @param {string} name
 
-    *        The class name of the component
 
-    *
 
-    * @param {Component} comp
 
-    *        The component class
 
-    *
 
-    * @return {Component}
 
-    *         The newly registered component
 
-    */
 
-   videojs.registerComponent = (name, comp) => {
 
-     if (Tech.isTech(comp)) {
 
-       log$1.warn(`The ${name} tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)`);
 
-     }
 
-     Component$1.registerComponent.call(Component$1, name, comp);
 
-   };
 
-   videojs.getTech = Tech.getTech;
 
-   videojs.registerTech = Tech.registerTech;
 
-   videojs.use = use;
 
-   /**
 
-    * An object that can be returned by a middleware to signify
 
-    * that the middleware is being terminated.
 
-    *
 
-    * @type {object}
 
-    * @property {object} middleware.TERMINATOR
 
-    */
 
-   Object.defineProperty(videojs, 'middleware', {
 
-     value: {},
 
-     writeable: false,
 
-     enumerable: true
 
-   });
 
-   Object.defineProperty(videojs.middleware, 'TERMINATOR', {
 
-     value: TERMINATOR,
 
-     writeable: false,
 
-     enumerable: true
 
-   });
 
-   /**
 
-    * A reference to the {@link module:browser|browser utility module} as an object.
 
-    *
 
-    * @type {Object}
 
-    * @see  {@link module:browser|browser}
 
-    */
 
-   videojs.browser = browser;
 
-   /**
 
-    * A reference to the {@link module:obj|obj utility module} as an object.
 
-    *
 
-    * @type {Object}
 
-    * @see  {@link module:obj|obj}
 
-    */
 
-   videojs.obj = Obj;
 
-   /**
 
-    * Deprecated reference to the {@link module:obj.merge|merge function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:obj.merge|merge}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.merge instead.
 
-    */
 
-   videojs.mergeOptions = deprecateForMajor(9, 'videojs.mergeOptions', 'videojs.obj.merge', merge$2);
 
-   /**
 
-    * Deprecated reference to the {@link module:obj.defineLazyProperty|defineLazyProperty function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:obj.defineLazyProperty|defineLazyProperty}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.defineLazyProperty instead.
 
-    */
 
-   videojs.defineLazyProperty = deprecateForMajor(9, 'videojs.defineLazyProperty', 'videojs.obj.defineLazyProperty', defineLazyProperty);
 
-   /**
 
-    * Deprecated reference to the {@link module:fn.bind_|fn.bind_ function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:fn.bind_|fn.bind_}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use native Function.prototype.bind instead.
 
-    */
 
-   videojs.bind = deprecateForMajor(9, 'videojs.bind', 'native Function.prototype.bind', bind_);
 
-   videojs.registerPlugin = Plugin.registerPlugin;
 
-   videojs.deregisterPlugin = Plugin.deregisterPlugin;
 
-   /**
 
-    * Deprecated method to register a plugin with Video.js
 
-    *
 
-    * @deprecated Deprecated and will be removed in 9.0. Use videojs.registerPlugin() instead.
 
-    *
 
-    * @param {string} name
 
-    *        The plugin name
 
-    *
 
-    * @param {Plugin|Function} plugin
 
-    *         The plugin sub-class or function
 
-    */
 
-   videojs.plugin = (name, plugin) => {
 
-     log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
 
-     return Plugin.registerPlugin(name, plugin);
 
-   };
 
-   videojs.getPlugins = Plugin.getPlugins;
 
-   videojs.getPlugin = Plugin.getPlugin;
 
-   videojs.getPluginVersion = Plugin.getPluginVersion;
 
-   /**
 
-    * Adding languages so that they're available to all players.
 
-    * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
 
-    *
 
-    * @param {string} code
 
-    *        The language code or dictionary property
 
-    *
 
-    * @param {Object} data
 
-    *        The data values to be translated
 
-    *
 
-    * @return {Object}
 
-    *         The resulting language dictionary object
 
-    */
 
-   videojs.addLanguage = function (code, data) {
 
-     code = ('' + code).toLowerCase();
 
-     videojs.options.languages = merge$2(videojs.options.languages, {
 
-       [code]: data
 
-     });
 
-     return videojs.options.languages[code];
 
-   };
 
-   /**
 
-    * A reference to the {@link module:log|log utility module} as an object.
 
-    *
 
-    * @type {Function}
 
-    * @see  {@link module:log|log}
 
-    */
 
-   videojs.log = log$1;
 
-   videojs.createLogger = createLogger;
 
-   /**
 
-    * A reference to the {@link module:time|time utility module} as an object.
 
-    *
 
-    * @type {Object}
 
-    * @see {@link module:time|time}
 
-    */
 
-   videojs.time = Time;
 
-   /**
 
-    * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:time.createTimeRanges|createTimeRanges}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
 
-    */
 
-   videojs.createTimeRange = deprecateForMajor(9, 'videojs.createTimeRange', 'videojs.time.createTimeRanges', createTimeRanges$1);
 
-   /**
 
-    * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:time.createTimeRanges|createTimeRanges}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
 
-    */
 
-   videojs.createTimeRanges = deprecateForMajor(9, 'videojs.createTimeRanges', 'videojs.time.createTimeRanges', createTimeRanges$1);
 
-   /**
 
-    * Deprecated reference to the {@link module:time.formatTime|formatTime function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:time.formatTime|formatTime}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.format instead.
 
-    */
 
-   videojs.formatTime = deprecateForMajor(9, 'videojs.formatTime', 'videojs.time.formatTime', formatTime);
 
-   /**
 
-    * Deprecated reference to the {@link module:time.setFormatTime|setFormatTime function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:time.setFormatTime|setFormatTime}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.setFormat instead.
 
-    */
 
-   videojs.setFormatTime = deprecateForMajor(9, 'videojs.setFormatTime', 'videojs.time.setFormatTime', setFormatTime);
 
-   /**
 
-    * Deprecated reference to the {@link module:time.resetFormatTime|resetFormatTime function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:time.resetFormatTime|resetFormatTime}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.resetFormat instead.
 
-    */
 
-   videojs.resetFormatTime = deprecateForMajor(9, 'videojs.resetFormatTime', 'videojs.time.resetFormatTime', resetFormatTime);
 
-   /**
 
-    * Deprecated reference to the {@link module:url.parseUrl|Url.parseUrl function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:url.parseUrl|parseUrl}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.parseUrl instead.
 
-    */
 
-   videojs.parseUrl = deprecateForMajor(9, 'videojs.parseUrl', 'videojs.url.parseUrl', parseUrl);
 
-   /**
 
-    * Deprecated reference to the {@link module:url.isCrossOrigin|Url.isCrossOrigin function}
 
-    *
 
-    * @type {Function}
 
-    * @see {@link module:url.isCrossOrigin|isCrossOrigin}
 
-    * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.isCrossOrigin instead.
 
-    */
 
-   videojs.isCrossOrigin = deprecateForMajor(9, 'videojs.isCrossOrigin', 'videojs.url.isCrossOrigin', isCrossOrigin);
 
-   videojs.EventTarget = EventTarget$2;
 
-   videojs.any = any;
 
-   videojs.on = on;
 
-   videojs.one = one;
 
-   videojs.off = off;
 
-   videojs.trigger = trigger;
 
-   /**
 
-    * A cross-browser XMLHttpRequest wrapper.
 
-    *
 
-    * @function
 
-    * @param    {Object} options
 
-    *           Settings for the request.
 
-    *
 
-    * @return   {XMLHttpRequest|XDomainRequest}
 
-    *           The request object.
 
-    *
 
-    * @see      https://github.com/Raynos/xhr
 
-    */
 
-   videojs.xhr = lib;
 
-   videojs.TextTrack = TextTrack;
 
-   videojs.AudioTrack = AudioTrack;
 
-   videojs.VideoTrack = VideoTrack;
 
-   ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(k => {
 
-     videojs[k] = function () {
 
-       log$1.warn(`videojs.${k}() is deprecated; use videojs.dom.${k}() instead`);
 
-       return Dom[k].apply(null, arguments);
 
-     };
 
-   });
 
-   videojs.computedStyle = deprecateForMajor(9, 'videojs.computedStyle', 'videojs.dom.computedStyle', computedStyle);
 
-   /**
 
-    * A reference to the {@link module:dom|DOM utility module} as an object.
 
-    *
 
-    * @type {Object}
 
-    * @see {@link module:dom|dom}
 
-    */
 
-   videojs.dom = Dom;
 
-   /**
 
-    * A reference to the {@link module:fn|fn utility module} as an object.
 
-    *
 
-    * @type {Object}
 
-    * @see {@link module:fn|fn}
 
-    */
 
-   videojs.fn = Fn;
 
-   /**
 
-    * A reference to the {@link module:num|num utility module} as an object.
 
-    *
 
-    * @type {Object}
 
-    * @see {@link module:num|num}
 
-    */
 
-   videojs.num = Num;
 
-   /**
 
-    * A reference to the {@link module:str|str utility module} as an object.
 
-    *
 
-    * @type {Object}
 
-    * @see {@link module:str|str}
 
-    */
 
-   videojs.str = Str;
 
-   /**
 
-    * A reference to the {@link module:url|URL utility module} as an object.
 
-    *
 
-    * @type {Object}
 
-    * @see {@link module:url|url}
 
-    */
 
-   videojs.url = Url;
 
-   createCommonjsModule(function (module, exports) {
 
-     /*! @name videojs-contrib-quality-levels @version 3.0.0 @license Apache-2.0 */
 
-     (function (global, factory) {
 
-       module.exports = factory(videojs) ;
 
-     })(commonjsGlobal, function (videojs) {
 
-       function _interopDefaultLegacy(e) {
 
-         return e && typeof e === 'object' && 'default' in e ? e : {
 
-           'default': e
 
-         };
 
-       }
 
-       var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
 
-       /**
 
-        * A single QualityLevel.
 
-        *
 
-        * interface QualityLevel {
 
-        *   readonly attribute DOMString id;
 
-        *            attribute DOMString label;
 
-        *   readonly attribute long width;
 
-        *   readonly attribute long height;
 
-        *   readonly attribute long bitrate;
 
-        *            attribute boolean enabled;
 
-        * };
 
-        *
 
-        * @class QualityLevel
 
-        */
 
-       class QualityLevel {
 
-         /**
 
-          * Creates a QualityLevel
 
-          *
 
-          * @param {Representation|Object} representation The representation of the quality level
 
-          * @param {string}   representation.id        Unique id of the QualityLevel
 
-          * @param {number=}  representation.width     Resolution width of the QualityLevel
 
-          * @param {number=}  representation.height    Resolution height of the QualityLevel
 
-          * @param {number}   representation.bandwidth Bitrate of the QualityLevel
 
-          * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
 
-          * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
 
-          */
 
-         constructor(representation) {
 
-           let level = this; // eslint-disable-line
 
-           level.id = representation.id;
 
-           level.label = level.id;
 
-           level.width = representation.width;
 
-           level.height = representation.height;
 
-           level.bitrate = representation.bandwidth;
 
-           level.frameRate = representation.frameRate;
 
-           level.enabled_ = representation.enabled;
 
-           Object.defineProperty(level, 'enabled', {
 
-             /**
 
-              * Get whether the QualityLevel is enabled.
 
-              *
 
-              * @return {boolean} True if the QualityLevel is enabled.
 
-              */
 
-             get() {
 
-               return level.enabled_();
 
-             },
 
-             /**
 
-              * Enable or disable the QualityLevel.
 
-              *
 
-              * @param {boolean} enable true to enable QualityLevel, false to disable.
 
-              */
 
-             set(enable) {
 
-               level.enabled_(enable);
 
-             }
 
-           });
 
-           return level;
 
-         }
 
-       }
 
-       /**
 
-        * A list of QualityLevels.
 
-        *
 
-        * interface QualityLevelList : EventTarget {
 
-        *   getter QualityLevel (unsigned long index);
 
-        *   readonly attribute unsigned long length;
 
-        *   readonly attribute long selectedIndex;
 
-        *
 
-        *   void addQualityLevel(QualityLevel qualityLevel)
 
-        *   void removeQualityLevel(QualityLevel remove)
 
-        *   QualityLevel? getQualityLevelById(DOMString id);
 
-        *
 
-        *   attribute EventHandler onchange;
 
-        *   attribute EventHandler onaddqualitylevel;
 
-        *   attribute EventHandler onremovequalitylevel;
 
-        * };
 
-        *
 
-        * @extends videojs.EventTarget
 
-        * @class QualityLevelList
 
-        */
 
-       class QualityLevelList extends videojs__default['default'].EventTarget {
 
-         constructor() {
 
-           super();
 
-           let list = this; // eslint-disable-line
 
-           list.levels_ = [];
 
-           list.selectedIndex_ = -1;
 
-           /**
 
-            * Get the index of the currently selected QualityLevel.
 
-            *
 
-            * @returns {number} The index of the selected QualityLevel. -1 if none selected.
 
-            * @readonly
 
-            */
 
-           Object.defineProperty(list, 'selectedIndex', {
 
-             get() {
 
-               return list.selectedIndex_;
 
-             }
 
-           });
 
-           /**
 
-            * Get the length of the list of QualityLevels.
 
-            *
 
-            * @returns {number} The length of the list.
 
-            * @readonly
 
-            */
 
-           Object.defineProperty(list, 'length', {
 
-             get() {
 
-               return list.levels_.length;
 
-             }
 
-           });
 
-           return list;
 
-         }
 
-         /**
 
-          * Adds a quality level to the list.
 
-          *
 
-          * @param {Representation|Object} representation The representation of the quality level
 
-          * @param {string}   representation.id        Unique id of the QualityLevel
 
-          * @param {number=}  representation.width     Resolution width of the QualityLevel
 
-          * @param {number=}  representation.height    Resolution height of the QualityLevel
 
-          * @param {number}   representation.bandwidth Bitrate of the QualityLevel
 
-          * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
 
-          * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
 
-          * @return {QualityLevel} the QualityLevel added to the list
 
-          * @method addQualityLevel
 
-          */
 
-         addQualityLevel(representation) {
 
-           let qualityLevel = this.getQualityLevelById(representation.id); // Do not add duplicate quality levels
 
-           if (qualityLevel) {
 
-             return qualityLevel;
 
-           }
 
-           const index = this.levels_.length;
 
-           qualityLevel = new QualityLevel(representation);
 
-           if (!('' + index in this)) {
 
-             Object.defineProperty(this, index, {
 
-               get() {
 
-                 return this.levels_[index];
 
-               }
 
-             });
 
-           }
 
-           this.levels_.push(qualityLevel);
 
-           this.trigger({
 
-             qualityLevel,
 
-             type: 'addqualitylevel'
 
-           });
 
-           return qualityLevel;
 
-         }
 
-         /**
 
-          * Removes a quality level from the list.
 
-          *
 
-          * @param {QualityLevel} remove QualityLevel to remove to the list.
 
-          * @return {QualityLevel|null} the QualityLevel removed or null if nothing removed
 
-          * @method removeQualityLevel
 
-          */
 
-         removeQualityLevel(qualityLevel) {
 
-           let removed = null;
 
-           for (let i = 0, l = this.length; i < l; i++) {
 
-             if (this[i] === qualityLevel) {
 
-               removed = this.levels_.splice(i, 1)[0];
 
-               if (this.selectedIndex_ === i) {
 
-                 this.selectedIndex_ = -1;
 
-               } else if (this.selectedIndex_ > i) {
 
-                 this.selectedIndex_--;
 
-               }
 
-               break;
 
-             }
 
-           }
 
-           if (removed) {
 
-             this.trigger({
 
-               qualityLevel,
 
-               type: 'removequalitylevel'
 
-             });
 
-           }
 
-           return removed;
 
-         }
 
-         /**
 
-          * Searches for a QualityLevel with the given id.
 
-          *
 
-          * @param {string} id The id of the QualityLevel to find.
 
-          * @return {QualityLevel|null} The QualityLevel with id, or null if not found.
 
-          * @method getQualityLevelById
 
-          */
 
-         getQualityLevelById(id) {
 
-           for (let i = 0, l = this.length; i < l; i++) {
 
-             const level = this[i];
 
-             if (level.id === id) {
 
-               return level;
 
-             }
 
-           }
 
-           return null;
 
-         }
 
-         /**
 
-          * Resets the list of QualityLevels to empty
 
-          *
 
-          * @method dispose
 
-          */
 
-         dispose() {
 
-           this.selectedIndex_ = -1;
 
-           this.levels_.length = 0;
 
-         }
 
-       }
 
-       /**
 
-        * change - The selected QualityLevel has changed.
 
-        * addqualitylevel - A QualityLevel has been added to the QualityLevelList.
 
-        * removequalitylevel - A QualityLevel has been removed from the QualityLevelList.
 
-        */
 
-       QualityLevelList.prototype.allowedEvents_ = {
 
-         change: 'change',
 
-         addqualitylevel: 'addqualitylevel',
 
-         removequalitylevel: 'removequalitylevel'
 
-       }; // emulate attribute EventHandler support to allow for feature detection
 
-       for (const event in QualityLevelList.prototype.allowedEvents_) {
 
-         QualityLevelList.prototype['on' + event] = null;
 
-       }
 
-       var version = "3.0.0";
 
-       const registerPlugin = videojs__default['default'].registerPlugin || videojs__default['default'].plugin;
 
-       /**
 
-        * Initialization function for the qualityLevels plugin. Sets up the QualityLevelList and
 
-        * event handlers.
 
-        *
 
-        * @param {Player} player Player object.
 
-        * @param {Object} options Plugin options object.
 
-        * @function initPlugin
 
-        */
 
-       const initPlugin = function (player, options) {
 
-         const originalPluginFn = player.qualityLevels;
 
-         const qualityLevelList = new QualityLevelList();
 
-         const disposeHandler = function () {
 
-           qualityLevelList.dispose();
 
-           player.qualityLevels = originalPluginFn;
 
-           player.off('dispose', disposeHandler);
 
-         };
 
-         player.on('dispose', disposeHandler);
 
-         player.qualityLevels = () => qualityLevelList;
 
-         player.qualityLevels.VERSION = version;
 
-         return qualityLevelList;
 
-       };
 
-       /**
 
-        * A video.js plugin.
 
-        *
 
-        * In the plugin function, the value of `this` is a video.js `Player`
 
-        * instance. You cannot rely on the player being in a "ready" state here,
 
-        * depending on how the plugin is invoked. This may or may not be important
 
-        * to you; if not, remove the wait for "ready"!
 
-        *
 
-        * @param {Object} options Plugin options object
 
-        * @function qualityLevels
 
-        */
 
-       const qualityLevels = function (options) {
 
-         return initPlugin(this, videojs__default['default'].mergeOptions({}, options));
 
-       }; // Register the plugin with video.js.
 
-       registerPlugin('qualityLevels', qualityLevels); // Include the version number.
 
-       qualityLevels.VERSION = version;
 
-       return qualityLevels;
 
-     });
 
-   });
 
-   var urlToolkit = createCommonjsModule(function (module, exports) {
 
-     // see https://tools.ietf.org/html/rfc1808
 
-     (function (root) {
 
-       var URL_REGEX = /^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/;
 
-       var FIRST_SEGMENT_REGEX = /^(?=([^\/?#]*))\1([^]*)$/;
 
-       var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
 
-       var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g;
 
-       var URLToolkit = {
 
-         // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
 
-         // E.g
 
-         // With opts.alwaysNormalize = false (default, spec compliant)
 
-         // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
 
-         // With opts.alwaysNormalize = true (not spec compliant)
 
-         // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
 
-         buildAbsoluteURL: function (baseURL, relativeURL, opts) {
 
-           opts = opts || {};
 
-           // remove any remaining space and CRLF
 
-           baseURL = baseURL.trim();
 
-           relativeURL = relativeURL.trim();
 
-           if (!relativeURL) {
 
-             // 2a) If the embedded URL is entirely empty, it inherits the
 
-             // entire base URL (i.e., is set equal to the base URL)
 
-             // and we are done.
 
-             if (!opts.alwaysNormalize) {
 
-               return baseURL;
 
-             }
 
-             var basePartsForNormalise = URLToolkit.parseURL(baseURL);
 
-             if (!basePartsForNormalise) {
 
-               throw new Error('Error trying to parse base URL.');
 
-             }
 
-             basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
 
-             return URLToolkit.buildURLFromParts(basePartsForNormalise);
 
-           }
 
-           var relativeParts = URLToolkit.parseURL(relativeURL);
 
-           if (!relativeParts) {
 
-             throw new Error('Error trying to parse relative URL.');
 
-           }
 
-           if (relativeParts.scheme) {
 
-             // 2b) If the embedded URL starts with a scheme name, it is
 
-             // interpreted as an absolute URL and we are done.
 
-             if (!opts.alwaysNormalize) {
 
-               return relativeURL;
 
-             }
 
-             relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
 
-             return URLToolkit.buildURLFromParts(relativeParts);
 
-           }
 
-           var baseParts = URLToolkit.parseURL(baseURL);
 
-           if (!baseParts) {
 
-             throw new Error('Error trying to parse base URL.');
 
-           }
 
-           if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
 
-             // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
 
-             // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
 
-             var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
 
-             baseParts.netLoc = pathParts[1];
 
-             baseParts.path = pathParts[2];
 
-           }
 
-           if (baseParts.netLoc && !baseParts.path) {
 
-             baseParts.path = '/';
 
-           }
 
-           var builtParts = {
 
-             // 2c) Otherwise, the embedded URL inherits the scheme of
 
-             // the base URL.
 
-             scheme: baseParts.scheme,
 
-             netLoc: relativeParts.netLoc,
 
-             path: null,
 
-             params: relativeParts.params,
 
-             query: relativeParts.query,
 
-             fragment: relativeParts.fragment
 
-           };
 
-           if (!relativeParts.netLoc) {
 
-             // 3) If the embedded URL's <net_loc> is non-empty, we skip to
 
-             // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
 
-             // (if any) of the base URL.
 
-             builtParts.netLoc = baseParts.netLoc;
 
-             // 4) If the embedded URL path is preceded by a slash "/", the
 
-             // path is not relative and we skip to Step 7.
 
-             if (relativeParts.path[0] !== '/') {
 
-               if (!relativeParts.path) {
 
-                 // 5) If the embedded URL path is empty (and not preceded by a
 
-                 // slash), then the embedded URL inherits the base URL path
 
-                 builtParts.path = baseParts.path;
 
-                 // 5a) if the embedded URL's <params> is non-empty, we skip to
 
-                 // step 7; otherwise, it inherits the <params> of the base
 
-                 // URL (if any) and
 
-                 if (!relativeParts.params) {
 
-                   builtParts.params = baseParts.params;
 
-                   // 5b) if the embedded URL's <query> is non-empty, we skip to
 
-                   // step 7; otherwise, it inherits the <query> of the base
 
-                   // URL (if any) and we skip to step 7.
 
-                   if (!relativeParts.query) {
 
-                     builtParts.query = baseParts.query;
 
-                   }
 
-                 }
 
-               } else {
 
-                 // 6) The last segment of the base URL's path (anything
 
-                 // following the rightmost slash "/", or the entire path if no
 
-                 // slash is present) is removed and the embedded URL's path is
 
-                 // appended in its place.
 
-                 var baseURLPath = baseParts.path;
 
-                 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
 
-                 builtParts.path = URLToolkit.normalizePath(newPath);
 
-               }
 
-             }
 
-           }
 
-           if (builtParts.path === null) {
 
-             builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
 
-           }
 
-           return URLToolkit.buildURLFromParts(builtParts);
 
-         },
 
-         parseURL: function (url) {
 
-           var parts = URL_REGEX.exec(url);
 
-           if (!parts) {
 
-             return null;
 
-           }
 
-           return {
 
-             scheme: parts[1] || '',
 
-             netLoc: parts[2] || '',
 
-             path: parts[3] || '',
 
-             params: parts[4] || '',
 
-             query: parts[5] || '',
 
-             fragment: parts[6] || ''
 
-           };
 
-         },
 
-         normalizePath: function (path) {
 
-           // The following operations are
 
-           // then applied, in order, to the new path:
 
-           // 6a) All occurrences of "./", where "." is a complete path
 
-           // segment, are removed.
 
-           // 6b) If the path ends with "." as a complete path segment,
 
-           // that "." is removed.
 
-           path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
 
-           // 6c) All occurrences of "<segment>/../", where <segment> is a
 
-           // complete path segment not equal to "..", are removed.
 
-           // Removal of these path segments is performed iteratively,
 
-           // removing the leftmost matching pattern on each iteration,
 
-           // until no matching pattern remains.
 
-           // 6d) If the path ends with "<segment>/..", where <segment> is a
 
-           // complete path segment not equal to "..", that
 
-           // "<segment>/.." is removed.
 
-           while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {}
 
-           return path.split('').reverse().join('');
 
-         },
 
-         buildURLFromParts: function (parts) {
 
-           return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
 
-         }
 
-       };
 
-       module.exports = URLToolkit;
 
-     })();
 
-   });
 
-   var DEFAULT_LOCATION$1 = 'http://example.com';
 
-   var resolveUrl$2 = function resolveUrl(baseUrl, relativeUrl) {
 
-     // return early if we don't need to resolve
 
-     if (/^[a-z]+:/i.test(relativeUrl)) {
 
-       return relativeUrl;
 
-     } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location
 
-     if (/^data:/.test(baseUrl)) {
 
-       baseUrl = window.location && window.location.href || '';
 
-     } // IE11 supports URL but not the URL constructor
 
-     // feature detect the behavior we want
 
-     var nativeURL = typeof window.URL === 'function';
 
-     var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node)
 
-     // and if baseUrl isn't an absolute url
 
-     var removeLocation = !window.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
 
-     if (nativeURL) {
 
-       baseUrl = new window.URL(baseUrl, window.location || DEFAULT_LOCATION$1);
 
-     } else if (!/\/\//i.test(baseUrl)) {
 
-       baseUrl = urlToolkit.buildAbsoluteURL(window.location && window.location.href || '', baseUrl);
 
-     }
 
-     if (nativeURL) {
 
-       var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol
 
-       // and if we're location-less, remove the location
 
-       // otherwise, return the url unmodified
 
-       if (removeLocation) {
 
-         return newUrl.href.slice(DEFAULT_LOCATION$1.length);
 
-       } else if (protocolLess) {
 
-         return newUrl.href.slice(newUrl.protocol.length);
 
-       }
 
-       return newUrl.href;
 
-     }
 
-     return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
 
-   };
 
-   /**
 
-    * @file stream.js
 
-    */
 
-   /**
 
-    * A lightweight readable stream implemention that handles event dispatching.
 
-    *
 
-    * @class Stream
 
-    */
 
-   var Stream = /*#__PURE__*/function () {
 
-     function Stream() {
 
-       this.listeners = {};
 
-     }
 
-     /**
 
-      * Add a listener for a specified event type.
 
-      *
 
-      * @param {string} type the event name
 
-      * @param {Function} listener the callback to be invoked when an event of
 
-      * the specified type occurs
 
-      */
 
-     var _proto = Stream.prototype;
 
-     _proto.on = function on(type, listener) {
 
-       if (!this.listeners[type]) {
 
-         this.listeners[type] = [];
 
-       }
 
-       this.listeners[type].push(listener);
 
-     }
 
-     /**
 
-      * Remove a listener for a specified event type.
 
-      *
 
-      * @param {string} type the event name
 
-      * @param {Function} listener  a function previously registered for this
 
-      * type of event through `on`
 
-      * @return {boolean} if we could turn it off or not
 
-      */;
 
-     _proto.off = function off(type, listener) {
 
-       if (!this.listeners[type]) {
 
-         return false;
 
-       }
 
-       var index = this.listeners[type].indexOf(listener); // TODO: which is better?
 
-       // In Video.js we slice listener functions
 
-       // on trigger so that it does not mess up the order
 
-       // while we loop through.
 
-       //
 
-       // Here we slice on off so that the loop in trigger
 
-       // can continue using it's old reference to loop without
 
-       // messing up the order.
 
-       this.listeners[type] = this.listeners[type].slice(0);
 
-       this.listeners[type].splice(index, 1);
 
-       return index > -1;
 
-     }
 
-     /**
 
-      * Trigger an event of the specified type on this stream. Any additional
 
-      * arguments to this function are passed as parameters to event listeners.
 
-      *
 
-      * @param {string} type the event name
 
-      */;
 
-     _proto.trigger = function trigger(type) {
 
-       var callbacks = this.listeners[type];
 
-       if (!callbacks) {
 
-         return;
 
-       } // Slicing the arguments on every invocation of this method
 
-       // can add a significant amount of overhead. Avoid the
 
-       // intermediate object creation for the common case of a
 
-       // single callback argument
 
-       if (arguments.length === 2) {
 
-         var length = callbacks.length;
 
-         for (var i = 0; i < length; ++i) {
 
-           callbacks[i].call(this, arguments[1]);
 
-         }
 
-       } else {
 
-         var args = Array.prototype.slice.call(arguments, 1);
 
-         var _length = callbacks.length;
 
-         for (var _i = 0; _i < _length; ++_i) {
 
-           callbacks[_i].apply(this, args);
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Destroys the stream and cleans up.
 
-      */;
 
-     _proto.dispose = function dispose() {
 
-       this.listeners = {};
 
-     }
 
-     /**
 
-      * Forwards all `data` events on this stream to the destination stream. The
 
-      * destination stream should provide a method `push` to receive the data
 
-      * events as they arrive.
 
-      *
 
-      * @param {Stream} destination the stream that will receive all `data` events
 
-      * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
 
-      */;
 
-     _proto.pipe = function pipe(destination) {
 
-       this.on('data', function (data) {
 
-         destination.push(data);
 
-       });
 
-     };
 
-     return Stream;
 
-   }();
 
-   var atob$1 = function atob(s) {
 
-     return window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
 
-   };
 
-   function decodeB64ToUint8Array$1(b64Text) {
 
-     var decodedString = atob$1(b64Text);
 
-     var array = new Uint8Array(decodedString.length);
 
-     for (var i = 0; i < decodedString.length; i++) {
 
-       array[i] = decodedString.charCodeAt(i);
 
-     }
 
-     return array;
 
-   }
 
-   /*! @name m3u8-parser @version 6.0.0 @license Apache-2.0 */
 
-   /**
 
-    * @file m3u8/line-stream.js
 
-    */
 
-   /**
 
-    * A stream that buffers string input and generates a `data` event for each
 
-    * line.
 
-    *
 
-    * @class LineStream
 
-    * @extends Stream
 
-    */
 
-   class LineStream extends Stream {
 
-     constructor() {
 
-       super();
 
-       this.buffer = '';
 
-     }
 
-     /**
 
-      * Add new data to be parsed.
 
-      *
 
-      * @param {string} data the text to process
 
-      */
 
-     push(data) {
 
-       let nextNewline;
 
-       this.buffer += data;
 
-       nextNewline = this.buffer.indexOf('\n');
 
-       for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
 
-         this.trigger('data', this.buffer.substring(0, nextNewline));
 
-         this.buffer = this.buffer.substring(nextNewline + 1);
 
-       }
 
-     }
 
-   }
 
-   const TAB = String.fromCharCode(0x09);
 
-   const parseByterange = function (byterangeString) {
 
-     // optionally match and capture 0+ digits before `@`
 
-     // optionally match and capture 0+ digits after `@`
 
-     const match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
 
-     const result = {};
 
-     if (match[1]) {
 
-       result.length = parseInt(match[1], 10);
 
-     }
 
-     if (match[2]) {
 
-       result.offset = parseInt(match[2], 10);
 
-     }
 
-     return result;
 
-   };
 
-   /**
 
-    * "forgiving" attribute list psuedo-grammar:
 
-    * attributes -> keyvalue (',' keyvalue)*
 
-    * keyvalue   -> key '=' value
 
-    * key        -> [^=]*
 
-    * value      -> '"' [^"]* '"' | [^,]*
 
-    */
 
-   const attributeSeparator = function () {
 
-     const key = '[^=]*';
 
-     const value = '"[^"]*"|[^,]*';
 
-     const keyvalue = '(?:' + key + ')=(?:' + value + ')';
 
-     return new RegExp('(?:^|,)(' + keyvalue + ')');
 
-   };
 
-   /**
 
-    * Parse attributes from a line given the separator
 
-    *
 
-    * @param {string} attributes the attribute line to parse
 
-    */
 
-   const parseAttributes$1 = function (attributes) {
 
-     const result = {};
 
-     if (!attributes) {
 
-       return result;
 
-     } // split the string using attributes as the separator
 
-     const attrs = attributes.split(attributeSeparator());
 
-     let i = attrs.length;
 
-     let attr;
 
-     while (i--) {
 
-       // filter out unmatched portions of the string
 
-       if (attrs[i] === '') {
 
-         continue;
 
-       } // split the key and value
 
-       attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
 
-       attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
 
-       attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
 
-       attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
 
-       result[attr[0]] = attr[1];
 
-     }
 
-     return result;
 
-   };
 
-   /**
 
-    * A line-level M3U8 parser event stream. It expects to receive input one
 
-    * line at a time and performs a context-free parse of its contents. A stream
 
-    * interpretation of a manifest can be useful if the manifest is expected to
 
-    * be too large to fit comfortably into memory or the entirety of the input
 
-    * is not immediately available. Otherwise, it's probably much easier to work
 
-    * with a regular `Parser` object.
 
-    *
 
-    * Produces `data` events with an object that captures the parser's
 
-    * interpretation of the input. That object has a property `tag` that is one
 
-    * of `uri`, `comment`, or `tag`. URIs only have a single additional
 
-    * property, `line`, which captures the entirety of the input without
 
-    * interpretation. Comments similarly have a single additional property
 
-    * `text` which is the input without the leading `#`.
 
-    *
 
-    * Tags always have a property `tagType` which is the lower-cased version of
 
-    * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
 
-    * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
 
-    * tags are given the tag type `unknown` and a single additional property
 
-    * `data` with the remainder of the input.
 
-    *
 
-    * @class ParseStream
 
-    * @extends Stream
 
-    */
 
-   class ParseStream extends Stream {
 
-     constructor() {
 
-       super();
 
-       this.customParsers = [];
 
-       this.tagMappers = [];
 
-     }
 
-     /**
 
-      * Parses an additional line of input.
 
-      *
 
-      * @param {string} line a single line of an M3U8 file to parse
 
-      */
 
-     push(line) {
 
-       let match;
 
-       let event; // strip whitespace
 
-       line = line.trim();
 
-       if (line.length === 0) {
 
-         // ignore empty lines
 
-         return;
 
-       } // URIs
 
-       if (line[0] !== '#') {
 
-         this.trigger('data', {
 
-           type: 'uri',
 
-           uri: line
 
-         });
 
-         return;
 
-       } // map tags
 
-       const newLines = this.tagMappers.reduce((acc, mapper) => {
 
-         const mappedLine = mapper(line); // skip if unchanged
 
-         if (mappedLine === line) {
 
-           return acc;
 
-         }
 
-         return acc.concat([mappedLine]);
 
-       }, [line]);
 
-       newLines.forEach(newLine => {
 
-         for (let i = 0; i < this.customParsers.length; i++) {
 
-           if (this.customParsers[i].call(this, newLine)) {
 
-             return;
 
-           }
 
-         } // Comments
 
-         if (newLine.indexOf('#EXT') !== 0) {
 
-           this.trigger('data', {
 
-             type: 'comment',
 
-             text: newLine.slice(1)
 
-           });
 
-           return;
 
-         } // strip off any carriage returns here so the regex matching
 
-         // doesn't have to account for them.
 
-         newLine = newLine.replace('\r', ''); // Tags
 
-         match = /^#EXTM3U/.exec(newLine);
 
-         if (match) {
 
-           this.trigger('data', {
 
-             type: 'tag',
 
-             tagType: 'm3u'
 
-           });
 
-           return;
 
-         }
 
-         match = /^#EXTINF:([0-9\.]*)?,?(.*)?$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'inf'
 
-           };
 
-           if (match[1]) {
 
-             event.duration = parseFloat(match[1]);
 
-           }
 
-           if (match[2]) {
 
-             event.title = match[2];
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'targetduration'
 
-           };
 
-           if (match[1]) {
 
-             event.duration = parseInt(match[1], 10);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'version'
 
-           };
 
-           if (match[1]) {
 
-             event.version = parseInt(match[1], 10);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'media-sequence'
 
-           };
 
-           if (match[1]) {
 
-             event.number = parseInt(match[1], 10);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'discontinuity-sequence'
 
-           };
 
-           if (match[1]) {
 
-             event.number = parseInt(match[1], 10);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'playlist-type'
 
-           };
 
-           if (match[1]) {
 
-             event.playlistType = match[1];
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
 
-         if (match) {
 
-           event = _extends$1(parseByterange(match[1]), {
 
-             type: 'tag',
 
-             tagType: 'byterange'
 
-           });
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'allow-cache'
 
-           };
 
-           if (match[1]) {
 
-             event.allowed = !/NO/.test(match[1]);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-MAP:(.*)$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'map'
 
-           };
 
-           if (match[1]) {
 
-             const attributes = parseAttributes$1(match[1]);
 
-             if (attributes.URI) {
 
-               event.uri = attributes.URI;
 
-             }
 
-             if (attributes.BYTERANGE) {
 
-               event.byterange = parseByterange(attributes.BYTERANGE);
 
-             }
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'stream-inf'
 
-           };
 
-           if (match[1]) {
 
-             event.attributes = parseAttributes$1(match[1]);
 
-             if (event.attributes.RESOLUTION) {
 
-               const split = event.attributes.RESOLUTION.split('x');
 
-               const resolution = {};
 
-               if (split[0]) {
 
-                 resolution.width = parseInt(split[0], 10);
 
-               }
 
-               if (split[1]) {
 
-                 resolution.height = parseInt(split[1], 10);
 
-               }
 
-               event.attributes.RESOLUTION = resolution;
 
-             }
 
-             if (event.attributes.BANDWIDTH) {
 
-               event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
 
-             }
 
-             if (event.attributes['FRAME-RATE']) {
 
-               event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
 
-             }
 
-             if (event.attributes['PROGRAM-ID']) {
 
-               event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
 
-             }
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'media'
 
-           };
 
-           if (match[1]) {
 
-             event.attributes = parseAttributes$1(match[1]);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-ENDLIST/.exec(newLine);
 
-         if (match) {
 
-           this.trigger('data', {
 
-             type: 'tag',
 
-             tagType: 'endlist'
 
-           });
 
-           return;
 
-         }
 
-         match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
 
-         if (match) {
 
-           this.trigger('data', {
 
-             type: 'tag',
 
-             tagType: 'discontinuity'
 
-           });
 
-           return;
 
-         }
 
-         match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'program-date-time'
 
-           };
 
-           if (match[1]) {
 
-             event.dateTimeString = match[1];
 
-             event.dateTimeObject = new Date(match[1]);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-KEY:(.*)$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'key'
 
-           };
 
-           if (match[1]) {
 
-             event.attributes = parseAttributes$1(match[1]); // parse the IV string into a Uint32Array
 
-             if (event.attributes.IV) {
 
-               if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
 
-                 event.attributes.IV = event.attributes.IV.substring(2);
 
-               }
 
-               event.attributes.IV = event.attributes.IV.match(/.{8}/g);
 
-               event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
 
-               event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
 
-               event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
 
-               event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
 
-               event.attributes.IV = new Uint32Array(event.attributes.IV);
 
-             }
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-START:(.*)$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'start'
 
-           };
 
-           if (match[1]) {
 
-             event.attributes = parseAttributes$1(match[1]);
 
-             event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
 
-             event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'cue-out-cont'
 
-           };
 
-           if (match[1]) {
 
-             event.data = match[1];
 
-           } else {
 
-             event.data = '';
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'cue-out'
 
-           };
 
-           if (match[1]) {
 
-             event.data = match[1];
 
-           } else {
 
-             event.data = '';
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-CUE-IN:(.*)?$/.exec(newLine);
 
-         if (match) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'cue-in'
 
-           };
 
-           if (match[1]) {
 
-             event.data = match[1];
 
-           } else {
 
-             event.data = '';
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-SKIP:(.*)$/.exec(newLine);
 
-         if (match && match[1]) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'skip'
 
-           };
 
-           event.attributes = parseAttributes$1(match[1]);
 
-           if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
 
-             event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
 
-           }
 
-           if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
 
-             event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-PART:(.*)$/.exec(newLine);
 
-         if (match && match[1]) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'part'
 
-           };
 
-           event.attributes = parseAttributes$1(match[1]);
 
-           ['DURATION'].forEach(function (key) {
 
-             if (event.attributes.hasOwnProperty(key)) {
 
-               event.attributes[key] = parseFloat(event.attributes[key]);
 
-             }
 
-           });
 
-           ['INDEPENDENT', 'GAP'].forEach(function (key) {
 
-             if (event.attributes.hasOwnProperty(key)) {
 
-               event.attributes[key] = /YES/.test(event.attributes[key]);
 
-             }
 
-           });
 
-           if (event.attributes.hasOwnProperty('BYTERANGE')) {
 
-             event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
 
-           }
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine);
 
-         if (match && match[1]) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'server-control'
 
-           };
 
-           event.attributes = parseAttributes$1(match[1]);
 
-           ['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function (key) {
 
-             if (event.attributes.hasOwnProperty(key)) {
 
-               event.attributes[key] = parseFloat(event.attributes[key]);
 
-             }
 
-           });
 
-           ['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function (key) {
 
-             if (event.attributes.hasOwnProperty(key)) {
 
-               event.attributes[key] = /YES/.test(event.attributes[key]);
 
-             }
 
-           });
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-PART-INF:(.*)$/.exec(newLine);
 
-         if (match && match[1]) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'part-inf'
 
-           };
 
-           event.attributes = parseAttributes$1(match[1]);
 
-           ['PART-TARGET'].forEach(function (key) {
 
-             if (event.attributes.hasOwnProperty(key)) {
 
-               event.attributes[key] = parseFloat(event.attributes[key]);
 
-             }
 
-           });
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine);
 
-         if (match && match[1]) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'preload-hint'
 
-           };
 
-           event.attributes = parseAttributes$1(match[1]);
 
-           ['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) {
 
-             if (event.attributes.hasOwnProperty(key)) {
 
-               event.attributes[key] = parseInt(event.attributes[key], 10);
 
-               const subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
 
-               event.attributes.byterange = event.attributes.byterange || {};
 
-               event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object.
 
-               delete event.attributes[key];
 
-             }
 
-           });
 
-           this.trigger('data', event);
 
-           return;
 
-         }
 
-         match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine);
 
-         if (match && match[1]) {
 
-           event = {
 
-             type: 'tag',
 
-             tagType: 'rendition-report'
 
-           };
 
-           event.attributes = parseAttributes$1(match[1]);
 
-           ['LAST-MSN', 'LAST-PART'].forEach(function (key) {
 
-             if (event.attributes.hasOwnProperty(key)) {
 
-               event.attributes[key] = parseInt(event.attributes[key], 10);
 
-             }
 
-           });
 
-           this.trigger('data', event);
 
-           return;
 
-         } // unknown tag type
 
-         this.trigger('data', {
 
-           type: 'tag',
 
-           data: newLine.slice(4)
 
-         });
 
-       });
 
-     }
 
-     /**
 
-      * Add a parser for custom headers
 
-      *
 
-      * @param {Object}   options              a map of options for the added parser
 
-      * @param {RegExp}   options.expression   a regular expression to match the custom header
 
-      * @param {string}   options.customType   the custom type to register to the output
 
-      * @param {Function} [options.dataParser] function to parse the line into an object
 
-      * @param {boolean}  [options.segment]    should tag data be attached to the segment object
 
-      */
 
-     addParser({
 
-       expression,
 
-       customType,
 
-       dataParser,
 
-       segment
 
-     }) {
 
-       if (typeof dataParser !== 'function') {
 
-         dataParser = line => line;
 
-       }
 
-       this.customParsers.push(line => {
 
-         const match = expression.exec(line);
 
-         if (match) {
 
-           this.trigger('data', {
 
-             type: 'custom',
 
-             data: dataParser(line),
 
-             customType,
 
-             segment
 
-           });
 
-           return true;
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * Add a custom header mapper
 
-      *
 
-      * @param {Object}   options
 
-      * @param {RegExp}   options.expression   a regular expression to match the custom header
 
-      * @param {Function} options.map          function to translate tag into a different tag
 
-      */
 
-     addTagMapper({
 
-       expression,
 
-       map
 
-     }) {
 
-       const mapFn = line => {
 
-         if (expression.test(line)) {
 
-           return map(line);
 
-         }
 
-         return line;
 
-       };
 
-       this.tagMappers.push(mapFn);
 
-     }
 
-   }
 
-   const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase());
 
-   const camelCaseKeys = function (attributes) {
 
-     const result = {};
 
-     Object.keys(attributes).forEach(function (key) {
 
-       result[camelCase(key)] = attributes[key];
 
-     });
 
-     return result;
 
-   }; // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration
 
-   // we need this helper because defaults are based upon targetDuration and
 
-   // partTargetDuration being set, but they may not be if SERVER-CONTROL appears before
 
-   // target durations are set.
 
-   const setHoldBack = function (manifest) {
 
-     const {
 
-       serverControl,
 
-       targetDuration,
 
-       partTargetDuration
 
-     } = manifest;
 
-     if (!serverControl) {
 
-       return;
 
-     }
 
-     const tag = '#EXT-X-SERVER-CONTROL';
 
-     const hb = 'holdBack';
 
-     const phb = 'partHoldBack';
 
-     const minTargetDuration = targetDuration && targetDuration * 3;
 
-     const minPartDuration = partTargetDuration && partTargetDuration * 2;
 
-     if (targetDuration && !serverControl.hasOwnProperty(hb)) {
 
-       serverControl[hb] = minTargetDuration;
 
-       this.trigger('info', {
 
-         message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).`
 
-       });
 
-     }
 
-     if (minTargetDuration && serverControl[hb] < minTargetDuration) {
 
-       this.trigger('warn', {
 
-         message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})`
 
-       });
 
-       serverControl[hb] = minTargetDuration;
 
-     } // default no part hold back to part target duration * 3
 
-     if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
 
-       serverControl[phb] = partTargetDuration * 3;
 
-       this.trigger('info', {
 
-         message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).`
 
-       });
 
-     } // if part hold back is too small default it to part target duration * 2
 
-     if (partTargetDuration && serverControl[phb] < minPartDuration) {
 
-       this.trigger('warn', {
 
-         message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
 
-       });
 
-       serverControl[phb] = minPartDuration;
 
-     }
 
-   };
 
-   /**
 
-    * A parser for M3U8 files. The current interpretation of the input is
 
-    * exposed as a property `manifest` on parser objects. It's just two lines to
 
-    * create and parse a manifest once you have the contents available as a string:
 
-    *
 
-    * ```js
 
-    * var parser = new m3u8.Parser();
 
-    * parser.push(xhr.responseText);
 
-    * ```
 
-    *
 
-    * New input can later be applied to update the manifest object by calling
 
-    * `push` again.
 
-    *
 
-    * The parser attempts to create a usable manifest object even if the
 
-    * underlying input is somewhat nonsensical. It emits `info` and `warning`
 
-    * events during the parse if it encounters input that seems invalid or
 
-    * requires some property of the manifest object to be defaulted.
 
-    *
 
-    * @class Parser
 
-    * @extends Stream
 
-    */
 
-   class Parser extends Stream {
 
-     constructor() {
 
-       super();
 
-       this.lineStream = new LineStream();
 
-       this.parseStream = new ParseStream();
 
-       this.lineStream.pipe(this.parseStream);
 
-       /* eslint-disable consistent-this */
 
-       const self = this;
 
-       /* eslint-enable consistent-this */
 
-       const uris = [];
 
-       let currentUri = {}; // if specified, the active EXT-X-MAP definition
 
-       let currentMap; // if specified, the active decryption key
 
-       let key;
 
-       let hasParts = false;
 
-       const noop = function () {};
 
-       const defaultMediaGroups = {
 
-         'AUDIO': {},
 
-         'VIDEO': {},
 
-         'CLOSED-CAPTIONS': {},
 
-         'SUBTITLES': {}
 
-       }; // This is the Widevine UUID from DASH IF IOP. The same exact string is
 
-       // used in MPDs with Widevine encrypted streams.
 
-       const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
 
-       let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
 
-       this.manifest = {
 
-         allowCache: true,
 
-         discontinuityStarts: [],
 
-         segments: []
 
-       }; // keep track of the last seen segment's byte range end, as segments are not required
 
-       // to provide the offset, in which case it defaults to the next byte after the
 
-       // previous segment
 
-       let lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
 
-       let lastPartByterangeEnd = 0;
 
-       this.on('end', () => {
 
-         // only add preloadSegment if we don't yet have a uri for it.
 
-         // and we actually have parts/preloadHints
 
-         if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) {
 
-           return;
 
-         }
 
-         if (!currentUri.map && currentMap) {
 
-           currentUri.map = currentMap;
 
-         }
 
-         if (!currentUri.key && key) {
 
-           currentUri.key = key;
 
-         }
 
-         if (!currentUri.timeline && typeof currentTimeline === 'number') {
 
-           currentUri.timeline = currentTimeline;
 
-         }
 
-         this.manifest.preloadSegment = currentUri;
 
-       }); // update the manifest with the m3u8 entry from the parse stream
 
-       this.parseStream.on('data', function (entry) {
 
-         let mediaGroup;
 
-         let rendition;
 
-         ({
 
-           tag() {
 
-             // switch based on the tag type
 
-             (({
 
-               version() {
 
-                 if (entry.version) {
 
-                   this.manifest.version = entry.version;
 
-                 }
 
-               },
 
-               'allow-cache'() {
 
-                 this.manifest.allowCache = entry.allowed;
 
-                 if (!('allowed' in entry)) {
 
-                   this.trigger('info', {
 
-                     message: 'defaulting allowCache to YES'
 
-                   });
 
-                   this.manifest.allowCache = true;
 
-                 }
 
-               },
 
-               byterange() {
 
-                 const byterange = {};
 
-                 if ('length' in entry) {
 
-                   currentUri.byterange = byterange;
 
-                   byterange.length = entry.length;
 
-                   if (!('offset' in entry)) {
 
-                     /*
 
-                      * From the latest spec (as of this writing):
 
-                      * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2
 
-                      *
 
-                      * Same text since EXT-X-BYTERANGE's introduction in draft 7:
 
-                      * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1)
 
-                      *
 
-                      * "If o [offset] is not present, the sub-range begins at the next byte
 
-                      * following the sub-range of the previous media segment."
 
-                      */
 
-                     entry.offset = lastByterangeEnd;
 
-                   }
 
-                 }
 
-                 if ('offset' in entry) {
 
-                   currentUri.byterange = byterange;
 
-                   byterange.offset = entry.offset;
 
-                 }
 
-                 lastByterangeEnd = byterange.offset + byterange.length;
 
-               },
 
-               endlist() {
 
-                 this.manifest.endList = true;
 
-               },
 
-               inf() {
 
-                 if (!('mediaSequence' in this.manifest)) {
 
-                   this.manifest.mediaSequence = 0;
 
-                   this.trigger('info', {
 
-                     message: 'defaulting media sequence to zero'
 
-                   });
 
-                 }
 
-                 if (!('discontinuitySequence' in this.manifest)) {
 
-                   this.manifest.discontinuitySequence = 0;
 
-                   this.trigger('info', {
 
-                     message: 'defaulting discontinuity sequence to zero'
 
-                   });
 
-                 }
 
-                 if (entry.duration > 0) {
 
-                   currentUri.duration = entry.duration;
 
-                 }
 
-                 if (entry.duration === 0) {
 
-                   currentUri.duration = 0.01;
 
-                   this.trigger('info', {
 
-                     message: 'updating zero segment duration to a small value'
 
-                   });
 
-                 }
 
-                 this.manifest.segments = uris;
 
-               },
 
-               key() {
 
-                 if (!entry.attributes) {
 
-                   this.trigger('warn', {
 
-                     message: 'ignoring key declaration without attribute list'
 
-                   });
 
-                   return;
 
-                 } // clear the active encryption key
 
-                 if (entry.attributes.METHOD === 'NONE') {
 
-                   key = null;
 
-                   return;
 
-                 }
 
-                 if (!entry.attributes.URI) {
 
-                   this.trigger('warn', {
 
-                     message: 'ignoring key declaration without URI'
 
-                   });
 
-                   return;
 
-                 }
 
-                 if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
 
-                   this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
 
-                   this.manifest.contentProtection['com.apple.fps.1_0'] = {
 
-                     attributes: entry.attributes
 
-                   };
 
-                   return;
 
-                 }
 
-                 if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
 
-                   this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
 
-                   this.manifest.contentProtection['com.microsoft.playready'] = {
 
-                     uri: entry.attributes.URI
 
-                   };
 
-                   return;
 
-                 } // check if the content is encrypted for Widevine
 
-                 // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
 
-                 if (entry.attributes.KEYFORMAT === widevineUuid) {
 
-                   const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
 
-                   if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
 
-                     this.trigger('warn', {
 
-                       message: 'invalid key method provided for Widevine'
 
-                     });
 
-                     return;
 
-                   }
 
-                   if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') {
 
-                     this.trigger('warn', {
 
-                       message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead'
 
-                     });
 
-                   }
 
-                   if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
 
-                     this.trigger('warn', {
 
-                       message: 'invalid key URI provided for Widevine'
 
-                     });
 
-                     return;
 
-                   }
 
-                   if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) {
 
-                     this.trigger('warn', {
 
-                       message: 'invalid key ID provided for Widevine'
 
-                     });
 
-                     return;
 
-                   } // if Widevine key attributes are valid, store them as `contentProtection`
 
-                   // on the manifest to emulate Widevine tag structure in a DASH mpd
 
-                   this.manifest.contentProtection = this.manifest.contentProtection || {};
 
-                   this.manifest.contentProtection['com.widevine.alpha'] = {
 
-                     attributes: {
 
-                       schemeIdUri: entry.attributes.KEYFORMAT,
 
-                       // remove '0x' from the key id string
 
-                       keyId: entry.attributes.KEYID.substring(2)
 
-                     },
 
-                     // decode the base64-encoded PSSH box
 
-                     pssh: decodeB64ToUint8Array$1(entry.attributes.URI.split(',')[1])
 
-                   };
 
-                   return;
 
-                 }
 
-                 if (!entry.attributes.METHOD) {
 
-                   this.trigger('warn', {
 
-                     message: 'defaulting key method to AES-128'
 
-                   });
 
-                 } // setup an encryption key for upcoming segments
 
-                 key = {
 
-                   method: entry.attributes.METHOD || 'AES-128',
 
-                   uri: entry.attributes.URI
 
-                 };
 
-                 if (typeof entry.attributes.IV !== 'undefined') {
 
-                   key.iv = entry.attributes.IV;
 
-                 }
 
-               },
 
-               'media-sequence'() {
 
-                 if (!isFinite(entry.number)) {
 
-                   this.trigger('warn', {
 
-                     message: 'ignoring invalid media sequence: ' + entry.number
 
-                   });
 
-                   return;
 
-                 }
 
-                 this.manifest.mediaSequence = entry.number;
 
-               },
 
-               'discontinuity-sequence'() {
 
-                 if (!isFinite(entry.number)) {
 
-                   this.trigger('warn', {
 
-                     message: 'ignoring invalid discontinuity sequence: ' + entry.number
 
-                   });
 
-                   return;
 
-                 }
 
-                 this.manifest.discontinuitySequence = entry.number;
 
-                 currentTimeline = entry.number;
 
-               },
 
-               'playlist-type'() {
 
-                 if (!/VOD|EVENT/.test(entry.playlistType)) {
 
-                   this.trigger('warn', {
 
-                     message: 'ignoring unknown playlist type: ' + entry.playlist
 
-                   });
 
-                   return;
 
-                 }
 
-                 this.manifest.playlistType = entry.playlistType;
 
-               },
 
-               map() {
 
-                 currentMap = {};
 
-                 if (entry.uri) {
 
-                   currentMap.uri = entry.uri;
 
-                 }
 
-                 if (entry.byterange) {
 
-                   currentMap.byterange = entry.byterange;
 
-                 }
 
-                 if (key) {
 
-                   currentMap.key = key;
 
-                 }
 
-               },
 
-               'stream-inf'() {
 
-                 this.manifest.playlists = uris;
 
-                 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
 
-                 if (!entry.attributes) {
 
-                   this.trigger('warn', {
 
-                     message: 'ignoring empty stream-inf attributes'
 
-                   });
 
-                   return;
 
-                 }
 
-                 if (!currentUri.attributes) {
 
-                   currentUri.attributes = {};
 
-                 }
 
-                 _extends$1(currentUri.attributes, entry.attributes);
 
-               },
 
-               media() {
 
-                 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
 
-                 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
 
-                   this.trigger('warn', {
 
-                     message: 'ignoring incomplete or missing media group'
 
-                   });
 
-                   return;
 
-                 } // find the media group, creating defaults as necessary
 
-                 const mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
 
-                 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
 
-                 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
 
-                 rendition = {
 
-                   default: /yes/i.test(entry.attributes.DEFAULT)
 
-                 };
 
-                 if (rendition.default) {
 
-                   rendition.autoselect = true;
 
-                 } else {
 
-                   rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
 
-                 }
 
-                 if (entry.attributes.LANGUAGE) {
 
-                   rendition.language = entry.attributes.LANGUAGE;
 
-                 }
 
-                 if (entry.attributes.URI) {
 
-                   rendition.uri = entry.attributes.URI;
 
-                 }
 
-                 if (entry.attributes['INSTREAM-ID']) {
 
-                   rendition.instreamId = entry.attributes['INSTREAM-ID'];
 
-                 }
 
-                 if (entry.attributes.CHARACTERISTICS) {
 
-                   rendition.characteristics = entry.attributes.CHARACTERISTICS;
 
-                 }
 
-                 if (entry.attributes.FORCED) {
 
-                   rendition.forced = /yes/i.test(entry.attributes.FORCED);
 
-                 } // insert the new rendition
 
-                 mediaGroup[entry.attributes.NAME] = rendition;
 
-               },
 
-               discontinuity() {
 
-                 currentTimeline += 1;
 
-                 currentUri.discontinuity = true;
 
-                 this.manifest.discontinuityStarts.push(uris.length);
 
-               },
 
-               'program-date-time'() {
 
-                 if (typeof this.manifest.dateTimeString === 'undefined') {
 
-                   // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
 
-                   // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
 
-                   // to the manifest object
 
-                   // TODO: Consider removing this in future major version
 
-                   this.manifest.dateTimeString = entry.dateTimeString;
 
-                   this.manifest.dateTimeObject = entry.dateTimeObject;
 
-                 }
 
-                 currentUri.dateTimeString = entry.dateTimeString;
 
-                 currentUri.dateTimeObject = entry.dateTimeObject;
 
-               },
 
-               targetduration() {
 
-                 if (!isFinite(entry.duration) || entry.duration < 0) {
 
-                   this.trigger('warn', {
 
-                     message: 'ignoring invalid target duration: ' + entry.duration
 
-                   });
 
-                   return;
 
-                 }
 
-                 this.manifest.targetDuration = entry.duration;
 
-                 setHoldBack.call(this, this.manifest);
 
-               },
 
-               start() {
 
-                 if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
 
-                   this.trigger('warn', {
 
-                     message: 'ignoring start declaration without appropriate attribute list'
 
-                   });
 
-                   return;
 
-                 }
 
-                 this.manifest.start = {
 
-                   timeOffset: entry.attributes['TIME-OFFSET'],
 
-                   precise: entry.attributes.PRECISE
 
-                 };
 
-               },
 
-               'cue-out'() {
 
-                 currentUri.cueOut = entry.data;
 
-               },
 
-               'cue-out-cont'() {
 
-                 currentUri.cueOutCont = entry.data;
 
-               },
 
-               'cue-in'() {
 
-                 currentUri.cueIn = entry.data;
 
-               },
 
-               'skip'() {
 
-                 this.manifest.skip = camelCaseKeys(entry.attributes);
 
-                 this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']);
 
-               },
 
-               'part'() {
 
-                 hasParts = true; // parts are always specifed before a segment
 
-                 const segmentIndex = this.manifest.segments.length;
 
-                 const part = camelCaseKeys(entry.attributes);
 
-                 currentUri.parts = currentUri.parts || [];
 
-                 currentUri.parts.push(part);
 
-                 if (part.byterange) {
 
-                   if (!part.byterange.hasOwnProperty('offset')) {
 
-                     part.byterange.offset = lastPartByterangeEnd;
 
-                   }
 
-                   lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
 
-                 }
 
-                 const partIndex = currentUri.parts.length - 1;
 
-                 this.warnOnMissingAttributes_(`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, entry.attributes, ['URI', 'DURATION']);
 
-                 if (this.manifest.renditionReports) {
 
-                   this.manifest.renditionReports.forEach((r, i) => {
 
-                     if (!r.hasOwnProperty('lastPart')) {
 
-                       this.trigger('warn', {
 
-                         message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART`
 
-                       });
 
-                     }
 
-                   });
 
-                 }
 
-               },
 
-               'server-control'() {
 
-                 const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
 
-                 if (!attrs.hasOwnProperty('canBlockReload')) {
 
-                   attrs.canBlockReload = false;
 
-                   this.trigger('info', {
 
-                     message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false'
 
-                   });
 
-                 }
 
-                 setHoldBack.call(this, this.manifest);
 
-                 if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) {
 
-                   this.trigger('warn', {
 
-                     message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set'
 
-                   });
 
-                 }
 
-               },
 
-               'preload-hint'() {
 
-                 // parts are always specifed before a segment
 
-                 const segmentIndex = this.manifest.segments.length;
 
-                 const hint = camelCaseKeys(entry.attributes);
 
-                 const isPart = hint.type && hint.type === 'PART';
 
-                 currentUri.preloadHints = currentUri.preloadHints || [];
 
-                 currentUri.preloadHints.push(hint);
 
-                 if (hint.byterange) {
 
-                   if (!hint.byterange.hasOwnProperty('offset')) {
 
-                     // use last part byterange end or zero if not a part.
 
-                     hint.byterange.offset = isPart ? lastPartByterangeEnd : 0;
 
-                     if (isPart) {
 
-                       lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length;
 
-                     }
 
-                   }
 
-                 }
 
-                 const index = currentUri.preloadHints.length - 1;
 
-                 this.warnOnMissingAttributes_(`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, entry.attributes, ['TYPE', 'URI']);
 
-                 if (!hint.type) {
 
-                   return;
 
-                 } // search through all preload hints except for the current one for
 
-                 // a duplicate type.
 
-                 for (let i = 0; i < currentUri.preloadHints.length - 1; i++) {
 
-                   const otherHint = currentUri.preloadHints[i];
 
-                   if (!otherHint.type) {
 
-                     continue;
 
-                   }
 
-                   if (otherHint.type === hint.type) {
 
-                     this.trigger('warn', {
 
-                       message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}`
 
-                     });
 
-                   }
 
-                 }
 
-               },
 
-               'rendition-report'() {
 
-                 const report = camelCaseKeys(entry.attributes);
 
-                 this.manifest.renditionReports = this.manifest.renditionReports || [];
 
-                 this.manifest.renditionReports.push(report);
 
-                 const index = this.manifest.renditionReports.length - 1;
 
-                 const required = ['LAST-MSN', 'URI'];
 
-                 if (hasParts) {
 
-                   required.push('LAST-PART');
 
-                 }
 
-                 this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required);
 
-               },
 
-               'part-inf'() {
 
-                 this.manifest.partInf = camelCaseKeys(entry.attributes);
 
-                 this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
 
-                 if (this.manifest.partInf.partTarget) {
 
-                   this.manifest.partTargetDuration = this.manifest.partInf.partTarget;
 
-                 }
 
-                 setHoldBack.call(this, this.manifest);
 
-               }
 
-             })[entry.tagType] || noop).call(self);
 
-           },
 
-           uri() {
 
-             currentUri.uri = entry.uri;
 
-             uris.push(currentUri); // if no explicit duration was declared, use the target duration
 
-             if (this.manifest.targetDuration && !('duration' in currentUri)) {
 
-               this.trigger('warn', {
 
-                 message: 'defaulting segment duration to the target duration'
 
-               });
 
-               currentUri.duration = this.manifest.targetDuration;
 
-             } // annotate with encryption information, if necessary
 
-             if (key) {
 
-               currentUri.key = key;
 
-             }
 
-             currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
 
-             if (currentMap) {
 
-               currentUri.map = currentMap;
 
-             } // reset the last byterange end as it needs to be 0 between parts
 
-             lastPartByterangeEnd = 0; // prepare for the next URI
 
-             currentUri = {};
 
-           },
 
-           comment() {// comments are not important for playback
 
-           },
 
-           custom() {
 
-             // if this is segment-level data attach the output to the segment
 
-             if (entry.segment) {
 
-               currentUri.custom = currentUri.custom || {};
 
-               currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
 
-             } else {
 
-               this.manifest.custom = this.manifest.custom || {};
 
-               this.manifest.custom[entry.customType] = entry.data;
 
-             }
 
-           }
 
-         })[entry.type].call(self);
 
-       });
 
-     }
 
-     warnOnMissingAttributes_(identifier, attributes, required) {
 
-       const missing = [];
 
-       required.forEach(function (key) {
 
-         if (!attributes.hasOwnProperty(key)) {
 
-           missing.push(key);
 
-         }
 
-       });
 
-       if (missing.length) {
 
-         this.trigger('warn', {
 
-           message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`
 
-         });
 
-       }
 
-     }
 
-     /**
 
-      * Parse the input string and update the manifest object.
 
-      *
 
-      * @param {string} chunk a potentially incomplete portion of the manifest
 
-      */
 
-     push(chunk) {
 
-       this.lineStream.push(chunk);
 
-     }
 
-     /**
 
-      * Flush any remaining input. This can be handy if the last line of an M3U8
 
-      * manifest did not contain a trailing newline but the file has been
 
-      * completely received.
 
-      */
 
-     end() {
 
-       // flush any buffered input
 
-       this.lineStream.push('\n');
 
-       this.trigger('end');
 
-     }
 
-     /**
 
-      * Add an additional parser for non-standard tags
 
-      *
 
-      * @param {Object}   options              a map of options for the added parser
 
-      * @param {RegExp}   options.expression   a regular expression to match the custom header
 
-      * @param {string}   options.type         the type to register to the output
 
-      * @param {Function} [options.dataParser] function to parse the line into an object
 
-      * @param {boolean}  [options.segment]    should tag data be attached to the segment object
 
-      */
 
-     addParser(options) {
 
-       this.parseStream.addParser(options);
 
-     }
 
-     /**
 
-      * Add a custom header mapper
 
-      *
 
-      * @param {Object}   options
 
-      * @param {RegExp}   options.expression   a regular expression to match the custom header
 
-      * @param {Function} options.map          function to translate tag into a different tag
 
-      */
 
-     addTagMapper(options) {
 
-       this.parseStream.addTagMapper(options);
 
-     }
 
-   }
 
-   var regexs = {
 
-     // to determine mime types
 
-     mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/,
 
-     webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
 
-     ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
 
-     // to determine if a codec is audio or video
 
-     video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
 
-     audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/,
 
-     text: /^(stpp.ttml.im1t)/,
 
-     // mux.js support regex
 
-     muxerVideo: /^(avc0?1)/,
 
-     muxerAudio: /^(mp4a)/,
 
-     // match nothing as muxer does not support text right now.
 
-     // there cannot never be a character before the start of a string
 
-     // so this matches nothing.
 
-     muxerText: /a^/
 
-   };
 
-   var mediaTypes = ['video', 'audio', 'text'];
 
-   var upperMediaTypes = ['Video', 'Audio', 'Text'];
 
-   /**
 
-    * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
 
-    * `avc1.<hhhhhh>`
 
-    *
 
-    * @param {string} codec
 
-    *        Codec string to translate
 
-    * @return {string}
 
-    *         The translated codec string
 
-    */
 
-   var translateLegacyCodec = function translateLegacyCodec(codec) {
 
-     if (!codec) {
 
-       return codec;
 
-     }
 
-     return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
 
-       var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
 
-       var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
 
-       return 'avc1.' + profileHex + '00' + avcLevelHex;
 
-     });
 
-   };
 
-   /**
 
-    * @typedef {Object} ParsedCodecInfo
 
-    * @property {number} codecCount
 
-    *           Number of codecs parsed
 
-    * @property {string} [videoCodec]
 
-    *           Parsed video codec (if found)
 
-    * @property {string} [videoObjectTypeIndicator]
 
-    *           Video object type indicator (if found)
 
-    * @property {string|null} audioProfile
 
-    *           Audio profile
 
-    */
 
-   /**
 
-    * Parses a codec string to retrieve the number of codecs specified, the video codec and
 
-    * object type indicator, and the audio profile.
 
-    *
 
-    * @param {string} [codecString]
 
-    *        The codec string to parse
 
-    * @return {ParsedCodecInfo}
 
-    *         Parsed codec info
 
-    */
 
-   var parseCodecs = function parseCodecs(codecString) {
 
-     if (codecString === void 0) {
 
-       codecString = '';
 
-     }
 
-     var codecs = codecString.split(',');
 
-     var result = [];
 
-     codecs.forEach(function (codec) {
 
-       codec = codec.trim();
 
-       var codecType;
 
-       mediaTypes.forEach(function (name) {
 
-         var match = regexs[name].exec(codec.toLowerCase());
 
-         if (!match || match.length <= 1) {
 
-           return;
 
-         }
 
-         codecType = name; // maintain codec case
 
-         var type = codec.substring(0, match[1].length);
 
-         var details = codec.replace(type, '');
 
-         result.push({
 
-           type: type,
 
-           details: details,
 
-           mediaType: name
 
-         });
 
-       });
 
-       if (!codecType) {
 
-         result.push({
 
-           type: codec,
 
-           details: '',
 
-           mediaType: 'unknown'
 
-         });
 
-       }
 
-     });
 
-     return result;
 
-   };
 
-   /**
 
-    * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
 
-    * a default alternate audio playlist for the provided audio group.
 
-    *
 
-    * @param {Object} master
 
-    *        The master playlist
 
-    * @param {string} audioGroupId
 
-    *        ID of the audio group for which to find the default codec info
 
-    * @return {ParsedCodecInfo}
 
-    *         Parsed codec info
 
-    */
 
-   var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
 
-     if (!master.mediaGroups.AUDIO || !audioGroupId) {
 
-       return null;
 
-     }
 
-     var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
 
-     if (!audioGroup) {
 
-       return null;
 
-     }
 
-     for (var name in audioGroup) {
 
-       var audioType = audioGroup[name];
 
-       if (audioType.default && audioType.playlists) {
 
-         // codec should be the same for all playlists within the audio type
 
-         return parseCodecs(audioType.playlists[0].attributes.CODECS);
 
-       }
 
-     }
 
-     return null;
 
-   };
 
-   var isAudioCodec = function isAudioCodec(codec) {
 
-     if (codec === void 0) {
 
-       codec = '';
 
-     }
 
-     return regexs.audio.test(codec.trim().toLowerCase());
 
-   };
 
-   var isTextCodec = function isTextCodec(codec) {
 
-     if (codec === void 0) {
 
-       codec = '';
 
-     }
 
-     return regexs.text.test(codec.trim().toLowerCase());
 
-   };
 
-   var getMimeForCodec = function getMimeForCodec(codecString) {
 
-     if (!codecString || typeof codecString !== 'string') {
 
-       return;
 
-     }
 
-     var codecs = codecString.toLowerCase().split(',').map(function (c) {
 
-       return translateLegacyCodec(c.trim());
 
-     }); // default to video type
 
-     var type = 'video'; // only change to audio type if the only codec we have is
 
-     // audio
 
-     if (codecs.length === 1 && isAudioCodec(codecs[0])) {
 
-       type = 'audio';
 
-     } else if (codecs.length === 1 && isTextCodec(codecs[0])) {
 
-       // text uses application/<container> for now
 
-       type = 'application';
 
-     } // default the container to mp4
 
-     var container = 'mp4'; // every codec must be able to go into the container
 
-     // for that container to be the correct one
 
-     if (codecs.every(function (c) {
 
-       return regexs.mp4.test(c);
 
-     })) {
 
-       container = 'mp4';
 
-     } else if (codecs.every(function (c) {
 
-       return regexs.webm.test(c);
 
-     })) {
 
-       container = 'webm';
 
-     } else if (codecs.every(function (c) {
 
-       return regexs.ogg.test(c);
 
-     })) {
 
-       container = 'ogg';
 
-     }
 
-     return type + "/" + container + ";codecs=\"" + codecString + "\"";
 
-   };
 
-   var browserSupportsCodec = function browserSupportsCodec(codecString) {
 
-     if (codecString === void 0) {
 
-       codecString = '';
 
-     }
 
-     return window.MediaSource && window.MediaSource.isTypeSupported && window.MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
 
-   };
 
-   var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
 
-     if (codecString === void 0) {
 
-       codecString = '';
 
-     }
 
-     return codecString.toLowerCase().split(',').every(function (codec) {
 
-       codec = codec.trim(); // any match is supported.
 
-       for (var i = 0; i < upperMediaTypes.length; i++) {
 
-         var type = upperMediaTypes[i];
 
-         if (regexs["muxer" + type].test(codec)) {
 
-           return true;
 
-         }
 
-       }
 
-       return false;
 
-     });
 
-   };
 
-   var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
 
-   var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
 
-   var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
 
-   var DASH_REGEX = /^application\/dash\+xml/i;
 
-   /**
 
-    * Returns a string that describes the type of source based on a video source object's
 
-    * media type.
 
-    *
 
-    * @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type}
 
-    *
 
-    * @param {string} type
 
-    *        Video source object media type
 
-    * @return {('hls'|'dash'|'vhs-json'|null)}
 
-    *         VHS source type string
 
-    */
 
-   var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
 
-     if (MPEGURL_REGEX.test(type)) {
 
-       return 'hls';
 
-     }
 
-     if (DASH_REGEX.test(type)) {
 
-       return 'dash';
 
-     } // Denotes the special case of a manifest object passed to http-streaming instead of a
 
-     // source URL.
 
-     //
 
-     // See https://en.wikipedia.org/wiki/Media_type for details on specifying media types.
 
-     //
 
-     // In this case, vnd stands for vendor, video.js for the organization, VHS for this
 
-     // project, and the +json suffix identifies the structure of the media type.
 
-     if (type === 'application/vnd.videojs.vhs+json') {
 
-       return 'vhs-json';
 
-     }
 
-     return null;
 
-   };
 
-   // const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2));
 
-   // we used to do this with log2 but BigInt does not support builtin math
 
-   // Math.ceil(log2(x));
 
-   var countBits = function countBits(x) {
 
-     return x.toString(2).length;
 
-   }; // count the number of whole bytes it would take to represent a number
 
-   var countBytes = function countBytes(x) {
 
-     return Math.ceil(countBits(x) / 8);
 
-   };
 
-   var isArrayBufferView = function isArrayBufferView(obj) {
 
-     if (ArrayBuffer.isView === 'function') {
 
-       return ArrayBuffer.isView(obj);
 
-     }
 
-     return obj && obj.buffer instanceof ArrayBuffer;
 
-   };
 
-   var isTypedArray = function isTypedArray(obj) {
 
-     return isArrayBufferView(obj);
 
-   };
 
-   var toUint8 = function toUint8(bytes) {
 
-     if (bytes instanceof Uint8Array) {
 
-       return bytes;
 
-     }
 
-     if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) {
 
-       // any non-number or NaN leads to empty uint8array
 
-       // eslint-disable-next-line
 
-       if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) {
 
-         bytes = 0;
 
-       } else {
 
-         bytes = [bytes];
 
-       }
 
-     }
 
-     return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
 
-   };
 
-   var BigInt = window.BigInt || Number;
 
-   var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
 
-   (function () {
 
-     var a = new Uint16Array([0xFFCC]);
 
-     var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
 
-     if (b[0] === 0xFF) {
 
-       return 'big';
 
-     }
 
-     if (b[0] === 0xCC) {
 
-       return 'little';
 
-     }
 
-     return 'unknown';
 
-   })();
 
-   var bytesToNumber = function bytesToNumber(bytes, _temp) {
 
-     var _ref = _temp === void 0 ? {} : _temp,
 
-       _ref$signed = _ref.signed,
 
-       signed = _ref$signed === void 0 ? false : _ref$signed,
 
-       _ref$le = _ref.le,
 
-       le = _ref$le === void 0 ? false : _ref$le;
 
-     bytes = toUint8(bytes);
 
-     var fn = le ? 'reduce' : 'reduceRight';
 
-     var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn];
 
-     var number = obj.call(bytes, function (total, byte, i) {
 
-       var exponent = le ? i : Math.abs(i + 1 - bytes.length);
 
-       return total + BigInt(byte) * BYTE_TABLE[exponent];
 
-     }, BigInt(0));
 
-     if (signed) {
 
-       var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1);
 
-       number = BigInt(number);
 
-       if (number > max) {
 
-         number -= max;
 
-         number -= max;
 
-         number -= BigInt(2);
 
-       }
 
-     }
 
-     return Number(number);
 
-   };
 
-   var numberToBytes = function numberToBytes(number, _temp2) {
 
-     var _ref2 = _temp2 === void 0 ? {} : _temp2,
 
-       _ref2$le = _ref2.le,
 
-       le = _ref2$le === void 0 ? false : _ref2$le;
 
-     // eslint-disable-next-line
 
-     if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
 
-       number = 0;
 
-     }
 
-     number = BigInt(number);
 
-     var byteCount = countBytes(number);
 
-     var bytes = new Uint8Array(new ArrayBuffer(byteCount));
 
-     for (var i = 0; i < byteCount; i++) {
 
-       var byteIndex = le ? i : Math.abs(i + 1 - bytes.length);
 
-       bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF));
 
-       if (number < 0) {
 
-         bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
 
-         bytes[byteIndex] -= i === 0 ? 1 : 2;
 
-       }
 
-     }
 
-     return bytes;
 
-   };
 
-   var stringToBytes = function stringToBytes(string, stringIsBytes) {
 
-     if (typeof string !== 'string' && string && typeof string.toString === 'function') {
 
-       string = string.toString();
 
-     }
 
-     if (typeof string !== 'string') {
 
-       return new Uint8Array();
 
-     } // If the string already is bytes, we don't have to do this
 
-     // otherwise we do this so that we split multi length characters
 
-     // into individual bytes
 
-     if (!stringIsBytes) {
 
-       string = unescape(encodeURIComponent(string));
 
-     }
 
-     var view = new Uint8Array(string.length);
 
-     for (var i = 0; i < string.length; i++) {
 
-       view[i] = string.charCodeAt(i);
 
-     }
 
-     return view;
 
-   };
 
-   var concatTypedArrays = function concatTypedArrays() {
 
-     for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
 
-       buffers[_key] = arguments[_key];
 
-     }
 
-     buffers = buffers.filter(function (b) {
 
-       return b && (b.byteLength || b.length) && typeof b !== 'string';
 
-     });
 
-     if (buffers.length <= 1) {
 
-       // for 0 length we will return empty uint8
 
-       // for 1 length we return the first uint8
 
-       return toUint8(buffers[0]);
 
-     }
 
-     var totalLen = buffers.reduce(function (total, buf, i) {
 
-       return total + (buf.byteLength || buf.length);
 
-     }, 0);
 
-     var tempBuffer = new Uint8Array(totalLen);
 
-     var offset = 0;
 
-     buffers.forEach(function (buf) {
 
-       buf = toUint8(buf);
 
-       tempBuffer.set(buf, offset);
 
-       offset += buf.byteLength;
 
-     });
 
-     return tempBuffer;
 
-   };
 
-   /**
 
-    * Check if the bytes "b" are contained within bytes "a".
 
-    *
 
-    * @param {Uint8Array|Array} a
 
-    *        Bytes to check in
 
-    *
 
-    * @param {Uint8Array|Array} b
 
-    *        Bytes to check for
 
-    *
 
-    * @param {Object} options
 
-    *        options
 
-    *
 
-    * @param {Array|Uint8Array} [offset=0]
 
-    *        offset to use when looking at bytes in a
 
-    *
 
-    * @param {Array|Uint8Array} [mask=[]]
 
-    *        mask to use on bytes before comparison.
 
-    *
 
-    * @return {boolean}
 
-    *         If all bytes in b are inside of a, taking into account
 
-    *         bit masks.
 
-    */
 
-   var bytesMatch = function bytesMatch(a, b, _temp3) {
 
-     var _ref3 = _temp3 === void 0 ? {} : _temp3,
 
-       _ref3$offset = _ref3.offset,
 
-       offset = _ref3$offset === void 0 ? 0 : _ref3$offset,
 
-       _ref3$mask = _ref3.mask,
 
-       mask = _ref3$mask === void 0 ? [] : _ref3$mask;
 
-     a = toUint8(a);
 
-     b = toUint8(b); // ie 11 does not support uint8 every
 
-     var fn = b.every ? b.every : Array.prototype.every;
 
-     return b.length && a.length - offset >= b.length &&
 
-     // ie 11 doesn't support every on uin8
 
-     fn.call(b, function (bByte, i) {
 
-       var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i];
 
-       return bByte === aByte;
 
-     });
 
-   };
 
-   var DEFAULT_LOCATION = 'http://example.com';
 
-   var resolveUrl$1 = function resolveUrl(baseUrl, relativeUrl) {
 
-     // return early if we don't need to resolve
 
-     if (/^[a-z]+:/i.test(relativeUrl)) {
 
-       return relativeUrl;
 
-     } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location
 
-     if (/^data:/.test(baseUrl)) {
 
-       baseUrl = window.location && window.location.href || '';
 
-     } // IE11 supports URL but not the URL constructor
 
-     // feature detect the behavior we want
 
-     var nativeURL = typeof window.URL === 'function';
 
-     var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node)
 
-     // and if baseUrl isn't an absolute url
 
-     var removeLocation = !window.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
 
-     if (nativeURL) {
 
-       baseUrl = new window.URL(baseUrl, window.location || DEFAULT_LOCATION);
 
-     } else if (!/\/\//i.test(baseUrl)) {
 
-       baseUrl = urlToolkit.buildAbsoluteURL(window.location && window.location.href || '', baseUrl);
 
-     }
 
-     if (nativeURL) {
 
-       var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol
 
-       // and if we're location-less, remove the location
 
-       // otherwise, return the url unmodified
 
-       if (removeLocation) {
 
-         return newUrl.href.slice(DEFAULT_LOCATION.length);
 
-       } else if (protocolLess) {
 
-         return newUrl.href.slice(newUrl.protocol.length);
 
-       }
 
-       return newUrl.href;
 
-     }
 
-     return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
 
-   };
 
-   /**
 
-    * Loops through all supported media groups in master and calls the provided
 
-    * callback for each group
 
-    *
 
-    * @param {Object} master
 
-    *        The parsed master manifest object
 
-    * @param {string[]} groups
 
-    *        The media groups to call the callback for
 
-    * @param {Function} callback
 
-    *        Callback to call for each media group
 
-    */
 
-   var forEachMediaGroup$1 = function forEachMediaGroup(master, groups, callback) {
 
-     groups.forEach(function (mediaType) {
 
-       for (var groupKey in master.mediaGroups[mediaType]) {
 
-         for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
 
-           var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
 
-           callback(mediaProperties, mediaType, groupKey, labelKey);
 
-         }
 
-       }
 
-     });
 
-   };
 
-   var atob = function atob(s) {
 
-     return window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
 
-   };
 
-   function decodeB64ToUint8Array(b64Text) {
 
-     var decodedString = atob(b64Text);
 
-     var array = new Uint8Array(decodedString.length);
 
-     for (var i = 0; i < decodedString.length; i++) {
 
-       array[i] = decodedString.charCodeAt(i);
 
-     }
 
-     return array;
 
-   }
 
-   /**
 
-    * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
 
-    *
 
-    * Works with anything that has a `length` property and index access properties, including NodeList.
 
-    *
 
-    * @template {unknown} T
 
-    * @param {Array<T> | ({length:number, [number]: T})} list
 
-    * @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
 
-    * @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default,
 
-    * 				allows injecting a custom implementation in tests
 
-    * @returns {T | undefined}
 
-    *
 
-    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
 
-    * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find
 
-    */
 
-   function find$1(list, predicate, ac) {
 
-     if (ac === undefined) {
 
-       ac = Array.prototype;
 
-     }
 
-     if (list && typeof ac.find === 'function') {
 
-       return ac.find.call(list, predicate);
 
-     }
 
-     for (var i = 0; i < list.length; i++) {
 
-       if (Object.prototype.hasOwnProperty.call(list, i)) {
 
-         var item = list[i];
 
-         if (predicate.call(undefined, item, i, list)) {
 
-           return item;
 
-         }
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * "Shallow freezes" an object to render it immutable.
 
-    * Uses `Object.freeze` if available,
 
-    * otherwise the immutability is only in the type.
 
-    *
 
-    * Is used to create "enum like" objects.
 
-    *
 
-    * @template T
 
-    * @param {T} object the object to freeze
 
-    * @param {Pick<ObjectConstructor, 'freeze'> = Object} oc `Object` by default,
 
-    * 				allows to inject custom object constructor for tests
 
-    * @returns {Readonly<T>}
 
-    *
 
-    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
 
-    */
 
-   function freeze(object, oc) {
 
-     if (oc === undefined) {
 
-       oc = Object;
 
-     }
 
-     return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object;
 
-   }
 
-   /**
 
-    * Since we can not rely on `Object.assign` we provide a simplified version
 
-    * that is sufficient for our needs.
 
-    *
 
-    * @param {Object} target
 
-    * @param {Object | null | undefined} source
 
-    *
 
-    * @returns {Object} target
 
-    * @throws TypeError if target is not an object
 
-    *
 
-    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
 
-    * @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign
 
-    */
 
-   function assign(target, source) {
 
-     if (target === null || typeof target !== 'object') {
 
-       throw new TypeError('target is not an object');
 
-     }
 
-     for (var key in source) {
 
-       if (Object.prototype.hasOwnProperty.call(source, key)) {
 
-         target[key] = source[key];
 
-       }
 
-     }
 
-     return target;
 
-   }
 
-   /**
 
-    * All mime types that are allowed as input to `DOMParser.parseFromString`
 
-    *
 
-    * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN
 
-    * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec
 
-    * @see DOMParser.prototype.parseFromString
 
-    */
 
-   var MIME_TYPE = freeze({
 
-     /**
 
-      * `text/html`, the only mime type that triggers treating an XML document as HTML.
 
-      *
 
-      * @see DOMParser.SupportedType.isHTML
 
-      * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
 
-      * @see https://en.wikipedia.org/wiki/HTML Wikipedia
 
-      * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
 
-      * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec
 
-      */
 
-     HTML: 'text/html',
 
-     /**
 
-      * Helper method to check a mime type if it indicates an HTML document
 
-      *
 
-      * @param {string} [value]
 
-      * @returns {boolean}
 
-      *
 
-      * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
 
-      * @see https://en.wikipedia.org/wiki/HTML Wikipedia
 
-      * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
 
-      * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring 	 */
 
-     isHTML: function (value) {
 
-       return value === MIME_TYPE.HTML;
 
-     },
 
-     /**
 
-      * `application/xml`, the standard mime type for XML documents.
 
-      *
 
-      * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration
 
-      * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303
 
-      * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
 
-      */
 
-     XML_APPLICATION: 'application/xml',
 
-     /**
 
-      * `text/html`, an alias for `application/xml`.
 
-      *
 
-      * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303
 
-      * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration
 
-      * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
 
-      */
 
-     XML_TEXT: 'text/xml',
 
-     /**
 
-      * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace,
 
-      * but is parsed as an XML document.
 
-      *
 
-      * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration
 
-      * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec
 
-      * @see https://en.wikipedia.org/wiki/XHTML Wikipedia
 
-      */
 
-     XML_XHTML_APPLICATION: 'application/xhtml+xml',
 
-     /**
 
-      * `image/svg+xml`,
 
-      *
 
-      * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration
 
-      * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1
 
-      * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia
 
-      */
 
-     XML_SVG_IMAGE: 'image/svg+xml'
 
-   });
 
-   /**
 
-    * Namespaces that are used in this code base.
 
-    *
 
-    * @see http://www.w3.org/TR/REC-xml-names
 
-    */
 
-   var NAMESPACE$3 = freeze({
 
-     /**
 
-      * The XHTML namespace.
 
-      *
 
-      * @see http://www.w3.org/1999/xhtml
 
-      */
 
-     HTML: 'http://www.w3.org/1999/xhtml',
 
-     /**
 
-      * Checks if `uri` equals `NAMESPACE.HTML`.
 
-      *
 
-      * @param {string} [uri]
 
-      *
 
-      * @see NAMESPACE.HTML
 
-      */
 
-     isHTML: function (uri) {
 
-       return uri === NAMESPACE$3.HTML;
 
-     },
 
-     /**
 
-      * The SVG namespace.
 
-      *
 
-      * @see http://www.w3.org/2000/svg
 
-      */
 
-     SVG: 'http://www.w3.org/2000/svg',
 
-     /**
 
-      * The `xml:` namespace.
 
-      *
 
-      * @see http://www.w3.org/XML/1998/namespace
 
-      */
 
-     XML: 'http://www.w3.org/XML/1998/namespace',
 
-     /**
 
-      * The `xmlns:` namespace
 
-      *
 
-      * @see https://www.w3.org/2000/xmlns/
 
-      */
 
-     XMLNS: 'http://www.w3.org/2000/xmlns/'
 
-   });
 
-   var assign_1 = assign;
 
-   var find_1 = find$1;
 
-   var freeze_1 = freeze;
 
-   var MIME_TYPE_1 = MIME_TYPE;
 
-   var NAMESPACE_1 = NAMESPACE$3;
 
-   var conventions = {
 
-     assign: assign_1,
 
-     find: find_1,
 
-     freeze: freeze_1,
 
-     MIME_TYPE: MIME_TYPE_1,
 
-     NAMESPACE: NAMESPACE_1
 
-   };
 
-   var find = conventions.find;
 
-   var NAMESPACE$2 = conventions.NAMESPACE;
 
-   /**
 
-    * A prerequisite for `[].filter`, to drop elements that are empty
 
-    * @param {string} input
 
-    * @returns {boolean}
 
-    */
 
-   function notEmptyString(input) {
 
-     return input !== '';
 
-   }
 
-   /**
 
-    * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace
 
-    * @see https://infra.spec.whatwg.org/#ascii-whitespace
 
-    *
 
-    * @param {string} input
 
-    * @returns {string[]} (can be empty)
 
-    */
 
-   function splitOnASCIIWhitespace(input) {
 
-     // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE
 
-     return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : [];
 
-   }
 
-   /**
 
-    * Adds element as a key to current if it is not already present.
 
-    *
 
-    * @param {Record<string, boolean | undefined>} current
 
-    * @param {string} element
 
-    * @returns {Record<string, boolean | undefined>}
 
-    */
 
-   function orderedSetReducer(current, element) {
 
-     if (!current.hasOwnProperty(element)) {
 
-       current[element] = true;
 
-     }
 
-     return current;
 
-   }
 
-   /**
 
-    * @see https://infra.spec.whatwg.org/#ordered-set
 
-    * @param {string} input
 
-    * @returns {string[]}
 
-    */
 
-   function toOrderedSet(input) {
 
-     if (!input) return [];
 
-     var list = splitOnASCIIWhitespace(input);
 
-     return Object.keys(list.reduce(orderedSetReducer, {}));
 
-   }
 
-   /**
 
-    * Uses `list.indexOf` to implement something like `Array.prototype.includes`,
 
-    * which we can not rely on being available.
 
-    *
 
-    * @param {any[]} list
 
-    * @returns {function(any): boolean}
 
-    */
 
-   function arrayIncludes(list) {
 
-     return function (element) {
 
-       return list && list.indexOf(element) !== -1;
 
-     };
 
-   }
 
-   function copy(src, dest) {
 
-     for (var p in src) {
 
-       if (Object.prototype.hasOwnProperty.call(src, p)) {
 
-         dest[p] = src[p];
 
-       }
 
-     }
 
-   }
 
-   /**
 
-   ^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));?
 
-   ^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));?
 
-    */
 
-   function _extends(Class, Super) {
 
-     var pt = Class.prototype;
 
-     if (!(pt instanceof Super)) {
 
-       function t() {}
 
-       t.prototype = Super.prototype;
 
-       t = new t();
 
-       copy(pt, t);
 
-       Class.prototype = pt = t;
 
-     }
 
-     if (pt.constructor != Class) {
 
-       if (typeof Class != 'function') {
 
-         console.error("unknown Class:" + Class);
 
-       }
 
-       pt.constructor = Class;
 
-     }
 
-   }
 
-   // Node Types
 
-   var NodeType = {};
 
-   var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1;
 
-   var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2;
 
-   var TEXT_NODE = NodeType.TEXT_NODE = 3;
 
-   var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4;
 
-   var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5;
 
-   var ENTITY_NODE = NodeType.ENTITY_NODE = 6;
 
-   var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7;
 
-   var COMMENT_NODE = NodeType.COMMENT_NODE = 8;
 
-   var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9;
 
-   var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10;
 
-   var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11;
 
-   var NOTATION_NODE = NodeType.NOTATION_NODE = 12;
 
-   // ExceptionCode
 
-   var ExceptionCode = {};
 
-   var ExceptionMessage = {};
 
-   ExceptionCode.INDEX_SIZE_ERR = (ExceptionMessage[1] = "Index size error", 1);
 
-   ExceptionCode.DOMSTRING_SIZE_ERR = (ExceptionMessage[2] = "DOMString size error", 2);
 
-   var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = (ExceptionMessage[3] = "Hierarchy request error", 3);
 
-   ExceptionCode.WRONG_DOCUMENT_ERR = (ExceptionMessage[4] = "Wrong document", 4);
 
-   ExceptionCode.INVALID_CHARACTER_ERR = (ExceptionMessage[5] = "Invalid character", 5);
 
-   ExceptionCode.NO_DATA_ALLOWED_ERR = (ExceptionMessage[6] = "No data allowed", 6);
 
-   ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = (ExceptionMessage[7] = "No modification allowed", 7);
 
-   var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = (ExceptionMessage[8] = "Not found", 8);
 
-   ExceptionCode.NOT_SUPPORTED_ERR = (ExceptionMessage[9] = "Not supported", 9);
 
-   var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = (ExceptionMessage[10] = "Attribute in use", 10);
 
-   //level2
 
-   ExceptionCode.INVALID_STATE_ERR = (ExceptionMessage[11] = "Invalid state", 11);
 
-   ExceptionCode.SYNTAX_ERR = (ExceptionMessage[12] = "Syntax error", 12);
 
-   ExceptionCode.INVALID_MODIFICATION_ERR = (ExceptionMessage[13] = "Invalid modification", 13);
 
-   ExceptionCode.NAMESPACE_ERR = (ExceptionMessage[14] = "Invalid namespace", 14);
 
-   ExceptionCode.INVALID_ACCESS_ERR = (ExceptionMessage[15] = "Invalid access", 15);
 
-   /**
 
-    * DOM Level 2
 
-    * Object DOMException
 
-    * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html
 
-    * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html
 
-    */
 
-   function DOMException(code, message) {
 
-     if (message instanceof Error) {
 
-       var error = message;
 
-     } else {
 
-       error = this;
 
-       Error.call(this, ExceptionMessage[code]);
 
-       this.message = ExceptionMessage[code];
 
-       if (Error.captureStackTrace) Error.captureStackTrace(this, DOMException);
 
-     }
 
-     error.code = code;
 
-     if (message) this.message = this.message + ": " + message;
 
-     return error;
 
-   }
 
-   DOMException.prototype = Error.prototype;
 
-   copy(ExceptionCode, DOMException);
 
-   /**
 
-    * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177
 
-    * The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live.
 
-    * The items in the NodeList are accessible via an integral index, starting from 0.
 
-    */
 
-   function NodeList() {}
 
-   NodeList.prototype = {
 
-     /**
 
-      * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive.
 
-      * @standard level1
 
-      */
 
-     length: 0,
 
-     /**
 
-      * Returns the indexth item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null.
 
-      * @standard level1
 
-      * @param index  unsigned long
 
-      *   Index into the collection.
 
-      * @return Node
 
-      * 	The node at the indexth position in the NodeList, or null if that is not a valid index.
 
-      */
 
-     item: function (index) {
 
-       return this[index] || null;
 
-     },
 
-     toString: function (isHTML, nodeFilter) {
 
-       for (var buf = [], i = 0; i < this.length; i++) {
 
-         serializeToString(this[i], buf, isHTML, nodeFilter);
 
-       }
 
-       return buf.join('');
 
-     },
 
-     /**
 
-      * @private
 
-      * @param {function (Node):boolean} predicate
 
-      * @returns {Node[]}
 
-      */
 
-     filter: function (predicate) {
 
-       return Array.prototype.filter.call(this, predicate);
 
-     },
 
-     /**
 
-      * @private
 
-      * @param {Node} item
 
-      * @returns {number}
 
-      */
 
-     indexOf: function (item) {
 
-       return Array.prototype.indexOf.call(this, item);
 
-     }
 
-   };
 
-   function LiveNodeList(node, refresh) {
 
-     this._node = node;
 
-     this._refresh = refresh;
 
-     _updateLiveList(this);
 
-   }
 
-   function _updateLiveList(list) {
 
-     var inc = list._node._inc || list._node.ownerDocument._inc;
 
-     if (list._inc != inc) {
 
-       var ls = list._refresh(list._node);
 
-       //console.log(ls.length)
 
-       __set__(list, 'length', ls.length);
 
-       copy(ls, list);
 
-       list._inc = inc;
 
-     }
 
-   }
 
-   LiveNodeList.prototype.item = function (i) {
 
-     _updateLiveList(this);
 
-     return this[i];
 
-   };
 
-   _extends(LiveNodeList, NodeList);
 
-   /**
 
-    * Objects implementing the NamedNodeMap interface are used
 
-    * to represent collections of nodes that can be accessed by name.
 
-    * Note that NamedNodeMap does not inherit from NodeList;
 
-    * NamedNodeMaps are not maintained in any particular order.
 
-    * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index,
 
-    * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap,
 
-    * and does not imply that the DOM specifies an order to these Nodes.
 
-    * NamedNodeMap objects in the DOM are live.
 
-    * used for attributes or DocumentType entities
 
-    */
 
-   function NamedNodeMap() {}
 
-   function _findNodeIndex(list, node) {
 
-     var i = list.length;
 
-     while (i--) {
 
-       if (list[i] === node) {
 
-         return i;
 
-       }
 
-     }
 
-   }
 
-   function _addNamedNode(el, list, newAttr, oldAttr) {
 
-     if (oldAttr) {
 
-       list[_findNodeIndex(list, oldAttr)] = newAttr;
 
-     } else {
 
-       list[list.length++] = newAttr;
 
-     }
 
-     if (el) {
 
-       newAttr.ownerElement = el;
 
-       var doc = el.ownerDocument;
 
-       if (doc) {
 
-         oldAttr && _onRemoveAttribute(doc, el, oldAttr);
 
-         _onAddAttribute(doc, el, newAttr);
 
-       }
 
-     }
 
-   }
 
-   function _removeNamedNode(el, list, attr) {
 
-     //console.log('remove attr:'+attr)
 
-     var i = _findNodeIndex(list, attr);
 
-     if (i >= 0) {
 
-       var lastIndex = list.length - 1;
 
-       while (i < lastIndex) {
 
-         list[i] = list[++i];
 
-       }
 
-       list.length = lastIndex;
 
-       if (el) {
 
-         var doc = el.ownerDocument;
 
-         if (doc) {
 
-           _onRemoveAttribute(doc, el, attr);
 
-           attr.ownerElement = null;
 
-         }
 
-       }
 
-     } else {
 
-       throw new DOMException(NOT_FOUND_ERR, new Error(el.tagName + '@' + attr));
 
-     }
 
-   }
 
-   NamedNodeMap.prototype = {
 
-     length: 0,
 
-     item: NodeList.prototype.item,
 
-     getNamedItem: function (key) {
 
-       //		if(key.indexOf(':')>0 || key == 'xmlns'){
 
-       //			return null;
 
-       //		}
 
-       //console.log()
 
-       var i = this.length;
 
-       while (i--) {
 
-         var attr = this[i];
 
-         //console.log(attr.nodeName,key)
 
-         if (attr.nodeName == key) {
 
-           return attr;
 
-         }
 
-       }
 
-     },
 
-     setNamedItem: function (attr) {
 
-       var el = attr.ownerElement;
 
-       if (el && el != this._ownerElement) {
 
-         throw new DOMException(INUSE_ATTRIBUTE_ERR);
 
-       }
 
-       var oldAttr = this.getNamedItem(attr.nodeName);
 
-       _addNamedNode(this._ownerElement, this, attr, oldAttr);
 
-       return oldAttr;
 
-     },
 
-     /* returns Node */
 
-     setNamedItemNS: function (attr) {
 
-       // raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR
 
-       var el = attr.ownerElement,
 
-         oldAttr;
 
-       if (el && el != this._ownerElement) {
 
-         throw new DOMException(INUSE_ATTRIBUTE_ERR);
 
-       }
 
-       oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);
 
-       _addNamedNode(this._ownerElement, this, attr, oldAttr);
 
-       return oldAttr;
 
-     },
 
-     /* returns Node */
 
-     removeNamedItem: function (key) {
 
-       var attr = this.getNamedItem(key);
 
-       _removeNamedNode(this._ownerElement, this, attr);
 
-       return attr;
 
-     },
 
-     // raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR
 
-     //for level2
 
-     removeNamedItemNS: function (namespaceURI, localName) {
 
-       var attr = this.getNamedItemNS(namespaceURI, localName);
 
-       _removeNamedNode(this._ownerElement, this, attr);
 
-       return attr;
 
-     },
 
-     getNamedItemNS: function (namespaceURI, localName) {
 
-       var i = this.length;
 
-       while (i--) {
 
-         var node = this[i];
 
-         if (node.localName == localName && node.namespaceURI == namespaceURI) {
 
-           return node;
 
-         }
 
-       }
 
-       return null;
 
-     }
 
-   };
 
-   /**
 
-    * The DOMImplementation interface represents an object providing methods
 
-    * which are not dependent on any particular document.
 
-    * Such an object is returned by the `Document.implementation` property.
 
-    *
 
-    * __The individual methods describe the differences compared to the specs.__
 
-    *
 
-    * @constructor
 
-    *
 
-    * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN
 
-    * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial)
 
-    * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core
 
-    * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core
 
-    * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard
 
-    */
 
-   function DOMImplementation$1() {}
 
-   DOMImplementation$1.prototype = {
 
-     /**
 
-      * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported.
 
-      * The different implementations fairly diverged in what kind of features were reported.
 
-      * The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use.
 
-      *
 
-      * @deprecated It is deprecated and modern browsers return true in all cases.
 
-      *
 
-      * @param {string} feature
 
-      * @param {string} [version]
 
-      * @returns {boolean} always true
 
-      *
 
-      * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN
 
-      * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core
 
-      * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard
 
-      */
 
-     hasFeature: function (feature, version) {
 
-       return true;
 
-     },
 
-     /**
 
-      * Creates an XML Document object of the specified type with its document element.
 
-      *
 
-      * __It behaves slightly different from the description in the living standard__:
 
-      * - There is no interface/class `XMLDocument`, it returns a `Document` instance.
 
-      * - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared.
 
-      * - this implementation is not validating names or qualified names
 
-      *   (when parsing XML strings, the SAX parser takes care of that)
 
-      *
 
-      * @param {string|null} namespaceURI
 
-      * @param {string} qualifiedName
 
-      * @param {DocumentType=null} doctype
 
-      * @returns {Document}
 
-      *
 
-      * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN
 
-      * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial)
 
-      * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument  DOM Level 2 Core
 
-      *
 
-      * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
 
-      * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
 
-      * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
 
-      */
 
-     createDocument: function (namespaceURI, qualifiedName, doctype) {
 
-       var doc = new Document();
 
-       doc.implementation = this;
 
-       doc.childNodes = new NodeList();
 
-       doc.doctype = doctype || null;
 
-       if (doctype) {
 
-         doc.appendChild(doctype);
 
-       }
 
-       if (qualifiedName) {
 
-         var root = doc.createElementNS(namespaceURI, qualifiedName);
 
-         doc.appendChild(root);
 
-       }
 
-       return doc;
 
-     },
 
-     /**
 
-      * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`.
 
-      *
 
-      * __This behavior is slightly different from the in the specs__:
 
-      * - this implementation is not validating names or qualified names
 
-      *   (when parsing XML strings, the SAX parser takes care of that)
 
-      *
 
-      * @param {string} qualifiedName
 
-      * @param {string} [publicId]
 
-      * @param {string} [systemId]
 
-      * @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation
 
-      * 				  or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()`
 
-      *
 
-      * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN
 
-      * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core
 
-      * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard
 
-      *
 
-      * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
 
-      * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
 
-      * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
 
-      */
 
-     createDocumentType: function (qualifiedName, publicId, systemId) {
 
-       var node = new DocumentType();
 
-       node.name = qualifiedName;
 
-       node.nodeName = qualifiedName;
 
-       node.publicId = publicId || '';
 
-       node.systemId = systemId || '';
 
-       return node;
 
-     }
 
-   };
 
-   /**
 
-    * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247
 
-    */
 
-   function Node() {}
 
-   Node.prototype = {
 
-     firstChild: null,
 
-     lastChild: null,
 
-     previousSibling: null,
 
-     nextSibling: null,
 
-     attributes: null,
 
-     parentNode: null,
 
-     childNodes: null,
 
-     ownerDocument: null,
 
-     nodeValue: null,
 
-     namespaceURI: null,
 
-     prefix: null,
 
-     localName: null,
 
-     // Modified in DOM Level 2:
 
-     insertBefore: function (newChild, refChild) {
 
-       //raises
 
-       return _insertBefore(this, newChild, refChild);
 
-     },
 
-     replaceChild: function (newChild, oldChild) {
 
-       //raises
 
-       _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
 
-       if (oldChild) {
 
-         this.removeChild(oldChild);
 
-       }
 
-     },
 
-     removeChild: function (oldChild) {
 
-       return _removeChild(this, oldChild);
 
-     },
 
-     appendChild: function (newChild) {
 
-       return this.insertBefore(newChild, null);
 
-     },
 
-     hasChildNodes: function () {
 
-       return this.firstChild != null;
 
-     },
 
-     cloneNode: function (deep) {
 
-       return cloneNode(this.ownerDocument || this, this, deep);
 
-     },
 
-     // Modified in DOM Level 2:
 
-     normalize: function () {
 
-       var child = this.firstChild;
 
-       while (child) {
 
-         var next = child.nextSibling;
 
-         if (next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE) {
 
-           this.removeChild(next);
 
-           child.appendData(next.data);
 
-         } else {
 
-           child.normalize();
 
-           child = next;
 
-         }
 
-       }
 
-     },
 
-     // Introduced in DOM Level 2:
 
-     isSupported: function (feature, version) {
 
-       return this.ownerDocument.implementation.hasFeature(feature, version);
 
-     },
 
-     // Introduced in DOM Level 2:
 
-     hasAttributes: function () {
 
-       return this.attributes.length > 0;
 
-     },
 
-     /**
 
-      * Look up the prefix associated to the given namespace URI, starting from this node.
 
-      * **The default namespace declarations are ignored by this method.**
 
-      * See Namespace Prefix Lookup for details on the algorithm used by this method.
 
-      *
 
-      * _Note: The implementation seems to be incomplete when compared to the algorithm described in the specs._
 
-      *
 
-      * @param {string | null} namespaceURI
 
-      * @returns {string | null}
 
-      * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespacePrefix
 
-      * @see https://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo
 
-      * @see https://dom.spec.whatwg.org/#dom-node-lookupprefix
 
-      * @see https://github.com/xmldom/xmldom/issues/322
 
-      */
 
-     lookupPrefix: function (namespaceURI) {
 
-       var el = this;
 
-       while (el) {
 
-         var map = el._nsMap;
 
-         //console.dir(map)
 
-         if (map) {
 
-           for (var n in map) {
 
-             if (Object.prototype.hasOwnProperty.call(map, n) && map[n] === namespaceURI) {
 
-               return n;
 
-             }
 
-           }
 
-         }
 
-         el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
 
-       }
 
-       return null;
 
-     },
 
-     // Introduced in DOM Level 3:
 
-     lookupNamespaceURI: function (prefix) {
 
-       var el = this;
 
-       while (el) {
 
-         var map = el._nsMap;
 
-         //console.dir(map)
 
-         if (map) {
 
-           if (Object.prototype.hasOwnProperty.call(map, prefix)) {
 
-             return map[prefix];
 
-           }
 
-         }
 
-         el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
 
-       }
 
-       return null;
 
-     },
 
-     // Introduced in DOM Level 3:
 
-     isDefaultNamespace: function (namespaceURI) {
 
-       var prefix = this.lookupPrefix(namespaceURI);
 
-       return prefix == null;
 
-     }
 
-   };
 
-   function _xmlEncoder(c) {
 
-     return c == '<' && '<' || c == '>' && '>' || c == '&' && '&' || c == '"' && '"' || '&#' + c.charCodeAt() + ';';
 
-   }
 
-   copy(NodeType, Node);
 
-   copy(NodeType, Node.prototype);
 
-   /**
 
-    * @param callback return true for continue,false for break
 
-    * @return boolean true: break visit;
 
-    */
 
-   function _visitNode(node, callback) {
 
-     if (callback(node)) {
 
-       return true;
 
-     }
 
-     if (node = node.firstChild) {
 
-       do {
 
-         if (_visitNode(node, callback)) {
 
-           return true;
 
-         }
 
-       } while (node = node.nextSibling);
 
-     }
 
-   }
 
-   function Document() {
 
-     this.ownerDocument = this;
 
-   }
 
-   function _onAddAttribute(doc, el, newAttr) {
 
-     doc && doc._inc++;
 
-     var ns = newAttr.namespaceURI;
 
-     if (ns === NAMESPACE$2.XMLNS) {
 
-       //update namespace
 
-       el._nsMap[newAttr.prefix ? newAttr.localName : ''] = newAttr.value;
 
-     }
 
-   }
 
-   function _onRemoveAttribute(doc, el, newAttr, remove) {
 
-     doc && doc._inc++;
 
-     var ns = newAttr.namespaceURI;
 
-     if (ns === NAMESPACE$2.XMLNS) {
 
-       //update namespace
 
-       delete el._nsMap[newAttr.prefix ? newAttr.localName : ''];
 
-     }
 
-   }
 
-   /**
 
-    * Updates `el.childNodes`, updating the indexed items and it's `length`.
 
-    * Passing `newChild` means it will be appended.
 
-    * Otherwise it's assumed that an item has been removed,
 
-    * and `el.firstNode` and it's `.nextSibling` are used
 
-    * to walk the current list of child nodes.
 
-    *
 
-    * @param {Document} doc
 
-    * @param {Node} el
 
-    * @param {Node} [newChild]
 
-    * @private
 
-    */
 
-   function _onUpdateChild(doc, el, newChild) {
 
-     if (doc && doc._inc) {
 
-       doc._inc++;
 
-       //update childNodes
 
-       var cs = el.childNodes;
 
-       if (newChild) {
 
-         cs[cs.length++] = newChild;
 
-       } else {
 
-         var child = el.firstChild;
 
-         var i = 0;
 
-         while (child) {
 
-           cs[i++] = child;
 
-           child = child.nextSibling;
 
-         }
 
-         cs.length = i;
 
-         delete cs[cs.length];
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * Removes the connections between `parentNode` and `child`
 
-    * and any existing `child.previousSibling` or `child.nextSibling`.
 
-    *
 
-    * @see https://github.com/xmldom/xmldom/issues/135
 
-    * @see https://github.com/xmldom/xmldom/issues/145
 
-    *
 
-    * @param {Node} parentNode
 
-    * @param {Node} child
 
-    * @returns {Node} the child that was removed.
 
-    * @private
 
-    */
 
-   function _removeChild(parentNode, child) {
 
-     var previous = child.previousSibling;
 
-     var next = child.nextSibling;
 
-     if (previous) {
 
-       previous.nextSibling = next;
 
-     } else {
 
-       parentNode.firstChild = next;
 
-     }
 
-     if (next) {
 
-       next.previousSibling = previous;
 
-     } else {
 
-       parentNode.lastChild = previous;
 
-     }
 
-     child.parentNode = null;
 
-     child.previousSibling = null;
 
-     child.nextSibling = null;
 
-     _onUpdateChild(parentNode.ownerDocument, parentNode);
 
-     return child;
 
-   }
 
-   /**
 
-    * Returns `true` if `node` can be a parent for insertion.
 
-    * @param {Node} node
 
-    * @returns {boolean}
 
-    */
 
-   function hasValidParentNodeType(node) {
 
-     return node && (node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE);
 
-   }
 
-   /**
 
-    * Returns `true` if `node` can be inserted according to it's `nodeType`.
 
-    * @param {Node} node
 
-    * @returns {boolean}
 
-    */
 
-   function hasInsertableNodeType(node) {
 
-     return node && (isElementNode(node) || isTextNode(node) || isDocTypeNode(node) || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.COMMENT_NODE || node.nodeType === Node.PROCESSING_INSTRUCTION_NODE);
 
-   }
 
-   /**
 
-    * Returns true if `node` is a DOCTYPE node
 
-    * @param {Node} node
 
-    * @returns {boolean}
 
-    */
 
-   function isDocTypeNode(node) {
 
-     return node && node.nodeType === Node.DOCUMENT_TYPE_NODE;
 
-   }
 
-   /**
 
-    * Returns true if the node is an element
 
-    * @param {Node} node
 
-    * @returns {boolean}
 
-    */
 
-   function isElementNode(node) {
 
-     return node && node.nodeType === Node.ELEMENT_NODE;
 
-   }
 
-   /**
 
-    * Returns true if `node` is a text node
 
-    * @param {Node} node
 
-    * @returns {boolean}
 
-    */
 
-   function isTextNode(node) {
 
-     return node && node.nodeType === Node.TEXT_NODE;
 
-   }
 
-   /**
 
-    * Check if en element node can be inserted before `child`, or at the end if child is falsy,
 
-    * according to the presence and position of a doctype node on the same level.
 
-    *
 
-    * @param {Document} doc The document node
 
-    * @param {Node} child the node that would become the nextSibling if the element would be inserted
 
-    * @returns {boolean} `true` if an element can be inserted before child
 
-    * @private
 
-    * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
 
-    */
 
-   function isElementInsertionPossible(doc, child) {
 
-     var parentChildNodes = doc.childNodes || [];
 
-     if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) {
 
-       return false;
 
-     }
 
-     var docTypeNode = find(parentChildNodes, isDocTypeNode);
 
-     return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
 
-   }
 
-   /**
 
-    * Check if en element node can be inserted before `child`, or at the end if child is falsy,
 
-    * according to the presence and position of a doctype node on the same level.
 
-    *
 
-    * @param {Node} doc The document node
 
-    * @param {Node} child the node that would become the nextSibling if the element would be inserted
 
-    * @returns {boolean} `true` if an element can be inserted before child
 
-    * @private
 
-    * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
 
-    */
 
-   function isElementReplacementPossible(doc, child) {
 
-     var parentChildNodes = doc.childNodes || [];
 
-     function hasElementChildThatIsNotChild(node) {
 
-       return isElementNode(node) && node !== child;
 
-     }
 
-     if (find(parentChildNodes, hasElementChildThatIsNotChild)) {
 
-       return false;
 
-     }
 
-     var docTypeNode = find(parentChildNodes, isDocTypeNode);
 
-     return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
 
-   }
 
-   /**
 
-    * @private
 
-    * Steps 1-5 of the checks before inserting and before replacing a child are the same.
 
-    *
 
-    * @param {Node} parent the parent node to insert `node` into
 
-    * @param {Node} node the node to insert
 
-    * @param {Node=} child the node that should become the `nextSibling` of `node`
 
-    * @returns {Node}
 
-    * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
 
-    * @throws DOMException if `child` is provided but is not a child of `parent`.
 
-    * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
 
-    * @see https://dom.spec.whatwg.org/#concept-node-replace
 
-    */
 
-   function assertPreInsertionValidity1to5(parent, node, child) {
 
-     // 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
 
-     if (!hasValidParentNodeType(parent)) {
 
-       throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
 
-     }
 
-     // 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException.
 
-     // not implemented!
 
-     // 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException.
 
-     if (child && child.parentNode !== parent) {
 
-       throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
 
-     }
 
-     if (
 
-     // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
 
-     !hasInsertableNodeType(node) ||
 
-     // 5. If either `node` is a Text node and `parent` is a document,
 
-     // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
 
-     // || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE)
 
-     // or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException.
 
-     isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE) {
 
-       throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType);
 
-     }
 
-   }
 
-   /**
 
-    * @private
 
-    * Step 6 of the checks before inserting and before replacing a child are different.
 
-    *
 
-    * @param {Document} parent the parent node to insert `node` into
 
-    * @param {Node} node the node to insert
 
-    * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
 
-    * @returns {Node}
 
-    * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
 
-    * @throws DOMException if `child` is provided but is not a child of `parent`.
 
-    * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
 
-    * @see https://dom.spec.whatwg.org/#concept-node-replace
 
-    */
 
-   function assertPreInsertionValidityInDocument(parent, node, child) {
 
-     var parentChildNodes = parent.childNodes || [];
 
-     var nodeChildNodes = node.childNodes || [];
 
-     // DocumentFragment
 
-     if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
 
-       var nodeChildElements = nodeChildNodes.filter(isElementNode);
 
-       // If node has more than one element child or has a Text node child.
 
-       if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
 
-       }
 
-       // Otherwise, if `node` has one element child and either `parent` has an element child,
 
-       // `child` is a doctype, or `child` is non-null and a doctype is following `child`.
 
-       if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
 
-       }
 
-     }
 
-     // Element
 
-     if (isElementNode(node)) {
 
-       // `parent` has an element child, `child` is a doctype,
 
-       // or `child` is non-null and a doctype is following `child`.
 
-       if (!isElementInsertionPossible(parent, child)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
 
-       }
 
-     }
 
-     // DocumentType
 
-     if (isDocTypeNode(node)) {
 
-       // `parent` has a doctype child,
 
-       if (find(parentChildNodes, isDocTypeNode)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
 
-       }
 
-       var parentElementChild = find(parentChildNodes, isElementNode);
 
-       // `child` is non-null and an element is preceding `child`,
 
-       if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
 
-       }
 
-       // or `child` is null and `parent` has an element child.
 
-       if (!child && parentElementChild) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * @private
 
-    * Step 6 of the checks before inserting and before replacing a child are different.
 
-    *
 
-    * @param {Document} parent the parent node to insert `node` into
 
-    * @param {Node} node the node to insert
 
-    * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
 
-    * @returns {Node}
 
-    * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
 
-    * @throws DOMException if `child` is provided but is not a child of `parent`.
 
-    * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
 
-    * @see https://dom.spec.whatwg.org/#concept-node-replace
 
-    */
 
-   function assertPreReplacementValidityInDocument(parent, node, child) {
 
-     var parentChildNodes = parent.childNodes || [];
 
-     var nodeChildNodes = node.childNodes || [];
 
-     // DocumentFragment
 
-     if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
 
-       var nodeChildElements = nodeChildNodes.filter(isElementNode);
 
-       // If `node` has more than one element child or has a Text node child.
 
-       if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
 
-       }
 
-       // Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`.
 
-       if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
 
-       }
 
-     }
 
-     // Element
 
-     if (isElementNode(node)) {
 
-       // `parent` has an element child that is not `child` or a doctype is following `child`.
 
-       if (!isElementReplacementPossible(parent, child)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
 
-       }
 
-     }
 
-     // DocumentType
 
-     if (isDocTypeNode(node)) {
 
-       function hasDoctypeChildThatIsNotChild(node) {
 
-         return isDocTypeNode(node) && node !== child;
 
-       }
 
-       // `parent` has a doctype child that is not `child`,
 
-       if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
 
-       }
 
-       var parentElementChild = find(parentChildNodes, isElementNode);
 
-       // or an element is preceding `child`.
 
-       if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
 
-         throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * @private
 
-    * @param {Node} parent the parent node to insert `node` into
 
-    * @param {Node} node the node to insert
 
-    * @param {Node=} child the node that should become the `nextSibling` of `node`
 
-    * @returns {Node}
 
-    * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
 
-    * @throws DOMException if `child` is provided but is not a child of `parent`.
 
-    * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
 
-    */
 
-   function _insertBefore(parent, node, child, _inDocumentAssertion) {
 
-     // To ensure pre-insertion validity of a node into a parent before a child, run these steps:
 
-     assertPreInsertionValidity1to5(parent, node, child);
 
-     // If parent is a document, and any of the statements below, switched on the interface node implements,
 
-     // are true, then throw a "HierarchyRequestError" DOMException.
 
-     if (parent.nodeType === Node.DOCUMENT_NODE) {
 
-       (_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child);
 
-     }
 
-     var cp = node.parentNode;
 
-     if (cp) {
 
-       cp.removeChild(node); //remove and update
 
-     }
 
-     if (node.nodeType === DOCUMENT_FRAGMENT_NODE) {
 
-       var newFirst = node.firstChild;
 
-       if (newFirst == null) {
 
-         return node;
 
-       }
 
-       var newLast = node.lastChild;
 
-     } else {
 
-       newFirst = newLast = node;
 
-     }
 
-     var pre = child ? child.previousSibling : parent.lastChild;
 
-     newFirst.previousSibling = pre;
 
-     newLast.nextSibling = child;
 
-     if (pre) {
 
-       pre.nextSibling = newFirst;
 
-     } else {
 
-       parent.firstChild = newFirst;
 
-     }
 
-     if (child == null) {
 
-       parent.lastChild = newLast;
 
-     } else {
 
-       child.previousSibling = newLast;
 
-     }
 
-     do {
 
-       newFirst.parentNode = parent;
 
-     } while (newFirst !== newLast && (newFirst = newFirst.nextSibling));
 
-     _onUpdateChild(parent.ownerDocument || parent, parent);
 
-     //console.log(parent.lastChild.nextSibling == null)
 
-     if (node.nodeType == DOCUMENT_FRAGMENT_NODE) {
 
-       node.firstChild = node.lastChild = null;
 
-     }
 
-     return node;
 
-   }
 
-   /**
 
-    * Appends `newChild` to `parentNode`.
 
-    * If `newChild` is already connected to a `parentNode` it is first removed from it.
 
-    *
 
-    * @see https://github.com/xmldom/xmldom/issues/135
 
-    * @see https://github.com/xmldom/xmldom/issues/145
 
-    * @param {Node} parentNode
 
-    * @param {Node} newChild
 
-    * @returns {Node}
 
-    * @private
 
-    */
 
-   function _appendSingleChild(parentNode, newChild) {
 
-     if (newChild.parentNode) {
 
-       newChild.parentNode.removeChild(newChild);
 
-     }
 
-     newChild.parentNode = parentNode;
 
-     newChild.previousSibling = parentNode.lastChild;
 
-     newChild.nextSibling = null;
 
-     if (newChild.previousSibling) {
 
-       newChild.previousSibling.nextSibling = newChild;
 
-     } else {
 
-       parentNode.firstChild = newChild;
 
-     }
 
-     parentNode.lastChild = newChild;
 
-     _onUpdateChild(parentNode.ownerDocument, parentNode, newChild);
 
-     return newChild;
 
-   }
 
-   Document.prototype = {
 
-     //implementation : null,
 
-     nodeName: '#document',
 
-     nodeType: DOCUMENT_NODE,
 
-     /**
 
-      * The DocumentType node of the document.
 
-      *
 
-      * @readonly
 
-      * @type DocumentType
 
-      */
 
-     doctype: null,
 
-     documentElement: null,
 
-     _inc: 1,
 
-     insertBefore: function (newChild, refChild) {
 
-       //raises
 
-       if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) {
 
-         var child = newChild.firstChild;
 
-         while (child) {
 
-           var next = child.nextSibling;
 
-           this.insertBefore(child, refChild);
 
-           child = next;
 
-         }
 
-         return newChild;
 
-       }
 
-       _insertBefore(this, newChild, refChild);
 
-       newChild.ownerDocument = this;
 
-       if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) {
 
-         this.documentElement = newChild;
 
-       }
 
-       return newChild;
 
-     },
 
-     removeChild: function (oldChild) {
 
-       if (this.documentElement == oldChild) {
 
-         this.documentElement = null;
 
-       }
 
-       return _removeChild(this, oldChild);
 
-     },
 
-     replaceChild: function (newChild, oldChild) {
 
-       //raises
 
-       _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
 
-       newChild.ownerDocument = this;
 
-       if (oldChild) {
 
-         this.removeChild(oldChild);
 
-       }
 
-       if (isElementNode(newChild)) {
 
-         this.documentElement = newChild;
 
-       }
 
-     },
 
-     // Introduced in DOM Level 2:
 
-     importNode: function (importedNode, deep) {
 
-       return importNode(this, importedNode, deep);
 
-     },
 
-     // Introduced in DOM Level 2:
 
-     getElementById: function (id) {
 
-       var rtv = null;
 
-       _visitNode(this.documentElement, function (node) {
 
-         if (node.nodeType == ELEMENT_NODE) {
 
-           if (node.getAttribute('id') == id) {
 
-             rtv = node;
 
-             return true;
 
-           }
 
-         }
 
-       });
 
-       return rtv;
 
-     },
 
-     /**
 
-      * The `getElementsByClassName` method of `Document` interface returns an array-like object
 
-      * of all child elements which have **all** of the given class name(s).
 
-      *
 
-      * Returns an empty list if `classeNames` is an empty string or only contains HTML white space characters.
 
-      *
 
-      *
 
-      * Warning: This is a live LiveNodeList.
 
-      * Changes in the DOM will reflect in the array as the changes occur.
 
-      * If an element selected by this array no longer qualifies for the selector,
 
-      * it will automatically be removed. Be aware of this for iteration purposes.
 
-      *
 
-      * @param {string} classNames is a string representing the class name(s) to match; multiple class names are separated by (ASCII-)whitespace
 
-      *
 
-      * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
 
-      * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname
 
-      */
 
-     getElementsByClassName: function (classNames) {
 
-       var classNamesSet = toOrderedSet(classNames);
 
-       return new LiveNodeList(this, function (base) {
 
-         var ls = [];
 
-         if (classNamesSet.length > 0) {
 
-           _visitNode(base.documentElement, function (node) {
 
-             if (node !== base && node.nodeType === ELEMENT_NODE) {
 
-               var nodeClassNames = node.getAttribute('class');
 
-               // can be null if the attribute does not exist
 
-               if (nodeClassNames) {
 
-                 // before splitting and iterating just compare them for the most common case
 
-                 var matches = classNames === nodeClassNames;
 
-                 if (!matches) {
 
-                   var nodeClassNamesSet = toOrderedSet(nodeClassNames);
 
-                   matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet));
 
-                 }
 
-                 if (matches) {
 
-                   ls.push(node);
 
-                 }
 
-               }
 
-             }
 
-           });
 
-         }
 
-         return ls;
 
-       });
 
-     },
 
-     //document factory method:
 
-     createElement: function (tagName) {
 
-       var node = new Element();
 
-       node.ownerDocument = this;
 
-       node.nodeName = tagName;
 
-       node.tagName = tagName;
 
-       node.localName = tagName;
 
-       node.childNodes = new NodeList();
 
-       var attrs = node.attributes = new NamedNodeMap();
 
-       attrs._ownerElement = node;
 
-       return node;
 
-     },
 
-     createDocumentFragment: function () {
 
-       var node = new DocumentFragment();
 
-       node.ownerDocument = this;
 
-       node.childNodes = new NodeList();
 
-       return node;
 
-     },
 
-     createTextNode: function (data) {
 
-       var node = new Text();
 
-       node.ownerDocument = this;
 
-       node.appendData(data);
 
-       return node;
 
-     },
 
-     createComment: function (data) {
 
-       var node = new Comment();
 
-       node.ownerDocument = this;
 
-       node.appendData(data);
 
-       return node;
 
-     },
 
-     createCDATASection: function (data) {
 
-       var node = new CDATASection();
 
-       node.ownerDocument = this;
 
-       node.appendData(data);
 
-       return node;
 
-     },
 
-     createProcessingInstruction: function (target, data) {
 
-       var node = new ProcessingInstruction();
 
-       node.ownerDocument = this;
 
-       node.tagName = node.target = target;
 
-       node.nodeValue = node.data = data;
 
-       return node;
 
-     },
 
-     createAttribute: function (name) {
 
-       var node = new Attr();
 
-       node.ownerDocument = this;
 
-       node.name = name;
 
-       node.nodeName = name;
 
-       node.localName = name;
 
-       node.specified = true;
 
-       return node;
 
-     },
 
-     createEntityReference: function (name) {
 
-       var node = new EntityReference();
 
-       node.ownerDocument = this;
 
-       node.nodeName = name;
 
-       return node;
 
-     },
 
-     // Introduced in DOM Level 2:
 
-     createElementNS: function (namespaceURI, qualifiedName) {
 
-       var node = new Element();
 
-       var pl = qualifiedName.split(':');
 
-       var attrs = node.attributes = new NamedNodeMap();
 
-       node.childNodes = new NodeList();
 
-       node.ownerDocument = this;
 
-       node.nodeName = qualifiedName;
 
-       node.tagName = qualifiedName;
 
-       node.namespaceURI = namespaceURI;
 
-       if (pl.length == 2) {
 
-         node.prefix = pl[0];
 
-         node.localName = pl[1];
 
-       } else {
 
-         //el.prefix = null;
 
-         node.localName = qualifiedName;
 
-       }
 
-       attrs._ownerElement = node;
 
-       return node;
 
-     },
 
-     // Introduced in DOM Level 2:
 
-     createAttributeNS: function (namespaceURI, qualifiedName) {
 
-       var node = new Attr();
 
-       var pl = qualifiedName.split(':');
 
-       node.ownerDocument = this;
 
-       node.nodeName = qualifiedName;
 
-       node.name = qualifiedName;
 
-       node.namespaceURI = namespaceURI;
 
-       node.specified = true;
 
-       if (pl.length == 2) {
 
-         node.prefix = pl[0];
 
-         node.localName = pl[1];
 
-       } else {
 
-         //el.prefix = null;
 
-         node.localName = qualifiedName;
 
-       }
 
-       return node;
 
-     }
 
-   };
 
-   _extends(Document, Node);
 
-   function Element() {
 
-     this._nsMap = {};
 
-   }
 
-   Element.prototype = {
 
-     nodeType: ELEMENT_NODE,
 
-     hasAttribute: function (name) {
 
-       return this.getAttributeNode(name) != null;
 
-     },
 
-     getAttribute: function (name) {
 
-       var attr = this.getAttributeNode(name);
 
-       return attr && attr.value || '';
 
-     },
 
-     getAttributeNode: function (name) {
 
-       return this.attributes.getNamedItem(name);
 
-     },
 
-     setAttribute: function (name, value) {
 
-       var attr = this.ownerDocument.createAttribute(name);
 
-       attr.value = attr.nodeValue = "" + value;
 
-       this.setAttributeNode(attr);
 
-     },
 
-     removeAttribute: function (name) {
 
-       var attr = this.getAttributeNode(name);
 
-       attr && this.removeAttributeNode(attr);
 
-     },
 
-     //four real opeartion method
 
-     appendChild: function (newChild) {
 
-       if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) {
 
-         return this.insertBefore(newChild, null);
 
-       } else {
 
-         return _appendSingleChild(this, newChild);
 
-       }
 
-     },
 
-     setAttributeNode: function (newAttr) {
 
-       return this.attributes.setNamedItem(newAttr);
 
-     },
 
-     setAttributeNodeNS: function (newAttr) {
 
-       return this.attributes.setNamedItemNS(newAttr);
 
-     },
 
-     removeAttributeNode: function (oldAttr) {
 
-       //console.log(this == oldAttr.ownerElement)
 
-       return this.attributes.removeNamedItem(oldAttr.nodeName);
 
-     },
 
-     //get real attribute name,and remove it by removeAttributeNode
 
-     removeAttributeNS: function (namespaceURI, localName) {
 
-       var old = this.getAttributeNodeNS(namespaceURI, localName);
 
-       old && this.removeAttributeNode(old);
 
-     },
 
-     hasAttributeNS: function (namespaceURI, localName) {
 
-       return this.getAttributeNodeNS(namespaceURI, localName) != null;
 
-     },
 
-     getAttributeNS: function (namespaceURI, localName) {
 
-       var attr = this.getAttributeNodeNS(namespaceURI, localName);
 
-       return attr && attr.value || '';
 
-     },
 
-     setAttributeNS: function (namespaceURI, qualifiedName, value) {
 
-       var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
 
-       attr.value = attr.nodeValue = "" + value;
 
-       this.setAttributeNode(attr);
 
-     },
 
-     getAttributeNodeNS: function (namespaceURI, localName) {
 
-       return this.attributes.getNamedItemNS(namespaceURI, localName);
 
-     },
 
-     getElementsByTagName: function (tagName) {
 
-       return new LiveNodeList(this, function (base) {
 
-         var ls = [];
 
-         _visitNode(base, function (node) {
 
-           if (node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)) {
 
-             ls.push(node);
 
-           }
 
-         });
 
-         return ls;
 
-       });
 
-     },
 
-     getElementsByTagNameNS: function (namespaceURI, localName) {
 
-       return new LiveNodeList(this, function (base) {
 
-         var ls = [];
 
-         _visitNode(base, function (node) {
 
-           if (node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)) {
 
-             ls.push(node);
 
-           }
 
-         });
 
-         return ls;
 
-       });
 
-     }
 
-   };
 
-   Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName;
 
-   Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS;
 
-   _extends(Element, Node);
 
-   function Attr() {}
 
-   Attr.prototype.nodeType = ATTRIBUTE_NODE;
 
-   _extends(Attr, Node);
 
-   function CharacterData() {}
 
-   CharacterData.prototype = {
 
-     data: '',
 
-     substringData: function (offset, count) {
 
-       return this.data.substring(offset, offset + count);
 
-     },
 
-     appendData: function (text) {
 
-       text = this.data + text;
 
-       this.nodeValue = this.data = text;
 
-       this.length = text.length;
 
-     },
 
-     insertData: function (offset, text) {
 
-       this.replaceData(offset, 0, text);
 
-     },
 
-     appendChild: function (newChild) {
 
-       throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]);
 
-     },
 
-     deleteData: function (offset, count) {
 
-       this.replaceData(offset, count, "");
 
-     },
 
-     replaceData: function (offset, count, text) {
 
-       var start = this.data.substring(0, offset);
 
-       var end = this.data.substring(offset + count);
 
-       text = start + text + end;
 
-       this.nodeValue = this.data = text;
 
-       this.length = text.length;
 
-     }
 
-   };
 
-   _extends(CharacterData, Node);
 
-   function Text() {}
 
-   Text.prototype = {
 
-     nodeName: "#text",
 
-     nodeType: TEXT_NODE,
 
-     splitText: function (offset) {
 
-       var text = this.data;
 
-       var newText = text.substring(offset);
 
-       text = text.substring(0, offset);
 
-       this.data = this.nodeValue = text;
 
-       this.length = text.length;
 
-       var newNode = this.ownerDocument.createTextNode(newText);
 
-       if (this.parentNode) {
 
-         this.parentNode.insertBefore(newNode, this.nextSibling);
 
-       }
 
-       return newNode;
 
-     }
 
-   };
 
-   _extends(Text, CharacterData);
 
-   function Comment() {}
 
-   Comment.prototype = {
 
-     nodeName: "#comment",
 
-     nodeType: COMMENT_NODE
 
-   };
 
-   _extends(Comment, CharacterData);
 
-   function CDATASection() {}
 
-   CDATASection.prototype = {
 
-     nodeName: "#cdata-section",
 
-     nodeType: CDATA_SECTION_NODE
 
-   };
 
-   _extends(CDATASection, CharacterData);
 
-   function DocumentType() {}
 
-   DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
 
-   _extends(DocumentType, Node);
 
-   function Notation() {}
 
-   Notation.prototype.nodeType = NOTATION_NODE;
 
-   _extends(Notation, Node);
 
-   function Entity() {}
 
-   Entity.prototype.nodeType = ENTITY_NODE;
 
-   _extends(Entity, Node);
 
-   function EntityReference() {}
 
-   EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE;
 
-   _extends(EntityReference, Node);
 
-   function DocumentFragment() {}
 
-   DocumentFragment.prototype.nodeName = "#document-fragment";
 
-   DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE;
 
-   _extends(DocumentFragment, Node);
 
-   function ProcessingInstruction() {}
 
-   ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE;
 
-   _extends(ProcessingInstruction, Node);
 
-   function XMLSerializer() {}
 
-   XMLSerializer.prototype.serializeToString = function (node, isHtml, nodeFilter) {
 
-     return nodeSerializeToString.call(node, isHtml, nodeFilter);
 
-   };
 
-   Node.prototype.toString = nodeSerializeToString;
 
-   function nodeSerializeToString(isHtml, nodeFilter) {
 
-     var buf = [];
 
-     var refNode = this.nodeType == 9 && this.documentElement || this;
 
-     var prefix = refNode.prefix;
 
-     var uri = refNode.namespaceURI;
 
-     if (uri && prefix == null) {
 
-       //console.log(prefix)
 
-       var prefix = refNode.lookupPrefix(uri);
 
-       if (prefix == null) {
 
-         //isHTML = true;
 
-         var visibleNamespaces = [{
 
-           namespace: uri,
 
-           prefix: null
 
-         }
 
-         //{namespace:uri,prefix:''}
 
-         ];
 
-       }
 
-     }
 
-     serializeToString(this, buf, isHtml, nodeFilter, visibleNamespaces);
 
-     //console.log('###',this.nodeType,uri,prefix,buf.join(''))
 
-     return buf.join('');
 
-   }
 
-   function needNamespaceDefine(node, isHTML, visibleNamespaces) {
 
-     var prefix = node.prefix || '';
 
-     var uri = node.namespaceURI;
 
-     // According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) ,
 
-     // and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl :
 
-     // > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty.
 
-     // in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using)
 
-     // and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared :
 
-     // > [...] Furthermore, the attribute value [...] must not be an empty string.
 
-     // so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document.
 
-     if (!uri) {
 
-       return false;
 
-     }
 
-     if (prefix === "xml" && uri === NAMESPACE$2.XML || uri === NAMESPACE$2.XMLNS) {
 
-       return false;
 
-     }
 
-     var i = visibleNamespaces.length;
 
-     while (i--) {
 
-       var ns = visibleNamespaces[i];
 
-       // get namespace prefix
 
-       if (ns.prefix === prefix) {
 
-         return ns.namespace !== uri;
 
-       }
 
-     }
 
-     return true;
 
-   }
 
-   /**
 
-    * Well-formed constraint: No < in Attribute Values
 
-    * > The replacement text of any entity referred to directly or indirectly
 
-    * > in an attribute value must not contain a <.
 
-    * @see https://www.w3.org/TR/xml11/#CleanAttrVals
 
-    * @see https://www.w3.org/TR/xml11/#NT-AttValue
 
-    *
 
-    * Literal whitespace other than space that appear in attribute values
 
-    * are serialized as their entity references, so they will be preserved.
 
-    * (In contrast to whitespace literals in the input which are normalized to spaces)
 
-    * @see https://www.w3.org/TR/xml11/#AVNormalize
 
-    * @see https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes
 
-    */
 
-   function addSerializedAttribute(buf, qualifiedName, value) {
 
-     buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"');
 
-   }
 
-   function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces) {
 
-     if (!visibleNamespaces) {
 
-       visibleNamespaces = [];
 
-     }
 
-     if (nodeFilter) {
 
-       node = nodeFilter(node);
 
-       if (node) {
 
-         if (typeof node == 'string') {
 
-           buf.push(node);
 
-           return;
 
-         }
 
-       } else {
 
-         return;
 
-       }
 
-       //buf.sort.apply(attrs, attributeSorter);
 
-     }
 
-     switch (node.nodeType) {
 
-       case ELEMENT_NODE:
 
-         var attrs = node.attributes;
 
-         var len = attrs.length;
 
-         var child = node.firstChild;
 
-         var nodeName = node.tagName;
 
-         isHTML = NAMESPACE$2.isHTML(node.namespaceURI) || isHTML;
 
-         var prefixedNodeName = nodeName;
 
-         if (!isHTML && !node.prefix && node.namespaceURI) {
 
-           var defaultNS;
 
-           // lookup current default ns from `xmlns` attribute
 
-           for (var ai = 0; ai < attrs.length; ai++) {
 
-             if (attrs.item(ai).name === 'xmlns') {
 
-               defaultNS = attrs.item(ai).value;
 
-               break;
 
-             }
 
-           }
 
-           if (!defaultNS) {
 
-             // lookup current default ns in visibleNamespaces
 
-             for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
 
-               var namespace = visibleNamespaces[nsi];
 
-               if (namespace.prefix === '' && namespace.namespace === node.namespaceURI) {
 
-                 defaultNS = namespace.namespace;
 
-                 break;
 
-               }
 
-             }
 
-           }
 
-           if (defaultNS !== node.namespaceURI) {
 
-             for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
 
-               var namespace = visibleNamespaces[nsi];
 
-               if (namespace.namespace === node.namespaceURI) {
 
-                 if (namespace.prefix) {
 
-                   prefixedNodeName = namespace.prefix + ':' + nodeName;
 
-                 }
 
-                 break;
 
-               }
 
-             }
 
-           }
 
-         }
 
-         buf.push('<', prefixedNodeName);
 
-         for (var i = 0; i < len; i++) {
 
-           // add namespaces for attributes
 
-           var attr = attrs.item(i);
 
-           if (attr.prefix == 'xmlns') {
 
-             visibleNamespaces.push({
 
-               prefix: attr.localName,
 
-               namespace: attr.value
 
-             });
 
-           } else if (attr.nodeName == 'xmlns') {
 
-             visibleNamespaces.push({
 
-               prefix: '',
 
-               namespace: attr.value
 
-             });
 
-           }
 
-         }
 
-         for (var i = 0; i < len; i++) {
 
-           var attr = attrs.item(i);
 
-           if (needNamespaceDefine(attr, isHTML, visibleNamespaces)) {
 
-             var prefix = attr.prefix || '';
 
-             var uri = attr.namespaceURI;
 
-             addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
 
-             visibleNamespaces.push({
 
-               prefix: prefix,
 
-               namespace: uri
 
-             });
 
-           }
 
-           serializeToString(attr, buf, isHTML, nodeFilter, visibleNamespaces);
 
-         }
 
-         // add namespace for current node
 
-         if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) {
 
-           var prefix = node.prefix || '';
 
-           var uri = node.namespaceURI;
 
-           addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
 
-           visibleNamespaces.push({
 
-             prefix: prefix,
 
-             namespace: uri
 
-           });
 
-         }
 
-         if (child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) {
 
-           buf.push('>');
 
-           //if is cdata child node
 
-           if (isHTML && /^script$/i.test(nodeName)) {
 
-             while (child) {
 
-               if (child.data) {
 
-                 buf.push(child.data);
 
-               } else {
 
-                 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
 
-               }
 
-               child = child.nextSibling;
 
-             }
 
-           } else {
 
-             while (child) {
 
-               serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
 
-               child = child.nextSibling;
 
-             }
 
-           }
 
-           buf.push('</', prefixedNodeName, '>');
 
-         } else {
 
-           buf.push('/>');
 
-         }
 
-         // remove added visible namespaces
 
-         //visibleNamespaces.length = startVisibleNamespaces;
 
-         return;
 
-       case DOCUMENT_NODE:
 
-       case DOCUMENT_FRAGMENT_NODE:
 
-         var child = node.firstChild;
 
-         while (child) {
 
-           serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
 
-           child = child.nextSibling;
 
-         }
 
-         return;
 
-       case ATTRIBUTE_NODE:
 
-         return addSerializedAttribute(buf, node.name, node.value);
 
-       case TEXT_NODE:
 
-         /**
 
-          * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form,
 
-          * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section.
 
-          * If they are needed elsewhere, they must be escaped using either numeric character references or the strings
 
-          * `&` and `<` respectively.
 
-          * The right angle bracket (>) may be represented using the string " > ", and must, for compatibility,
 
-          * be escaped using either `>` or a character reference when it appears in the string `]]>` in content,
 
-          * when that string is not marking the end of a CDATA section.
 
-          *
 
-          * In the content of elements, character data is any string of characters
 
-          * which does not contain the start-delimiter of any markup
 
-          * and does not include the CDATA-section-close delimiter, `]]>`.
 
-          *
 
-          * @see https://www.w3.org/TR/xml/#NT-CharData
 
-          * @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node
 
-          */
 
-         return buf.push(node.data.replace(/[<&>]/g, _xmlEncoder));
 
-       case CDATA_SECTION_NODE:
 
-         return buf.push('<![CDATA[', node.data, ']]>');
 
-       case COMMENT_NODE:
 
-         return buf.push("<!--", node.data, "-->");
 
-       case DOCUMENT_TYPE_NODE:
 
-         var pubid = node.publicId;
 
-         var sysid = node.systemId;
 
-         buf.push('<!DOCTYPE ', node.name);
 
-         if (pubid) {
 
-           buf.push(' PUBLIC ', pubid);
 
-           if (sysid && sysid != '.') {
 
-             buf.push(' ', sysid);
 
-           }
 
-           buf.push('>');
 
-         } else if (sysid && sysid != '.') {
 
-           buf.push(' SYSTEM ', sysid, '>');
 
-         } else {
 
-           var sub = node.internalSubset;
 
-           if (sub) {
 
-             buf.push(" [", sub, "]");
 
-           }
 
-           buf.push(">");
 
-         }
 
-         return;
 
-       case PROCESSING_INSTRUCTION_NODE:
 
-         return buf.push("<?", node.target, " ", node.data, "?>");
 
-       case ENTITY_REFERENCE_NODE:
 
-         return buf.push('&', node.nodeName, ';');
 
-       //case ENTITY_NODE:
 
-       //case NOTATION_NODE:
 
-       default:
 
-         buf.push('??', node.nodeName);
 
-     }
 
-   }
 
-   function importNode(doc, node, deep) {
 
-     var node2;
 
-     switch (node.nodeType) {
 
-       case ELEMENT_NODE:
 
-         node2 = node.cloneNode(false);
 
-         node2.ownerDocument = doc;
 
-       //var attrs = node2.attributes;
 
-       //var len = attrs.length;
 
-       //for(var i=0;i<len;i++){
 
-       //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep));
 
-       //}
 
-       case DOCUMENT_FRAGMENT_NODE:
 
-         break;
 
-       case ATTRIBUTE_NODE:
 
-         deep = true;
 
-         break;
 
-       //case ENTITY_REFERENCE_NODE:
 
-       //case PROCESSING_INSTRUCTION_NODE:
 
-       ////case TEXT_NODE:
 
-       //case CDATA_SECTION_NODE:
 
-       //case COMMENT_NODE:
 
-       //	deep = false;
 
-       //	break;
 
-       //case DOCUMENT_NODE:
 
-       //case DOCUMENT_TYPE_NODE:
 
-       //cannot be imported.
 
-       //case ENTITY_NODE:
 
-       //case NOTATION_NODE:
 
-       //can not hit in level3
 
-       //default:throw e;
 
-     }
 
-     if (!node2) {
 
-       node2 = node.cloneNode(false); //false
 
-     }
 
-     node2.ownerDocument = doc;
 
-     node2.parentNode = null;
 
-     if (deep) {
 
-       var child = node.firstChild;
 
-       while (child) {
 
-         node2.appendChild(importNode(doc, child, deep));
 
-         child = child.nextSibling;
 
-       }
 
-     }
 
-     return node2;
 
-   }
 
-   //
 
-   //var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1,
 
-   //					attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,};
 
-   function cloneNode(doc, node, deep) {
 
-     var node2 = new node.constructor();
 
-     for (var n in node) {
 
-       if (Object.prototype.hasOwnProperty.call(node, n)) {
 
-         var v = node[n];
 
-         if (typeof v != "object") {
 
-           if (v != node2[n]) {
 
-             node2[n] = v;
 
-           }
 
-         }
 
-       }
 
-     }
 
-     if (node.childNodes) {
 
-       node2.childNodes = new NodeList();
 
-     }
 
-     node2.ownerDocument = doc;
 
-     switch (node2.nodeType) {
 
-       case ELEMENT_NODE:
 
-         var attrs = node.attributes;
 
-         var attrs2 = node2.attributes = new NamedNodeMap();
 
-         var len = attrs.length;
 
-         attrs2._ownerElement = node2;
 
-         for (var i = 0; i < len; i++) {
 
-           node2.setAttributeNode(cloneNode(doc, attrs.item(i), true));
 
-         }
 
-         break;
 
-       case ATTRIBUTE_NODE:
 
-         deep = true;
 
-     }
 
-     if (deep) {
 
-       var child = node.firstChild;
 
-       while (child) {
 
-         node2.appendChild(cloneNode(doc, child, deep));
 
-         child = child.nextSibling;
 
-       }
 
-     }
 
-     return node2;
 
-   }
 
-   function __set__(object, key, value) {
 
-     object[key] = value;
 
-   }
 
-   //do dynamic
 
-   try {
 
-     if (Object.defineProperty) {
 
-       Object.defineProperty(LiveNodeList.prototype, 'length', {
 
-         get: function () {
 
-           _updateLiveList(this);
 
-           return this.$$length;
 
-         }
 
-       });
 
-       Object.defineProperty(Node.prototype, 'textContent', {
 
-         get: function () {
 
-           return getTextContent(this);
 
-         },
 
-         set: function (data) {
 
-           switch (this.nodeType) {
 
-             case ELEMENT_NODE:
 
-             case DOCUMENT_FRAGMENT_NODE:
 
-               while (this.firstChild) {
 
-                 this.removeChild(this.firstChild);
 
-               }
 
-               if (data || String(data)) {
 
-                 this.appendChild(this.ownerDocument.createTextNode(data));
 
-               }
 
-               break;
 
-             default:
 
-               this.data = data;
 
-               this.value = data;
 
-               this.nodeValue = data;
 
-           }
 
-         }
 
-       });
 
-       function getTextContent(node) {
 
-         switch (node.nodeType) {
 
-           case ELEMENT_NODE:
 
-           case DOCUMENT_FRAGMENT_NODE:
 
-             var buf = [];
 
-             node = node.firstChild;
 
-             while (node) {
 
-               if (node.nodeType !== 7 && node.nodeType !== 8) {
 
-                 buf.push(getTextContent(node));
 
-               }
 
-               node = node.nextSibling;
 
-             }
 
-             return buf.join('');
 
-           default:
 
-             return node.nodeValue;
 
-         }
 
-       }
 
-       __set__ = function (object, key, value) {
 
-         //console.log(value)
 
-         object['$$' + key] = value;
 
-       };
 
-     }
 
-   } catch (e) {//ie8
 
-   }
 
-   //if(typeof require == 'function'){
 
-   var DocumentType_1 = DocumentType;
 
-   var DOMException_1 = DOMException;
 
-   var DOMImplementation_1 = DOMImplementation$1;
 
-   var Element_1 = Element;
 
-   var Node_1 = Node;
 
-   var NodeList_1 = NodeList;
 
-   var XMLSerializer_1 = XMLSerializer;
 
-   //}
 
-   var dom = {
 
-     DocumentType: DocumentType_1,
 
-     DOMException: DOMException_1,
 
-     DOMImplementation: DOMImplementation_1,
 
-     Element: Element_1,
 
-     Node: Node_1,
 
-     NodeList: NodeList_1,
 
-     XMLSerializer: XMLSerializer_1
 
-   };
 
-   var entities = createCommonjsModule(function (module, exports) {
 
-     var freeze = conventions.freeze;
 
-     /**
 
-      * The entities that are predefined in every XML document.
 
-      *
 
-      * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1
 
-      * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0
 
-      * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML Wikipedia
 
-      */
 
-     exports.XML_ENTITIES = freeze({
 
-       amp: '&',
 
-       apos: "'",
 
-       gt: '>',
 
-       lt: '<',
 
-       quot: '"'
 
-     });
 
-     /**
 
-      * A map of currently 241 entities that are detected in an HTML document.
 
-      * They contain all entries from `XML_ENTITIES`.
 
-      *
 
-      * @see XML_ENTITIES
 
-      * @see DOMParser.parseFromString
 
-      * @see DOMImplementation.prototype.createHTMLDocument
 
-      * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) Spec
 
-      * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names
 
-      * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML
 
-      * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML Wikipedia (HTML)
 
-      * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML Wikpedia (XHTML)
 
-      */
 
-     exports.HTML_ENTITIES = freeze({
 
-       lt: '<',
 
-       gt: '>',
 
-       amp: '&',
 
-       quot: '"',
 
-       apos: "'",
 
-       Agrave: "À",
 
-       Aacute: "Á",
 
-       Acirc: "Â",
 
-       Atilde: "Ã",
 
-       Auml: "Ä",
 
-       Aring: "Å",
 
-       AElig: "Æ",
 
-       Ccedil: "Ç",
 
-       Egrave: "È",
 
-       Eacute: "É",
 
-       Ecirc: "Ê",
 
-       Euml: "Ë",
 
-       Igrave: "Ì",
 
-       Iacute: "Í",
 
-       Icirc: "Î",
 
-       Iuml: "Ï",
 
-       ETH: "Ð",
 
-       Ntilde: "Ñ",
 
-       Ograve: "Ò",
 
-       Oacute: "Ó",
 
-       Ocirc: "Ô",
 
-       Otilde: "Õ",
 
-       Ouml: "Ö",
 
-       Oslash: "Ø",
 
-       Ugrave: "Ù",
 
-       Uacute: "Ú",
 
-       Ucirc: "Û",
 
-       Uuml: "Ü",
 
-       Yacute: "Ý",
 
-       THORN: "Þ",
 
-       szlig: "ß",
 
-       agrave: "à",
 
-       aacute: "á",
 
-       acirc: "â",
 
-       atilde: "ã",
 
-       auml: "ä",
 
-       aring: "å",
 
-       aelig: "æ",
 
-       ccedil: "ç",
 
-       egrave: "è",
 
-       eacute: "é",
 
-       ecirc: "ê",
 
-       euml: "ë",
 
-       igrave: "ì",
 
-       iacute: "í",
 
-       icirc: "î",
 
-       iuml: "ï",
 
-       eth: "ð",
 
-       ntilde: "ñ",
 
-       ograve: "ò",
 
-       oacute: "ó",
 
-       ocirc: "ô",
 
-       otilde: "õ",
 
-       ouml: "ö",
 
-       oslash: "ø",
 
-       ugrave: "ù",
 
-       uacute: "ú",
 
-       ucirc: "û",
 
-       uuml: "ü",
 
-       yacute: "ý",
 
-       thorn: "þ",
 
-       yuml: "ÿ",
 
-       nbsp: "\u00a0",
 
-       iexcl: "¡",
 
-       cent: "¢",
 
-       pound: "£",
 
-       curren: "¤",
 
-       yen: "¥",
 
-       brvbar: "¦",
 
-       sect: "§",
 
-       uml: "¨",
 
-       copy: "©",
 
-       ordf: "ª",
 
-       laquo: "«",
 
-       not: "¬",
 
-       shy: "",
 
-       reg: "®",
 
-       macr: "¯",
 
-       deg: "°",
 
-       plusmn: "±",
 
-       sup2: "²",
 
-       sup3: "³",
 
-       acute: "´",
 
-       micro: "µ",
 
-       para: "¶",
 
-       middot: "·",
 
-       cedil: "¸",
 
-       sup1: "¹",
 
-       ordm: "º",
 
-       raquo: "»",
 
-       frac14: "¼",
 
-       frac12: "½",
 
-       frac34: "¾",
 
-       iquest: "¿",
 
-       times: "×",
 
-       divide: "÷",
 
-       forall: "∀",
 
-       part: "∂",
 
-       exist: "∃",
 
-       empty: "∅",
 
-       nabla: "∇",
 
-       isin: "∈",
 
-       notin: "∉",
 
-       ni: "∋",
 
-       prod: "∏",
 
-       sum: "∑",
 
-       minus: "−",
 
-       lowast: "∗",
 
-       radic: "√",
 
-       prop: "∝",
 
-       infin: "∞",
 
-       ang: "∠",
 
-       and: "∧",
 
-       or: "∨",
 
-       cap: "∩",
 
-       cup: "∪",
 
-       'int': "∫",
 
-       there4: "∴",
 
-       sim: "∼",
 
-       cong: "≅",
 
-       asymp: "≈",
 
-       ne: "≠",
 
-       equiv: "≡",
 
-       le: "≤",
 
-       ge: "≥",
 
-       sub: "⊂",
 
-       sup: "⊃",
 
-       nsub: "⊄",
 
-       sube: "⊆",
 
-       supe: "⊇",
 
-       oplus: "⊕",
 
-       otimes: "⊗",
 
-       perp: "⊥",
 
-       sdot: "⋅",
 
-       Alpha: "Α",
 
-       Beta: "Β",
 
-       Gamma: "Γ",
 
-       Delta: "Δ",
 
-       Epsilon: "Ε",
 
-       Zeta: "Ζ",
 
-       Eta: "Η",
 
-       Theta: "Θ",
 
-       Iota: "Ι",
 
-       Kappa: "Κ",
 
-       Lambda: "Λ",
 
-       Mu: "Μ",
 
-       Nu: "Ν",
 
-       Xi: "Ξ",
 
-       Omicron: "Ο",
 
-       Pi: "Π",
 
-       Rho: "Ρ",
 
-       Sigma: "Σ",
 
-       Tau: "Τ",
 
-       Upsilon: "Υ",
 
-       Phi: "Φ",
 
-       Chi: "Χ",
 
-       Psi: "Ψ",
 
-       Omega: "Ω",
 
-       alpha: "α",
 
-       beta: "β",
 
-       gamma: "γ",
 
-       delta: "δ",
 
-       epsilon: "ε",
 
-       zeta: "ζ",
 
-       eta: "η",
 
-       theta: "θ",
 
-       iota: "ι",
 
-       kappa: "κ",
 
-       lambda: "λ",
 
-       mu: "μ",
 
-       nu: "ν",
 
-       xi: "ξ",
 
-       omicron: "ο",
 
-       pi: "π",
 
-       rho: "ρ",
 
-       sigmaf: "ς",
 
-       sigma: "σ",
 
-       tau: "τ",
 
-       upsilon: "υ",
 
-       phi: "φ",
 
-       chi: "χ",
 
-       psi: "ψ",
 
-       omega: "ω",
 
-       thetasym: "ϑ",
 
-       upsih: "ϒ",
 
-       piv: "ϖ",
 
-       OElig: "Œ",
 
-       oelig: "œ",
 
-       Scaron: "Š",
 
-       scaron: "š",
 
-       Yuml: "Ÿ",
 
-       fnof: "ƒ",
 
-       circ: "ˆ",
 
-       tilde: "˜",
 
-       ensp: " ",
 
-       emsp: " ",
 
-       thinsp: " ",
 
-       zwnj: "",
 
-       zwj: "",
 
-       lrm: "",
 
-       rlm: "",
 
-       ndash: "–",
 
-       mdash: "—",
 
-       lsquo: "‘",
 
-       rsquo: "’",
 
-       sbquo: "‚",
 
-       ldquo: "“",
 
-       rdquo: "”",
 
-       bdquo: "„",
 
-       dagger: "†",
 
-       Dagger: "‡",
 
-       bull: "•",
 
-       hellip: "…",
 
-       permil: "‰",
 
-       prime: "′",
 
-       Prime: "″",
 
-       lsaquo: "‹",
 
-       rsaquo: "›",
 
-       oline: "‾",
 
-       euro: "€",
 
-       trade: "™",
 
-       larr: "←",
 
-       uarr: "↑",
 
-       rarr: "→",
 
-       darr: "↓",
 
-       harr: "↔",
 
-       crarr: "↵",
 
-       lceil: "⌈",
 
-       rceil: "⌉",
 
-       lfloor: "⌊",
 
-       rfloor: "⌋",
 
-       loz: "◊",
 
-       spades: "♠",
 
-       clubs: "♣",
 
-       hearts: "♥",
 
-       diams: "♦"
 
-     });
 
-     /**
 
-      * @deprecated use `HTML_ENTITIES` instead
 
-      * @see HTML_ENTITIES
 
-      */
 
-     exports.entityMap = exports.HTML_ENTITIES;
 
-   });
 
-   entities.XML_ENTITIES;
 
-   entities.HTML_ENTITIES;
 
-   entities.entityMap;
 
-   var NAMESPACE$1 = conventions.NAMESPACE;
 
-   //[4]   	NameStartChar	   ::=   	":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
 
-   //[4a]   	NameChar	   ::=   	NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
 
-   //[5]   	Name	   ::=   	NameStartChar (NameChar)*
 
-   var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/; //\u10000-\uEFFFF
 
-   var nameChar = new RegExp("[\\-\\.0-9" + nameStartChar.source.slice(1, -1) + "\\u00B7\\u0300-\\u036F\\u203F-\\u2040]");
 
-   var tagNamePattern = new RegExp('^' + nameStartChar.source + nameChar.source + '*(?:\:' + nameStartChar.source + nameChar.source + '*)?$');
 
-   //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
 
-   //var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',')
 
-   //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
 
-   //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
 
-   var S_TAG = 0; //tag name offerring
 
-   var S_ATTR = 1; //attr name offerring
 
-   var S_ATTR_SPACE = 2; //attr name end and space offer
 
-   var S_EQ = 3; //=space?
 
-   var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only)
 
-   var S_ATTR_END = 5; //attr value end and no space(quot end)
 
-   var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer)
 
-   var S_TAG_CLOSE = 7; //closed el<el />
 
-   /**
 
-    * Creates an error that will not be caught by XMLReader aka the SAX parser.
 
-    *
 
-    * @param {string} message
 
-    * @param {any?} locator Optional, can provide details about the location in the source
 
-    * @constructor
 
-    */
 
-   function ParseError$1(message, locator) {
 
-     this.message = message;
 
-     this.locator = locator;
 
-     if (Error.captureStackTrace) Error.captureStackTrace(this, ParseError$1);
 
-   }
 
-   ParseError$1.prototype = new Error();
 
-   ParseError$1.prototype.name = ParseError$1.name;
 
-   function XMLReader$1() {}
 
-   XMLReader$1.prototype = {
 
-     parse: function (source, defaultNSMap, entityMap) {
 
-       var domBuilder = this.domBuilder;
 
-       domBuilder.startDocument();
 
-       _copy(defaultNSMap, defaultNSMap = {});
 
-       parse$1(source, defaultNSMap, entityMap, domBuilder, this.errorHandler);
 
-       domBuilder.endDocument();
 
-     }
 
-   };
 
-   function parse$1(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) {
 
-     function fixedFromCharCode(code) {
 
-       // String.prototype.fromCharCode does not supports
 
-       // > 2 bytes unicode chars directly
 
-       if (code > 0xffff) {
 
-         code -= 0x10000;
 
-         var surrogate1 = 0xd800 + (code >> 10),
 
-           surrogate2 = 0xdc00 + (code & 0x3ff);
 
-         return String.fromCharCode(surrogate1, surrogate2);
 
-       } else {
 
-         return String.fromCharCode(code);
 
-       }
 
-     }
 
-     function entityReplacer(a) {
 
-       var k = a.slice(1, -1);
 
-       if (Object.hasOwnProperty.call(entityMap, k)) {
 
-         return entityMap[k];
 
-       } else if (k.charAt(0) === '#') {
 
-         return fixedFromCharCode(parseInt(k.substr(1).replace('x', '0x')));
 
-       } else {
 
-         errorHandler.error('entity not found:' + a);
 
-         return a;
 
-       }
 
-     }
 
-     function appendText(end) {
 
-       //has some bugs
 
-       if (end > start) {
 
-         var xt = source.substring(start, end).replace(/&#?\w+;/g, entityReplacer);
 
-         locator && position(start);
 
-         domBuilder.characters(xt, 0, end - start);
 
-         start = end;
 
-       }
 
-     }
 
-     function position(p, m) {
 
-       while (p >= lineEnd && (m = linePattern.exec(source))) {
 
-         lineStart = m.index;
 
-         lineEnd = lineStart + m[0].length;
 
-         locator.lineNumber++;
 
-         //console.log('line++:',locator,startPos,endPos)
 
-       }
 
-       locator.columnNumber = p - lineStart + 1;
 
-     }
 
-     var lineStart = 0;
 
-     var lineEnd = 0;
 
-     var linePattern = /.*(?:\r\n?|\n)|.*$/g;
 
-     var locator = domBuilder.locator;
 
-     var parseStack = [{
 
-       currentNSMap: defaultNSMapCopy
 
-     }];
 
-     var closeMap = {};
 
-     var start = 0;
 
-     while (true) {
 
-       try {
 
-         var tagStart = source.indexOf('<', start);
 
-         if (tagStart < 0) {
 
-           if (!source.substr(start).match(/^\s*$/)) {
 
-             var doc = domBuilder.doc;
 
-             var text = doc.createTextNode(source.substr(start));
 
-             doc.appendChild(text);
 
-             domBuilder.currentElement = text;
 
-           }
 
-           return;
 
-         }
 
-         if (tagStart > start) {
 
-           appendText(tagStart);
 
-         }
 
-         switch (source.charAt(tagStart + 1)) {
 
-           case '/':
 
-             var end = source.indexOf('>', tagStart + 3);
 
-             var tagName = source.substring(tagStart + 2, end).replace(/[ \t\n\r]+$/g, '');
 
-             var config = parseStack.pop();
 
-             if (end < 0) {
 
-               tagName = source.substring(tagStart + 2).replace(/[\s<].*/, '');
 
-               errorHandler.error("end tag name: " + tagName + ' is not complete:' + config.tagName);
 
-               end = tagStart + 1 + tagName.length;
 
-             } else if (tagName.match(/\s</)) {
 
-               tagName = tagName.replace(/[\s<].*/, '');
 
-               errorHandler.error("end tag name: " + tagName + ' maybe not complete');
 
-               end = tagStart + 1 + tagName.length;
 
-             }
 
-             var localNSMap = config.localNSMap;
 
-             var endMatch = config.tagName == tagName;
 
-             var endIgnoreCaseMach = endMatch || config.tagName && config.tagName.toLowerCase() == tagName.toLowerCase();
 
-             if (endIgnoreCaseMach) {
 
-               domBuilder.endElement(config.uri, config.localName, tagName);
 
-               if (localNSMap) {
 
-                 for (var prefix in localNSMap) {
 
-                   if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
 
-                     domBuilder.endPrefixMapping(prefix);
 
-                   }
 
-                 }
 
-               }
 
-               if (!endMatch) {
 
-                 errorHandler.fatalError("end tag name: " + tagName + ' is not match the current start tagName:' + config.tagName); // No known test case
 
-               }
 
-             } else {
 
-               parseStack.push(config);
 
-             }
 
-             end++;
 
-             break;
 
-           // end elment
 
-           case '?':
 
-             // <?...?>
 
-             locator && position(tagStart);
 
-             end = parseInstruction(source, tagStart, domBuilder);
 
-             break;
 
-           case '!':
 
-             // <!doctype,<![CDATA,<!--
 
-             locator && position(tagStart);
 
-             end = parseDCC(source, tagStart, domBuilder, errorHandler);
 
-             break;
 
-           default:
 
-             locator && position(tagStart);
 
-             var el = new ElementAttributes();
 
-             var currentNSMap = parseStack[parseStack.length - 1].currentNSMap;
 
-             //elStartEnd
 
-             var end = parseElementStartPart(source, tagStart, el, currentNSMap, entityReplacer, errorHandler);
 
-             var len = el.length;
 
-             if (!el.closed && fixSelfClosed(source, end, el.tagName, closeMap)) {
 
-               el.closed = true;
 
-               if (!entityMap.nbsp) {
 
-                 errorHandler.warning('unclosed xml attribute');
 
-               }
 
-             }
 
-             if (locator && len) {
 
-               var locator2 = copyLocator(locator, {});
 
-               //try{//attribute position fixed
 
-               for (var i = 0; i < len; i++) {
 
-                 var a = el[i];
 
-                 position(a.offset);
 
-                 a.locator = copyLocator(locator, {});
 
-               }
 
-               domBuilder.locator = locator2;
 
-               if (appendElement$1(el, domBuilder, currentNSMap)) {
 
-                 parseStack.push(el);
 
-               }
 
-               domBuilder.locator = locator;
 
-             } else {
 
-               if (appendElement$1(el, domBuilder, currentNSMap)) {
 
-                 parseStack.push(el);
 
-               }
 
-             }
 
-             if (NAMESPACE$1.isHTML(el.uri) && !el.closed) {
 
-               end = parseHtmlSpecialContent(source, end, el.tagName, entityReplacer, domBuilder);
 
-             } else {
 
-               end++;
 
-             }
 
-         }
 
-       } catch (e) {
 
-         if (e instanceof ParseError$1) {
 
-           throw e;
 
-         }
 
-         errorHandler.error('element parse error: ' + e);
 
-         end = -1;
 
-       }
 
-       if (end > start) {
 
-         start = end;
 
-       } else {
 
-         //TODO: 这里有可能sax回退,有位置错误风险
 
-         appendText(Math.max(tagStart, start) + 1);
 
-       }
 
-     }
 
-   }
 
-   function copyLocator(f, t) {
 
-     t.lineNumber = f.lineNumber;
 
-     t.columnNumber = f.columnNumber;
 
-     return t;
 
-   }
 
-   /**
 
-    * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
 
-    * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
 
-    */
 
-   function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler) {
 
-     /**
 
-      * @param {string} qname
 
-      * @param {string} value
 
-      * @param {number} startIndex
 
-      */
 
-     function addAttribute(qname, value, startIndex) {
 
-       if (el.attributeNames.hasOwnProperty(qname)) {
 
-         errorHandler.fatalError('Attribute ' + qname + ' redefined');
 
-       }
 
-       el.addValue(qname,
 
-       // @see https://www.w3.org/TR/xml/#AVNormalize
 
-       // since the xmldom sax parser does not "interpret" DTD the following is not implemented:
 
-       // - recursive replacement of (DTD) entity references
 
-       // - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA
 
-       value.replace(/[\t\n\r]/g, ' ').replace(/&#?\w+;/g, entityReplacer), startIndex);
 
-     }
 
-     var attrName;
 
-     var value;
 
-     var p = ++start;
 
-     var s = S_TAG; //status
 
-     while (true) {
 
-       var c = source.charAt(p);
 
-       switch (c) {
 
-         case '=':
 
-           if (s === S_ATTR) {
 
-             //attrName
 
-             attrName = source.slice(start, p);
 
-             s = S_EQ;
 
-           } else if (s === S_ATTR_SPACE) {
 
-             s = S_EQ;
 
-           } else {
 
-             //fatalError: equal must after attrName or space after attrName
 
-             throw new Error('attribute equal must after attrName'); // No known test case
 
-           }
 
-           break;
 
-         case '\'':
 
-         case '"':
 
-           if (s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE
 
-           ) {
 
-             //equal
 
-             if (s === S_ATTR) {
 
-               errorHandler.warning('attribute value must after "="');
 
-               attrName = source.slice(start, p);
 
-             }
 
-             start = p + 1;
 
-             p = source.indexOf(c, start);
 
-             if (p > 0) {
 
-               value = source.slice(start, p);
 
-               addAttribute(attrName, value, start - 1);
 
-               s = S_ATTR_END;
 
-             } else {
 
-               //fatalError: no end quot match
 
-               throw new Error('attribute value no end \'' + c + '\' match');
 
-             }
 
-           } else if (s == S_ATTR_NOQUOT_VALUE) {
 
-             value = source.slice(start, p);
 
-             addAttribute(attrName, value, start);
 
-             errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!');
 
-             start = p + 1;
 
-             s = S_ATTR_END;
 
-           } else {
 
-             //fatalError: no equal before
 
-             throw new Error('attribute value must after "="'); // No known test case
 
-           }
 
-           break;
 
-         case '/':
 
-           switch (s) {
 
-             case S_TAG:
 
-               el.setTagName(source.slice(start, p));
 
-             case S_ATTR_END:
 
-             case S_TAG_SPACE:
 
-             case S_TAG_CLOSE:
 
-               s = S_TAG_CLOSE;
 
-               el.closed = true;
 
-             case S_ATTR_NOQUOT_VALUE:
 
-             case S_ATTR:
 
-             case S_ATTR_SPACE:
 
-               break;
 
-             //case S_EQ:
 
-             default:
 
-               throw new Error("attribute invalid close char('/')");
 
-             // No known test case
 
-           }
 
-           break;
 
-         case '':
 
-           //end document
 
-           errorHandler.error('unexpected end of input');
 
-           if (s == S_TAG) {
 
-             el.setTagName(source.slice(start, p));
 
-           }
 
-           return p;
 
-         case '>':
 
-           switch (s) {
 
-             case S_TAG:
 
-               el.setTagName(source.slice(start, p));
 
-             case S_ATTR_END:
 
-             case S_TAG_SPACE:
 
-             case S_TAG_CLOSE:
 
-               break;
 
-             //normal
 
-             case S_ATTR_NOQUOT_VALUE: //Compatible state
 
-             case S_ATTR:
 
-               value = source.slice(start, p);
 
-               if (value.slice(-1) === '/') {
 
-                 el.closed = true;
 
-                 value = value.slice(0, -1);
 
-               }
 
-             case S_ATTR_SPACE:
 
-               if (s === S_ATTR_SPACE) {
 
-                 value = attrName;
 
-               }
 
-               if (s == S_ATTR_NOQUOT_VALUE) {
 
-                 errorHandler.warning('attribute "' + value + '" missed quot(")!');
 
-                 addAttribute(attrName, value, start);
 
-               } else {
 
-                 if (!NAMESPACE$1.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)) {
 
-                   errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!');
 
-                 }
 
-                 addAttribute(value, value, start);
 
-               }
 
-               break;
 
-             case S_EQ:
 
-               throw new Error('attribute value missed!!');
 
-           }
 
-           //			console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
 
-           return p;
 
-         /*xml space '\x20' | #x9 | #xD | #xA; */
 
-         case '\u0080':
 
-           c = ' ';
 
-         default:
 
-           if (c <= ' ') {
 
-             //space
 
-             switch (s) {
 
-               case S_TAG:
 
-                 el.setTagName(source.slice(start, p)); //tagName
 
-                 s = S_TAG_SPACE;
 
-                 break;
 
-               case S_ATTR:
 
-                 attrName = source.slice(start, p);
 
-                 s = S_ATTR_SPACE;
 
-                 break;
 
-               case S_ATTR_NOQUOT_VALUE:
 
-                 var value = source.slice(start, p);
 
-                 errorHandler.warning('attribute "' + value + '" missed quot(")!!');
 
-                 addAttribute(attrName, value, start);
 
-               case S_ATTR_END:
 
-                 s = S_TAG_SPACE;
 
-                 break;
 
-               //case S_TAG_SPACE:
 
-               //case S_EQ:
 
-               //case S_ATTR_SPACE:
 
-               //	void();break;
 
-               //case S_TAG_CLOSE:
 
-               //ignore warning
 
-             }
 
-           } else {
 
-             //not space
 
-             //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
 
-             //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
 
-             switch (s) {
 
-               //case S_TAG:void();break;
 
-               //case S_ATTR:void();break;
 
-               //case S_ATTR_NOQUOT_VALUE:void();break;
 
-               case S_ATTR_SPACE:
 
-                 el.tagName;
 
-                 if (!NAMESPACE$1.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) {
 
-                   errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!');
 
-                 }
 
-                 addAttribute(attrName, attrName, start);
 
-                 start = p;
 
-                 s = S_ATTR;
 
-                 break;
 
-               case S_ATTR_END:
 
-                 errorHandler.warning('attribute space is required"' + attrName + '"!!');
 
-               case S_TAG_SPACE:
 
-                 s = S_ATTR;
 
-                 start = p;
 
-                 break;
 
-               case S_EQ:
 
-                 s = S_ATTR_NOQUOT_VALUE;
 
-                 start = p;
 
-                 break;
 
-               case S_TAG_CLOSE:
 
-                 throw new Error("elements closed character '/' and '>' must be connected to");
 
-             }
 
-           }
 
-       } //end outer switch
 
-       //console.log('p++',p)
 
-       p++;
 
-     }
 
-   }
 
-   /**
 
-    * @return true if has new namespace define
 
-    */
 
-   function appendElement$1(el, domBuilder, currentNSMap) {
 
-     var tagName = el.tagName;
 
-     var localNSMap = null;
 
-     //var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
 
-     var i = el.length;
 
-     while (i--) {
 
-       var a = el[i];
 
-       var qName = a.qName;
 
-       var value = a.value;
 
-       var nsp = qName.indexOf(':');
 
-       if (nsp > 0) {
 
-         var prefix = a.prefix = qName.slice(0, nsp);
 
-         var localName = qName.slice(nsp + 1);
 
-         var nsPrefix = prefix === 'xmlns' && localName;
 
-       } else {
 
-         localName = qName;
 
-         prefix = null;
 
-         nsPrefix = qName === 'xmlns' && '';
 
-       }
 
-       //can not set prefix,because prefix !== ''
 
-       a.localName = localName;
 
-       //prefix == null for no ns prefix attribute
 
-       if (nsPrefix !== false) {
 
-         //hack!!
 
-         if (localNSMap == null) {
 
-           localNSMap = {};
 
-           //console.log(currentNSMap,0)
 
-           _copy(currentNSMap, currentNSMap = {});
 
-           //console.log(currentNSMap,1)
 
-         }
 
-         currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
 
-         a.uri = NAMESPACE$1.XMLNS;
 
-         domBuilder.startPrefixMapping(nsPrefix, value);
 
-       }
 
-     }
 
-     var i = el.length;
 
-     while (i--) {
 
-       a = el[i];
 
-       var prefix = a.prefix;
 
-       if (prefix) {
 
-         //no prefix attribute has no namespace
 
-         if (prefix === 'xml') {
 
-           a.uri = NAMESPACE$1.XML;
 
-         }
 
-         if (prefix !== 'xmlns') {
 
-           a.uri = currentNSMap[prefix || ''];
 
-           //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
 
-         }
 
-       }
 
-     }
 
-     var nsp = tagName.indexOf(':');
 
-     if (nsp > 0) {
 
-       prefix = el.prefix = tagName.slice(0, nsp);
 
-       localName = el.localName = tagName.slice(nsp + 1);
 
-     } else {
 
-       prefix = null; //important!!
 
-       localName = el.localName = tagName;
 
-     }
 
-     //no prefix element has default namespace
 
-     var ns = el.uri = currentNSMap[prefix || ''];
 
-     domBuilder.startElement(ns, localName, tagName, el);
 
-     //endPrefixMapping and startPrefixMapping have not any help for dom builder
 
-     //localNSMap = null
 
-     if (el.closed) {
 
-       domBuilder.endElement(ns, localName, tagName);
 
-       if (localNSMap) {
 
-         for (prefix in localNSMap) {
 
-           if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
 
-             domBuilder.endPrefixMapping(prefix);
 
-           }
 
-         }
 
-       }
 
-     } else {
 
-       el.currentNSMap = currentNSMap;
 
-       el.localNSMap = localNSMap;
 
-       //parseStack.push(el);
 
-       return true;
 
-     }
 
-   }
 
-   function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) {
 
-     if (/^(?:script|textarea)$/i.test(tagName)) {
 
-       var elEndStart = source.indexOf('</' + tagName + '>', elStartEnd);
 
-       var text = source.substring(elStartEnd + 1, elEndStart);
 
-       if (/[&<]/.test(text)) {
 
-         if (/^script$/i.test(tagName)) {
 
-           //if(!/\]\]>/.test(text)){
 
-           //lexHandler.startCDATA();
 
-           domBuilder.characters(text, 0, text.length);
 
-           //lexHandler.endCDATA();
 
-           return elEndStart;
 
-           //}
 
-         } //}else{//text area
 
-         text = text.replace(/&#?\w+;/g, entityReplacer);
 
-         domBuilder.characters(text, 0, text.length);
 
-         return elEndStart;
 
-         //}
 
-       }
 
-     }
 
-     return elStartEnd + 1;
 
-   }
 
-   function fixSelfClosed(source, elStartEnd, tagName, closeMap) {
 
-     //if(tagName in closeMap){
 
-     var pos = closeMap[tagName];
 
-     if (pos == null) {
 
-       //console.log(tagName)
 
-       pos = source.lastIndexOf('</' + tagName + '>');
 
-       if (pos < elStartEnd) {
 
-         //忘记闭合
 
-         pos = source.lastIndexOf('</' + tagName);
 
-       }
 
-       closeMap[tagName] = pos;
 
-     }
 
-     return pos < elStartEnd;
 
-     //}
 
-   }
 
-   function _copy(source, target) {
 
-     for (var n in source) {
 
-       if (Object.prototype.hasOwnProperty.call(source, n)) {
 
-         target[n] = source[n];
 
-       }
 
-     }
 
-   }
 
-   function parseDCC(source, start, domBuilder, errorHandler) {
 
-     //sure start with '<!'
 
-     var next = source.charAt(start + 2);
 
-     switch (next) {
 
-       case '-':
 
-         if (source.charAt(start + 3) === '-') {
 
-           var end = source.indexOf('-->', start + 4);
 
-           //append comment source.substring(4,end)//<!--
 
-           if (end > start) {
 
-             domBuilder.comment(source, start + 4, end - start - 4);
 
-             return end + 3;
 
-           } else {
 
-             errorHandler.error("Unclosed comment");
 
-             return -1;
 
-           }
 
-         } else {
 
-           //error
 
-           return -1;
 
-         }
 
-       default:
 
-         if (source.substr(start + 3, 6) == 'CDATA[') {
 
-           var end = source.indexOf(']]>', start + 9);
 
-           domBuilder.startCDATA();
 
-           domBuilder.characters(source, start + 9, end - start - 9);
 
-           domBuilder.endCDATA();
 
-           return end + 3;
 
-         }
 
-         //<!DOCTYPE
 
-         //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)
 
-         var matchs = split(source, start);
 
-         var len = matchs.length;
 
-         if (len > 1 && /!doctype/i.test(matchs[0][0])) {
 
-           var name = matchs[1][0];
 
-           var pubid = false;
 
-           var sysid = false;
 
-           if (len > 3) {
 
-             if (/^public$/i.test(matchs[2][0])) {
 
-               pubid = matchs[3][0];
 
-               sysid = len > 4 && matchs[4][0];
 
-             } else if (/^system$/i.test(matchs[2][0])) {
 
-               sysid = matchs[3][0];
 
-             }
 
-           }
 
-           var lastMatch = matchs[len - 1];
 
-           domBuilder.startDTD(name, pubid, sysid);
 
-           domBuilder.endDTD();
 
-           return lastMatch.index + lastMatch[0].length;
 
-         }
 
-     }
 
-     return -1;
 
-   }
 
-   function parseInstruction(source, start, domBuilder) {
 
-     var end = source.indexOf('?>', start);
 
-     if (end) {
 
-       var match = source.substring(start, end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);
 
-       if (match) {
 
-         match[0].length;
 
-         domBuilder.processingInstruction(match[1], match[2]);
 
-         return end + 2;
 
-       } else {
 
-         //error
 
-         return -1;
 
-       }
 
-     }
 
-     return -1;
 
-   }
 
-   function ElementAttributes() {
 
-     this.attributeNames = {};
 
-   }
 
-   ElementAttributes.prototype = {
 
-     setTagName: function (tagName) {
 
-       if (!tagNamePattern.test(tagName)) {
 
-         throw new Error('invalid tagName:' + tagName);
 
-       }
 
-       this.tagName = tagName;
 
-     },
 
-     addValue: function (qName, value, offset) {
 
-       if (!tagNamePattern.test(qName)) {
 
-         throw new Error('invalid attribute:' + qName);
 
-       }
 
-       this.attributeNames[qName] = this.length;
 
-       this[this.length++] = {
 
-         qName: qName,
 
-         value: value,
 
-         offset: offset
 
-       };
 
-     },
 
-     length: 0,
 
-     getLocalName: function (i) {
 
-       return this[i].localName;
 
-     },
 
-     getLocator: function (i) {
 
-       return this[i].locator;
 
-     },
 
-     getQName: function (i) {
 
-       return this[i].qName;
 
-     },
 
-     getURI: function (i) {
 
-       return this[i].uri;
 
-     },
 
-     getValue: function (i) {
 
-       return this[i].value;
 
-     }
 
-     //	,getIndex:function(uri, localName)){
 
-     //		if(localName){
 
-     //
 
-     //		}else{
 
-     //			var qName = uri
 
-     //		}
 
-     //	},
 
-     //	getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
 
-     //	getType:function(uri,localName){}
 
-     //	getType:function(i){},
 
-   };
 
-   function split(source, start) {
 
-     var match;
 
-     var buf = [];
 
-     var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
 
-     reg.lastIndex = start;
 
-     reg.exec(source); //skip <
 
-     while (match = reg.exec(source)) {
 
-       buf.push(match);
 
-       if (match[1]) return buf;
 
-     }
 
-   }
 
-   var XMLReader_1 = XMLReader$1;
 
-   var ParseError_1 = ParseError$1;
 
-   var sax = {
 
-     XMLReader: XMLReader_1,
 
-     ParseError: ParseError_1
 
-   };
 
-   var DOMImplementation = dom.DOMImplementation;
 
-   var NAMESPACE = conventions.NAMESPACE;
 
-   var ParseError = sax.ParseError;
 
-   var XMLReader = sax.XMLReader;
 
-   /**
 
-    * Normalizes line ending according to https://www.w3.org/TR/xml11/#sec-line-ends:
 
-    *
 
-    * > XML parsed entities are often stored in computer files which,
 
-    * > for editing convenience, are organized into lines.
 
-    * > These lines are typically separated by some combination
 
-    * > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA).
 
-    * >
 
-    * > To simplify the tasks of applications, the XML processor must behave
 
-    * > as if it normalized all line breaks in external parsed entities (including the document entity)
 
-    * > on input, before parsing, by translating all of the following to a single #xA character:
 
-    * >
 
-    * > 1. the two-character sequence #xD #xA
 
-    * > 2. the two-character sequence #xD #x85
 
-    * > 3. the single character #x85
 
-    * > 4. the single character #x2028
 
-    * > 5. any #xD character that is not immediately followed by #xA or #x85.
 
-    *
 
-    * @param {string} input
 
-    * @returns {string}
 
-    */
 
-   function normalizeLineEndings(input) {
 
-     return input.replace(/\r[\n\u0085]/g, '\n').replace(/[\r\u0085\u2028]/g, '\n');
 
-   }
 
-   /**
 
-    * @typedef Locator
 
-    * @property {number} [columnNumber]
 
-    * @property {number} [lineNumber]
 
-    */
 
-   /**
 
-    * @typedef DOMParserOptions
 
-    * @property {DOMHandler} [domBuilder]
 
-    * @property {Function} [errorHandler]
 
-    * @property {(string) => string} [normalizeLineEndings] used to replace line endings before parsing
 
-    * 						defaults to `normalizeLineEndings`
 
-    * @property {Locator} [locator]
 
-    * @property {Record<string, string>} [xmlns]
 
-    *
 
-    * @see normalizeLineEndings
 
-    */
 
-   /**
 
-    * The DOMParser interface provides the ability to parse XML or HTML source code
 
-    * from a string into a DOM `Document`.
 
-    *
 
-    * _xmldom is different from the spec in that it allows an `options` parameter,
 
-    * to override the default behavior._
 
-    *
 
-    * @param {DOMParserOptions} [options]
 
-    * @constructor
 
-    *
 
-    * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
 
-    * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization
 
-    */
 
-   function DOMParser$1(options) {
 
-     this.options = options || {
 
-       locator: {}
 
-     };
 
-   }
 
-   DOMParser$1.prototype.parseFromString = function (source, mimeType) {
 
-     var options = this.options;
 
-     var sax = new XMLReader();
 
-     var domBuilder = options.domBuilder || new DOMHandler(); //contentHandler and LexicalHandler
 
-     var errorHandler = options.errorHandler;
 
-     var locator = options.locator;
 
-     var defaultNSMap = options.xmlns || {};
 
-     var isHTML = /\/x?html?$/.test(mimeType); //mimeType.toLowerCase().indexOf('html') > -1;
 
-     var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES;
 
-     if (locator) {
 
-       domBuilder.setDocumentLocator(locator);
 
-     }
 
-     sax.errorHandler = buildErrorHandler(errorHandler, domBuilder, locator);
 
-     sax.domBuilder = options.domBuilder || domBuilder;
 
-     if (isHTML) {
 
-       defaultNSMap[''] = NAMESPACE.HTML;
 
-     }
 
-     defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML;
 
-     var normalize = options.normalizeLineEndings || normalizeLineEndings;
 
-     if (source && typeof source === 'string') {
 
-       sax.parse(normalize(source), defaultNSMap, entityMap);
 
-     } else {
 
-       sax.errorHandler.error('invalid doc source');
 
-     }
 
-     return domBuilder.doc;
 
-   };
 
-   function buildErrorHandler(errorImpl, domBuilder, locator) {
 
-     if (!errorImpl) {
 
-       if (domBuilder instanceof DOMHandler) {
 
-         return domBuilder;
 
-       }
 
-       errorImpl = domBuilder;
 
-     }
 
-     var errorHandler = {};
 
-     var isCallback = errorImpl instanceof Function;
 
-     locator = locator || {};
 
-     function build(key) {
 
-       var fn = errorImpl[key];
 
-       if (!fn && isCallback) {
 
-         fn = errorImpl.length == 2 ? function (msg) {
 
-           errorImpl(key, msg);
 
-         } : errorImpl;
 
-       }
 
-       errorHandler[key] = fn && function (msg) {
 
-         fn('[xmldom ' + key + ']\t' + msg + _locator(locator));
 
-       } || function () {};
 
-     }
 
-     build('warning');
 
-     build('error');
 
-     build('fatalError');
 
-     return errorHandler;
 
-   }
 
-   //console.log('#\n\n\n\n\n\n\n####')
 
-   /**
 
-    * +ContentHandler+ErrorHandler
 
-    * +LexicalHandler+EntityResolver2
 
-    * -DeclHandler-DTDHandler
 
-    *
 
-    * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler
 
-    * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2
 
-    * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html
 
-    */
 
-   function DOMHandler() {
 
-     this.cdata = false;
 
-   }
 
-   function position(locator, node) {
 
-     node.lineNumber = locator.lineNumber;
 
-     node.columnNumber = locator.columnNumber;
 
-   }
 
-   /**
 
-    * @see org.xml.sax.ContentHandler#startDocument
 
-    * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html
 
-    */
 
-   DOMHandler.prototype = {
 
-     startDocument: function () {
 
-       this.doc = new DOMImplementation().createDocument(null, null, null);
 
-       if (this.locator) {
 
-         this.doc.documentURI = this.locator.systemId;
 
-       }
 
-     },
 
-     startElement: function (namespaceURI, localName, qName, attrs) {
 
-       var doc = this.doc;
 
-       var el = doc.createElementNS(namespaceURI, qName || localName);
 
-       var len = attrs.length;
 
-       appendElement(this, el);
 
-       this.currentElement = el;
 
-       this.locator && position(this.locator, el);
 
-       for (var i = 0; i < len; i++) {
 
-         var namespaceURI = attrs.getURI(i);
 
-         var value = attrs.getValue(i);
 
-         var qName = attrs.getQName(i);
 
-         var attr = doc.createAttributeNS(namespaceURI, qName);
 
-         this.locator && position(attrs.getLocator(i), attr);
 
-         attr.value = attr.nodeValue = value;
 
-         el.setAttributeNode(attr);
 
-       }
 
-     },
 
-     endElement: function (namespaceURI, localName, qName) {
 
-       var current = this.currentElement;
 
-       current.tagName;
 
-       this.currentElement = current.parentNode;
 
-     },
 
-     startPrefixMapping: function (prefix, uri) {},
 
-     endPrefixMapping: function (prefix) {},
 
-     processingInstruction: function (target, data) {
 
-       var ins = this.doc.createProcessingInstruction(target, data);
 
-       this.locator && position(this.locator, ins);
 
-       appendElement(this, ins);
 
-     },
 
-     ignorableWhitespace: function (ch, start, length) {},
 
-     characters: function (chars, start, length) {
 
-       chars = _toString.apply(this, arguments);
 
-       //console.log(chars)
 
-       if (chars) {
 
-         if (this.cdata) {
 
-           var charNode = this.doc.createCDATASection(chars);
 
-         } else {
 
-           var charNode = this.doc.createTextNode(chars);
 
-         }
 
-         if (this.currentElement) {
 
-           this.currentElement.appendChild(charNode);
 
-         } else if (/^\s*$/.test(chars)) {
 
-           this.doc.appendChild(charNode);
 
-           //process xml
 
-         }
 
-         this.locator && position(this.locator, charNode);
 
-       }
 
-     },
 
-     skippedEntity: function (name) {},
 
-     endDocument: function () {
 
-       this.doc.normalize();
 
-     },
 
-     setDocumentLocator: function (locator) {
 
-       if (this.locator = locator) {
 
-         // && !('lineNumber' in locator)){
 
-         locator.lineNumber = 0;
 
-       }
 
-     },
 
-     //LexicalHandler
 
-     comment: function (chars, start, length) {
 
-       chars = _toString.apply(this, arguments);
 
-       var comm = this.doc.createComment(chars);
 
-       this.locator && position(this.locator, comm);
 
-       appendElement(this, comm);
 
-     },
 
-     startCDATA: function () {
 
-       //used in characters() methods
 
-       this.cdata = true;
 
-     },
 
-     endCDATA: function () {
 
-       this.cdata = false;
 
-     },
 
-     startDTD: function (name, publicId, systemId) {
 
-       var impl = this.doc.implementation;
 
-       if (impl && impl.createDocumentType) {
 
-         var dt = impl.createDocumentType(name, publicId, systemId);
 
-         this.locator && position(this.locator, dt);
 
-         appendElement(this, dt);
 
-         this.doc.doctype = dt;
 
-       }
 
-     },
 
-     /**
 
-      * @see org.xml.sax.ErrorHandler
 
-      * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
 
-      */
 
-     warning: function (error) {
 
-       console.warn('[xmldom warning]\t' + error, _locator(this.locator));
 
-     },
 
-     error: function (error) {
 
-       console.error('[xmldom error]\t' + error, _locator(this.locator));
 
-     },
 
-     fatalError: function (error) {
 
-       throw new ParseError(error, this.locator);
 
-     }
 
-   };
 
-   function _locator(l) {
 
-     if (l) {
 
-       return '\n@' + (l.systemId || '') + '#[line:' + l.lineNumber + ',col:' + l.columnNumber + ']';
 
-     }
 
-   }
 
-   function _toString(chars, start, length) {
 
-     if (typeof chars == 'string') {
 
-       return chars.substr(start, length);
 
-     } else {
 
-       //java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)")
 
-       if (chars.length >= start + length || start) {
 
-         return new java.lang.String(chars, start, length) + '';
 
-       }
 
-       return chars;
 
-     }
 
-   }
 
-   /*
 
-    * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html
 
-    * used method of org.xml.sax.ext.LexicalHandler:
 
-    *  #comment(chars, start, length)
 
-    *  #startCDATA()
 
-    *  #endCDATA()
 
-    *  #startDTD(name, publicId, systemId)
 
-    *
 
-    *
 
-    * IGNORED method of org.xml.sax.ext.LexicalHandler:
 
-    *  #endDTD()
 
-    *  #startEntity(name)
 
-    *  #endEntity(name)
 
-    *
 
-    *
 
-    * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html
 
-    * IGNORED method of org.xml.sax.ext.DeclHandler
 
-    * 	#attributeDecl(eName, aName, type, mode, value)
 
-    *  #elementDecl(name, model)
 
-    *  #externalEntityDecl(name, publicId, systemId)
 
-    *  #internalEntityDecl(name, value)
 
-    * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html
 
-    * IGNORED method of org.xml.sax.EntityResolver2
 
-    *  #resolveEntity(String name,String publicId,String baseURI,String systemId)
 
-    *  #resolveEntity(publicId, systemId)
 
-    *  #getExternalSubset(name, baseURI)
 
-    * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html
 
-    * IGNORED method of org.xml.sax.DTDHandler
 
-    *  #notationDecl(name, publicId, systemId) {};
 
-    *  #unparsedEntityDecl(name, publicId, systemId, notationName) {};
 
-    */
 
-   "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g, function (key) {
 
-     DOMHandler.prototype[key] = function () {
 
-       return null;
 
-     };
 
-   });
 
-   /* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */
 
-   function appendElement(hander, node) {
 
-     if (!hander.currentElement) {
 
-       hander.doc.appendChild(node);
 
-     } else {
 
-       hander.currentElement.appendChild(node);
 
-     }
 
-   } //appendChild and setAttributeNS are preformance key
 
-   var __DOMHandler = DOMHandler;
 
-   var normalizeLineEndings_1 = normalizeLineEndings;
 
-   var DOMParser_1 = DOMParser$1;
 
-   var domParser = {
 
-     __DOMHandler: __DOMHandler,
 
-     normalizeLineEndings: normalizeLineEndings_1,
 
-     DOMParser: DOMParser_1
 
-   };
 
-   var DOMParser = domParser.DOMParser;
 
-   /*! @name mpd-parser @version 1.0.1 @license Apache-2.0 */
 
-   const isObject = obj => {
 
-     return !!obj && typeof obj === 'object';
 
-   };
 
-   const merge$1 = (...objects) => {
 
-     return objects.reduce((result, source) => {
 
-       if (typeof source !== 'object') {
 
-         return result;
 
-       }
 
-       Object.keys(source).forEach(key => {
 
-         if (Array.isArray(result[key]) && Array.isArray(source[key])) {
 
-           result[key] = result[key].concat(source[key]);
 
-         } else if (isObject(result[key]) && isObject(source[key])) {
 
-           result[key] = merge$1(result[key], source[key]);
 
-         } else {
 
-           result[key] = source[key];
 
-         }
 
-       });
 
-       return result;
 
-     }, {});
 
-   };
 
-   const values = o => Object.keys(o).map(k => o[k]);
 
-   const range = (start, end) => {
 
-     const result = [];
 
-     for (let i = start; i < end; i++) {
 
-       result.push(i);
 
-     }
 
-     return result;
 
-   };
 
-   const flatten = lists => lists.reduce((x, y) => x.concat(y), []);
 
-   const from = list => {
 
-     if (!list.length) {
 
-       return [];
 
-     }
 
-     const result = [];
 
-     for (let i = 0; i < list.length; i++) {
 
-       result.push(list[i]);
 
-     }
 
-     return result;
 
-   };
 
-   const findIndexes = (l, key) => l.reduce((a, e, i) => {
 
-     if (e[key]) {
 
-       a.push(i);
 
-     }
 
-     return a;
 
-   }, []);
 
-   /**
 
-    * Returns a union of the included lists provided each element can be identified by a key.
 
-    *
 
-    * @param {Array} list - list of lists to get the union of
 
-    * @param {Function} keyFunction - the function to use as a key for each element
 
-    *
 
-    * @return {Array} the union of the arrays
 
-    */
 
-   const union = (lists, keyFunction) => {
 
-     return values(lists.reduce((acc, list) => {
 
-       list.forEach(el => {
 
-         acc[keyFunction(el)] = el;
 
-       });
 
-       return acc;
 
-     }, {}));
 
-   };
 
-   var errors = {
 
-     INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
 
-     DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
 
-     DASH_INVALID_XML: 'DASH_INVALID_XML',
 
-     NO_BASE_URL: 'NO_BASE_URL',
 
-     MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
 
-     SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
 
-     UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
 
-   };
 
-   /**
 
-    * @typedef {Object} SingleUri
 
-    * @property {string} uri - relative location of segment
 
-    * @property {string} resolvedUri - resolved location of segment
 
-    * @property {Object} byterange - Object containing information on how to make byte range
 
-    *   requests following byte-range-spec per RFC2616.
 
-    * @property {String} byterange.length - length of range request
 
-    * @property {String} byterange.offset - byte offset of range request
 
-    *
 
-    * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
 
-    */
 
-   /**
 
-    * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
 
-    * that conforms to how m3u8-parser is structured
 
-    *
 
-    * @see https://github.com/videojs/m3u8-parser
 
-    *
 
-    * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
 
-    * @param {string} source - source url for segment
 
-    * @param {string} range - optional range used for range calls,
 
-    *   follows  RFC 2616, Clause 14.35.1
 
-    * @return {SingleUri} full segment information transformed into a format similar
 
-    *   to m3u8-parser
 
-    */
 
-   const urlTypeToSegment = ({
 
-     baseUrl = '',
 
-     source = '',
 
-     range = '',
 
-     indexRange = ''
 
-   }) => {
 
-     const segment = {
 
-       uri: source,
 
-       resolvedUri: resolveUrl$1(baseUrl || '', source)
 
-     };
 
-     if (range || indexRange) {
 
-       const rangeStr = range ? range : indexRange;
 
-       const ranges = rangeStr.split('-'); // default to parsing this as a BigInt if possible
 
-       let startRange = window.BigInt ? window.BigInt(ranges[0]) : parseInt(ranges[0], 10);
 
-       let endRange = window.BigInt ? window.BigInt(ranges[1]) : parseInt(ranges[1], 10); // convert back to a number if less than MAX_SAFE_INTEGER
 
-       if (startRange < Number.MAX_SAFE_INTEGER && typeof startRange === 'bigint') {
 
-         startRange = Number(startRange);
 
-       }
 
-       if (endRange < Number.MAX_SAFE_INTEGER && typeof endRange === 'bigint') {
 
-         endRange = Number(endRange);
 
-       }
 
-       let length;
 
-       if (typeof endRange === 'bigint' || typeof startRange === 'bigint') {
 
-         length = window.BigInt(endRange) - window.BigInt(startRange) + window.BigInt(1);
 
-       } else {
 
-         length = endRange - startRange + 1;
 
-       }
 
-       if (typeof length === 'bigint' && length < Number.MAX_SAFE_INTEGER) {
 
-         length = Number(length);
 
-       } // byterange should be inclusive according to
 
-       // RFC 2616, Clause 14.35.1
 
-       segment.byterange = {
 
-         length,
 
-         offset: startRange
 
-       };
 
-     }
 
-     return segment;
 
-   };
 
-   const byteRangeToString = byterange => {
 
-     // `endRange` is one less than `offset + length` because the HTTP range
 
-     // header uses inclusive ranges
 
-     let endRange;
 
-     if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
 
-       endRange = window.BigInt(byterange.offset) + window.BigInt(byterange.length) - window.BigInt(1);
 
-     } else {
 
-       endRange = byterange.offset + byterange.length - 1;
 
-     }
 
-     return `${byterange.offset}-${endRange}`;
 
-   };
 
-   /**
 
-    * parse the end number attribue that can be a string
 
-    * number, or undefined.
 
-    *
 
-    * @param {string|number|undefined} endNumber
 
-    *        The end number attribute.
 
-    *
 
-    * @return {number|null}
 
-    *          The result of parsing the end number.
 
-    */
 
-   const parseEndNumber = endNumber => {
 
-     if (endNumber && typeof endNumber !== 'number') {
 
-       endNumber = parseInt(endNumber, 10);
 
-     }
 
-     if (isNaN(endNumber)) {
 
-       return null;
 
-     }
 
-     return endNumber;
 
-   };
 
-   /**
 
-    * Functions for calculating the range of available segments in static and dynamic
 
-    * manifests.
 
-    */
 
-   const segmentRange = {
 
-     /**
 
-      * Returns the entire range of available segments for a static MPD
 
-      *
 
-      * @param {Object} attributes
 
-      *        Inheritied MPD attributes
 
-      * @return {{ start: number, end: number }}
 
-      *         The start and end numbers for available segments
 
-      */
 
-     static(attributes) {
 
-       const {
 
-         duration,
 
-         timescale = 1,
 
-         sourceDuration,
 
-         periodDuration
 
-       } = attributes;
 
-       const endNumber = parseEndNumber(attributes.endNumber);
 
-       const segmentDuration = duration / timescale;
 
-       if (typeof endNumber === 'number') {
 
-         return {
 
-           start: 0,
 
-           end: endNumber
 
-         };
 
-       }
 
-       if (typeof periodDuration === 'number') {
 
-         return {
 
-           start: 0,
 
-           end: periodDuration / segmentDuration
 
-         };
 
-       }
 
-       return {
 
-         start: 0,
 
-         end: sourceDuration / segmentDuration
 
-       };
 
-     },
 
-     /**
 
-      * Returns the current live window range of available segments for a dynamic MPD
 
-      *
 
-      * @param {Object} attributes
 
-      *        Inheritied MPD attributes
 
-      * @return {{ start: number, end: number }}
 
-      *         The start and end numbers for available segments
 
-      */
 
-     dynamic(attributes) {
 
-       const {
 
-         NOW,
 
-         clientOffset,
 
-         availabilityStartTime,
 
-         timescale = 1,
 
-         duration,
 
-         periodStart = 0,
 
-         minimumUpdatePeriod = 0,
 
-         timeShiftBufferDepth = Infinity
 
-       } = attributes;
 
-       const endNumber = parseEndNumber(attributes.endNumber); // clientOffset is passed in at the top level of mpd-parser and is an offset calculated
 
-       // after retrieving UTC server time.
 
-       const now = (NOW + clientOffset) / 1000; // WC stands for Wall Clock.
 
-       // Convert the period start time to EPOCH.
 
-       const periodStartWC = availabilityStartTime + periodStart; // Period end in EPOCH is manifest's retrieval time + time until next update.
 
-       const periodEndWC = now + minimumUpdatePeriod;
 
-       const periodDuration = periodEndWC - periodStartWC;
 
-       const segmentCount = Math.ceil(periodDuration * timescale / duration);
 
-       const availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
 
-       const availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
 
-       return {
 
-         start: Math.max(0, availableStart),
 
-         end: typeof endNumber === 'number' ? endNumber : Math.min(segmentCount, availableEnd)
 
-       };
 
-     }
 
-   };
 
-   /**
 
-    * Maps a range of numbers to objects with information needed to build the corresponding
 
-    * segment list
 
-    *
 
-    * @name toSegmentsCallback
 
-    * @function
 
-    * @param {number} number
 
-    *        Number of the segment
 
-    * @param {number} index
 
-    *        Index of the number in the range list
 
-    * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
 
-    *         Object with segment timing and duration info
 
-    */
 
-   /**
 
-    * Returns a callback for Array.prototype.map for mapping a range of numbers to
 
-    * information needed to build the segment list.
 
-    *
 
-    * @param {Object} attributes
 
-    *        Inherited MPD attributes
 
-    * @return {toSegmentsCallback}
 
-    *         Callback map function
 
-    */
 
-   const toSegments = attributes => number => {
 
-     const {
 
-       duration,
 
-       timescale = 1,
 
-       periodStart,
 
-       startNumber = 1
 
-     } = attributes;
 
-     return {
 
-       number: startNumber + number,
 
-       duration: duration / timescale,
 
-       timeline: periodStart,
 
-       time: number * duration
 
-     };
 
-   };
 
-   /**
 
-    * Returns a list of objects containing segment timing and duration info used for
 
-    * building the list of segments. This uses the @duration attribute specified
 
-    * in the MPD manifest to derive the range of segments.
 
-    *
 
-    * @param {Object} attributes
 
-    *        Inherited MPD attributes
 
-    * @return {{number: number, duration: number, time: number, timeline: number}[]}
 
-    *         List of Objects with segment timing and duration info
 
-    */
 
-   const parseByDuration = attributes => {
 
-     const {
 
-       type,
 
-       duration,
 
-       timescale = 1,
 
-       periodDuration,
 
-       sourceDuration
 
-     } = attributes;
 
-     const {
 
-       start,
 
-       end
 
-     } = segmentRange[type](attributes);
 
-     const segments = range(start, end).map(toSegments(attributes));
 
-     if (type === 'static') {
 
-       const index = segments.length - 1; // section is either a period or the full source
 
-       const sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration
 
-       segments[index].duration = sectionDuration - duration / timescale * index;
 
-     }
 
-     return segments;
 
-   };
 
-   /**
 
-    * Translates SegmentBase into a set of segments.
 
-    * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
 
-    * node should be translated into segment.
 
-    *
 
-    * @param {Object} attributes
 
-    *   Object containing all inherited attributes from parent elements with attribute
 
-    *   names as keys
 
-    * @return {Object.<Array>} list of segments
 
-    */
 
-   const segmentsFromBase = attributes => {
 
-     const {
 
-       baseUrl,
 
-       initialization = {},
 
-       sourceDuration,
 
-       indexRange = '',
 
-       periodStart,
 
-       presentationTime,
 
-       number = 0,
 
-       duration
 
-     } = attributes; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
 
-     if (!baseUrl) {
 
-       throw new Error(errors.NO_BASE_URL);
 
-     }
 
-     const initSegment = urlTypeToSegment({
 
-       baseUrl,
 
-       source: initialization.sourceURL,
 
-       range: initialization.range
 
-     });
 
-     const segment = urlTypeToSegment({
 
-       baseUrl,
 
-       source: baseUrl,
 
-       indexRange
 
-     });
 
-     segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
 
-     // (since SegmentBase is only for one total segment)
 
-     if (duration) {
 
-       const segmentTimeInfo = parseByDuration(attributes);
 
-       if (segmentTimeInfo.length) {
 
-         segment.duration = segmentTimeInfo[0].duration;
 
-         segment.timeline = segmentTimeInfo[0].timeline;
 
-       }
 
-     } else if (sourceDuration) {
 
-       segment.duration = sourceDuration;
 
-       segment.timeline = periodStart;
 
-     } // If presentation time is provided, these segments are being generated by SIDX
 
-     // references, and should use the time provided. For the general case of SegmentBase,
 
-     // there should only be one segment in the period, so its presentation time is the same
 
-     // as its period start.
 
-     segment.presentationTime = presentationTime || periodStart;
 
-     segment.number = number;
 
-     return [segment];
 
-   };
 
-   /**
 
-    * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
 
-    * according to the sidx information given.
 
-    *
 
-    * playlist.sidx has metadadata about the sidx where-as the sidx param
 
-    * is the parsed sidx box itself.
 
-    *
 
-    * @param {Object} playlist the playlist to update the sidx information for
 
-    * @param {Object} sidx the parsed sidx box
 
-    * @return {Object} the playlist object with the updated sidx information
 
-    */
 
-   const addSidxSegmentsToPlaylist$1 = (playlist, sidx, baseUrl) => {
 
-     // Retain init segment information
 
-     const initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial main manifest parsing
 
-     const sourceDuration = playlist.sidx.duration; // Retain source timeline
 
-     const timeline = playlist.timeline || 0;
 
-     const sidxByteRange = playlist.sidx.byterange;
 
-     const sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
 
-     const timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
 
-     const mediaReferences = sidx.references.filter(r => r.referenceType !== 1);
 
-     const segments = [];
 
-     const type = playlist.endList ? 'static' : 'dynamic';
 
-     const periodStart = playlist.sidx.timeline;
 
-     let presentationTime = periodStart;
 
-     let number = playlist.mediaSequence || 0; // firstOffset is the offset from the end of the sidx box
 
-     let startIndex; // eslint-disable-next-line
 
-     if (typeof sidx.firstOffset === 'bigint') {
 
-       startIndex = window.BigInt(sidxEnd) + sidx.firstOffset;
 
-     } else {
 
-       startIndex = sidxEnd + sidx.firstOffset;
 
-     }
 
-     for (let i = 0; i < mediaReferences.length; i++) {
 
-       const reference = sidx.references[i]; // size of the referenced (sub)segment
 
-       const size = reference.referencedSize; // duration of the referenced (sub)segment, in  the  timescale
 
-       // this will be converted to seconds when generating segments
 
-       const duration = reference.subsegmentDuration; // should be an inclusive range
 
-       let endIndex; // eslint-disable-next-line
 
-       if (typeof startIndex === 'bigint') {
 
-         endIndex = startIndex + window.BigInt(size) - window.BigInt(1);
 
-       } else {
 
-         endIndex = startIndex + size - 1;
 
-       }
 
-       const indexRange = `${startIndex}-${endIndex}`;
 
-       const attributes = {
 
-         baseUrl,
 
-         timescale,
 
-         timeline,
 
-         periodStart,
 
-         presentationTime,
 
-         number,
 
-         duration,
 
-         sourceDuration,
 
-         indexRange,
 
-         type
 
-       };
 
-       const segment = segmentsFromBase(attributes)[0];
 
-       if (initSegment) {
 
-         segment.map = initSegment;
 
-       }
 
-       segments.push(segment);
 
-       if (typeof startIndex === 'bigint') {
 
-         startIndex += window.BigInt(size);
 
-       } else {
 
-         startIndex += size;
 
-       }
 
-       presentationTime += duration / timescale;
 
-       number++;
 
-     }
 
-     playlist.segments = segments;
 
-     return playlist;
 
-   };
 
-   const SUPPORTED_MEDIA_TYPES = ['AUDIO', 'SUBTITLES']; // allow one 60fps frame as leniency (arbitrarily chosen)
 
-   const TIME_FUDGE = 1 / 60;
 
-   /**
 
-    * Given a list of timelineStarts, combines, dedupes, and sorts them.
 
-    *
 
-    * @param {TimelineStart[]} timelineStarts - list of timeline starts
 
-    *
 
-    * @return {TimelineStart[]} the combined and deduped timeline starts
 
-    */
 
-   const getUniqueTimelineStarts = timelineStarts => {
 
-     return union(timelineStarts, ({
 
-       timeline
 
-     }) => timeline).sort((a, b) => a.timeline > b.timeline ? 1 : -1);
 
-   };
 
-   /**
 
-    * Finds the playlist with the matching NAME attribute.
 
-    *
 
-    * @param {Array} playlists - playlists to search through
 
-    * @param {string} name - the NAME attribute to search for
 
-    *
 
-    * @return {Object|null} the matching playlist object, or null
 
-    */
 
-   const findPlaylistWithName = (playlists, name) => {
 
-     for (let i = 0; i < playlists.length; i++) {
 
-       if (playlists[i].attributes.NAME === name) {
 
-         return playlists[i];
 
-       }
 
-     }
 
-     return null;
 
-   };
 
-   /**
 
-    * Gets a flattened array of media group playlists.
 
-    *
 
-    * @param {Object} manifest - the main manifest object
 
-    *
 
-    * @return {Array} the media group playlists
 
-    */
 
-   const getMediaGroupPlaylists = manifest => {
 
-     let mediaGroupPlaylists = [];
 
-     forEachMediaGroup$1(manifest, SUPPORTED_MEDIA_TYPES, (properties, type, group, label) => {
 
-       mediaGroupPlaylists = mediaGroupPlaylists.concat(properties.playlists || []);
 
-     });
 
-     return mediaGroupPlaylists;
 
-   };
 
-   /**
 
-    * Updates the playlist's media sequence numbers.
 
-    *
 
-    * @param {Object} config - options object
 
-    * @param {Object} config.playlist - the playlist to update
 
-    * @param {number} config.mediaSequence - the mediaSequence number to start with
 
-    */
 
-   const updateMediaSequenceForPlaylist = ({
 
-     playlist,
 
-     mediaSequence
 
-   }) => {
 
-     playlist.mediaSequence = mediaSequence;
 
-     playlist.segments.forEach((segment, index) => {
 
-       segment.number = playlist.mediaSequence + index;
 
-     });
 
-   };
 
-   /**
 
-    * Updates the media and discontinuity sequence numbers of newPlaylists given oldPlaylists
 
-    * and a complete list of timeline starts.
 
-    *
 
-    * If no matching playlist is found, only the discontinuity sequence number of the playlist
 
-    * will be updated.
 
-    *
 
-    * Since early available timelines are not supported, at least one segment must be present.
 
-    *
 
-    * @param {Object} config - options object
 
-    * @param {Object[]} oldPlaylists - the old playlists to use as a reference
 
-    * @param {Object[]} newPlaylists - the new playlists to update
 
-    * @param {Object} timelineStarts - all timelineStarts seen in the stream to this point
 
-    */
 
-   const updateSequenceNumbers = ({
 
-     oldPlaylists,
 
-     newPlaylists,
 
-     timelineStarts
 
-   }) => {
 
-     newPlaylists.forEach(playlist => {
 
-       playlist.discontinuitySequence = timelineStarts.findIndex(function ({
 
-         timeline
 
-       }) {
 
-         return timeline === playlist.timeline;
 
-       }); // Playlists NAMEs come from DASH Representation IDs, which are mandatory
 
-       // (see ISO_23009-1-2012 5.3.5.2).
 
-       //
 
-       // If the same Representation existed in a prior Period, it will retain the same NAME.
 
-       const oldPlaylist = findPlaylistWithName(oldPlaylists, playlist.attributes.NAME);
 
-       if (!oldPlaylist) {
 
-         // Since this is a new playlist, the media sequence values can start from 0 without
 
-         // consequence.
 
-         return;
 
-       } // TODO better support for live SIDX
 
-       //
 
-       // As of this writing, mpd-parser does not support multiperiod SIDX (in live or VOD).
 
-       // This is evident by a playlist only having a single SIDX reference. In a multiperiod
 
-       // playlist there would need to be multiple SIDX references. In addition, live SIDX is
 
-       // not supported when the SIDX properties change on refreshes.
 
-       //
 
-       // In the future, if support needs to be added, the merging logic here can be called
 
-       // after SIDX references are resolved. For now, exit early to prevent exceptions being
 
-       // thrown due to undefined references.
 
-       if (playlist.sidx) {
 
-         return;
 
-       } // Since we don't yet support early available timelines, we don't need to support
 
-       // playlists with no segments.
 
-       const firstNewSegment = playlist.segments[0];
 
-       const oldMatchingSegmentIndex = oldPlaylist.segments.findIndex(function (oldSegment) {
 
-         return Math.abs(oldSegment.presentationTime - firstNewSegment.presentationTime) < TIME_FUDGE;
 
-       }); // No matching segment from the old playlist means the entire playlist was refreshed.
 
-       // In this case the media sequence should account for this update, and the new segments
 
-       // should be marked as discontinuous from the prior content, since the last prior
 
-       // timeline was removed.
 
-       if (oldMatchingSegmentIndex === -1) {
 
-         updateMediaSequenceForPlaylist({
 
-           playlist,
 
-           mediaSequence: oldPlaylist.mediaSequence + oldPlaylist.segments.length
 
-         });
 
-         playlist.segments[0].discontinuity = true;
 
-         playlist.discontinuityStarts.unshift(0); // No matching segment does not necessarily mean there's missing content.
 
-         //
 
-         // If the new playlist's timeline is the same as the last seen segment's timeline,
 
-         // then a discontinuity can be added to identify that there's potentially missing
 
-         // content. If there's no missing content, the discontinuity should still be rather
 
-         // harmless. It's possible that if segment durations are accurate enough, that the
 
-         // existence of a gap can be determined using the presentation times and durations,
 
-         // but if the segment timing info is off, it may introduce more problems than simply
 
-         // adding the discontinuity.
 
-         //
 
-         // If the new playlist's timeline is different from the last seen segment's timeline,
 
-         // then a discontinuity can be added to identify that this is the first seen segment
 
-         // of a new timeline. However, the logic at the start of this function that
 
-         // determined the disconinuity sequence by timeline index is now off by one (the
 
-         // discontinuity of the newest timeline hasn't yet fallen off the manifest...since
 
-         // we added it), so the disconinuity sequence must be decremented.
 
-         //
 
-         // A period may also have a duration of zero, so the case of no segments is handled
 
-         // here even though we don't yet support early available periods.
 
-         if (!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline || oldPlaylist.segments.length && playlist.timeline > oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline) {
 
-           playlist.discontinuitySequence--;
 
-         }
 
-         return;
 
-       } // If the first segment matched with a prior segment on a discontinuity (it's matching
 
-       // on the first segment of a period), then the discontinuitySequence shouldn't be the
 
-       // timeline's matching one, but instead should be the one prior, and the first segment
 
-       // of the new manifest should be marked with a discontinuity.
 
-       //
 
-       // The reason for this special case is that discontinuity sequence shows how many
 
-       // discontinuities have fallen off of the playlist, and discontinuities are marked on
 
-       // the first segment of a new "timeline." Because of this, while DASH will retain that
 
-       // Period while the "timeline" exists, HLS keeps track of it via the discontinuity
 
-       // sequence, and that first segment is an indicator, but can be removed before that
 
-       // timeline is gone.
 
-       const oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex];
 
-       if (oldMatchingSegment.discontinuity && !firstNewSegment.discontinuity) {
 
-         firstNewSegment.discontinuity = true;
 
-         playlist.discontinuityStarts.unshift(0);
 
-         playlist.discontinuitySequence--;
 
-       }
 
-       updateMediaSequenceForPlaylist({
 
-         playlist,
 
-         mediaSequence: oldPlaylist.segments[oldMatchingSegmentIndex].number
 
-       });
 
-     });
 
-   };
 
-   /**
 
-    * Given an old parsed manifest object and a new parsed manifest object, updates the
 
-    * sequence and timing values within the new manifest to ensure that it lines up with the
 
-    * old.
 
-    *
 
-    * @param {Array} oldManifest - the old main manifest object
 
-    * @param {Array} newManifest - the new main manifest object
 
-    *
 
-    * @return {Object} the updated new manifest object
 
-    */
 
-   const positionManifestOnTimeline = ({
 
-     oldManifest,
 
-     newManifest
 
-   }) => {
 
-     // Starting from v4.1.2 of the IOP, section 4.4.3.3 states:
 
-     //
 
-     // "MPD@availabilityStartTime and Period@start shall not be changed over MPD updates."
 
-     //
 
-     // This was added from https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/160
 
-     //
 
-     // Because of this change, and the difficulty of supporting periods with changing start
 
-     // times, periods with changing start times are not supported. This makes the logic much
 
-     // simpler, since periods with the same start time can be considerred the same period
 
-     // across refreshes.
 
-     //
 
-     // To give an example as to the difficulty of handling periods where the start time may
 
-     // change, if a single period manifest is refreshed with another manifest with a single
 
-     // period, and both the start and end times are increased, then the only way to determine
 
-     // if it's a new period or an old one that has changed is to look through the segments of
 
-     // each playlist and determine the presentation time bounds to find a match. In addition,
 
-     // if the period start changed to exceed the old period end, then there would be no
 
-     // match, and it would not be possible to determine whether the refreshed period is a new
 
-     // one or the old one.
 
-     const oldPlaylists = oldManifest.playlists.concat(getMediaGroupPlaylists(oldManifest));
 
-     const newPlaylists = newManifest.playlists.concat(getMediaGroupPlaylists(newManifest)); // Save all seen timelineStarts to the new manifest. Although this potentially means that
 
-     // there's a "memory leak" in that it will never stop growing, in reality, only a couple
 
-     // of properties are saved for each seen Period. Even long running live streams won't
 
-     // generate too many Periods, unless the stream is watched for decades. In the future,
 
-     // this can be optimized by mapping to discontinuity sequence numbers for each timeline,
 
-     // but it may not become an issue, and the additional info can be useful for debugging.
 
-     newManifest.timelineStarts = getUniqueTimelineStarts([oldManifest.timelineStarts, newManifest.timelineStarts]);
 
-     updateSequenceNumbers({
 
-       oldPlaylists,
 
-       newPlaylists,
 
-       timelineStarts: newManifest.timelineStarts
 
-     });
 
-     return newManifest;
 
-   };
 
-   const generateSidxKey = sidx => sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange);
 
-   const mergeDiscontiguousPlaylists = playlists => {
 
-     const mergedPlaylists = values(playlists.reduce((acc, playlist) => {
 
-       // assuming playlist IDs are the same across periods
 
-       // TODO: handle multiperiod where representation sets are not the same
 
-       // across periods
 
-       const name = playlist.attributes.id + (playlist.attributes.lang || '');
 
-       if (!acc[name]) {
 
-         // First Period
 
-         acc[name] = playlist;
 
-         acc[name].attributes.timelineStarts = [];
 
-       } else {
 
-         // Subsequent Periods
 
-         if (playlist.segments) {
 
-           // first segment of subsequent periods signal a discontinuity
 
-           if (playlist.segments[0]) {
 
-             playlist.segments[0].discontinuity = true;
 
-           }
 
-           acc[name].segments.push(...playlist.segments);
 
-         } // bubble up contentProtection, this assumes all DRM content
 
-         // has the same contentProtection
 
-         if (playlist.attributes.contentProtection) {
 
-           acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
 
-         }
 
-       }
 
-       acc[name].attributes.timelineStarts.push({
 
-         // Although they represent the same number, it's important to have both to make it
 
-         // compatible with HLS potentially having a similar attribute.
 
-         start: playlist.attributes.periodStart,
 
-         timeline: playlist.attributes.periodStart
 
-       });
 
-       return acc;
 
-     }, {}));
 
-     return mergedPlaylists.map(playlist => {
 
-       playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity');
 
-       return playlist;
 
-     });
 
-   };
 
-   const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => {
 
-     const sidxKey = generateSidxKey(playlist.sidx);
 
-     const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
 
-     if (sidxMatch) {
 
-       addSidxSegmentsToPlaylist$1(playlist, sidxMatch, playlist.sidx.resolvedUri);
 
-     }
 
-     return playlist;
 
-   };
 
-   const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => {
 
-     if (!Object.keys(sidxMapping).length) {
 
-       return playlists;
 
-     }
 
-     for (const i in playlists) {
 
-       playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
 
-     }
 
-     return playlists;
 
-   };
 
-   const formatAudioPlaylist = ({
 
-     attributes,
 
-     segments,
 
-     sidx,
 
-     mediaSequence,
 
-     discontinuitySequence,
 
-     discontinuityStarts
 
-   }, isAudioOnly) => {
 
-     const playlist = {
 
-       attributes: {
 
-         NAME: attributes.id,
 
-         BANDWIDTH: attributes.bandwidth,
 
-         CODECS: attributes.codecs,
 
-         ['PROGRAM-ID']: 1
 
-       },
 
-       uri: '',
 
-       endList: attributes.type === 'static',
 
-       timeline: attributes.periodStart,
 
-       resolvedUri: '',
 
-       targetDuration: attributes.duration,
 
-       discontinuitySequence,
 
-       discontinuityStarts,
 
-       timelineStarts: attributes.timelineStarts,
 
-       mediaSequence,
 
-       segments
 
-     };
 
-     if (attributes.contentProtection) {
 
-       playlist.contentProtection = attributes.contentProtection;
 
-     }
 
-     if (sidx) {
 
-       playlist.sidx = sidx;
 
-     }
 
-     if (isAudioOnly) {
 
-       playlist.attributes.AUDIO = 'audio';
 
-       playlist.attributes.SUBTITLES = 'subs';
 
-     }
 
-     return playlist;
 
-   };
 
-   const formatVttPlaylist = ({
 
-     attributes,
 
-     segments,
 
-     mediaSequence,
 
-     discontinuityStarts,
 
-     discontinuitySequence
 
-   }) => {
 
-     if (typeof segments === 'undefined') {
 
-       // vtt tracks may use single file in BaseURL
 
-       segments = [{
 
-         uri: attributes.baseUrl,
 
-         timeline: attributes.periodStart,
 
-         resolvedUri: attributes.baseUrl || '',
 
-         duration: attributes.sourceDuration,
 
-         number: 0
 
-       }]; // targetDuration should be the same duration as the only segment
 
-       attributes.duration = attributes.sourceDuration;
 
-     }
 
-     const m3u8Attributes = {
 
-       NAME: attributes.id,
 
-       BANDWIDTH: attributes.bandwidth,
 
-       ['PROGRAM-ID']: 1
 
-     };
 
-     if (attributes.codecs) {
 
-       m3u8Attributes.CODECS = attributes.codecs;
 
-     }
 
-     return {
 
-       attributes: m3u8Attributes,
 
-       uri: '',
 
-       endList: attributes.type === 'static',
 
-       timeline: attributes.periodStart,
 
-       resolvedUri: attributes.baseUrl || '',
 
-       targetDuration: attributes.duration,
 
-       timelineStarts: attributes.timelineStarts,
 
-       discontinuityStarts,
 
-       discontinuitySequence,
 
-       mediaSequence,
 
-       segments
 
-     };
 
-   };
 
-   const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => {
 
-     let mainPlaylist;
 
-     const formattedPlaylists = playlists.reduce((a, playlist) => {
 
-       const role = playlist.attributes.role && playlist.attributes.role.value || '';
 
-       const language = playlist.attributes.lang || '';
 
-       let label = playlist.attributes.label || 'main';
 
-       if (language && !playlist.attributes.label) {
 
-         const roleLabel = role ? ` (${role})` : '';
 
-         label = `${playlist.attributes.lang}${roleLabel}`;
 
-       }
 
-       if (!a[label]) {
 
-         a[label] = {
 
-           language,
 
-           autoselect: true,
 
-           default: role === 'main',
 
-           playlists: [],
 
-           uri: ''
 
-         };
 
-       }
 
-       const formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
 
-       a[label].playlists.push(formatted);
 
-       if (typeof mainPlaylist === 'undefined' && role === 'main') {
 
-         mainPlaylist = playlist;
 
-         mainPlaylist.default = true;
 
-       }
 
-       return a;
 
-     }, {}); // if no playlists have role "main", mark the first as main
 
-     if (!mainPlaylist) {
 
-       const firstLabel = Object.keys(formattedPlaylists)[0];
 
-       formattedPlaylists[firstLabel].default = true;
 
-     }
 
-     return formattedPlaylists;
 
-   };
 
-   const organizeVttPlaylists = (playlists, sidxMapping = {}) => {
 
-     return playlists.reduce((a, playlist) => {
 
-       const label = playlist.attributes.lang || 'text';
 
-       if (!a[label]) {
 
-         a[label] = {
 
-           language: label,
 
-           default: false,
 
-           autoselect: false,
 
-           playlists: [],
 
-           uri: ''
 
-         };
 
-       }
 
-       a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
 
-       return a;
 
-     }, {});
 
-   };
 
-   const organizeCaptionServices = captionServices => captionServices.reduce((svcObj, svc) => {
 
-     if (!svc) {
 
-       return svcObj;
 
-     }
 
-     svc.forEach(service => {
 
-       const {
 
-         channel,
 
-         language
 
-       } = service;
 
-       svcObj[language] = {
 
-         autoselect: false,
 
-         default: false,
 
-         instreamId: channel,
 
-         language
 
-       };
 
-       if (service.hasOwnProperty('aspectRatio')) {
 
-         svcObj[language].aspectRatio = service.aspectRatio;
 
-       }
 
-       if (service.hasOwnProperty('easyReader')) {
 
-         svcObj[language].easyReader = service.easyReader;
 
-       }
 
-       if (service.hasOwnProperty('3D')) {
 
-         svcObj[language]['3D'] = service['3D'];
 
-       }
 
-     });
 
-     return svcObj;
 
-   }, {});
 
-   const formatVideoPlaylist = ({
 
-     attributes,
 
-     segments,
 
-     sidx,
 
-     discontinuityStarts
 
-   }) => {
 
-     const playlist = {
 
-       attributes: {
 
-         NAME: attributes.id,
 
-         AUDIO: 'audio',
 
-         SUBTITLES: 'subs',
 
-         RESOLUTION: {
 
-           width: attributes.width,
 
-           height: attributes.height
 
-         },
 
-         CODECS: attributes.codecs,
 
-         BANDWIDTH: attributes.bandwidth,
 
-         ['PROGRAM-ID']: 1
 
-       },
 
-       uri: '',
 
-       endList: attributes.type === 'static',
 
-       timeline: attributes.periodStart,
 
-       resolvedUri: '',
 
-       targetDuration: attributes.duration,
 
-       discontinuityStarts,
 
-       timelineStarts: attributes.timelineStarts,
 
-       segments
 
-     };
 
-     if (attributes.frameRate) {
 
-       playlist.attributes['FRAME-RATE'] = attributes.frameRate;
 
-     }
 
-     if (attributes.contentProtection) {
 
-       playlist.contentProtection = attributes.contentProtection;
 
-     }
 
-     if (sidx) {
 
-       playlist.sidx = sidx;
 
-     }
 
-     return playlist;
 
-   };
 
-   const videoOnly = ({
 
-     attributes
 
-   }) => attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
 
-   const audioOnly = ({
 
-     attributes
 
-   }) => attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
 
-   const vttOnly = ({
 
-     attributes
 
-   }) => attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
 
-   /**
 
-    * Contains start and timeline properties denoting a timeline start. For DASH, these will
 
-    * be the same number.
 
-    *
 
-    * @typedef {Object} TimelineStart
 
-    * @property {number} start - the start time of the timeline
 
-    * @property {number} timeline - the timeline number
 
-    */
 
-   /**
 
-    * Adds appropriate media and discontinuity sequence values to the segments and playlists.
 
-    *
 
-    * Throughout mpd-parser, the `number` attribute is used in relation to `startNumber`, a
 
-    * DASH specific attribute used in constructing segment URI's from templates. However, from
 
-    * an HLS perspective, the `number` attribute on a segment would be its `mediaSequence`
 
-    * value, which should start at the original media sequence value (or 0) and increment by 1
 
-    * for each segment thereafter. Since DASH's `startNumber` values are independent per
 
-    * period, it doesn't make sense to use it for `number`. Instead, assume everything starts
 
-    * from a 0 mediaSequence value and increment from there.
 
-    *
 
-    * Note that VHS currently doesn't use the `number` property, but it can be helpful for
 
-    * debugging and making sense of the manifest.
 
-    *
 
-    * For live playlists, to account for values increasing in manifests when periods are
 
-    * removed on refreshes, merging logic should be used to update the numbers to their
 
-    * appropriate values (to ensure they're sequential and increasing).
 
-    *
 
-    * @param {Object[]} playlists - the playlists to update
 
-    * @param {TimelineStart[]} timelineStarts - the timeline starts for the manifest
 
-    */
 
-   const addMediaSequenceValues = (playlists, timelineStarts) => {
 
-     // increment all segments sequentially
 
-     playlists.forEach(playlist => {
 
-       playlist.mediaSequence = 0;
 
-       playlist.discontinuitySequence = timelineStarts.findIndex(function ({
 
-         timeline
 
-       }) {
 
-         return timeline === playlist.timeline;
 
-       });
 
-       if (!playlist.segments) {
 
-         return;
 
-       }
 
-       playlist.segments.forEach((segment, index) => {
 
-         segment.number = index;
 
-       });
 
-     });
 
-   };
 
-   /**
 
-    * Given a media group object, flattens all playlists within the media group into a single
 
-    * array.
 
-    *
 
-    * @param {Object} mediaGroupObject - the media group object
 
-    *
 
-    * @return {Object[]}
 
-    *         The media group playlists
 
-    */
 
-   const flattenMediaGroupPlaylists = mediaGroupObject => {
 
-     if (!mediaGroupObject) {
 
-       return [];
 
-     }
 
-     return Object.keys(mediaGroupObject).reduce((acc, label) => {
 
-       const labelContents = mediaGroupObject[label];
 
-       return acc.concat(labelContents.playlists);
 
-     }, []);
 
-   };
 
-   const toM3u8 = ({
 
-     dashPlaylists,
 
-     locations,
 
-     sidxMapping = {},
 
-     previousManifest
 
-   }) => {
 
-     if (!dashPlaylists.length) {
 
-       return {};
 
-     } // grab all main manifest attributes
 
-     const {
 
-       sourceDuration: duration,
 
-       type,
 
-       suggestedPresentationDelay,
 
-       minimumUpdatePeriod
 
-     } = dashPlaylists[0].attributes;
 
-     const videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
 
-     const audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
 
-     const vttPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(vttOnly));
 
-     const captions = dashPlaylists.map(playlist => playlist.attributes.captionServices).filter(Boolean);
 
-     const manifest = {
 
-       allowCache: true,
 
-       discontinuityStarts: [],
 
-       segments: [],
 
-       endList: true,
 
-       mediaGroups: {
 
-         AUDIO: {},
 
-         VIDEO: {},
 
-         ['CLOSED-CAPTIONS']: {},
 
-         SUBTITLES: {}
 
-       },
 
-       uri: '',
 
-       duration,
 
-       playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping)
 
-     };
 
-     if (minimumUpdatePeriod >= 0) {
 
-       manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
 
-     }
 
-     if (locations) {
 
-       manifest.locations = locations;
 
-     }
 
-     if (type === 'dynamic') {
 
-       manifest.suggestedPresentationDelay = suggestedPresentationDelay;
 
-     }
 
-     const isAudioOnly = manifest.playlists.length === 0;
 
-     const organizedAudioGroup = audioPlaylists.length ? organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly) : null;
 
-     const organizedVttGroup = vttPlaylists.length ? organizeVttPlaylists(vttPlaylists, sidxMapping) : null;
 
-     const formattedPlaylists = videoPlaylists.concat(flattenMediaGroupPlaylists(organizedAudioGroup), flattenMediaGroupPlaylists(organizedVttGroup));
 
-     const playlistTimelineStarts = formattedPlaylists.map(({
 
-       timelineStarts
 
-     }) => timelineStarts);
 
-     manifest.timelineStarts = getUniqueTimelineStarts(playlistTimelineStarts);
 
-     addMediaSequenceValues(formattedPlaylists, manifest.timelineStarts);
 
-     if (organizedAudioGroup) {
 
-       manifest.mediaGroups.AUDIO.audio = organizedAudioGroup;
 
-     }
 
-     if (organizedVttGroup) {
 
-       manifest.mediaGroups.SUBTITLES.subs = organizedVttGroup;
 
-     }
 
-     if (captions.length) {
 
-       manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
 
-     }
 
-     if (previousManifest) {
 
-       return positionManifestOnTimeline({
 
-         oldManifest: previousManifest,
 
-         newManifest: manifest
 
-       });
 
-     }
 
-     return manifest;
 
-   };
 
-   /**
 
-    * Calculates the R (repetition) value for a live stream (for the final segment
 
-    * in a manifest where the r value is negative 1)
 
-    *
 
-    * @param {Object} attributes
 
-    *        Object containing all inherited attributes from parent elements with attribute
 
-    *        names as keys
 
-    * @param {number} time
 
-    *        current time (typically the total time up until the final segment)
 
-    * @param {number} duration
 
-    *        duration property for the given <S />
 
-    *
 
-    * @return {number}
 
-    *        R value to reach the end of the given period
 
-    */
 
-   const getLiveRValue = (attributes, time, duration) => {
 
-     const {
 
-       NOW,
 
-       clientOffset,
 
-       availabilityStartTime,
 
-       timescale = 1,
 
-       periodStart = 0,
 
-       minimumUpdatePeriod = 0
 
-     } = attributes;
 
-     const now = (NOW + clientOffset) / 1000;
 
-     const periodStartWC = availabilityStartTime + periodStart;
 
-     const periodEndWC = now + minimumUpdatePeriod;
 
-     const periodDuration = periodEndWC - periodStartWC;
 
-     return Math.ceil((periodDuration * timescale - time) / duration);
 
-   };
 
-   /**
 
-    * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
 
-    * timing and duration
 
-    *
 
-    * @param {Object} attributes
 
-    *        Object containing all inherited attributes from parent elements with attribute
 
-    *        names as keys
 
-    * @param {Object[]} segmentTimeline
 
-    *        List of objects representing the attributes of each S element contained within
 
-    *
 
-    * @return {{number: number, duration: number, time: number, timeline: number}[]}
 
-    *         List of Objects with segment timing and duration info
 
-    */
 
-   const parseByTimeline = (attributes, segmentTimeline) => {
 
-     const {
 
-       type,
 
-       minimumUpdatePeriod = 0,
 
-       media = '',
 
-       sourceDuration,
 
-       timescale = 1,
 
-       startNumber = 1,
 
-       periodStart: timeline
 
-     } = attributes;
 
-     const segments = [];
 
-     let time = -1;
 
-     for (let sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
 
-       const S = segmentTimeline[sIndex];
 
-       const duration = S.d;
 
-       const repeat = S.r || 0;
 
-       const segmentTime = S.t || 0;
 
-       if (time < 0) {
 
-         // first segment
 
-         time = segmentTime;
 
-       }
 
-       if (segmentTime && segmentTime > time) {
 
-         // discontinuity
 
-         // TODO: How to handle this type of discontinuity
 
-         // timeline++ here would treat it like HLS discontuity and content would
 
-         // get appended without gap
 
-         // E.G.
 
-         //  <S t="0" d="1" />
 
-         //  <S d="1" />
 
-         //  <S d="1" />
 
-         //  <S t="5" d="1" />
 
-         // would have $Time$ values of [0, 1, 2, 5]
 
-         // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
 
-         // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
 
-         // does the value of sourceDuration consider this when calculating arbitrary
 
-         // negative @r repeat value?
 
-         // E.G. Same elements as above with this added at the end
 
-         //  <S d="1" r="-1" />
 
-         //  with a sourceDuration of 10
 
-         // Would the 2 gaps be included in the time duration calculations resulting in
 
-         // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
 
-         // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
 
-         time = segmentTime;
 
-       }
 
-       let count;
 
-       if (repeat < 0) {
 
-         const nextS = sIndex + 1;
 
-         if (nextS === segmentTimeline.length) {
 
-           // last segment
 
-           if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
 
-             count = getLiveRValue(attributes, time, duration);
 
-           } else {
 
-             // TODO: This may be incorrect depending on conclusion of TODO above
 
-             count = (sourceDuration * timescale - time) / duration;
 
-           }
 
-         } else {
 
-           count = (segmentTimeline[nextS].t - time) / duration;
 
-         }
 
-       } else {
 
-         count = repeat + 1;
 
-       }
 
-       const end = startNumber + segments.length + count;
 
-       let number = startNumber + segments.length;
 
-       while (number < end) {
 
-         segments.push({
 
-           number,
 
-           duration: duration / timescale,
 
-           time,
 
-           timeline
 
-         });
 
-         time += duration;
 
-         number++;
 
-       }
 
-     }
 
-     return segments;
 
-   };
 
-   const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
 
-   /**
 
-    * Replaces template identifiers with corresponding values. To be used as the callback
 
-    * for String.prototype.replace
 
-    *
 
-    * @name replaceCallback
 
-    * @function
 
-    * @param {string} match
 
-    *        Entire match of identifier
 
-    * @param {string} identifier
 
-    *        Name of matched identifier
 
-    * @param {string} format
 
-    *        Format tag string. Its presence indicates that padding is expected
 
-    * @param {string} width
 
-    *        Desired length of the replaced value. Values less than this width shall be left
 
-    *        zero padded
 
-    * @return {string}
 
-    *         Replacement for the matched identifier
 
-    */
 
-   /**
 
-    * Returns a function to be used as a callback for String.prototype.replace to replace
 
-    * template identifiers
 
-    *
 
-    * @param {Obect} values
 
-    *        Object containing values that shall be used to replace known identifiers
 
-    * @param {number} values.RepresentationID
 
-    *        Value of the Representation@id attribute
 
-    * @param {number} values.Number
 
-    *        Number of the corresponding segment
 
-    * @param {number} values.Bandwidth
 
-    *        Value of the Representation@bandwidth attribute.
 
-    * @param {number} values.Time
 
-    *        Timestamp value of the corresponding segment
 
-    * @return {replaceCallback}
 
-    *         Callback to be used with String.prototype.replace to replace identifiers
 
-    */
 
-   const identifierReplacement = values => (match, identifier, format, width) => {
 
-     if (match === '$$') {
 
-       // escape sequence
 
-       return '$';
 
-     }
 
-     if (typeof values[identifier] === 'undefined') {
 
-       return match;
 
-     }
 
-     const value = '' + values[identifier];
 
-     if (identifier === 'RepresentationID') {
 
-       // Format tag shall not be present with RepresentationID
 
-       return value;
 
-     }
 
-     if (!format) {
 
-       width = 1;
 
-     } else {
 
-       width = parseInt(width, 10);
 
-     }
 
-     if (value.length >= width) {
 
-       return value;
 
-     }
 
-     return `${new Array(width - value.length + 1).join('0')}${value}`;
 
-   };
 
-   /**
 
-    * Constructs a segment url from a template string
 
-    *
 
-    * @param {string} url
 
-    *        Template string to construct url from
 
-    * @param {Obect} values
 
-    *        Object containing values that shall be used to replace known identifiers
 
-    * @param {number} values.RepresentationID
 
-    *        Value of the Representation@id attribute
 
-    * @param {number} values.Number
 
-    *        Number of the corresponding segment
 
-    * @param {number} values.Bandwidth
 
-    *        Value of the Representation@bandwidth attribute.
 
-    * @param {number} values.Time
 
-    *        Timestamp value of the corresponding segment
 
-    * @return {string}
 
-    *         Segment url with identifiers replaced
 
-    */
 
-   const constructTemplateUrl = (url, values) => url.replace(identifierPattern, identifierReplacement(values));
 
-   /**
 
-    * Generates a list of objects containing timing and duration information about each
 
-    * segment needed to generate segment uris and the complete segment object
 
-    *
 
-    * @param {Object} attributes
 
-    *        Object containing all inherited attributes from parent elements with attribute
 
-    *        names as keys
 
-    * @param {Object[]|undefined} segmentTimeline
 
-    *        List of objects representing the attributes of each S element contained within
 
-    *        the SegmentTimeline element
 
-    * @return {{number: number, duration: number, time: number, timeline: number}[]}
 
-    *         List of Objects with segment timing and duration info
 
-    */
 
-   const parseTemplateInfo = (attributes, segmentTimeline) => {
 
-     if (!attributes.duration && !segmentTimeline) {
 
-       // if neither @duration or SegmentTimeline are present, then there shall be exactly
 
-       // one media segment
 
-       return [{
 
-         number: attributes.startNumber || 1,
 
-         duration: attributes.sourceDuration,
 
-         time: 0,
 
-         timeline: attributes.periodStart
 
-       }];
 
-     }
 
-     if (attributes.duration) {
 
-       return parseByDuration(attributes);
 
-     }
 
-     return parseByTimeline(attributes, segmentTimeline);
 
-   };
 
-   /**
 
-    * Generates a list of segments using information provided by the SegmentTemplate element
 
-    *
 
-    * @param {Object} attributes
 
-    *        Object containing all inherited attributes from parent elements with attribute
 
-    *        names as keys
 
-    * @param {Object[]|undefined} segmentTimeline
 
-    *        List of objects representing the attributes of each S element contained within
 
-    *        the SegmentTimeline element
 
-    * @return {Object[]}
 
-    *         List of segment objects
 
-    */
 
-   const segmentsFromTemplate = (attributes, segmentTimeline) => {
 
-     const templateValues = {
 
-       RepresentationID: attributes.id,
 
-       Bandwidth: attributes.bandwidth || 0
 
-     };
 
-     const {
 
-       initialization = {
 
-         sourceURL: '',
 
-         range: ''
 
-       }
 
-     } = attributes;
 
-     const mapSegment = urlTypeToSegment({
 
-       baseUrl: attributes.baseUrl,
 
-       source: constructTemplateUrl(initialization.sourceURL, templateValues),
 
-       range: initialization.range
 
-     });
 
-     const segments = parseTemplateInfo(attributes, segmentTimeline);
 
-     return segments.map(segment => {
 
-       templateValues.Number = segment.number;
 
-       templateValues.Time = segment.time;
 
-       const uri = constructTemplateUrl(attributes.media || '', templateValues); // See DASH spec section 5.3.9.2.2
 
-       // - if timescale isn't present on any level, default to 1.
 
-       const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
 
-       const presentationTimeOffset = attributes.presentationTimeOffset || 0;
 
-       const presentationTime =
 
-       // Even if the @t attribute is not specified for the segment, segment.time is
 
-       // calculated in mpd-parser prior to this, so it's assumed to be available.
 
-       attributes.periodStart + (segment.time - presentationTimeOffset) / timescale;
 
-       const map = {
 
-         uri,
 
-         timeline: segment.timeline,
 
-         duration: segment.duration,
 
-         resolvedUri: resolveUrl$1(attributes.baseUrl || '', uri),
 
-         map: mapSegment,
 
-         number: segment.number,
 
-         presentationTime
 
-       };
 
-       return map;
 
-     });
 
-   };
 
-   /**
 
-    * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
 
-    * to an object that matches the output of a segment in videojs/mpd-parser
 
-    *
 
-    * @param {Object} attributes
 
-    *   Object containing all inherited attributes from parent elements with attribute
 
-    *   names as keys
 
-    * @param {Object} segmentUrl
 
-    *   <SegmentURL> node to translate into a segment object
 
-    * @return {Object} translated segment object
 
-    */
 
-   const SegmentURLToSegmentObject = (attributes, segmentUrl) => {
 
-     const {
 
-       baseUrl,
 
-       initialization = {}
 
-     } = attributes;
 
-     const initSegment = urlTypeToSegment({
 
-       baseUrl,
 
-       source: initialization.sourceURL,
 
-       range: initialization.range
 
-     });
 
-     const segment = urlTypeToSegment({
 
-       baseUrl,
 
-       source: segmentUrl.media,
 
-       range: segmentUrl.mediaRange
 
-     });
 
-     segment.map = initSegment;
 
-     return segment;
 
-   };
 
-   /**
 
-    * Generates a list of segments using information provided by the SegmentList element
 
-    * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
 
-    * node should be translated into segment.
 
-    *
 
-    * @param {Object} attributes
 
-    *   Object containing all inherited attributes from parent elements with attribute
 
-    *   names as keys
 
-    * @param {Object[]|undefined} segmentTimeline
 
-    *        List of objects representing the attributes of each S element contained within
 
-    *        the SegmentTimeline element
 
-    * @return {Object.<Array>} list of segments
 
-    */
 
-   const segmentsFromList = (attributes, segmentTimeline) => {
 
-     const {
 
-       duration,
 
-       segmentUrls = [],
 
-       periodStart
 
-     } = attributes; // Per spec (5.3.9.2.1) no way to determine segment duration OR
 
-     // if both SegmentTimeline and @duration are defined, it is outside of spec.
 
-     if (!duration && !segmentTimeline || duration && segmentTimeline) {
 
-       throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
 
-     }
 
-     const segmentUrlMap = segmentUrls.map(segmentUrlObject => SegmentURLToSegmentObject(attributes, segmentUrlObject));
 
-     let segmentTimeInfo;
 
-     if (duration) {
 
-       segmentTimeInfo = parseByDuration(attributes);
 
-     }
 
-     if (segmentTimeline) {
 
-       segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
 
-     }
 
-     const segments = segmentTimeInfo.map((segmentTime, index) => {
 
-       if (segmentUrlMap[index]) {
 
-         const segment = segmentUrlMap[index]; // See DASH spec section 5.3.9.2.2
 
-         // - if timescale isn't present on any level, default to 1.
 
-         const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
 
-         const presentationTimeOffset = attributes.presentationTimeOffset || 0;
 
-         segment.timeline = segmentTime.timeline;
 
-         segment.duration = segmentTime.duration;
 
-         segment.number = segmentTime.number;
 
-         segment.presentationTime = periodStart + (segmentTime.time - presentationTimeOffset) / timescale;
 
-         return segment;
 
-       } // Since we're mapping we should get rid of any blank segments (in case
 
-       // the given SegmentTimeline is handling for more elements than we have
 
-       // SegmentURLs for).
 
-     }).filter(segment => segment);
 
-     return segments;
 
-   };
 
-   const generateSegments = ({
 
-     attributes,
 
-     segmentInfo
 
-   }) => {
 
-     let segmentAttributes;
 
-     let segmentsFn;
 
-     if (segmentInfo.template) {
 
-       segmentsFn = segmentsFromTemplate;
 
-       segmentAttributes = merge$1(attributes, segmentInfo.template);
 
-     } else if (segmentInfo.base) {
 
-       segmentsFn = segmentsFromBase;
 
-       segmentAttributes = merge$1(attributes, segmentInfo.base);
 
-     } else if (segmentInfo.list) {
 
-       segmentsFn = segmentsFromList;
 
-       segmentAttributes = merge$1(attributes, segmentInfo.list);
 
-     }
 
-     const segmentsInfo = {
 
-       attributes
 
-     };
 
-     if (!segmentsFn) {
 
-       return segmentsInfo;
 
-     }
 
-     const segments = segmentsFn(segmentAttributes, segmentInfo.segmentTimeline); // The @duration attribute will be used to determin the playlist's targetDuration which
 
-     // must be in seconds. Since we've generated the segment list, we no longer need
 
-     // @duration to be in @timescale units, so we can convert it here.
 
-     if (segmentAttributes.duration) {
 
-       const {
 
-         duration,
 
-         timescale = 1
 
-       } = segmentAttributes;
 
-       segmentAttributes.duration = duration / timescale;
 
-     } else if (segments.length) {
 
-       // if there is no @duration attribute, use the largest segment duration as
 
-       // as target duration
 
-       segmentAttributes.duration = segments.reduce((max, segment) => {
 
-         return Math.max(max, Math.ceil(segment.duration));
 
-       }, 0);
 
-     } else {
 
-       segmentAttributes.duration = 0;
 
-     }
 
-     segmentsInfo.attributes = segmentAttributes;
 
-     segmentsInfo.segments = segments; // This is a sidx box without actual segment information
 
-     if (segmentInfo.base && segmentAttributes.indexRange) {
 
-       segmentsInfo.sidx = segments[0];
 
-       segmentsInfo.segments = [];
 
-     }
 
-     return segmentsInfo;
 
-   };
 
-   const toPlaylists = representations => representations.map(generateSegments);
 
-   const findChildren = (element, name) => from(element.childNodes).filter(({
 
-     tagName
 
-   }) => tagName === name);
 
-   const getContent = element => element.textContent.trim();
 
-   /**
 
-    * Converts the provided string that may contain a division operation to a number.
 
-    *
 
-    * @param {string} value - the provided string value
 
-    *
 
-    * @return {number} the parsed string value
 
-    */
 
-   const parseDivisionValue = value => {
 
-     return parseFloat(value.split('/').reduce((prev, current) => prev / current));
 
-   };
 
-   const parseDuration = str => {
 
-     const SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
 
-     const SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
 
-     const SECONDS_IN_DAY = 24 * 60 * 60;
 
-     const SECONDS_IN_HOUR = 60 * 60;
 
-     const SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
 
-     const durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
 
-     const match = durationRegex.exec(str);
 
-     if (!match) {
 
-       return 0;
 
-     }
 
-     const [year, month, day, hour, minute, second] = match.slice(1);
 
-     return parseFloat(year || 0) * SECONDS_IN_YEAR + parseFloat(month || 0) * SECONDS_IN_MONTH + parseFloat(day || 0) * SECONDS_IN_DAY + parseFloat(hour || 0) * SECONDS_IN_HOUR + parseFloat(minute || 0) * SECONDS_IN_MIN + parseFloat(second || 0);
 
-   };
 
-   const parseDate = str => {
 
-     // Date format without timezone according to ISO 8601
 
-     // YYY-MM-DDThh:mm:ss.ssssss
 
-     const dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is
 
-     // expressed by ending with 'Z'
 
-     if (dateRegex.test(str)) {
 
-       str += 'Z';
 
-     }
 
-     return Date.parse(str);
 
-   };
 
-   const parsers = {
 
-     /**
 
-      * Specifies the duration of the entire Media Presentation. Format is a duration string
 
-      * as specified in ISO 8601
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The duration in seconds
 
-      */
 
-     mediaPresentationDuration(value) {
 
-       return parseDuration(value);
 
-     },
 
-     /**
 
-      * Specifies the Segment availability start time for all Segments referred to in this
 
-      * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
 
-      * time. Format is a date string as specified in ISO 8601
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The date as seconds from unix epoch
 
-      */
 
-     availabilityStartTime(value) {
 
-       return parseDate(value) / 1000;
 
-     },
 
-     /**
 
-      * Specifies the smallest period between potential changes to the MPD. Format is a
 
-      * duration string as specified in ISO 8601
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The duration in seconds
 
-      */
 
-     minimumUpdatePeriod(value) {
 
-       return parseDuration(value);
 
-     },
 
-     /**
 
-      * Specifies the suggested presentation delay. Format is a
 
-      * duration string as specified in ISO 8601
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The duration in seconds
 
-      */
 
-     suggestedPresentationDelay(value) {
 
-       return parseDuration(value);
 
-     },
 
-     /**
 
-      * specifices the type of mpd. Can be either "static" or "dynamic"
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      *
 
-      * @return {string}
 
-      *         The type as a string
 
-      */
 
-     type(value) {
 
-       return value;
 
-     },
 
-     /**
 
-      * Specifies the duration of the smallest time shifting buffer for any Representation
 
-      * in the MPD. Format is a duration string as specified in ISO 8601
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The duration in seconds
 
-      */
 
-     timeShiftBufferDepth(value) {
 
-       return parseDuration(value);
 
-     },
 
-     /**
 
-      * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
 
-      * Format is a duration string as specified in ISO 8601
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The duration in seconds
 
-      */
 
-     start(value) {
 
-       return parseDuration(value);
 
-     },
 
-     /**
 
-      * Specifies the width of the visual presentation
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed width
 
-      */
 
-     width(value) {
 
-       return parseInt(value, 10);
 
-     },
 
-     /**
 
-      * Specifies the height of the visual presentation
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed height
 
-      */
 
-     height(value) {
 
-       return parseInt(value, 10);
 
-     },
 
-     /**
 
-      * Specifies the bitrate of the representation
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed bandwidth
 
-      */
 
-     bandwidth(value) {
 
-       return parseInt(value, 10);
 
-     },
 
-     /**
 
-      * Specifies the frame rate of the representation
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed frame rate
 
-      */
 
-     frameRate(value) {
 
-       return parseDivisionValue(value);
 
-     },
 
-     /**
 
-      * Specifies the number of the first Media Segment in this Representation in the Period
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed number
 
-      */
 
-     startNumber(value) {
 
-       return parseInt(value, 10);
 
-     },
 
-     /**
 
-      * Specifies the timescale in units per seconds
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed timescale
 
-      */
 
-     timescale(value) {
 
-       return parseInt(value, 10);
 
-     },
 
-     /**
 
-      * Specifies the presentationTimeOffset.
 
-      *
 
-      * @param {string} value
 
-      *        value of the attribute as a string
 
-      *
 
-      * @return {number}
 
-      *         The parsed presentationTimeOffset
 
-      */
 
-     presentationTimeOffset(value) {
 
-       return parseInt(value, 10);
 
-     },
 
-     /**
 
-      * Specifies the constant approximate Segment duration
 
-      * NOTE: The <Period> element also contains an @duration attribute. This duration
 
-      *       specifies the duration of the Period. This attribute is currently not
 
-      *       supported by the rest of the parser, however we still check for it to prevent
 
-      *       errors.
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed duration
 
-      */
 
-     duration(value) {
 
-       const parsedValue = parseInt(value, 10);
 
-       if (isNaN(parsedValue)) {
 
-         return parseDuration(value);
 
-       }
 
-       return parsedValue;
 
-     },
 
-     /**
 
-      * Specifies the Segment duration, in units of the value of the @timescale.
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed duration
 
-      */
 
-     d(value) {
 
-       return parseInt(value, 10);
 
-     },
 
-     /**
 
-      * Specifies the MPD start time, in @timescale units, the first Segment in the series
 
-      * starts relative to the beginning of the Period
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed time
 
-      */
 
-     t(value) {
 
-       return parseInt(value, 10);
 
-     },
 
-     /**
 
-      * Specifies the repeat count of the number of following contiguous Segments with the
 
-      * same duration expressed by the value of @d
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {number}
 
-      *         The parsed number
 
-      */
 
-     r(value) {
 
-       return parseInt(value, 10);
 
-     },
 
-     /**
 
-      * Default parser for all other attributes. Acts as a no-op and just returns the value
 
-      * as a string
 
-      *
 
-      * @param {string} value
 
-      *        value of attribute as a string
 
-      * @return {string}
 
-      *         Unparsed value
 
-      */
 
-     DEFAULT(value) {
 
-       return value;
 
-     }
 
-   };
 
-   /**
 
-    * Gets all the attributes and values of the provided node, parses attributes with known
 
-    * types, and returns an object with attribute names mapped to values.
 
-    *
 
-    * @param {Node} el
 
-    *        The node to parse attributes from
 
-    * @return {Object}
 
-    *         Object with all attributes of el parsed
 
-    */
 
-   const parseAttributes = el => {
 
-     if (!(el && el.attributes)) {
 
-       return {};
 
-     }
 
-     return from(el.attributes).reduce((a, e) => {
 
-       const parseFn = parsers[e.name] || parsers.DEFAULT;
 
-       a[e.name] = parseFn(e.value);
 
-       return a;
 
-     }, {});
 
-   };
 
-   const keySystemsMap = {
 
-     'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
 
-     'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
 
-     'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
 
-     'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
 
-   };
 
-   /**
 
-    * Builds a list of urls that is the product of the reference urls and BaseURL values
 
-    *
 
-    * @param {string[]} referenceUrls
 
-    *        List of reference urls to resolve to
 
-    * @param {Node[]} baseUrlElements
 
-    *        List of BaseURL nodes from the mpd
 
-    * @return {string[]}
 
-    *         List of resolved urls
 
-    */
 
-   const buildBaseUrls = (referenceUrls, baseUrlElements) => {
 
-     if (!baseUrlElements.length) {
 
-       return referenceUrls;
 
-     }
 
-     return flatten(referenceUrls.map(function (reference) {
 
-       return baseUrlElements.map(function (baseUrlElement) {
 
-         return resolveUrl$1(reference, getContent(baseUrlElement));
 
-       });
 
-     }));
 
-   };
 
-   /**
 
-    * Contains all Segment information for its containing AdaptationSet
 
-    *
 
-    * @typedef {Object} SegmentInformation
 
-    * @property {Object|undefined} template
 
-    *           Contains the attributes for the SegmentTemplate node
 
-    * @property {Object[]|undefined} segmentTimeline
 
-    *           Contains a list of atrributes for each S node within the SegmentTimeline node
 
-    * @property {Object|undefined} list
 
-    *           Contains the attributes for the SegmentList node
 
-    * @property {Object|undefined} base
 
-    *           Contains the attributes for the SegmentBase node
 
-    */
 
-   /**
 
-    * Returns all available Segment information contained within the AdaptationSet node
 
-    *
 
-    * @param {Node} adaptationSet
 
-    *        The AdaptationSet node to get Segment information from
 
-    * @return {SegmentInformation}
 
-    *         The Segment information contained within the provided AdaptationSet
 
-    */
 
-   const getSegmentInformation = adaptationSet => {
 
-     const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
 
-     const segmentList = findChildren(adaptationSet, 'SegmentList')[0];
 
-     const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(s => merge$1({
 
-       tag: 'SegmentURL'
 
-     }, parseAttributes(s)));
 
-     const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
 
-     const segmentTimelineParentNode = segmentList || segmentTemplate;
 
-     const segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
 
-     const segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
 
-     const segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
 
-     // @initialization and an <Initialization> node.  @initialization can be templated,
 
-     // while the node can have a url and range specified.  If the <SegmentTemplate> has
 
-     // both @initialization and an <Initialization> subelement we opt to override with
 
-     // the node, as this interaction is not defined in the spec.
 
-     const template = segmentTemplate && parseAttributes(segmentTemplate);
 
-     if (template && segmentInitialization) {
 
-       template.initialization = segmentInitialization && parseAttributes(segmentInitialization);
 
-     } else if (template && template.initialization) {
 
-       // If it is @initialization we convert it to an object since this is the format that
 
-       // later functions will rely on for the initialization segment.  This is only valid
 
-       // for <SegmentTemplate>
 
-       template.initialization = {
 
-         sourceURL: template.initialization
 
-       };
 
-     }
 
-     const segmentInfo = {
 
-       template,
 
-       segmentTimeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(s => parseAttributes(s)),
 
-       list: segmentList && merge$1(parseAttributes(segmentList), {
 
-         segmentUrls,
 
-         initialization: parseAttributes(segmentInitialization)
 
-       }),
 
-       base: segmentBase && merge$1(parseAttributes(segmentBase), {
 
-         initialization: parseAttributes(segmentInitialization)
 
-       })
 
-     };
 
-     Object.keys(segmentInfo).forEach(key => {
 
-       if (!segmentInfo[key]) {
 
-         delete segmentInfo[key];
 
-       }
 
-     });
 
-     return segmentInfo;
 
-   };
 
-   /**
 
-    * Contains Segment information and attributes needed to construct a Playlist object
 
-    * from a Representation
 
-    *
 
-    * @typedef {Object} RepresentationInformation
 
-    * @property {SegmentInformation} segmentInfo
 
-    *           Segment information for this Representation
 
-    * @property {Object} attributes
 
-    *           Inherited attributes for this Representation
 
-    */
 
-   /**
 
-    * Maps a Representation node to an object containing Segment information and attributes
 
-    *
 
-    * @name inheritBaseUrlsCallback
 
-    * @function
 
-    * @param {Node} representation
 
-    *        Representation node from the mpd
 
-    * @return {RepresentationInformation}
 
-    *         Representation information needed to construct a Playlist object
 
-    */
 
-   /**
 
-    * Returns a callback for Array.prototype.map for mapping Representation nodes to
 
-    * Segment information and attributes using inherited BaseURL nodes.
 
-    *
 
-    * @param {Object} adaptationSetAttributes
 
-    *        Contains attributes inherited by the AdaptationSet
 
-    * @param {string[]} adaptationSetBaseUrls
 
-    *        Contains list of resolved base urls inherited by the AdaptationSet
 
-    * @param {SegmentInformation} adaptationSetSegmentInfo
 
-    *        Contains Segment information for the AdaptationSet
 
-    * @return {inheritBaseUrlsCallback}
 
-    *         Callback map function
 
-    */
 
-   const inheritBaseUrls = (adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) => representation => {
 
-     const repBaseUrlElements = findChildren(representation, 'BaseURL');
 
-     const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
 
-     const attributes = merge$1(adaptationSetAttributes, parseAttributes(representation));
 
-     const representationSegmentInfo = getSegmentInformation(representation);
 
-     return repBaseUrls.map(baseUrl => {
 
-       return {
 
-         segmentInfo: merge$1(adaptationSetSegmentInfo, representationSegmentInfo),
 
-         attributes: merge$1(attributes, {
 
-           baseUrl
 
-         })
 
-       };
 
-     });
 
-   };
 
-   /**
 
-    * Tranforms a series of content protection nodes to
 
-    * an object containing pssh data by key system
 
-    *
 
-    * @param {Node[]} contentProtectionNodes
 
-    *        Content protection nodes
 
-    * @return {Object}
 
-    *        Object containing pssh data by key system
 
-    */
 
-   const generateKeySystemInformation = contentProtectionNodes => {
 
-     return contentProtectionNodes.reduce((acc, node) => {
 
-       const attributes = parseAttributes(node); // Although it could be argued that according to the UUID RFC spec the UUID string (a-f chars) should be generated
 
-       // as a lowercase string it also mentions it should be treated as case-insensitive on input. Since the key system
 
-       // UUIDs in the keySystemsMap are hardcoded as lowercase in the codebase there isn't any reason not to do
 
-       // .toLowerCase() on the input UUID string from the manifest (at least I could not think of one).
 
-       if (attributes.schemeIdUri) {
 
-         attributes.schemeIdUri = attributes.schemeIdUri.toLowerCase();
 
-       }
 
-       const keySystem = keySystemsMap[attributes.schemeIdUri];
 
-       if (keySystem) {
 
-         acc[keySystem] = {
 
-           attributes
 
-         };
 
-         const psshNode = findChildren(node, 'cenc:pssh')[0];
 
-         if (psshNode) {
 
-           const pssh = getContent(psshNode);
 
-           acc[keySystem].pssh = pssh && decodeB64ToUint8Array(pssh);
 
-         }
 
-       }
 
-       return acc;
 
-     }, {});
 
-   }; // defined in ANSI_SCTE 214-1 2016
 
-   const parseCaptionServiceMetadata = service => {
 
-     // 608 captions
 
-     if (service.schemeIdUri === 'urn:scte:dash:cc:cea-608:2015') {
 
-       const values = typeof service.value !== 'string' ? [] : service.value.split(';');
 
-       return values.map(value => {
 
-         let channel;
 
-         let language; // default language to value
 
-         language = value;
 
-         if (/^CC\d=/.test(value)) {
 
-           [channel, language] = value.split('=');
 
-         } else if (/^CC\d$/.test(value)) {
 
-           channel = value;
 
-         }
 
-         return {
 
-           channel,
 
-           language
 
-         };
 
-       });
 
-     } else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') {
 
-       const values = typeof service.value !== 'string' ? [] : service.value.split(';');
 
-       return values.map(value => {
 
-         const flags = {
 
-           // service or channel number 1-63
 
-           'channel': undefined,
 
-           // language is a 3ALPHA per ISO 639.2/B
 
-           // field is required
 
-           'language': undefined,
 
-           // BIT 1/0 or ?
 
-           // default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown
 
-           'aspectRatio': 1,
 
-           // BIT 1/0
 
-           // easy reader flag indicated the text is tailed to the needs of beginning readers
 
-           // default 0, or off
 
-           'easyReader': 0,
 
-           // BIT 1/0
 
-           // If 3d metadata is present (CEA-708.1) then 1
 
-           // default 0
 
-           '3D': 0
 
-         };
 
-         if (/=/.test(value)) {
 
-           const [channel, opts = ''] = value.split('=');
 
-           flags.channel = channel;
 
-           flags.language = value;
 
-           opts.split(',').forEach(opt => {
 
-             const [name, val] = opt.split(':');
 
-             if (name === 'lang') {
 
-               flags.language = val; // er for easyReadery
 
-             } else if (name === 'er') {
 
-               flags.easyReader = Number(val); // war for wide aspect ratio
 
-             } else if (name === 'war') {
 
-               flags.aspectRatio = Number(val);
 
-             } else if (name === '3D') {
 
-               flags['3D'] = Number(val);
 
-             }
 
-           });
 
-         } else {
 
-           flags.language = value;
 
-         }
 
-         if (flags.channel) {
 
-           flags.channel = 'SERVICE' + flags.channel;
 
-         }
 
-         return flags;
 
-       });
 
-     }
 
-   };
 
-   /**
 
-    * Maps an AdaptationSet node to a list of Representation information objects
 
-    *
 
-    * @name toRepresentationsCallback
 
-    * @function
 
-    * @param {Node} adaptationSet
 
-    *        AdaptationSet node from the mpd
 
-    * @return {RepresentationInformation[]}
 
-    *         List of objects containing Representaion information
 
-    */
 
-   /**
 
-    * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
 
-    * Representation information objects
 
-    *
 
-    * @param {Object} periodAttributes
 
-    *        Contains attributes inherited by the Period
 
-    * @param {string[]} periodBaseUrls
 
-    *        Contains list of resolved base urls inherited by the Period
 
-    * @param {string[]} periodSegmentInfo
 
-    *        Contains Segment Information at the period level
 
-    * @return {toRepresentationsCallback}
 
-    *         Callback map function
 
-    */
 
-   const toRepresentations = (periodAttributes, periodBaseUrls, periodSegmentInfo) => adaptationSet => {
 
-     const adaptationSetAttributes = parseAttributes(adaptationSet);
 
-     const adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
 
-     const role = findChildren(adaptationSet, 'Role')[0];
 
-     const roleAttributes = {
 
-       role: parseAttributes(role)
 
-     };
 
-     let attrs = merge$1(periodAttributes, adaptationSetAttributes, roleAttributes);
 
-     const accessibility = findChildren(adaptationSet, 'Accessibility')[0];
 
-     const captionServices = parseCaptionServiceMetadata(parseAttributes(accessibility));
 
-     if (captionServices) {
 
-       attrs = merge$1(attrs, {
 
-         captionServices
 
-       });
 
-     }
 
-     const label = findChildren(adaptationSet, 'Label')[0];
 
-     if (label && label.childNodes.length) {
 
-       const labelVal = label.childNodes[0].nodeValue.trim();
 
-       attrs = merge$1(attrs, {
 
-         label: labelVal
 
-       });
 
-     }
 
-     const contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
 
-     if (Object.keys(contentProtection).length) {
 
-       attrs = merge$1(attrs, {
 
-         contentProtection
 
-       });
 
-     }
 
-     const segmentInfo = getSegmentInformation(adaptationSet);
 
-     const representations = findChildren(adaptationSet, 'Representation');
 
-     const adaptationSetSegmentInfo = merge$1(periodSegmentInfo, segmentInfo);
 
-     return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
 
-   };
 
-   /**
 
-    * Contains all period information for mapping nodes onto adaptation sets.
 
-    *
 
-    * @typedef {Object} PeriodInformation
 
-    * @property {Node} period.node
 
-    *           Period node from the mpd
 
-    * @property {Object} period.attributes
 
-    *           Parsed period attributes from node plus any added
 
-    */
 
-   /**
 
-    * Maps a PeriodInformation object to a list of Representation information objects for all
 
-    * AdaptationSet nodes contained within the Period.
 
-    *
 
-    * @name toAdaptationSetsCallback
 
-    * @function
 
-    * @param {PeriodInformation} period
 
-    *        Period object containing necessary period information
 
-    * @param {number} periodStart
 
-    *        Start time of the Period within the mpd
 
-    * @return {RepresentationInformation[]}
 
-    *         List of objects containing Representaion information
 
-    */
 
-   /**
 
-    * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
 
-    * Representation information objects
 
-    *
 
-    * @param {Object} mpdAttributes
 
-    *        Contains attributes inherited by the mpd
 
-    * @param {string[]} mpdBaseUrls
 
-    *        Contains list of resolved base urls inherited by the mpd
 
-    * @return {toAdaptationSetsCallback}
 
-    *         Callback map function
 
-    */
 
-   const toAdaptationSets = (mpdAttributes, mpdBaseUrls) => (period, index) => {
 
-     const periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL'));
 
-     const periodAttributes = merge$1(mpdAttributes, {
 
-       periodStart: period.attributes.start
 
-     });
 
-     if (typeof period.attributes.duration === 'number') {
 
-       periodAttributes.periodDuration = period.attributes.duration;
 
-     }
 
-     const adaptationSets = findChildren(period.node, 'AdaptationSet');
 
-     const periodSegmentInfo = getSegmentInformation(period.node);
 
-     return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
 
-   };
 
-   /**
 
-    * Gets Period@start property for a given period.
 
-    *
 
-    * @param {Object} options
 
-    *        Options object
 
-    * @param {Object} options.attributes
 
-    *        Period attributes
 
-    * @param {Object} [options.priorPeriodAttributes]
 
-    *        Prior period attributes (if prior period is available)
 
-    * @param {string} options.mpdType
 
-    *        The MPD@type these periods came from
 
-    * @return {number|null}
 
-    *         The period start, or null if it's an early available period or error
 
-    */
 
-   const getPeriodStart = ({
 
-     attributes,
 
-     priorPeriodAttributes,
 
-     mpdType
 
-   }) => {
 
-     // Summary of period start time calculation from DASH spec section 5.3.2.1
 
-     //
 
-     // A period's start is the first period's start + time elapsed after playing all
 
-     // prior periods to this one. Periods continue one after the other in time (without
 
-     // gaps) until the end of the presentation.
 
-     //
 
-     // The value of Period@start should be:
 
-     // 1. if Period@start is present: value of Period@start
 
-     // 2. if previous period exists and it has @duration: previous Period@start +
 
-     //    previous Period@duration
 
-     // 3. if this is first period and MPD@type is 'static': 0
 
-     // 4. in all other cases, consider the period an "early available period" (note: not
 
-     //    currently supported)
 
-     // (1)
 
-     if (typeof attributes.start === 'number') {
 
-       return attributes.start;
 
-     } // (2)
 
-     if (priorPeriodAttributes && typeof priorPeriodAttributes.start === 'number' && typeof priorPeriodAttributes.duration === 'number') {
 
-       return priorPeriodAttributes.start + priorPeriodAttributes.duration;
 
-     } // (3)
 
-     if (!priorPeriodAttributes && mpdType === 'static') {
 
-       return 0;
 
-     } // (4)
 
-     // There is currently no logic for calculating the Period@start value if there is
 
-     // no Period@start or prior Period@start and Period@duration available. This is not made
 
-     // explicit by the DASH interop guidelines or the DASH spec, however, since there's
 
-     // nothing about any other resolution strategies, it's implied. Thus, this case should
 
-     // be considered an early available period, or error, and null should suffice for both
 
-     // of those cases.
 
-     return null;
 
-   };
 
-   /**
 
-    * Traverses the mpd xml tree to generate a list of Representation information objects
 
-    * that have inherited attributes from parent nodes
 
-    *
 
-    * @param {Node} mpd
 
-    *        The root node of the mpd
 
-    * @param {Object} options
 
-    *        Available options for inheritAttributes
 
-    * @param {string} options.manifestUri
 
-    *        The uri source of the mpd
 
-    * @param {number} options.NOW
 
-    *        Current time per DASH IOP.  Default is current time in ms since epoch
 
-    * @param {number} options.clientOffset
 
-    *        Client time difference from NOW (in milliseconds)
 
-    * @return {RepresentationInformation[]}
 
-    *         List of objects containing Representation information
 
-    */
 
-   const inheritAttributes = (mpd, options = {}) => {
 
-     const {
 
-       manifestUri = '',
 
-       NOW = Date.now(),
 
-       clientOffset = 0
 
-     } = options;
 
-     const periodNodes = findChildren(mpd, 'Period');
 
-     if (!periodNodes.length) {
 
-       throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
 
-     }
 
-     const locations = findChildren(mpd, 'Location');
 
-     const mpdAttributes = parseAttributes(mpd);
 
-     const mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'.
 
-     mpdAttributes.type = mpdAttributes.type || 'static';
 
-     mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
 
-     mpdAttributes.NOW = NOW;
 
-     mpdAttributes.clientOffset = clientOffset;
 
-     if (locations.length) {
 
-       mpdAttributes.locations = locations.map(getContent);
 
-     }
 
-     const periods = []; // Since toAdaptationSets acts on individual periods right now, the simplest approach to
 
-     // adding properties that require looking at prior periods is to parse attributes and add
 
-     // missing ones before toAdaptationSets is called. If more such properties are added, it
 
-     // may be better to refactor toAdaptationSets.
 
-     periodNodes.forEach((node, index) => {
 
-       const attributes = parseAttributes(node); // Use the last modified prior period, as it may contain added information necessary
 
-       // for this period.
 
-       const priorPeriod = periods[index - 1];
 
-       attributes.start = getPeriodStart({
 
-         attributes,
 
-         priorPeriodAttributes: priorPeriod ? priorPeriod.attributes : null,
 
-         mpdType: mpdAttributes.type
 
-       });
 
-       periods.push({
 
-         node,
 
-         attributes
 
-       });
 
-     });
 
-     return {
 
-       locations: mpdAttributes.locations,
 
-       representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)))
 
-     };
 
-   };
 
-   const stringToMpdXml = manifestString => {
 
-     if (manifestString === '') {
 
-       throw new Error(errors.DASH_EMPTY_MANIFEST);
 
-     }
 
-     const parser = new DOMParser();
 
-     let xml;
 
-     let mpd;
 
-     try {
 
-       xml = parser.parseFromString(manifestString, 'application/xml');
 
-       mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
 
-     } catch (e) {// ie 11 throwsw on invalid xml
 
-     }
 
-     if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
 
-       throw new Error(errors.DASH_INVALID_XML);
 
-     }
 
-     return mpd;
 
-   };
 
-   /**
 
-    * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
 
-    *
 
-    * @param {string} mpd
 
-    *        XML string of the MPD manifest
 
-    * @return {Object|null}
 
-    *         Attributes of UTCTiming node specified in the manifest. Null if none found
 
-    */
 
-   const parseUTCTimingScheme = mpd => {
 
-     const UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
 
-     if (!UTCTimingNode) {
 
-       return null;
 
-     }
 
-     const attributes = parseAttributes(UTCTimingNode);
 
-     switch (attributes.schemeIdUri) {
 
-       case 'urn:mpeg:dash:utc:http-head:2014':
 
-       case 'urn:mpeg:dash:utc:http-head:2012':
 
-         attributes.method = 'HEAD';
 
-         break;
 
-       case 'urn:mpeg:dash:utc:http-xsdate:2014':
 
-       case 'urn:mpeg:dash:utc:http-iso:2014':
 
-       case 'urn:mpeg:dash:utc:http-xsdate:2012':
 
-       case 'urn:mpeg:dash:utc:http-iso:2012':
 
-         attributes.method = 'GET';
 
-         break;
 
-       case 'urn:mpeg:dash:utc:direct:2014':
 
-       case 'urn:mpeg:dash:utc:direct:2012':
 
-         attributes.method = 'DIRECT';
 
-         attributes.value = Date.parse(attributes.value);
 
-         break;
 
-       case 'urn:mpeg:dash:utc:http-ntp:2014':
 
-       case 'urn:mpeg:dash:utc:ntp:2014':
 
-       case 'urn:mpeg:dash:utc:sntp:2014':
 
-       default:
 
-         throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
 
-     }
 
-     return attributes;
 
-   };
 
-   /*
 
-    * Given a DASH manifest string and options, parses the DASH manifest into an object in the
 
-    * form outputed by m3u8-parser and accepted by videojs/http-streaming.
 
-    *
 
-    * For live DASH manifests, if `previousManifest` is provided in options, then the newly
 
-    * parsed DASH manifest will have its media sequence and discontinuity sequence values
 
-    * updated to reflect its position relative to the prior manifest.
 
-    *
 
-    * @param {string} manifestString - the DASH manifest as a string
 
-    * @param {options} [options] - any options
 
-    *
 
-    * @return {Object} the manifest object
 
-    */
 
-   const parse = (manifestString, options = {}) => {
 
-     const parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
 
-     const playlists = toPlaylists(parsedManifestInfo.representationInfo);
 
-     return toM3u8({
 
-       dashPlaylists: playlists,
 
-       locations: parsedManifestInfo.locations,
 
-       sidxMapping: options.sidxMapping,
 
-       previousManifest: options.previousManifest
 
-     });
 
-   };
 
-   /**
 
-    * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
 
-    *
 
-    * @param {string} manifestString
 
-    *        XML string of the MPD manifest
 
-    * @return {Object|null}
 
-    *         Attributes of UTCTiming node specified in the manifest. Null if none found
 
-    */
 
-   const parseUTCTiming = manifestString => parseUTCTimingScheme(stringToMpdXml(manifestString));
 
-   var MAX_UINT32 = Math.pow(2, 32);
 
-   var getUint64$1 = function (uint8) {
 
-     var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
 
-     var value;
 
-     if (dv.getBigUint64) {
 
-       value = dv.getBigUint64(0);
 
-       if (value < Number.MAX_SAFE_INTEGER) {
 
-         return Number(value);
 
-       }
 
-       return value;
 
-     }
 
-     return dv.getUint32(0) * MAX_UINT32 + dv.getUint32(4);
 
-   };
 
-   var numbers = {
 
-     getUint64: getUint64$1,
 
-     MAX_UINT32: MAX_UINT32
 
-   };
 
-   var getUint64 = numbers.getUint64;
 
-   var parseSidx = function (data) {
 
-     var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
 
-       result = {
 
-         version: data[0],
 
-         flags: new Uint8Array(data.subarray(1, 4)),
 
-         references: [],
 
-         referenceId: view.getUint32(4),
 
-         timescale: view.getUint32(8)
 
-       },
 
-       i = 12;
 
-     if (result.version === 0) {
 
-       result.earliestPresentationTime = view.getUint32(i);
 
-       result.firstOffset = view.getUint32(i + 4);
 
-       i += 8;
 
-     } else {
 
-       // read 64 bits
 
-       result.earliestPresentationTime = getUint64(data.subarray(i));
 
-       result.firstOffset = getUint64(data.subarray(i + 8));
 
-       i += 16;
 
-     }
 
-     i += 2; // reserved
 
-     var referenceCount = view.getUint16(i);
 
-     i += 2; // start of references
 
-     for (; referenceCount > 0; i += 12, referenceCount--) {
 
-       result.references.push({
 
-         referenceType: (data[i] & 0x80) >>> 7,
 
-         referencedSize: view.getUint32(i) & 0x7FFFFFFF,
 
-         subsegmentDuration: view.getUint32(i + 4),
 
-         startsWithSap: !!(data[i + 8] & 0x80),
 
-         sapType: (data[i + 8] & 0x70) >>> 4,
 
-         sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
 
-       });
 
-     }
 
-     return result;
 
-   };
 
-   var parseSidx_1 = parseSidx;
 
-   var ID3 = toUint8([0x49, 0x44, 0x33]);
 
-   var getId3Size = function getId3Size(bytes, offset) {
 
-     if (offset === void 0) {
 
-       offset = 0;
 
-     }
 
-     bytes = toUint8(bytes);
 
-     var flags = bytes[offset + 5];
 
-     var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9];
 
-     var footerPresent = (flags & 16) >> 4;
 
-     if (footerPresent) {
 
-       return returnSize + 20;
 
-     }
 
-     return returnSize + 10;
 
-   };
 
-   var getId3Offset = function getId3Offset(bytes, offset) {
 
-     if (offset === void 0) {
 
-       offset = 0;
 
-     }
 
-     bytes = toUint8(bytes);
 
-     if (bytes.length - offset < 10 || !bytesMatch(bytes, ID3, {
 
-       offset: offset
 
-     })) {
 
-       return offset;
 
-     }
 
-     offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files
 
-     // have multiple ID3 tag sections even though
 
-     // they should not.
 
-     return getId3Offset(bytes, offset);
 
-   };
 
-   var normalizePath$1 = function normalizePath(path) {
 
-     if (typeof path === 'string') {
 
-       return stringToBytes(path);
 
-     }
 
-     if (typeof path === 'number') {
 
-       return path;
 
-     }
 
-     return path;
 
-   };
 
-   var normalizePaths$1 = function normalizePaths(paths) {
 
-     if (!Array.isArray(paths)) {
 
-       return [normalizePath$1(paths)];
 
-     }
 
-     return paths.map(function (p) {
 
-       return normalizePath$1(p);
 
-     });
 
-   };
 
-   /**
 
-    * find any number of boxes by name given a path to it in an iso bmff
 
-    * such as mp4.
 
-    *
 
-    * @param {TypedArray} bytes
 
-    *        bytes for the iso bmff to search for boxes in
 
-    *
 
-    * @param {Uint8Array[]|string[]|string|Uint8Array} name
 
-    *        An array of paths or a single path representing the name
 
-    *        of boxes to search through in bytes. Paths may be
 
-    *        uint8 (character codes) or strings.
 
-    *
 
-    * @param {boolean} [complete=false]
 
-    *        Should we search only for complete boxes on the final path.
 
-    *        This is very useful when you do not want to get back partial boxes
 
-    *        in the case of streaming files.
 
-    *
 
-    * @return {Uint8Array[]}
 
-    *         An array of the end paths that we found.
 
-    */
 
-   var findBox = function findBox(bytes, paths, complete) {
 
-     if (complete === void 0) {
 
-       complete = false;
 
-     }
 
-     paths = normalizePaths$1(paths);
 
-     bytes = toUint8(bytes);
 
-     var results = [];
 
-     if (!paths.length) {
 
-       // short-circuit the search for empty paths
 
-       return results;
 
-     }
 
-     var i = 0;
 
-     while (i < bytes.length) {
 
-       var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0;
 
-       var type = bytes.subarray(i + 4, i + 8); // invalid box format.
 
-       if (size === 0) {
 
-         break;
 
-       }
 
-       var end = i + size;
 
-       if (end > bytes.length) {
 
-         // this box is bigger than the number of bytes we have
 
-         // and complete is set, we cannot find any more boxes.
 
-         if (complete) {
 
-           break;
 
-         }
 
-         end = bytes.length;
 
-       }
 
-       var data = bytes.subarray(i + 8, end);
 
-       if (bytesMatch(type, paths[0])) {
 
-         if (paths.length === 1) {
 
-           // this is the end of the path and we've found the box we were
 
-           // looking for
 
-           results.push(data);
 
-         } else {
 
-           // recursively search for the next box along the path
 
-           results.push.apply(results, findBox(data, paths.slice(1), complete));
 
-         }
 
-       }
 
-       i = end;
 
-     } // we've finished searching all of bytes
 
-     return results;
 
-   };
 
-   // https://matroska-org.github.io/libebml/specs.html
 
-   // https://www.matroska.org/technical/elements.html
 
-   // https://www.webmproject.org/docs/container/
 
-   var EBML_TAGS = {
 
-     EBML: toUint8([0x1A, 0x45, 0xDF, 0xA3]),
 
-     DocType: toUint8([0x42, 0x82]),
 
-     Segment: toUint8([0x18, 0x53, 0x80, 0x67]),
 
-     SegmentInfo: toUint8([0x15, 0x49, 0xA9, 0x66]),
 
-     Tracks: toUint8([0x16, 0x54, 0xAE, 0x6B]),
 
-     Track: toUint8([0xAE]),
 
-     TrackNumber: toUint8([0xd7]),
 
-     DefaultDuration: toUint8([0x23, 0xe3, 0x83]),
 
-     TrackEntry: toUint8([0xAE]),
 
-     TrackType: toUint8([0x83]),
 
-     FlagDefault: toUint8([0x88]),
 
-     CodecID: toUint8([0x86]),
 
-     CodecPrivate: toUint8([0x63, 0xA2]),
 
-     VideoTrack: toUint8([0xe0]),
 
-     AudioTrack: toUint8([0xe1]),
 
-     // Not used yet, but will be used for live webm/mkv
 
-     // see https://www.matroska.org/technical/basics.html#block-structure
 
-     // see https://www.matroska.org/technical/basics.html#simpleblock-structure
 
-     Cluster: toUint8([0x1F, 0x43, 0xB6, 0x75]),
 
-     Timestamp: toUint8([0xE7]),
 
-     TimestampScale: toUint8([0x2A, 0xD7, 0xB1]),
 
-     BlockGroup: toUint8([0xA0]),
 
-     BlockDuration: toUint8([0x9B]),
 
-     Block: toUint8([0xA1]),
 
-     SimpleBlock: toUint8([0xA3])
 
-   };
 
-   /**
 
-    * This is a simple table to determine the length
 
-    * of things in ebml. The length is one based (starts at 1,
 
-    * rather than zero) and for every zero bit before a one bit
 
-    * we add one to length. We also need this table because in some
 
-    * case we have to xor all the length bits from another value.
 
-    */
 
-   var LENGTH_TABLE = [128, 64, 32, 16, 8, 4, 2, 1];
 
-   var getLength = function getLength(byte) {
 
-     var len = 1;
 
-     for (var i = 0; i < LENGTH_TABLE.length; i++) {
 
-       if (byte & LENGTH_TABLE[i]) {
 
-         break;
 
-       }
 
-       len++;
 
-     }
 
-     return len;
 
-   }; // length in ebml is stored in the first 4 to 8 bits
 
-   // of the first byte. 4 for the id length and 8 for the
 
-   // data size length. Length is measured by converting the number to binary
 
-   // then 1 + the number of zeros before a 1 is encountered starting
 
-   // from the left.
 
-   var getvint = function getvint(bytes, offset, removeLength, signed) {
 
-     if (removeLength === void 0) {
 
-       removeLength = true;
 
-     }
 
-     if (signed === void 0) {
 
-       signed = false;
 
-     }
 
-     var length = getLength(bytes[offset]);
 
-     var valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes
 
-     // as they will be modified below to remove the dataSizeLen bits and we do not
 
-     // want to modify the original data. normally we could just call slice on
 
-     // uint8array but ie 11 does not support that...
 
-     if (removeLength) {
 
-       valueBytes = Array.prototype.slice.call(bytes, offset, offset + length);
 
-       valueBytes[0] ^= LENGTH_TABLE[length - 1];
 
-     }
 
-     return {
 
-       length: length,
 
-       value: bytesToNumber(valueBytes, {
 
-         signed: signed
 
-       }),
 
-       bytes: valueBytes
 
-     };
 
-   };
 
-   var normalizePath = function normalizePath(path) {
 
-     if (typeof path === 'string') {
 
-       return path.match(/.{1,2}/g).map(function (p) {
 
-         return normalizePath(p);
 
-       });
 
-     }
 
-     if (typeof path === 'number') {
 
-       return numberToBytes(path);
 
-     }
 
-     return path;
 
-   };
 
-   var normalizePaths = function normalizePaths(paths) {
 
-     if (!Array.isArray(paths)) {
 
-       return [normalizePath(paths)];
 
-     }
 
-     return paths.map(function (p) {
 
-       return normalizePath(p);
 
-     });
 
-   };
 
-   var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) {
 
-     if (offset >= bytes.length) {
 
-       return bytes.length;
 
-     }
 
-     var innerid = getvint(bytes, offset, false);
 
-     if (bytesMatch(id.bytes, innerid.bytes)) {
 
-       return offset;
 
-     }
 
-     var dataHeader = getvint(bytes, offset + innerid.length);
 
-     return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length);
 
-   };
 
-   /**
 
-    * Notes on the EBLM format.
 
-    *
 
-    * EBLM uses "vints" tags. Every vint tag contains
 
-    * two parts
 
-    *
 
-    * 1. The length from the first byte. You get this by
 
-    *    converting the byte to binary and counting the zeros
 
-    *    before a 1. Then you add 1 to that. Examples
 
-    *    00011111 = length 4 because there are 3 zeros before a 1.
 
-    *    00100000 = length 3 because there are 2 zeros before a 1.
 
-    *    00000011 = length 7 because there are 6 zeros before a 1.
 
-    *
 
-    * 2. The bits used for length are removed from the first byte
 
-    *    Then all the bytes are merged into a value. NOTE: this
 
-    *    is not the case for id ebml tags as there id includes
 
-    *    length bits.
 
-    *
 
-    */
 
-   var findEbml = function findEbml(bytes, paths) {
 
-     paths = normalizePaths(paths);
 
-     bytes = toUint8(bytes);
 
-     var results = [];
 
-     if (!paths.length) {
 
-       return results;
 
-     }
 
-     var i = 0;
 
-     while (i < bytes.length) {
 
-       var id = getvint(bytes, i, false);
 
-       var dataHeader = getvint(bytes, i + id.length);
 
-       var dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream
 
-       if (dataHeader.value === 0x7f) {
 
-         dataHeader.value = getInfinityDataSize(id, bytes, dataStart);
 
-         if (dataHeader.value !== bytes.length) {
 
-           dataHeader.value -= dataStart;
 
-         }
 
-       }
 
-       var dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value;
 
-       var data = bytes.subarray(dataStart, dataEnd);
 
-       if (bytesMatch(paths[0], id.bytes)) {
 
-         if (paths.length === 1) {
 
-           // this is the end of the paths and we've found the tag we were
 
-           // looking for
 
-           results.push(data);
 
-         } else {
 
-           // recursively search for the next tag inside of the data
 
-           // of this one
 
-           results = results.concat(findEbml(data, paths.slice(1)));
 
-         }
 
-       }
 
-       var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it
 
-       i += totalLength;
 
-     }
 
-     return results;
 
-   }; // see https://www.matroska.org/technical/basics.html#block-structure
 
-   var NAL_TYPE_ONE = toUint8([0x00, 0x00, 0x00, 0x01]);
 
-   var NAL_TYPE_TWO = toUint8([0x00, 0x00, 0x01]);
 
-   var EMULATION_PREVENTION = toUint8([0x00, 0x00, 0x03]);
 
-   /**
 
-    * Expunge any "Emulation Prevention" bytes from a "Raw Byte
 
-    * Sequence Payload"
 
-    *
 
-    * @param data {Uint8Array} the bytes of a RBSP from a NAL
 
-    * unit
 
-    * @return {Uint8Array} the RBSP without any Emulation
 
-    * Prevention Bytes
 
-    */
 
-   var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(bytes) {
 
-     var positions = [];
 
-     var i = 1; // Find all `Emulation Prevention Bytes`
 
-     while (i < bytes.length - 2) {
 
-       if (bytesMatch(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) {
 
-         positions.push(i + 2);
 
-         i++;
 
-       }
 
-       i++;
 
-     } // If no Emulation Prevention Bytes were found just return the original
 
-     // array
 
-     if (positions.length === 0) {
 
-       return bytes;
 
-     } // Create a new array to hold the NAL unit data
 
-     var newLength = bytes.length - positions.length;
 
-     var newData = new Uint8Array(newLength);
 
-     var sourceIndex = 0;
 
-     for (i = 0; i < newLength; sourceIndex++, i++) {
 
-       if (sourceIndex === positions[0]) {
 
-         // Skip this byte
 
-         sourceIndex++; // Remove this position index
 
-         positions.shift();
 
-       }
 
-       newData[i] = bytes[sourceIndex];
 
-     }
 
-     return newData;
 
-   };
 
-   var findNal = function findNal(bytes, dataType, types, nalLimit) {
 
-     if (nalLimit === void 0) {
 
-       nalLimit = Infinity;
 
-     }
 
-     bytes = toUint8(bytes);
 
-     types = [].concat(types);
 
-     var i = 0;
 
-     var nalStart;
 
-     var nalsFound = 0; // keep searching until:
 
-     // we reach the end of bytes
 
-     // we reach the maximum number of nals they want to seach
 
-     // NOTE: that we disregard nalLimit when we have found the start
 
-     // of the nal we want so that we can find the end of the nal we want.
 
-     while (i < bytes.length && (nalsFound < nalLimit || nalStart)) {
 
-       var nalOffset = void 0;
 
-       if (bytesMatch(bytes.subarray(i), NAL_TYPE_ONE)) {
 
-         nalOffset = 4;
 
-       } else if (bytesMatch(bytes.subarray(i), NAL_TYPE_TWO)) {
 
-         nalOffset = 3;
 
-       } // we are unsynced,
 
-       // find the next nal unit
 
-       if (!nalOffset) {
 
-         i++;
 
-         continue;
 
-       }
 
-       nalsFound++;
 
-       if (nalStart) {
 
-         return discardEmulationPreventionBytes(bytes.subarray(nalStart, i));
 
-       }
 
-       var nalType = void 0;
 
-       if (dataType === 'h264') {
 
-         nalType = bytes[i + nalOffset] & 0x1f;
 
-       } else if (dataType === 'h265') {
 
-         nalType = bytes[i + nalOffset] >> 1 & 0x3f;
 
-       }
 
-       if (types.indexOf(nalType) !== -1) {
 
-         nalStart = i + nalOffset;
 
-       } // nal header is 1 length for h264, and 2 for h265
 
-       i += nalOffset + (dataType === 'h264' ? 1 : 2);
 
-     }
 
-     return bytes.subarray(0, 0);
 
-   };
 
-   var findH264Nal = function findH264Nal(bytes, type, nalLimit) {
 
-     return findNal(bytes, 'h264', type, nalLimit);
 
-   };
 
-   var findH265Nal = function findH265Nal(bytes, type, nalLimit) {
 
-     return findNal(bytes, 'h265', type, nalLimit);
 
-   };
 
-   var CONSTANTS = {
 
-     // "webm" string literal in hex
 
-     'webm': toUint8([0x77, 0x65, 0x62, 0x6d]),
 
-     // "matroska" string literal in hex
 
-     'matroska': toUint8([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]),
 
-     // "fLaC" string literal in hex
 
-     'flac': toUint8([0x66, 0x4c, 0x61, 0x43]),
 
-     // "OggS" string literal in hex
 
-     'ogg': toUint8([0x4f, 0x67, 0x67, 0x53]),
 
-     // ac-3 sync byte, also works for ec-3 as that is simply a codec
 
-     // of ac-3
 
-     'ac3': toUint8([0x0b, 0x77]),
 
-     // "RIFF" string literal in hex used for wav and avi
 
-     'riff': toUint8([0x52, 0x49, 0x46, 0x46]),
 
-     // "AVI" string literal in hex
 
-     'avi': toUint8([0x41, 0x56, 0x49]),
 
-     // "WAVE" string literal in hex
 
-     'wav': toUint8([0x57, 0x41, 0x56, 0x45]),
 
-     // "ftyp3g" string literal in hex
 
-     '3gp': toUint8([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]),
 
-     // "ftyp" string literal in hex
 
-     'mp4': toUint8([0x66, 0x74, 0x79, 0x70]),
 
-     // "styp" string literal in hex
 
-     'fmp4': toUint8([0x73, 0x74, 0x79, 0x70]),
 
-     // "ftypqt" string literal in hex
 
-     'mov': toUint8([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]),
 
-     // moov string literal in hex
 
-     'moov': toUint8([0x6D, 0x6F, 0x6F, 0x76]),
 
-     // moof string literal in hex
 
-     'moof': toUint8([0x6D, 0x6F, 0x6F, 0x66])
 
-   };
 
-   var _isLikely = {
 
-     aac: function aac(bytes) {
 
-       var offset = getId3Offset(bytes);
 
-       return bytesMatch(bytes, [0xFF, 0x10], {
 
-         offset: offset,
 
-         mask: [0xFF, 0x16]
 
-       });
 
-     },
 
-     mp3: function mp3(bytes) {
 
-       var offset = getId3Offset(bytes);
 
-       return bytesMatch(bytes, [0xFF, 0x02], {
 
-         offset: offset,
 
-         mask: [0xFF, 0x06]
 
-       });
 
-     },
 
-     webm: function webm(bytes) {
 
-       var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm
 
-       return bytesMatch(docType, CONSTANTS.webm);
 
-     },
 
-     mkv: function mkv(bytes) {
 
-       var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska
 
-       return bytesMatch(docType, CONSTANTS.matroska);
 
-     },
 
-     mp4: function mp4(bytes) {
 
-       // if this file is another base media file format, it is not mp4
 
-       if (_isLikely['3gp'](bytes) || _isLikely.mov(bytes)) {
 
-         return false;
 
-       } // if this file starts with a ftyp or styp box its mp4
 
-       if (bytesMatch(bytes, CONSTANTS.mp4, {
 
-         offset: 4
 
-       }) || bytesMatch(bytes, CONSTANTS.fmp4, {
 
-         offset: 4
 
-       })) {
 
-         return true;
 
-       } // if this file starts with a moof/moov box its mp4
 
-       if (bytesMatch(bytes, CONSTANTS.moof, {
 
-         offset: 4
 
-       }) || bytesMatch(bytes, CONSTANTS.moov, {
 
-         offset: 4
 
-       })) {
 
-         return true;
 
-       }
 
-     },
 
-     mov: function mov(bytes) {
 
-       return bytesMatch(bytes, CONSTANTS.mov, {
 
-         offset: 4
 
-       });
 
-     },
 
-     '3gp': function gp(bytes) {
 
-       return bytesMatch(bytes, CONSTANTS['3gp'], {
 
-         offset: 4
 
-       });
 
-     },
 
-     ac3: function ac3(bytes) {
 
-       var offset = getId3Offset(bytes);
 
-       return bytesMatch(bytes, CONSTANTS.ac3, {
 
-         offset: offset
 
-       });
 
-     },
 
-     ts: function ts(bytes) {
 
-       if (bytes.length < 189 && bytes.length >= 1) {
 
-         return bytes[0] === 0x47;
 
-       }
 
-       var i = 0; // check the first 376 bytes for two matching sync bytes
 
-       while (i + 188 < bytes.length && i < 188) {
 
-         if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) {
 
-           return true;
 
-         }
 
-         i += 1;
 
-       }
 
-       return false;
 
-     },
 
-     flac: function flac(bytes) {
 
-       var offset = getId3Offset(bytes);
 
-       return bytesMatch(bytes, CONSTANTS.flac, {
 
-         offset: offset
 
-       });
 
-     },
 
-     ogg: function ogg(bytes) {
 
-       return bytesMatch(bytes, CONSTANTS.ogg);
 
-     },
 
-     avi: function avi(bytes) {
 
-       return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.avi, {
 
-         offset: 8
 
-       });
 
-     },
 
-     wav: function wav(bytes) {
 
-       return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.wav, {
 
-         offset: 8
 
-       });
 
-     },
 
-     'h264': function h264(bytes) {
 
-       // find seq_parameter_set_rbsp
 
-       return findH264Nal(bytes, 7, 3).length;
 
-     },
 
-     'h265': function h265(bytes) {
 
-       // find video_parameter_set_rbsp or seq_parameter_set_rbsp
 
-       return findH265Nal(bytes, [32, 33], 3).length;
 
-     }
 
-   }; // get all the isLikely functions
 
-   // but make sure 'ts' is above h264 and h265
 
-   // but below everything else as it is the least specific
 
-   var isLikelyTypes = Object.keys(_isLikely) // remove ts, h264, h265
 
-   .filter(function (t) {
 
-     return t !== 'ts' && t !== 'h264' && t !== 'h265';
 
-   }) // add it back to the bottom
 
-   .concat(['ts', 'h264', 'h265']); // make sure we are dealing with uint8 data.
 
-   isLikelyTypes.forEach(function (type) {
 
-     var isLikelyFn = _isLikely[type];
 
-     _isLikely[type] = function (bytes) {
 
-       return isLikelyFn(toUint8(bytes));
 
-     };
 
-   }); // export after wrapping
 
-   var isLikely = _isLikely; // A useful list of file signatures can be found here
 
-   // https://en.wikipedia.org/wiki/List_of_file_signatures
 
-   var detectContainerForBytes = function detectContainerForBytes(bytes) {
 
-     bytes = toUint8(bytes);
 
-     for (var i = 0; i < isLikelyTypes.length; i++) {
 
-       var type = isLikelyTypes[i];
 
-       if (isLikely[type](bytes)) {
 
-         return type;
 
-       }
 
-     }
 
-     return '';
 
-   }; // fmp4 is not a container
 
-   var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) {
 
-     return findBox(bytes, ['moof']).length > 0;
 
-   };
 
-   /**
 
-    * mux.js
 
-    *
 
-    * Copyright (c) Brightcove
 
-    * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-    */
 
-   var ONE_SECOND_IN_TS = 90000,
 
-     // 90kHz clock
 
-     secondsToVideoTs,
 
-     secondsToAudioTs,
 
-     videoTsToSeconds,
 
-     audioTsToSeconds,
 
-     audioTsToVideoTs,
 
-     videoTsToAudioTs,
 
-     metadataTsToSeconds;
 
-   secondsToVideoTs = function (seconds) {
 
-     return seconds * ONE_SECOND_IN_TS;
 
-   };
 
-   secondsToAudioTs = function (seconds, sampleRate) {
 
-     return seconds * sampleRate;
 
-   };
 
-   videoTsToSeconds = function (timestamp) {
 
-     return timestamp / ONE_SECOND_IN_TS;
 
-   };
 
-   audioTsToSeconds = function (timestamp, sampleRate) {
 
-     return timestamp / sampleRate;
 
-   };
 
-   audioTsToVideoTs = function (timestamp, sampleRate) {
 
-     return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
 
-   };
 
-   videoTsToAudioTs = function (timestamp, sampleRate) {
 
-     return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
 
-   };
 
-   /**
 
-    * Adjust ID3 tag or caption timing information by the timeline pts values
 
-    * (if keepOriginalTimestamps is false) and convert to seconds
 
-    */
 
-   metadataTsToSeconds = function (timestamp, timelineStartPts, keepOriginalTimestamps) {
 
-     return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
 
-   };
 
-   var clock = {
 
-     ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
 
-     secondsToVideoTs: secondsToVideoTs,
 
-     secondsToAudioTs: secondsToAudioTs,
 
-     videoTsToSeconds: videoTsToSeconds,
 
-     audioTsToSeconds: audioTsToSeconds,
 
-     audioTsToVideoTs: audioTsToVideoTs,
 
-     videoTsToAudioTs: videoTsToAudioTs,
 
-     metadataTsToSeconds: metadataTsToSeconds
 
-   };
 
-   var clock_1 = clock.ONE_SECOND_IN_TS;
 
-   /*! @name @videojs/http-streaming @version 3.0.2 @license Apache-2.0 */
 
-   /**
 
-    * @file resolve-url.js - Handling how URLs are resolved and manipulated
 
-    */
 
-   const resolveUrl = resolveUrl$2;
 
-   /**
 
-    * If the xhr request was redirected, return the responseURL, otherwise,
 
-    * return the original url.
 
-    *
 
-    * @api private
 
-    *
 
-    * @param  {string} url - an url being requested
 
-    * @param  {XMLHttpRequest} req - xhr request result
 
-    *
 
-    * @return {string}
 
-    */
 
-   const resolveManifestRedirect = (url, req) => {
 
-     // To understand how the responseURL below is set and generated:
 
-     // - https://fetch.spec.whatwg.org/#concept-response-url
 
-     // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
 
-     if (req && req.responseURL && url !== req.responseURL) {
 
-       return req.responseURL;
 
-     }
 
-     return url;
 
-   };
 
-   const logger = source => {
 
-     if (videojs.log.debug) {
 
-       return videojs.log.debug.bind(videojs, 'VHS:', `${source} >`);
 
-     }
 
-     return function () {};
 
-   };
 
-   /**
 
-    * Provides a compatibility layer between Video.js 7 and 8 API changes for VHS.
 
-    */
 
-   /**
 
-    * Delegates to videojs.obj.merge (Video.js 8) or
 
-    * videojs.mergeOptions (Video.js 7).
 
-    */
 
-   function merge(...args) {
 
-     const context = videojs.obj || videojs;
 
-     const fn = context.merge || context.mergeOptions;
 
-     return fn.apply(context, args);
 
-   }
 
-   /**
 
-    * Delegates to videojs.time.createTimeRanges (Video.js 8) or
 
-    * videojs.createTimeRanges (Video.js 7).
 
-    */
 
-   function createTimeRanges(...args) {
 
-     const context = videojs.time || videojs;
 
-     const fn = context.createTimeRanges || context.createTimeRanges;
 
-     return fn.apply(context, args);
 
-   }
 
-   /**
 
-    * ranges
 
-    *
 
-    * Utilities for working with TimeRanges.
 
-    *
 
-    */
 
-   const TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
 
-   // can be misleading because of precision differences or when the current media has poorly
 
-   // aligned audio and video, which can cause values to be slightly off from what you would
 
-   // expect. This value is what we consider to be safe to use in such comparisons to account
 
-   // for these scenarios.
 
-   const SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
 
-   const filterRanges = function (timeRanges, predicate) {
 
-     const results = [];
 
-     let i;
 
-     if (timeRanges && timeRanges.length) {
 
-       // Search for ranges that match the predicate
 
-       for (i = 0; i < timeRanges.length; i++) {
 
-         if (predicate(timeRanges.start(i), timeRanges.end(i))) {
 
-           results.push([timeRanges.start(i), timeRanges.end(i)]);
 
-         }
 
-       }
 
-     }
 
-     return createTimeRanges(results);
 
-   };
 
-   /**
 
-    * Attempts to find the buffered TimeRange that contains the specified
 
-    * time.
 
-    *
 
-    * @param {TimeRanges} buffered - the TimeRanges object to query
 
-    * @param {number} time  - the time to filter on.
 
-    * @return {TimeRanges} a new TimeRanges object
 
-    */
 
-   const findRange = function (buffered, time) {
 
-     return filterRanges(buffered, function (start, end) {
 
-       return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time;
 
-     });
 
-   };
 
-   /**
 
-    * Returns the TimeRanges that begin later than the specified time.
 
-    *
 
-    * @param {TimeRanges} timeRanges - the TimeRanges object to query
 
-    * @param {number} time - the time to filter on.
 
-    * @return {TimeRanges} a new TimeRanges object.
 
-    */
 
-   const findNextRange = function (timeRanges, time) {
 
-     return filterRanges(timeRanges, function (start) {
 
-       return start - TIME_FUDGE_FACTOR >= time;
 
-     });
 
-   };
 
-   /**
 
-    * Returns gaps within a list of TimeRanges
 
-    *
 
-    * @param {TimeRanges} buffered - the TimeRanges object
 
-    * @return {TimeRanges} a TimeRanges object of gaps
 
-    */
 
-   const findGaps = function (buffered) {
 
-     if (buffered.length < 2) {
 
-       return createTimeRanges();
 
-     }
 
-     const ranges = [];
 
-     for (let i = 1; i < buffered.length; i++) {
 
-       const start = buffered.end(i - 1);
 
-       const end = buffered.start(i);
 
-       ranges.push([start, end]);
 
-     }
 
-     return createTimeRanges(ranges);
 
-   };
 
-   /**
 
-    * Calculate the intersection of two TimeRanges
 
-    *
 
-    * @param {TimeRanges} bufferA
 
-    * @param {TimeRanges} bufferB
 
-    * @return {TimeRanges} The interesection of `bufferA` with `bufferB`
 
-    */
 
-   const bufferIntersection = function (bufferA, bufferB) {
 
-     let start = null;
 
-     let end = null;
 
-     let arity = 0;
 
-     const extents = [];
 
-     const ranges = [];
 
-     if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
 
-       return createTimeRanges();
 
-     } // Handle the case where we have both buffers and create an
 
-     // intersection of the two
 
-     let count = bufferA.length; // A) Gather up all start and end times
 
-     while (count--) {
 
-       extents.push({
 
-         time: bufferA.start(count),
 
-         type: 'start'
 
-       });
 
-       extents.push({
 
-         time: bufferA.end(count),
 
-         type: 'end'
 
-       });
 
-     }
 
-     count = bufferB.length;
 
-     while (count--) {
 
-       extents.push({
 
-         time: bufferB.start(count),
 
-         type: 'start'
 
-       });
 
-       extents.push({
 
-         time: bufferB.end(count),
 
-         type: 'end'
 
-       });
 
-     } // B) Sort them by time
 
-     extents.sort(function (a, b) {
 
-       return a.time - b.time;
 
-     }); // C) Go along one by one incrementing arity for start and decrementing
 
-     //    arity for ends
 
-     for (count = 0; count < extents.length; count++) {
 
-       if (extents[count].type === 'start') {
 
-         arity++; // D) If arity is ever incremented to 2 we are entering an
 
-         //    overlapping range
 
-         if (arity === 2) {
 
-           start = extents[count].time;
 
-         }
 
-       } else if (extents[count].type === 'end') {
 
-         arity--; // E) If arity is ever decremented to 1 we leaving an
 
-         //    overlapping range
 
-         if (arity === 1) {
 
-           end = extents[count].time;
 
-         }
 
-       } // F) Record overlapping ranges
 
-       if (start !== null && end !== null) {
 
-         ranges.push([start, end]);
 
-         start = null;
 
-         end = null;
 
-       }
 
-     }
 
-     return createTimeRanges(ranges);
 
-   };
 
-   /**
 
-    * Gets a human readable string for a TimeRange
 
-    *
 
-    * @param {TimeRange} range
 
-    * @return {string} a human readable string
 
-    */
 
-   const printableRange = range => {
 
-     const strArr = [];
 
-     if (!range || !range.length) {
 
-       return '';
 
-     }
 
-     for (let i = 0; i < range.length; i++) {
 
-       strArr.push(range.start(i) + ' => ' + range.end(i));
 
-     }
 
-     return strArr.join(', ');
 
-   };
 
-   /**
 
-    * Calculates the amount of time left in seconds until the player hits the end of the
 
-    * buffer and causes a rebuffer
 
-    *
 
-    * @param {TimeRange} buffered
 
-    *        The state of the buffer
 
-    * @param {Numnber} currentTime
 
-    *        The current time of the player
 
-    * @param {number} playbackRate
 
-    *        The current playback rate of the player. Defaults to 1.
 
-    * @return {number}
 
-    *         Time until the player has to start rebuffering in seconds.
 
-    * @function timeUntilRebuffer
 
-    */
 
-   const timeUntilRebuffer = function (buffered, currentTime, playbackRate = 1) {
 
-     const bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
 
-     return (bufferedEnd - currentTime) / playbackRate;
 
-   };
 
-   /**
 
-    * Converts a TimeRanges object into an array representation
 
-    *
 
-    * @param {TimeRanges} timeRanges
 
-    * @return {Array}
 
-    */
 
-   const timeRangesToArray = timeRanges => {
 
-     const timeRangesList = [];
 
-     for (let i = 0; i < timeRanges.length; i++) {
 
-       timeRangesList.push({
 
-         start: timeRanges.start(i),
 
-         end: timeRanges.end(i)
 
-       });
 
-     }
 
-     return timeRangesList;
 
-   };
 
-   /**
 
-    * Determines if two time range objects are different.
 
-    *
 
-    * @param {TimeRange} a
 
-    *        the first time range object to check
 
-    *
 
-    * @param {TimeRange} b
 
-    *        the second time range object to check
 
-    *
 
-    * @return {Boolean}
 
-    *         Whether the time range objects differ
 
-    */
 
-   const isRangeDifferent = function (a, b) {
 
-     // same object
 
-     if (a === b) {
 
-       return false;
 
-     } // one or the other is undefined
 
-     if (!a && b || !b && a) {
 
-       return true;
 
-     } // length is different
 
-     if (a.length !== b.length) {
 
-       return true;
 
-     } // see if any start/end pair is different
 
-     for (let i = 0; i < a.length; i++) {
 
-       if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) {
 
-         return true;
 
-       }
 
-     } // if the length and every pair is the same
 
-     // this is the same time range
 
-     return false;
 
-   };
 
-   const lastBufferedEnd = function (a) {
 
-     if (!a || !a.length || !a.end) {
 
-       return;
 
-     }
 
-     return a.end(a.length - 1);
 
-   };
 
-   /**
 
-    * A utility function to add up the amount of time in a timeRange
 
-    * after a specified startTime.
 
-    * ie:[[0, 10], [20, 40], [50, 60]] with a startTime 0
 
-    *     would return 40 as there are 40s seconds after 0 in the timeRange
 
-    *
 
-    * @param {TimeRange} range
 
-    *        The range to check against
 
-    * @param {number} startTime
 
-    *        The time in the time range that you should start counting from
 
-    *
 
-    * @return {number}
 
-    *          The number of seconds in the buffer passed the specified time.
 
-    */
 
-   const timeAheadOf = function (range, startTime) {
 
-     let time = 0;
 
-     if (!range || !range.length) {
 
-       return time;
 
-     }
 
-     for (let i = 0; i < range.length; i++) {
 
-       const start = range.start(i);
 
-       const end = range.end(i); // startTime is after this range entirely
 
-       if (startTime > end) {
 
-         continue;
 
-       } // startTime is within this range
 
-       if (startTime > start && startTime <= end) {
 
-         time += end - startTime;
 
-         continue;
 
-       } // startTime is before this range.
 
-       time += end - start;
 
-     }
 
-     return time;
 
-   };
 
-   /**
 
-    * @file playlist.js
 
-    *
 
-    * Playlist related utilities.
 
-    */
 
-   /**
 
-    * Get the duration of a segment, with special cases for
 
-    * llhls segments that do not have a duration yet.
 
-    *
 
-    * @param {Object} playlist
 
-    *        the playlist that the segment belongs to.
 
-    * @param {Object} segment
 
-    *        the segment to get a duration for.
 
-    *
 
-    * @return {number}
 
-    *          the segment duration
 
-    */
 
-   const segmentDurationWithParts = (playlist, segment) => {
 
-     // if this isn't a preload segment
 
-     // then we will have a segment duration that is accurate.
 
-     if (!segment.preload) {
 
-       return segment.duration;
 
-     } // otherwise we have to add up parts and preload hints
 
-     // to get an up to date duration.
 
-     let result = 0;
 
-     (segment.parts || []).forEach(function (p) {
 
-       result += p.duration;
 
-     }); // for preload hints we have to use partTargetDuration
 
-     // as they won't even have a duration yet.
 
-     (segment.preloadHints || []).forEach(function (p) {
 
-       if (p.type === 'PART') {
 
-         result += playlist.partTargetDuration;
 
-       }
 
-     });
 
-     return result;
 
-   };
 
-   /**
 
-    * A function to get a combined list of parts and segments with durations
 
-    * and indexes.
 
-    *
 
-    * @param {Playlist} playlist the playlist to get the list for.
 
-    *
 
-    * @return {Array} The part/segment list.
 
-    */
 
-   const getPartsAndSegments = playlist => (playlist.segments || []).reduce((acc, segment, si) => {
 
-     if (segment.parts) {
 
-       segment.parts.forEach(function (part, pi) {
 
-         acc.push({
 
-           duration: part.duration,
 
-           segmentIndex: si,
 
-           partIndex: pi,
 
-           part,
 
-           segment
 
-         });
 
-       });
 
-     } else {
 
-       acc.push({
 
-         duration: segment.duration,
 
-         segmentIndex: si,
 
-         partIndex: null,
 
-         segment,
 
-         part: null
 
-       });
 
-     }
 
-     return acc;
 
-   }, []);
 
-   const getLastParts = media => {
 
-     const lastSegment = media.segments && media.segments.length && media.segments[media.segments.length - 1];
 
-     return lastSegment && lastSegment.parts || [];
 
-   };
 
-   const getKnownPartCount = ({
 
-     preloadSegment
 
-   }) => {
 
-     if (!preloadSegment) {
 
-       return;
 
-     }
 
-     const {
 
-       parts,
 
-       preloadHints
 
-     } = preloadSegment;
 
-     let partCount = (preloadHints || []).reduce((count, hint) => count + (hint.type === 'PART' ? 1 : 0), 0);
 
-     partCount += parts && parts.length ? parts.length : 0;
 
-     return partCount;
 
-   };
 
-   /**
 
-    * Get the number of seconds to delay from the end of a
 
-    * live playlist.
 
-    *
 
-    * @param {Playlist} main the main playlist
 
-    * @param {Playlist} media the media playlist
 
-    * @return {number} the hold back in seconds.
 
-    */
 
-   const liveEdgeDelay = (main, media) => {
 
-     if (media.endList) {
 
-       return 0;
 
-     } // dash suggestedPresentationDelay trumps everything
 
-     if (main && main.suggestedPresentationDelay) {
 
-       return main.suggestedPresentationDelay;
 
-     }
 
-     const hasParts = getLastParts(media).length > 0; // look for "part" delays from ll-hls first
 
-     if (hasParts && media.serverControl && media.serverControl.partHoldBack) {
 
-       return media.serverControl.partHoldBack;
 
-     } else if (hasParts && media.partTargetDuration) {
 
-       return media.partTargetDuration * 3; // finally look for full segment delays
 
-     } else if (media.serverControl && media.serverControl.holdBack) {
 
-       return media.serverControl.holdBack;
 
-     } else if (media.targetDuration) {
 
-       return media.targetDuration * 3;
 
-     }
 
-     return 0;
 
-   };
 
-   /**
 
-    * walk backward until we find a duration we can use
 
-    * or return a failure
 
-    *
 
-    * @param {Playlist} playlist the playlist to walk through
 
-    * @param {Number} endSequence the mediaSequence to stop walking on
 
-    */
 
-   const backwardDuration = function (playlist, endSequence) {
 
-     let result = 0;
 
-     let i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
 
-     // the interval, use it
 
-     let segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
 
-     // information that is earlier than endSequence
 
-     if (segment) {
 
-       if (typeof segment.start !== 'undefined') {
 
-         return {
 
-           result: segment.start,
 
-           precise: true
 
-         };
 
-       }
 
-       if (typeof segment.end !== 'undefined') {
 
-         return {
 
-           result: segment.end - segment.duration,
 
-           precise: true
 
-         };
 
-       }
 
-     }
 
-     while (i--) {
 
-       segment = playlist.segments[i];
 
-       if (typeof segment.end !== 'undefined') {
 
-         return {
 
-           result: result + segment.end,
 
-           precise: true
 
-         };
 
-       }
 
-       result += segmentDurationWithParts(playlist, segment);
 
-       if (typeof segment.start !== 'undefined') {
 
-         return {
 
-           result: result + segment.start,
 
-           precise: true
 
-         };
 
-       }
 
-     }
 
-     return {
 
-       result,
 
-       precise: false
 
-     };
 
-   };
 
-   /**
 
-    * walk forward until we find a duration we can use
 
-    * or return a failure
 
-    *
 
-    * @param {Playlist} playlist the playlist to walk through
 
-    * @param {number} endSequence the mediaSequence to stop walking on
 
-    */
 
-   const forwardDuration = function (playlist, endSequence) {
 
-     let result = 0;
 
-     let segment;
 
-     let i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
 
-     // information
 
-     for (; i < playlist.segments.length; i++) {
 
-       segment = playlist.segments[i];
 
-       if (typeof segment.start !== 'undefined') {
 
-         return {
 
-           result: segment.start - result,
 
-           precise: true
 
-         };
 
-       }
 
-       result += segmentDurationWithParts(playlist, segment);
 
-       if (typeof segment.end !== 'undefined') {
 
-         return {
 
-           result: segment.end - result,
 
-           precise: true
 
-         };
 
-       }
 
-     } // indicate we didn't find a useful duration estimate
 
-     return {
 
-       result: -1,
 
-       precise: false
 
-     };
 
-   };
 
-   /**
 
-     * Calculate the media duration from the segments associated with a
 
-     * playlist. The duration of a subinterval of the available segments
 
-     * may be calculated by specifying an end index.
 
-     *
 
-     * @param {Object} playlist a media playlist object
 
-     * @param {number=} endSequence an exclusive upper boundary
 
-     * for the playlist.  Defaults to playlist length.
 
-     * @param {number} expired the amount of time that has dropped
 
-     * off the front of the playlist in a live scenario
 
-     * @return {number} the duration between the first available segment
 
-     * and end index.
 
-     */
 
-   const intervalDuration = function (playlist, endSequence, expired) {
 
-     if (typeof endSequence === 'undefined') {
 
-       endSequence = playlist.mediaSequence + playlist.segments.length;
 
-     }
 
-     if (endSequence < playlist.mediaSequence) {
 
-       return 0;
 
-     } // do a backward walk to estimate the duration
 
-     const backward = backwardDuration(playlist, endSequence);
 
-     if (backward.precise) {
 
-       // if we were able to base our duration estimate on timing
 
-       // information provided directly from the Media Source, return
 
-       // it
 
-       return backward.result;
 
-     } // walk forward to see if a precise duration estimate can be made
 
-     // that way
 
-     const forward = forwardDuration(playlist, endSequence);
 
-     if (forward.precise) {
 
-       // we found a segment that has been buffered and so it's
 
-       // position is known precisely
 
-       return forward.result;
 
-     } // return the less-precise, playlist-based duration estimate
 
-     return backward.result + expired;
 
-   };
 
-   /**
 
-     * Calculates the duration of a playlist. If a start and end index
 
-     * are specified, the duration will be for the subset of the media
 
-     * timeline between those two indices. The total duration for live
 
-     * playlists is always Infinity.
 
-     *
 
-     * @param {Object} playlist a media playlist object
 
-     * @param {number=} endSequence an exclusive upper
 
-     * boundary for the playlist. Defaults to the playlist media
 
-     * sequence number plus its length.
 
-     * @param {number=} expired the amount of time that has
 
-     * dropped off the front of the playlist in a live scenario
 
-     * @return {number} the duration between the start index and end
 
-     * index.
 
-     */
 
-   const duration = function (playlist, endSequence, expired) {
 
-     if (!playlist) {
 
-       return 0;
 
-     }
 
-     if (typeof expired !== 'number') {
 
-       expired = 0;
 
-     } // if a slice of the total duration is not requested, use
 
-     // playlist-level duration indicators when they're present
 
-     if (typeof endSequence === 'undefined') {
 
-       // if present, use the duration specified in the playlist
 
-       if (playlist.totalDuration) {
 
-         return playlist.totalDuration;
 
-       } // duration should be Infinity for live playlists
 
-       if (!playlist.endList) {
 
-         return window.Infinity;
 
-       }
 
-     } // calculate the total duration based on the segment durations
 
-     return intervalDuration(playlist, endSequence, expired);
 
-   };
 
-   /**
 
-     * Calculate the time between two indexes in the current playlist
 
-     * neight the start- nor the end-index need to be within the current
 
-     * playlist in which case, the targetDuration of the playlist is used
 
-     * to approximate the durations of the segments
 
-     *
 
-     * @param {Array} options.durationList list to iterate over for durations.
 
-     * @param {number} options.defaultDuration duration to use for elements before or after the durationList
 
-     * @param {number} options.startIndex partsAndSegments index to start
 
-     * @param {number} options.endIndex partsAndSegments index to end.
 
-     * @return {number} the number of seconds between startIndex and endIndex
 
-     */
 
-   const sumDurations = function ({
 
-     defaultDuration,
 
-     durationList,
 
-     startIndex,
 
-     endIndex
 
-   }) {
 
-     let durations = 0;
 
-     if (startIndex > endIndex) {
 
-       [startIndex, endIndex] = [endIndex, startIndex];
 
-     }
 
-     if (startIndex < 0) {
 
-       for (let i = startIndex; i < Math.min(0, endIndex); i++) {
 
-         durations += defaultDuration;
 
-       }
 
-       startIndex = 0;
 
-     }
 
-     for (let i = startIndex; i < endIndex; i++) {
 
-       durations += durationList[i].duration;
 
-     }
 
-     return durations;
 
-   };
 
-   /**
 
-    * Calculates the playlist end time
 
-    *
 
-    * @param {Object} playlist a media playlist object
 
-    * @param {number=} expired the amount of time that has
 
-    *                  dropped off the front of the playlist in a live scenario
 
-    * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
 
-    *                        playlist end calculation should consider the safe live end
 
-    *                        (truncate the playlist end by three segments). This is normally
 
-    *                        used for calculating the end of the playlist's seekable range.
 
-    *                        This takes into account the value of liveEdgePadding.
 
-    *                        Setting liveEdgePadding to 0 is equivalent to setting this to false.
 
-    * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds.
 
-    *                 If this is provided, it is used in the safe live end calculation.
 
-    *                 Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent.
 
-    *                 Corresponds to suggestedPresentationDelay in DASH manifests.
 
-    * @return {number} the end time of playlist
 
-    * @function playlistEnd
 
-    */
 
-   const playlistEnd = function (playlist, expired, useSafeLiveEnd, liveEdgePadding) {
 
-     if (!playlist || !playlist.segments) {
 
-       return null;
 
-     }
 
-     if (playlist.endList) {
 
-       return duration(playlist);
 
-     }
 
-     if (expired === null) {
 
-       return null;
 
-     }
 
-     expired = expired || 0;
 
-     let lastSegmentEndTime = intervalDuration(playlist, playlist.mediaSequence + playlist.segments.length, expired);
 
-     if (useSafeLiveEnd) {
 
-       liveEdgePadding = typeof liveEdgePadding === 'number' ? liveEdgePadding : liveEdgeDelay(null, playlist);
 
-       lastSegmentEndTime -= liveEdgePadding;
 
-     } // don't return a time less than zero
 
-     return Math.max(0, lastSegmentEndTime);
 
-   };
 
-   /**
 
-     * Calculates the interval of time that is currently seekable in a
 
-     * playlist. The returned time ranges are relative to the earliest
 
-     * moment in the specified playlist that is still available. A full
 
-     * seekable implementation for live streams would need to offset
 
-     * these values by the duration of content that has expired from the
 
-     * stream.
 
-     *
 
-     * @param {Object} playlist a media playlist object
 
-     * dropped off the front of the playlist in a live scenario
 
-     * @param {number=} expired the amount of time that has
 
-     * dropped off the front of the playlist in a live scenario
 
-     * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds.
 
-     *        Corresponds to suggestedPresentationDelay in DASH manifests.
 
-     * @return {TimeRanges} the periods of time that are valid targets
 
-     * for seeking
 
-     */
 
-   const seekable = function (playlist, expired, liveEdgePadding) {
 
-     const useSafeLiveEnd = true;
 
-     const seekableStart = expired || 0;
 
-     const seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
 
-     if (seekableEnd === null) {
 
-       return createTimeRanges();
 
-     }
 
-     return createTimeRanges(seekableStart, seekableEnd);
 
-   };
 
-   /**
 
-    * Determine the index and estimated starting time of the segment that
 
-    * contains a specified playback position in a media playlist.
 
-    *
 
-    * @param {Object} options.playlist the media playlist to query
 
-    * @param {number} options.currentTime The number of seconds since the earliest
 
-    * possible position to determine the containing segment for
 
-    * @param {number} options.startTime the time when the segment/part starts
 
-    * @param {number} options.startingSegmentIndex the segment index to start looking at.
 
-    * @param {number?} [options.startingPartIndex] the part index to look at within the segment.
 
-    *
 
-    * @return {Object} an object with partIndex, segmentIndex, and startTime.
 
-    */
 
-   const getMediaInfoForTime = function ({
 
-     playlist,
 
-     currentTime,
 
-     startingSegmentIndex,
 
-     startingPartIndex,
 
-     startTime,
 
-     exactManifestTimings
 
-   }) {
 
-     let time = currentTime - startTime;
 
-     const partsAndSegments = getPartsAndSegments(playlist);
 
-     let startIndex = 0;
 
-     for (let i = 0; i < partsAndSegments.length; i++) {
 
-       const partAndSegment = partsAndSegments[i];
 
-       if (startingSegmentIndex !== partAndSegment.segmentIndex) {
 
-         continue;
 
-       } // skip this if part index does not match.
 
-       if (typeof startingPartIndex === 'number' && typeof partAndSegment.partIndex === 'number' && startingPartIndex !== partAndSegment.partIndex) {
 
-         continue;
 
-       }
 
-       startIndex = i;
 
-       break;
 
-     }
 
-     if (time < 0) {
 
-       // Walk backward from startIndex in the playlist, adding durations
 
-       // until we find a segment that contains `time` and return it
 
-       if (startIndex > 0) {
 
-         for (let i = startIndex - 1; i >= 0; i--) {
 
-           const partAndSegment = partsAndSegments[i];
 
-           time += partAndSegment.duration;
 
-           if (exactManifestTimings) {
 
-             if (time < 0) {
 
-               continue;
 
-             }
 
-           } else if (time + TIME_FUDGE_FACTOR <= 0) {
 
-             continue;
 
-           }
 
-           return {
 
-             partIndex: partAndSegment.partIndex,
 
-             segmentIndex: partAndSegment.segmentIndex,
 
-             startTime: startTime - sumDurations({
 
-               defaultDuration: playlist.targetDuration,
 
-               durationList: partsAndSegments,
 
-               startIndex,
 
-               endIndex: i
 
-             })
 
-           };
 
-         }
 
-       } // We were unable to find a good segment within the playlist
 
-       // so select the first segment
 
-       return {
 
-         partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
 
-         segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
 
-         startTime: currentTime
 
-       };
 
-     } // When startIndex is negative, we first walk forward to first segment
 
-     // adding target durations. If we "run out of time" before getting to
 
-     // the first segment, return the first segment
 
-     if (startIndex < 0) {
 
-       for (let i = startIndex; i < 0; i++) {
 
-         time -= playlist.targetDuration;
 
-         if (time < 0) {
 
-           return {
 
-             partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
 
-             segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
 
-             startTime: currentTime
 
-           };
 
-         }
 
-       }
 
-       startIndex = 0;
 
-     } // Walk forward from startIndex in the playlist, subtracting durations
 
-     // until we find a segment that contains `time` and return it
 
-     for (let i = startIndex; i < partsAndSegments.length; i++) {
 
-       const partAndSegment = partsAndSegments[i];
 
-       time -= partAndSegment.duration;
 
-       if (exactManifestTimings) {
 
-         if (time > 0) {
 
-           continue;
 
-         }
 
-       } else if (time - TIME_FUDGE_FACTOR >= 0) {
 
-         continue;
 
-       }
 
-       return {
 
-         partIndex: partAndSegment.partIndex,
 
-         segmentIndex: partAndSegment.segmentIndex,
 
-         startTime: startTime + sumDurations({
 
-           defaultDuration: playlist.targetDuration,
 
-           durationList: partsAndSegments,
 
-           startIndex,
 
-           endIndex: i
 
-         })
 
-       };
 
-     } // We are out of possible candidates so load the last one...
 
-     return {
 
-       segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
 
-       partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex,
 
-       startTime: currentTime
 
-     };
 
-   };
 
-   /**
 
-    * Check whether the playlist is excluded or not.
 
-    *
 
-    * @param {Object} playlist the media playlist object
 
-    * @return {boolean} whether the playlist is excluded or not
 
-    * @function isExcluded
 
-    */
 
-   const isExcluded = function (playlist) {
 
-     return playlist.excludeUntil && playlist.excludeUntil > Date.now();
 
-   };
 
-   /**
 
-    * Check whether the playlist is compatible with current playback configuration or has
 
-    * been excluded permanently for being incompatible.
 
-    *
 
-    * @param {Object} playlist the media playlist object
 
-    * @return {boolean} whether the playlist is incompatible or not
 
-    * @function isIncompatible
 
-    */
 
-   const isIncompatible = function (playlist) {
 
-     return playlist.excludeUntil && playlist.excludeUntil === Infinity;
 
-   };
 
-   /**
 
-    * Check whether the playlist is enabled or not.
 
-    *
 
-    * @param {Object} playlist the media playlist object
 
-    * @return {boolean} whether the playlist is enabled or not
 
-    * @function isEnabled
 
-    */
 
-   const isEnabled = function (playlist) {
 
-     const excluded = isExcluded(playlist);
 
-     return !playlist.disabled && !excluded;
 
-   };
 
-   /**
 
-    * Check whether the playlist has been manually disabled through the representations api.
 
-    *
 
-    * @param {Object} playlist the media playlist object
 
-    * @return {boolean} whether the playlist is disabled manually or not
 
-    * @function isDisabled
 
-    */
 
-   const isDisabled = function (playlist) {
 
-     return playlist.disabled;
 
-   };
 
-   /**
 
-    * Returns whether the current playlist is an AES encrypted HLS stream
 
-    *
 
-    * @return {boolean} true if it's an AES encrypted HLS stream
 
-    */
 
-   const isAes = function (media) {
 
-     for (let i = 0; i < media.segments.length; i++) {
 
-       if (media.segments[i].key) {
 
-         return true;
 
-       }
 
-     }
 
-     return false;
 
-   };
 
-   /**
 
-    * Checks if the playlist has a value for the specified attribute
 
-    *
 
-    * @param {string} attr
 
-    *        Attribute to check for
 
-    * @param {Object} playlist
 
-    *        The media playlist object
 
-    * @return {boolean}
 
-    *         Whether the playlist contains a value for the attribute or not
 
-    * @function hasAttribute
 
-    */
 
-   const hasAttribute = function (attr, playlist) {
 
-     return playlist.attributes && playlist.attributes[attr];
 
-   };
 
-   /**
 
-    * Estimates the time required to complete a segment download from the specified playlist
 
-    *
 
-    * @param {number} segmentDuration
 
-    *        Duration of requested segment
 
-    * @param {number} bandwidth
 
-    *        Current measured bandwidth of the player
 
-    * @param {Object} playlist
 
-    *        The media playlist object
 
-    * @param {number=} bytesReceived
 
-    *        Number of bytes already received for the request. Defaults to 0
 
-    * @return {number|NaN}
 
-    *         The estimated time to request the segment. NaN if bandwidth information for
 
-    *         the given playlist is unavailable
 
-    * @function estimateSegmentRequestTime
 
-    */
 
-   const estimateSegmentRequestTime = function (segmentDuration, bandwidth, playlist, bytesReceived = 0) {
 
-     if (!hasAttribute('BANDWIDTH', playlist)) {
 
-       return NaN;
 
-     }
 
-     const size = segmentDuration * playlist.attributes.BANDWIDTH;
 
-     return (size - bytesReceived * 8) / bandwidth;
 
-   };
 
-   /*
 
-    * Returns whether the current playlist is the lowest rendition
 
-    *
 
-    * @return {Boolean} true if on lowest rendition
 
-    */
 
-   const isLowestEnabledRendition = (main, media) => {
 
-     if (main.playlists.length === 1) {
 
-       return true;
 
-     }
 
-     const currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
 
-     return main.playlists.filter(playlist => {
 
-       if (!isEnabled(playlist)) {
 
-         return false;
 
-       }
 
-       return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
 
-     }).length === 0;
 
-   };
 
-   const playlistMatch = (a, b) => {
 
-     // both playlits are null
 
-     // or only one playlist is non-null
 
-     // no match
 
-     if (!a && !b || !a && b || a && !b) {
 
-       return false;
 
-     } // playlist objects are the same, match
 
-     if (a === b) {
 
-       return true;
 
-     } // first try to use id as it should be the most
 
-     // accurate
 
-     if (a.id && b.id && a.id === b.id) {
 
-       return true;
 
-     } // next try to use reslovedUri as it should be the
 
-     // second most accurate.
 
-     if (a.resolvedUri && b.resolvedUri && a.resolvedUri === b.resolvedUri) {
 
-       return true;
 
-     } // finally try to use uri as it should be accurate
 
-     // but might miss a few cases for relative uris
 
-     if (a.uri && b.uri && a.uri === b.uri) {
 
-       return true;
 
-     }
 
-     return false;
 
-   };
 
-   const someAudioVariant = function (main, callback) {
 
-     const AUDIO = main && main.mediaGroups && main.mediaGroups.AUDIO || {};
 
-     let found = false;
 
-     for (const groupName in AUDIO) {
 
-       for (const label in AUDIO[groupName]) {
 
-         found = callback(AUDIO[groupName][label]);
 
-         if (found) {
 
-           break;
 
-         }
 
-       }
 
-       if (found) {
 
-         break;
 
-       }
 
-     }
 
-     return !!found;
 
-   };
 
-   const isAudioOnly = main => {
 
-     // we are audio only if we have no main playlists but do
 
-     // have media group playlists.
 
-     if (!main || !main.playlists || !main.playlists.length) {
 
-       // without audio variants or playlists this
 
-       // is not an audio only main.
 
-       const found = someAudioVariant(main, variant => variant.playlists && variant.playlists.length || variant.uri);
 
-       return found;
 
-     } // if every playlist has only an audio codec it is audio only
 
-     for (let i = 0; i < main.playlists.length; i++) {
 
-       const playlist = main.playlists[i];
 
-       const CODECS = playlist.attributes && playlist.attributes.CODECS; // all codecs are audio, this is an audio playlist.
 
-       if (CODECS && CODECS.split(',').every(c => isAudioCodec(c))) {
 
-         continue;
 
-       } // playlist is in an audio group it is audio only
 
-       const found = someAudioVariant(main, variant => playlistMatch(playlist, variant));
 
-       if (found) {
 
-         continue;
 
-       } // if we make it here this playlist isn't audio and we
 
-       // are not audio only
 
-       return false;
 
-     } // if we make it past every playlist without returning, then
 
-     // this is an audio only playlist.
 
-     return true;
 
-   }; // exports
 
-   var Playlist = {
 
-     liveEdgeDelay,
 
-     duration,
 
-     seekable,
 
-     getMediaInfoForTime,
 
-     isEnabled,
 
-     isDisabled,
 
-     isExcluded,
 
-     isIncompatible,
 
-     playlistEnd,
 
-     isAes,
 
-     hasAttribute,
 
-     estimateSegmentRequestTime,
 
-     isLowestEnabledRendition,
 
-     isAudioOnly,
 
-     playlistMatch,
 
-     segmentDurationWithParts
 
-   };
 
-   const {
 
-     log
 
-   } = videojs;
 
-   const createPlaylistID = (index, uri) => {
 
-     return `${index}-${uri}`;
 
-   }; // default function for creating a group id
 
-   const groupID = (type, group, label) => {
 
-     return `placeholder-uri-${type}-${group}-${label}`;
 
-   };
 
-   /**
 
-    * Parses a given m3u8 playlist
 
-    *
 
-    * @param {Function} [onwarn]
 
-    *        a function to call when the parser triggers a warning event.
 
-    * @param {Function} [oninfo]
 
-    *        a function to call when the parser triggers an info event.
 
-    * @param {string} manifestString
 
-    *        The downloaded manifest string
 
-    * @param {Object[]} [customTagParsers]
 
-    *        An array of custom tag parsers for the m3u8-parser instance
 
-    * @param {Object[]} [customTagMappers]
 
-    *        An array of custom tag mappers for the m3u8-parser instance
 
-    * @param {boolean} [llhls]
 
-    *        Whether to keep ll-hls features in the manifest after parsing.
 
-    * @return {Object}
 
-    *         The manifest object
 
-    */
 
-   const parseManifest = ({
 
-     onwarn,
 
-     oninfo,
 
-     manifestString,
 
-     customTagParsers = [],
 
-     customTagMappers = [],
 
-     llhls
 
-   }) => {
 
-     const parser = new Parser();
 
-     if (onwarn) {
 
-       parser.on('warn', onwarn);
 
-     }
 
-     if (oninfo) {
 
-       parser.on('info', oninfo);
 
-     }
 
-     customTagParsers.forEach(customParser => parser.addParser(customParser));
 
-     customTagMappers.forEach(mapper => parser.addTagMapper(mapper));
 
-     parser.push(manifestString);
 
-     parser.end();
 
-     const manifest = parser.manifest; // remove llhls features from the parsed manifest
 
-     // if we don't want llhls support.
 
-     if (!llhls) {
 
-       ['preloadSegment', 'skip', 'serverControl', 'renditionReports', 'partInf', 'partTargetDuration'].forEach(function (k) {
 
-         if (manifest.hasOwnProperty(k)) {
 
-           delete manifest[k];
 
-         }
 
-       });
 
-       if (manifest.segments) {
 
-         manifest.segments.forEach(function (segment) {
 
-           ['parts', 'preloadHints'].forEach(function (k) {
 
-             if (segment.hasOwnProperty(k)) {
 
-               delete segment[k];
 
-             }
 
-           });
 
-         });
 
-       }
 
-     }
 
-     if (!manifest.targetDuration) {
 
-       let targetDuration = 10;
 
-       if (manifest.segments && manifest.segments.length) {
 
-         targetDuration = manifest.segments.reduce((acc, s) => Math.max(acc, s.duration), 0);
 
-       }
 
-       if (onwarn) {
 
-         onwarn(`manifest has no targetDuration defaulting to ${targetDuration}`);
 
-       }
 
-       manifest.targetDuration = targetDuration;
 
-     }
 
-     const parts = getLastParts(manifest);
 
-     if (parts.length && !manifest.partTargetDuration) {
 
-       const partTargetDuration = parts.reduce((acc, p) => Math.max(acc, p.duration), 0);
 
-       if (onwarn) {
 
-         onwarn(`manifest has no partTargetDuration defaulting to ${partTargetDuration}`);
 
-         log.error('LL-HLS manifest has parts but lacks required #EXT-X-PART-INF:PART-TARGET value. See https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-09#section-4.4.3.7. Playback is not guaranteed.');
 
-       }
 
-       manifest.partTargetDuration = partTargetDuration;
 
-     }
 
-     return manifest;
 
-   };
 
-   /**
 
-    * Loops through all supported media groups in main and calls the provided
 
-    * callback for each group
 
-    *
 
-    * @param {Object} main
 
-    *        The parsed main manifest object
 
-    * @param {Function} callback
 
-    *        Callback to call for each media group
 
-    */
 
-   const forEachMediaGroup = (main, callback) => {
 
-     if (!main.mediaGroups) {
 
-       return;
 
-     }
 
-     ['AUDIO', 'SUBTITLES'].forEach(mediaType => {
 
-       if (!main.mediaGroups[mediaType]) {
 
-         return;
 
-       }
 
-       for (const groupKey in main.mediaGroups[mediaType]) {
 
-         for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
 
-           const mediaProperties = main.mediaGroups[mediaType][groupKey][labelKey];
 
-           callback(mediaProperties, mediaType, groupKey, labelKey);
 
-         }
 
-       }
 
-     });
 
-   };
 
-   /**
 
-    * Adds properties and attributes to the playlist to keep consistent functionality for
 
-    * playlists throughout VHS.
 
-    *
 
-    * @param {Object} config
 
-    *        Arguments object
 
-    * @param {Object} config.playlist
 
-    *        The media playlist
 
-    * @param {string} [config.uri]
 
-    *        The uri to the media playlist (if media playlist is not from within a main
 
-    *        playlist)
 
-    * @param {string} id
 
-    *        ID to use for the playlist
 
-    */
 
-   const setupMediaPlaylist = ({
 
-     playlist,
 
-     uri,
 
-     id
 
-   }) => {
 
-     playlist.id = id;
 
-     playlist.playlistErrors_ = 0;
 
-     if (uri) {
 
-       // For media playlists, m3u8-parser does not have access to a URI, as HLS media
 
-       // playlists do not contain their own source URI, but one is needed for consistency in
 
-       // VHS.
 
-       playlist.uri = uri;
 
-     } // For HLS main playlists, even though certain attributes MUST be defined, the
 
-     // stream may still be played without them.
 
-     // For HLS media playlists, m3u8-parser does not attach an attributes object to the
 
-     // manifest.
 
-     //
 
-     // To avoid undefined reference errors through the project, and make the code easier
 
-     // to write/read, add an empty attributes object for these cases.
 
-     playlist.attributes = playlist.attributes || {};
 
-   };
 
-   /**
 
-    * Adds ID, resolvedUri, and attributes properties to each playlist of the main, where
 
-    * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to
 
-    * playlist references to the playlists array.
 
-    *
 
-    * @param {Object} main
 
-    *        The main playlist
 
-    */
 
-   const setupMediaPlaylists = main => {
 
-     let i = main.playlists.length;
 
-     while (i--) {
 
-       const playlist = main.playlists[i];
 
-       setupMediaPlaylist({
 
-         playlist,
 
-         id: createPlaylistID(i, playlist.uri)
 
-       });
 
-       playlist.resolvedUri = resolveUrl(main.uri, playlist.uri);
 
-       main.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility
 
-       main.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute,
 
-       // the stream can be played without it. Although an attributes property may have been
 
-       // added to the playlist to prevent undefined references, issue a warning to fix the
 
-       // manifest.
 
-       if (!playlist.attributes.BANDWIDTH) {
 
-         log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
 
-       }
 
-     }
 
-   };
 
-   /**
 
-    * Adds resolvedUri properties to each media group.
 
-    *
 
-    * @param {Object} main
 
-    *        The main playlist
 
-    */
 
-   const resolveMediaGroupUris = main => {
 
-     forEachMediaGroup(main, properties => {
 
-       if (properties.uri) {
 
-         properties.resolvedUri = resolveUrl(main.uri, properties.uri);
 
-       }
 
-     });
 
-   };
 
-   /**
 
-    * Creates a main playlist wrapper to insert a sole media playlist into.
 
-    *
 
-    * @param {Object} media
 
-    *        Media playlist
 
-    * @param {string} uri
 
-    *        The media URI
 
-    *
 
-    * @return {Object}
 
-    *         main playlist
 
-    */
 
-   const mainForMedia = (media, uri) => {
 
-     const id = createPlaylistID(0, uri);
 
-     const main = {
 
-       mediaGroups: {
 
-         'AUDIO': {},
 
-         'VIDEO': {},
 
-         'CLOSED-CAPTIONS': {},
 
-         'SUBTITLES': {}
 
-       },
 
-       uri: window.location.href,
 
-       resolvedUri: window.location.href,
 
-       playlists: [{
 
-         uri,
 
-         id,
 
-         resolvedUri: uri,
 
-         // m3u8-parser does not attach an attributes property to media playlists so make
 
-         // sure that the property is attached to avoid undefined reference errors
 
-         attributes: {}
 
-       }]
 
-     }; // set up ID reference
 
-     main.playlists[id] = main.playlists[0]; // URI reference added for backwards compatibility
 
-     main.playlists[uri] = main.playlists[0];
 
-     return main;
 
-   };
 
-   /**
 
-    * Does an in-place update of the main manifest to add updated playlist URI references
 
-    * as well as other properties needed by VHS that aren't included by the parser.
 
-    *
 
-    * @param {Object} main
 
-    *        main manifest object
 
-    * @param {string} uri
 
-    *        The source URI
 
-    * @param {function} createGroupID
 
-    *        A function to determine how to create the groupID for mediaGroups
 
-    */
 
-   const addPropertiesToMain = (main, uri, createGroupID = groupID) => {
 
-     main.uri = uri;
 
-     for (let i = 0; i < main.playlists.length; i++) {
 
-       if (!main.playlists[i].uri) {
 
-         // Set up phony URIs for the playlists since playlists are referenced by their URIs
 
-         // throughout VHS, but some formats (e.g., DASH) don't have external URIs
 
-         // TODO: consider adding dummy URIs in mpd-parser
 
-         const phonyUri = `placeholder-uri-${i}`;
 
-         main.playlists[i].uri = phonyUri;
 
-       }
 
-     }
 
-     const audioOnlyMain = isAudioOnly(main);
 
-     forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
 
-       // add a playlist array under properties
 
-       if (!properties.playlists || !properties.playlists.length) {
 
-         // If the manifest is audio only and this media group does not have a uri, check
 
-         // if the media group is located in the main list of playlists. If it is, don't add
 
-         // placeholder properties as it shouldn't be considered an alternate audio track.
 
-         if (audioOnlyMain && mediaType === 'AUDIO' && !properties.uri) {
 
-           for (let i = 0; i < main.playlists.length; i++) {
 
-             const p = main.playlists[i];
 
-             if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) {
 
-               return;
 
-             }
 
-           }
 
-         }
 
-         properties.playlists = [_extends$1({}, properties)];
 
-       }
 
-       properties.playlists.forEach(function (p, i) {
 
-         const groupId = createGroupID(mediaType, groupKey, labelKey, p);
 
-         const id = createPlaylistID(i, groupId);
 
-         if (p.uri) {
 
-           p.resolvedUri = p.resolvedUri || resolveUrl(main.uri, p.uri);
 
-         } else {
 
-           // DEPRECATED, this has been added to prevent a breaking change.
 
-           // previously we only ever had a single media group playlist, so
 
-           // we mark the first playlist uri without prepending the index as we used to
 
-           // ideally we would do all of the playlists the same way.
 
-           p.uri = i === 0 ? groupId : id; // don't resolve a placeholder uri to an absolute url, just use
 
-           // the placeholder again
 
-           p.resolvedUri = p.uri;
 
-         }
 
-         p.id = p.id || id; // add an empty attributes object, all playlists are
 
-         // expected to have this.
 
-         p.attributes = p.attributes || {}; // setup ID and URI references (URI for backwards compatibility)
 
-         main.playlists[p.id] = p;
 
-         main.playlists[p.uri] = p;
 
-       });
 
-     });
 
-     setupMediaPlaylists(main);
 
-     resolveMediaGroupUris(main);
 
-   };
 
-   /**
 
-    * @file playlist-loader.js
 
-    *
 
-    * A state machine that manages the loading, caching, and updating of
 
-    * M3U8 playlists.
 
-    *
 
-    */
 
-   const {
 
-     EventTarget: EventTarget$1
 
-   } = videojs;
 
-   const addLLHLSQueryDirectives = (uri, media) => {
 
-     if (media.endList || !media.serverControl) {
 
-       return uri;
 
-     }
 
-     const parameters = {};
 
-     if (media.serverControl.canBlockReload) {
 
-       const {
 
-         preloadSegment
 
-       } = media; // next msn is a zero based value, length is not.
 
-       let nextMSN = media.mediaSequence + media.segments.length; // If preload segment has parts then it is likely
 
-       // that we are going to request a part of that preload segment.
 
-       // the logic below is used to determine that.
 
-       if (preloadSegment) {
 
-         const parts = preloadSegment.parts || []; // _HLS_part is a zero based index
 
-         const nextPart = getKnownPartCount(media) - 1; // if nextPart is > -1 and not equal to just the
 
-         // length of parts, then we know we had part preload hints
 
-         // and we need to add the _HLS_part= query
 
-         if (nextPart > -1 && nextPart !== parts.length - 1) {
 
-           // add existing parts to our preload hints
 
-           // eslint-disable-next-line
 
-           parameters._HLS_part = nextPart;
 
-         } // this if statement makes sure that we request the msn
 
-         // of the preload segment if:
 
-         // 1. the preload segment had parts (and was not yet a full segment)
 
-         //    but was added to our segments array
 
-         // 2. the preload segment had preload hints for parts that are not in
 
-         //    the manifest yet.
 
-         // in all other cases we want the segment after the preload segment
 
-         // which will be given by using media.segments.length because it is 1 based
 
-         // rather than 0 based.
 
-         if (nextPart > -1 || parts.length) {
 
-           nextMSN--;
 
-         }
 
-       } // add _HLS_msn= in front of any _HLS_part query
 
-       // eslint-disable-next-line
 
-       parameters._HLS_msn = nextMSN;
 
-     }
 
-     if (media.serverControl && media.serverControl.canSkipUntil) {
 
-       // add _HLS_skip= infront of all other queries.
 
-       // eslint-disable-next-line
 
-       parameters._HLS_skip = media.serverControl.canSkipDateranges ? 'v2' : 'YES';
 
-     }
 
-     if (Object.keys(parameters).length) {
 
-       const parsedUri = new window.URL(uri);
 
-       ['_HLS_skip', '_HLS_msn', '_HLS_part'].forEach(function (name) {
 
-         if (!parameters.hasOwnProperty(name)) {
 
-           return;
 
-         }
 
-         parsedUri.searchParams.set(name, parameters[name]);
 
-       });
 
-       uri = parsedUri.toString();
 
-     }
 
-     return uri;
 
-   };
 
-   /**
 
-    * Returns a new segment object with properties and
 
-    * the parts array merged.
 
-    *
 
-    * @param {Object} a the old segment
 
-    * @param {Object} b the new segment
 
-    *
 
-    * @return {Object} the merged segment
 
-    */
 
-   const updateSegment = (a, b) => {
 
-     if (!a) {
 
-       return b;
 
-     }
 
-     const result = merge(a, b); // if only the old segment has preload hints
 
-     // and the new one does not, remove preload hints.
 
-     if (a.preloadHints && !b.preloadHints) {
 
-       delete result.preloadHints;
 
-     } // if only the old segment has parts
 
-     // then the parts are no longer valid
 
-     if (a.parts && !b.parts) {
 
-       delete result.parts; // if both segments have parts
 
-       // copy part propeties from the old segment
 
-       // to the new one.
 
-     } else if (a.parts && b.parts) {
 
-       for (let i = 0; i < b.parts.length; i++) {
 
-         if (a.parts && a.parts[i]) {
 
-           result.parts[i] = merge(a.parts[i], b.parts[i]);
 
-         }
 
-       }
 
-     } // set skipped to false for segments that have
 
-     // have had information merged from the old segment.
 
-     if (!a.skipped && b.skipped) {
 
-       result.skipped = false;
 
-     } // set preload to false for segments that have
 
-     // had information added in the new segment.
 
-     if (a.preload && !b.preload) {
 
-       result.preload = false;
 
-     }
 
-     return result;
 
-   };
 
-   /**
 
-    * Returns a new array of segments that is the result of merging
 
-    * properties from an older list of segments onto an updated
 
-    * list. No properties on the updated playlist will be ovewritten.
 
-    *
 
-    * @param {Array} original the outdated list of segments
 
-    * @param {Array} update the updated list of segments
 
-    * @param {number=} offset the index of the first update
 
-    * segment in the original segment list. For non-live playlists,
 
-    * this should always be zero and does not need to be
 
-    * specified. For live playlists, it should be the difference
 
-    * between the media sequence numbers in the original and updated
 
-    * playlists.
 
-    * @return {Array} a list of merged segment objects
 
-    */
 
-   const updateSegments = (original, update, offset) => {
 
-     const oldSegments = original.slice();
 
-     const newSegments = update.slice();
 
-     offset = offset || 0;
 
-     const result = [];
 
-     let currentMap;
 
-     for (let newIndex = 0; newIndex < newSegments.length; newIndex++) {
 
-       const oldSegment = oldSegments[newIndex + offset];
 
-       const newSegment = newSegments[newIndex];
 
-       if (oldSegment) {
 
-         currentMap = oldSegment.map || currentMap;
 
-         result.push(updateSegment(oldSegment, newSegment));
 
-       } else {
 
-         // carry over map to new segment if it is missing
 
-         if (currentMap && !newSegment.map) {
 
-           newSegment.map = currentMap;
 
-         }
 
-         result.push(newSegment);
 
-       }
 
-     }
 
-     return result;
 
-   };
 
-   const resolveSegmentUris = (segment, baseUri) => {
 
-     // preloadSegment will not have a uri at all
 
-     // as the segment isn't actually in the manifest yet, only parts
 
-     if (!segment.resolvedUri && segment.uri) {
 
-       segment.resolvedUri = resolveUrl(baseUri, segment.uri);
 
-     }
 
-     if (segment.key && !segment.key.resolvedUri) {
 
-       segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri);
 
-     }
 
-     if (segment.map && !segment.map.resolvedUri) {
 
-       segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri);
 
-     }
 
-     if (segment.map && segment.map.key && !segment.map.key.resolvedUri) {
 
-       segment.map.key.resolvedUri = resolveUrl(baseUri, segment.map.key.uri);
 
-     }
 
-     if (segment.parts && segment.parts.length) {
 
-       segment.parts.forEach(p => {
 
-         if (p.resolvedUri) {
 
-           return;
 
-         }
 
-         p.resolvedUri = resolveUrl(baseUri, p.uri);
 
-       });
 
-     }
 
-     if (segment.preloadHints && segment.preloadHints.length) {
 
-       segment.preloadHints.forEach(p => {
 
-         if (p.resolvedUri) {
 
-           return;
 
-         }
 
-         p.resolvedUri = resolveUrl(baseUri, p.uri);
 
-       });
 
-     }
 
-   };
 
-   const getAllSegments = function (media) {
 
-     const segments = media.segments || [];
 
-     const preloadSegment = media.preloadSegment; // a preloadSegment with only preloadHints is not currently
 
-     // a usable segment, only include a preloadSegment that has
 
-     // parts.
 
-     if (preloadSegment && preloadSegment.parts && preloadSegment.parts.length) {
 
-       // if preloadHints has a MAP that means that the
 
-       // init segment is going to change. We cannot use any of the parts
 
-       // from this preload segment.
 
-       if (preloadSegment.preloadHints) {
 
-         for (let i = 0; i < preloadSegment.preloadHints.length; i++) {
 
-           if (preloadSegment.preloadHints[i].type === 'MAP') {
 
-             return segments;
 
-           }
 
-         }
 
-       } // set the duration for our preload segment to target duration.
 
-       preloadSegment.duration = media.targetDuration;
 
-       preloadSegment.preload = true;
 
-       segments.push(preloadSegment);
 
-     }
 
-     return segments;
 
-   }; // consider the playlist unchanged if the playlist object is the same or
 
-   // the number of segments is equal, the media sequence number is unchanged,
 
-   // and this playlist hasn't become the end of the playlist
 
-   const isPlaylistUnchanged = (a, b) => a === b || a.segments && b.segments && a.segments.length === b.segments.length && a.endList === b.endList && a.mediaSequence === b.mediaSequence && a.preloadSegment === b.preloadSegment;
 
-   /**
 
-     * Returns a new main playlist that is the result of merging an
 
-     * updated media playlist into the original version. If the
 
-     * updated media playlist does not match any of the playlist
 
-     * entries in the original main playlist, null is returned.
 
-     *
 
-     * @param {Object} main a parsed main M3U8 object
 
-     * @param {Object} media a parsed media M3U8 object
 
-     * @return {Object} a new object that represents the original
 
-     * main playlist with the updated media playlist merged in, or
 
-     * null if the merge produced no change.
 
-     */
 
-   const updateMain$1 = (main, newMedia, unchangedCheck = isPlaylistUnchanged) => {
 
-     const result = merge(main, {});
 
-     const oldMedia = result.playlists[newMedia.id];
 
-     if (!oldMedia) {
 
-       return null;
 
-     }
 
-     if (unchangedCheck(oldMedia, newMedia)) {
 
-       return null;
 
-     }
 
-     newMedia.segments = getAllSegments(newMedia);
 
-     const mergedPlaylist = merge(oldMedia, newMedia); // always use the new media's preload segment
 
-     if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) {
 
-       delete mergedPlaylist.preloadSegment;
 
-     } // if the update could overlap existing segment information, merge the two segment lists
 
-     if (oldMedia.segments) {
 
-       if (newMedia.skip) {
 
-         newMedia.segments = newMedia.segments || []; // add back in objects for skipped segments, so that we merge
 
-         // old properties into the new segments
 
-         for (let i = 0; i < newMedia.skip.skippedSegments; i++) {
 
-           newMedia.segments.unshift({
 
-             skipped: true
 
-           });
 
-         }
 
-       }
 
-       mergedPlaylist.segments = updateSegments(oldMedia.segments, newMedia.segments, newMedia.mediaSequence - oldMedia.mediaSequence);
 
-     } // resolve any segment URIs to prevent us from having to do it later
 
-     mergedPlaylist.segments.forEach(segment => {
 
-       resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
 
-     }); // TODO Right now in the playlists array there are two references to each playlist, one
 
-     // that is referenced by index, and one by URI. The index reference may no longer be
 
-     // necessary.
 
-     for (let i = 0; i < result.playlists.length; i++) {
 
-       if (result.playlists[i].id === newMedia.id) {
 
-         result.playlists[i] = mergedPlaylist;
 
-       }
 
-     }
 
-     result.playlists[newMedia.id] = mergedPlaylist; // URI reference added for backwards compatibility
 
-     result.playlists[newMedia.uri] = mergedPlaylist; // update media group playlist references.
 
-     forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
 
-       if (!properties.playlists) {
 
-         return;
 
-       }
 
-       for (let i = 0; i < properties.playlists.length; i++) {
 
-         if (newMedia.id === properties.playlists[i].id) {
 
-           properties.playlists[i] = mergedPlaylist;
 
-         }
 
-       }
 
-     });
 
-     return result;
 
-   };
 
-   /**
 
-    * Calculates the time to wait before refreshing a live playlist
 
-    *
 
-    * @param {Object} media
 
-    *        The current media
 
-    * @param {boolean} update
 
-    *        True if there were any updates from the last refresh, false otherwise
 
-    * @return {number}
 
-    *         The time in ms to wait before refreshing the live playlist
 
-    */
 
-   const refreshDelay = (media, update) => {
 
-     const segments = media.segments || [];
 
-     const lastSegment = segments[segments.length - 1];
 
-     const lastPart = lastSegment && lastSegment.parts && lastSegment.parts[lastSegment.parts.length - 1];
 
-     const lastDuration = lastPart && lastPart.duration || lastSegment && lastSegment.duration;
 
-     if (update && lastDuration) {
 
-       return lastDuration * 1000;
 
-     } // if the playlist is unchanged since the last reload or last segment duration
 
-     // cannot be determined, try again after half the target duration
 
-     return (media.partTargetDuration || media.targetDuration || 10) * 500;
 
-   };
 
-   /**
 
-    * Load a playlist from a remote location
 
-    *
 
-    * @class PlaylistLoader
 
-    * @extends Stream
 
-    * @param {string|Object} src url or object of manifest
 
-    * @param {boolean} withCredentials the withCredentials xhr option
 
-    * @class
 
-    */
 
-   class PlaylistLoader extends EventTarget$1 {
 
-     constructor(src, vhs, options = {}) {
 
-       super();
 
-       if (!src) {
 
-         throw new Error('A non-empty playlist URL or object is required');
 
-       }
 
-       this.logger_ = logger('PlaylistLoader');
 
-       const {
 
-         withCredentials = false
 
-       } = options;
 
-       this.src = src;
 
-       this.vhs_ = vhs;
 
-       this.withCredentials = withCredentials;
 
-       const vhsOptions = vhs.options_;
 
-       this.customTagParsers = vhsOptions && vhsOptions.customTagParsers || [];
 
-       this.customTagMappers = vhsOptions && vhsOptions.customTagMappers || [];
 
-       this.llhls = vhsOptions && vhsOptions.llhls; // initialize the loader state
 
-       this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
 
-       this.handleMediaupdatetimeout_ = this.handleMediaupdatetimeout_.bind(this);
 
-       this.on('mediaupdatetimeout', this.handleMediaupdatetimeout_);
 
-     }
 
-     handleMediaupdatetimeout_() {
 
-       if (this.state !== 'HAVE_METADATA') {
 
-         // only refresh the media playlist if no other activity is going on
 
-         return;
 
-       }
 
-       const media = this.media();
 
-       let uri = resolveUrl(this.main.uri, media.uri);
 
-       if (this.llhls) {
 
-         uri = addLLHLSQueryDirectives(uri, media);
 
-       }
 
-       this.state = 'HAVE_CURRENT_METADATA';
 
-       this.request = this.vhs_.xhr({
 
-         uri,
 
-         withCredentials: this.withCredentials
 
-       }, (error, req) => {
 
-         // disposed
 
-         if (!this.request) {
 
-           return;
 
-         }
 
-         if (error) {
 
-           return this.playlistRequestError(this.request, this.media(), 'HAVE_METADATA');
 
-         }
 
-         this.haveMetadata({
 
-           playlistString: this.request.responseText,
 
-           url: this.media().uri,
 
-           id: this.media().id
 
-         });
 
-       });
 
-     }
 
-     playlistRequestError(xhr, playlist, startingState) {
 
-       const {
 
-         uri,
 
-         id
 
-       } = playlist; // any in-flight request is now finished
 
-       this.request = null;
 
-       if (startingState) {
 
-         this.state = startingState;
 
-       }
 
-       this.error = {
 
-         playlist: this.main.playlists[id],
 
-         status: xhr.status,
 
-         message: `HLS playlist request error at URL: ${uri}.`,
 
-         responseText: xhr.responseText,
 
-         code: xhr.status >= 500 ? 4 : 2
 
-       };
 
-       this.trigger('error');
 
-     }
 
-     parseManifest_({
 
-       url,
 
-       manifestString
 
-     }) {
 
-       return parseManifest({
 
-         onwarn: ({
 
-           message
 
-         }) => this.logger_(`m3u8-parser warn for ${url}: ${message}`),
 
-         oninfo: ({
 
-           message
 
-         }) => this.logger_(`m3u8-parser info for ${url}: ${message}`),
 
-         manifestString,
 
-         customTagParsers: this.customTagParsers,
 
-         customTagMappers: this.customTagMappers,
 
-         llhls: this.llhls
 
-       });
 
-     }
 
-     /**
 
-      * Update the playlist loader's state in response to a new or updated playlist.
 
-      *
 
-      * @param {string} [playlistString]
 
-      *        Playlist string (if playlistObject is not provided)
 
-      * @param {Object} [playlistObject]
 
-      *        Playlist object (if playlistString is not provided)
 
-      * @param {string} url
 
-      *        URL of playlist
 
-      * @param {string} id
 
-      *        ID to use for playlist
 
-      */
 
-     haveMetadata({
 
-       playlistString,
 
-       playlistObject,
 
-       url,
 
-       id
 
-     }) {
 
-       // any in-flight request is now finished
 
-       this.request = null;
 
-       this.state = 'HAVE_METADATA';
 
-       const playlist = playlistObject || this.parseManifest_({
 
-         url,
 
-         manifestString: playlistString
 
-       });
 
-       playlist.lastRequest = Date.now();
 
-       setupMediaPlaylist({
 
-         playlist,
 
-         uri: url,
 
-         id
 
-       }); // merge this playlist into the main manifest
 
-       const update = updateMain$1(this.main, playlist);
 
-       this.targetDuration = playlist.partTargetDuration || playlist.targetDuration;
 
-       this.pendingMedia_ = null;
 
-       if (update) {
 
-         this.main = update;
 
-         this.media_ = this.main.playlists[id];
 
-       } else {
 
-         this.trigger('playlistunchanged');
 
-       }
 
-       this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update));
 
-       this.trigger('loadedplaylist');
 
-     }
 
-     /**
 
-       * Abort any outstanding work and clean up.
 
-       */
 
-     dispose() {
 
-       this.trigger('dispose');
 
-       this.stopRequest();
 
-       window.clearTimeout(this.mediaUpdateTimeout);
 
-       window.clearTimeout(this.finalRenditionTimeout);
 
-       this.off();
 
-     }
 
-     stopRequest() {
 
-       if (this.request) {
 
-         const oldRequest = this.request;
 
-         this.request = null;
 
-         oldRequest.onreadystatechange = null;
 
-         oldRequest.abort();
 
-       }
 
-     }
 
-     /**
 
-       * When called without any arguments, returns the currently
 
-       * active media playlist. When called with a single argument,
 
-       * triggers the playlist loader to asynchronously switch to the
 
-       * specified media playlist. Calling this method while the
 
-       * loader is in the HAVE_NOTHING causes an error to be emitted
 
-       * but otherwise has no effect.
 
-       *
 
-       * @param {Object=} playlist the parsed media playlist
 
-       * object to switch to
 
-       * @param {boolean=} shouldDelay whether we should delay the request by half target duration
 
-       *
 
-       * @return {Playlist} the current loaded media
 
-       */
 
-     media(playlist, shouldDelay) {
 
-       // getter
 
-       if (!playlist) {
 
-         return this.media_;
 
-       } // setter
 
-       if (this.state === 'HAVE_NOTHING') {
 
-         throw new Error('Cannot switch media playlist from ' + this.state);
 
-       } // find the playlist object if the target playlist has been
 
-       // specified by URI
 
-       if (typeof playlist === 'string') {
 
-         if (!this.main.playlists[playlist]) {
 
-           throw new Error('Unknown playlist URI: ' + playlist);
 
-         }
 
-         playlist = this.main.playlists[playlist];
 
-       }
 
-       window.clearTimeout(this.finalRenditionTimeout);
 
-       if (shouldDelay) {
 
-         const delay = (playlist.partTargetDuration || playlist.targetDuration) / 2 * 1000 || 5 * 1000;
 
-         this.finalRenditionTimeout = window.setTimeout(this.media.bind(this, playlist, false), delay);
 
-         return;
 
-       }
 
-       const startingState = this.state;
 
-       const mediaChange = !this.media_ || playlist.id !== this.media_.id;
 
-       const mainPlaylistRef = this.main.playlists[playlist.id]; // switch to fully loaded playlists immediately
 
-       if (mainPlaylistRef && mainPlaylistRef.endList ||
 
-       // handle the case of a playlist object (e.g., if using vhs-json with a resolved
 
-       // media playlist or, for the case of demuxed audio, a resolved audio media group)
 
-       playlist.endList && playlist.segments.length) {
 
-         // abort outstanding playlist requests
 
-         if (this.request) {
 
-           this.request.onreadystatechange = null;
 
-           this.request.abort();
 
-           this.request = null;
 
-         }
 
-         this.state = 'HAVE_METADATA';
 
-         this.media_ = playlist; // trigger media change if the active media has been updated
 
-         if (mediaChange) {
 
-           this.trigger('mediachanging');
 
-           if (startingState === 'HAVE_MAIN_MANIFEST') {
 
-             // The initial playlist was a main manifest, and the first media selected was
 
-             // also provided (in the form of a resolved playlist object) as part of the
 
-             // source object (rather than just a URL). Therefore, since the media playlist
 
-             // doesn't need to be requested, loadedmetadata won't trigger as part of the
 
-             // normal flow, and needs an explicit trigger here.
 
-             this.trigger('loadedmetadata');
 
-           } else {
 
-             this.trigger('mediachange');
 
-           }
 
-         }
 
-         return;
 
-       } // We update/set the timeout here so that live playlists
 
-       // that are not a media change will "start" the loader as expected.
 
-       // We expect that this function will start the media update timeout
 
-       // cycle again. This also prevents a playlist switch failure from
 
-       // causing us to stall during live.
 
-       this.updateMediaUpdateTimeout_(refreshDelay(playlist, true)); // switching to the active playlist is a no-op
 
-       if (!mediaChange) {
 
-         return;
 
-       }
 
-       this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
 
-       if (this.request) {
 
-         if (playlist.resolvedUri === this.request.url) {
 
-           // requesting to switch to the same playlist multiple times
 
-           // has no effect after the first
 
-           return;
 
-         }
 
-         this.request.onreadystatechange = null;
 
-         this.request.abort();
 
-         this.request = null;
 
-       } // request the new playlist
 
-       if (this.media_) {
 
-         this.trigger('mediachanging');
 
-       }
 
-       this.pendingMedia_ = playlist;
 
-       this.request = this.vhs_.xhr({
 
-         uri: playlist.resolvedUri,
 
-         withCredentials: this.withCredentials
 
-       }, (error, req) => {
 
-         // disposed
 
-         if (!this.request) {
 
-           return;
 
-         }
 
-         playlist.lastRequest = Date.now();
 
-         playlist.resolvedUri = resolveManifestRedirect(playlist.resolvedUri, req);
 
-         if (error) {
 
-           return this.playlistRequestError(this.request, playlist, startingState);
 
-         }
 
-         this.haveMetadata({
 
-           playlistString: req.responseText,
 
-           url: playlist.uri,
 
-           id: playlist.id
 
-         }); // fire loadedmetadata the first time a media playlist is loaded
 
-         if (startingState === 'HAVE_MAIN_MANIFEST') {
 
-           this.trigger('loadedmetadata');
 
-         } else {
 
-           this.trigger('mediachange');
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * pause loading of the playlist
 
-      */
 
-     pause() {
 
-       if (this.mediaUpdateTimeout) {
 
-         window.clearTimeout(this.mediaUpdateTimeout);
 
-         this.mediaUpdateTimeout = null;
 
-       }
 
-       this.stopRequest();
 
-       if (this.state === 'HAVE_NOTHING') {
 
-         // If we pause the loader before any data has been retrieved, its as if we never
 
-         // started, so reset to an unstarted state.
 
-         this.started = false;
 
-       } // Need to restore state now that no activity is happening
 
-       if (this.state === 'SWITCHING_MEDIA') {
 
-         // if the loader was in the process of switching media, it should either return to
 
-         // HAVE_MAIN_MANIFEST or HAVE_METADATA depending on if the loader has loaded a media
 
-         // playlist yet. This is determined by the existence of loader.media_
 
-         if (this.media_) {
 
-           this.state = 'HAVE_METADATA';
 
-         } else {
 
-           this.state = 'HAVE_MAIN_MANIFEST';
 
-         }
 
-       } else if (this.state === 'HAVE_CURRENT_METADATA') {
 
-         this.state = 'HAVE_METADATA';
 
-       }
 
-     }
 
-     /**
 
-      * start loading of the playlist
 
-      */
 
-     load(shouldDelay) {
 
-       if (this.mediaUpdateTimeout) {
 
-         window.clearTimeout(this.mediaUpdateTimeout);
 
-         this.mediaUpdateTimeout = null;
 
-       }
 
-       const media = this.media();
 
-       if (shouldDelay) {
 
-         const delay = media ? (media.partTargetDuration || media.targetDuration) / 2 * 1000 : 5 * 1000;
 
-         this.mediaUpdateTimeout = window.setTimeout(() => {
 
-           this.mediaUpdateTimeout = null;
 
-           this.load();
 
-         }, delay);
 
-         return;
 
-       }
 
-       if (!this.started) {
 
-         this.start();
 
-         return;
 
-       }
 
-       if (media && !media.endList) {
 
-         this.trigger('mediaupdatetimeout');
 
-       } else {
 
-         this.trigger('loadedplaylist');
 
-       }
 
-     }
 
-     updateMediaUpdateTimeout_(delay) {
 
-       if (this.mediaUpdateTimeout) {
 
-         window.clearTimeout(this.mediaUpdateTimeout);
 
-         this.mediaUpdateTimeout = null;
 
-       } // we only have use mediaupdatetimeout for live playlists.
 
-       if (!this.media() || this.media().endList) {
 
-         return;
 
-       }
 
-       this.mediaUpdateTimeout = window.setTimeout(() => {
 
-         this.mediaUpdateTimeout = null;
 
-         this.trigger('mediaupdatetimeout');
 
-         this.updateMediaUpdateTimeout_(delay);
 
-       }, delay);
 
-     }
 
-     /**
 
-      * start loading of the playlist
 
-      */
 
-     start() {
 
-       this.started = true;
 
-       if (typeof this.src === 'object') {
 
-         // in the case of an entirely constructed manifest object (meaning there's no actual
 
-         // manifest on a server), default the uri to the page's href
 
-         if (!this.src.uri) {
 
-           this.src.uri = window.location.href;
 
-         } // resolvedUri is added on internally after the initial request. Since there's no
 
-         // request for pre-resolved manifests, add on resolvedUri here.
 
-         this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first
 
-         // request can be skipped (since the top level of the manifest, at a minimum, is
 
-         // already available as a parsed manifest object). However, if the manifest object
 
-         // represents a main playlist, some media playlists may need to be resolved before
 
-         // the starting segment list is available. Therefore, go directly to setup of the
 
-         // initial playlist, and let the normal flow continue from there.
 
-         //
 
-         // Note that the call to setup is asynchronous, as other sections of VHS may assume
 
-         // that the first request is asynchronous.
 
-         setTimeout(() => {
 
-           this.setupInitialPlaylist(this.src);
 
-         }, 0);
 
-         return;
 
-       } // request the specified URL
 
-       this.request = this.vhs_.xhr({
 
-         uri: this.src,
 
-         withCredentials: this.withCredentials
 
-       }, (error, req) => {
 
-         // disposed
 
-         if (!this.request) {
 
-           return;
 
-         } // clear the loader's request reference
 
-         this.request = null;
 
-         if (error) {
 
-           this.error = {
 
-             status: req.status,
 
-             message: `HLS playlist request error at URL: ${this.src}.`,
 
-             responseText: req.responseText,
 
-             // MEDIA_ERR_NETWORK
 
-             code: 2
 
-           };
 
-           if (this.state === 'HAVE_NOTHING') {
 
-             this.started = false;
 
-           }
 
-           return this.trigger('error');
 
-         }
 
-         this.src = resolveManifestRedirect(this.src, req);
 
-         const manifest = this.parseManifest_({
 
-           manifestString: req.responseText,
 
-           url: this.src
 
-         });
 
-         this.setupInitialPlaylist(manifest);
 
-       });
 
-     }
 
-     srcUri() {
 
-       return typeof this.src === 'string' ? this.src : this.src.uri;
 
-     }
 
-     /**
 
-      * Given a manifest object that's either a main or media playlist, trigger the proper
 
-      * events and set the state of the playlist loader.
 
-      *
 
-      * If the manifest object represents a main playlist, `loadedplaylist` will be
 
-      * triggered to allow listeners to select a playlist. If none is selected, the loader
 
-      * will default to the first one in the playlists array.
 
-      *
 
-      * If the manifest object represents a media playlist, `loadedplaylist` will be
 
-      * triggered followed by `loadedmetadata`, as the only available playlist is loaded.
 
-      *
 
-      * In the case of a media playlist, a main playlist object wrapper with one playlist
 
-      * will be created so that all logic can handle playlists in the same fashion (as an
 
-      * assumed manifest object schema).
 
-      *
 
-      * @param {Object} manifest
 
-      *        The parsed manifest object
 
-      */
 
-     setupInitialPlaylist(manifest) {
 
-       this.state = 'HAVE_MAIN_MANIFEST';
 
-       if (manifest.playlists) {
 
-         this.main = manifest;
 
-         addPropertiesToMain(this.main, this.srcUri()); // If the initial main playlist has playlists wtih segments already resolved,
 
-         // then resolve URIs in advance, as they are usually done after a playlist request,
 
-         // which may not happen if the playlist is resolved.
 
-         manifest.playlists.forEach(playlist => {
 
-           playlist.segments = getAllSegments(playlist);
 
-           playlist.segments.forEach(segment => {
 
-             resolveSegmentUris(segment, playlist.resolvedUri);
 
-           });
 
-         });
 
-         this.trigger('loadedplaylist');
 
-         if (!this.request) {
 
-           // no media playlist was specifically selected so start
 
-           // from the first listed one
 
-           this.media(this.main.playlists[0]);
 
-         }
 
-         return;
 
-       } // In order to support media playlists passed in as vhs-json, the case where the uri
 
-       // is not provided as part of the manifest should be considered, and an appropriate
 
-       // default used.
 
-       const uri = this.srcUri() || window.location.href;
 
-       this.main = mainForMedia(manifest, uri);
 
-       this.haveMetadata({
 
-         playlistObject: manifest,
 
-         url: uri,
 
-         id: this.main.playlists[0].id
 
-       });
 
-       this.trigger('loadedmetadata');
 
-     }
 
-   }
 
-   /**
 
-    * @file xhr.js
 
-    */
 
-   const {
 
-     xhr: videojsXHR
 
-   } = videojs;
 
-   const callbackWrapper = function (request, error, response, callback) {
 
-     const reqResponse = request.responseType === 'arraybuffer' ? request.response : request.responseText;
 
-     if (!error && reqResponse) {
 
-       request.responseTime = Date.now();
 
-       request.roundTripTime = request.responseTime - request.requestTime;
 
-       request.bytesReceived = reqResponse.byteLength || reqResponse.length;
 
-       if (!request.bandwidth) {
 
-         request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
 
-       }
 
-     }
 
-     if (response.headers) {
 
-       request.responseHeaders = response.headers;
 
-     } // videojs.xhr now uses a specific code on the error
 
-     // object to signal that a request has timed out instead
 
-     // of setting a boolean on the request object
 
-     if (error && error.code === 'ETIMEDOUT') {
 
-       request.timedout = true;
 
-     } // videojs.xhr no longer considers status codes outside of 200 and 0
 
-     // (for file uris) to be errors, but the old XHR did, so emulate that
 
-     // behavior. Status 206 may be used in response to byterange requests.
 
-     if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
 
-       error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
 
-     }
 
-     callback(error, request);
 
-   };
 
-   const xhrFactory = function () {
 
-     const xhr = function XhrFunction(options, callback) {
 
-       // Add a default timeout
 
-       options = merge({
 
-         timeout: 45e3
 
-       }, options); // Allow an optional user-specified function to modify the option
 
-       // object before we construct the xhr request
 
-       const beforeRequest = XhrFunction.beforeRequest || videojs.Vhs.xhr.beforeRequest;
 
-       if (beforeRequest && typeof beforeRequest === 'function') {
 
-         const newOptions = beforeRequest(options);
 
-         if (newOptions) {
 
-           options = newOptions;
 
-         }
 
-       } // Use the standard videojs.xhr() method unless `videojs.Vhs.xhr` has been overriden
 
-       // TODO: switch back to videojs.Vhs.xhr.name === 'XhrFunction' when we drop IE11
 
-       const xhrMethod = videojs.Vhs.xhr.original === true ? videojsXHR : videojs.Vhs.xhr;
 
-       const request = xhrMethod(options, function (error, response) {
 
-         return callbackWrapper(request, error, response, callback);
 
-       });
 
-       const originalAbort = request.abort;
 
-       request.abort = function () {
 
-         request.aborted = true;
 
-         return originalAbort.apply(request, arguments);
 
-       };
 
-       request.uri = options.uri;
 
-       request.requestTime = Date.now();
 
-       return request;
 
-     };
 
-     xhr.original = true;
 
-     return xhr;
 
-   };
 
-   /**
 
-    * Turns segment byterange into a string suitable for use in
 
-    * HTTP Range requests
 
-    *
 
-    * @param {Object} byterange - an object with two values defining the start and end
 
-    *                             of a byte-range
 
-    */
 
-   const byterangeStr = function (byterange) {
 
-     // `byterangeEnd` is one less than `offset + length` because the HTTP range
 
-     // header uses inclusive ranges
 
-     let byterangeEnd;
 
-     const byterangeStart = byterange.offset;
 
-     if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
 
-       byterangeEnd = window.BigInt(byterange.offset) + window.BigInt(byterange.length) - window.BigInt(1);
 
-     } else {
 
-       byterangeEnd = byterange.offset + byterange.length - 1;
 
-     }
 
-     return 'bytes=' + byterangeStart + '-' + byterangeEnd;
 
-   };
 
-   /**
 
-    * Defines headers for use in the xhr request for a particular segment.
 
-    *
 
-    * @param {Object} segment - a simplified copy of the segmentInfo object
 
-    *                           from SegmentLoader
 
-    */
 
-   const segmentXhrHeaders = function (segment) {
 
-     const headers = {};
 
-     if (segment.byterange) {
 
-       headers.Range = byterangeStr(segment.byterange);
 
-     }
 
-     return headers;
 
-   };
 
-   /**
 
-    * @file bin-utils.js
 
-    */
 
-   /**
 
-    * convert a TimeRange to text
 
-    *
 
-    * @param {TimeRange} range the timerange to use for conversion
 
-    * @param {number} i the iterator on the range to convert
 
-    * @return {string} the range in string format
 
-    */
 
-   const textRange = function (range, i) {
 
-     return range.start(i) + '-' + range.end(i);
 
-   };
 
-   /**
 
-    * format a number as hex string
 
-    *
 
-    * @param {number} e The number
 
-    * @param {number} i the iterator
 
-    * @return {string} the hex formatted number as a string
 
-    */
 
-   const formatHexString = function (e, i) {
 
-     const value = e.toString(16);
 
-     return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
 
-   };
 
-   const formatAsciiString = function (e) {
 
-     if (e >= 0x20 && e < 0x7e) {
 
-       return String.fromCharCode(e);
 
-     }
 
-     return '.';
 
-   };
 
-   /**
 
-    * Creates an object for sending to a web worker modifying properties that are TypedArrays
 
-    * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
 
-    *
 
-    * @param {Object} message
 
-    *        Object of properties and values to send to the web worker
 
-    * @return {Object}
 
-    *         Modified message with TypedArray values expanded
 
-    * @function createTransferableMessage
 
-    */
 
-   const createTransferableMessage = function (message) {
 
-     const transferable = {};
 
-     Object.keys(message).forEach(key => {
 
-       const value = message[key];
 
-       if (isArrayBufferView(value)) {
 
-         transferable[key] = {
 
-           bytes: value.buffer,
 
-           byteOffset: value.byteOffset,
 
-           byteLength: value.byteLength
 
-         };
 
-       } else {
 
-         transferable[key] = value;
 
-       }
 
-     });
 
-     return transferable;
 
-   };
 
-   /**
 
-    * Returns a unique string identifier for a media initialization
 
-    * segment.
 
-    *
 
-    * @param {Object} initSegment
 
-    *        the init segment object.
 
-    *
 
-    * @return {string} the generated init segment id
 
-    */
 
-   const initSegmentId = function (initSegment) {
 
-     const byterange = initSegment.byterange || {
 
-       length: Infinity,
 
-       offset: 0
 
-     };
 
-     return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
 
-   };
 
-   /**
 
-    * Returns a unique string identifier for a media segment key.
 
-    *
 
-    * @param {Object} key the encryption key
 
-    * @return {string} the unique id for the media segment key.
 
-    */
 
-   const segmentKeyId = function (key) {
 
-     return key.resolvedUri;
 
-   };
 
-   /**
 
-    * utils to help dump binary data to the console
 
-    *
 
-    * @param {Array|TypedArray} data
 
-    *        data to dump to a string
 
-    *
 
-    * @return {string} the data as a hex string.
 
-    */
 
-   const hexDump = data => {
 
-     const bytes = Array.prototype.slice.call(data);
 
-     const step = 16;
 
-     let result = '';
 
-     let hex;
 
-     let ascii;
 
-     for (let j = 0; j < bytes.length / step; j++) {
 
-       hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
 
-       ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
 
-       result += hex + ' ' + ascii + '\n';
 
-     }
 
-     return result;
 
-   };
 
-   const tagDump = ({
 
-     bytes
 
-   }) => hexDump(bytes);
 
-   const textRanges = ranges => {
 
-     let result = '';
 
-     let i;
 
-     for (i = 0; i < ranges.length; i++) {
 
-       result += textRange(ranges, i) + ' ';
 
-     }
 
-     return result;
 
-   };
 
-   var utils = /*#__PURE__*/Object.freeze({
 
-     __proto__: null,
 
-     createTransferableMessage: createTransferableMessage,
 
-     initSegmentId: initSegmentId,
 
-     segmentKeyId: segmentKeyId,
 
-     hexDump: hexDump,
 
-     tagDump: tagDump,
 
-     textRanges: textRanges
 
-   });
 
-   // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
 
-   // 25% was arbitrarily chosen, and may need to be refined over time.
 
-   const SEGMENT_END_FUDGE_PERCENT = 0.25;
 
-   /**
 
-    * Converts a player time (any time that can be gotten/set from player.currentTime(),
 
-    * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
 
-    * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
 
-    *
 
-    * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
 
-    * point" (a point where we have a mapping from program time to player time, with player
 
-    * time being the post transmux start of the segment).
 
-    *
 
-    * For more details, see [this doc](../../docs/program-time-from-player-time.md).
 
-    *
 
-    * @param {number} playerTime the player time
 
-    * @param {Object} segment the segment which contains the player time
 
-    * @return {Date} program time
 
-    */
 
-   const playerTimeToProgramTime = (playerTime, segment) => {
 
-     if (!segment.dateTimeObject) {
 
-       // Can't convert without an "anchor point" for the program time (i.e., a time that can
 
-       // be used to map the start of a segment with a real world time).
 
-       return null;
 
-     }
 
-     const transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
 
-     const transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
 
-     const startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
 
-     const offsetFromSegmentStart = playerTime - startOfSegment;
 
-     return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
 
-   };
 
-   const originalSegmentVideoDuration = videoTimingInfo => {
 
-     return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
 
-   };
 
-   /**
 
-    * Finds a segment that contains the time requested given as an ISO-8601 string. The
 
-    * returned segment might be an estimate or an accurate match.
 
-    *
 
-    * @param {string} programTime The ISO-8601 programTime to find a match for
 
-    * @param {Object} playlist A playlist object to search within
 
-    */
 
-   const findSegmentForProgramTime = (programTime, playlist) => {
 
-     // Assumptions:
 
-     //  - verifyProgramDateTimeTags has already been run
 
-     //  - live streams have been started
 
-     let dateTimeObject;
 
-     try {
 
-       dateTimeObject = new Date(programTime);
 
-     } catch (e) {
 
-       return null;
 
-     }
 
-     if (!playlist || !playlist.segments || playlist.segments.length === 0) {
 
-       return null;
 
-     }
 
-     let segment = playlist.segments[0];
 
-     if (dateTimeObject < segment.dateTimeObject) {
 
-       // Requested time is before stream start.
 
-       return null;
 
-     }
 
-     for (let i = 0; i < playlist.segments.length - 1; i++) {
 
-       segment = playlist.segments[i];
 
-       const nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
 
-       if (dateTimeObject < nextSegmentStart) {
 
-         break;
 
-       }
 
-     }
 
-     const lastSegment = playlist.segments[playlist.segments.length - 1];
 
-     const lastSegmentStart = lastSegment.dateTimeObject;
 
-     const lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
 
-     const lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
 
-     if (dateTimeObject > lastSegmentEnd) {
 
-       // Beyond the end of the stream, or our best guess of the end of the stream.
 
-       return null;
 
-     }
 
-     if (dateTimeObject > lastSegmentStart) {
 
-       segment = lastSegment;
 
-     }
 
-     return {
 
-       segment,
 
-       estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
 
-       // Although, given that all segments have accurate date time objects, the segment
 
-       // selected should be accurate, unless the video has been transmuxed at some point
 
-       // (determined by the presence of the videoTimingInfo object), the segment's "player
 
-       // time" (the start time in the player) can't be considered accurate.
 
-       type: segment.videoTimingInfo ? 'accurate' : 'estimate'
 
-     };
 
-   };
 
-   /**
 
-    * Finds a segment that contains the given player time(in seconds).
 
-    *
 
-    * @param {number} time The player time to find a match for
 
-    * @param {Object} playlist A playlist object to search within
 
-    */
 
-   const findSegmentForPlayerTime = (time, playlist) => {
 
-     // Assumptions:
 
-     // - there will always be a segment.duration
 
-     // - we can start from zero
 
-     // - segments are in time order
 
-     if (!playlist || !playlist.segments || playlist.segments.length === 0) {
 
-       return null;
 
-     }
 
-     let segmentEnd = 0;
 
-     let segment;
 
-     for (let i = 0; i < playlist.segments.length; i++) {
 
-       segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
 
-       // should contain the most accurate values we have for the segment's player times.
 
-       //
 
-       // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
 
-       // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
 
-       // calculate an end value.
 
-       segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
 
-       if (time <= segmentEnd) {
 
-         break;
 
-       }
 
-     }
 
-     const lastSegment = playlist.segments[playlist.segments.length - 1];
 
-     if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
 
-       // The time requested is beyond the stream end.
 
-       return null;
 
-     }
 
-     if (time > segmentEnd) {
 
-       // The time is within or beyond the last segment.
 
-       //
 
-       // Check to see if the time is beyond a reasonable guess of the end of the stream.
 
-       if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
 
-         // Technically, because the duration value is only an estimate, the time may still
 
-         // exist in the last segment, however, there isn't enough information to make even
 
-         // a reasonable estimate.
 
-         return null;
 
-       }
 
-       segment = lastSegment;
 
-     }
 
-     return {
 
-       segment,
 
-       estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
 
-       // Because videoTimingInfo is only set after transmux, it is the only way to get
 
-       // accurate timing values.
 
-       type: segment.videoTimingInfo ? 'accurate' : 'estimate'
 
-     };
 
-   };
 
-   /**
 
-    * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
 
-    * If the offset returned is positive, the programTime occurs after the
 
-    * comparisonTimestamp.
 
-    * If the offset is negative, the programTime occurs before the comparisonTimestamp.
 
-    *
 
-    * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against
 
-    * @param {string} programTime The programTime as an ISO-8601 string
 
-    * @return {number} offset
 
-    */
 
-   const getOffsetFromTimestamp = (comparisonTimeStamp, programTime) => {
 
-     let segmentDateTime;
 
-     let programDateTime;
 
-     try {
 
-       segmentDateTime = new Date(comparisonTimeStamp);
 
-       programDateTime = new Date(programTime);
 
-     } catch (e) {// TODO handle error
 
-     }
 
-     const segmentTimeEpoch = segmentDateTime.getTime();
 
-     const programTimeEpoch = programDateTime.getTime();
 
-     return (programTimeEpoch - segmentTimeEpoch) / 1000;
 
-   };
 
-   /**
 
-    * Checks that all segments in this playlist have programDateTime tags.
 
-    *
 
-    * @param {Object} playlist A playlist object
 
-    */
 
-   const verifyProgramDateTimeTags = playlist => {
 
-     if (!playlist.segments || playlist.segments.length === 0) {
 
-       return false;
 
-     }
 
-     for (let i = 0; i < playlist.segments.length; i++) {
 
-       const segment = playlist.segments[i];
 
-       if (!segment.dateTimeObject) {
 
-         return false;
 
-       }
 
-     }
 
-     return true;
 
-   };
 
-   /**
 
-    * Returns the programTime of the media given a playlist and a playerTime.
 
-    * The playlist must have programDateTime tags for a programDateTime tag to be returned.
 
-    * If the segments containing the time requested have not been buffered yet, an estimate
 
-    * may be returned to the callback.
 
-    *
 
-    * @param {Object} args
 
-    * @param {Object} args.playlist A playlist object to search within
 
-    * @param {number} time A playerTime in seconds
 
-    * @param {Function} callback(err, programTime)
 
-    * @return {string} err.message A detailed error message
 
-    * @return {Object} programTime
 
-    * @return {number} programTime.mediaSeconds The streamTime in seconds
 
-    * @return {string} programTime.programDateTime The programTime as an ISO-8601 String
 
-    */
 
-   const getProgramTime = ({
 
-     playlist,
 
-     time = undefined,
 
-     callback
 
-   }) => {
 
-     if (!callback) {
 
-       throw new Error('getProgramTime: callback must be provided');
 
-     }
 
-     if (!playlist || time === undefined) {
 
-       return callback({
 
-         message: 'getProgramTime: playlist and time must be provided'
 
-       });
 
-     }
 
-     const matchedSegment = findSegmentForPlayerTime(time, playlist);
 
-     if (!matchedSegment) {
 
-       return callback({
 
-         message: 'valid programTime was not found'
 
-       });
 
-     }
 
-     if (matchedSegment.type === 'estimate') {
 
-       return callback({
 
-         message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
 
-         seekTime: matchedSegment.estimatedStart
 
-       });
 
-     }
 
-     const programTimeObject = {
 
-       mediaSeconds: time
 
-     };
 
-     const programTime = playerTimeToProgramTime(time, matchedSegment.segment);
 
-     if (programTime) {
 
-       programTimeObject.programDateTime = programTime.toISOString();
 
-     }
 
-     return callback(null, programTimeObject);
 
-   };
 
-   /**
 
-    * Seeks in the player to a time that matches the given programTime ISO-8601 string.
 
-    *
 
-    * @param {Object} args
 
-    * @param {string} args.programTime A programTime to seek to as an ISO-8601 String
 
-    * @param {Object} args.playlist A playlist to look within
 
-    * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2.
 
-    * @param {Function} args.seekTo A method to perform a seek
 
-    * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
 
-    * @param {Object} args.tech The tech to seek on
 
-    * @param {Function} args.callback(err, newTime) A callback to return the new time to
 
-    * @return {string} err.message A detailed error message
 
-    * @return {number} newTime The exact time that was seeked to in seconds
 
-    */
 
-   const seekToProgramTime = ({
 
-     programTime,
 
-     playlist,
 
-     retryCount = 2,
 
-     seekTo,
 
-     pauseAfterSeek = true,
 
-     tech,
 
-     callback
 
-   }) => {
 
-     if (!callback) {
 
-       throw new Error('seekToProgramTime: callback must be provided');
 
-     }
 
-     if (typeof programTime === 'undefined' || !playlist || !seekTo) {
 
-       return callback({
 
-         message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
 
-       });
 
-     }
 
-     if (!playlist.endList && !tech.hasStarted_) {
 
-       return callback({
 
-         message: 'player must be playing a live stream to start buffering'
 
-       });
 
-     }
 
-     if (!verifyProgramDateTimeTags(playlist)) {
 
-       return callback({
 
-         message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
 
-       });
 
-     }
 
-     const matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
 
-     if (!matchedSegment) {
 
-       return callback({
 
-         message: `${programTime} was not found in the stream`
 
-       });
 
-     }
 
-     const segment = matchedSegment.segment;
 
-     const mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
 
-     if (matchedSegment.type === 'estimate') {
 
-       // we've run out of retries
 
-       if (retryCount === 0) {
 
-         return callback({
 
-           message: `${programTime} is not buffered yet. Try again`
 
-         });
 
-       }
 
-       seekTo(matchedSegment.estimatedStart + mediaOffset);
 
-       tech.one('seeked', () => {
 
-         seekToProgramTime({
 
-           programTime,
 
-           playlist,
 
-           retryCount: retryCount - 1,
 
-           seekTo,
 
-           pauseAfterSeek,
 
-           tech,
 
-           callback
 
-         });
 
-       });
 
-       return;
 
-     } // Since the segment.start value is determined from the buffered end or ending time
 
-     // of the prior segment, the seekToTime doesn't need to account for any transmuxer
 
-     // modifications.
 
-     const seekToTime = segment.start + mediaOffset;
 
-     const seekedCallback = () => {
 
-       return callback(null, tech.currentTime());
 
-     }; // listen for seeked event
 
-     tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
 
-     if (pauseAfterSeek) {
 
-       tech.pause();
 
-     }
 
-     seekTo(seekToTime);
 
-   };
 
-   // which will only happen if the request is complete.
 
-   const callbackOnCompleted = (request, cb) => {
 
-     if (request.readyState === 4) {
 
-       return cb();
 
-     }
 
-     return;
 
-   };
 
-   const containerRequest = (uri, xhr, cb) => {
 
-     let bytes = [];
 
-     let id3Offset;
 
-     let finished = false;
 
-     const endRequestAndCallback = function (err, req, type, _bytes) {
 
-       req.abort();
 
-       finished = true;
 
-       return cb(err, req, type, _bytes);
 
-     };
 
-     const progressListener = function (error, request) {
 
-       if (finished) {
 
-         return;
 
-       }
 
-       if (error) {
 
-         return endRequestAndCallback(error, request, '', bytes);
 
-       } // grap the new part of content that was just downloaded
 
-       const newPart = request.responseText.substring(bytes && bytes.byteLength || 0, request.responseText.length); // add that onto bytes
 
-       bytes = concatTypedArrays(bytes, stringToBytes(newPart, true));
 
-       id3Offset = id3Offset || getId3Offset(bytes); // we need at least 10 bytes to determine a type
 
-       // or we need at least two bytes after an id3Offset
 
-       if (bytes.length < 10 || id3Offset && bytes.length < id3Offset + 2) {
 
-         return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
 
-       }
 
-       const type = detectContainerForBytes(bytes); // if this looks like a ts segment but we don't have enough data
 
-       // to see the second sync byte, wait until we have enough data
 
-       // before declaring it ts
 
-       if (type === 'ts' && bytes.length < 188) {
 
-         return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
 
-       } // this may be an unsynced ts segment
 
-       // wait for 376 bytes before detecting no container
 
-       if (!type && bytes.length < 376) {
 
-         return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
 
-       }
 
-       return endRequestAndCallback(null, request, type, bytes);
 
-     };
 
-     const options = {
 
-       uri,
 
-       beforeSend(request) {
 
-         // this forces the browser to pass the bytes to us unprocessed
 
-         request.overrideMimeType('text/plain; charset=x-user-defined');
 
-         request.addEventListener('progress', function ({
 
-           total,
 
-           loaded
 
-         }) {
 
-           return callbackWrapper(request, null, {
 
-             statusCode: request.status
 
-           }, progressListener);
 
-         });
 
-       }
 
-     };
 
-     const request = xhr(options, function (error, response) {
 
-       return callbackWrapper(request, error, response, progressListener);
 
-     });
 
-     return request;
 
-   };
 
-   const {
 
-     EventTarget
 
-   } = videojs;
 
-   const dashPlaylistUnchanged = function (a, b) {
 
-     if (!isPlaylistUnchanged(a, b)) {
 
-       return false;
 
-     } // for dash the above check will often return true in scenarios where
 
-     // the playlist actually has changed because mediaSequence isn't a
 
-     // dash thing, and we often set it to 1. So if the playlists have the same amount
 
-     // of segments we return true.
 
-     // So for dash we need to make sure that the underlying segments are different.
 
-     // if sidx changed then the playlists are different.
 
-     if (a.sidx && b.sidx && (a.sidx.offset !== b.sidx.offset || a.sidx.length !== b.sidx.length)) {
 
-       return false;
 
-     } else if (!a.sidx && b.sidx || a.sidx && !b.sidx) {
 
-       return false;
 
-     } // one or the other does not have segments
 
-     // there was a change.
 
-     if (a.segments && !b.segments || !a.segments && b.segments) {
 
-       return false;
 
-     } // neither has segments nothing changed
 
-     if (!a.segments && !b.segments) {
 
-       return true;
 
-     } // check segments themselves
 
-     for (let i = 0; i < a.segments.length; i++) {
 
-       const aSegment = a.segments[i];
 
-       const bSegment = b.segments[i]; // if uris are different between segments there was a change
 
-       if (aSegment.uri !== bSegment.uri) {
 
-         return false;
 
-       } // neither segment has a byterange, there will be no byterange change.
 
-       if (!aSegment.byterange && !bSegment.byterange) {
 
-         continue;
 
-       }
 
-       const aByterange = aSegment.byterange;
 
-       const bByterange = bSegment.byterange; // if byterange only exists on one of the segments, there was a change.
 
-       if (aByterange && !bByterange || !aByterange && bByterange) {
 
-         return false;
 
-       } // if both segments have byterange with different offsets, there was a change.
 
-       if (aByterange.offset !== bByterange.offset || aByterange.length !== bByterange.length) {
 
-         return false;
 
-       }
 
-     } // if everything was the same with segments, this is the same playlist.
 
-     return true;
 
-   };
 
-   /**
 
-    * Use the representation IDs from the mpd object to create groupIDs, the NAME is set to mandatory representation
 
-    * ID in the parser. This allows for continuous playout across periods with the same representation IDs
 
-    * (continuous periods as defined in DASH-IF 3.2.12). This is assumed in the mpd-parser as well. If we want to support
 
-    * periods without continuous playback this function may need modification as well as the parser.
 
-    */
 
-   const dashGroupId = (type, group, label, playlist) => {
 
-     // If the manifest somehow does not have an ID (non-dash compliant), use the label.
 
-     const playlistId = playlist.attributes.NAME || label;
 
-     return `placeholder-uri-${type}-${group}-${playlistId}`;
 
-   };
 
-   /**
 
-    * Parses the main XML string and updates playlist URI references.
 
-    *
 
-    * @param {Object} config
 
-    *        Object of arguments
 
-    * @param {string} config.mainXml
 
-    *        The mpd XML
 
-    * @param {string} config.srcUrl
 
-    *        The mpd URL
 
-    * @param {Date} config.clientOffset
 
-    *         A time difference between server and client
 
-    * @param {Object} config.sidxMapping
 
-    *        SIDX mappings for moof/mdat URIs and byte ranges
 
-    * @return {Object}
 
-    *         The parsed mpd manifest object
 
-    */
 
-   const parseMainXml = ({
 
-     mainXml,
 
-     srcUrl,
 
-     clientOffset,
 
-     sidxMapping,
 
-     previousManifest
 
-   }) => {
 
-     const manifest = parse(mainXml, {
 
-       manifestUri: srcUrl,
 
-       clientOffset,
 
-       sidxMapping,
 
-       previousManifest
 
-     });
 
-     addPropertiesToMain(manifest, srcUrl, dashGroupId);
 
-     return manifest;
 
-   };
 
-   /**
 
-    * Removes any mediaGroup labels that no longer exist in the newMain
 
-    *
 
-    * @param {Object} update
 
-    *         The previous mpd object being updated
 
-    * @param {Object} newMain
 
-    *         The new mpd object
 
-    */
 
-   const removeOldMediaGroupLabels = (update, newMain) => {
 
-     forEachMediaGroup(update, (properties, type, group, label) => {
 
-       if (!(label in newMain.mediaGroups[type][group])) {
 
-         delete update.mediaGroups[type][group][label];
 
-       }
 
-     });
 
-   };
 
-   /**
 
-    * Returns a new main manifest that is the result of merging an updated main manifest
 
-    * into the original version.
 
-    *
 
-    * @param {Object} oldMain
 
-    *        The old parsed mpd object
 
-    * @param {Object} newMain
 
-    *        The updated parsed mpd object
 
-    * @return {Object}
 
-    *         A new object representing the original main manifest with the updated media
 
-    *         playlists merged in
 
-    */
 
-   const updateMain = (oldMain, newMain, sidxMapping) => {
 
-     let noChanges = true;
 
-     let update = merge(oldMain, {
 
-       // These are top level properties that can be updated
 
-       duration: newMain.duration,
 
-       minimumUpdatePeriod: newMain.minimumUpdatePeriod,
 
-       timelineStarts: newMain.timelineStarts
 
-     }); // First update the playlists in playlist list
 
-     for (let i = 0; i < newMain.playlists.length; i++) {
 
-       const playlist = newMain.playlists[i];
 
-       if (playlist.sidx) {
 
-         const sidxKey = generateSidxKey(playlist.sidx); // add sidx segments to the playlist if we have all the sidx info already
 
-         if (sidxMapping && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx) {
 
-           addSidxSegmentsToPlaylist$1(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
 
-         }
 
-       }
 
-       const playlistUpdate = updateMain$1(update, playlist, dashPlaylistUnchanged);
 
-       if (playlistUpdate) {
 
-         update = playlistUpdate;
 
-         noChanges = false;
 
-       }
 
-     } // Then update media group playlists
 
-     forEachMediaGroup(newMain, (properties, type, group, label) => {
 
-       if (properties.playlists && properties.playlists.length) {
 
-         const id = properties.playlists[0].id;
 
-         const playlistUpdate = updateMain$1(update, properties.playlists[0], dashPlaylistUnchanged);
 
-         if (playlistUpdate) {
 
-           update = playlistUpdate; // add new mediaGroup label if it doesn't exist and assign the new mediaGroup.
 
-           if (!(label in update.mediaGroups[type][group])) {
 
-             update.mediaGroups[type][group][label] = properties;
 
-           } // update the playlist reference within media groups
 
-           update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];
 
-           noChanges = false;
 
-         }
 
-       }
 
-     }); // remove mediaGroup labels and references that no longer exist in the newMain
 
-     removeOldMediaGroupLabels(update, newMain);
 
-     if (newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
 
-       noChanges = false;
 
-     }
 
-     if (noChanges) {
 
-       return null;
 
-     }
 
-     return update;
 
-   }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
 
-   // If the SIDXs have maps, the two maps should match,
 
-   // both `a` and `b` missing SIDXs is considered matching.
 
-   // If `a` or `b` but not both have a map, they aren't matching.
 
-   const equivalentSidx = (a, b) => {
 
-     const neitherMap = Boolean(!a.map && !b.map);
 
-     const equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
 
-     return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
 
-   }; // exported for testing
 
-   const compareSidxEntry = (playlists, oldSidxMapping) => {
 
-     const newSidxMapping = {};
 
-     for (const id in playlists) {
 
-       const playlist = playlists[id];
 
-       const currentSidxInfo = playlist.sidx;
 
-       if (currentSidxInfo) {
 
-         const key = generateSidxKey(currentSidxInfo);
 
-         if (!oldSidxMapping[key]) {
 
-           break;
 
-         }
 
-         const savedSidxInfo = oldSidxMapping[key].sidxInfo;
 
-         if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
 
-           newSidxMapping[key] = oldSidxMapping[key];
 
-         }
 
-       }
 
-     }
 
-     return newSidxMapping;
 
-   };
 
-   /**
 
-    *  A function that filters out changed items as they need to be requested separately.
 
-    *
 
-    *  The method is exported for testing
 
-    *
 
-    *  @param {Object} main the parsed mpd XML returned via mpd-parser
 
-    *  @param {Object} oldSidxMapping the SIDX to compare against
 
-    */
 
-   const filterChangedSidxMappings = (main, oldSidxMapping) => {
 
-     const videoSidx = compareSidxEntry(main.playlists, oldSidxMapping);
 
-     let mediaGroupSidx = videoSidx;
 
-     forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
 
-       if (properties.playlists && properties.playlists.length) {
 
-         const playlists = properties.playlists;
 
-         mediaGroupSidx = merge(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
 
-       }
 
-     });
 
-     return mediaGroupSidx;
 
-   };
 
-   class DashPlaylistLoader extends EventTarget {
 
-     // DashPlaylistLoader must accept either a src url or a playlist because subsequent
 
-     // playlist loader setups from media groups will expect to be able to pass a playlist
 
-     // (since there aren't external URLs to media playlists with DASH)
 
-     constructor(srcUrlOrPlaylist, vhs, options = {}, mainPlaylistLoader) {
 
-       super();
 
-       this.mainPlaylistLoader_ = mainPlaylistLoader || this;
 
-       if (!mainPlaylistLoader) {
 
-         this.isMain_ = true;
 
-       }
 
-       const {
 
-         withCredentials = false
 
-       } = options;
 
-       this.vhs_ = vhs;
 
-       this.withCredentials = withCredentials;
 
-       if (!srcUrlOrPlaylist) {
 
-         throw new Error('A non-empty playlist URL or object is required');
 
-       } // event naming?
 
-       this.on('minimumUpdatePeriod', () => {
 
-         this.refreshXml_();
 
-       }); // live playlist staleness timeout
 
-       this.on('mediaupdatetimeout', () => {
 
-         this.refreshMedia_(this.media().id);
 
-       });
 
-       this.state = 'HAVE_NOTHING';
 
-       this.loadedPlaylists_ = {};
 
-       this.logger_ = logger('DashPlaylistLoader'); // initialize the loader state
 
-       // The mainPlaylistLoader will be created with a string
 
-       if (this.isMain_) {
 
-         this.mainPlaylistLoader_.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
 
-         // once multi-period is refactored
 
-         this.mainPlaylistLoader_.sidxMapping_ = {};
 
-       } else {
 
-         this.childPlaylist_ = srcUrlOrPlaylist;
 
-       }
 
-     }
 
-     requestErrored_(err, request, startingState) {
 
-       // disposed
 
-       if (!this.request) {
 
-         return true;
 
-       } // pending request is cleared
 
-       this.request = null;
 
-       if (err) {
 
-         // use the provided error object or create one
 
-         // based on the request/response
 
-         this.error = typeof err === 'object' && !(err instanceof Error) ? err : {
 
-           status: request.status,
 
-           message: 'DASH request error at URL: ' + request.uri,
 
-           response: request.response,
 
-           // MEDIA_ERR_NETWORK
 
-           code: 2
 
-         };
 
-         if (startingState) {
 
-           this.state = startingState;
 
-         }
 
-         this.trigger('error');
 
-         return true;
 
-       }
 
-     }
 
-     /**
 
-      * Verify that the container of the sidx segment can be parsed
 
-      * and if it can, get and parse that segment.
 
-      */
 
-     addSidxSegments_(playlist, startingState, cb) {
 
-       const sidxKey = playlist.sidx && generateSidxKey(playlist.sidx); // playlist lacks sidx or sidx segments were added to this playlist already.
 
-       if (!playlist.sidx || !sidxKey || this.mainPlaylistLoader_.sidxMapping_[sidxKey]) {
 
-         // keep this function async
 
-         this.mediaRequest_ = window.setTimeout(() => cb(false), 0);
 
-         return;
 
-       } // resolve the segment URL relative to the playlist
 
-       const uri = resolveManifestRedirect(playlist.sidx.resolvedUri);
 
-       const fin = (err, request) => {
 
-         if (this.requestErrored_(err, request, startingState)) {
 
-           return;
 
-         }
 
-         const sidxMapping = this.mainPlaylistLoader_.sidxMapping_;
 
-         let sidx;
 
-         try {
 
-           sidx = parseSidx_1(toUint8(request.response).subarray(8));
 
-         } catch (e) {
 
-           // sidx parsing failed.
 
-           this.requestErrored_(e, request, startingState);
 
-           return;
 
-         }
 
-         sidxMapping[sidxKey] = {
 
-           sidxInfo: playlist.sidx,
 
-           sidx
 
-         };
 
-         addSidxSegmentsToPlaylist$1(playlist, sidx, playlist.sidx.resolvedUri);
 
-         return cb(true);
 
-       };
 
-       this.request = containerRequest(uri, this.vhs_.xhr, (err, request, container, bytes) => {
 
-         if (err) {
 
-           return fin(err, request);
 
-         }
 
-         if (!container || container !== 'mp4') {
 
-           return fin({
 
-             status: request.status,
 
-             message: `Unsupported ${container || 'unknown'} container type for sidx segment at URL: ${uri}`,
 
-             // response is just bytes in this case
 
-             // but we really don't want to return that.
 
-             response: '',
 
-             playlist,
 
-             internal: true,
 
-             playlistExclusionDuration: Infinity,
 
-             // MEDIA_ERR_NETWORK
 
-             code: 2
 
-           }, request);
 
-         } // if we already downloaded the sidx bytes in the container request, use them
 
-         const {
 
-           offset,
 
-           length
 
-         } = playlist.sidx.byterange;
 
-         if (bytes.length >= length + offset) {
 
-           return fin(err, {
 
-             response: bytes.subarray(offset, offset + length),
 
-             status: request.status,
 
-             uri: request.uri
 
-           });
 
-         } // otherwise request sidx bytes
 
-         this.request = this.vhs_.xhr({
 
-           uri,
 
-           responseType: 'arraybuffer',
 
-           headers: segmentXhrHeaders({
 
-             byterange: playlist.sidx.byterange
 
-           })
 
-         }, fin);
 
-       });
 
-     }
 
-     dispose() {
 
-       this.trigger('dispose');
 
-       this.stopRequest();
 
-       this.loadedPlaylists_ = {};
 
-       window.clearTimeout(this.minimumUpdatePeriodTimeout_);
 
-       window.clearTimeout(this.mediaRequest_);
 
-       window.clearTimeout(this.mediaUpdateTimeout);
 
-       this.mediaUpdateTimeout = null;
 
-       this.mediaRequest_ = null;
 
-       this.minimumUpdatePeriodTimeout_ = null;
 
-       if (this.mainPlaylistLoader_.createMupOnMedia_) {
 
-         this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
 
-         this.mainPlaylistLoader_.createMupOnMedia_ = null;
 
-       }
 
-       this.off();
 
-     }
 
-     hasPendingRequest() {
 
-       return this.request || this.mediaRequest_;
 
-     }
 
-     stopRequest() {
 
-       if (this.request) {
 
-         const oldRequest = this.request;
 
-         this.request = null;
 
-         oldRequest.onreadystatechange = null;
 
-         oldRequest.abort();
 
-       }
 
-     }
 
-     media(playlist) {
 
-       // getter
 
-       if (!playlist) {
 
-         return this.media_;
 
-       } // setter
 
-       if (this.state === 'HAVE_NOTHING') {
 
-         throw new Error('Cannot switch media playlist from ' + this.state);
 
-       }
 
-       const startingState = this.state; // find the playlist object if the target playlist has been specified by URI
 
-       if (typeof playlist === 'string') {
 
-         if (!this.mainPlaylistLoader_.main.playlists[playlist]) {
 
-           throw new Error('Unknown playlist URI: ' + playlist);
 
-         }
 
-         playlist = this.mainPlaylistLoader_.main.playlists[playlist];
 
-       }
 
-       const mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately
 
-       if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) {
 
-         this.state = 'HAVE_METADATA';
 
-         this.media_ = playlist; // trigger media change if the active media has been updated
 
-         if (mediaChange) {
 
-           this.trigger('mediachanging');
 
-           this.trigger('mediachange');
 
-         }
 
-         return;
 
-       } // switching to the active playlist is a no-op
 
-       if (!mediaChange) {
 
-         return;
 
-       } // switching from an already loaded playlist
 
-       if (this.media_) {
 
-         this.trigger('mediachanging');
 
-       }
 
-       this.addSidxSegments_(playlist, startingState, sidxChanged => {
 
-         // everything is ready just continue to haveMetadata
 
-         this.haveMetadata({
 
-           startingState,
 
-           playlist
 
-         });
 
-       });
 
-     }
 
-     haveMetadata({
 
-       startingState,
 
-       playlist
 
-     }) {
 
-       this.state = 'HAVE_METADATA';
 
-       this.loadedPlaylists_[playlist.id] = playlist;
 
-       this.mediaRequest_ = null; // This will trigger loadedplaylist
 
-       this.refreshMedia_(playlist.id); // fire loadedmetadata the first time a media playlist is loaded
 
-       // to resolve setup of media groups
 
-       if (startingState === 'HAVE_MAIN_MANIFEST') {
 
-         this.trigger('loadedmetadata');
 
-       } else {
 
-         // trigger media change if the active media has been updated
 
-         this.trigger('mediachange');
 
-       }
 
-     }
 
-     pause() {
 
-       if (this.mainPlaylistLoader_.createMupOnMedia_) {
 
-         this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
 
-         this.mainPlaylistLoader_.createMupOnMedia_ = null;
 
-       }
 
-       this.stopRequest();
 
-       window.clearTimeout(this.mediaUpdateTimeout);
 
-       this.mediaUpdateTimeout = null;
 
-       if (this.isMain_) {
 
-         window.clearTimeout(this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_);
 
-         this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_ = null;
 
-       }
 
-       if (this.state === 'HAVE_NOTHING') {
 
-         // If we pause the loader before any data has been retrieved, its as if we never
 
-         // started, so reset to an unstarted state.
 
-         this.started = false;
 
-       }
 
-     }
 
-     load(isFinalRendition) {
 
-       window.clearTimeout(this.mediaUpdateTimeout);
 
-       this.mediaUpdateTimeout = null;
 
-       const media = this.media();
 
-       if (isFinalRendition) {
 
-         const delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
 
-         this.mediaUpdateTimeout = window.setTimeout(() => this.load(), delay);
 
-         return;
 
-       } // because the playlists are internal to the manifest, load should either load the
 
-       // main manifest, or do nothing but trigger an event
 
-       if (!this.started) {
 
-         this.start();
 
-         return;
 
-       }
 
-       if (media && !media.endList) {
 
-         // Check to see if this is the main loader and the MUP was cleared (this happens
 
-         // when the loader was paused). `media` should be set at this point since one is always
 
-         // set during `start()`.
 
-         if (this.isMain_ && !this.minimumUpdatePeriodTimeout_) {
 
-           // Trigger minimumUpdatePeriod to refresh the main manifest
 
-           this.trigger('minimumUpdatePeriod'); // Since there was no prior minimumUpdatePeriodTimeout it should be recreated
 
-           this.updateMinimumUpdatePeriodTimeout_();
 
-         }
 
-         this.trigger('mediaupdatetimeout');
 
-       } else {
 
-         this.trigger('loadedplaylist');
 
-       }
 
-     }
 
-     start() {
 
-       this.started = true; // We don't need to request the main manifest again
 
-       // Call this asynchronously to match the xhr request behavior below
 
-       if (!this.isMain_) {
 
-         this.mediaRequest_ = window.setTimeout(() => this.haveMain_(), 0);
 
-         return;
 
-       }
 
-       this.requestMain_((req, mainChanged) => {
 
-         this.haveMain_();
 
-         if (!this.hasPendingRequest() && !this.media_) {
 
-           this.media(this.mainPlaylistLoader_.main.playlists[0]);
 
-         }
 
-       });
 
-     }
 
-     requestMain_(cb) {
 
-       this.request = this.vhs_.xhr({
 
-         uri: this.mainPlaylistLoader_.srcUrl,
 
-         withCredentials: this.withCredentials
 
-       }, (error, req) => {
 
-         if (this.requestErrored_(error, req)) {
 
-           if (this.state === 'HAVE_NOTHING') {
 
-             this.started = false;
 
-           }
 
-           return;
 
-         }
 
-         const mainChanged = req.responseText !== this.mainPlaylistLoader_.mainXml_;
 
-         this.mainPlaylistLoader_.mainXml_ = req.responseText;
 
-         if (req.responseHeaders && req.responseHeaders.date) {
 
-           this.mainLoaded_ = Date.parse(req.responseHeaders.date);
 
-         } else {
 
-           this.mainLoaded_ = Date.now();
 
-         }
 
-         this.mainPlaylistLoader_.srcUrl = resolveManifestRedirect(this.mainPlaylistLoader_.srcUrl, req);
 
-         if (mainChanged) {
 
-           this.handleMain_();
 
-           this.syncClientServerClock_(() => {
 
-             return cb(req, mainChanged);
 
-           });
 
-           return;
 
-         }
 
-         return cb(req, mainChanged);
 
-       });
 
-     }
 
-     /**
 
-      * Parses the main xml for UTCTiming node to sync the client clock to the server
 
-      * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
 
-      *
 
-      * @param {Function} done
 
-      *        Function to call when clock sync has completed
 
-      */
 
-     syncClientServerClock_(done) {
 
-       const utcTiming = parseUTCTiming(this.mainPlaylistLoader_.mainXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
 
-       // server clock
 
-       if (utcTiming === null) {
 
-         this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
 
-         return done();
 
-       }
 
-       if (utcTiming.method === 'DIRECT') {
 
-         this.mainPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now();
 
-         return done();
 
-       }
 
-       this.request = this.vhs_.xhr({
 
-         uri: resolveUrl(this.mainPlaylistLoader_.srcUrl, utcTiming.value),
 
-         method: utcTiming.method,
 
-         withCredentials: this.withCredentials
 
-       }, (error, req) => {
 
-         // disposed
 
-         if (!this.request) {
 
-           return;
 
-         }
 
-         if (error) {
 
-           // sync request failed, fall back to using date header from mpd
 
-           // TODO: log warning
 
-           this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
 
-           return done();
 
-         }
 
-         let serverTime;
 
-         if (utcTiming.method === 'HEAD') {
 
-           if (!req.responseHeaders || !req.responseHeaders.date) {
 
-             // expected date header not preset, fall back to using date header from mpd
 
-             // TODO: log warning
 
-             serverTime = this.mainLoaded_;
 
-           } else {
 
-             serverTime = Date.parse(req.responseHeaders.date);
 
-           }
 
-         } else {
 
-           serverTime = Date.parse(req.responseText);
 
-         }
 
-         this.mainPlaylistLoader_.clientOffset_ = serverTime - Date.now();
 
-         done();
 
-       });
 
-     }
 
-     haveMain_() {
 
-       this.state = 'HAVE_MAIN_MANIFEST';
 
-       if (this.isMain_) {
 
-         // We have the main playlist at this point, so
 
-         // trigger this to allow PlaylistController
 
-         // to make an initial playlist selection
 
-         this.trigger('loadedplaylist');
 
-       } else if (!this.media_) {
 
-         // no media playlist was specifically selected so select
 
-         // the one the child playlist loader was created with
 
-         this.media(this.childPlaylist_);
 
-       }
 
-     }
 
-     handleMain_() {
 
-       // clear media request
 
-       this.mediaRequest_ = null;
 
-       const oldMain = this.mainPlaylistLoader_.main;
 
-       let newMain = parseMainXml({
 
-         mainXml: this.mainPlaylistLoader_.mainXml_,
 
-         srcUrl: this.mainPlaylistLoader_.srcUrl,
 
-         clientOffset: this.mainPlaylistLoader_.clientOffset_,
 
-         sidxMapping: this.mainPlaylistLoader_.sidxMapping_,
 
-         previousManifest: oldMain
 
-       }); // if we have an old main to compare the new main against
 
-       if (oldMain) {
 
-         newMain = updateMain(oldMain, newMain, this.mainPlaylistLoader_.sidxMapping_);
 
-       } // only update main if we have a new main
 
-       this.mainPlaylistLoader_.main = newMain ? newMain : oldMain;
 
-       const location = this.mainPlaylistLoader_.main.locations && this.mainPlaylistLoader_.main.locations[0];
 
-       if (location && location !== this.mainPlaylistLoader_.srcUrl) {
 
-         this.mainPlaylistLoader_.srcUrl = location;
 
-       }
 
-       if (!oldMain || newMain && newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
 
-         this.updateMinimumUpdatePeriodTimeout_();
 
-       }
 
-       return Boolean(newMain);
 
-     }
 
-     updateMinimumUpdatePeriodTimeout_() {
 
-       const mpl = this.mainPlaylistLoader_; // cancel any pending creation of mup on media
 
-       // a new one will be added if needed.
 
-       if (mpl.createMupOnMedia_) {
 
-         mpl.off('loadedmetadata', mpl.createMupOnMedia_);
 
-         mpl.createMupOnMedia_ = null;
 
-       } // clear any pending timeouts
 
-       if (mpl.minimumUpdatePeriodTimeout_) {
 
-         window.clearTimeout(mpl.minimumUpdatePeriodTimeout_);
 
-         mpl.minimumUpdatePeriodTimeout_ = null;
 
-       }
 
-       let mup = mpl.main && mpl.main.minimumUpdatePeriod; // If the minimumUpdatePeriod has a value of 0, that indicates that the current
 
-       // MPD has no future validity, so a new one will need to be acquired when new
 
-       // media segments are to be made available. Thus, we use the target duration
 
-       // in this case
 
-       if (mup === 0) {
 
-         if (mpl.media()) {
 
-           mup = mpl.media().targetDuration * 1000;
 
-         } else {
 
-           mpl.createMupOnMedia_ = mpl.updateMinimumUpdatePeriodTimeout_;
 
-           mpl.one('loadedmetadata', mpl.createMupOnMedia_);
 
-         }
 
-       } // if minimumUpdatePeriod is invalid or <= zero, which
 
-       // can happen when a live video becomes VOD. skip timeout
 
-       // creation.
 
-       if (typeof mup !== 'number' || mup <= 0) {
 
-         if (mup < 0) {
 
-           this.logger_(`found invalid minimumUpdatePeriod of ${mup}, not setting a timeout`);
 
-         }
 
-         return;
 
-       }
 
-       this.createMUPTimeout_(mup);
 
-     }
 
-     createMUPTimeout_(mup) {
 
-       const mpl = this.mainPlaylistLoader_;
 
-       mpl.minimumUpdatePeriodTimeout_ = window.setTimeout(() => {
 
-         mpl.minimumUpdatePeriodTimeout_ = null;
 
-         mpl.trigger('minimumUpdatePeriod');
 
-         mpl.createMUPTimeout_(mup);
 
-       }, mup);
 
-     }
 
-     /**
 
-      * Sends request to refresh the main xml and updates the parsed main manifest
 
-      */
 
-     refreshXml_() {
 
-       this.requestMain_((req, mainChanged) => {
 
-         if (!mainChanged) {
 
-           return;
 
-         }
 
-         if (this.media_) {
 
-           this.media_ = this.mainPlaylistLoader_.main.playlists[this.media_.id];
 
-         } // This will filter out updated sidx info from the mapping
 
-         this.mainPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.sidxMapping_);
 
-         this.addSidxSegments_(this.media(), this.state, sidxChanged => {
 
-           // TODO: do we need to reload the current playlist?
 
-           this.refreshMedia_(this.media().id);
 
-         });
 
-       });
 
-     }
 
-     /**
 
-      * Refreshes the media playlist by re-parsing the main xml and updating playlist
 
-      * references. If this is an alternate loader, the updated parsed manifest is retrieved
 
-      * from the main loader.
 
-      */
 
-     refreshMedia_(mediaID) {
 
-       if (!mediaID) {
 
-         throw new Error('refreshMedia_ must take a media id');
 
-       } // for main we have to reparse the main xml
 
-       // to re-create segments based on current timing values
 
-       // which may change media. We only skip updating the main manifest
 
-       // if this is the first time this.media_ is being set.
 
-       // as main was just parsed in that case.
 
-       if (this.media_ && this.isMain_) {
 
-         this.handleMain_();
 
-       }
 
-       const playlists = this.mainPlaylistLoader_.main.playlists;
 
-       const mediaChanged = !this.media_ || this.media_ !== playlists[mediaID];
 
-       if (mediaChanged) {
 
-         this.media_ = playlists[mediaID];
 
-       } else {
 
-         this.trigger('playlistunchanged');
 
-       }
 
-       if (!this.mediaUpdateTimeout) {
 
-         const createMediaUpdateTimeout = () => {
 
-           if (this.media().endList) {
 
-             return;
 
-           }
 
-           this.mediaUpdateTimeout = window.setTimeout(() => {
 
-             this.trigger('mediaupdatetimeout');
 
-             createMediaUpdateTimeout();
 
-           }, refreshDelay(this.media(), Boolean(mediaChanged)));
 
-         };
 
-         createMediaUpdateTimeout();
 
-       }
 
-       this.trigger('loadedplaylist');
 
-     }
 
-   }
 
-   var Config = {
 
-     GOAL_BUFFER_LENGTH: 30,
 
-     MAX_GOAL_BUFFER_LENGTH: 60,
 
-     BACK_BUFFER_LENGTH: 30,
 
-     GOAL_BUFFER_LENGTH_RATE: 1,
 
-     // 0.5 MB/s
 
-     INITIAL_BANDWIDTH: 4194304,
 
-     // A fudge factor to apply to advertised playlist bitrates to account for
 
-     // temporary flucations in client bandwidth
 
-     BANDWIDTH_VARIANCE: 1.2,
 
-     // How much of the buffer must be filled before we consider upswitching
 
-     BUFFER_LOW_WATER_LINE: 0,
 
-     MAX_BUFFER_LOW_WATER_LINE: 30,
 
-     // TODO: Remove this when experimentalBufferBasedABR is removed
 
-     EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE: 16,
 
-     BUFFER_LOW_WATER_LINE_RATE: 1,
 
-     // If the buffer is greater than the high water line, we won't switch down
 
-     BUFFER_HIGH_WATER_LINE: 30
 
-   };
 
-   const stringToArrayBuffer = string => {
 
-     const view = new Uint8Array(new ArrayBuffer(string.length));
 
-     for (let i = 0; i < string.length; i++) {
 
-       view[i] = string.charCodeAt(i);
 
-     }
 
-     return view.buffer;
 
-   };
 
-   /* global Blob, BlobBuilder, Worker */
 
-   // unify worker interface
 
-   const browserWorkerPolyFill = function (workerObj) {
 
-     // node only supports on/off
 
-     workerObj.on = workerObj.addEventListener;
 
-     workerObj.off = workerObj.removeEventListener;
 
-     return workerObj;
 
-   };
 
-   const createObjectURL = function (str) {
 
-     try {
 
-       return URL.createObjectURL(new Blob([str], {
 
-         type: 'application/javascript'
 
-       }));
 
-     } catch (e) {
 
-       const blob = new BlobBuilder();
 
-       blob.append(str);
 
-       return URL.createObjectURL(blob.getBlob());
 
-     }
 
-   };
 
-   const factory = function (code) {
 
-     return function () {
 
-       const objectUrl = createObjectURL(code);
 
-       const worker = browserWorkerPolyFill(new Worker(objectUrl));
 
-       worker.objURL = objectUrl;
 
-       const terminate = worker.terminate;
 
-       worker.on = worker.addEventListener;
 
-       worker.off = worker.removeEventListener;
 
-       worker.terminate = function () {
 
-         URL.revokeObjectURL(objectUrl);
 
-         return terminate.call(this);
 
-       };
 
-       return worker;
 
-     };
 
-   };
 
-   const transform = function (code) {
 
-     return `var browserWorkerPolyFill = ${browserWorkerPolyFill.toString()};\n` + 'browserWorkerPolyFill(self);\n' + code;
 
-   };
 
-   const getWorkerString = function (fn) {
 
-     return fn.toString().replace(/^function.+?{/, '').slice(0, -1);
 
-   };
 
-   /* rollup-plugin-worker-factory start for worker!/Users/ddashkevich/projects/http-streaming/src/transmuxer-worker.js */
 
-   const workerCode$1 = transform(getWorkerString(function () {
 
-     var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * A lightweight readable stream implemention that handles event dispatching.
 
-      * Objects that inherit from streams should call init in their constructors.
 
-      */
 
-     var Stream$8 = function () {
 
-       this.init = function () {
 
-         var listeners = {};
 
-         /**
 
-          * Add a listener for a specified event type.
 
-          * @param type {string} the event name
 
-          * @param listener {function} the callback to be invoked when an event of
 
-          * the specified type occurs
 
-          */
 
-         this.on = function (type, listener) {
 
-           if (!listeners[type]) {
 
-             listeners[type] = [];
 
-           }
 
-           listeners[type] = listeners[type].concat(listener);
 
-         };
 
-         /**
 
-          * Remove a listener for a specified event type.
 
-          * @param type {string} the event name
 
-          * @param listener {function} a function previously registered for this
 
-          * type of event through `on`
 
-          */
 
-         this.off = function (type, listener) {
 
-           var index;
 
-           if (!listeners[type]) {
 
-             return false;
 
-           }
 
-           index = listeners[type].indexOf(listener);
 
-           listeners[type] = listeners[type].slice();
 
-           listeners[type].splice(index, 1);
 
-           return index > -1;
 
-         };
 
-         /**
 
-          * Trigger an event of the specified type on this stream. Any additional
 
-          * arguments to this function are passed as parameters to event listeners.
 
-          * @param type {string} the event name
 
-          */
 
-         this.trigger = function (type) {
 
-           var callbacks, i, length, args;
 
-           callbacks = listeners[type];
 
-           if (!callbacks) {
 
-             return;
 
-           } // Slicing the arguments on every invocation of this method
 
-           // can add a significant amount of overhead. Avoid the
 
-           // intermediate object creation for the common case of a
 
-           // single callback argument
 
-           if (arguments.length === 2) {
 
-             length = callbacks.length;
 
-             for (i = 0; i < length; ++i) {
 
-               callbacks[i].call(this, arguments[1]);
 
-             }
 
-           } else {
 
-             args = [];
 
-             i = arguments.length;
 
-             for (i = 1; i < arguments.length; ++i) {
 
-               args.push(arguments[i]);
 
-             }
 
-             length = callbacks.length;
 
-             for (i = 0; i < length; ++i) {
 
-               callbacks[i].apply(this, args);
 
-             }
 
-           }
 
-         };
 
-         /**
 
-          * Destroys the stream and cleans up.
 
-          */
 
-         this.dispose = function () {
 
-           listeners = {};
 
-         };
 
-       };
 
-     };
 
-     /**
 
-      * Forwards all `data` events on this stream to the destination stream. The
 
-      * destination stream should provide a method `push` to receive the data
 
-      * events as they arrive.
 
-      * @param destination {stream} the stream that will receive all `data` events
 
-      * @param autoFlush {boolean} if false, we will not call `flush` on the destination
 
-      *                            when the current stream emits a 'done' event
 
-      * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
 
-      */
 
-     Stream$8.prototype.pipe = function (destination) {
 
-       this.on('data', function (data) {
 
-         destination.push(data);
 
-       });
 
-       this.on('done', function (flushSource) {
 
-         destination.flush(flushSource);
 
-       });
 
-       this.on('partialdone', function (flushSource) {
 
-         destination.partialFlush(flushSource);
 
-       });
 
-       this.on('endedtimeline', function (flushSource) {
 
-         destination.endTimeline(flushSource);
 
-       });
 
-       this.on('reset', function (flushSource) {
 
-         destination.reset(flushSource);
 
-       });
 
-       return destination;
 
-     }; // Default stream functions that are expected to be overridden to perform
 
-     // actual work. These are provided by the prototype as a sort of no-op
 
-     // implementation so that we don't have to check for their existence in the
 
-     // `pipe` function above.
 
-     Stream$8.prototype.push = function (data) {
 
-       this.trigger('data', data);
 
-     };
 
-     Stream$8.prototype.flush = function (flushSource) {
 
-       this.trigger('done', flushSource);
 
-     };
 
-     Stream$8.prototype.partialFlush = function (flushSource) {
 
-       this.trigger('partialdone', flushSource);
 
-     };
 
-     Stream$8.prototype.endTimeline = function (flushSource) {
 
-       this.trigger('endedtimeline', flushSource);
 
-     };
 
-     Stream$8.prototype.reset = function (flushSource) {
 
-       this.trigger('reset', flushSource);
 
-     };
 
-     var stream = Stream$8;
 
-     var MAX_UINT32$1 = Math.pow(2, 32);
 
-     var getUint64$3 = function (uint8) {
 
-       var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
 
-       var value;
 
-       if (dv.getBigUint64) {
 
-         value = dv.getBigUint64(0);
 
-         if (value < Number.MAX_SAFE_INTEGER) {
 
-           return Number(value);
 
-         }
 
-         return value;
 
-       }
 
-       return dv.getUint32(0) * MAX_UINT32$1 + dv.getUint32(4);
 
-     };
 
-     var numbers = {
 
-       getUint64: getUint64$3,
 
-       MAX_UINT32: MAX_UINT32$1
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Functions that generate fragmented MP4s suitable for use with Media
 
-      * Source Extensions.
 
-      */
 
-     var MAX_UINT32 = numbers.MAX_UINT32;
 
-     var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun$1, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
 
-     (function () {
 
-       var i;
 
-       types = {
 
-         avc1: [],
 
-         // codingname
 
-         avcC: [],
 
-         btrt: [],
 
-         dinf: [],
 
-         dref: [],
 
-         esds: [],
 
-         ftyp: [],
 
-         hdlr: [],
 
-         mdat: [],
 
-         mdhd: [],
 
-         mdia: [],
 
-         mfhd: [],
 
-         minf: [],
 
-         moof: [],
 
-         moov: [],
 
-         mp4a: [],
 
-         // codingname
 
-         mvex: [],
 
-         mvhd: [],
 
-         pasp: [],
 
-         sdtp: [],
 
-         smhd: [],
 
-         stbl: [],
 
-         stco: [],
 
-         stsc: [],
 
-         stsd: [],
 
-         stsz: [],
 
-         stts: [],
 
-         styp: [],
 
-         tfdt: [],
 
-         tfhd: [],
 
-         traf: [],
 
-         trak: [],
 
-         trun: [],
 
-         trex: [],
 
-         tkhd: [],
 
-         vmhd: []
 
-       }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
 
-       // don't throw an error
 
-       if (typeof Uint8Array === 'undefined') {
 
-         return;
 
-       }
 
-       for (i in types) {
 
-         if (types.hasOwnProperty(i)) {
 
-           types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
 
-         }
 
-       }
 
-       MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
 
-       AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
 
-       MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
 
-       VIDEO_HDLR = new Uint8Array([0x00,
 
-       // version 0
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // pre_defined
 
-       0x76, 0x69, 0x64, 0x65,
 
-       // handler_type: 'vide'
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
 
-       ]);
 
-       AUDIO_HDLR = new Uint8Array([0x00,
 
-       // version 0
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // pre_defined
 
-       0x73, 0x6f, 0x75, 0x6e,
 
-       // handler_type: 'soun'
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
 
-       ]);
 
-       HDLR_TYPES = {
 
-         video: VIDEO_HDLR,
 
-         audio: AUDIO_HDLR
 
-       };
 
-       DREF = new Uint8Array([0x00,
 
-       // version 0
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       0x00, 0x00, 0x00, 0x01,
 
-       // entry_count
 
-       0x00, 0x00, 0x00, 0x0c,
 
-       // entry_size
 
-       0x75, 0x72, 0x6c, 0x20,
 
-       // 'url' type
 
-       0x00,
 
-       // version 0
 
-       0x00, 0x00, 0x01 // entry_flags
 
-       ]);
 
-       SMHD = new Uint8Array([0x00,
 
-       // version
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       0x00, 0x00,
 
-       // balance, 0 means centered
 
-       0x00, 0x00 // reserved
 
-       ]);
 
-       STCO = new Uint8Array([0x00,
 
-       // version
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       0x00, 0x00, 0x00, 0x00 // entry_count
 
-       ]);
 
-       STSC = STCO;
 
-       STSZ = new Uint8Array([0x00,
 
-       // version
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // sample_size
 
-       0x00, 0x00, 0x00, 0x00 // sample_count
 
-       ]);
 
-       STTS = STCO;
 
-       VMHD = new Uint8Array([0x00,
 
-       // version
 
-       0x00, 0x00, 0x01,
 
-       // flags
 
-       0x00, 0x00,
 
-       // graphicsmode
 
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
 
-       ]);
 
-     })();
 
-     box = function (type) {
 
-       var payload = [],
 
-         size = 0,
 
-         i,
 
-         result,
 
-         view;
 
-       for (i = 1; i < arguments.length; i++) {
 
-         payload.push(arguments[i]);
 
-       }
 
-       i = payload.length; // calculate the total size we need to allocate
 
-       while (i--) {
 
-         size += payload[i].byteLength;
 
-       }
 
-       result = new Uint8Array(size + 8);
 
-       view = new DataView(result.buffer, result.byteOffset, result.byteLength);
 
-       view.setUint32(0, result.byteLength);
 
-       result.set(type, 4); // copy the payload into the result
 
-       for (i = 0, size = 8; i < payload.length; i++) {
 
-         result.set(payload[i], size);
 
-         size += payload[i].byteLength;
 
-       }
 
-       return result;
 
-     };
 
-     dinf = function () {
 
-       return box(types.dinf, box(types.dref, DREF));
 
-     };
 
-     esds = function (track) {
 
-       return box(types.esds, new Uint8Array([0x00,
 
-       // version
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       // ES_Descriptor
 
-       0x03,
 
-       // tag, ES_DescrTag
 
-       0x19,
 
-       // length
 
-       0x00, 0x00,
 
-       // ES_ID
 
-       0x00,
 
-       // streamDependenceFlag, URL_flag, reserved, streamPriority
 
-       // DecoderConfigDescriptor
 
-       0x04,
 
-       // tag, DecoderConfigDescrTag
 
-       0x11,
 
-       // length
 
-       0x40,
 
-       // object type
 
-       0x15,
 
-       // streamType
 
-       0x00, 0x06, 0x00,
 
-       // bufferSizeDB
 
-       0x00, 0x00, 0xda, 0xc0,
 
-       // maxBitrate
 
-       0x00, 0x00, 0xda, 0xc0,
 
-       // avgBitrate
 
-       // DecoderSpecificInfo
 
-       0x05,
 
-       // tag, DecoderSpecificInfoTag
 
-       0x02,
 
-       // length
 
-       // ISO/IEC 14496-3, AudioSpecificConfig
 
-       // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
 
-       track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
 
-       ]));
 
-     };
 
-     ftyp = function () {
 
-       return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
 
-     };
 
-     hdlr = function (type) {
 
-       return box(types.hdlr, HDLR_TYPES[type]);
 
-     };
 
-     mdat = function (data) {
 
-       return box(types.mdat, data);
 
-     };
 
-     mdhd = function (track) {
 
-       var result = new Uint8Array([0x00,
 
-       // version 0
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       0x00, 0x00, 0x00, 0x02,
 
-       // creation_time
 
-       0x00, 0x00, 0x00, 0x03,
 
-       // modification_time
 
-       0x00, 0x01, 0x5f, 0x90,
 
-       // timescale, 90,000 "ticks" per second
 
-       track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF,
 
-       // duration
 
-       0x55, 0xc4,
 
-       // 'und' language (undetermined)
 
-       0x00, 0x00]); // Use the sample rate from the track metadata, when it is
 
-       // defined. The sample rate can be parsed out of an ADTS header, for
 
-       // instance.
 
-       if (track.samplerate) {
 
-         result[12] = track.samplerate >>> 24 & 0xFF;
 
-         result[13] = track.samplerate >>> 16 & 0xFF;
 
-         result[14] = track.samplerate >>> 8 & 0xFF;
 
-         result[15] = track.samplerate & 0xFF;
 
-       }
 
-       return box(types.mdhd, result);
 
-     };
 
-     mdia = function (track) {
 
-       return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
 
-     };
 
-     mfhd = function (sequenceNumber) {
 
-       return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00,
 
-       // flags
 
-       (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
 
-       ]));
 
-     };
 
-     minf = function (track) {
 
-       return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
 
-     };
 
-     moof = function (sequenceNumber, tracks) {
 
-       var trackFragments = [],
 
-         i = tracks.length; // build traf boxes for each track fragment
 
-       while (i--) {
 
-         trackFragments[i] = traf(tracks[i]);
 
-       }
 
-       return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
 
-     };
 
-     /**
 
-      * Returns a movie box.
 
-      * @param tracks {array} the tracks associated with this movie
 
-      * @see ISO/IEC 14496-12:2012(E), section 8.2.1
 
-      */
 
-     moov = function (tracks) {
 
-       var i = tracks.length,
 
-         boxes = [];
 
-       while (i--) {
 
-         boxes[i] = trak(tracks[i]);
 
-       }
 
-       return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
 
-     };
 
-     mvex = function (tracks) {
 
-       var i = tracks.length,
 
-         boxes = [];
 
-       while (i--) {
 
-         boxes[i] = trex(tracks[i]);
 
-       }
 
-       return box.apply(null, [types.mvex].concat(boxes));
 
-     };
 
-     mvhd = function (duration) {
 
-       var bytes = new Uint8Array([0x00,
 
-       // version 0
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       0x00, 0x00, 0x00, 0x01,
 
-       // creation_time
 
-       0x00, 0x00, 0x00, 0x02,
 
-       // modification_time
 
-       0x00, 0x01, 0x5f, 0x90,
 
-       // timescale, 90,000 "ticks" per second
 
-       (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF,
 
-       // duration
 
-       0x00, 0x01, 0x00, 0x00,
 
-       // 1.0 rate
 
-       0x01, 0x00,
 
-       // 1.0 volume
 
-       0x00, 0x00,
 
-       // reserved
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       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
 
-       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
 
-       0xff, 0xff, 0xff, 0xff // next_track_ID
 
-       ]);
 
-       return box(types.mvhd, bytes);
 
-     };
 
-     sdtp = function (track) {
 
-       var samples = track.samples || [],
 
-         bytes = new Uint8Array(4 + samples.length),
 
-         flags,
 
-         i; // leave the full box header (4 bytes) all zero
 
-       // write the sample table
 
-       for (i = 0; i < samples.length; i++) {
 
-         flags = samples[i].flags;
 
-         bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
 
-       }
 
-       return box(types.sdtp, bytes);
 
-     };
 
-     stbl = function (track) {
 
-       return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
 
-     };
 
-     (function () {
 
-       var videoSample, audioSample;
 
-       stsd = function (track) {
 
-         return box(types.stsd, new Uint8Array([0x00,
 
-         // version 0
 
-         0x00, 0x00, 0x00,
 
-         // flags
 
-         0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
 
-       };
 
-       videoSample = function (track) {
 
-         var sps = track.sps || [],
 
-           pps = track.pps || [],
 
-           sequenceParameterSets = [],
 
-           pictureParameterSets = [],
 
-           i,
 
-           avc1Box; // assemble the SPSs
 
-         for (i = 0; i < sps.length; i++) {
 
-           sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
 
-           sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
 
-           sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
 
-         } // assemble the PPSs
 
-         for (i = 0; i < pps.length; i++) {
 
-           pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
 
-           pictureParameterSets.push(pps[i].byteLength & 0xFF);
 
-           pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
 
-         }
 
-         avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 
-         // reserved
 
-         0x00, 0x01,
 
-         // data_reference_index
 
-         0x00, 0x00,
 
-         // pre_defined
 
-         0x00, 0x00,
 
-         // reserved
 
-         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 
-         // pre_defined
 
-         (track.width & 0xff00) >> 8, track.width & 0xff,
 
-         // width
 
-         (track.height & 0xff00) >> 8, track.height & 0xff,
 
-         // height
 
-         0x00, 0x48, 0x00, 0x00,
 
-         // horizresolution
 
-         0x00, 0x48, 0x00, 0x00,
 
-         // vertresolution
 
-         0x00, 0x00, 0x00, 0x00,
 
-         // reserved
 
-         0x00, 0x01,
 
-         // frame_count
 
-         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
 
-         0x00, 0x18,
 
-         // depth = 24
 
-         0x11, 0x11 // pre_defined = -1
 
-         ]), box(types.avcC, new Uint8Array([0x01,
 
-         // configurationVersion
 
-         track.profileIdc,
 
-         // AVCProfileIndication
 
-         track.profileCompatibility,
 
-         // profile_compatibility
 
-         track.levelIdc,
 
-         // AVCLevelIndication
 
-         0xff // lengthSizeMinusOne, hard-coded to 4 bytes
 
-         ].concat([sps.length],
 
-         // numOfSequenceParameterSets
 
-         sequenceParameterSets,
 
-         // "SPS"
 
-         [pps.length],
 
-         // numOfPictureParameterSets
 
-         pictureParameterSets // "PPS"
 
-         ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
 
-         // bufferSizeDB
 
-         0x00, 0x2d, 0xc6, 0xc0,
 
-         // maxBitrate
 
-         0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
 
-         ]))];
 
-         if (track.sarRatio) {
 
-           var hSpacing = track.sarRatio[0],
 
-             vSpacing = track.sarRatio[1];
 
-           avc1Box.push(box(types.pasp, new Uint8Array([(hSpacing & 0xFF000000) >> 24, (hSpacing & 0xFF0000) >> 16, (hSpacing & 0xFF00) >> 8, hSpacing & 0xFF, (vSpacing & 0xFF000000) >> 24, (vSpacing & 0xFF0000) >> 16, (vSpacing & 0xFF00) >> 8, vSpacing & 0xFF])));
 
-         }
 
-         return box.apply(null, avc1Box);
 
-       };
 
-       audioSample = function (track) {
 
-         return box(types.mp4a, new Uint8Array([
 
-         // SampleEntry, ISO/IEC 14496-12
 
-         0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 
-         // reserved
 
-         0x00, 0x01,
 
-         // data_reference_index
 
-         // AudioSampleEntry, ISO/IEC 14496-12
 
-         0x00, 0x00, 0x00, 0x00,
 
-         // reserved
 
-         0x00, 0x00, 0x00, 0x00,
 
-         // reserved
 
-         (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff,
 
-         // channelcount
 
-         (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff,
 
-         // samplesize
 
-         0x00, 0x00,
 
-         // pre_defined
 
-         0x00, 0x00,
 
-         // reserved
 
-         (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
 
-         // MP4AudioSampleEntry, ISO/IEC 14496-14
 
-         ]), esds(track));
 
-       };
 
-     })();
 
-     tkhd = function (track) {
 
-       var result = new Uint8Array([0x00,
 
-       // version 0
 
-       0x00, 0x00, 0x07,
 
-       // flags
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // creation_time
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // modification_time
 
-       (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
 
-       // track_ID
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF,
 
-       // duration
 
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 
-       // reserved
 
-       0x00, 0x00,
 
-       // layer
 
-       0x00, 0x00,
 
-       // alternate_group
 
-       0x01, 0x00,
 
-       // non-audio track volume
 
-       0x00, 0x00,
 
-       // reserved
 
-       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
 
-       (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00,
 
-       // width
 
-       (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
 
-       ]);
 
-       return box(types.tkhd, result);
 
-     };
 
-     /**
 
-      * Generate a track fragment (traf) box. A traf box collects metadata
 
-      * about tracks in a movie fragment (moof) box.
 
-      */
 
-     traf = function (track) {
 
-       var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
 
-       trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00,
 
-       // version 0
 
-       0x00, 0x00, 0x3a,
 
-       // flags
 
-       (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
 
-       // track_ID
 
-       0x00, 0x00, 0x00, 0x01,
 
-       // sample_description_index
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // default_sample_duration
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // default_sample_size
 
-       0x00, 0x00, 0x00, 0x00 // default_sample_flags
 
-       ]));
 
-       upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / MAX_UINT32);
 
-       lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % MAX_UINT32);
 
-       trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01,
 
-       // version 1
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       // baseMediaDecodeTime
 
-       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
 
-       // the containing moof to the first payload byte of the associated
 
-       // mdat
 
-       dataOffset = 32 +
 
-       // tfhd
 
-       20 +
 
-       // tfdt
 
-       8 +
 
-       // traf header
 
-       16 +
 
-       // mfhd
 
-       8 +
 
-       // moof header
 
-       8; // mdat header
 
-       // audio tracks require less metadata
 
-       if (track.type === 'audio') {
 
-         trackFragmentRun = trun$1(track, dataOffset);
 
-         return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
 
-       } // video tracks should contain an independent and disposable samples
 
-       // box (sdtp)
 
-       // generate one and adjust offsets to match
 
-       sampleDependencyTable = sdtp(track);
 
-       trackFragmentRun = trun$1(track, sampleDependencyTable.length + dataOffset);
 
-       return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
 
-     };
 
-     /**
 
-      * Generate a track box.
 
-      * @param track {object} a track definition
 
-      * @return {Uint8Array} the track box
 
-      */
 
-     trak = function (track) {
 
-       track.duration = track.duration || 0xffffffff;
 
-       return box(types.trak, tkhd(track), mdia(track));
 
-     };
 
-     trex = function (track) {
 
-       var result = new Uint8Array([0x00,
 
-       // version 0
 
-       0x00, 0x00, 0x00,
 
-       // flags
 
-       (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
 
-       // track_ID
 
-       0x00, 0x00, 0x00, 0x01,
 
-       // default_sample_description_index
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // default_sample_duration
 
-       0x00, 0x00, 0x00, 0x00,
 
-       // default_sample_size
 
-       0x00, 0x01, 0x00, 0x01 // default_sample_flags
 
-       ]); // the last two bytes of default_sample_flags is the sample
 
-       // degradation priority, a hint about the importance of this sample
 
-       // relative to others. Lower the degradation priority for all sample
 
-       // types other than video.
 
-       if (track.type !== 'video') {
 
-         result[result.length - 1] = 0x00;
 
-       }
 
-       return box(types.trex, result);
 
-     };
 
-     (function () {
 
-       var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
 
-       // duration is present for the first sample, it will be present for
 
-       // all subsequent samples.
 
-       // see ISO/IEC 14496-12:2012, Section 8.8.8.1
 
-       trunHeader = function (samples, offset) {
 
-         var durationPresent = 0,
 
-           sizePresent = 0,
 
-           flagsPresent = 0,
 
-           compositionTimeOffset = 0; // trun flag constants
 
-         if (samples.length) {
 
-           if (samples[0].duration !== undefined) {
 
-             durationPresent = 0x1;
 
-           }
 
-           if (samples[0].size !== undefined) {
 
-             sizePresent = 0x2;
 
-           }
 
-           if (samples[0].flags !== undefined) {
 
-             flagsPresent = 0x4;
 
-           }
 
-           if (samples[0].compositionTimeOffset !== undefined) {
 
-             compositionTimeOffset = 0x8;
 
-           }
 
-         }
 
-         return [0x00,
 
-         // version 0
 
-         0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01,
 
-         // flags
 
-         (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF,
 
-         // sample_count
 
-         (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
 
-         ];
 
-       };
 
-       videoTrun = function (track, offset) {
 
-         var bytesOffest, bytes, header, samples, sample, i;
 
-         samples = track.samples || [];
 
-         offset += 8 + 12 + 16 * samples.length;
 
-         header = trunHeader(samples, offset);
 
-         bytes = new Uint8Array(header.length + samples.length * 16);
 
-         bytes.set(header);
 
-         bytesOffest = header.length;
 
-         for (i = 0; i < samples.length; i++) {
 
-           sample = samples[i];
 
-           bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
 
-           bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
 
-           bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
 
-           bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
 
-           bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
 
-           bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
 
-           bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
 
-           bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
 
-           bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn;
 
-           bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample;
 
-           bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
 
-           bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
 
-           bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
 
-           bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
 
-           bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
 
-           bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
 
-         }
 
-         return box(types.trun, bytes);
 
-       };
 
-       audioTrun = function (track, offset) {
 
-         var bytes, bytesOffest, header, samples, sample, i;
 
-         samples = track.samples || [];
 
-         offset += 8 + 12 + 8 * samples.length;
 
-         header = trunHeader(samples, offset);
 
-         bytes = new Uint8Array(header.length + samples.length * 8);
 
-         bytes.set(header);
 
-         bytesOffest = header.length;
 
-         for (i = 0; i < samples.length; i++) {
 
-           sample = samples[i];
 
-           bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
 
-           bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
 
-           bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
 
-           bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
 
-           bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
 
-           bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
 
-           bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
 
-           bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
 
-         }
 
-         return box(types.trun, bytes);
 
-       };
 
-       trun$1 = function (track, offset) {
 
-         if (track.type === 'audio') {
 
-           return audioTrun(track, offset);
 
-         }
 
-         return videoTrun(track, offset);
 
-       };
 
-     })();
 
-     var mp4Generator = {
 
-       ftyp: ftyp,
 
-       mdat: mdat,
 
-       moof: moof,
 
-       moov: moov,
 
-       initSegment: function (tracks) {
 
-         var fileType = ftyp(),
 
-           movie = moov(tracks),
 
-           result;
 
-         result = new Uint8Array(fileType.byteLength + movie.byteLength);
 
-         result.set(fileType);
 
-         result.set(movie, fileType.byteLength);
 
-         return result;
 
-       }
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     // composed of the nal units that make up that frame
 
-     // Also keep track of cummulative data about the frame from the nal units such
 
-     // as the frame duration, starting pts, etc.
 
-     var groupNalsIntoFrames = function (nalUnits) {
 
-       var i,
 
-         currentNal,
 
-         currentFrame = [],
 
-         frames = []; // TODO added for LHLS, make sure this is OK
 
-       frames.byteLength = 0;
 
-       frames.nalCount = 0;
 
-       frames.duration = 0;
 
-       currentFrame.byteLength = 0;
 
-       for (i = 0; i < nalUnits.length; i++) {
 
-         currentNal = nalUnits[i]; // Split on 'aud'-type nal units
 
-         if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
 
-           // Since the very first nal unit is expected to be an AUD
 
-           // only push to the frames array when currentFrame is not empty
 
-           if (currentFrame.length) {
 
-             currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
 
-             frames.byteLength += currentFrame.byteLength;
 
-             frames.nalCount += currentFrame.length;
 
-             frames.duration += currentFrame.duration;
 
-             frames.push(currentFrame);
 
-           }
 
-           currentFrame = [currentNal];
 
-           currentFrame.byteLength = currentNal.data.byteLength;
 
-           currentFrame.pts = currentNal.pts;
 
-           currentFrame.dts = currentNal.dts;
 
-         } else {
 
-           // Specifically flag key frames for ease of use later
 
-           if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
 
-             currentFrame.keyFrame = true;
 
-           }
 
-           currentFrame.duration = currentNal.dts - currentFrame.dts;
 
-           currentFrame.byteLength += currentNal.data.byteLength;
 
-           currentFrame.push(currentNal);
 
-         }
 
-       } // For the last frame, use the duration of the previous frame if we
 
-       // have nothing better to go on
 
-       if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
 
-         currentFrame.duration = frames[frames.length - 1].duration;
 
-       } // Push the final frame
 
-       // TODO added for LHLS, make sure this is OK
 
-       frames.byteLength += currentFrame.byteLength;
 
-       frames.nalCount += currentFrame.length;
 
-       frames.duration += currentFrame.duration;
 
-       frames.push(currentFrame);
 
-       return frames;
 
-     }; // Convert an array of frames into an array of Gop with each Gop being composed
 
-     // of the frames that make up that Gop
 
-     // Also keep track of cummulative data about the Gop from the frames such as the
 
-     // Gop duration, starting pts, etc.
 
-     var groupFramesIntoGops = function (frames) {
 
-       var i,
 
-         currentFrame,
 
-         currentGop = [],
 
-         gops = []; // We must pre-set some of the values on the Gop since we
 
-       // keep running totals of these values
 
-       currentGop.byteLength = 0;
 
-       currentGop.nalCount = 0;
 
-       currentGop.duration = 0;
 
-       currentGop.pts = frames[0].pts;
 
-       currentGop.dts = frames[0].dts; // store some metadata about all the Gops
 
-       gops.byteLength = 0;
 
-       gops.nalCount = 0;
 
-       gops.duration = 0;
 
-       gops.pts = frames[0].pts;
 
-       gops.dts = frames[0].dts;
 
-       for (i = 0; i < frames.length; i++) {
 
-         currentFrame = frames[i];
 
-         if (currentFrame.keyFrame) {
 
-           // Since the very first frame is expected to be an keyframe
 
-           // only push to the gops array when currentGop is not empty
 
-           if (currentGop.length) {
 
-             gops.push(currentGop);
 
-             gops.byteLength += currentGop.byteLength;
 
-             gops.nalCount += currentGop.nalCount;
 
-             gops.duration += currentGop.duration;
 
-           }
 
-           currentGop = [currentFrame];
 
-           currentGop.nalCount = currentFrame.length;
 
-           currentGop.byteLength = currentFrame.byteLength;
 
-           currentGop.pts = currentFrame.pts;
 
-           currentGop.dts = currentFrame.dts;
 
-           currentGop.duration = currentFrame.duration;
 
-         } else {
 
-           currentGop.duration += currentFrame.duration;
 
-           currentGop.nalCount += currentFrame.length;
 
-           currentGop.byteLength += currentFrame.byteLength;
 
-           currentGop.push(currentFrame);
 
-         }
 
-       }
 
-       if (gops.length && currentGop.duration <= 0) {
 
-         currentGop.duration = gops[gops.length - 1].duration;
 
-       }
 
-       gops.byteLength += currentGop.byteLength;
 
-       gops.nalCount += currentGop.nalCount;
 
-       gops.duration += currentGop.duration; // push the final Gop
 
-       gops.push(currentGop);
 
-       return gops;
 
-     };
 
-     /*
 
-      * Search for the first keyframe in the GOPs and throw away all frames
 
-      * until that keyframe. Then extend the duration of the pulled keyframe
 
-      * and pull the PTS and DTS of the keyframe so that it covers the time
 
-      * range of the frames that were disposed.
 
-      *
 
-      * @param {Array} gops video GOPs
 
-      * @returns {Array} modified video GOPs
 
-      */
 
-     var extendFirstKeyFrame = function (gops) {
 
-       var currentGop;
 
-       if (!gops[0][0].keyFrame && gops.length > 1) {
 
-         // Remove the first GOP
 
-         currentGop = gops.shift();
 
-         gops.byteLength -= currentGop.byteLength;
 
-         gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
 
-         // first gop to cover the time period of the
 
-         // frames we just removed
 
-         gops[0][0].dts = currentGop.dts;
 
-         gops[0][0].pts = currentGop.pts;
 
-         gops[0][0].duration += currentGop.duration;
 
-       }
 
-       return gops;
 
-     };
 
-     /**
 
-      * Default sample object
 
-      * see ISO/IEC 14496-12:2012, section 8.6.4.3
 
-      */
 
-     var createDefaultSample = function () {
 
-       return {
 
-         size: 0,
 
-         flags: {
 
-           isLeading: 0,
 
-           dependsOn: 1,
 
-           isDependedOn: 0,
 
-           hasRedundancy: 0,
 
-           degradationPriority: 0,
 
-           isNonSyncSample: 1
 
-         }
 
-       };
 
-     };
 
-     /*
 
-      * Collates information from a video frame into an object for eventual
 
-      * entry into an MP4 sample table.
 
-      *
 
-      * @param {Object} frame the video frame
 
-      * @param {Number} dataOffset the byte offset to position the sample
 
-      * @return {Object} object containing sample table info for a frame
 
-      */
 
-     var sampleForFrame = function (frame, dataOffset) {
 
-       var sample = createDefaultSample();
 
-       sample.dataOffset = dataOffset;
 
-       sample.compositionTimeOffset = frame.pts - frame.dts;
 
-       sample.duration = frame.duration;
 
-       sample.size = 4 * frame.length; // Space for nal unit size
 
-       sample.size += frame.byteLength;
 
-       if (frame.keyFrame) {
 
-         sample.flags.dependsOn = 2;
 
-         sample.flags.isNonSyncSample = 0;
 
-       }
 
-       return sample;
 
-     }; // generate the track's sample table from an array of gops
 
-     var generateSampleTable$1 = function (gops, baseDataOffset) {
 
-       var h,
 
-         i,
 
-         sample,
 
-         currentGop,
 
-         currentFrame,
 
-         dataOffset = baseDataOffset || 0,
 
-         samples = [];
 
-       for (h = 0; h < gops.length; h++) {
 
-         currentGop = gops[h];
 
-         for (i = 0; i < currentGop.length; i++) {
 
-           currentFrame = currentGop[i];
 
-           sample = sampleForFrame(currentFrame, dataOffset);
 
-           dataOffset += sample.size;
 
-           samples.push(sample);
 
-         }
 
-       }
 
-       return samples;
 
-     }; // generate the track's raw mdat data from an array of gops
 
-     var concatenateNalData = function (gops) {
 
-       var h,
 
-         i,
 
-         j,
 
-         currentGop,
 
-         currentFrame,
 
-         currentNal,
 
-         dataOffset = 0,
 
-         nalsByteLength = gops.byteLength,
 
-         numberOfNals = gops.nalCount,
 
-         totalByteLength = nalsByteLength + 4 * numberOfNals,
 
-         data = new Uint8Array(totalByteLength),
 
-         view = new DataView(data.buffer); // For each Gop..
 
-       for (h = 0; h < gops.length; h++) {
 
-         currentGop = gops[h]; // For each Frame..
 
-         for (i = 0; i < currentGop.length; i++) {
 
-           currentFrame = currentGop[i]; // For each NAL..
 
-           for (j = 0; j < currentFrame.length; j++) {
 
-             currentNal = currentFrame[j];
 
-             view.setUint32(dataOffset, currentNal.data.byteLength);
 
-             dataOffset += 4;
 
-             data.set(currentNal.data, dataOffset);
 
-             dataOffset += currentNal.data.byteLength;
 
-           }
 
-         }
 
-       }
 
-       return data;
 
-     }; // generate the track's sample table from a frame
 
-     var generateSampleTableForFrame = function (frame, baseDataOffset) {
 
-       var sample,
 
-         dataOffset = baseDataOffset || 0,
 
-         samples = [];
 
-       sample = sampleForFrame(frame, dataOffset);
 
-       samples.push(sample);
 
-       return samples;
 
-     }; // generate the track's raw mdat data from a frame
 
-     var concatenateNalDataForFrame = function (frame) {
 
-       var i,
 
-         currentNal,
 
-         dataOffset = 0,
 
-         nalsByteLength = frame.byteLength,
 
-         numberOfNals = frame.length,
 
-         totalByteLength = nalsByteLength + 4 * numberOfNals,
 
-         data = new Uint8Array(totalByteLength),
 
-         view = new DataView(data.buffer); // For each NAL..
 
-       for (i = 0; i < frame.length; i++) {
 
-         currentNal = frame[i];
 
-         view.setUint32(dataOffset, currentNal.data.byteLength);
 
-         dataOffset += 4;
 
-         data.set(currentNal.data, dataOffset);
 
-         dataOffset += currentNal.data.byteLength;
 
-       }
 
-       return data;
 
-     };
 
-     var frameUtils$1 = {
 
-       groupNalsIntoFrames: groupNalsIntoFrames,
 
-       groupFramesIntoGops: groupFramesIntoGops,
 
-       extendFirstKeyFrame: extendFirstKeyFrame,
 
-       generateSampleTable: generateSampleTable$1,
 
-       concatenateNalData: concatenateNalData,
 
-       generateSampleTableForFrame: generateSampleTableForFrame,
 
-       concatenateNalDataForFrame: concatenateNalDataForFrame
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     var highPrefix = [33, 16, 5, 32, 164, 27];
 
-     var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
 
-     var zeroFill = function (count) {
 
-       var a = [];
 
-       while (count--) {
 
-         a.push(0);
 
-       }
 
-       return a;
 
-     };
 
-     var makeTable = function (metaTable) {
 
-       return Object.keys(metaTable).reduce(function (obj, key) {
 
-         obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
 
-           return arr.concat(part);
 
-         }, []));
 
-         return obj;
 
-       }, {});
 
-     };
 
-     var silence;
 
-     var silence_1 = function () {
 
-       if (!silence) {
 
-         // Frames-of-silence to use for filling in missing AAC frames
 
-         var coneOfSilence = {
 
-           96000: [highPrefix, [227, 64], zeroFill(154), [56]],
 
-           88200: [highPrefix, [231], zeroFill(170), [56]],
 
-           64000: [highPrefix, [248, 192], zeroFill(240), [56]],
 
-           48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
 
-           44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
 
-           32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
 
-           24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
 
-           16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
 
-           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]],
 
-           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]],
 
-           8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
 
-         };
 
-         silence = makeTable(coneOfSilence);
 
-       }
 
-       return silence;
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     var ONE_SECOND_IN_TS$4 = 90000,
 
-       // 90kHz clock
 
-       secondsToVideoTs,
 
-       secondsToAudioTs,
 
-       videoTsToSeconds,
 
-       audioTsToSeconds,
 
-       audioTsToVideoTs,
 
-       videoTsToAudioTs,
 
-       metadataTsToSeconds;
 
-     secondsToVideoTs = function (seconds) {
 
-       return seconds * ONE_SECOND_IN_TS$4;
 
-     };
 
-     secondsToAudioTs = function (seconds, sampleRate) {
 
-       return seconds * sampleRate;
 
-     };
 
-     videoTsToSeconds = function (timestamp) {
 
-       return timestamp / ONE_SECOND_IN_TS$4;
 
-     };
 
-     audioTsToSeconds = function (timestamp, sampleRate) {
 
-       return timestamp / sampleRate;
 
-     };
 
-     audioTsToVideoTs = function (timestamp, sampleRate) {
 
-       return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
 
-     };
 
-     videoTsToAudioTs = function (timestamp, sampleRate) {
 
-       return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
 
-     };
 
-     /**
 
-      * Adjust ID3 tag or caption timing information by the timeline pts values
 
-      * (if keepOriginalTimestamps is false) and convert to seconds
 
-      */
 
-     metadataTsToSeconds = function (timestamp, timelineStartPts, keepOriginalTimestamps) {
 
-       return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
 
-     };
 
-     var clock$2 = {
 
-       ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$4,
 
-       secondsToVideoTs: secondsToVideoTs,
 
-       secondsToAudioTs: secondsToAudioTs,
 
-       videoTsToSeconds: videoTsToSeconds,
 
-       audioTsToSeconds: audioTsToSeconds,
 
-       audioTsToVideoTs: audioTsToVideoTs,
 
-       videoTsToAudioTs: videoTsToAudioTs,
 
-       metadataTsToSeconds: metadataTsToSeconds
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     var coneOfSilence = silence_1;
 
-     var clock$1 = clock$2;
 
-     /**
 
-      * Sum the `byteLength` properties of the data in each AAC frame
 
-      */
 
-     var sumFrameByteLengths = function (array) {
 
-       var i,
 
-         currentObj,
 
-         sum = 0; // sum the byteLength's all each nal unit in the frame
 
-       for (i = 0; i < array.length; i++) {
 
-         currentObj = array[i];
 
-         sum += currentObj.data.byteLength;
 
-       }
 
-       return sum;
 
-     }; // Possibly pad (prefix) the audio track with silence if appending this track
 
-     // would lead to the introduction of a gap in the audio buffer
 
-     var prefixWithSilence = function (track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
 
-       var baseMediaDecodeTimeTs,
 
-         frameDuration = 0,
 
-         audioGapDuration = 0,
 
-         audioFillFrameCount = 0,
 
-         audioFillDuration = 0,
 
-         silentFrame,
 
-         i,
 
-         firstFrame;
 
-       if (!frames.length) {
 
-         return;
 
-       }
 
-       baseMediaDecodeTimeTs = clock$1.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
 
-       frameDuration = Math.ceil(clock$1.ONE_SECOND_IN_TS / (track.samplerate / 1024));
 
-       if (audioAppendStartTs && videoBaseMediaDecodeTime) {
 
-         // insert the shortest possible amount (audio gap or audio to video gap)
 
-         audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
 
-         audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
 
-         audioFillDuration = audioFillFrameCount * frameDuration;
 
-       } // don't attempt to fill gaps smaller than a single frame or larger
 
-       // than a half second
 
-       if (audioFillFrameCount < 1 || audioFillDuration > clock$1.ONE_SECOND_IN_TS / 2) {
 
-         return;
 
-       }
 
-       silentFrame = coneOfSilence()[track.samplerate];
 
-       if (!silentFrame) {
 
-         // we don't have a silent frame pregenerated for the sample rate, so use a frame
 
-         // from the content instead
 
-         silentFrame = frames[0].data;
 
-       }
 
-       for (i = 0; i < audioFillFrameCount; i++) {
 
-         firstFrame = frames[0];
 
-         frames.splice(0, 0, {
 
-           data: silentFrame,
 
-           dts: firstFrame.dts - frameDuration,
 
-           pts: firstFrame.pts - frameDuration
 
-         });
 
-       }
 
-       track.baseMediaDecodeTime -= Math.floor(clock$1.videoTsToAudioTs(audioFillDuration, track.samplerate));
 
-       return audioFillDuration;
 
-     }; // If the audio segment extends before the earliest allowed dts
 
-     // value, remove AAC frames until starts at or after the earliest
 
-     // allowed DTS so that we don't end up with a negative baseMedia-
 
-     // DecodeTime for the audio track
 
-     var trimAdtsFramesByEarliestDts = function (adtsFrames, track, earliestAllowedDts) {
 
-       if (track.minSegmentDts >= earliestAllowedDts) {
 
-         return adtsFrames;
 
-       } // We will need to recalculate the earliest segment Dts
 
-       track.minSegmentDts = Infinity;
 
-       return adtsFrames.filter(function (currentFrame) {
 
-         // If this is an allowed frame, keep it and record it's Dts
 
-         if (currentFrame.dts >= earliestAllowedDts) {
 
-           track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
 
-           track.minSegmentPts = track.minSegmentDts;
 
-           return true;
 
-         } // Otherwise, discard it
 
-         return false;
 
-       });
 
-     }; // generate the track's raw mdat data from an array of frames
 
-     var generateSampleTable = function (frames) {
 
-       var i,
 
-         currentFrame,
 
-         samples = [];
 
-       for (i = 0; i < frames.length; i++) {
 
-         currentFrame = frames[i];
 
-         samples.push({
 
-           size: currentFrame.data.byteLength,
 
-           duration: 1024 // For AAC audio, all samples contain 1024 samples
 
-         });
 
-       }
 
-       return samples;
 
-     }; // generate the track's sample table from an array of frames
 
-     var concatenateFrameData = function (frames) {
 
-       var i,
 
-         currentFrame,
 
-         dataOffset = 0,
 
-         data = new Uint8Array(sumFrameByteLengths(frames));
 
-       for (i = 0; i < frames.length; i++) {
 
-         currentFrame = frames[i];
 
-         data.set(currentFrame.data, dataOffset);
 
-         dataOffset += currentFrame.data.byteLength;
 
-       }
 
-       return data;
 
-     };
 
-     var audioFrameUtils$1 = {
 
-       prefixWithSilence: prefixWithSilence,
 
-       trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
 
-       generateSampleTable: generateSampleTable,
 
-       concatenateFrameData: concatenateFrameData
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     var ONE_SECOND_IN_TS$3 = clock$2.ONE_SECOND_IN_TS;
 
-     /**
 
-      * Store information about the start and end of the track and the
 
-      * duration for each frame/sample we process in order to calculate
 
-      * the baseMediaDecodeTime
 
-      */
 
-     var collectDtsInfo = function (track, data) {
 
-       if (typeof data.pts === 'number') {
 
-         if (track.timelineStartInfo.pts === undefined) {
 
-           track.timelineStartInfo.pts = data.pts;
 
-         }
 
-         if (track.minSegmentPts === undefined) {
 
-           track.minSegmentPts = data.pts;
 
-         } else {
 
-           track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
 
-         }
 
-         if (track.maxSegmentPts === undefined) {
 
-           track.maxSegmentPts = data.pts;
 
-         } else {
 
-           track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
 
-         }
 
-       }
 
-       if (typeof data.dts === 'number') {
 
-         if (track.timelineStartInfo.dts === undefined) {
 
-           track.timelineStartInfo.dts = data.dts;
 
-         }
 
-         if (track.minSegmentDts === undefined) {
 
-           track.minSegmentDts = data.dts;
 
-         } else {
 
-           track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
 
-         }
 
-         if (track.maxSegmentDts === undefined) {
 
-           track.maxSegmentDts = data.dts;
 
-         } else {
 
-           track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
 
-         }
 
-       }
 
-     };
 
-     /**
 
-      * Clear values used to calculate the baseMediaDecodeTime between
 
-      * tracks
 
-      */
 
-     var clearDtsInfo = function (track) {
 
-       delete track.minSegmentDts;
 
-       delete track.maxSegmentDts;
 
-       delete track.minSegmentPts;
 
-       delete track.maxSegmentPts;
 
-     };
 
-     /**
 
-      * Calculate the track's baseMediaDecodeTime based on the earliest
 
-      * DTS the transmuxer has ever seen and the minimum DTS for the
 
-      * current track
 
-      * @param track {object} track metadata configuration
 
-      * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
 
-      *        in the source; false to adjust the first segment to start at 0.
 
-      */
 
-     var calculateTrackBaseMediaDecodeTime = function (track, keepOriginalTimestamps) {
 
-       var baseMediaDecodeTime,
 
-         scale,
 
-         minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
 
-       if (!keepOriginalTimestamps) {
 
-         minSegmentDts -= track.timelineStartInfo.dts;
 
-       } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
 
-       // we want the start of the first segment to be placed
 
-       baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
 
-       baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
 
-       baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
 
-       if (track.type === 'audio') {
 
-         // Audio has a different clock equal to the sampling_rate so we need to
 
-         // scale the PTS values into the clock rate of the track
 
-         scale = track.samplerate / ONE_SECOND_IN_TS$3;
 
-         baseMediaDecodeTime *= scale;
 
-         baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
 
-       }
 
-       return baseMediaDecodeTime;
 
-     };
 
-     var trackDecodeInfo$1 = {
 
-       clearDtsInfo: clearDtsInfo,
 
-       calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
 
-       collectDtsInfo: collectDtsInfo
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Reads in-band caption information from a video elementary
 
-      * stream. Captions must follow the CEA-708 standard for injection
 
-      * into an MPEG-2 transport streams.
 
-      * @see https://en.wikipedia.org/wiki/CEA-708
 
-      * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
 
-      */
 
-     // payload type field to indicate how they are to be
 
-     // interpreted. CEAS-708 caption content is always transmitted with
 
-     // payload type 0x04.
 
-     var USER_DATA_REGISTERED_ITU_T_T35 = 4,
 
-       RBSP_TRAILING_BITS = 128;
 
-     /**
 
-       * Parse a supplemental enhancement information (SEI) NAL unit.
 
-       * Stops parsing once a message of type ITU T T35 has been found.
 
-       *
 
-       * @param bytes {Uint8Array} the bytes of a SEI NAL unit
 
-       * @return {object} the parsed SEI payload
 
-       * @see Rec. ITU-T H.264, 7.3.2.3.1
 
-       */
 
-     var parseSei = function (bytes) {
 
-       var i = 0,
 
-         result = {
 
-           payloadType: -1,
 
-           payloadSize: 0
 
-         },
 
-         payloadType = 0,
 
-         payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
 
-       while (i < bytes.byteLength) {
 
-         // stop once we have hit the end of the sei_rbsp
 
-         if (bytes[i] === RBSP_TRAILING_BITS) {
 
-           break;
 
-         } // Parse payload type
 
-         while (bytes[i] === 0xFF) {
 
-           payloadType += 255;
 
-           i++;
 
-         }
 
-         payloadType += bytes[i++]; // Parse payload size
 
-         while (bytes[i] === 0xFF) {
 
-           payloadSize += 255;
 
-           i++;
 
-         }
 
-         payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
 
-         // there can only ever be one caption message in a frame's sei
 
-         if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
 
-           var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);
 
-           if (userIdentifier === 'GA94') {
 
-             result.payloadType = payloadType;
 
-             result.payloadSize = payloadSize;
 
-             result.payload = bytes.subarray(i, i + payloadSize);
 
-             break;
 
-           } else {
 
-             result.payload = void 0;
 
-           }
 
-         } // skip the payload and parse the next message
 
-         i += payloadSize;
 
-         payloadType = 0;
 
-         payloadSize = 0;
 
-       }
 
-       return result;
 
-     }; // see ANSI/SCTE 128-1 (2013), section 8.1
 
-     var parseUserData = function (sei) {
 
-       // itu_t_t35_contry_code must be 181 (United States) for
 
-       // captions
 
-       if (sei.payload[0] !== 181) {
 
-         return null;
 
-       } // itu_t_t35_provider_code should be 49 (ATSC) for captions
 
-       if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
 
-         return null;
 
-       } // the user_identifier should be "GA94" to indicate ATSC1 data
 
-       if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
 
-         return null;
 
-       } // finally, user_data_type_code should be 0x03 for caption data
 
-       if (sei.payload[7] !== 0x03) {
 
-         return null;
 
-       } // return the user_data_type_structure and strip the trailing
 
-       // marker bits
 
-       return sei.payload.subarray(8, sei.payload.length - 1);
 
-     }; // see CEA-708-D, section 4.4
 
-     var parseCaptionPackets = function (pts, userData) {
 
-       var results = [],
 
-         i,
 
-         count,
 
-         offset,
 
-         data; // if this is just filler, return immediately
 
-       if (!(userData[0] & 0x40)) {
 
-         return results;
 
-       } // parse out the cc_data_1 and cc_data_2 fields
 
-       count = userData[0] & 0x1f;
 
-       for (i = 0; i < count; i++) {
 
-         offset = i * 3;
 
-         data = {
 
-           type: userData[offset + 2] & 0x03,
 
-           pts: pts
 
-         }; // capture cc data when cc_valid is 1
 
-         if (userData[offset + 2] & 0x04) {
 
-           data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
 
-           results.push(data);
 
-         }
 
-       }
 
-       return results;
 
-     };
 
-     var discardEmulationPreventionBytes$1 = function (data) {
 
-       var length = data.byteLength,
 
-         emulationPreventionBytesPositions = [],
 
-         i = 1,
 
-         newLength,
 
-         newData; // Find all `Emulation Prevention Bytes`
 
-       while (i < length - 2) {
 
-         if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
 
-           emulationPreventionBytesPositions.push(i + 2);
 
-           i += 2;
 
-         } else {
 
-           i++;
 
-         }
 
-       } // If no Emulation Prevention Bytes were found just return the original
 
-       // array
 
-       if (emulationPreventionBytesPositions.length === 0) {
 
-         return data;
 
-       } // Create a new array to hold the NAL unit data
 
-       newLength = length - emulationPreventionBytesPositions.length;
 
-       newData = new Uint8Array(newLength);
 
-       var sourceIndex = 0;
 
-       for (i = 0; i < newLength; sourceIndex++, i++) {
 
-         if (sourceIndex === emulationPreventionBytesPositions[0]) {
 
-           // Skip this byte
 
-           sourceIndex++; // Remove this position index
 
-           emulationPreventionBytesPositions.shift();
 
-         }
 
-         newData[i] = data[sourceIndex];
 
-       }
 
-       return newData;
 
-     }; // exports
 
-     var captionPacketParser = {
 
-       parseSei: parseSei,
 
-       parseUserData: parseUserData,
 
-       parseCaptionPackets: parseCaptionPackets,
 
-       discardEmulationPreventionBytes: discardEmulationPreventionBytes$1,
 
-       USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Reads in-band caption information from a video elementary
 
-      * stream. Captions must follow the CEA-708 standard for injection
 
-      * into an MPEG-2 transport streams.
 
-      * @see https://en.wikipedia.org/wiki/CEA-708
 
-      * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
 
-      */
 
-     // Link To Transport
 
-     // -----------------
 
-     var Stream$7 = stream;
 
-     var cea708Parser = captionPacketParser;
 
-     var CaptionStream$2 = function (options) {
 
-       options = options || {};
 
-       CaptionStream$2.prototype.init.call(this); // parse708captions flag, default to true
 
-       this.parse708captions_ = typeof options.parse708captions === 'boolean' ? options.parse708captions : true;
 
-       this.captionPackets_ = [];
 
-       this.ccStreams_ = [new Cea608Stream(0, 0),
 
-       // eslint-disable-line no-use-before-define
 
-       new Cea608Stream(0, 1),
 
-       // eslint-disable-line no-use-before-define
 
-       new Cea608Stream(1, 0),
 
-       // eslint-disable-line no-use-before-define
 
-       new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
 
-       ];
 
-       if (this.parse708captions_) {
 
-         this.cc708Stream_ = new Cea708Stream({
 
-           captionServices: options.captionServices
 
-         }); // eslint-disable-line no-use-before-define
 
-       }
 
-       this.reset(); // forward data and done events from CCs to this CaptionStream
 
-       this.ccStreams_.forEach(function (cc) {
 
-         cc.on('data', this.trigger.bind(this, 'data'));
 
-         cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
 
-         cc.on('done', this.trigger.bind(this, 'done'));
 
-       }, this);
 
-       if (this.parse708captions_) {
 
-         this.cc708Stream_.on('data', this.trigger.bind(this, 'data'));
 
-         this.cc708Stream_.on('partialdone', this.trigger.bind(this, 'partialdone'));
 
-         this.cc708Stream_.on('done', this.trigger.bind(this, 'done'));
 
-       }
 
-     };
 
-     CaptionStream$2.prototype = new Stream$7();
 
-     CaptionStream$2.prototype.push = function (event) {
 
-       var sei, userData, newCaptionPackets; // only examine SEI NALs
 
-       if (event.nalUnitType !== 'sei_rbsp') {
 
-         return;
 
-       } // parse the sei
 
-       sei = cea708Parser.parseSei(event.escapedRBSP); // no payload data, skip
 
-       if (!sei.payload) {
 
-         return;
 
-       } // ignore everything but user_data_registered_itu_t_t35
 
-       if (sei.payloadType !== cea708Parser.USER_DATA_REGISTERED_ITU_T_T35) {
 
-         return;
 
-       } // parse out the user data payload
 
-       userData = cea708Parser.parseUserData(sei); // ignore unrecognized userData
 
-       if (!userData) {
 
-         return;
 
-       } // Sometimes, the same segment # will be downloaded twice. To stop the
 
-       // caption data from being processed twice, we track the latest dts we've
 
-       // received and ignore everything with a dts before that. However, since
 
-       // data for a specific dts can be split across packets on either side of
 
-       // a segment boundary, we need to make sure we *don't* ignore the packets
 
-       // from the *next* segment that have dts === this.latestDts_. By constantly
 
-       // tracking the number of packets received with dts === this.latestDts_, we
 
-       // know how many should be ignored once we start receiving duplicates.
 
-       if (event.dts < this.latestDts_) {
 
-         // We've started getting older data, so set the flag.
 
-         this.ignoreNextEqualDts_ = true;
 
-         return;
 
-       } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
 
-         this.numSameDts_--;
 
-         if (!this.numSameDts_) {
 
-           // We've received the last duplicate packet, time to start processing again
 
-           this.ignoreNextEqualDts_ = false;
 
-         }
 
-         return;
 
-       } // parse out CC data packets and save them for later
 
-       newCaptionPackets = cea708Parser.parseCaptionPackets(event.pts, userData);
 
-       this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
 
-       if (this.latestDts_ !== event.dts) {
 
-         this.numSameDts_ = 0;
 
-       }
 
-       this.numSameDts_++;
 
-       this.latestDts_ = event.dts;
 
-     };
 
-     CaptionStream$2.prototype.flushCCStreams = function (flushType) {
 
-       this.ccStreams_.forEach(function (cc) {
 
-         return flushType === 'flush' ? cc.flush() : cc.partialFlush();
 
-       }, this);
 
-     };
 
-     CaptionStream$2.prototype.flushStream = function (flushType) {
 
-       // make sure we actually parsed captions before proceeding
 
-       if (!this.captionPackets_.length) {
 
-         this.flushCCStreams(flushType);
 
-         return;
 
-       } // In Chrome, the Array#sort function is not stable so add a
 
-       // presortIndex that we can use to ensure we get a stable-sort
 
-       this.captionPackets_.forEach(function (elem, idx) {
 
-         elem.presortIndex = idx;
 
-       }); // sort caption byte-pairs based on their PTS values
 
-       this.captionPackets_.sort(function (a, b) {
 
-         if (a.pts === b.pts) {
 
-           return a.presortIndex - b.presortIndex;
 
-         }
 
-         return a.pts - b.pts;
 
-       });
 
-       this.captionPackets_.forEach(function (packet) {
 
-         if (packet.type < 2) {
 
-           // Dispatch packet to the right Cea608Stream
 
-           this.dispatchCea608Packet(packet);
 
-         } else {
 
-           // Dispatch packet to the Cea708Stream
 
-           this.dispatchCea708Packet(packet);
 
-         }
 
-       }, this);
 
-       this.captionPackets_.length = 0;
 
-       this.flushCCStreams(flushType);
 
-     };
 
-     CaptionStream$2.prototype.flush = function () {
 
-       return this.flushStream('flush');
 
-     }; // Only called if handling partial data
 
-     CaptionStream$2.prototype.partialFlush = function () {
 
-       return this.flushStream('partialFlush');
 
-     };
 
-     CaptionStream$2.prototype.reset = function () {
 
-       this.latestDts_ = null;
 
-       this.ignoreNextEqualDts_ = false;
 
-       this.numSameDts_ = 0;
 
-       this.activeCea608Channel_ = [null, null];
 
-       this.ccStreams_.forEach(function (ccStream) {
 
-         ccStream.reset();
 
-       });
 
-     }; // From the CEA-608 spec:
 
-     /*
 
-      * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
 
-      * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
 
-      * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
 
-      * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
 
-      * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
 
-      * to switch to captioning or Text.
 
-     */
 
-     // With that in mind, we ignore any data between an XDS control code and a
 
-     // subsequent closed-captioning control code.
 
-     CaptionStream$2.prototype.dispatchCea608Packet = function (packet) {
 
-       // NOTE: packet.type is the CEA608 field
 
-       if (this.setsTextOrXDSActive(packet)) {
 
-         this.activeCea608Channel_[packet.type] = null;
 
-       } else if (this.setsChannel1Active(packet)) {
 
-         this.activeCea608Channel_[packet.type] = 0;
 
-       } else if (this.setsChannel2Active(packet)) {
 
-         this.activeCea608Channel_[packet.type] = 1;
 
-       }
 
-       if (this.activeCea608Channel_[packet.type] === null) {
 
-         // If we haven't received anything to set the active channel, or the
 
-         // packets are Text/XDS data, discard the data; we don't want jumbled
 
-         // captions
 
-         return;
 
-       }
 
-       this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
 
-     };
 
-     CaptionStream$2.prototype.setsChannel1Active = function (packet) {
 
-       return (packet.ccData & 0x7800) === 0x1000;
 
-     };
 
-     CaptionStream$2.prototype.setsChannel2Active = function (packet) {
 
-       return (packet.ccData & 0x7800) === 0x1800;
 
-     };
 
-     CaptionStream$2.prototype.setsTextOrXDSActive = function (packet) {
 
-       return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
 
-     };
 
-     CaptionStream$2.prototype.dispatchCea708Packet = function (packet) {
 
-       if (this.parse708captions_) {
 
-         this.cc708Stream_.push(packet);
 
-       }
 
-     }; // ----------------------
 
-     // Session to Application
 
-     // ----------------------
 
-     // This hash maps special and extended character codes to their
 
-     // proper Unicode equivalent. The first one-byte key is just a
 
-     // non-standard character code. The two-byte keys that follow are
 
-     // the extended CEA708 character codes, along with the preceding
 
-     // 0x10 extended character byte to distinguish these codes from
 
-     // non-extended character codes. Every CEA708 character code that
 
-     // is not in this object maps directly to a standard unicode
 
-     // character code.
 
-     // The transparent space and non-breaking transparent space are
 
-     // technically not fully supported since there is no code to
 
-     // make them transparent, so they have normal non-transparent
 
-     // stand-ins.
 
-     // The special closed caption (CC) character isn't a standard
 
-     // unicode character, so a fairly similar unicode character was
 
-     // chosen in it's place.
 
-     var CHARACTER_TRANSLATION_708 = {
 
-       0x7f: 0x266a,
 
-       // ♪
 
-       0x1020: 0x20,
 
-       // Transparent Space
 
-       0x1021: 0xa0,
 
-       // Nob-breaking Transparent Space
 
-       0x1025: 0x2026,
 
-       // …
 
-       0x102a: 0x0160,
 
-       // Š
 
-       0x102c: 0x0152,
 
-       // Œ
 
-       0x1030: 0x2588,
 
-       // █
 
-       0x1031: 0x2018,
 
-       // ‘
 
-       0x1032: 0x2019,
 
-       // ’
 
-       0x1033: 0x201c,
 
-       // “
 
-       0x1034: 0x201d,
 
-       // ”
 
-       0x1035: 0x2022,
 
-       // •
 
-       0x1039: 0x2122,
 
-       // ™
 
-       0x103a: 0x0161,
 
-       // š
 
-       0x103c: 0x0153,
 
-       // œ
 
-       0x103d: 0x2120,
 
-       // ℠
 
-       0x103f: 0x0178,
 
-       // Ÿ
 
-       0x1076: 0x215b,
 
-       // ⅛
 
-       0x1077: 0x215c,
 
-       // ⅜
 
-       0x1078: 0x215d,
 
-       // ⅝
 
-       0x1079: 0x215e,
 
-       // ⅞
 
-       0x107a: 0x23d0,
 
-       // ⏐
 
-       0x107b: 0x23a4,
 
-       // ⎤
 
-       0x107c: 0x23a3,
 
-       // ⎣
 
-       0x107d: 0x23af,
 
-       // ⎯
 
-       0x107e: 0x23a6,
 
-       // ⎦
 
-       0x107f: 0x23a1,
 
-       // ⎡
 
-       0x10a0: 0x3138 // ㄸ (CC char)
 
-     };
 
-     var get708CharFromCode = function (code) {
 
-       var newCode = CHARACTER_TRANSLATION_708[code] || code;
 
-       if (code & 0x1000 && code === newCode) {
 
-         // Invalid extended code
 
-         return '';
 
-       }
 
-       return String.fromCharCode(newCode);
 
-     };
 
-     var within708TextBlock = function (b) {
 
-       return 0x20 <= b && b <= 0x7f || 0xa0 <= b && b <= 0xff;
 
-     };
 
-     var Cea708Window = function (windowNum) {
 
-       this.windowNum = windowNum;
 
-       this.reset();
 
-     };
 
-     Cea708Window.prototype.reset = function () {
 
-       this.clearText();
 
-       this.pendingNewLine = false;
 
-       this.winAttr = {};
 
-       this.penAttr = {};
 
-       this.penLoc = {};
 
-       this.penColor = {}; // These default values are arbitrary,
 
-       // defineWindow will usually override them
 
-       this.visible = 0;
 
-       this.rowLock = 0;
 
-       this.columnLock = 0;
 
-       this.priority = 0;
 
-       this.relativePositioning = 0;
 
-       this.anchorVertical = 0;
 
-       this.anchorHorizontal = 0;
 
-       this.anchorPoint = 0;
 
-       this.rowCount = 1;
 
-       this.virtualRowCount = this.rowCount + 1;
 
-       this.columnCount = 41;
 
-       this.windowStyle = 0;
 
-       this.penStyle = 0;
 
-     };
 
-     Cea708Window.prototype.getText = function () {
 
-       return this.rows.join('\n');
 
-     };
 
-     Cea708Window.prototype.clearText = function () {
 
-       this.rows = [''];
 
-       this.rowIdx = 0;
 
-     };
 
-     Cea708Window.prototype.newLine = function (pts) {
 
-       if (this.rows.length >= this.virtualRowCount && typeof this.beforeRowOverflow === 'function') {
 
-         this.beforeRowOverflow(pts);
 
-       }
 
-       if (this.rows.length > 0) {
 
-         this.rows.push('');
 
-         this.rowIdx++;
 
-       } // Show all virtual rows since there's no visible scrolling
 
-       while (this.rows.length > this.virtualRowCount) {
 
-         this.rows.shift();
 
-         this.rowIdx--;
 
-       }
 
-     };
 
-     Cea708Window.prototype.isEmpty = function () {
 
-       if (this.rows.length === 0) {
 
-         return true;
 
-       } else if (this.rows.length === 1) {
 
-         return this.rows[0] === '';
 
-       }
 
-       return false;
 
-     };
 
-     Cea708Window.prototype.addText = function (text) {
 
-       this.rows[this.rowIdx] += text;
 
-     };
 
-     Cea708Window.prototype.backspace = function () {
 
-       if (!this.isEmpty()) {
 
-         var row = this.rows[this.rowIdx];
 
-         this.rows[this.rowIdx] = row.substr(0, row.length - 1);
 
-       }
 
-     };
 
-     var Cea708Service = function (serviceNum, encoding, stream) {
 
-       this.serviceNum = serviceNum;
 
-       this.text = '';
 
-       this.currentWindow = new Cea708Window(-1);
 
-       this.windows = [];
 
-       this.stream = stream; // Try to setup a TextDecoder if an `encoding` value was provided
 
-       if (typeof encoding === 'string') {
 
-         this.createTextDecoder(encoding);
 
-       }
 
-     };
 
-     /**
 
-      * Initialize service windows
 
-      * Must be run before service use
 
-      *
 
-      * @param  {Integer}  pts               PTS value
 
-      * @param  {Function} beforeRowOverflow Function to execute before row overflow of a window
 
-      */
 
-     Cea708Service.prototype.init = function (pts, beforeRowOverflow) {
 
-       this.startPts = pts;
 
-       for (var win = 0; win < 8; win++) {
 
-         this.windows[win] = new Cea708Window(win);
 
-         if (typeof beforeRowOverflow === 'function') {
 
-           this.windows[win].beforeRowOverflow = beforeRowOverflow;
 
-         }
 
-       }
 
-     };
 
-     /**
 
-      * Set current window of service to be affected by commands
 
-      *
 
-      * @param  {Integer} windowNum Window number
 
-      */
 
-     Cea708Service.prototype.setCurrentWindow = function (windowNum) {
 
-       this.currentWindow = this.windows[windowNum];
 
-     };
 
-     /**
 
-      * Try to create a TextDecoder if it is natively supported
 
-      */
 
-     Cea708Service.prototype.createTextDecoder = function (encoding) {
 
-       if (typeof TextDecoder === 'undefined') {
 
-         this.stream.trigger('log', {
 
-           level: 'warn',
 
-           message: 'The `encoding` option is unsupported without TextDecoder support'
 
-         });
 
-       } else {
 
-         try {
 
-           this.textDecoder_ = new TextDecoder(encoding);
 
-         } catch (error) {
 
-           this.stream.trigger('log', {
 
-             level: 'warn',
 
-             message: 'TextDecoder could not be created with ' + encoding + ' encoding. ' + error
 
-           });
 
-         }
 
-       }
 
-     };
 
-     var Cea708Stream = function (options) {
 
-       options = options || {};
 
-       Cea708Stream.prototype.init.call(this);
 
-       var self = this;
 
-       var captionServices = options.captionServices || {};
 
-       var captionServiceEncodings = {};
 
-       var serviceProps; // Get service encodings from captionServices option block
 
-       Object.keys(captionServices).forEach(serviceName => {
 
-         serviceProps = captionServices[serviceName];
 
-         if (/^SERVICE/.test(serviceName)) {
 
-           captionServiceEncodings[serviceName] = serviceProps.encoding;
 
-         }
 
-       });
 
-       this.serviceEncodings = captionServiceEncodings;
 
-       this.current708Packet = null;
 
-       this.services = {};
 
-       this.push = function (packet) {
 
-         if (packet.type === 3) {
 
-           // 708 packet start
 
-           self.new708Packet();
 
-           self.add708Bytes(packet);
 
-         } else {
 
-           if (self.current708Packet === null) {
 
-             // This should only happen at the start of a file if there's no packet start.
 
-             self.new708Packet();
 
-           }
 
-           self.add708Bytes(packet);
 
-         }
 
-       };
 
-     };
 
-     Cea708Stream.prototype = new Stream$7();
 
-     /**
 
-      * Push current 708 packet, create new 708 packet.
 
-      */
 
-     Cea708Stream.prototype.new708Packet = function () {
 
-       if (this.current708Packet !== null) {
 
-         this.push708Packet();
 
-       }
 
-       this.current708Packet = {
 
-         data: [],
 
-         ptsVals: []
 
-       };
 
-     };
 
-     /**
 
-      * Add pts and both bytes from packet into current 708 packet.
 
-      */
 
-     Cea708Stream.prototype.add708Bytes = function (packet) {
 
-       var data = packet.ccData;
 
-       var byte0 = data >>> 8;
 
-       var byte1 = data & 0xff; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec
 
-       // that service blocks will always line up with byte pairs.
 
-       this.current708Packet.ptsVals.push(packet.pts);
 
-       this.current708Packet.data.push(byte0);
 
-       this.current708Packet.data.push(byte1);
 
-     };
 
-     /**
 
-      * Parse completed 708 packet into service blocks and push each service block.
 
-      */
 
-     Cea708Stream.prototype.push708Packet = function () {
 
-       var packet708 = this.current708Packet;
 
-       var packetData = packet708.data;
 
-       var serviceNum = null;
 
-       var blockSize = null;
 
-       var i = 0;
 
-       var b = packetData[i++];
 
-       packet708.seq = b >> 6;
 
-       packet708.sizeCode = b & 0x3f; // 0b00111111;
 
-       for (; i < packetData.length; i++) {
 
-         b = packetData[i++];
 
-         serviceNum = b >> 5;
 
-         blockSize = b & 0x1f; // 0b00011111
 
-         if (serviceNum === 7 && blockSize > 0) {
 
-           // Extended service num
 
-           b = packetData[i++];
 
-           serviceNum = b;
 
-         }
 
-         this.pushServiceBlock(serviceNum, i, blockSize);
 
-         if (blockSize > 0) {
 
-           i += blockSize - 1;
 
-         }
 
-       }
 
-     };
 
-     /**
 
-      * Parse service block, execute commands, read text.
 
-      *
 
-      * Note: While many of these commands serve important purposes,
 
-      * many others just parse out the parameters or attributes, but
 
-      * nothing is done with them because this is not a full and complete
 
-      * implementation of the entire 708 spec.
 
-      *
 
-      * @param  {Integer} serviceNum Service number
 
-      * @param  {Integer} start      Start index of the 708 packet data
 
-      * @param  {Integer} size       Block size
 
-      */
 
-     Cea708Stream.prototype.pushServiceBlock = function (serviceNum, start, size) {
 
-       var b;
 
-       var i = start;
 
-       var packetData = this.current708Packet.data;
 
-       var service = this.services[serviceNum];
 
-       if (!service) {
 
-         service = this.initService(serviceNum, i);
 
-       }
 
-       for (; i < start + size && i < packetData.length; i++) {
 
-         b = packetData[i];
 
-         if (within708TextBlock(b)) {
 
-           i = this.handleText(i, service);
 
-         } else if (b === 0x18) {
 
-           i = this.multiByteCharacter(i, service);
 
-         } else if (b === 0x10) {
 
-           i = this.extendedCommands(i, service);
 
-         } else if (0x80 <= b && b <= 0x87) {
 
-           i = this.setCurrentWindow(i, service);
 
-         } else if (0x98 <= b && b <= 0x9f) {
 
-           i = this.defineWindow(i, service);
 
-         } else if (b === 0x88) {
 
-           i = this.clearWindows(i, service);
 
-         } else if (b === 0x8c) {
 
-           i = this.deleteWindows(i, service);
 
-         } else if (b === 0x89) {
 
-           i = this.displayWindows(i, service);
 
-         } else if (b === 0x8a) {
 
-           i = this.hideWindows(i, service);
 
-         } else if (b === 0x8b) {
 
-           i = this.toggleWindows(i, service);
 
-         } else if (b === 0x97) {
 
-           i = this.setWindowAttributes(i, service);
 
-         } else if (b === 0x90) {
 
-           i = this.setPenAttributes(i, service);
 
-         } else if (b === 0x91) {
 
-           i = this.setPenColor(i, service);
 
-         } else if (b === 0x92) {
 
-           i = this.setPenLocation(i, service);
 
-         } else if (b === 0x8f) {
 
-           service = this.reset(i, service);
 
-         } else if (b === 0x08) {
 
-           // BS: Backspace
 
-           service.currentWindow.backspace();
 
-         } else if (b === 0x0c) {
 
-           // FF: Form feed
 
-           service.currentWindow.clearText();
 
-         } else if (b === 0x0d) {
 
-           // CR: Carriage return
 
-           service.currentWindow.pendingNewLine = true;
 
-         } else if (b === 0x0e) {
 
-           // HCR: Horizontal carriage return
 
-           service.currentWindow.clearText();
 
-         } else if (b === 0x8d) {
 
-           // DLY: Delay, nothing to do
 
-           i++;
 
-         } else ;
 
-       }
 
-     };
 
-     /**
 
-      * Execute an extended command
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.extendedCommands = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[++i];
 
-       if (within708TextBlock(b)) {
 
-         i = this.handleText(i, service, {
 
-           isExtended: true
 
-         });
 
-       }
 
-       return i;
 
-     };
 
-     /**
 
-      * Get PTS value of a given byte index
 
-      *
 
-      * @param  {Integer} byteIndex  Index of the byte
 
-      * @return {Integer}            PTS
 
-      */
 
-     Cea708Stream.prototype.getPts = function (byteIndex) {
 
-       // There's 1 pts value per 2 bytes
 
-       return this.current708Packet.ptsVals[Math.floor(byteIndex / 2)];
 
-     };
 
-     /**
 
-      * Initializes a service
 
-      *
 
-      * @param  {Integer} serviceNum Service number
 
-      * @return {Service}            Initialized service object
 
-      */
 
-     Cea708Stream.prototype.initService = function (serviceNum, i) {
 
-       var serviceName = 'SERVICE' + serviceNum;
 
-       var self = this;
 
-       var serviceName;
 
-       var encoding;
 
-       if (serviceName in this.serviceEncodings) {
 
-         encoding = this.serviceEncodings[serviceName];
 
-       }
 
-       this.services[serviceNum] = new Cea708Service(serviceNum, encoding, self);
 
-       this.services[serviceNum].init(this.getPts(i), function (pts) {
 
-         self.flushDisplayed(pts, self.services[serviceNum]);
 
-       });
 
-       return this.services[serviceNum];
 
-     };
 
-     /**
 
-      * Execute text writing to current window
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.handleText = function (i, service, options) {
 
-       var isExtended = options && options.isExtended;
 
-       var isMultiByte = options && options.isMultiByte;
 
-       var packetData = this.current708Packet.data;
 
-       var extended = isExtended ? 0x1000 : 0x0000;
 
-       var currentByte = packetData[i];
 
-       var nextByte = packetData[i + 1];
 
-       var win = service.currentWindow;
 
-       var char;
 
-       var charCodeArray; // Use the TextDecoder if one was created for this service
 
-       if (service.textDecoder_ && !isExtended) {
 
-         if (isMultiByte) {
 
-           charCodeArray = [currentByte, nextByte];
 
-           i++;
 
-         } else {
 
-           charCodeArray = [currentByte];
 
-         }
 
-         char = service.textDecoder_.decode(new Uint8Array(charCodeArray));
 
-       } else {
 
-         char = get708CharFromCode(extended | currentByte);
 
-       }
 
-       if (win.pendingNewLine && !win.isEmpty()) {
 
-         win.newLine(this.getPts(i));
 
-       }
 
-       win.pendingNewLine = false;
 
-       win.addText(char);
 
-       return i;
 
-     };
 
-     /**
 
-      * Handle decoding of multibyte character
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.multiByteCharacter = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var firstByte = packetData[i + 1];
 
-       var secondByte = packetData[i + 2];
 
-       if (within708TextBlock(firstByte) && within708TextBlock(secondByte)) {
 
-         i = this.handleText(++i, service, {
 
-           isMultiByte: true
 
-         });
 
-       }
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the CW# command.
 
-      *
 
-      * Set the current window.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.setCurrentWindow = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[i];
 
-       var windowNum = b & 0x07;
 
-       service.setCurrentWindow(windowNum);
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the DF# command.
 
-      *
 
-      * Define a window and set it as the current window.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.defineWindow = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[i];
 
-       var windowNum = b & 0x07;
 
-       service.setCurrentWindow(windowNum);
 
-       var win = service.currentWindow;
 
-       b = packetData[++i];
 
-       win.visible = (b & 0x20) >> 5; // v
 
-       win.rowLock = (b & 0x10) >> 4; // rl
 
-       win.columnLock = (b & 0x08) >> 3; // cl
 
-       win.priority = b & 0x07; // p
 
-       b = packetData[++i];
 
-       win.relativePositioning = (b & 0x80) >> 7; // rp
 
-       win.anchorVertical = b & 0x7f; // av
 
-       b = packetData[++i];
 
-       win.anchorHorizontal = b; // ah
 
-       b = packetData[++i];
 
-       win.anchorPoint = (b & 0xf0) >> 4; // ap
 
-       win.rowCount = b & 0x0f; // rc
 
-       b = packetData[++i];
 
-       win.columnCount = b & 0x3f; // cc
 
-       b = packetData[++i];
 
-       win.windowStyle = (b & 0x38) >> 3; // ws
 
-       win.penStyle = b & 0x07; // ps
 
-       // The spec says there are (rowCount+1) "virtual rows"
 
-       win.virtualRowCount = win.rowCount + 1;
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the SWA command.
 
-      *
 
-      * Set attributes of the current window.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.setWindowAttributes = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[i];
 
-       var winAttr = service.currentWindow.winAttr;
 
-       b = packetData[++i];
 
-       winAttr.fillOpacity = (b & 0xc0) >> 6; // fo
 
-       winAttr.fillRed = (b & 0x30) >> 4; // fr
 
-       winAttr.fillGreen = (b & 0x0c) >> 2; // fg
 
-       winAttr.fillBlue = b & 0x03; // fb
 
-       b = packetData[++i];
 
-       winAttr.borderType = (b & 0xc0) >> 6; // bt
 
-       winAttr.borderRed = (b & 0x30) >> 4; // br
 
-       winAttr.borderGreen = (b & 0x0c) >> 2; // bg
 
-       winAttr.borderBlue = b & 0x03; // bb
 
-       b = packetData[++i];
 
-       winAttr.borderType += (b & 0x80) >> 5; // bt
 
-       winAttr.wordWrap = (b & 0x40) >> 6; // ww
 
-       winAttr.printDirection = (b & 0x30) >> 4; // pd
 
-       winAttr.scrollDirection = (b & 0x0c) >> 2; // sd
 
-       winAttr.justify = b & 0x03; // j
 
-       b = packetData[++i];
 
-       winAttr.effectSpeed = (b & 0xf0) >> 4; // es
 
-       winAttr.effectDirection = (b & 0x0c) >> 2; // ed
 
-       winAttr.displayEffect = b & 0x03; // de
 
-       return i;
 
-     };
 
-     /**
 
-      * Gather text from all displayed windows and push a caption to output.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      */
 
-     Cea708Stream.prototype.flushDisplayed = function (pts, service) {
 
-       var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily
 
-       // display text in the correct order, but sample files so far have not shown any issue.
 
-       for (var winId = 0; winId < 8; winId++) {
 
-         if (service.windows[winId].visible && !service.windows[winId].isEmpty()) {
 
-           displayedText.push(service.windows[winId].getText());
 
-         }
 
-       }
 
-       service.endPts = pts;
 
-       service.text = displayedText.join('\n\n');
 
-       this.pushCaption(service);
 
-       service.startPts = pts;
 
-     };
 
-     /**
 
-      * Push a caption to output if the caption contains text.
 
-      *
 
-      * @param  {Service} service  The service object to be affected
 
-      */
 
-     Cea708Stream.prototype.pushCaption = function (service) {
 
-       if (service.text !== '') {
 
-         this.trigger('data', {
 
-           startPts: service.startPts,
 
-           endPts: service.endPts,
 
-           text: service.text,
 
-           stream: 'cc708_' + service.serviceNum
 
-         });
 
-         service.text = '';
 
-         service.startPts = service.endPts;
 
-       }
 
-     };
 
-     /**
 
-      * Parse and execute the DSW command.
 
-      *
 
-      * Set visible property of windows based on the parsed bitmask.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.displayWindows = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[++i];
 
-       var pts = this.getPts(i);
 
-       this.flushDisplayed(pts, service);
 
-       for (var winId = 0; winId < 8; winId++) {
 
-         if (b & 0x01 << winId) {
 
-           service.windows[winId].visible = 1;
 
-         }
 
-       }
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the HDW command.
 
-      *
 
-      * Set visible property of windows based on the parsed bitmask.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.hideWindows = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[++i];
 
-       var pts = this.getPts(i);
 
-       this.flushDisplayed(pts, service);
 
-       for (var winId = 0; winId < 8; winId++) {
 
-         if (b & 0x01 << winId) {
 
-           service.windows[winId].visible = 0;
 
-         }
 
-       }
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the TGW command.
 
-      *
 
-      * Set visible property of windows based on the parsed bitmask.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.toggleWindows = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[++i];
 
-       var pts = this.getPts(i);
 
-       this.flushDisplayed(pts, service);
 
-       for (var winId = 0; winId < 8; winId++) {
 
-         if (b & 0x01 << winId) {
 
-           service.windows[winId].visible ^= 1;
 
-         }
 
-       }
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the CLW command.
 
-      *
 
-      * Clear text of windows based on the parsed bitmask.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.clearWindows = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[++i];
 
-       var pts = this.getPts(i);
 
-       this.flushDisplayed(pts, service);
 
-       for (var winId = 0; winId < 8; winId++) {
 
-         if (b & 0x01 << winId) {
 
-           service.windows[winId].clearText();
 
-         }
 
-       }
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the DLW command.
 
-      *
 
-      * Re-initialize windows based on the parsed bitmask.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.deleteWindows = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[++i];
 
-       var pts = this.getPts(i);
 
-       this.flushDisplayed(pts, service);
 
-       for (var winId = 0; winId < 8; winId++) {
 
-         if (b & 0x01 << winId) {
 
-           service.windows[winId].reset();
 
-         }
 
-       }
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the SPA command.
 
-      *
 
-      * Set pen attributes of the current window.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.setPenAttributes = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[i];
 
-       var penAttr = service.currentWindow.penAttr;
 
-       b = packetData[++i];
 
-       penAttr.textTag = (b & 0xf0) >> 4; // tt
 
-       penAttr.offset = (b & 0x0c) >> 2; // o
 
-       penAttr.penSize = b & 0x03; // s
 
-       b = packetData[++i];
 
-       penAttr.italics = (b & 0x80) >> 7; // i
 
-       penAttr.underline = (b & 0x40) >> 6; // u
 
-       penAttr.edgeType = (b & 0x38) >> 3; // et
 
-       penAttr.fontStyle = b & 0x07; // fs
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the SPC command.
 
-      *
 
-      * Set pen color of the current window.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.setPenColor = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[i];
 
-       var penColor = service.currentWindow.penColor;
 
-       b = packetData[++i];
 
-       penColor.fgOpacity = (b & 0xc0) >> 6; // fo
 
-       penColor.fgRed = (b & 0x30) >> 4; // fr
 
-       penColor.fgGreen = (b & 0x0c) >> 2; // fg
 
-       penColor.fgBlue = b & 0x03; // fb
 
-       b = packetData[++i];
 
-       penColor.bgOpacity = (b & 0xc0) >> 6; // bo
 
-       penColor.bgRed = (b & 0x30) >> 4; // br
 
-       penColor.bgGreen = (b & 0x0c) >> 2; // bg
 
-       penColor.bgBlue = b & 0x03; // bb
 
-       b = packetData[++i];
 
-       penColor.edgeRed = (b & 0x30) >> 4; // er
 
-       penColor.edgeGreen = (b & 0x0c) >> 2; // eg
 
-       penColor.edgeBlue = b & 0x03; // eb
 
-       return i;
 
-     };
 
-     /**
 
-      * Parse and execute the SPL command.
 
-      *
 
-      * Set pen location of the current window.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Integer}          New index after parsing
 
-      */
 
-     Cea708Stream.prototype.setPenLocation = function (i, service) {
 
-       var packetData = this.current708Packet.data;
 
-       var b = packetData[i];
 
-       var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak
 
-       service.currentWindow.pendingNewLine = true;
 
-       b = packetData[++i];
 
-       penLoc.row = b & 0x0f; // r
 
-       b = packetData[++i];
 
-       penLoc.column = b & 0x3f; // c
 
-       return i;
 
-     };
 
-     /**
 
-      * Execute the RST command.
 
-      *
 
-      * Reset service to a clean slate. Re-initialize.
 
-      *
 
-      * @param  {Integer} i        Current index in the 708 packet
 
-      * @param  {Service} service  The service object to be affected
 
-      * @return {Service}          Re-initialized service
 
-      */
 
-     Cea708Stream.prototype.reset = function (i, service) {
 
-       var pts = this.getPts(i);
 
-       this.flushDisplayed(pts, service);
 
-       return this.initService(service.serviceNum, i);
 
-     }; // This hash maps non-ASCII, special, and extended character codes to their
 
-     // proper Unicode equivalent. The first keys that are only a single byte
 
-     // are the non-standard ASCII characters, which simply map the CEA608 byte
 
-     // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
 
-     // character codes, but have their MSB bitmasked with 0x03 so that a lookup
 
-     // can be performed regardless of the field and data channel on which the
 
-     // character code was received.
 
-     var CHARACTER_TRANSLATION = {
 
-       0x2a: 0xe1,
 
-       // á
 
-       0x5c: 0xe9,
 
-       // é
 
-       0x5e: 0xed,
 
-       // í
 
-       0x5f: 0xf3,
 
-       // ó
 
-       0x60: 0xfa,
 
-       // ú
 
-       0x7b: 0xe7,
 
-       // ç
 
-       0x7c: 0xf7,
 
-       // ÷
 
-       0x7d: 0xd1,
 
-       // Ñ
 
-       0x7e: 0xf1,
 
-       // ñ
 
-       0x7f: 0x2588,
 
-       // █
 
-       0x0130: 0xae,
 
-       // ®
 
-       0x0131: 0xb0,
 
-       // °
 
-       0x0132: 0xbd,
 
-       // ½
 
-       0x0133: 0xbf,
 
-       // ¿
 
-       0x0134: 0x2122,
 
-       // ™
 
-       0x0135: 0xa2,
 
-       // ¢
 
-       0x0136: 0xa3,
 
-       // £
 
-       0x0137: 0x266a,
 
-       // ♪
 
-       0x0138: 0xe0,
 
-       // à
 
-       0x0139: 0xa0,
 
-       //
 
-       0x013a: 0xe8,
 
-       // è
 
-       0x013b: 0xe2,
 
-       // â
 
-       0x013c: 0xea,
 
-       // ê
 
-       0x013d: 0xee,
 
-       // î
 
-       0x013e: 0xf4,
 
-       // ô
 
-       0x013f: 0xfb,
 
-       // û
 
-       0x0220: 0xc1,
 
-       // Á
 
-       0x0221: 0xc9,
 
-       // É
 
-       0x0222: 0xd3,
 
-       // Ó
 
-       0x0223: 0xda,
 
-       // Ú
 
-       0x0224: 0xdc,
 
-       // Ü
 
-       0x0225: 0xfc,
 
-       // ü
 
-       0x0226: 0x2018,
 
-       // ‘
 
-       0x0227: 0xa1,
 
-       // ¡
 
-       0x0228: 0x2a,
 
-       // *
 
-       0x0229: 0x27,
 
-       // '
 
-       0x022a: 0x2014,
 
-       // —
 
-       0x022b: 0xa9,
 
-       // ©
 
-       0x022c: 0x2120,
 
-       // ℠
 
-       0x022d: 0x2022,
 
-       // •
 
-       0x022e: 0x201c,
 
-       // “
 
-       0x022f: 0x201d,
 
-       // ”
 
-       0x0230: 0xc0,
 
-       // À
 
-       0x0231: 0xc2,
 
-       // Â
 
-       0x0232: 0xc7,
 
-       // Ç
 
-       0x0233: 0xc8,
 
-       // È
 
-       0x0234: 0xca,
 
-       // Ê
 
-       0x0235: 0xcb,
 
-       // Ë
 
-       0x0236: 0xeb,
 
-       // ë
 
-       0x0237: 0xce,
 
-       // Î
 
-       0x0238: 0xcf,
 
-       // Ï
 
-       0x0239: 0xef,
 
-       // ï
 
-       0x023a: 0xd4,
 
-       // Ô
 
-       0x023b: 0xd9,
 
-       // Ù
 
-       0x023c: 0xf9,
 
-       // ù
 
-       0x023d: 0xdb,
 
-       // Û
 
-       0x023e: 0xab,
 
-       // «
 
-       0x023f: 0xbb,
 
-       // »
 
-       0x0320: 0xc3,
 
-       // Ã
 
-       0x0321: 0xe3,
 
-       // ã
 
-       0x0322: 0xcd,
 
-       // Í
 
-       0x0323: 0xcc,
 
-       // Ì
 
-       0x0324: 0xec,
 
-       // ì
 
-       0x0325: 0xd2,
 
-       // Ò
 
-       0x0326: 0xf2,
 
-       // ò
 
-       0x0327: 0xd5,
 
-       // Õ
 
-       0x0328: 0xf5,
 
-       // õ
 
-       0x0329: 0x7b,
 
-       // {
 
-       0x032a: 0x7d,
 
-       // }
 
-       0x032b: 0x5c,
 
-       // \
 
-       0x032c: 0x5e,
 
-       // ^
 
-       0x032d: 0x5f,
 
-       // _
 
-       0x032e: 0x7c,
 
-       // |
 
-       0x032f: 0x7e,
 
-       // ~
 
-       0x0330: 0xc4,
 
-       // Ä
 
-       0x0331: 0xe4,
 
-       // ä
 
-       0x0332: 0xd6,
 
-       // Ö
 
-       0x0333: 0xf6,
 
-       // ö
 
-       0x0334: 0xdf,
 
-       // ß
 
-       0x0335: 0xa5,
 
-       // ¥
 
-       0x0336: 0xa4,
 
-       // ¤
 
-       0x0337: 0x2502,
 
-       // │
 
-       0x0338: 0xc5,
 
-       // Å
 
-       0x0339: 0xe5,
 
-       // å
 
-       0x033a: 0xd8,
 
-       // Ø
 
-       0x033b: 0xf8,
 
-       // ø
 
-       0x033c: 0x250c,
 
-       // ┌
 
-       0x033d: 0x2510,
 
-       // ┐
 
-       0x033e: 0x2514,
 
-       // └
 
-       0x033f: 0x2518 // ┘
 
-     };
 
-     var getCharFromCode = function (code) {
 
-       if (code === null) {
 
-         return '';
 
-       }
 
-       code = CHARACTER_TRANSLATION[code] || code;
 
-       return String.fromCharCode(code);
 
-     }; // the index of the last row in a CEA-608 display buffer
 
-     var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
 
-     // getting it through bit logic.
 
-     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
 
-     // cells. The "bottom" row is the last element in the outer array.
 
-     var createDisplayBuffer = function () {
 
-       var result = [],
 
-         i = BOTTOM_ROW + 1;
 
-       while (i--) {
 
-         result.push('');
 
-       }
 
-       return result;
 
-     };
 
-     var Cea608Stream = function (field, dataChannel) {
 
-       Cea608Stream.prototype.init.call(this);
 
-       this.field_ = field || 0;
 
-       this.dataChannel_ = dataChannel || 0;
 
-       this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
 
-       this.setConstants();
 
-       this.reset();
 
-       this.push = function (packet) {
 
-         var data, swap, char0, char1, text; // remove the parity bits
 
-         data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
 
-         if (data === this.lastControlCode_) {
 
-           this.lastControlCode_ = null;
 
-           return;
 
-         } // Store control codes
 
-         if ((data & 0xf000) === 0x1000) {
 
-           this.lastControlCode_ = data;
 
-         } else if (data !== this.PADDING_) {
 
-           this.lastControlCode_ = null;
 
-         }
 
-         char0 = data >>> 8;
 
-         char1 = data & 0xff;
 
-         if (data === this.PADDING_) {
 
-           return;
 
-         } else if (data === this.RESUME_CAPTION_LOADING_) {
 
-           this.mode_ = 'popOn';
 
-         } else if (data === this.END_OF_CAPTION_) {
 
-           // If an EOC is received while in paint-on mode, the displayed caption
 
-           // text should be swapped to non-displayed memory as if it was a pop-on
 
-           // caption. Because of that, we should explicitly switch back to pop-on
 
-           // mode
 
-           this.mode_ = 'popOn';
 
-           this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
 
-           this.flushDisplayed(packet.pts); // flip memory
 
-           swap = this.displayed_;
 
-           this.displayed_ = this.nonDisplayed_;
 
-           this.nonDisplayed_ = swap; // start measuring the time to display the caption
 
-           this.startPts_ = packet.pts;
 
-         } else if (data === this.ROLL_UP_2_ROWS_) {
 
-           this.rollUpRows_ = 2;
 
-           this.setRollUp(packet.pts);
 
-         } else if (data === this.ROLL_UP_3_ROWS_) {
 
-           this.rollUpRows_ = 3;
 
-           this.setRollUp(packet.pts);
 
-         } else if (data === this.ROLL_UP_4_ROWS_) {
 
-           this.rollUpRows_ = 4;
 
-           this.setRollUp(packet.pts);
 
-         } else if (data === this.CARRIAGE_RETURN_) {
 
-           this.clearFormatting(packet.pts);
 
-           this.flushDisplayed(packet.pts);
 
-           this.shiftRowsUp_();
 
-           this.startPts_ = packet.pts;
 
-         } else if (data === this.BACKSPACE_) {
 
-           if (this.mode_ === 'popOn') {
 
-             this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
 
-           } else {
 
-             this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
 
-           }
 
-         } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
 
-           this.flushDisplayed(packet.pts);
 
-           this.displayed_ = createDisplayBuffer();
 
-         } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
 
-           this.nonDisplayed_ = createDisplayBuffer();
 
-         } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
 
-           if (this.mode_ !== 'paintOn') {
 
-             // NOTE: This should be removed when proper caption positioning is
 
-             // implemented
 
-             this.flushDisplayed(packet.pts);
 
-             this.displayed_ = createDisplayBuffer();
 
-           }
 
-           this.mode_ = 'paintOn';
 
-           this.startPts_ = packet.pts; // Append special characters to caption text
 
-         } else if (this.isSpecialCharacter(char0, char1)) {
 
-           // Bitmask char0 so that we can apply character transformations
 
-           // regardless of field and data channel.
 
-           // Then byte-shift to the left and OR with char1 so we can pass the
 
-           // entire character code to `getCharFromCode`.
 
-           char0 = (char0 & 0x03) << 8;
 
-           text = getCharFromCode(char0 | char1);
 
-           this[this.mode_](packet.pts, text);
 
-           this.column_++; // Append extended characters to caption text
 
-         } else if (this.isExtCharacter(char0, char1)) {
 
-           // Extended characters always follow their "non-extended" equivalents.
 
-           // IE if a "è" is desired, you'll always receive "eè"; non-compliant
 
-           // decoders are supposed to drop the "è", while compliant decoders
 
-           // backspace the "e" and insert "è".
 
-           // Delete the previous character
 
-           if (this.mode_ === 'popOn') {
 
-             this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
 
-           } else {
 
-             this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
 
-           } // Bitmask char0 so that we can apply character transformations
 
-           // regardless of field and data channel.
 
-           // Then byte-shift to the left and OR with char1 so we can pass the
 
-           // entire character code to `getCharFromCode`.
 
-           char0 = (char0 & 0x03) << 8;
 
-           text = getCharFromCode(char0 | char1);
 
-           this[this.mode_](packet.pts, text);
 
-           this.column_++; // Process mid-row codes
 
-         } else if (this.isMidRowCode(char0, char1)) {
 
-           // Attributes are not additive, so clear all formatting
 
-           this.clearFormatting(packet.pts); // According to the standard, mid-row codes
 
-           // should be replaced with spaces, so add one now
 
-           this[this.mode_](packet.pts, ' ');
 
-           this.column_++;
 
-           if ((char1 & 0xe) === 0xe) {
 
-             this.addFormatting(packet.pts, ['i']);
 
-           }
 
-           if ((char1 & 0x1) === 0x1) {
 
-             this.addFormatting(packet.pts, ['u']);
 
-           } // Detect offset control codes and adjust cursor
 
-         } else if (this.isOffsetControlCode(char0, char1)) {
 
-           // Cursor position is set by indent PAC (see below) in 4-column
 
-           // increments, with an additional offset code of 1-3 to reach any
 
-           // of the 32 columns specified by CEA-608. So all we need to do
 
-           // here is increment the column cursor by the given offset.
 
-           this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
 
-         } else if (this.isPAC(char0, char1)) {
 
-           // There's no logic for PAC -> row mapping, so we have to just
 
-           // find the row code in an array and use its index :(
 
-           var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
 
-           if (this.mode_ === 'rollUp') {
 
-             // This implies that the base row is incorrectly set.
 
-             // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
 
-             // of roll-up rows set.
 
-             if (row - this.rollUpRows_ + 1 < 0) {
 
-               row = this.rollUpRows_ - 1;
 
-             }
 
-             this.setRollUp(packet.pts, row);
 
-           }
 
-           if (row !== this.row_) {
 
-             // formatting is only persistent for current row
 
-             this.clearFormatting(packet.pts);
 
-             this.row_ = row;
 
-           } // All PACs can apply underline, so detect and apply
 
-           // (All odd-numbered second bytes set underline)
 
-           if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
 
-             this.addFormatting(packet.pts, ['u']);
 
-           }
 
-           if ((data & 0x10) === 0x10) {
 
-             // We've got an indent level code. Each successive even number
 
-             // increments the column cursor by 4, so we can get the desired
 
-             // column position by bit-shifting to the right (to get n/2)
 
-             // and multiplying by 4.
 
-             this.column_ = ((data & 0xe) >> 1) * 4;
 
-           }
 
-           if (this.isColorPAC(char1)) {
 
-             // it's a color code, though we only support white, which
 
-             // can be either normal or italicized. white italics can be
 
-             // either 0x4e or 0x6e depending on the row, so we just
 
-             // bitwise-and with 0xe to see if italics should be turned on
 
-             if ((char1 & 0xe) === 0xe) {
 
-               this.addFormatting(packet.pts, ['i']);
 
-             }
 
-           } // We have a normal character in char0, and possibly one in char1
 
-         } else if (this.isNormalChar(char0)) {
 
-           if (char1 === 0x00) {
 
-             char1 = null;
 
-           }
 
-           text = getCharFromCode(char0);
 
-           text += getCharFromCode(char1);
 
-           this[this.mode_](packet.pts, text);
 
-           this.column_ += text.length;
 
-         } // finish data processing
 
-       };
 
-     };
 
-     Cea608Stream.prototype = new Stream$7(); // Trigger a cue point that captures the current state of the
 
-     // display buffer
 
-     Cea608Stream.prototype.flushDisplayed = function (pts) {
 
-       var content = this.displayed_ // remove spaces from the start and end of the string
 
-       .map(function (row, index) {
 
-         try {
 
-           return row.trim();
 
-         } catch (e) {
 
-           // Ordinarily, this shouldn't happen. However, caption
 
-           // parsing errors should not throw exceptions and
 
-           // break playback.
 
-           this.trigger('log', {
 
-             level: 'warn',
 
-             message: 'Skipping a malformed 608 caption at index ' + index + '.'
 
-           });
 
-           return '';
 
-         }
 
-       }, this) // combine all text rows to display in one cue
 
-       .join('\n') // and remove blank rows from the start and end, but not the middle
 
-       .replace(/^\n+|\n+$/g, '');
 
-       if (content.length) {
 
-         this.trigger('data', {
 
-           startPts: this.startPts_,
 
-           endPts: pts,
 
-           text: content,
 
-           stream: this.name_
 
-         });
 
-       }
 
-     };
 
-     /**
 
-      * Zero out the data, used for startup and on seek
 
-      */
 
-     Cea608Stream.prototype.reset = function () {
 
-       this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
 
-       // actually display captions. If a caption is shifted to a row
 
-       // with a lower index than this, it is cleared from the display
 
-       // buffer
 
-       this.topRow_ = 0;
 
-       this.startPts_ = 0;
 
-       this.displayed_ = createDisplayBuffer();
 
-       this.nonDisplayed_ = createDisplayBuffer();
 
-       this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
 
-       this.column_ = 0;
 
-       this.row_ = BOTTOM_ROW;
 
-       this.rollUpRows_ = 2; // This variable holds currently-applied formatting
 
-       this.formatting_ = [];
 
-     };
 
-     /**
 
-      * Sets up control code and related constants for this instance
 
-      */
 
-     Cea608Stream.prototype.setConstants = function () {
 
-       // The following attributes have these uses:
 
-       // ext_ :    char0 for mid-row codes, and the base for extended
 
-       //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
 
-       //           extended codes)
 
-       // control_: char0 for control codes, except byte-shifted to the
 
-       //           left so that we can do this.control_ | CONTROL_CODE
 
-       // offset_:  char0 for tab offset codes
 
-       //
 
-       // It's also worth noting that control codes, and _only_ control codes,
 
-       // differ between field 1 and field2. Field 2 control codes are always
 
-       // their field 1 value plus 1. That's why there's the "| field" on the
 
-       // control value.
 
-       if (this.dataChannel_ === 0) {
 
-         this.BASE_ = 0x10;
 
-         this.EXT_ = 0x11;
 
-         this.CONTROL_ = (0x14 | this.field_) << 8;
 
-         this.OFFSET_ = 0x17;
 
-       } else if (this.dataChannel_ === 1) {
 
-         this.BASE_ = 0x18;
 
-         this.EXT_ = 0x19;
 
-         this.CONTROL_ = (0x1c | this.field_) << 8;
 
-         this.OFFSET_ = 0x1f;
 
-       } // Constants for the LSByte command codes recognized by Cea608Stream. This
 
-       // list is not exhaustive. For a more comprehensive listing and semantics see
 
-       // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
 
-       // Padding
 
-       this.PADDING_ = 0x0000; // Pop-on Mode
 
-       this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
 
-       this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
 
-       this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
 
-       this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
 
-       this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
 
-       this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
 
-       this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
 
-       this.BACKSPACE_ = this.CONTROL_ | 0x21;
 
-       this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
 
-       this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
 
-     };
 
-     /**
 
-      * Detects if the 2-byte packet data is a special character
 
-      *
 
-      * Special characters have a second byte in the range 0x30 to 0x3f,
 
-      * with the first byte being 0x11 (for data channel 1) or 0x19 (for
 
-      * data channel 2).
 
-      *
 
-      * @param  {Integer} char0 The first byte
 
-      * @param  {Integer} char1 The second byte
 
-      * @return {Boolean}       Whether the 2 bytes are an special character
 
-      */
 
-     Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
 
-       return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
 
-     };
 
-     /**
 
-      * Detects if the 2-byte packet data is an extended character
 
-      *
 
-      * Extended characters have a second byte in the range 0x20 to 0x3f,
 
-      * with the first byte being 0x12 or 0x13 (for data channel 1) or
 
-      * 0x1a or 0x1b (for data channel 2).
 
-      *
 
-      * @param  {Integer} char0 The first byte
 
-      * @param  {Integer} char1 The second byte
 
-      * @return {Boolean}       Whether the 2 bytes are an extended character
 
-      */
 
-     Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
 
-       return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
 
-     };
 
-     /**
 
-      * Detects if the 2-byte packet is a mid-row code
 
-      *
 
-      * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
 
-      * the first byte being 0x11 (for data channel 1) or 0x19 (for data
 
-      * channel 2).
 
-      *
 
-      * @param  {Integer} char0 The first byte
 
-      * @param  {Integer} char1 The second byte
 
-      * @return {Boolean}       Whether the 2 bytes are a mid-row code
 
-      */
 
-     Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
 
-       return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
 
-     };
 
-     /**
 
-      * Detects if the 2-byte packet is an offset control code
 
-      *
 
-      * Offset control codes have a second byte in the range 0x21 to 0x23,
 
-      * with the first byte being 0x17 (for data channel 1) or 0x1f (for
 
-      * data channel 2).
 
-      *
 
-      * @param  {Integer} char0 The first byte
 
-      * @param  {Integer} char1 The second byte
 
-      * @return {Boolean}       Whether the 2 bytes are an offset control code
 
-      */
 
-     Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
 
-       return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
 
-     };
 
-     /**
 
-      * Detects if the 2-byte packet is a Preamble Address Code
 
-      *
 
-      * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
 
-      * or 0x18 to 0x1f (for data channel 2), with the second byte in the
 
-      * range 0x40 to 0x7f.
 
-      *
 
-      * @param  {Integer} char0 The first byte
 
-      * @param  {Integer} char1 The second byte
 
-      * @return {Boolean}       Whether the 2 bytes are a PAC
 
-      */
 
-     Cea608Stream.prototype.isPAC = function (char0, char1) {
 
-       return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
 
-     };
 
-     /**
 
-      * Detects if a packet's second byte is in the range of a PAC color code
 
-      *
 
-      * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
 
-      * 0x60 to 0x6f.
 
-      *
 
-      * @param  {Integer} char1 The second byte
 
-      * @return {Boolean}       Whether the byte is a color PAC
 
-      */
 
-     Cea608Stream.prototype.isColorPAC = function (char1) {
 
-       return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
 
-     };
 
-     /**
 
-      * Detects if a single byte is in the range of a normal character
 
-      *
 
-      * Normal text bytes are in the range 0x20 to 0x7f.
 
-      *
 
-      * @param  {Integer} char  The byte
 
-      * @return {Boolean}       Whether the byte is a normal character
 
-      */
 
-     Cea608Stream.prototype.isNormalChar = function (char) {
 
-       return char >= 0x20 && char <= 0x7f;
 
-     };
 
-     /**
 
-      * Configures roll-up
 
-      *
 
-      * @param  {Integer} pts         Current PTS
 
-      * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
 
-      *                               a new position
 
-      */
 
-     Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
 
-       // Reset the base row to the bottom row when switching modes
 
-       if (this.mode_ !== 'rollUp') {
 
-         this.row_ = BOTTOM_ROW;
 
-         this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
 
-         this.flushDisplayed(pts);
 
-         this.nonDisplayed_ = createDisplayBuffer();
 
-         this.displayed_ = createDisplayBuffer();
 
-       }
 
-       if (newBaseRow !== undefined && newBaseRow !== this.row_) {
 
-         // move currently displayed captions (up or down) to the new base row
 
-         for (var i = 0; i < this.rollUpRows_; i++) {
 
-           this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
 
-           this.displayed_[this.row_ - i] = '';
 
-         }
 
-       }
 
-       if (newBaseRow === undefined) {
 
-         newBaseRow = this.row_;
 
-       }
 
-       this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
 
-     }; // Adds the opening HTML tag for the passed character to the caption text,
 
-     // and keeps track of it for later closing
 
-     Cea608Stream.prototype.addFormatting = function (pts, format) {
 
-       this.formatting_ = this.formatting_.concat(format);
 
-       var text = format.reduce(function (text, format) {
 
-         return text + '<' + format + '>';
 
-       }, '');
 
-       this[this.mode_](pts, text);
 
-     }; // Adds HTML closing tags for current formatting to caption text and
 
-     // clears remembered formatting
 
-     Cea608Stream.prototype.clearFormatting = function (pts) {
 
-       if (!this.formatting_.length) {
 
-         return;
 
-       }
 
-       var text = this.formatting_.reverse().reduce(function (text, format) {
 
-         return text + '</' + format + '>';
 
-       }, '');
 
-       this.formatting_ = [];
 
-       this[this.mode_](pts, text);
 
-     }; // Mode Implementations
 
-     Cea608Stream.prototype.popOn = function (pts, text) {
 
-       var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
 
-       baseRow += text;
 
-       this.nonDisplayed_[this.row_] = baseRow;
 
-     };
 
-     Cea608Stream.prototype.rollUp = function (pts, text) {
 
-       var baseRow = this.displayed_[this.row_];
 
-       baseRow += text;
 
-       this.displayed_[this.row_] = baseRow;
 
-     };
 
-     Cea608Stream.prototype.shiftRowsUp_ = function () {
 
-       var i; // clear out inactive rows
 
-       for (i = 0; i < this.topRow_; i++) {
 
-         this.displayed_[i] = '';
 
-       }
 
-       for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
 
-         this.displayed_[i] = '';
 
-       } // shift displayed rows up
 
-       for (i = this.topRow_; i < this.row_; i++) {
 
-         this.displayed_[i] = this.displayed_[i + 1];
 
-       } // clear out the bottom row
 
-       this.displayed_[this.row_] = '';
 
-     };
 
-     Cea608Stream.prototype.paintOn = function (pts, text) {
 
-       var baseRow = this.displayed_[this.row_];
 
-       baseRow += text;
 
-       this.displayed_[this.row_] = baseRow;
 
-     }; // exports
 
-     var captionStream = {
 
-       CaptionStream: CaptionStream$2,
 
-       Cea608Stream: Cea608Stream,
 
-       Cea708Stream: Cea708Stream
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     var streamTypes = {
 
-       H264_STREAM_TYPE: 0x1B,
 
-       ADTS_STREAM_TYPE: 0x0F,
 
-       METADATA_STREAM_TYPE: 0x15
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Accepts program elementary stream (PES) data events and corrects
 
-      * decode and presentation time stamps to account for a rollover
 
-      * of the 33 bit value.
 
-      */
 
-     var Stream$6 = stream;
 
-     var MAX_TS = 8589934592;
 
-     var RO_THRESH = 4294967296;
 
-     var TYPE_SHARED = 'shared';
 
-     var handleRollover$1 = function (value, reference) {
 
-       var direction = 1;
 
-       if (value > reference) {
 
-         // If the current timestamp value is greater than our reference timestamp and we detect a
 
-         // timestamp rollover, this means the roll over is happening in the opposite direction.
 
-         // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
 
-         // point will be set to a small number, e.g. 1. The user then seeks backwards over the
 
-         // rollover point. In loading this segment, the timestamp values will be very large,
 
-         // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
 
-         // the time stamp to be `value - 2^33`.
 
-         direction = -1;
 
-       } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
 
-       // cause an incorrect adjustment.
 
-       while (Math.abs(reference - value) > RO_THRESH) {
 
-         value += direction * MAX_TS;
 
-       }
 
-       return value;
 
-     };
 
-     var TimestampRolloverStream$1 = function (type) {
 
-       var lastDTS, referenceDTS;
 
-       TimestampRolloverStream$1.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
 
-       // video and audio. We could use `undefined` here, but having a string
 
-       // makes debugging a little clearer.
 
-       this.type_ = type || TYPE_SHARED;
 
-       this.push = function (data) {
 
-         // Any "shared" rollover streams will accept _all_ data. Otherwise,
 
-         // streams will only accept data that matches their type.
 
-         if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
 
-           return;
 
-         }
 
-         if (referenceDTS === undefined) {
 
-           referenceDTS = data.dts;
 
-         }
 
-         data.dts = handleRollover$1(data.dts, referenceDTS);
 
-         data.pts = handleRollover$1(data.pts, referenceDTS);
 
-         lastDTS = data.dts;
 
-         this.trigger('data', data);
 
-       };
 
-       this.flush = function () {
 
-         referenceDTS = lastDTS;
 
-         this.trigger('done');
 
-       };
 
-       this.endTimeline = function () {
 
-         this.flush();
 
-         this.trigger('endedtimeline');
 
-       };
 
-       this.discontinuity = function () {
 
-         referenceDTS = void 0;
 
-         lastDTS = void 0;
 
-       };
 
-       this.reset = function () {
 
-         this.discontinuity();
 
-         this.trigger('reset');
 
-       };
 
-     };
 
-     TimestampRolloverStream$1.prototype = new Stream$6();
 
-     var timestampRolloverStream = {
 
-       TimestampRolloverStream: TimestampRolloverStream$1,
 
-       handleRollover: handleRollover$1
 
-     }; // Once IE11 support is dropped, this function should be removed.
 
-     var typedArrayIndexOf$1 = (typedArray, element, fromIndex) => {
 
-       if (!typedArray) {
 
-         return -1;
 
-       }
 
-       var currentIndex = fromIndex;
 
-       for (; currentIndex < typedArray.length; currentIndex++) {
 
-         if (typedArray[currentIndex] === element) {
 
-           return currentIndex;
 
-         }
 
-       }
 
-       return -1;
 
-     };
 
-     var typedArray = {
 
-       typedArrayIndexOf: typedArrayIndexOf$1
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Tools for parsing ID3 frame data
 
-      * @see http://id3.org/id3v2.3.0
 
-      */
 
-     var typedArrayIndexOf = typedArray.typedArrayIndexOf,
 
-       // Frames that allow different types of text encoding contain a text
 
-       // encoding description byte [ID3v2.4.0 section 4.]
 
-       textEncodingDescriptionByte = {
 
-         Iso88591: 0x00,
 
-         // ISO-8859-1, terminated with \0.
 
-         Utf16: 0x01,
 
-         // UTF-16 encoded Unicode BOM, terminated with \0\0
 
-         Utf16be: 0x02,
 
-         // UTF-16BE encoded Unicode, without BOM, terminated with \0\0
 
-         Utf8: 0x03 // UTF-8 encoded Unicode, terminated with \0
 
-       },
 
-       // return a percent-encoded representation of the specified byte range
 
-       // @see http://en.wikipedia.org/wiki/Percent-encoding 
 
-       percentEncode$1 = function (bytes, start, end) {
 
-         var i,
 
-           result = '';
 
-         for (i = start; i < end; i++) {
 
-           result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
 
-         }
 
-         return result;
 
-       },
 
-       // return the string representation of the specified byte range,
 
-       // interpreted as UTf-8.
 
-       parseUtf8 = function (bytes, start, end) {
 
-         return decodeURIComponent(percentEncode$1(bytes, start, end));
 
-       },
 
-       // return the string representation of the specified byte range,
 
-       // interpreted as ISO-8859-1.
 
-       parseIso88591$1 = function (bytes, start, end) {
 
-         return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
 
-       },
 
-       parseSyncSafeInteger$1 = function (data) {
 
-         return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
 
-       },
 
-       frameParsers = {
 
-         'APIC': function (frame) {
 
-           var i = 1,
 
-             mimeTypeEndIndex,
 
-             descriptionEndIndex,
 
-             LINK_MIME_TYPE = '-->';
 
-           if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
 
-             // ignore frames with unrecognized character encodings
 
-             return;
 
-           } // parsing fields [ID3v2.4.0 section 4.14.]
 
-           mimeTypeEndIndex = typedArrayIndexOf(frame.data, 0, i);
 
-           if (mimeTypeEndIndex < 0) {
 
-             // malformed frame
 
-             return;
 
-           } // parsing Mime type field (terminated with \0)
 
-           frame.mimeType = parseIso88591$1(frame.data, i, mimeTypeEndIndex);
 
-           i = mimeTypeEndIndex + 1; // parsing 1-byte Picture Type field
 
-           frame.pictureType = frame.data[i];
 
-           i++;
 
-           descriptionEndIndex = typedArrayIndexOf(frame.data, 0, i);
 
-           if (descriptionEndIndex < 0) {
 
-             // malformed frame
 
-             return;
 
-           } // parsing Description field (terminated with \0)
 
-           frame.description = parseUtf8(frame.data, i, descriptionEndIndex);
 
-           i = descriptionEndIndex + 1;
 
-           if (frame.mimeType === LINK_MIME_TYPE) {
 
-             // parsing Picture Data field as URL (always represented as ISO-8859-1 [ID3v2.4.0 section 4.])
 
-             frame.url = parseIso88591$1(frame.data, i, frame.data.length);
 
-           } else {
 
-             // parsing Picture Data field as binary data
 
-             frame.pictureData = frame.data.subarray(i, frame.data.length);
 
-           }
 
-         },
 
-         'T*': function (frame) {
 
-           if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
 
-             // ignore frames with unrecognized character encodings
 
-             return;
 
-           } // parse text field, do not include null terminator in the frame value
 
-           // frames that allow different types of encoding contain terminated text [ID3v2.4.0 section 4.]
 
-           frame.value = parseUtf8(frame.data, 1, frame.data.length).replace(/\0*$/, ''); // text information frames supports multiple strings, stored as a terminator separated list [ID3v2.4.0 section 4.2.]
 
-           frame.values = frame.value.split('\0');
 
-         },
 
-         'TXXX': function (frame) {
 
-           var descriptionEndIndex;
 
-           if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
 
-             // ignore frames with unrecognized character encodings
 
-             return;
 
-           }
 
-           descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
 
-           if (descriptionEndIndex === -1) {
 
-             return;
 
-           } // parse the text fields
 
-           frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // do not include the null terminator in the tag value
 
-           // frames that allow different types of encoding contain terminated text
 
-           // [ID3v2.4.0 section 4.]
 
-           frame.value = parseUtf8(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0*$/, '');
 
-           frame.data = frame.value;
 
-         },
 
-         'W*': function (frame) {
 
-           // parse URL field; URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
 
-           // if the value is followed by a string termination all the following information should be ignored [ID3v2.4.0 section 4.3]
 
-           frame.url = parseIso88591$1(frame.data, 0, frame.data.length).replace(/\0.*$/, '');
 
-         },
 
-         'WXXX': function (frame) {
 
-           var descriptionEndIndex;
 
-           if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
 
-             // ignore frames with unrecognized character encodings
 
-             return;
 
-           }
 
-           descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
 
-           if (descriptionEndIndex === -1) {
 
-             return;
 
-           } // parse the description and URL fields
 
-           frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
 
-           // if the value is followed by a string termination all the following information
 
-           // should be ignored [ID3v2.4.0 section 4.3]
 
-           frame.url = parseIso88591$1(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0.*$/, '');
 
-         },
 
-         'PRIV': function (frame) {
 
-           var i;
 
-           for (i = 0; i < frame.data.length; i++) {
 
-             if (frame.data[i] === 0) {
 
-               // parse the description and URL fields
 
-               frame.owner = parseIso88591$1(frame.data, 0, i);
 
-               break;
 
-             }
 
-           }
 
-           frame.privateData = frame.data.subarray(i + 1);
 
-           frame.data = frame.privateData;
 
-         }
 
-       };
 
-     var parseId3Frames$1 = function (data) {
 
-       var frameSize,
 
-         frameHeader,
 
-         frameStart = 10,
 
-         tagSize = 0,
 
-         frames = []; // If we don't have enough data for a header, 10 bytes, 
 
-       // or 'ID3' in the first 3 bytes this is not a valid ID3 tag.
 
-       if (data.length < 10 || data[0] !== 'I'.charCodeAt(0) || data[1] !== 'D'.charCodeAt(0) || data[2] !== '3'.charCodeAt(0)) {
 
-         return;
 
-       } // the frame size is transmitted as a 28-bit integer in the
 
-       // last four bytes of the ID3 header.
 
-       // The most significant bit of each byte is dropped and the
 
-       // results concatenated to recover the actual value.
 
-       tagSize = parseSyncSafeInteger$1(data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
 
-       // convenient for our comparisons to include it
 
-       tagSize += 10; // check bit 6 of byte 5 for the extended header flag.
 
-       var hasExtendedHeader = data[5] & 0x40;
 
-       if (hasExtendedHeader) {
 
-         // advance the frame start past the extended header
 
-         frameStart += 4; // header size field
 
-         frameStart += parseSyncSafeInteger$1(data.subarray(10, 14));
 
-         tagSize -= parseSyncSafeInteger$1(data.subarray(16, 20)); // clip any padding off the end
 
-       } // parse one or more ID3 frames
 
-       // http://id3.org/id3v2.3.0#ID3v2_frame_overview
 
-       do {
 
-         // determine the number of bytes in this frame
 
-         frameSize = parseSyncSafeInteger$1(data.subarray(frameStart + 4, frameStart + 8));
 
-         if (frameSize < 1) {
 
-           break;
 
-         }
 
-         frameHeader = String.fromCharCode(data[frameStart], data[frameStart + 1], data[frameStart + 2], data[frameStart + 3]);
 
-         var frame = {
 
-           id: frameHeader,
 
-           data: data.subarray(frameStart + 10, frameStart + frameSize + 10)
 
-         };
 
-         frame.key = frame.id; // parse frame values
 
-         if (frameParsers[frame.id]) {
 
-           // use frame specific parser
 
-           frameParsers[frame.id](frame);
 
-         } else if (frame.id[0] === 'T') {
 
-           // use text frame generic parser
 
-           frameParsers['T*'](frame);
 
-         } else if (frame.id[0] === 'W') {
 
-           // use URL link frame generic parser
 
-           frameParsers['W*'](frame);
 
-         }
 
-         frames.push(frame);
 
-         frameStart += 10; // advance past the frame header
 
-         frameStart += frameSize; // advance past the frame body
 
-       } while (frameStart < tagSize);
 
-       return frames;
 
-     };
 
-     var parseId3 = {
 
-       parseId3Frames: parseId3Frames$1,
 
-       parseSyncSafeInteger: parseSyncSafeInteger$1,
 
-       frameParsers: frameParsers
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Accepts program elementary stream (PES) data events and parses out
 
-      * ID3 metadata from them, if present.
 
-      * @see http://id3.org/id3v2.3.0
 
-      */
 
-     var Stream$5 = stream,
 
-       StreamTypes$3 = streamTypes,
 
-       id3 = parseId3,
 
-       MetadataStream;
 
-     MetadataStream = function (options) {
 
-       var settings = {
 
-           // the bytes of the program-level descriptor field in MP2T
 
-           // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
 
-           // program element descriptors"
 
-           descriptor: options && options.descriptor
 
-         },
 
-         // the total size in bytes of the ID3 tag being parsed
 
-         tagSize = 0,
 
-         // tag data that is not complete enough to be parsed
 
-         buffer = [],
 
-         // the total number of bytes currently in the buffer
 
-         bufferSize = 0,
 
-         i;
 
-       MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
 
-       // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
 
-       this.dispatchType = StreamTypes$3.METADATA_STREAM_TYPE.toString(16);
 
-       if (settings.descriptor) {
 
-         for (i = 0; i < settings.descriptor.length; i++) {
 
-           this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
 
-         }
 
-       }
 
-       this.push = function (chunk) {
 
-         var tag, frameStart, frameSize, frame, i, frameHeader;
 
-         if (chunk.type !== 'timed-metadata') {
 
-           return;
 
-         } // if data_alignment_indicator is set in the PES header,
 
-         // we must have the start of a new ID3 tag. Assume anything
 
-         // remaining in the buffer was malformed and throw it out
 
-         if (chunk.dataAlignmentIndicator) {
 
-           bufferSize = 0;
 
-           buffer.length = 0;
 
-         } // ignore events that don't look like ID3 data
 
-         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))) {
 
-           this.trigger('log', {
 
-             level: 'warn',
 
-             message: 'Skipping unrecognized metadata packet'
 
-           });
 
-           return;
 
-         } // add this chunk to the data we've collected so far
 
-         buffer.push(chunk);
 
-         bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
 
-         if (buffer.length === 1) {
 
-           // the frame size is transmitted as a 28-bit integer in the
 
-           // last four bytes of the ID3 header.
 
-           // The most significant bit of each byte is dropped and the
 
-           // results concatenated to recover the actual value.
 
-           tagSize = id3.parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
 
-           // convenient for our comparisons to include it
 
-           tagSize += 10;
 
-         } // if the entire frame has not arrived, wait for more data
 
-         if (bufferSize < tagSize) {
 
-           return;
 
-         } // collect the entire frame so it can be parsed
 
-         tag = {
 
-           data: new Uint8Array(tagSize),
 
-           frames: [],
 
-           pts: buffer[0].pts,
 
-           dts: buffer[0].dts
 
-         };
 
-         for (i = 0; i < tagSize;) {
 
-           tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
 
-           i += buffer[0].data.byteLength;
 
-           bufferSize -= buffer[0].data.byteLength;
 
-           buffer.shift();
 
-         } // find the start of the first frame and the end of the tag
 
-         frameStart = 10;
 
-         if (tag.data[5] & 0x40) {
 
-           // advance the frame start past the extended header
 
-           frameStart += 4; // header size field
 
-           frameStart += id3.parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
 
-           tagSize -= id3.parseSyncSafeInteger(tag.data.subarray(16, 20));
 
-         } // parse one or more ID3 frames
 
-         // http://id3.org/id3v2.3.0#ID3v2_frame_overview
 
-         do {
 
-           // determine the number of bytes in this frame
 
-           frameSize = id3.parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
 
-           if (frameSize < 1) {
 
-             this.trigger('log', {
 
-               level: 'warn',
 
-               message: 'Malformed ID3 frame encountered. Skipping remaining metadata parsing.'
 
-             }); // If the frame is malformed, don't parse any further frames but allow previous valid parsed frames
 
-             // to be sent along.
 
-             break;
 
-           }
 
-           frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
 
-           frame = {
 
-             id: frameHeader,
 
-             data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
 
-           };
 
-           frame.key = frame.id; // parse frame values
 
-           if (id3.frameParsers[frame.id]) {
 
-             // use frame specific parser
 
-             id3.frameParsers[frame.id](frame);
 
-           } else if (frame.id[0] === 'T') {
 
-             // use text frame generic parser
 
-             id3.frameParsers['T*'](frame);
 
-           } else if (frame.id[0] === 'W') {
 
-             // use URL link frame generic parser
 
-             id3.frameParsers['W*'](frame);
 
-           } // handle the special PRIV frame used to indicate the start
 
-           // time for raw AAC data
 
-           if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
 
-             var d = frame.data,
 
-               size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
 
-             size *= 4;
 
-             size += d[7] & 0x03;
 
-             frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
 
-             // on the value of this frame
 
-             // we couldn't have known the appropriate pts and dts before
 
-             // parsing this ID3 tag so set those values now
 
-             if (tag.pts === undefined && tag.dts === undefined) {
 
-               tag.pts = frame.timeStamp;
 
-               tag.dts = frame.timeStamp;
 
-             }
 
-             this.trigger('timestamp', frame);
 
-           }
 
-           tag.frames.push(frame);
 
-           frameStart += 10; // advance past the frame header
 
-           frameStart += frameSize; // advance past the frame body
 
-         } while (frameStart < tagSize);
 
-         this.trigger('data', tag);
 
-       };
 
-     };
 
-     MetadataStream.prototype = new Stream$5();
 
-     var metadataStream = MetadataStream;
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * A stream-based mp2t to mp4 converter. This utility can be used to
 
-      * deliver mp4s to a SourceBuffer on platforms that support native
 
-      * Media Source Extensions.
 
-      */
 
-     var Stream$4 = stream,
 
-       CaptionStream$1 = captionStream,
 
-       StreamTypes$2 = streamTypes,
 
-       TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types
 
-     var TransportPacketStream, TransportParseStream, ElementaryStream; // constants
 
-     var MP2T_PACKET_LENGTH$1 = 188,
 
-       // bytes
 
-       SYNC_BYTE$1 = 0x47;
 
-     /**
 
-      * Splits an incoming stream of binary data into MPEG-2 Transport
 
-      * Stream packets.
 
-      */
 
-     TransportPacketStream = function () {
 
-       var buffer = new Uint8Array(MP2T_PACKET_LENGTH$1),
 
-         bytesInBuffer = 0;
 
-       TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
 
-       /**
 
-        * Split a stream of data into M2TS packets
 
-       **/
 
-       this.push = function (bytes) {
 
-         var startIndex = 0,
 
-           endIndex = MP2T_PACKET_LENGTH$1,
 
-           everything; // If there are bytes remaining from the last segment, prepend them to the
 
-         // bytes that were pushed in
 
-         if (bytesInBuffer) {
 
-           everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
 
-           everything.set(buffer.subarray(0, bytesInBuffer));
 
-           everything.set(bytes, bytesInBuffer);
 
-           bytesInBuffer = 0;
 
-         } else {
 
-           everything = bytes;
 
-         } // While we have enough data for a packet
 
-         while (endIndex < everything.byteLength) {
 
-           // Look for a pair of start and end sync bytes in the data..
 
-           if (everything[startIndex] === SYNC_BYTE$1 && everything[endIndex] === SYNC_BYTE$1) {
 
-             // We found a packet so emit it and jump one whole packet forward in
 
-             // the stream
 
-             this.trigger('data', everything.subarray(startIndex, endIndex));
 
-             startIndex += MP2T_PACKET_LENGTH$1;
 
-             endIndex += MP2T_PACKET_LENGTH$1;
 
-             continue;
 
-           } // If we get here, we have somehow become de-synchronized and we need to step
 
-           // forward one byte at a time until we find a pair of sync bytes that denote
 
-           // a packet
 
-           startIndex++;
 
-           endIndex++;
 
-         } // If there was some data left over at the end of the segment that couldn't
 
-         // possibly be a whole packet, keep it because it might be the start of a packet
 
-         // that continues in the next segment
 
-         if (startIndex < everything.byteLength) {
 
-           buffer.set(everything.subarray(startIndex), 0);
 
-           bytesInBuffer = everything.byteLength - startIndex;
 
-         }
 
-       };
 
-       /**
 
-        * Passes identified M2TS packets to the TransportParseStream to be parsed
 
-       **/
 
-       this.flush = function () {
 
-         // If the buffer contains a whole packet when we are being flushed, emit it
 
-         // and empty the buffer. Otherwise hold onto the data because it may be
 
-         // important for decoding the next segment
 
-         if (bytesInBuffer === MP2T_PACKET_LENGTH$1 && buffer[0] === SYNC_BYTE$1) {
 
-           this.trigger('data', buffer);
 
-           bytesInBuffer = 0;
 
-         }
 
-         this.trigger('done');
 
-       };
 
-       this.endTimeline = function () {
 
-         this.flush();
 
-         this.trigger('endedtimeline');
 
-       };
 
-       this.reset = function () {
 
-         bytesInBuffer = 0;
 
-         this.trigger('reset');
 
-       };
 
-     };
 
-     TransportPacketStream.prototype = new Stream$4();
 
-     /**
 
-      * Accepts an MP2T TransportPacketStream and emits data events with parsed
 
-      * forms of the individual transport stream packets.
 
-      */
 
-     TransportParseStream = function () {
 
-       var parsePsi, parsePat, parsePmt, self;
 
-       TransportParseStream.prototype.init.call(this);
 
-       self = this;
 
-       this.packetsWaitingForPmt = [];
 
-       this.programMapTable = undefined;
 
-       parsePsi = function (payload, psi) {
 
-         var offset = 0; // PSI packets may be split into multiple sections and those
 
-         // sections may be split into multiple packets. If a PSI
 
-         // section starts in this packet, the payload_unit_start_indicator
 
-         // will be true and the first byte of the payload will indicate
 
-         // the offset from the current position to the start of the
 
-         // section.
 
-         if (psi.payloadUnitStartIndicator) {
 
-           offset += payload[offset] + 1;
 
-         }
 
-         if (psi.type === 'pat') {
 
-           parsePat(payload.subarray(offset), psi);
 
-         } else {
 
-           parsePmt(payload.subarray(offset), psi);
 
-         }
 
-       };
 
-       parsePat = function (payload, pat) {
 
-         pat.section_number = payload[7]; // eslint-disable-line camelcase
 
-         pat.last_section_number = payload[8]; // eslint-disable-line camelcase
 
-         // skip the PSI header and parse the first PMT entry
 
-         self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
 
-         pat.pmtPid = self.pmtPid;
 
-       };
 
-       /**
 
-        * Parse out the relevant fields of a Program Map Table (PMT).
 
-        * @param payload {Uint8Array} the PMT-specific portion of an MP2T
 
-        * packet. The first byte in this array should be the table_id
 
-        * field.
 
-        * @param pmt {object} the object that should be decorated with
 
-        * fields parsed from the PMT.
 
-        */
 
-       parsePmt = function (payload, pmt) {
 
-         var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
 
-         // take effect. We don't believe this should ever be the case
 
-         // for HLS but we'll ignore "forward" PMT declarations if we see
 
-         // them. Future PMT declarations have the current_next_indicator
 
-         // set to zero.
 
-         if (!(payload[5] & 0x01)) {
 
-           return;
 
-         } // overwrite any existing program map table
 
-         self.programMapTable = {
 
-           video: null,
 
-           audio: null,
 
-           'timed-metadata': {}
 
-         }; // the mapping table ends at the end of the current section
 
-         sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
 
-         tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
 
-         // long the program info descriptors are
 
-         programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
 
-         offset = 12 + programInfoLength;
 
-         while (offset < tableEnd) {
 
-           var streamType = payload[offset];
 
-           var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
 
-           // TODO: should this be done for metadata too? for now maintain behavior of
 
-           //       multiple metadata streams
 
-           if (streamType === StreamTypes$2.H264_STREAM_TYPE && self.programMapTable.video === null) {
 
-             self.programMapTable.video = pid;
 
-           } else if (streamType === StreamTypes$2.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
 
-             self.programMapTable.audio = pid;
 
-           } else if (streamType === StreamTypes$2.METADATA_STREAM_TYPE) {
 
-             // map pid to stream type for metadata streams
 
-             self.programMapTable['timed-metadata'][pid] = streamType;
 
-           } // move to the next table entry
 
-           // skip past the elementary stream descriptors, if present
 
-           offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
 
-         } // record the map on the packet as well
 
-         pmt.programMapTable = self.programMapTable;
 
-       };
 
-       /**
 
-        * Deliver a new MP2T packet to the next stream in the pipeline.
 
-        */
 
-       this.push = function (packet) {
 
-         var result = {},
 
-           offset = 4;
 
-         result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
 
-         result.pid = packet[1] & 0x1f;
 
-         result.pid <<= 8;
 
-         result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
 
-         // fifth byte of the TS packet header. The adaptation field is
 
-         // used to add stuffing to PES packets that don't fill a complete
 
-         // TS packet, and to specify some forms of timing and control data
 
-         // that we do not currently use.
 
-         if ((packet[3] & 0x30) >>> 4 > 0x01) {
 
-           offset += packet[offset] + 1;
 
-         } // parse the rest of the packet based on the type
 
-         if (result.pid === 0) {
 
-           result.type = 'pat';
 
-           parsePsi(packet.subarray(offset), result);
 
-           this.trigger('data', result);
 
-         } else if (result.pid === this.pmtPid) {
 
-           result.type = 'pmt';
 
-           parsePsi(packet.subarray(offset), result);
 
-           this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
 
-           while (this.packetsWaitingForPmt.length) {
 
-             this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
 
-           }
 
-         } else if (this.programMapTable === undefined) {
 
-           // When we have not seen a PMT yet, defer further processing of
 
-           // PES packets until one has been parsed
 
-           this.packetsWaitingForPmt.push([packet, offset, result]);
 
-         } else {
 
-           this.processPes_(packet, offset, result);
 
-         }
 
-       };
 
-       this.processPes_ = function (packet, offset, result) {
 
-         // set the appropriate stream type
 
-         if (result.pid === this.programMapTable.video) {
 
-           result.streamType = StreamTypes$2.H264_STREAM_TYPE;
 
-         } else if (result.pid === this.programMapTable.audio) {
 
-           result.streamType = StreamTypes$2.ADTS_STREAM_TYPE;
 
-         } else {
 
-           // if not video or audio, it is timed-metadata or unknown
 
-           // if unknown, streamType will be undefined
 
-           result.streamType = this.programMapTable['timed-metadata'][result.pid];
 
-         }
 
-         result.type = 'pes';
 
-         result.data = packet.subarray(offset);
 
-         this.trigger('data', result);
 
-       };
 
-     };
 
-     TransportParseStream.prototype = new Stream$4();
 
-     TransportParseStream.STREAM_TYPES = {
 
-       h264: 0x1b,
 
-       adts: 0x0f
 
-     };
 
-     /**
 
-      * Reconsistutes program elementary stream (PES) packets from parsed
 
-      * transport stream packets. That is, if you pipe an
 
-      * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
 
-      * events will be events which capture the bytes for individual PES
 
-      * packets plus relevant metadata that has been extracted from the
 
-      * container.
 
-      */
 
-     ElementaryStream = function () {
 
-       var self = this,
 
-         segmentHadPmt = false,
 
-         // PES packet fragments
 
-         video = {
 
-           data: [],
 
-           size: 0
 
-         },
 
-         audio = {
 
-           data: [],
 
-           size: 0
 
-         },
 
-         timedMetadata = {
 
-           data: [],
 
-           size: 0
 
-         },
 
-         programMapTable,
 
-         parsePes = function (payload, pes) {
 
-           var ptsDtsFlags;
 
-           const startPrefix = payload[0] << 16 | payload[1] << 8 | payload[2]; // default to an empty array
 
-           pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets
 
-           // that are frame data that is continuing from the previous fragment. This
 
-           // is to check that the pes data is the start of a new pes payload
 
-           if (startPrefix !== 1) {
 
-             return;
 
-           } // get the packet length, this will be 0 for video
 
-           pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
 
-           pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
 
-           // and a DTS value. Determine what combination of values is
 
-           // available to work with.
 
-           ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
 
-           // performs all bitwise operations on 32-bit integers but javascript
 
-           // supports a much greater range (52-bits) of integer using standard
 
-           // mathematical operations.
 
-           // We construct a 31-bit value using bitwise operators over the 31
 
-           // most significant bits and then multiply by 4 (equal to a left-shift
 
-           // of 2) before we add the final 2 least significant bits of the
 
-           // timestamp (equal to an OR.)
 
-           if (ptsDtsFlags & 0xC0) {
 
-             // the PTS and DTS are not written out directly. For information
 
-             // on how they are encoded, see
 
-             // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
 
-             pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
 
-             pes.pts *= 4; // Left shift by 2
 
-             pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
 
-             pes.dts = pes.pts;
 
-             if (ptsDtsFlags & 0x40) {
 
-               pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
 
-               pes.dts *= 4; // Left shift by 2
 
-               pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
 
-             }
 
-           } // the data section starts immediately after the PES header.
 
-           // pes_header_data_length specifies the number of header bytes
 
-           // that follow the last byte of the field.
 
-           pes.data = payload.subarray(9 + payload[8]);
 
-         },
 
-         /**
 
-           * Pass completely parsed PES packets to the next stream in the pipeline
 
-          **/
 
-         flushStream = function (stream, type, forceFlush) {
 
-           var packetData = new Uint8Array(stream.size),
 
-             event = {
 
-               type: type
 
-             },
 
-             i = 0,
 
-             offset = 0,
 
-             packetFlushable = false,
 
-             fragment; // do nothing if there is not enough buffered data for a complete
 
-           // PES header
 
-           if (!stream.data.length || stream.size < 9) {
 
-             return;
 
-           }
 
-           event.trackId = stream.data[0].pid; // reassemble the packet
 
-           for (i = 0; i < stream.data.length; i++) {
 
-             fragment = stream.data[i];
 
-             packetData.set(fragment.data, offset);
 
-             offset += fragment.data.byteLength;
 
-           } // parse assembled packet's PES header
 
-           parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
 
-           // check that there is enough stream data to fill the packet
 
-           packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
 
-           if (forceFlush || packetFlushable) {
 
-             stream.size = 0;
 
-             stream.data.length = 0;
 
-           } // only emit packets that are complete. this is to avoid assembling
 
-           // incomplete PES packets due to poor segmentation
 
-           if (packetFlushable) {
 
-             self.trigger('data', event);
 
-           }
 
-         };
 
-       ElementaryStream.prototype.init.call(this);
 
-       /**
 
-        * Identifies M2TS packet types and parses PES packets using metadata
 
-        * parsed from the PMT
 
-        **/
 
-       this.push = function (data) {
 
-         ({
 
-           pat: function () {// we have to wait for the PMT to arrive as well before we
 
-             // have any meaningful metadata
 
-           },
 
-           pes: function () {
 
-             var stream, streamType;
 
-             switch (data.streamType) {
 
-               case StreamTypes$2.H264_STREAM_TYPE:
 
-                 stream = video;
 
-                 streamType = 'video';
 
-                 break;
 
-               case StreamTypes$2.ADTS_STREAM_TYPE:
 
-                 stream = audio;
 
-                 streamType = 'audio';
 
-                 break;
 
-               case StreamTypes$2.METADATA_STREAM_TYPE:
 
-                 stream = timedMetadata;
 
-                 streamType = 'timed-metadata';
 
-                 break;
 
-               default:
 
-                 // ignore unknown stream types
 
-                 return;
 
-             } // if a new packet is starting, we can flush the completed
 
-             // packet
 
-             if (data.payloadUnitStartIndicator) {
 
-               flushStream(stream, streamType, true);
 
-             } // buffer this fragment until we are sure we've received the
 
-             // complete payload
 
-             stream.data.push(data);
 
-             stream.size += data.data.byteLength;
 
-           },
 
-           pmt: function () {
 
-             var event = {
 
-               type: 'metadata',
 
-               tracks: []
 
-             };
 
-             programMapTable = data.programMapTable; // translate audio and video streams to tracks
 
-             if (programMapTable.video !== null) {
 
-               event.tracks.push({
 
-                 timelineStartInfo: {
 
-                   baseMediaDecodeTime: 0
 
-                 },
 
-                 id: +programMapTable.video,
 
-                 codec: 'avc',
 
-                 type: 'video'
 
-               });
 
-             }
 
-             if (programMapTable.audio !== null) {
 
-               event.tracks.push({
 
-                 timelineStartInfo: {
 
-                   baseMediaDecodeTime: 0
 
-                 },
 
-                 id: +programMapTable.audio,
 
-                 codec: 'adts',
 
-                 type: 'audio'
 
-               });
 
-             }
 
-             segmentHadPmt = true;
 
-             self.trigger('data', event);
 
-           }
 
-         })[data.type]();
 
-       };
 
-       this.reset = function () {
 
-         video.size = 0;
 
-         video.data.length = 0;
 
-         audio.size = 0;
 
-         audio.data.length = 0;
 
-         this.trigger('reset');
 
-       };
 
-       /**
 
-        * Flush any remaining input. Video PES packets may be of variable
 
-        * length. Normally, the start of a new video packet can trigger the
 
-        * finalization of the previous packet. That is not possible if no
 
-        * more video is forthcoming, however. In that case, some other
 
-        * mechanism (like the end of the file) has to be employed. When it is
 
-        * clear that no additional data is forthcoming, calling this method
 
-        * will flush the buffered packets.
 
-        */
 
-       this.flushStreams_ = function () {
 
-         // !!THIS ORDER IS IMPORTANT!!
 
-         // video first then audio
 
-         flushStream(video, 'video');
 
-         flushStream(audio, 'audio');
 
-         flushStream(timedMetadata, 'timed-metadata');
 
-       };
 
-       this.flush = function () {
 
-         // if on flush we haven't had a pmt emitted
 
-         // and we have a pmt to emit. emit the pmt
 
-         // so that we trigger a trackinfo downstream.
 
-         if (!segmentHadPmt && programMapTable) {
 
-           var pmt = {
 
-             type: 'metadata',
 
-             tracks: []
 
-           }; // translate audio and video streams to tracks
 
-           if (programMapTable.video !== null) {
 
-             pmt.tracks.push({
 
-               timelineStartInfo: {
 
-                 baseMediaDecodeTime: 0
 
-               },
 
-               id: +programMapTable.video,
 
-               codec: 'avc',
 
-               type: 'video'
 
-             });
 
-           }
 
-           if (programMapTable.audio !== null) {
 
-             pmt.tracks.push({
 
-               timelineStartInfo: {
 
-                 baseMediaDecodeTime: 0
 
-               },
 
-               id: +programMapTable.audio,
 
-               codec: 'adts',
 
-               type: 'audio'
 
-             });
 
-           }
 
-           self.trigger('data', pmt);
 
-         }
 
-         segmentHadPmt = false;
 
-         this.flushStreams_();
 
-         this.trigger('done');
 
-       };
 
-     };
 
-     ElementaryStream.prototype = new Stream$4();
 
-     var m2ts$1 = {
 
-       PAT_PID: 0x0000,
 
-       MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH$1,
 
-       TransportPacketStream: TransportPacketStream,
 
-       TransportParseStream: TransportParseStream,
 
-       ElementaryStream: ElementaryStream,
 
-       TimestampRolloverStream: TimestampRolloverStream,
 
-       CaptionStream: CaptionStream$1.CaptionStream,
 
-       Cea608Stream: CaptionStream$1.Cea608Stream,
 
-       Cea708Stream: CaptionStream$1.Cea708Stream,
 
-       MetadataStream: metadataStream
 
-     };
 
-     for (var type in StreamTypes$2) {
 
-       if (StreamTypes$2.hasOwnProperty(type)) {
 
-         m2ts$1[type] = StreamTypes$2[type];
 
-       }
 
-     }
 
-     var m2ts_1 = m2ts$1;
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     var Stream$3 = stream;
 
-     var ONE_SECOND_IN_TS$2 = clock$2.ONE_SECOND_IN_TS;
 
-     var AdtsStream$1;
 
-     var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
 
-     /*
 
-      * Accepts a ElementaryStream and emits data events with parsed
 
-      * AAC Audio Frames of the individual packets. Input audio in ADTS
 
-      * format is unpacked and re-emitted as AAC frames.
 
-      *
 
-      * @see http://wiki.multimedia.cx/index.php?title=ADTS
 
-      * @see http://wiki.multimedia.cx/?title=Understanding_AAC
 
-      */
 
-     AdtsStream$1 = function (handlePartialSegments) {
 
-       var buffer,
 
-         frameNum = 0;
 
-       AdtsStream$1.prototype.init.call(this);
 
-       this.skipWarn_ = function (start, end) {
 
-         this.trigger('log', {
 
-           level: 'warn',
 
-           message: `adts skiping bytes ${start} to ${end} in frame ${frameNum} outside syncword`
 
-         });
 
-       };
 
-       this.push = function (packet) {
 
-         var i = 0,
 
-           frameLength,
 
-           protectionSkipBytes,
 
-           oldBuffer,
 
-           sampleCount,
 
-           adtsFrameDuration;
 
-         if (!handlePartialSegments) {
 
-           frameNum = 0;
 
-         }
 
-         if (packet.type !== 'audio') {
 
-           // ignore non-audio data
 
-           return;
 
-         } // Prepend any data in the buffer to the input data so that we can parse
 
-         // aac frames the cross a PES packet boundary
 
-         if (buffer && buffer.length) {
 
-           oldBuffer = buffer;
 
-           buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
 
-           buffer.set(oldBuffer);
 
-           buffer.set(packet.data, oldBuffer.byteLength);
 
-         } else {
 
-           buffer = packet.data;
 
-         } // unpack any ADTS frames which have been fully received
 
-         // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
 
-         var skip; // We use i + 7 here because we want to be able to parse the entire header.
 
-         // If we don't have enough bytes to do that, then we definitely won't have a full frame.
 
-         while (i + 7 < buffer.length) {
 
-           // Look for the start of an ADTS header..
 
-           if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
 
-             if (typeof skip !== 'number') {
 
-               skip = i;
 
-             } // If a valid header was not found,  jump one forward and attempt to
 
-             // find a valid ADTS header starting at the next byte
 
-             i++;
 
-             continue;
 
-           }
 
-           if (typeof skip === 'number') {
 
-             this.skipWarn_(skip, i);
 
-             skip = null;
 
-           } // The protection skip bit tells us if we have 2 bytes of CRC data at the
 
-           // end of the ADTS header
 
-           protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
 
-           // end of the sync sequence
 
-           // NOTE: frame length includes the size of the header
 
-           frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
 
-           sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
 
-           adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2]; // If we don't have enough data to actually finish this ADTS frame,
 
-           // then we have to wait for more data
 
-           if (buffer.byteLength - i < frameLength) {
 
-             break;
 
-           } // Otherwise, deliver the complete AAC frame
 
-           this.trigger('data', {
 
-             pts: packet.pts + frameNum * adtsFrameDuration,
 
-             dts: packet.dts + frameNum * adtsFrameDuration,
 
-             sampleCount: sampleCount,
 
-             audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
 
-             channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
 
-             samplerate: ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2],
 
-             samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
 
-             // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
 
-             samplesize: 16,
 
-             // data is the frame without it's header
 
-             data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength)
 
-           });
 
-           frameNum++;
 
-           i += frameLength;
 
-         }
 
-         if (typeof skip === 'number') {
 
-           this.skipWarn_(skip, i);
 
-           skip = null;
 
-         } // remove processed bytes from the buffer.
 
-         buffer = buffer.subarray(i);
 
-       };
 
-       this.flush = function () {
 
-         frameNum = 0;
 
-         this.trigger('done');
 
-       };
 
-       this.reset = function () {
 
-         buffer = void 0;
 
-         this.trigger('reset');
 
-       };
 
-       this.endTimeline = function () {
 
-         buffer = void 0;
 
-         this.trigger('endedtimeline');
 
-       };
 
-     };
 
-     AdtsStream$1.prototype = new Stream$3();
 
-     var adts = AdtsStream$1;
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     var ExpGolomb$1;
 
-     /**
 
-      * Parser for exponential Golomb codes, a variable-bitwidth number encoding
 
-      * scheme used by h264.
 
-      */
 
-     ExpGolomb$1 = function (workingData) {
 
-       var
 
-         // the number of bytes left to examine in workingData
 
-         workingBytesAvailable = workingData.byteLength,
 
-         // the current word being examined
 
-         workingWord = 0,
 
-         // :uint
 
-         // the number of bits left to examine in the current word
 
-         workingBitsAvailable = 0; // :uint;
 
-       // ():uint
 
-       this.length = function () {
 
-         return 8 * workingBytesAvailable;
 
-       }; // ():uint
 
-       this.bitsAvailable = function () {
 
-         return 8 * workingBytesAvailable + workingBitsAvailable;
 
-       }; // ():void
 
-       this.loadWord = function () {
 
-         var position = workingData.byteLength - workingBytesAvailable,
 
-           workingBytes = new Uint8Array(4),
 
-           availableBytes = Math.min(4, workingBytesAvailable);
 
-         if (availableBytes === 0) {
 
-           throw new Error('no bytes available');
 
-         }
 
-         workingBytes.set(workingData.subarray(position, position + availableBytes));
 
-         workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
 
-         workingBitsAvailable = availableBytes * 8;
 
-         workingBytesAvailable -= availableBytes;
 
-       }; // (count:int):void
 
-       this.skipBits = function (count) {
 
-         var skipBytes; // :int
 
-         if (workingBitsAvailable > count) {
 
-           workingWord <<= count;
 
-           workingBitsAvailable -= count;
 
-         } else {
 
-           count -= workingBitsAvailable;
 
-           skipBytes = Math.floor(count / 8);
 
-           count -= skipBytes * 8;
 
-           workingBytesAvailable -= skipBytes;
 
-           this.loadWord();
 
-           workingWord <<= count;
 
-           workingBitsAvailable -= count;
 
-         }
 
-       }; // (size:int):uint
 
-       this.readBits = function (size) {
 
-         var bits = Math.min(workingBitsAvailable, size),
 
-           // :uint
 
-           valu = workingWord >>> 32 - bits; // :uint
 
-         // if size > 31, handle error
 
-         workingBitsAvailable -= bits;
 
-         if (workingBitsAvailable > 0) {
 
-           workingWord <<= bits;
 
-         } else if (workingBytesAvailable > 0) {
 
-           this.loadWord();
 
-         }
 
-         bits = size - bits;
 
-         if (bits > 0) {
 
-           return valu << bits | this.readBits(bits);
 
-         }
 
-         return valu;
 
-       }; // ():uint
 
-       this.skipLeadingZeros = function () {
 
-         var leadingZeroCount; // :uint
 
-         for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
 
-           if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
 
-             // the first bit of working word is 1
 
-             workingWord <<= leadingZeroCount;
 
-             workingBitsAvailable -= leadingZeroCount;
 
-             return leadingZeroCount;
 
-           }
 
-         } // we exhausted workingWord and still have not found a 1
 
-         this.loadWord();
 
-         return leadingZeroCount + this.skipLeadingZeros();
 
-       }; // ():void
 
-       this.skipUnsignedExpGolomb = function () {
 
-         this.skipBits(1 + this.skipLeadingZeros());
 
-       }; // ():void
 
-       this.skipExpGolomb = function () {
 
-         this.skipBits(1 + this.skipLeadingZeros());
 
-       }; // ():uint
 
-       this.readUnsignedExpGolomb = function () {
 
-         var clz = this.skipLeadingZeros(); // :uint
 
-         return this.readBits(clz + 1) - 1;
 
-       }; // ():int
 
-       this.readExpGolomb = function () {
 
-         var valu = this.readUnsignedExpGolomb(); // :int
 
-         if (0x01 & valu) {
 
-           // the number is odd if the low order bit is set
 
-           return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
 
-         }
 
-         return -1 * (valu >>> 1); // divide by two then make it negative
 
-       }; // Some convenience functions
 
-       // :Boolean
 
-       this.readBoolean = function () {
 
-         return this.readBits(1) === 1;
 
-       }; // ():int
 
-       this.readUnsignedByte = function () {
 
-         return this.readBits(8);
 
-       };
 
-       this.loadWord();
 
-     };
 
-     var expGolomb = ExpGolomb$1;
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     var Stream$2 = stream;
 
-     var ExpGolomb = expGolomb;
 
-     var H264Stream$1, NalByteStream;
 
-     var PROFILES_WITH_OPTIONAL_SPS_DATA;
 
-     /**
 
-      * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
 
-      */
 
-     NalByteStream = function () {
 
-       var syncPoint = 0,
 
-         i,
 
-         buffer;
 
-       NalByteStream.prototype.init.call(this);
 
-       /*
 
-        * Scans a byte stream and triggers a data event with the NAL units found.
 
-        * @param {Object} data Event received from H264Stream
 
-        * @param {Uint8Array} data.data The h264 byte stream to be scanned
 
-        *
 
-        * @see H264Stream.push
 
-        */
 
-       this.push = function (data) {
 
-         var swapBuffer;
 
-         if (!buffer) {
 
-           buffer = data.data;
 
-         } else {
 
-           swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
 
-           swapBuffer.set(buffer);
 
-           swapBuffer.set(data.data, buffer.byteLength);
 
-           buffer = swapBuffer;
 
-         }
 
-         var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
 
-         // scan for NAL unit boundaries
 
-         // a match looks like this:
 
-         // 0 0 1 .. NAL .. 0 0 1
 
-         // ^ sync point        ^ i
 
-         // or this:
 
-         // 0 0 1 .. NAL .. 0 0 0
 
-         // ^ sync point        ^ i
 
-         // advance the sync point to a NAL start, if necessary
 
-         for (; syncPoint < len - 3; syncPoint++) {
 
-           if (buffer[syncPoint + 2] === 1) {
 
-             // the sync point is properly aligned
 
-             i = syncPoint + 5;
 
-             break;
 
-           }
 
-         }
 
-         while (i < len) {
 
-           // look at the current byte to determine if we've hit the end of
 
-           // a NAL unit boundary
 
-           switch (buffer[i]) {
 
-             case 0:
 
-               // skip past non-sync sequences
 
-               if (buffer[i - 1] !== 0) {
 
-                 i += 2;
 
-                 break;
 
-               } else if (buffer[i - 2] !== 0) {
 
-                 i++;
 
-                 break;
 
-               } // deliver the NAL unit if it isn't empty
 
-               if (syncPoint + 3 !== i - 2) {
 
-                 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
 
-               } // drop trailing zeroes
 
-               do {
 
-                 i++;
 
-               } while (buffer[i] !== 1 && i < len);
 
-               syncPoint = i - 2;
 
-               i += 3;
 
-               break;
 
-             case 1:
 
-               // skip past non-sync sequences
 
-               if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
 
-                 i += 3;
 
-                 break;
 
-               } // deliver the NAL unit
 
-               this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
 
-               syncPoint = i - 2;
 
-               i += 3;
 
-               break;
 
-             default:
 
-               // the current byte isn't a one or zero, so it cannot be part
 
-               // of a sync sequence
 
-               i += 3;
 
-               break;
 
-           }
 
-         } // filter out the NAL units that were delivered
 
-         buffer = buffer.subarray(syncPoint);
 
-         i -= syncPoint;
 
-         syncPoint = 0;
 
-       };
 
-       this.reset = function () {
 
-         buffer = null;
 
-         syncPoint = 0;
 
-         this.trigger('reset');
 
-       };
 
-       this.flush = function () {
 
-         // deliver the last buffered NAL unit
 
-         if (buffer && buffer.byteLength > 3) {
 
-           this.trigger('data', buffer.subarray(syncPoint + 3));
 
-         } // reset the stream state
 
-         buffer = null;
 
-         syncPoint = 0;
 
-         this.trigger('done');
 
-       };
 
-       this.endTimeline = function () {
 
-         this.flush();
 
-         this.trigger('endedtimeline');
 
-       };
 
-     };
 
-     NalByteStream.prototype = new Stream$2(); // values of profile_idc that indicate additional fields are included in the SPS
 
-     // see Recommendation ITU-T H.264 (4/2013),
 
-     // 7.3.2.1.1 Sequence parameter set data syntax
 
-     PROFILES_WITH_OPTIONAL_SPS_DATA = {
 
-       100: true,
 
-       110: true,
 
-       122: true,
 
-       244: true,
 
-       44: true,
 
-       83: true,
 
-       86: true,
 
-       118: true,
 
-       128: true,
 
-       // TODO: the three profiles below don't
 
-       // appear to have sps data in the specificiation anymore?
 
-       138: true,
 
-       139: true,
 
-       134: true
 
-     };
 
-     /**
 
-      * Accepts input from a ElementaryStream and produces H.264 NAL unit data
 
-      * events.
 
-      */
 
-     H264Stream$1 = function () {
 
-       var nalByteStream = new NalByteStream(),
 
-         self,
 
-         trackId,
 
-         currentPts,
 
-         currentDts,
 
-         discardEmulationPreventionBytes,
 
-         readSequenceParameterSet,
 
-         skipScalingList;
 
-       H264Stream$1.prototype.init.call(this);
 
-       self = this;
 
-       /*
 
-        * Pushes a packet from a stream onto the NalByteStream
 
-        *
 
-        * @param {Object} packet - A packet received from a stream
 
-        * @param {Uint8Array} packet.data - The raw bytes of the packet
 
-        * @param {Number} packet.dts - Decode timestamp of the packet
 
-        * @param {Number} packet.pts - Presentation timestamp of the packet
 
-        * @param {Number} packet.trackId - The id of the h264 track this packet came from
 
-        * @param {('video'|'audio')} packet.type - The type of packet
 
-        *
 
-        */
 
-       this.push = function (packet) {
 
-         if (packet.type !== 'video') {
 
-           return;
 
-         }
 
-         trackId = packet.trackId;
 
-         currentPts = packet.pts;
 
-         currentDts = packet.dts;
 
-         nalByteStream.push(packet);
 
-       };
 
-       /*
 
-        * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
 
-        * for the NALUs to the next stream component.
 
-        * Also, preprocess caption and sequence parameter NALUs.
 
-        *
 
-        * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
 
-        * @see NalByteStream.push
 
-        */
 
-       nalByteStream.on('data', function (data) {
 
-         var event = {
 
-           trackId: trackId,
 
-           pts: currentPts,
 
-           dts: currentDts,
 
-           data: data,
 
-           nalUnitTypeCode: data[0] & 0x1f
 
-         };
 
-         switch (event.nalUnitTypeCode) {
 
-           case 0x05:
 
-             event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
 
-             break;
 
-           case 0x06:
 
-             event.nalUnitType = 'sei_rbsp';
 
-             event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
 
-             break;
 
-           case 0x07:
 
-             event.nalUnitType = 'seq_parameter_set_rbsp';
 
-             event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
 
-             event.config = readSequenceParameterSet(event.escapedRBSP);
 
-             break;
 
-           case 0x08:
 
-             event.nalUnitType = 'pic_parameter_set_rbsp';
 
-             break;
 
-           case 0x09:
 
-             event.nalUnitType = 'access_unit_delimiter_rbsp';
 
-             break;
 
-         } // This triggers data on the H264Stream
 
-         self.trigger('data', event);
 
-       });
 
-       nalByteStream.on('done', function () {
 
-         self.trigger('done');
 
-       });
 
-       nalByteStream.on('partialdone', function () {
 
-         self.trigger('partialdone');
 
-       });
 
-       nalByteStream.on('reset', function () {
 
-         self.trigger('reset');
 
-       });
 
-       nalByteStream.on('endedtimeline', function () {
 
-         self.trigger('endedtimeline');
 
-       });
 
-       this.flush = function () {
 
-         nalByteStream.flush();
 
-       };
 
-       this.partialFlush = function () {
 
-         nalByteStream.partialFlush();
 
-       };
 
-       this.reset = function () {
 
-         nalByteStream.reset();
 
-       };
 
-       this.endTimeline = function () {
 
-         nalByteStream.endTimeline();
 
-       };
 
-       /**
 
-        * Advance the ExpGolomb decoder past a scaling list. The scaling
 
-        * list is optionally transmitted as part of a sequence parameter
 
-        * set and is not relevant to transmuxing.
 
-        * @param count {number} the number of entries in this scaling list
 
-        * @param expGolombDecoder {object} an ExpGolomb pointed to the
 
-        * start of a scaling list
 
-        * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
 
-        */
 
-       skipScalingList = function (count, expGolombDecoder) {
 
-         var lastScale = 8,
 
-           nextScale = 8,
 
-           j,
 
-           deltaScale;
 
-         for (j = 0; j < count; j++) {
 
-           if (nextScale !== 0) {
 
-             deltaScale = expGolombDecoder.readExpGolomb();
 
-             nextScale = (lastScale + deltaScale + 256) % 256;
 
-           }
 
-           lastScale = nextScale === 0 ? lastScale : nextScale;
 
-         }
 
-       };
 
-       /**
 
-        * Expunge any "Emulation Prevention" bytes from a "Raw Byte
 
-        * Sequence Payload"
 
-        * @param data {Uint8Array} the bytes of a RBSP from a NAL
 
-        * unit
 
-        * @return {Uint8Array} the RBSP without any Emulation
 
-        * Prevention Bytes
 
-        */
 
-       discardEmulationPreventionBytes = function (data) {
 
-         var length = data.byteLength,
 
-           emulationPreventionBytesPositions = [],
 
-           i = 1,
 
-           newLength,
 
-           newData; // Find all `Emulation Prevention Bytes`
 
-         while (i < length - 2) {
 
-           if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
 
-             emulationPreventionBytesPositions.push(i + 2);
 
-             i += 2;
 
-           } else {
 
-             i++;
 
-           }
 
-         } // If no Emulation Prevention Bytes were found just return the original
 
-         // array
 
-         if (emulationPreventionBytesPositions.length === 0) {
 
-           return data;
 
-         } // Create a new array to hold the NAL unit data
 
-         newLength = length - emulationPreventionBytesPositions.length;
 
-         newData = new Uint8Array(newLength);
 
-         var sourceIndex = 0;
 
-         for (i = 0; i < newLength; sourceIndex++, i++) {
 
-           if (sourceIndex === emulationPreventionBytesPositions[0]) {
 
-             // Skip this byte
 
-             sourceIndex++; // Remove this position index
 
-             emulationPreventionBytesPositions.shift();
 
-           }
 
-           newData[i] = data[sourceIndex];
 
-         }
 
-         return newData;
 
-       };
 
-       /**
 
-        * Read a sequence parameter set and return some interesting video
 
-        * properties. A sequence parameter set is the H264 metadata that
 
-        * describes the properties of upcoming video frames.
 
-        * @param data {Uint8Array} the bytes of a sequence parameter set
 
-        * @return {object} an object with configuration parsed from the
 
-        * sequence parameter set, including the dimensions of the
 
-        * associated video frames.
 
-        */
 
-       readSequenceParameterSet = function (data) {
 
-         var frameCropLeftOffset = 0,
 
-           frameCropRightOffset = 0,
 
-           frameCropTopOffset = 0,
 
-           frameCropBottomOffset = 0,
 
-           expGolombDecoder,
 
-           profileIdc,
 
-           levelIdc,
 
-           profileCompatibility,
 
-           chromaFormatIdc,
 
-           picOrderCntType,
 
-           numRefFramesInPicOrderCntCycle,
 
-           picWidthInMbsMinus1,
 
-           picHeightInMapUnitsMinus1,
 
-           frameMbsOnlyFlag,
 
-           scalingListCount,
 
-           sarRatio = [1, 1],
 
-           aspectRatioIdc,
 
-           i;
 
-         expGolombDecoder = new ExpGolomb(data);
 
-         profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
 
-         profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
 
-         levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
 
-         expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
 
-         // some profiles have more optional data we don't need
 
-         if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
 
-           chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
 
-           if (chromaFormatIdc === 3) {
 
-             expGolombDecoder.skipBits(1); // separate_colour_plane_flag
 
-           }
 
-           expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
 
-           expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
 
-           expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
 
-           if (expGolombDecoder.readBoolean()) {
 
-             // seq_scaling_matrix_present_flag
 
-             scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
 
-             for (i = 0; i < scalingListCount; i++) {
 
-               if (expGolombDecoder.readBoolean()) {
 
-                 // seq_scaling_list_present_flag[ i ]
 
-                 if (i < 6) {
 
-                   skipScalingList(16, expGolombDecoder);
 
-                 } else {
 
-                   skipScalingList(64, expGolombDecoder);
 
-                 }
 
-               }
 
-             }
 
-           }
 
-         }
 
-         expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
 
-         picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
 
-         if (picOrderCntType === 0) {
 
-           expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
 
-         } else if (picOrderCntType === 1) {
 
-           expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
 
-           expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
 
-           expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
 
-           numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
 
-           for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
 
-             expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
 
-           }
 
-         }
 
-         expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
 
-         expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
 
-         picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
 
-         picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
 
-         frameMbsOnlyFlag = expGolombDecoder.readBits(1);
 
-         if (frameMbsOnlyFlag === 0) {
 
-           expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
 
-         }
 
-         expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
 
-         if (expGolombDecoder.readBoolean()) {
 
-           // frame_cropping_flag
 
-           frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
 
-           frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
 
-           frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
 
-           frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
 
-         }
 
-         if (expGolombDecoder.readBoolean()) {
 
-           // vui_parameters_present_flag
 
-           if (expGolombDecoder.readBoolean()) {
 
-             // aspect_ratio_info_present_flag
 
-             aspectRatioIdc = expGolombDecoder.readUnsignedByte();
 
-             switch (aspectRatioIdc) {
 
-               case 1:
 
-                 sarRatio = [1, 1];
 
-                 break;
 
-               case 2:
 
-                 sarRatio = [12, 11];
 
-                 break;
 
-               case 3:
 
-                 sarRatio = [10, 11];
 
-                 break;
 
-               case 4:
 
-                 sarRatio = [16, 11];
 
-                 break;
 
-               case 5:
 
-                 sarRatio = [40, 33];
 
-                 break;
 
-               case 6:
 
-                 sarRatio = [24, 11];
 
-                 break;
 
-               case 7:
 
-                 sarRatio = [20, 11];
 
-                 break;
 
-               case 8:
 
-                 sarRatio = [32, 11];
 
-                 break;
 
-               case 9:
 
-                 sarRatio = [80, 33];
 
-                 break;
 
-               case 10:
 
-                 sarRatio = [18, 11];
 
-                 break;
 
-               case 11:
 
-                 sarRatio = [15, 11];
 
-                 break;
 
-               case 12:
 
-                 sarRatio = [64, 33];
 
-                 break;
 
-               case 13:
 
-                 sarRatio = [160, 99];
 
-                 break;
 
-               case 14:
 
-                 sarRatio = [4, 3];
 
-                 break;
 
-               case 15:
 
-                 sarRatio = [3, 2];
 
-                 break;
 
-               case 16:
 
-                 sarRatio = [2, 1];
 
-                 break;
 
-               case 255:
 
-                 {
 
-                   sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
 
-                   break;
 
-                 }
 
-             }
 
-             if (sarRatio) {
 
-               sarRatio[0] / sarRatio[1];
 
-             }
 
-           }
 
-         }
 
-         return {
 
-           profileIdc: profileIdc,
 
-           levelIdc: levelIdc,
 
-           profileCompatibility: profileCompatibility,
 
-           width: (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
 
-           height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
 
-           // sar is sample aspect ratio
 
-           sarRatio: sarRatio
 
-         };
 
-       };
 
-     };
 
-     H264Stream$1.prototype = new Stream$2();
 
-     var h264 = {
 
-       H264Stream: H264Stream$1,
 
-       NalByteStream: NalByteStream
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Utilities to detect basic properties and metadata about Aac data.
 
-      */
 
-     var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
 
-     var parseId3TagSize = function (header, byteIndex) {
 
-       var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
 
-         flags = header[byteIndex + 5],
 
-         footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0
 
-       returnSize = returnSize >= 0 ? returnSize : 0;
 
-       if (footerPresent) {
 
-         return returnSize + 20;
 
-       }
 
-       return returnSize + 10;
 
-     };
 
-     var getId3Offset = function (data, offset) {
 
-       if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) {
 
-         return offset;
 
-       }
 
-       offset += parseId3TagSize(data, offset);
 
-       return getId3Offset(data, offset);
 
-     }; // TODO: use vhs-utils
 
-     var isLikelyAacData$1 = function (data) {
 
-       var offset = getId3Offset(data, 0);
 
-       return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 &&
 
-       // verify that the 2 layer bits are 0, aka this
 
-       // is not mp3 data but aac data.
 
-       (data[offset + 1] & 0x16) === 0x10;
 
-     };
 
-     var parseSyncSafeInteger = function (data) {
 
-       return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
 
-     }; // return a percent-encoded representation of the specified byte range
 
-     // @see http://en.wikipedia.org/wiki/Percent-encoding
 
-     var percentEncode = function (bytes, start, end) {
 
-       var i,
 
-         result = '';
 
-       for (i = start; i < end; i++) {
 
-         result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
 
-       }
 
-       return result;
 
-     }; // return the string representation of the specified byte range,
 
-     // interpreted as ISO-8859-1.
 
-     var parseIso88591 = function (bytes, start, end) {
 
-       return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
 
-     };
 
-     var parseAdtsSize = function (header, byteIndex) {
 
-       var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
 
-         middle = header[byteIndex + 4] << 3,
 
-         highTwo = header[byteIndex + 3] & 0x3 << 11;
 
-       return highTwo | middle | lowThree;
 
-     };
 
-     var parseType$4 = function (header, byteIndex) {
 
-       if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
 
-         return 'timed-metadata';
 
-       } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
 
-         return 'audio';
 
-       }
 
-       return null;
 
-     };
 
-     var parseSampleRate = function (packet) {
 
-       var i = 0;
 
-       while (i + 5 < packet.length) {
 
-         if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
 
-           // If a valid header was not found,  jump one forward and attempt to
 
-           // find a valid ADTS header starting at the next byte
 
-           i++;
 
-           continue;
 
-         }
 
-         return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
 
-       }
 
-       return null;
 
-     };
 
-     var parseAacTimestamp = function (packet) {
 
-       var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
 
-       frameStart = 10;
 
-       if (packet[5] & 0x40) {
 
-         // advance the frame start past the extended header
 
-         frameStart += 4; // header size field
 
-         frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
 
-       } // parse one or more ID3 frames
 
-       // http://id3.org/id3v2.3.0#ID3v2_frame_overview
 
-       do {
 
-         // determine the number of bytes in this frame
 
-         frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
 
-         if (frameSize < 1) {
 
-           return null;
 
-         }
 
-         frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
 
-         if (frameHeader === 'PRIV') {
 
-           frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
 
-           for (var i = 0; i < frame.byteLength; i++) {
 
-             if (frame[i] === 0) {
 
-               var owner = parseIso88591(frame, 0, i);
 
-               if (owner === 'com.apple.streaming.transportStreamTimestamp') {
 
-                 var d = frame.subarray(i + 1);
 
-                 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
 
-                 size *= 4;
 
-                 size += d[7] & 0x03;
 
-                 return size;
 
-               }
 
-               break;
 
-             }
 
-           }
 
-         }
 
-         frameStart += 10; // advance past the frame header
 
-         frameStart += frameSize; // advance past the frame body
 
-       } while (frameStart < packet.byteLength);
 
-       return null;
 
-     };
 
-     var utils = {
 
-       isLikelyAacData: isLikelyAacData$1,
 
-       parseId3TagSize: parseId3TagSize,
 
-       parseAdtsSize: parseAdtsSize,
 
-       parseType: parseType$4,
 
-       parseSampleRate: parseSampleRate,
 
-       parseAacTimestamp: parseAacTimestamp
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * A stream-based aac to mp4 converter. This utility can be used to
 
-      * deliver mp4s to a SourceBuffer on platforms that support native
 
-      * Media Source Extensions.
 
-      */
 
-     var Stream$1 = stream;
 
-     var aacUtils = utils; // Constants
 
-     var AacStream$1;
 
-     /**
 
-      * Splits an incoming stream of binary data into ADTS and ID3 Frames.
 
-      */
 
-     AacStream$1 = function () {
 
-       var everything = new Uint8Array(),
 
-         timeStamp = 0;
 
-       AacStream$1.prototype.init.call(this);
 
-       this.setTimestamp = function (timestamp) {
 
-         timeStamp = timestamp;
 
-       };
 
-       this.push = function (bytes) {
 
-         var frameSize = 0,
 
-           byteIndex = 0,
 
-           bytesLeft,
 
-           chunk,
 
-           packet,
 
-           tempLength; // If there are bytes remaining from the last segment, prepend them to the
 
-         // bytes that were pushed in
 
-         if (everything.length) {
 
-           tempLength = everything.length;
 
-           everything = new Uint8Array(bytes.byteLength + tempLength);
 
-           everything.set(everything.subarray(0, tempLength));
 
-           everything.set(bytes, tempLength);
 
-         } else {
 
-           everything = bytes;
 
-         }
 
-         while (everything.length - byteIndex >= 3) {
 
-           if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
 
-             // Exit early because we don't have enough to parse
 
-             // the ID3 tag header
 
-             if (everything.length - byteIndex < 10) {
 
-               break;
 
-             } // check framesize
 
-             frameSize = aacUtils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
 
-             // to emit a full packet
 
-             // Add to byteIndex to support multiple ID3 tags in sequence
 
-             if (byteIndex + frameSize > everything.length) {
 
-               break;
 
-             }
 
-             chunk = {
 
-               type: 'timed-metadata',
 
-               data: everything.subarray(byteIndex, byteIndex + frameSize)
 
-             };
 
-             this.trigger('data', chunk);
 
-             byteIndex += frameSize;
 
-             continue;
 
-           } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
 
-             // Exit early because we don't have enough to parse
 
-             // the ADTS frame header
 
-             if (everything.length - byteIndex < 7) {
 
-               break;
 
-             }
 
-             frameSize = aacUtils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
 
-             // to emit a full packet
 
-             if (byteIndex + frameSize > everything.length) {
 
-               break;
 
-             }
 
-             packet = {
 
-               type: 'audio',
 
-               data: everything.subarray(byteIndex, byteIndex + frameSize),
 
-               pts: timeStamp,
 
-               dts: timeStamp
 
-             };
 
-             this.trigger('data', packet);
 
-             byteIndex += frameSize;
 
-             continue;
 
-           }
 
-           byteIndex++;
 
-         }
 
-         bytesLeft = everything.length - byteIndex;
 
-         if (bytesLeft > 0) {
 
-           everything = everything.subarray(byteIndex);
 
-         } else {
 
-           everything = new Uint8Array();
 
-         }
 
-       };
 
-       this.reset = function () {
 
-         everything = new Uint8Array();
 
-         this.trigger('reset');
 
-       };
 
-       this.endTimeline = function () {
 
-         everything = new Uint8Array();
 
-         this.trigger('endedtimeline');
 
-       };
 
-     };
 
-     AacStream$1.prototype = new Stream$1();
 
-     var aac = AacStream$1;
 
-     var AUDIO_PROPERTIES$1 = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
 
-     var audioProperties = AUDIO_PROPERTIES$1;
 
-     var VIDEO_PROPERTIES$1 = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio'];
 
-     var videoProperties = VIDEO_PROPERTIES$1;
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * A stream-based mp2t to mp4 converter. This utility can be used to
 
-      * deliver mp4s to a SourceBuffer on platforms that support native
 
-      * Media Source Extensions.
 
-      */
 
-     var Stream = stream;
 
-     var mp4 = mp4Generator;
 
-     var frameUtils = frameUtils$1;
 
-     var audioFrameUtils = audioFrameUtils$1;
 
-     var trackDecodeInfo = trackDecodeInfo$1;
 
-     var m2ts = m2ts_1;
 
-     var clock = clock$2;
 
-     var AdtsStream = adts;
 
-     var H264Stream = h264.H264Stream;
 
-     var AacStream = aac;
 
-     var isLikelyAacData = utils.isLikelyAacData;
 
-     var ONE_SECOND_IN_TS$1 = clock$2.ONE_SECOND_IN_TS;
 
-     var AUDIO_PROPERTIES = audioProperties;
 
-     var VIDEO_PROPERTIES = videoProperties; // object types
 
-     var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream;
 
-     var retriggerForStream = function (key, event) {
 
-       event.stream = key;
 
-       this.trigger('log', event);
 
-     };
 
-     var addPipelineLogRetriggers = function (transmuxer, pipeline) {
 
-       var keys = Object.keys(pipeline);
 
-       for (var i = 0; i < keys.length; i++) {
 
-         var key = keys[i]; // skip non-stream keys and headOfPipeline
 
-         // which is just a duplicate
 
-         if (key === 'headOfPipeline' || !pipeline[key].on) {
 
-           continue;
 
-         }
 
-         pipeline[key].on('log', retriggerForStream.bind(transmuxer, key));
 
-       }
 
-     };
 
-     /**
 
-      * Compare two arrays (even typed) for same-ness
 
-      */
 
-     var arrayEquals = function (a, b) {
 
-       var i;
 
-       if (a.length !== b.length) {
 
-         return false;
 
-       } // compare the value of each element in the array
 
-       for (i = 0; i < a.length; i++) {
 
-         if (a[i] !== b[i]) {
 
-           return false;
 
-         }
 
-       }
 
-       return true;
 
-     };
 
-     var generateSegmentTimingInfo = function (baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
 
-       var ptsOffsetFromDts = startPts - startDts,
 
-         decodeDuration = endDts - startDts,
 
-         presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
 
-       // however, the player time values will reflect a start from the baseMediaDecodeTime.
 
-       // In order to provide relevant values for the player times, base timing info on the
 
-       // baseMediaDecodeTime and the DTS and PTS durations of the segment.
 
-       return {
 
-         start: {
 
-           dts: baseMediaDecodeTime,
 
-           pts: baseMediaDecodeTime + ptsOffsetFromDts
 
-         },
 
-         end: {
 
-           dts: baseMediaDecodeTime + decodeDuration,
 
-           pts: baseMediaDecodeTime + presentationDuration
 
-         },
 
-         prependedContentDuration: prependedContentDuration,
 
-         baseMediaDecodeTime: baseMediaDecodeTime
 
-       };
 
-     };
 
-     /**
 
-      * Constructs a single-track, ISO BMFF media segment from AAC data
 
-      * events. The output of this stream can be fed to a SourceBuffer
 
-      * configured with a suitable initialization segment.
 
-      * @param track {object} track metadata configuration
 
-      * @param options {object} transmuxer options object
 
-      * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
 
-      *        in the source; false to adjust the first segment to start at 0.
 
-      */
 
-     AudioSegmentStream = function (track, options) {
 
-       var adtsFrames = [],
 
-         sequenceNumber,
 
-         earliestAllowedDts = 0,
 
-         audioAppendStartTs = 0,
 
-         videoBaseMediaDecodeTime = Infinity;
 
-       options = options || {};
 
-       sequenceNumber = options.firstSequenceNumber || 0;
 
-       AudioSegmentStream.prototype.init.call(this);
 
-       this.push = function (data) {
 
-         trackDecodeInfo.collectDtsInfo(track, data);
 
-         if (track) {
 
-           AUDIO_PROPERTIES.forEach(function (prop) {
 
-             track[prop] = data[prop];
 
-           });
 
-         } // buffer audio data until end() is called
 
-         adtsFrames.push(data);
 
-       };
 
-       this.setEarliestDts = function (earliestDts) {
 
-         earliestAllowedDts = earliestDts;
 
-       };
 
-       this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
 
-         videoBaseMediaDecodeTime = baseMediaDecodeTime;
 
-       };
 
-       this.setAudioAppendStart = function (timestamp) {
 
-         audioAppendStartTs = timestamp;
 
-       };
 
-       this.flush = function () {
 
-         var frames, moof, mdat, boxes, frameDuration, segmentDuration, videoClockCyclesOfSilencePrefixed; // return early if no audio data has been observed
 
-         if (adtsFrames.length === 0) {
 
-           this.trigger('done', 'AudioSegmentStream');
 
-           return;
 
-         }
 
-         frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
 
-         track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); // amount of audio filled but the value is in video clock rather than audio clock
 
-         videoClockCyclesOfSilencePrefixed = audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
 
-         // samples (that is, adts frames) in the audio data
 
-         track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
 
-         mdat = mp4.mdat(audioFrameUtils.concatenateFrameData(frames));
 
-         adtsFrames = [];
 
-         moof = mp4.moof(sequenceNumber, [track]);
 
-         boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
 
-         sequenceNumber++;
 
-         boxes.set(moof);
 
-         boxes.set(mdat, moof.byteLength);
 
-         trackDecodeInfo.clearDtsInfo(track);
 
-         frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with
 
-         // tests) on adding the timingInfo event. However, it seems unlikely that there's a
 
-         // valid use-case where an init segment/data should be triggered without associated
 
-         // frames. Leaving for now, but should be looked into.
 
-         if (frames.length) {
 
-           segmentDuration = frames.length * frameDuration;
 
-           this.trigger('segmentTimingInfo', generateSegmentTimingInfo(
 
-           // The audio track's baseMediaDecodeTime is in audio clock cycles, but the
 
-           // frame info is in video clock cycles. Convert to match expectation of
 
-           // listeners (that all timestamps will be based on video clock cycles).
 
-           clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate),
 
-           // frame times are already in video clock, as is segment duration
 
-           frames[0].dts, frames[0].pts, frames[0].dts + segmentDuration, frames[0].pts + segmentDuration, videoClockCyclesOfSilencePrefixed || 0));
 
-           this.trigger('timingInfo', {
 
-             start: frames[0].pts,
 
-             end: frames[0].pts + segmentDuration
 
-           });
 
-         }
 
-         this.trigger('data', {
 
-           track: track,
 
-           boxes: boxes
 
-         });
 
-         this.trigger('done', 'AudioSegmentStream');
 
-       };
 
-       this.reset = function () {
 
-         trackDecodeInfo.clearDtsInfo(track);
 
-         adtsFrames = [];
 
-         this.trigger('reset');
 
-       };
 
-     };
 
-     AudioSegmentStream.prototype = new Stream();
 
-     /**
 
-      * Constructs a single-track, ISO BMFF media segment from H264 data
 
-      * events. The output of this stream can be fed to a SourceBuffer
 
-      * configured with a suitable initialization segment.
 
-      * @param track {object} track metadata configuration
 
-      * @param options {object} transmuxer options object
 
-      * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
 
-      *        gopsToAlignWith list when attempting to align gop pts
 
-      * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
 
-      *        in the source; false to adjust the first segment to start at 0.
 
-      */
 
-     VideoSegmentStream = function (track, options) {
 
-       var sequenceNumber,
 
-         nalUnits = [],
 
-         gopsToAlignWith = [],
 
-         config,
 
-         pps;
 
-       options = options || {};
 
-       sequenceNumber = options.firstSequenceNumber || 0;
 
-       VideoSegmentStream.prototype.init.call(this);
 
-       delete track.minPTS;
 
-       this.gopCache_ = [];
 
-       /**
 
-         * Constructs a ISO BMFF segment given H264 nalUnits
 
-         * @param {Object} nalUnit A data event representing a nalUnit
 
-         * @param {String} nalUnit.nalUnitType
 
-         * @param {Object} nalUnit.config Properties for a mp4 track
 
-         * @param {Uint8Array} nalUnit.data The nalUnit bytes
 
-         * @see lib/codecs/h264.js
 
-        **/
 
-       this.push = function (nalUnit) {
 
-         trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
 
-         if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
 
-           config = nalUnit.config;
 
-           track.sps = [nalUnit.data];
 
-           VIDEO_PROPERTIES.forEach(function (prop) {
 
-             track[prop] = config[prop];
 
-           }, this);
 
-         }
 
-         if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
 
-           pps = nalUnit.data;
 
-           track.pps = [nalUnit.data];
 
-         } // buffer video until flush() is called
 
-         nalUnits.push(nalUnit);
 
-       };
 
-       /**
 
-         * Pass constructed ISO BMFF track and boxes on to the
 
-         * next stream in the pipeline
 
-        **/
 
-       this.flush = function () {
 
-         var frames,
 
-           gopForFusion,
 
-           gops,
 
-           moof,
 
-           mdat,
 
-           boxes,
 
-           prependedContentDuration = 0,
 
-           firstGop,
 
-           lastGop; // Throw away nalUnits at the start of the byte stream until
 
-         // we find the first AUD
 
-         while (nalUnits.length) {
 
-           if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
 
-             break;
 
-           }
 
-           nalUnits.shift();
 
-         } // Return early if no video data has been observed
 
-         if (nalUnits.length === 0) {
 
-           this.resetStream_();
 
-           this.trigger('done', 'VideoSegmentStream');
 
-           return;
 
-         } // Organize the raw nal-units into arrays that represent
 
-         // higher-level constructs such as frames and gops
 
-         // (group-of-pictures)
 
-         frames = frameUtils.groupNalsIntoFrames(nalUnits);
 
-         gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
 
-         // a problem since MSE (on Chrome) requires a leading keyframe.
 
-         //
 
-         // We have two approaches to repairing this situation:
 
-         // 1) GOP-FUSION:
 
-         //    This is where we keep track of the GOPS (group-of-pictures)
 
-         //    from previous fragments and attempt to find one that we can
 
-         //    prepend to the current fragment in order to create a valid
 
-         //    fragment.
 
-         // 2) KEYFRAME-PULLING:
 
-         //    Here we search for the first keyframe in the fragment and
 
-         //    throw away all the frames between the start of the fragment
 
-         //    and that keyframe. We then extend the duration and pull the
 
-         //    PTS of the keyframe forward so that it covers the time range
 
-         //    of the frames that were disposed of.
 
-         //
 
-         // #1 is far prefereable over #2 which can cause "stuttering" but
 
-         // requires more things to be just right.
 
-         if (!gops[0][0].keyFrame) {
 
-           // Search for a gop for fusion from our gopCache
 
-           gopForFusion = this.getGopForFusion_(nalUnits[0], track);
 
-           if (gopForFusion) {
 
-             // in order to provide more accurate timing information about the segment, save
 
-             // the number of seconds prepended to the original segment due to GOP fusion
 
-             prependedContentDuration = gopForFusion.duration;
 
-             gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
 
-             // new gop at the beginning
 
-             gops.byteLength += gopForFusion.byteLength;
 
-             gops.nalCount += gopForFusion.nalCount;
 
-             gops.pts = gopForFusion.pts;
 
-             gops.dts = gopForFusion.dts;
 
-             gops.duration += gopForFusion.duration;
 
-           } else {
 
-             // If we didn't find a candidate gop fall back to keyframe-pulling
 
-             gops = frameUtils.extendFirstKeyFrame(gops);
 
-           }
 
-         } // Trim gops to align with gopsToAlignWith
 
-         if (gopsToAlignWith.length) {
 
-           var alignedGops;
 
-           if (options.alignGopsAtEnd) {
 
-             alignedGops = this.alignGopsAtEnd_(gops);
 
-           } else {
 
-             alignedGops = this.alignGopsAtStart_(gops);
 
-           }
 
-           if (!alignedGops) {
 
-             // save all the nals in the last GOP into the gop cache
 
-             this.gopCache_.unshift({
 
-               gop: gops.pop(),
 
-               pps: track.pps,
 
-               sps: track.sps
 
-             }); // Keep a maximum of 6 GOPs in the cache
 
-             this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
 
-             nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
 
-             this.resetStream_();
 
-             this.trigger('done', 'VideoSegmentStream');
 
-             return;
 
-           } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
 
-           // when recalculated before sending off to CoalesceStream
 
-           trackDecodeInfo.clearDtsInfo(track);
 
-           gops = alignedGops;
 
-         }
 
-         trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
 
-         // samples (that is, frames) in the video data
 
-         track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
 
-         mdat = mp4.mdat(frameUtils.concatenateNalData(gops));
 
-         track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
 
-         this.trigger('processedGopsInfo', gops.map(function (gop) {
 
-           return {
 
-             pts: gop.pts,
 
-             dts: gop.dts,
 
-             byteLength: gop.byteLength
 
-           };
 
-         }));
 
-         firstGop = gops[0];
 
-         lastGop = gops[gops.length - 1];
 
-         this.trigger('segmentTimingInfo', generateSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration));
 
-         this.trigger('timingInfo', {
 
-           start: gops[0].pts,
 
-           end: gops[gops.length - 1].pts + gops[gops.length - 1].duration
 
-         }); // save all the nals in the last GOP into the gop cache
 
-         this.gopCache_.unshift({
 
-           gop: gops.pop(),
 
-           pps: track.pps,
 
-           sps: track.sps
 
-         }); // Keep a maximum of 6 GOPs in the cache
 
-         this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
 
-         nalUnits = [];
 
-         this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
 
-         this.trigger('timelineStartInfo', track.timelineStartInfo);
 
-         moof = mp4.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
 
-         // throwing away hundreds of media segment fragments
 
-         boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
 
-         sequenceNumber++;
 
-         boxes.set(moof);
 
-         boxes.set(mdat, moof.byteLength);
 
-         this.trigger('data', {
 
-           track: track,
 
-           boxes: boxes
 
-         });
 
-         this.resetStream_(); // Continue with the flush process now
 
-         this.trigger('done', 'VideoSegmentStream');
 
-       };
 
-       this.reset = function () {
 
-         this.resetStream_();
 
-         nalUnits = [];
 
-         this.gopCache_.length = 0;
 
-         gopsToAlignWith.length = 0;
 
-         this.trigger('reset');
 
-       };
 
-       this.resetStream_ = function () {
 
-         trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
 
-         // for instance, when we are rendition switching
 
-         config = undefined;
 
-         pps = undefined;
 
-       }; // Search for a candidate Gop for gop-fusion from the gop cache and
 
-       // return it or return null if no good candidate was found
 
-       this.getGopForFusion_ = function (nalUnit) {
 
-         var halfSecond = 45000,
 
-           // Half-a-second in a 90khz clock
 
-           allowableOverlap = 10000,
 
-           // About 3 frames @ 30fps
 
-           nearestDistance = Infinity,
 
-           dtsDistance,
 
-           nearestGopObj,
 
-           currentGop,
 
-           currentGopObj,
 
-           i; // Search for the GOP nearest to the beginning of this nal unit
 
-         for (i = 0; i < this.gopCache_.length; i++) {
 
-           currentGopObj = this.gopCache_[i];
 
-           currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
 
-           if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
 
-             continue;
 
-           } // Reject Gops that would require a negative baseMediaDecodeTime
 
-           if (currentGop.dts < track.timelineStartInfo.dts) {
 
-             continue;
 
-           } // The distance between the end of the gop and the start of the nalUnit
 
-           dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
 
-           // a half-second of the nal unit
 
-           if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
 
-             // Always use the closest GOP we found if there is more than
 
-             // one candidate
 
-             if (!nearestGopObj || nearestDistance > dtsDistance) {
 
-               nearestGopObj = currentGopObj;
 
-               nearestDistance = dtsDistance;
 
-             }
 
-           }
 
-         }
 
-         if (nearestGopObj) {
 
-           return nearestGopObj.gop;
 
-         }
 
-         return null;
 
-       }; // trim gop list to the first gop found that has a matching pts with a gop in the list
 
-       // of gopsToAlignWith starting from the START of the list
 
-       this.alignGopsAtStart_ = function (gops) {
 
-         var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
 
-         byteLength = gops.byteLength;
 
-         nalCount = gops.nalCount;
 
-         duration = gops.duration;
 
-         alignIndex = gopIndex = 0;
 
-         while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
 
-           align = gopsToAlignWith[alignIndex];
 
-           gop = gops[gopIndex];
 
-           if (align.pts === gop.pts) {
 
-             break;
 
-           }
 
-           if (gop.pts > align.pts) {
 
-             // this current gop starts after the current gop we want to align on, so increment
 
-             // align index
 
-             alignIndex++;
 
-             continue;
 
-           } // current gop starts before the current gop we want to align on. so increment gop
 
-           // index
 
-           gopIndex++;
 
-           byteLength -= gop.byteLength;
 
-           nalCount -= gop.nalCount;
 
-           duration -= gop.duration;
 
-         }
 
-         if (gopIndex === 0) {
 
-           // no gops to trim
 
-           return gops;
 
-         }
 
-         if (gopIndex === gops.length) {
 
-           // all gops trimmed, skip appending all gops
 
-           return null;
 
-         }
 
-         alignedGops = gops.slice(gopIndex);
 
-         alignedGops.byteLength = byteLength;
 
-         alignedGops.duration = duration;
 
-         alignedGops.nalCount = nalCount;
 
-         alignedGops.pts = alignedGops[0].pts;
 
-         alignedGops.dts = alignedGops[0].dts;
 
-         return alignedGops;
 
-       }; // trim gop list to the first gop found that has a matching pts with a gop in the list
 
-       // of gopsToAlignWith starting from the END of the list
 
-       this.alignGopsAtEnd_ = function (gops) {
 
-         var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
 
-         alignIndex = gopsToAlignWith.length - 1;
 
-         gopIndex = gops.length - 1;
 
-         alignEndIndex = null;
 
-         matchFound = false;
 
-         while (alignIndex >= 0 && gopIndex >= 0) {
 
-           align = gopsToAlignWith[alignIndex];
 
-           gop = gops[gopIndex];
 
-           if (align.pts === gop.pts) {
 
-             matchFound = true;
 
-             break;
 
-           }
 
-           if (align.pts > gop.pts) {
 
-             alignIndex--;
 
-             continue;
 
-           }
 
-           if (alignIndex === gopsToAlignWith.length - 1) {
 
-             // gop.pts is greater than the last alignment candidate. If no match is found
 
-             // by the end of this loop, we still want to append gops that come after this
 
-             // point
 
-             alignEndIndex = gopIndex;
 
-           }
 
-           gopIndex--;
 
-         }
 
-         if (!matchFound && alignEndIndex === null) {
 
-           return null;
 
-         }
 
-         var trimIndex;
 
-         if (matchFound) {
 
-           trimIndex = gopIndex;
 
-         } else {
 
-           trimIndex = alignEndIndex;
 
-         }
 
-         if (trimIndex === 0) {
 
-           return gops;
 
-         }
 
-         var alignedGops = gops.slice(trimIndex);
 
-         var metadata = alignedGops.reduce(function (total, gop) {
 
-           total.byteLength += gop.byteLength;
 
-           total.duration += gop.duration;
 
-           total.nalCount += gop.nalCount;
 
-           return total;
 
-         }, {
 
-           byteLength: 0,
 
-           duration: 0,
 
-           nalCount: 0
 
-         });
 
-         alignedGops.byteLength = metadata.byteLength;
 
-         alignedGops.duration = metadata.duration;
 
-         alignedGops.nalCount = metadata.nalCount;
 
-         alignedGops.pts = alignedGops[0].pts;
 
-         alignedGops.dts = alignedGops[0].dts;
 
-         return alignedGops;
 
-       };
 
-       this.alignGopsWith = function (newGopsToAlignWith) {
 
-         gopsToAlignWith = newGopsToAlignWith;
 
-       };
 
-     };
 
-     VideoSegmentStream.prototype = new Stream();
 
-     /**
 
-      * A Stream that can combine multiple streams (ie. audio & video)
 
-      * into a single output segment for MSE. Also supports audio-only
 
-      * and video-only streams.
 
-      * @param options {object} transmuxer options object
 
-      * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
 
-      *        in the source; false to adjust the first segment to start at media timeline start.
 
-      */
 
-     CoalesceStream = function (options, metadataStream) {
 
-       // Number of Tracks per output segment
 
-       // If greater than 1, we combine multiple
 
-       // tracks into a single segment
 
-       this.numberOfTracks = 0;
 
-       this.metadataStream = metadataStream;
 
-       options = options || {};
 
-       if (typeof options.remux !== 'undefined') {
 
-         this.remuxTracks = !!options.remux;
 
-       } else {
 
-         this.remuxTracks = true;
 
-       }
 
-       if (typeof options.keepOriginalTimestamps === 'boolean') {
 
-         this.keepOriginalTimestamps = options.keepOriginalTimestamps;
 
-       } else {
 
-         this.keepOriginalTimestamps = false;
 
-       }
 
-       this.pendingTracks = [];
 
-       this.videoTrack = null;
 
-       this.pendingBoxes = [];
 
-       this.pendingCaptions = [];
 
-       this.pendingMetadata = [];
 
-       this.pendingBytes = 0;
 
-       this.emittedTracks = 0;
 
-       CoalesceStream.prototype.init.call(this); // Take output from multiple
 
-       this.push = function (output) {
 
-         // buffer incoming captions until the associated video segment
 
-         // finishes
 
-         if (output.text) {
 
-           return this.pendingCaptions.push(output);
 
-         } // buffer incoming id3 tags until the final flush
 
-         if (output.frames) {
 
-           return this.pendingMetadata.push(output);
 
-         } // Add this track to the list of pending tracks and store
 
-         // important information required for the construction of
 
-         // the final segment
 
-         this.pendingTracks.push(output.track);
 
-         this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome?
 
-         // We unshift audio and push video because
 
-         // as of Chrome 75 when switching from
 
-         // one init segment to another if the video
 
-         // mdat does not appear after the audio mdat
 
-         // only audio will play for the duration of our transmux.
 
-         if (output.track.type === 'video') {
 
-           this.videoTrack = output.track;
 
-           this.pendingBoxes.push(output.boxes);
 
-         }
 
-         if (output.track.type === 'audio') {
 
-           this.audioTrack = output.track;
 
-           this.pendingBoxes.unshift(output.boxes);
 
-         }
 
-       };
 
-     };
 
-     CoalesceStream.prototype = new Stream();
 
-     CoalesceStream.prototype.flush = function (flushSource) {
 
-       var offset = 0,
 
-         event = {
 
-           captions: [],
 
-           captionStreams: {},
 
-           metadata: [],
 
-           info: {}
 
-         },
 
-         caption,
 
-         id3,
 
-         initSegment,
 
-         timelineStartPts = 0,
 
-         i;
 
-       if (this.pendingTracks.length < this.numberOfTracks) {
 
-         if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
 
-           // Return because we haven't received a flush from a data-generating
 
-           // portion of the segment (meaning that we have only recieved meta-data
 
-           // or captions.)
 
-           return;
 
-         } else if (this.remuxTracks) {
 
-           // Return until we have enough tracks from the pipeline to remux (if we
 
-           // are remuxing audio and video into a single MP4)
 
-           return;
 
-         } else if (this.pendingTracks.length === 0) {
 
-           // In the case where we receive a flush without any data having been
 
-           // received we consider it an emitted track for the purposes of coalescing
 
-           // `done` events.
 
-           // We do this for the case where there is an audio and video track in the
 
-           // segment but no audio data. (seen in several playlists with alternate
 
-           // audio tracks and no audio present in the main TS segments.)
 
-           this.emittedTracks++;
 
-           if (this.emittedTracks >= this.numberOfTracks) {
 
-             this.trigger('done');
 
-             this.emittedTracks = 0;
 
-           }
 
-           return;
 
-         }
 
-       }
 
-       if (this.videoTrack) {
 
-         timelineStartPts = this.videoTrack.timelineStartInfo.pts;
 
-         VIDEO_PROPERTIES.forEach(function (prop) {
 
-           event.info[prop] = this.videoTrack[prop];
 
-         }, this);
 
-       } else if (this.audioTrack) {
 
-         timelineStartPts = this.audioTrack.timelineStartInfo.pts;
 
-         AUDIO_PROPERTIES.forEach(function (prop) {
 
-           event.info[prop] = this.audioTrack[prop];
 
-         }, this);
 
-       }
 
-       if (this.videoTrack || this.audioTrack) {
 
-         if (this.pendingTracks.length === 1) {
 
-           event.type = this.pendingTracks[0].type;
 
-         } else {
 
-           event.type = 'combined';
 
-         }
 
-         this.emittedTracks += this.pendingTracks.length;
 
-         initSegment = mp4.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
 
-         event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
 
-         // and track definitions
 
-         event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
 
-         event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
 
-         for (i = 0; i < this.pendingBoxes.length; i++) {
 
-           event.data.set(this.pendingBoxes[i], offset);
 
-           offset += this.pendingBoxes[i].byteLength;
 
-         } // Translate caption PTS times into second offsets to match the
 
-         // video timeline for the segment, and add track info
 
-         for (i = 0; i < this.pendingCaptions.length; i++) {
 
-           caption = this.pendingCaptions[i];
 
-           caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps);
 
-           caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps);
 
-           event.captionStreams[caption.stream] = true;
 
-           event.captions.push(caption);
 
-         } // Translate ID3 frame PTS times into second offsets to match the
 
-         // video timeline for the segment
 
-         for (i = 0; i < this.pendingMetadata.length; i++) {
 
-           id3 = this.pendingMetadata[i];
 
-           id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps);
 
-           event.metadata.push(id3);
 
-         } // We add this to every single emitted segment even though we only need
 
-         // it for the first
 
-         event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
 
-         this.pendingTracks.length = 0;
 
-         this.videoTrack = null;
 
-         this.pendingBoxes.length = 0;
 
-         this.pendingCaptions.length = 0;
 
-         this.pendingBytes = 0;
 
-         this.pendingMetadata.length = 0; // Emit the built segment
 
-         // We include captions and ID3 tags for backwards compatibility,
 
-         // ideally we should send only video and audio in the data event
 
-         this.trigger('data', event); // Emit each caption to the outside world
 
-         // Ideally, this would happen immediately on parsing captions,
 
-         // but we need to ensure that video data is sent back first
 
-         // so that caption timing can be adjusted to match video timing
 
-         for (i = 0; i < event.captions.length; i++) {
 
-           caption = event.captions[i];
 
-           this.trigger('caption', caption);
 
-         } // Emit each id3 tag to the outside world
 
-         // Ideally, this would happen immediately on parsing the tag,
 
-         // but we need to ensure that video data is sent back first
 
-         // so that ID3 frame timing can be adjusted to match video timing
 
-         for (i = 0; i < event.metadata.length; i++) {
 
-           id3 = event.metadata[i];
 
-           this.trigger('id3Frame', id3);
 
-         }
 
-       } // Only emit `done` if all tracks have been flushed and emitted
 
-       if (this.emittedTracks >= this.numberOfTracks) {
 
-         this.trigger('done');
 
-         this.emittedTracks = 0;
 
-       }
 
-     };
 
-     CoalesceStream.prototype.setRemux = function (val) {
 
-       this.remuxTracks = val;
 
-     };
 
-     /**
 
-      * A Stream that expects MP2T binary data as input and produces
 
-      * corresponding media segments, suitable for use with Media Source
 
-      * Extension (MSE) implementations that support the ISO BMFF byte
 
-      * stream format, like Chrome.
 
-      */
 
-     Transmuxer = function (options) {
 
-       var self = this,
 
-         hasFlushed = true,
 
-         videoTrack,
 
-         audioTrack;
 
-       Transmuxer.prototype.init.call(this);
 
-       options = options || {};
 
-       this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
 
-       this.transmuxPipeline_ = {};
 
-       this.setupAacPipeline = function () {
 
-         var pipeline = {};
 
-         this.transmuxPipeline_ = pipeline;
 
-         pipeline.type = 'aac';
 
-         pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
 
-         pipeline.aacStream = new AacStream();
 
-         pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio');
 
-         pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata');
 
-         pipeline.adtsStream = new AdtsStream();
 
-         pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
 
-         pipeline.headOfPipeline = pipeline.aacStream;
 
-         pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
 
-         pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
 
-         pipeline.metadataStream.on('timestamp', function (frame) {
 
-           pipeline.aacStream.setTimestamp(frame.timeStamp);
 
-         });
 
-         pipeline.aacStream.on('data', function (data) {
 
-           if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) {
 
-             return;
 
-           }
 
-           audioTrack = audioTrack || {
 
-             timelineStartInfo: {
 
-               baseMediaDecodeTime: self.baseMediaDecodeTime
 
-             },
 
-             codec: 'adts',
 
-             type: 'audio'
 
-           }; // hook up the audio segment stream to the first track with aac data
 
-           pipeline.coalesceStream.numberOfTracks++;
 
-           pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
 
-           pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
 
-           pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
 
-           pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info
 
-           self.trigger('trackinfo', {
 
-             hasAudio: !!audioTrack,
 
-             hasVideo: !!videoTrack
 
-           });
 
-         }); // Re-emit any data coming from the coalesce stream to the outside world
 
-         pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
 
-         pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
 
-         addPipelineLogRetriggers(this, pipeline);
 
-       };
 
-       this.setupTsPipeline = function () {
 
-         var pipeline = {};
 
-         this.transmuxPipeline_ = pipeline;
 
-         pipeline.type = 'ts';
 
-         pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
 
-         pipeline.packetStream = new m2ts.TransportPacketStream();
 
-         pipeline.parseStream = new m2ts.TransportParseStream();
 
-         pipeline.elementaryStream = new m2ts.ElementaryStream();
 
-         pipeline.timestampRolloverStream = new m2ts.TimestampRolloverStream();
 
-         pipeline.adtsStream = new AdtsStream();
 
-         pipeline.h264Stream = new H264Stream();
 
-         pipeline.captionStream = new m2ts.CaptionStream(options);
 
-         pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
 
-         pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
 
-         pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!!
 
-         // demux the streams
 
-         pipeline.timestampRolloverStream.pipe(pipeline.h264Stream);
 
-         pipeline.timestampRolloverStream.pipe(pipeline.adtsStream);
 
-         pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
 
-         pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
 
-         pipeline.elementaryStream.on('data', function (data) {
 
-           var i;
 
-           if (data.type === 'metadata') {
 
-             i = data.tracks.length; // scan the tracks listed in the metadata
 
-             while (i--) {
 
-               if (!videoTrack && data.tracks[i].type === 'video') {
 
-                 videoTrack = data.tracks[i];
 
-                 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
 
-               } else if (!audioTrack && data.tracks[i].type === 'audio') {
 
-                 audioTrack = data.tracks[i];
 
-                 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
 
-               }
 
-             } // hook up the video segment stream to the first track with h264 data
 
-             if (videoTrack && !pipeline.videoSegmentStream) {
 
-               pipeline.coalesceStream.numberOfTracks++;
 
-               pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack, options);
 
-               pipeline.videoSegmentStream.on('log', self.getLogTrigger_('videoSegmentStream'));
 
-               pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
 
-                 // When video emits timelineStartInfo data after a flush, we forward that
 
-                 // info to the AudioSegmentStream, if it exists, because video timeline
 
-                 // data takes precedence.  Do not do this if keepOriginalTimestamps is set,
 
-                 // because this is a particularly subtle form of timestamp alteration.
 
-                 if (audioTrack && !options.keepOriginalTimestamps) {
 
-                   audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
 
-                   // very earliest DTS we have seen in video because Chrome will
 
-                   // interpret any video track with a baseMediaDecodeTime that is
 
-                   // non-zero as a gap.
 
-                   pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime);
 
-                 }
 
-               });
 
-               pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
 
-               pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
 
-               pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
 
-                 if (audioTrack) {
 
-                   pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
 
-                 }
 
-               });
 
-               pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline
 
-               pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
 
-             }
 
-             if (audioTrack && !pipeline.audioSegmentStream) {
 
-               // hook up the audio segment stream to the first track with aac data
 
-               pipeline.coalesceStream.numberOfTracks++;
 
-               pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
 
-               pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
 
-               pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo'));
 
-               pipeline.audioSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'audioSegmentTimingInfo')); // Set up the final part of the audio pipeline
 
-               pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
 
-             } // emit pmt info
 
-             self.trigger('trackinfo', {
 
-               hasAudio: !!audioTrack,
 
-               hasVideo: !!videoTrack
 
-             });
 
-           }
 
-         }); // Re-emit any data coming from the coalesce stream to the outside world
 
-         pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
 
-         pipeline.coalesceStream.on('id3Frame', function (id3Frame) {
 
-           id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
 
-           self.trigger('id3Frame', id3Frame);
 
-         });
 
-         pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline
 
-         pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
 
-         addPipelineLogRetriggers(this, pipeline);
 
-       }; // hook up the segment streams once track metadata is delivered
 
-       this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
 
-         var pipeline = this.transmuxPipeline_;
 
-         if (!options.keepOriginalTimestamps) {
 
-           this.baseMediaDecodeTime = baseMediaDecodeTime;
 
-         }
 
-         if (audioTrack) {
 
-           audioTrack.timelineStartInfo.dts = undefined;
 
-           audioTrack.timelineStartInfo.pts = undefined;
 
-           trackDecodeInfo.clearDtsInfo(audioTrack);
 
-           if (pipeline.audioTimestampRolloverStream) {
 
-             pipeline.audioTimestampRolloverStream.discontinuity();
 
-           }
 
-         }
 
-         if (videoTrack) {
 
-           if (pipeline.videoSegmentStream) {
 
-             pipeline.videoSegmentStream.gopCache_ = [];
 
-           }
 
-           videoTrack.timelineStartInfo.dts = undefined;
 
-           videoTrack.timelineStartInfo.pts = undefined;
 
-           trackDecodeInfo.clearDtsInfo(videoTrack);
 
-           pipeline.captionStream.reset();
 
-         }
 
-         if (pipeline.timestampRolloverStream) {
 
-           pipeline.timestampRolloverStream.discontinuity();
 
-         }
 
-       };
 
-       this.setAudioAppendStart = function (timestamp) {
 
-         if (audioTrack) {
 
-           this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
 
-         }
 
-       };
 
-       this.setRemux = function (val) {
 
-         var pipeline = this.transmuxPipeline_;
 
-         options.remux = val;
 
-         if (pipeline && pipeline.coalesceStream) {
 
-           pipeline.coalesceStream.setRemux(val);
 
-         }
 
-       };
 
-       this.alignGopsWith = function (gopsToAlignWith) {
 
-         if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
 
-           this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
 
-         }
 
-       };
 
-       this.getLogTrigger_ = function (key) {
 
-         var self = this;
 
-         return function (event) {
 
-           event.stream = key;
 
-           self.trigger('log', event);
 
-         };
 
-       }; // feed incoming data to the front of the parsing pipeline
 
-       this.push = function (data) {
 
-         if (hasFlushed) {
 
-           var isAac = isLikelyAacData(data);
 
-           if (isAac && this.transmuxPipeline_.type !== 'aac') {
 
-             this.setupAacPipeline();
 
-           } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
 
-             this.setupTsPipeline();
 
-           }
 
-           hasFlushed = false;
 
-         }
 
-         this.transmuxPipeline_.headOfPipeline.push(data);
 
-       }; // flush any buffered data
 
-       this.flush = function () {
 
-         hasFlushed = true; // Start at the top of the pipeline and flush all pending work
 
-         this.transmuxPipeline_.headOfPipeline.flush();
 
-       };
 
-       this.endTimeline = function () {
 
-         this.transmuxPipeline_.headOfPipeline.endTimeline();
 
-       };
 
-       this.reset = function () {
 
-         if (this.transmuxPipeline_.headOfPipeline) {
 
-           this.transmuxPipeline_.headOfPipeline.reset();
 
-         }
 
-       }; // Caption data has to be reset when seeking outside buffered range
 
-       this.resetCaptions = function () {
 
-         if (this.transmuxPipeline_.captionStream) {
 
-           this.transmuxPipeline_.captionStream.reset();
 
-         }
 
-       };
 
-     };
 
-     Transmuxer.prototype = new Stream();
 
-     var transmuxer = {
 
-       Transmuxer: Transmuxer,
 
-       VideoSegmentStream: VideoSegmentStream,
 
-       AudioSegmentStream: AudioSegmentStream,
 
-       AUDIO_PROPERTIES: AUDIO_PROPERTIES,
 
-       VIDEO_PROPERTIES: VIDEO_PROPERTIES,
 
-       // exported for testing
 
-       generateSegmentTimingInfo: generateSegmentTimingInfo
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      */
 
-     var toUnsigned$3 = function (value) {
 
-       return value >>> 0;
 
-     };
 
-     var toHexString$1 = function (value) {
 
-       return ('00' + value.toString(16)).slice(-2);
 
-     };
 
-     var bin = {
 
-       toUnsigned: toUnsigned$3,
 
-       toHexString: toHexString$1
 
-     };
 
-     var parseType$3 = function (buffer) {
 
-       var result = '';
 
-       result += String.fromCharCode(buffer[0]);
 
-       result += String.fromCharCode(buffer[1]);
 
-       result += String.fromCharCode(buffer[2]);
 
-       result += String.fromCharCode(buffer[3]);
 
-       return result;
 
-     };
 
-     var parseType_1 = parseType$3;
 
-     var toUnsigned$2 = bin.toUnsigned;
 
-     var parseType$2 = parseType_1;
 
-     var findBox$2 = function (data, path) {
 
-       var results = [],
 
-         i,
 
-         size,
 
-         type,
 
-         end,
 
-         subresults;
 
-       if (!path.length) {
 
-         // short-circuit the search for empty paths
 
-         return null;
 
-       }
 
-       for (i = 0; i < data.byteLength;) {
 
-         size = toUnsigned$2(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
 
-         type = parseType$2(data.subarray(i + 4, i + 8));
 
-         end = size > 1 ? i + size : data.byteLength;
 
-         if (type === path[0]) {
 
-           if (path.length === 1) {
 
-             // this is the end of the path and we've found the box we were
 
-             // looking for
 
-             results.push(data.subarray(i + 8, end));
 
-           } else {
 
-             // recursively search for the next box along the path
 
-             subresults = findBox$2(data.subarray(i + 8, end), path.slice(1));
 
-             if (subresults.length) {
 
-               results = results.concat(subresults);
 
-             }
 
-           }
 
-         }
 
-         i = end;
 
-       } // we've finished searching all of data
 
-       return results;
 
-     };
 
-     var findBox_1 = findBox$2;
 
-     var toUnsigned$1 = bin.toUnsigned;
 
-     var getUint64$2 = numbers.getUint64;
 
-     var tfdt = function (data) {
 
-       var result = {
 
-         version: data[0],
 
-         flags: new Uint8Array(data.subarray(1, 4))
 
-       };
 
-       if (result.version === 1) {
 
-         result.baseMediaDecodeTime = getUint64$2(data.subarray(4));
 
-       } else {
 
-         result.baseMediaDecodeTime = toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]);
 
-       }
 
-       return result;
 
-     };
 
-     var parseTfdt$2 = tfdt;
 
-     var parseSampleFlags$1 = function (flags) {
 
-       return {
 
-         isLeading: (flags[0] & 0x0c) >>> 2,
 
-         dependsOn: flags[0] & 0x03,
 
-         isDependedOn: (flags[1] & 0xc0) >>> 6,
 
-         hasRedundancy: (flags[1] & 0x30) >>> 4,
 
-         paddingValue: (flags[1] & 0x0e) >>> 1,
 
-         isNonSyncSample: flags[1] & 0x01,
 
-         degradationPriority: flags[2] << 8 | flags[3]
 
-       };
 
-     };
 
-     var parseSampleFlags_1 = parseSampleFlags$1;
 
-     var parseSampleFlags = parseSampleFlags_1;
 
-     var trun = function (data) {
 
-       var result = {
 
-           version: data[0],
 
-           flags: new Uint8Array(data.subarray(1, 4)),
 
-           samples: []
 
-         },
 
-         view = new DataView(data.buffer, data.byteOffset, data.byteLength),
 
-         // Flag interpretation
 
-         dataOffsetPresent = result.flags[2] & 0x01,
 
-         // compare with 2nd byte of 0x1
 
-         firstSampleFlagsPresent = result.flags[2] & 0x04,
 
-         // compare with 2nd byte of 0x4
 
-         sampleDurationPresent = result.flags[1] & 0x01,
 
-         // compare with 2nd byte of 0x100
 
-         sampleSizePresent = result.flags[1] & 0x02,
 
-         // compare with 2nd byte of 0x200
 
-         sampleFlagsPresent = result.flags[1] & 0x04,
 
-         // compare with 2nd byte of 0x400
 
-         sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
 
-         // compare with 2nd byte of 0x800
 
-         sampleCount = view.getUint32(4),
 
-         offset = 8,
 
-         sample;
 
-       if (dataOffsetPresent) {
 
-         // 32 bit signed integer
 
-         result.dataOffset = view.getInt32(offset);
 
-         offset += 4;
 
-       } // Overrides the flags for the first sample only. The order of
 
-       // optional values will be: duration, size, compositionTimeOffset
 
-       if (firstSampleFlagsPresent && sampleCount) {
 
-         sample = {
 
-           flags: parseSampleFlags(data.subarray(offset, offset + 4))
 
-         };
 
-         offset += 4;
 
-         if (sampleDurationPresent) {
 
-           sample.duration = view.getUint32(offset);
 
-           offset += 4;
 
-         }
 
-         if (sampleSizePresent) {
 
-           sample.size = view.getUint32(offset);
 
-           offset += 4;
 
-         }
 
-         if (sampleCompositionTimeOffsetPresent) {
 
-           if (result.version === 1) {
 
-             sample.compositionTimeOffset = view.getInt32(offset);
 
-           } else {
 
-             sample.compositionTimeOffset = view.getUint32(offset);
 
-           }
 
-           offset += 4;
 
-         }
 
-         result.samples.push(sample);
 
-         sampleCount--;
 
-       }
 
-       while (sampleCount--) {
 
-         sample = {};
 
-         if (sampleDurationPresent) {
 
-           sample.duration = view.getUint32(offset);
 
-           offset += 4;
 
-         }
 
-         if (sampleSizePresent) {
 
-           sample.size = view.getUint32(offset);
 
-           offset += 4;
 
-         }
 
-         if (sampleFlagsPresent) {
 
-           sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
 
-           offset += 4;
 
-         }
 
-         if (sampleCompositionTimeOffsetPresent) {
 
-           if (result.version === 1) {
 
-             sample.compositionTimeOffset = view.getInt32(offset);
 
-           } else {
 
-             sample.compositionTimeOffset = view.getUint32(offset);
 
-           }
 
-           offset += 4;
 
-         }
 
-         result.samples.push(sample);
 
-       }
 
-       return result;
 
-     };
 
-     var parseTrun$2 = trun;
 
-     var tfhd = function (data) {
 
-       var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
 
-         result = {
 
-           version: data[0],
 
-           flags: new Uint8Array(data.subarray(1, 4)),
 
-           trackId: view.getUint32(4)
 
-         },
 
-         baseDataOffsetPresent = result.flags[2] & 0x01,
 
-         sampleDescriptionIndexPresent = result.flags[2] & 0x02,
 
-         defaultSampleDurationPresent = result.flags[2] & 0x08,
 
-         defaultSampleSizePresent = result.flags[2] & 0x10,
 
-         defaultSampleFlagsPresent = result.flags[2] & 0x20,
 
-         durationIsEmpty = result.flags[0] & 0x010000,
 
-         defaultBaseIsMoof = result.flags[0] & 0x020000,
 
-         i;
 
-       i = 8;
 
-       if (baseDataOffsetPresent) {
 
-         i += 4; // truncate top 4 bytes
 
-         // FIXME: should we read the full 64 bits?
 
-         result.baseDataOffset = view.getUint32(12);
 
-         i += 4;
 
-       }
 
-       if (sampleDescriptionIndexPresent) {
 
-         result.sampleDescriptionIndex = view.getUint32(i);
 
-         i += 4;
 
-       }
 
-       if (defaultSampleDurationPresent) {
 
-         result.defaultSampleDuration = view.getUint32(i);
 
-         i += 4;
 
-       }
 
-       if (defaultSampleSizePresent) {
 
-         result.defaultSampleSize = view.getUint32(i);
 
-         i += 4;
 
-       }
 
-       if (defaultSampleFlagsPresent) {
 
-         result.defaultSampleFlags = view.getUint32(i);
 
-       }
 
-       if (durationIsEmpty) {
 
-         result.durationIsEmpty = true;
 
-       }
 
-       if (!baseDataOffsetPresent && defaultBaseIsMoof) {
 
-         result.baseDataOffsetIsMoof = true;
 
-       }
 
-       return result;
 
-     };
 
-     var parseTfhd$2 = tfhd;
 
-     var win;
 
-     if (typeof window !== "undefined") {
 
-       win = window;
 
-     } else if (typeof commonjsGlobal !== "undefined") {
 
-       win = commonjsGlobal;
 
-     } else if (typeof self !== "undefined") {
 
-       win = self;
 
-     } else {
 
-       win = {};
 
-     }
 
-     var window_1 = win;
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Reads in-band CEA-708 captions out of FMP4 segments.
 
-      * @see https://en.wikipedia.org/wiki/CEA-708
 
-      */
 
-     var discardEmulationPreventionBytes = captionPacketParser.discardEmulationPreventionBytes;
 
-     var CaptionStream = captionStream.CaptionStream;
 
-     var findBox$1 = findBox_1;
 
-     var parseTfdt$1 = parseTfdt$2;
 
-     var parseTrun$1 = parseTrun$2;
 
-     var parseTfhd$1 = parseTfhd$2;
 
-     var window$2 = window_1;
 
-     /**
 
-       * Maps an offset in the mdat to a sample based on the the size of the samples.
 
-       * Assumes that `parseSamples` has been called first.
 
-       *
 
-       * @param {Number} offset - The offset into the mdat
 
-       * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
 
-       * @return {?Object} The matching sample, or null if no match was found.
 
-       *
 
-       * @see ISO-BMFF-12/2015, Section 8.8.8
 
-      **/
 
-     var mapToSample = function (offset, samples) {
 
-       var approximateOffset = offset;
 
-       for (var i = 0; i < samples.length; i++) {
 
-         var sample = samples[i];
 
-         if (approximateOffset < sample.size) {
 
-           return sample;
 
-         }
 
-         approximateOffset -= sample.size;
 
-       }
 
-       return null;
 
-     };
 
-     /**
 
-       * Finds SEI nal units contained in a Media Data Box.
 
-       * Assumes that `parseSamples` has been called first.
 
-       *
 
-       * @param {Uint8Array} avcStream - The bytes of the mdat
 
-       * @param {Object[]} samples - The samples parsed out by `parseSamples`
 
-       * @param {Number} trackId - The trackId of this video track
 
-       * @return {Object[]} seiNals - the parsed SEI NALUs found.
 
-       *   The contents of the seiNal should match what is expected by
 
-       *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
 
-       *
 
-       * @see ISO-BMFF-12/2015, Section 8.1.1
 
-       * @see Rec. ITU-T H.264, 7.3.2.3.1
 
-      **/
 
-     var findSeiNals = function (avcStream, samples, trackId) {
 
-       var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
 
-         result = {
 
-           logs: [],
 
-           seiNals: []
 
-         },
 
-         seiNal,
 
-         i,
 
-         length,
 
-         lastMatchedSample;
 
-       for (i = 0; i + 4 < avcStream.length; i += length) {
 
-         length = avcView.getUint32(i);
 
-         i += 4; // Bail if this doesn't appear to be an H264 stream
 
-         if (length <= 0) {
 
-           continue;
 
-         }
 
-         switch (avcStream[i] & 0x1F) {
 
-           case 0x06:
 
-             var data = avcStream.subarray(i + 1, i + 1 + length);
 
-             var matchingSample = mapToSample(i, samples);
 
-             seiNal = {
 
-               nalUnitType: 'sei_rbsp',
 
-               size: length,
 
-               data: data,
 
-               escapedRBSP: discardEmulationPreventionBytes(data),
 
-               trackId: trackId
 
-             };
 
-             if (matchingSample) {
 
-               seiNal.pts = matchingSample.pts;
 
-               seiNal.dts = matchingSample.dts;
 
-               lastMatchedSample = matchingSample;
 
-             } else if (lastMatchedSample) {
 
-               // If a matching sample cannot be found, use the last
 
-               // sample's values as they should be as close as possible
 
-               seiNal.pts = lastMatchedSample.pts;
 
-               seiNal.dts = lastMatchedSample.dts;
 
-             } else {
 
-               result.logs.push({
 
-                 level: 'warn',
 
-                 message: 'We\'ve encountered a nal unit without data at ' + i + ' for trackId ' + trackId + '. See mux.js#223.'
 
-               });
 
-               break;
 
-             }
 
-             result.seiNals.push(seiNal);
 
-             break;
 
-         }
 
-       }
 
-       return result;
 
-     };
 
-     /**
 
-       * Parses sample information out of Track Run Boxes and calculates
 
-       * the absolute presentation and decode timestamps of each sample.
 
-       *
 
-       * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
 
-       * @param {Number|BigInt} baseMediaDecodeTime - base media decode time from tfdt
 
-           @see ISO-BMFF-12/2015, Section 8.8.12
 
-       * @param {Object} tfhd - The parsed Track Fragment Header
 
-       *   @see inspect.parseTfhd
 
-       * @return {Object[]} the parsed samples
 
-       *
 
-       * @see ISO-BMFF-12/2015, Section 8.8.8
 
-      **/
 
-     var parseSamples = function (truns, baseMediaDecodeTime, tfhd) {
 
-       var currentDts = baseMediaDecodeTime;
 
-       var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
 
-       var defaultSampleSize = tfhd.defaultSampleSize || 0;
 
-       var trackId = tfhd.trackId;
 
-       var allSamples = [];
 
-       truns.forEach(function (trun) {
 
-         // Note: We currently do not parse the sample table as well
 
-         // as the trun. It's possible some sources will require this.
 
-         // moov > trak > mdia > minf > stbl
 
-         var trackRun = parseTrun$1(trun);
 
-         var samples = trackRun.samples;
 
-         samples.forEach(function (sample) {
 
-           if (sample.duration === undefined) {
 
-             sample.duration = defaultSampleDuration;
 
-           }
 
-           if (sample.size === undefined) {
 
-             sample.size = defaultSampleSize;
 
-           }
 
-           sample.trackId = trackId;
 
-           sample.dts = currentDts;
 
-           if (sample.compositionTimeOffset === undefined) {
 
-             sample.compositionTimeOffset = 0;
 
-           }
 
-           if (typeof currentDts === 'bigint') {
 
-             sample.pts = currentDts + window$2.BigInt(sample.compositionTimeOffset);
 
-             currentDts += window$2.BigInt(sample.duration);
 
-           } else {
 
-             sample.pts = currentDts + sample.compositionTimeOffset;
 
-             currentDts += sample.duration;
 
-           }
 
-         });
 
-         allSamples = allSamples.concat(samples);
 
-       });
 
-       return allSamples;
 
-     };
 
-     /**
 
-       * Parses out caption nals from an FMP4 segment's video tracks.
 
-       *
 
-       * @param {Uint8Array} segment - The bytes of a single segment
 
-       * @param {Number} videoTrackId - The trackId of a video track in the segment
 
-       * @return {Object.<Number, Object[]>} A mapping of video trackId to
 
-       *   a list of seiNals found in that track
 
-      **/
 
-     var parseCaptionNals = function (segment, videoTrackId) {
 
-       // To get the samples
 
-       var trafs = findBox$1(segment, ['moof', 'traf']); // To get SEI NAL units
 
-       var mdats = findBox$1(segment, ['mdat']);
 
-       var captionNals = {};
 
-       var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
 
-       mdats.forEach(function (mdat, index) {
 
-         var matchingTraf = trafs[index];
 
-         mdatTrafPairs.push({
 
-           mdat: mdat,
 
-           traf: matchingTraf
 
-         });
 
-       });
 
-       mdatTrafPairs.forEach(function (pair) {
 
-         var mdat = pair.mdat;
 
-         var traf = pair.traf;
 
-         var tfhd = findBox$1(traf, ['tfhd']); // Exactly 1 tfhd per traf
 
-         var headerInfo = parseTfhd$1(tfhd[0]);
 
-         var trackId = headerInfo.trackId;
 
-         var tfdt = findBox$1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
 
-         var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt$1(tfdt[0]).baseMediaDecodeTime : 0;
 
-         var truns = findBox$1(traf, ['trun']);
 
-         var samples;
 
-         var result; // Only parse video data for the chosen video track
 
-         if (videoTrackId === trackId && truns.length > 0) {
 
-           samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
 
-           result = findSeiNals(mdat, samples, trackId);
 
-           if (!captionNals[trackId]) {
 
-             captionNals[trackId] = {
 
-               seiNals: [],
 
-               logs: []
 
-             };
 
-           }
 
-           captionNals[trackId].seiNals = captionNals[trackId].seiNals.concat(result.seiNals);
 
-           captionNals[trackId].logs = captionNals[trackId].logs.concat(result.logs);
 
-         }
 
-       });
 
-       return captionNals;
 
-     };
 
-     /**
 
-       * Parses out inband captions from an MP4 container and returns
 
-       * caption objects that can be used by WebVTT and the TextTrack API.
 
-       * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
 
-       * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
 
-       * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
 
-       *
 
-       * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
 
-       * @param {Number} trackId - The id of the video track to parse
 
-       * @param {Number} timescale - The timescale for the video track from the init segment
 
-       *
 
-       * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
 
-       * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
 
-       * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
 
-       * @return {String} parsedCaptions[].text - The visible content of the caption
 
-      **/
 
-     var parseEmbeddedCaptions = function (segment, trackId, timescale) {
 
-       var captionNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
 
-       if (trackId === null) {
 
-         return null;
 
-       }
 
-       captionNals = parseCaptionNals(segment, trackId);
 
-       var trackNals = captionNals[trackId] || {};
 
-       return {
 
-         seiNals: trackNals.seiNals,
 
-         logs: trackNals.logs,
 
-         timescale: timescale
 
-       };
 
-     };
 
-     /**
 
-       * Converts SEI NALUs into captions that can be used by video.js
 
-      **/
 
-     var CaptionParser = function () {
 
-       var isInitialized = false;
 
-       var captionStream; // Stores segments seen before trackId and timescale are set
 
-       var segmentCache; // Stores video track ID of the track being parsed
 
-       var trackId; // Stores the timescale of the track being parsed
 
-       var timescale; // Stores captions parsed so far
 
-       var parsedCaptions; // Stores whether we are receiving partial data or not
 
-       var parsingPartial;
 
-       /**
 
-         * A method to indicate whether a CaptionParser has been initalized
 
-         * @returns {Boolean}
 
-        **/
 
-       this.isInitialized = function () {
 
-         return isInitialized;
 
-       };
 
-       /**
 
-         * Initializes the underlying CaptionStream, SEI NAL parsing
 
-         * and management, and caption collection
 
-        **/
 
-       this.init = function (options) {
 
-         captionStream = new CaptionStream();
 
-         isInitialized = true;
 
-         parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
 
-         captionStream.on('data', function (event) {
 
-           // Convert to seconds in the source's timescale
 
-           event.startTime = event.startPts / timescale;
 
-           event.endTime = event.endPts / timescale;
 
-           parsedCaptions.captions.push(event);
 
-           parsedCaptions.captionStreams[event.stream] = true;
 
-         });
 
-         captionStream.on('log', function (log) {
 
-           parsedCaptions.logs.push(log);
 
-         });
 
-       };
 
-       /**
 
-         * Determines if a new video track will be selected
 
-         * or if the timescale changed
 
-         * @return {Boolean}
 
-        **/
 
-       this.isNewInit = function (videoTrackIds, timescales) {
 
-         if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
 
-           return false;
 
-         }
 
-         return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
 
-       };
 
-       /**
 
-         * Parses out SEI captions and interacts with underlying
 
-         * CaptionStream to return dispatched captions
 
-         *
 
-         * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
 
-         * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
 
-         * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
 
-         * @see parseEmbeddedCaptions
 
-         * @see m2ts/caption-stream.js
 
-        **/
 
-       this.parse = function (segment, videoTrackIds, timescales) {
 
-         var parsedData;
 
-         if (!this.isInitialized()) {
 
-           return null; // This is not likely to be a video segment
 
-         } else if (!videoTrackIds || !timescales) {
 
-           return null;
 
-         } else if (this.isNewInit(videoTrackIds, timescales)) {
 
-           // Use the first video track only as there is no
 
-           // mechanism to switch to other video tracks
 
-           trackId = videoTrackIds[0];
 
-           timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
 
-           // data until we have one.
 
-           // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
 
-         } else if (trackId === null || !timescale) {
 
-           segmentCache.push(segment);
 
-           return null;
 
-         } // Now that a timescale and trackId is set, parse cached segments
 
-         while (segmentCache.length > 0) {
 
-           var cachedSegment = segmentCache.shift();
 
-           this.parse(cachedSegment, videoTrackIds, timescales);
 
-         }
 
-         parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
 
-         if (parsedData && parsedData.logs) {
 
-           parsedCaptions.logs = parsedCaptions.logs.concat(parsedData.logs);
 
-         }
 
-         if (parsedData === null || !parsedData.seiNals) {
 
-           if (parsedCaptions.logs.length) {
 
-             return {
 
-               logs: parsedCaptions.logs,
 
-               captions: [],
 
-               captionStreams: []
 
-             };
 
-           }
 
-           return null;
 
-         }
 
-         this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
 
-         this.flushStream();
 
-         return parsedCaptions;
 
-       };
 
-       /**
 
-         * Pushes SEI NALUs onto CaptionStream
 
-         * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
 
-         * Assumes that `parseCaptionNals` has been called first
 
-         * @see m2ts/caption-stream.js
 
-         **/
 
-       this.pushNals = function (nals) {
 
-         if (!this.isInitialized() || !nals || nals.length === 0) {
 
-           return null;
 
-         }
 
-         nals.forEach(function (nal) {
 
-           captionStream.push(nal);
 
-         });
 
-       };
 
-       /**
 
-         * Flushes underlying CaptionStream to dispatch processed, displayable captions
 
-         * @see m2ts/caption-stream.js
 
-        **/
 
-       this.flushStream = function () {
 
-         if (!this.isInitialized()) {
 
-           return null;
 
-         }
 
-         if (!parsingPartial) {
 
-           captionStream.flush();
 
-         } else {
 
-           captionStream.partialFlush();
 
-         }
 
-       };
 
-       /**
 
-         * Reset caption buckets for new data
 
-        **/
 
-       this.clearParsedCaptions = function () {
 
-         parsedCaptions.captions = [];
 
-         parsedCaptions.captionStreams = {};
 
-         parsedCaptions.logs = [];
 
-       };
 
-       /**
 
-         * Resets underlying CaptionStream
 
-         * @see m2ts/caption-stream.js
 
-        **/
 
-       this.resetCaptionStream = function () {
 
-         if (!this.isInitialized()) {
 
-           return null;
 
-         }
 
-         captionStream.reset();
 
-       };
 
-       /**
 
-         * Convenience method to clear all captions flushed from the
 
-         * CaptionStream and still being parsed
 
-         * @see m2ts/caption-stream.js
 
-        **/
 
-       this.clearAllCaptions = function () {
 
-         this.clearParsedCaptions();
 
-         this.resetCaptionStream();
 
-       };
 
-       /**
 
-         * Reset caption parser
 
-        **/
 
-       this.reset = function () {
 
-         segmentCache = [];
 
-         trackId = null;
 
-         timescale = null;
 
-         if (!parsedCaptions) {
 
-           parsedCaptions = {
 
-             captions: [],
 
-             // CC1, CC2, CC3, CC4
 
-             captionStreams: {},
 
-             logs: []
 
-           };
 
-         } else {
 
-           this.clearParsedCaptions();
 
-         }
 
-         this.resetCaptionStream();
 
-       };
 
-       this.reset();
 
-     };
 
-     var captionParser = CaptionParser;
 
-     /**
 
-      * Returns the first string in the data array ending with a null char '\0'
 
-      * @param {UInt8} data 
 
-      * @returns the string with the null char
 
-      */
 
-     var uint8ToCString$1 = function (data) {
 
-       var index = 0;
 
-       var curChar = String.fromCharCode(data[index]);
 
-       var retString = '';
 
-       while (curChar !== '\0') {
 
-         retString += curChar;
 
-         index++;
 
-         curChar = String.fromCharCode(data[index]);
 
-       } // Add nullChar
 
-       retString += curChar;
 
-       return retString;
 
-     };
 
-     var string = {
 
-       uint8ToCString: uint8ToCString$1
 
-     };
 
-     var uint8ToCString = string.uint8ToCString;
 
-     var getUint64$1 = numbers.getUint64;
 
-     /**
 
-      * Based on: ISO/IEC 23009 Section: 5.10.3.3
 
-      * References:
 
-      * https://dashif-documents.azurewebsites.net/Events/master/event.html#emsg-format
 
-      * https://aomediacodec.github.io/id3-emsg/
 
-      * 
 
-      * Takes emsg box data as a uint8 array and returns a emsg box object
 
-      * @param {UInt8Array} boxData data from emsg box
 
-      * @returns A parsed emsg box object
 
-      */
 
-     var parseEmsgBox = function (boxData) {
 
-       // version + flags
 
-       var offset = 4;
 
-       var version = boxData[0];
 
-       var scheme_id_uri, value, timescale, presentation_time, presentation_time_delta, event_duration, id, message_data;
 
-       if (version === 0) {
 
-         scheme_id_uri = uint8ToCString(boxData.subarray(offset));
 
-         offset += scheme_id_uri.length;
 
-         value = uint8ToCString(boxData.subarray(offset));
 
-         offset += value.length;
 
-         var dv = new DataView(boxData.buffer);
 
-         timescale = dv.getUint32(offset);
 
-         offset += 4;
 
-         presentation_time_delta = dv.getUint32(offset);
 
-         offset += 4;
 
-         event_duration = dv.getUint32(offset);
 
-         offset += 4;
 
-         id = dv.getUint32(offset);
 
-         offset += 4;
 
-       } else if (version === 1) {
 
-         var dv = new DataView(boxData.buffer);
 
-         timescale = dv.getUint32(offset);
 
-         offset += 4;
 
-         presentation_time = getUint64$1(boxData.subarray(offset));
 
-         offset += 8;
 
-         event_duration = dv.getUint32(offset);
 
-         offset += 4;
 
-         id = dv.getUint32(offset);
 
-         offset += 4;
 
-         scheme_id_uri = uint8ToCString(boxData.subarray(offset));
 
-         offset += scheme_id_uri.length;
 
-         value = uint8ToCString(boxData.subarray(offset));
 
-         offset += value.length;
 
-       }
 
-       message_data = new Uint8Array(boxData.subarray(offset, boxData.byteLength));
 
-       var emsgBox = {
 
-         scheme_id_uri,
 
-         value,
 
-         // if timescale is undefined or 0 set to 1 
 
-         timescale: timescale ? timescale : 1,
 
-         presentation_time,
 
-         presentation_time_delta,
 
-         event_duration,
 
-         id,
 
-         message_data
 
-       };
 
-       return isValidEmsgBox(version, emsgBox) ? emsgBox : undefined;
 
-     };
 
-     /**
 
-      * Scales a presentation time or time delta with an offset with a provided timescale
 
-      * @param {number} presentationTime 
 
-      * @param {number} timescale 
 
-      * @param {number} timeDelta 
 
-      * @param {number} offset 
 
-      * @returns the scaled time as a number
 
-      */
 
-     var scaleTime = function (presentationTime, timescale, timeDelta, offset) {
 
-       return presentationTime || presentationTime === 0 ? presentationTime / timescale : offset + timeDelta / timescale;
 
-     };
 
-     /**
 
-      * Checks the emsg box data for validity based on the version
 
-      * @param {number} version of the emsg box to validate
 
-      * @param {Object} emsg the emsg data to validate
 
-      * @returns if the box is valid as a boolean
 
-      */
 
-     var isValidEmsgBox = function (version, emsg) {
 
-       var hasScheme = emsg.scheme_id_uri !== '\0';
 
-       var isValidV0Box = version === 0 && isDefined(emsg.presentation_time_delta) && hasScheme;
 
-       var isValidV1Box = version === 1 && isDefined(emsg.presentation_time) && hasScheme; // Only valid versions of emsg are 0 and 1
 
-       return !(version > 1) && isValidV0Box || isValidV1Box;
 
-     }; // Utility function to check if an object is defined
 
-     var isDefined = function (data) {
 
-       return data !== undefined || data !== null;
 
-     };
 
-     var emsg$1 = {
 
-       parseEmsgBox: parseEmsgBox,
 
-       scaleTime: scaleTime
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Utilities to detect basic properties and metadata about MP4s.
 
-      */
 
-     var toUnsigned = bin.toUnsigned;
 
-     var toHexString = bin.toHexString;
 
-     var findBox = findBox_1;
 
-     var parseType$1 = parseType_1;
 
-     var emsg = emsg$1;
 
-     var parseTfhd = parseTfhd$2;
 
-     var parseTrun = parseTrun$2;
 
-     var parseTfdt = parseTfdt$2;
 
-     var getUint64 = numbers.getUint64;
 
-     var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader, getEmsgID3;
 
-     var window$1 = window_1;
 
-     var parseId3Frames = parseId3.parseId3Frames;
 
-     /**
 
-      * Parses an MP4 initialization segment and extracts the timescale
 
-      * values for any declared tracks. Timescale values indicate the
 
-      * number of clock ticks per second to assume for time-based values
 
-      * elsewhere in the MP4.
 
-      *
 
-      * To determine the start time of an MP4, you need two pieces of
 
-      * information: the timescale unit and the earliest base media decode
 
-      * time. Multiple timescales can be specified within an MP4 but the
 
-      * base media decode time is always expressed in the timescale from
 
-      * the media header box for the track:
 
-      * ```
 
-      * moov > trak > mdia > mdhd.timescale
 
-      * ```
 
-      * @param init {Uint8Array} the bytes of the init segment
 
-      * @return {object} a hash of track ids to timescale values or null if
 
-      * the init segment is malformed.
 
-      */
 
-     timescale = function (init) {
 
-       var result = {},
 
-         traks = findBox(init, ['moov', 'trak']); // mdhd timescale
 
-       return traks.reduce(function (result, trak) {
 
-         var tkhd, version, index, id, mdhd;
 
-         tkhd = findBox(trak, ['tkhd'])[0];
 
-         if (!tkhd) {
 
-           return null;
 
-         }
 
-         version = tkhd[0];
 
-         index = version === 0 ? 12 : 20;
 
-         id = toUnsigned(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
 
-         mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
 
-         if (!mdhd) {
 
-           return null;
 
-         }
 
-         version = mdhd[0];
 
-         index = version === 0 ? 12 : 20;
 
-         result[id] = toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
 
-         return result;
 
-       }, result);
 
-     };
 
-     /**
 
-      * Determine the base media decode start time, in seconds, for an MP4
 
-      * fragment. If multiple fragments are specified, the earliest time is
 
-      * returned.
 
-      *
 
-      * The base media decode time can be parsed from track fragment
 
-      * metadata:
 
-      * ```
 
-      * moof > traf > tfdt.baseMediaDecodeTime
 
-      * ```
 
-      * It requires the timescale value from the mdhd to interpret.
 
-      *
 
-      * @param timescale {object} a hash of track ids to timescale values.
 
-      * @return {number} the earliest base media decode start time for the
 
-      * fragment, in seconds
 
-      */
 
-     startTime = function (timescale, fragment) {
 
-       var trafs; // we need info from two childrend of each track fragment box
 
-       trafs = findBox(fragment, ['moof', 'traf']); // determine the start times for each track
 
-       var lowestTime = trafs.reduce(function (acc, traf) {
 
-         var tfhd = findBox(traf, ['tfhd'])[0]; // get the track id from the tfhd
 
-         var id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
 
-         var scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
 
-         var tfdt = findBox(traf, ['tfdt'])[0];
 
-         var dv = new DataView(tfdt.buffer, tfdt.byteOffset, tfdt.byteLength);
 
-         var baseTime; // version 1 is 64 bit
 
-         if (tfdt[0] === 1) {
 
-           baseTime = getUint64(tfdt.subarray(4, 12));
 
-         } else {
 
-           baseTime = dv.getUint32(4);
 
-         } // convert base time to seconds if it is a valid number.
 
-         let seconds;
 
-         if (typeof baseTime === 'bigint') {
 
-           seconds = baseTime / window$1.BigInt(scale);
 
-         } else if (typeof baseTime === 'number' && !isNaN(baseTime)) {
 
-           seconds = baseTime / scale;
 
-         }
 
-         if (seconds < Number.MAX_SAFE_INTEGER) {
 
-           seconds = Number(seconds);
 
-         }
 
-         if (seconds < acc) {
 
-           acc = seconds;
 
-         }
 
-         return acc;
 
-       }, Infinity);
 
-       return typeof lowestTime === 'bigint' || isFinite(lowestTime) ? lowestTime : 0;
 
-     };
 
-     /**
 
-      * Determine the composition start, in seconds, for an MP4
 
-      * fragment.
 
-      *
 
-      * The composition start time of a fragment can be calculated using the base
 
-      * media decode time, composition time offset, and timescale, as follows:
 
-      *
 
-      * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
 
-      *
 
-      * All of the aforementioned information is contained within a media fragment's
 
-      * `traf` box, except for timescale info, which comes from the initialization
 
-      * segment, so a track id (also contained within a `traf`) is also necessary to
 
-      * associate it with a timescale
 
-      *
 
-      *
 
-      * @param timescales {object} - a hash of track ids to timescale values.
 
-      * @param fragment {Unit8Array} - the bytes of a media segment
 
-      * @return {number} the composition start time for the fragment, in seconds
 
-      **/
 
-     compositionStartTime = function (timescales, fragment) {
 
-       var trafBoxes = findBox(fragment, ['moof', 'traf']);
 
-       var baseMediaDecodeTime = 0;
 
-       var compositionTimeOffset = 0;
 
-       var trackId;
 
-       if (trafBoxes && trafBoxes.length) {
 
-         // The spec states that track run samples contained within a `traf` box are contiguous, but
 
-         // it does not explicitly state whether the `traf` boxes themselves are contiguous.
 
-         // We will assume that they are, so we only need the first to calculate start time.
 
-         var tfhd = findBox(trafBoxes[0], ['tfhd'])[0];
 
-         var trun = findBox(trafBoxes[0], ['trun'])[0];
 
-         var tfdt = findBox(trafBoxes[0], ['tfdt'])[0];
 
-         if (tfhd) {
 
-           var parsedTfhd = parseTfhd(tfhd);
 
-           trackId = parsedTfhd.trackId;
 
-         }
 
-         if (tfdt) {
 
-           var parsedTfdt = parseTfdt(tfdt);
 
-           baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime;
 
-         }
 
-         if (trun) {
 
-           var parsedTrun = parseTrun(trun);
 
-           if (parsedTrun.samples && parsedTrun.samples.length) {
 
-             compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0;
 
-           }
 
-         }
 
-       } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
 
-       // specified.
 
-       var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
 
-       if (typeof baseMediaDecodeTime === 'bigint') {
 
-         compositionTimeOffset = window$1.BigInt(compositionTimeOffset);
 
-         timescale = window$1.BigInt(timescale);
 
-       }
 
-       var result = (baseMediaDecodeTime + compositionTimeOffset) / timescale;
 
-       if (typeof result === 'bigint' && result < Number.MAX_SAFE_INTEGER) {
 
-         result = Number(result);
 
-       }
 
-       return result;
 
-     };
 
-     /**
 
-       * Find the trackIds of the video tracks in this source.
 
-       * Found by parsing the Handler Reference and Track Header Boxes:
 
-       *   moov > trak > mdia > hdlr
 
-       *   moov > trak > tkhd
 
-       *
 
-       * @param {Uint8Array} init - The bytes of the init segment for this source
 
-       * @return {Number[]} A list of trackIds
 
-       *
 
-       * @see ISO-BMFF-12/2015, Section 8.4.3
 
-      **/
 
-     getVideoTrackIds = function (init) {
 
-       var traks = findBox(init, ['moov', 'trak']);
 
-       var videoTrackIds = [];
 
-       traks.forEach(function (trak) {
 
-         var hdlrs = findBox(trak, ['mdia', 'hdlr']);
 
-         var tkhds = findBox(trak, ['tkhd']);
 
-         hdlrs.forEach(function (hdlr, index) {
 
-           var handlerType = parseType$1(hdlr.subarray(8, 12));
 
-           var tkhd = tkhds[index];
 
-           var view;
 
-           var version;
 
-           var trackId;
 
-           if (handlerType === 'vide') {
 
-             view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
 
-             version = view.getUint8(0);
 
-             trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
 
-             videoTrackIds.push(trackId);
 
-           }
 
-         });
 
-       });
 
-       return videoTrackIds;
 
-     };
 
-     getTimescaleFromMediaHeader = function (mdhd) {
 
-       // mdhd is a FullBox, meaning it will have its own version as the first byte
 
-       var version = mdhd[0];
 
-       var index = version === 0 ? 12 : 20;
 
-       return toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
 
-     };
 
-     /**
 
-      * Get all the video, audio, and hint tracks from a non fragmented
 
-      * mp4 segment
 
-      */
 
-     getTracks = function (init) {
 
-       var traks = findBox(init, ['moov', 'trak']);
 
-       var tracks = [];
 
-       traks.forEach(function (trak) {
 
-         var track = {};
 
-         var tkhd = findBox(trak, ['tkhd'])[0];
 
-         var view, tkhdVersion; // id
 
-         if (tkhd) {
 
-           view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
 
-           tkhdVersion = view.getUint8(0);
 
-           track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
 
-         }
 
-         var hdlr = findBox(trak, ['mdia', 'hdlr'])[0]; // type
 
-         if (hdlr) {
 
-           var type = parseType$1(hdlr.subarray(8, 12));
 
-           if (type === 'vide') {
 
-             track.type = 'video';
 
-           } else if (type === 'soun') {
 
-             track.type = 'audio';
 
-           } else {
 
-             track.type = type;
 
-           }
 
-         } // codec
 
-         var stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
 
-         if (stsd) {
 
-           var sampleDescriptions = stsd.subarray(8); // gives the codec type string
 
-           track.codec = parseType$1(sampleDescriptions.subarray(4, 8));
 
-           var codecBox = findBox(sampleDescriptions, [track.codec])[0];
 
-           var codecConfig, codecConfigType;
 
-           if (codecBox) {
 
-             // https://tools.ietf.org/html/rfc6381#section-3.3
 
-             if (/^[asm]vc[1-9]$/i.test(track.codec)) {
 
-               // we don't need anything but the "config" parameter of the
 
-               // avc1 codecBox
 
-               codecConfig = codecBox.subarray(78);
 
-               codecConfigType = parseType$1(codecConfig.subarray(4, 8));
 
-               if (codecConfigType === 'avcC' && codecConfig.length > 11) {
 
-                 track.codec += '.'; // left padded with zeroes for single digit hex
 
-                 // profile idc
 
-                 track.codec += toHexString(codecConfig[9]); // the byte containing the constraint_set flags
 
-                 track.codec += toHexString(codecConfig[10]); // level idc
 
-                 track.codec += toHexString(codecConfig[11]);
 
-               } else {
 
-                 // TODO: show a warning that we couldn't parse the codec
 
-                 // and are using the default
 
-                 track.codec = 'avc1.4d400d';
 
-               }
 
-             } else if (/^mp4[a,v]$/i.test(track.codec)) {
 
-               // we do not need anything but the streamDescriptor of the mp4a codecBox
 
-               codecConfig = codecBox.subarray(28);
 
-               codecConfigType = parseType$1(codecConfig.subarray(4, 8));
 
-               if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
 
-                 track.codec += '.' + toHexString(codecConfig[19]); // this value is only a single digit
 
-                 track.codec += '.' + toHexString(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, '');
 
-               } else {
 
-                 // TODO: show a warning that we couldn't parse the codec
 
-                 // and are using the default
 
-                 track.codec = 'mp4a.40.2';
 
-               }
 
-             } else {
 
-               // flac, opus, etc
 
-               track.codec = track.codec.toLowerCase();
 
-             }
 
-           }
 
-         }
 
-         var mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
 
-         if (mdhd) {
 
-           track.timescale = getTimescaleFromMediaHeader(mdhd);
 
-         }
 
-         tracks.push(track);
 
-       });
 
-       return tracks;
 
-     };
 
-     /**
 
-      * Returns an array of emsg ID3 data from the provided segmentData.
 
-      * An offset can also be provided as the Latest Arrival Time to calculate 
 
-      * the Event Start Time of v0 EMSG boxes. 
 
-      * See: https://dashif-documents.azurewebsites.net/Events/master/event.html#Inband-event-timing
 
-      * 
 
-      * @param {Uint8Array} segmentData the segment byte array.
 
-      * @param {number} offset the segment start time or Latest Arrival Time, 
 
-      * @return {Object[]} an array of ID3 parsed from EMSG boxes
 
-      */
 
-     getEmsgID3 = function (segmentData, offset = 0) {
 
-       var emsgBoxes = findBox(segmentData, ['emsg']);
 
-       return emsgBoxes.map(data => {
 
-         var parsedBox = emsg.parseEmsgBox(new Uint8Array(data));
 
-         var parsedId3Frames = parseId3Frames(parsedBox.message_data);
 
-         return {
 
-           cueTime: emsg.scaleTime(parsedBox.presentation_time, parsedBox.timescale, parsedBox.presentation_time_delta, offset),
 
-           duration: emsg.scaleTime(parsedBox.event_duration, parsedBox.timescale),
 
-           frames: parsedId3Frames
 
-         };
 
-       });
 
-     };
 
-     var probe$2 = {
 
-       // export mp4 inspector's findBox and parseType for backwards compatibility
 
-       findBox: findBox,
 
-       parseType: parseType$1,
 
-       timescale: timescale,
 
-       startTime: startTime,
 
-       compositionStartTime: compositionStartTime,
 
-       videoTrackIds: getVideoTrackIds,
 
-       tracks: getTracks,
 
-       getTimescaleFromMediaHeader: getTimescaleFromMediaHeader,
 
-       getEmsgID3: getEmsgID3
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Utilities to detect basic properties and metadata about TS Segments.
 
-      */
 
-     var StreamTypes$1 = streamTypes;
 
-     var parsePid = function (packet) {
 
-       var pid = packet[1] & 0x1f;
 
-       pid <<= 8;
 
-       pid |= packet[2];
 
-       return pid;
 
-     };
 
-     var parsePayloadUnitStartIndicator = function (packet) {
 
-       return !!(packet[1] & 0x40);
 
-     };
 
-     var parseAdaptionField = function (packet) {
 
-       var offset = 0; // if an adaption field is present, its length is specified by the
 
-       // fifth byte of the TS packet header. The adaptation field is
 
-       // used to add stuffing to PES packets that don't fill a complete
 
-       // TS packet, and to specify some forms of timing and control data
 
-       // that we do not currently use.
 
-       if ((packet[3] & 0x30) >>> 4 > 0x01) {
 
-         offset += packet[4] + 1;
 
-       }
 
-       return offset;
 
-     };
 
-     var parseType = function (packet, pmtPid) {
 
-       var pid = parsePid(packet);
 
-       if (pid === 0) {
 
-         return 'pat';
 
-       } else if (pid === pmtPid) {
 
-         return 'pmt';
 
-       } else if (pmtPid) {
 
-         return 'pes';
 
-       }
 
-       return null;
 
-     };
 
-     var parsePat = function (packet) {
 
-       var pusi = parsePayloadUnitStartIndicator(packet);
 
-       var offset = 4 + parseAdaptionField(packet);
 
-       if (pusi) {
 
-         offset += packet[offset] + 1;
 
-       }
 
-       return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
 
-     };
 
-     var parsePmt = function (packet) {
 
-       var programMapTable = {};
 
-       var pusi = parsePayloadUnitStartIndicator(packet);
 
-       var payloadOffset = 4 + parseAdaptionField(packet);
 
-       if (pusi) {
 
-         payloadOffset += packet[payloadOffset] + 1;
 
-       } // PMTs can be sent ahead of the time when they should actually
 
-       // take effect. We don't believe this should ever be the case
 
-       // for HLS but we'll ignore "forward" PMT declarations if we see
 
-       // them. Future PMT declarations have the current_next_indicator
 
-       // set to zero.
 
-       if (!(packet[payloadOffset + 5] & 0x01)) {
 
-         return;
 
-       }
 
-       var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
 
-       sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
 
-       tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
 
-       // long the program info descriptors are
 
-       programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
 
-       var offset = 12 + programInfoLength;
 
-       while (offset < tableEnd) {
 
-         var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
 
-         programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
 
-         // skip past the elementary stream descriptors, if present
 
-         offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
 
-       }
 
-       return programMapTable;
 
-     };
 
-     var parsePesType = function (packet, programMapTable) {
 
-       var pid = parsePid(packet);
 
-       var type = programMapTable[pid];
 
-       switch (type) {
 
-         case StreamTypes$1.H264_STREAM_TYPE:
 
-           return 'video';
 
-         case StreamTypes$1.ADTS_STREAM_TYPE:
 
-           return 'audio';
 
-         case StreamTypes$1.METADATA_STREAM_TYPE:
 
-           return 'timed-metadata';
 
-         default:
 
-           return null;
 
-       }
 
-     };
 
-     var parsePesTime = function (packet) {
 
-       var pusi = parsePayloadUnitStartIndicator(packet);
 
-       if (!pusi) {
 
-         return null;
 
-       }
 
-       var offset = 4 + parseAdaptionField(packet);
 
-       if (offset >= packet.byteLength) {
 
-         // From the H 222.0 MPEG-TS spec
 
-         // "For transport stream packets carrying PES packets, stuffing is needed when there
 
-         //  is insufficient PES packet data to completely fill the transport stream packet
 
-         //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
 
-         //  the sum of the lengths of the data elements in it, so that the payload bytes
 
-         //  remaining after the adaptation field exactly accommodates the available PES packet
 
-         //  data."
 
-         //
 
-         // If the offset is >= the length of the packet, then the packet contains no data
 
-         // and instead is just adaption field stuffing bytes
 
-         return null;
 
-       }
 
-       var pes = null;
 
-       var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
 
-       // and a DTS value. Determine what combination of values is
 
-       // available to work with.
 
-       ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
 
-       // performs all bitwise operations on 32-bit integers but javascript
 
-       // supports a much greater range (52-bits) of integer using standard
 
-       // mathematical operations.
 
-       // We construct a 31-bit value using bitwise operators over the 31
 
-       // most significant bits and then multiply by 4 (equal to a left-shift
 
-       // of 2) before we add the final 2 least significant bits of the
 
-       // timestamp (equal to an OR.)
 
-       if (ptsDtsFlags & 0xC0) {
 
-         pes = {}; // the PTS and DTS are not written out directly. For information
 
-         // on how they are encoded, see
 
-         // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
 
-         pes.pts = (packet[offset + 9] & 0x0E) << 27 | (packet[offset + 10] & 0xFF) << 20 | (packet[offset + 11] & 0xFE) << 12 | (packet[offset + 12] & 0xFF) << 5 | (packet[offset + 13] & 0xFE) >>> 3;
 
-         pes.pts *= 4; // Left shift by 2
 
-         pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
 
-         pes.dts = pes.pts;
 
-         if (ptsDtsFlags & 0x40) {
 
-           pes.dts = (packet[offset + 14] & 0x0E) << 27 | (packet[offset + 15] & 0xFF) << 20 | (packet[offset + 16] & 0xFE) << 12 | (packet[offset + 17] & 0xFF) << 5 | (packet[offset + 18] & 0xFE) >>> 3;
 
-           pes.dts *= 4; // Left shift by 2
 
-           pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
 
-         }
 
-       }
 
-       return pes;
 
-     };
 
-     var parseNalUnitType = function (type) {
 
-       switch (type) {
 
-         case 0x05:
 
-           return 'slice_layer_without_partitioning_rbsp_idr';
 
-         case 0x06:
 
-           return 'sei_rbsp';
 
-         case 0x07:
 
-           return 'seq_parameter_set_rbsp';
 
-         case 0x08:
 
-           return 'pic_parameter_set_rbsp';
 
-         case 0x09:
 
-           return 'access_unit_delimiter_rbsp';
 
-         default:
 
-           return null;
 
-       }
 
-     };
 
-     var videoPacketContainsKeyFrame = function (packet) {
 
-       var offset = 4 + parseAdaptionField(packet);
 
-       var frameBuffer = packet.subarray(offset);
 
-       var frameI = 0;
 
-       var frameSyncPoint = 0;
 
-       var foundKeyFrame = false;
 
-       var nalType; // advance the sync point to a NAL start, if necessary
 
-       for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
 
-         if (frameBuffer[frameSyncPoint + 2] === 1) {
 
-           // the sync point is properly aligned
 
-           frameI = frameSyncPoint + 5;
 
-           break;
 
-         }
 
-       }
 
-       while (frameI < frameBuffer.byteLength) {
 
-         // look at the current byte to determine if we've hit the end of
 
-         // a NAL unit boundary
 
-         switch (frameBuffer[frameI]) {
 
-           case 0:
 
-             // skip past non-sync sequences
 
-             if (frameBuffer[frameI - 1] !== 0) {
 
-               frameI += 2;
 
-               break;
 
-             } else if (frameBuffer[frameI - 2] !== 0) {
 
-               frameI++;
 
-               break;
 
-             }
 
-             if (frameSyncPoint + 3 !== frameI - 2) {
 
-               nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
 
-               if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
 
-                 foundKeyFrame = true;
 
-               }
 
-             } // drop trailing zeroes
 
-             do {
 
-               frameI++;
 
-             } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
 
-             frameSyncPoint = frameI - 2;
 
-             frameI += 3;
 
-             break;
 
-           case 1:
 
-             // skip past non-sync sequences
 
-             if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
 
-               frameI += 3;
 
-               break;
 
-             }
 
-             nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
 
-             if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
 
-               foundKeyFrame = true;
 
-             }
 
-             frameSyncPoint = frameI - 2;
 
-             frameI += 3;
 
-             break;
 
-           default:
 
-             // the current byte isn't a one or zero, so it cannot be part
 
-             // of a sync sequence
 
-             frameI += 3;
 
-             break;
 
-         }
 
-       }
 
-       frameBuffer = frameBuffer.subarray(frameSyncPoint);
 
-       frameI -= frameSyncPoint;
 
-       frameSyncPoint = 0; // parse the final nal
 
-       if (frameBuffer && frameBuffer.byteLength > 3) {
 
-         nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
 
-         if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
 
-           foundKeyFrame = true;
 
-         }
 
-       }
 
-       return foundKeyFrame;
 
-     };
 
-     var probe$1 = {
 
-       parseType: parseType,
 
-       parsePat: parsePat,
 
-       parsePmt: parsePmt,
 
-       parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
 
-       parsePesType: parsePesType,
 
-       parsePesTime: parsePesTime,
 
-       videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
 
-     };
 
-     /**
 
-      * mux.js
 
-      *
 
-      * Copyright (c) Brightcove
 
-      * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 
-      *
 
-      * Parse mpeg2 transport stream packets to extract basic timing information
 
-      */
 
-     var StreamTypes = streamTypes;
 
-     var handleRollover = timestampRolloverStream.handleRollover;
 
-     var probe = {};
 
-     probe.ts = probe$1;
 
-     probe.aac = utils;
 
-     var ONE_SECOND_IN_TS = clock$2.ONE_SECOND_IN_TS;
 
-     var MP2T_PACKET_LENGTH = 188,
 
-       // bytes
 
-       SYNC_BYTE = 0x47;
 
-     /**
 
-      * walks through segment data looking for pat and pmt packets to parse out
 
-      * program map table information
 
-      */
 
-     var parsePsi_ = function (bytes, pmt) {
 
-       var startIndex = 0,
 
-         endIndex = MP2T_PACKET_LENGTH,
 
-         packet,
 
-         type;
 
-       while (endIndex < bytes.byteLength) {
 
-         // Look for a pair of start and end sync bytes in the data..
 
-         if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
 
-           // We found a packet
 
-           packet = bytes.subarray(startIndex, endIndex);
 
-           type = probe.ts.parseType(packet, pmt.pid);
 
-           switch (type) {
 
-             case 'pat':
 
-               pmt.pid = probe.ts.parsePat(packet);
 
-               break;
 
-             case 'pmt':
 
-               var table = probe.ts.parsePmt(packet);
 
-               pmt.table = pmt.table || {};
 
-               Object.keys(table).forEach(function (key) {
 
-                 pmt.table[key] = table[key];
 
-               });
 
-               break;
 
-           }
 
-           startIndex += MP2T_PACKET_LENGTH;
 
-           endIndex += MP2T_PACKET_LENGTH;
 
-           continue;
 
-         } // If we get here, we have somehow become de-synchronized and we need to step
 
-         // forward one byte at a time until we find a pair of sync bytes that denote
 
-         // a packet
 
-         startIndex++;
 
-         endIndex++;
 
-       }
 
-     };
 
-     /**
 
-      * walks through the segment data from the start and end to get timing information
 
-      * for the first and last audio pes packets
 
-      */
 
-     var parseAudioPes_ = function (bytes, pmt, result) {
 
-       var startIndex = 0,
 
-         endIndex = MP2T_PACKET_LENGTH,
 
-         packet,
 
-         type,
 
-         pesType,
 
-         pusi,
 
-         parsed;
 
-       var endLoop = false; // Start walking from start of segment to get first audio packet
 
-       while (endIndex <= bytes.byteLength) {
 
-         // Look for a pair of start and end sync bytes in the data..
 
-         if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
 
-           // We found a packet
 
-           packet = bytes.subarray(startIndex, endIndex);
 
-           type = probe.ts.parseType(packet, pmt.pid);
 
-           switch (type) {
 
-             case 'pes':
 
-               pesType = probe.ts.parsePesType(packet, pmt.table);
 
-               pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
 
-               if (pesType === 'audio' && pusi) {
 
-                 parsed = probe.ts.parsePesTime(packet);
 
-                 if (parsed) {
 
-                   parsed.type = 'audio';
 
-                   result.audio.push(parsed);
 
-                   endLoop = true;
 
-                 }
 
-               }
 
-               break;
 
-           }
 
-           if (endLoop) {
 
-             break;
 
-           }
 
-           startIndex += MP2T_PACKET_LENGTH;
 
-           endIndex += MP2T_PACKET_LENGTH;
 
-           continue;
 
-         } // If we get here, we have somehow become de-synchronized and we need to step
 
-         // forward one byte at a time until we find a pair of sync bytes that denote
 
-         // a packet
 
-         startIndex++;
 
-         endIndex++;
 
-       } // Start walking from end of segment to get last audio packet
 
-       endIndex = bytes.byteLength;
 
-       startIndex = endIndex - MP2T_PACKET_LENGTH;
 
-       endLoop = false;
 
-       while (startIndex >= 0) {
 
-         // Look for a pair of start and end sync bytes in the data..
 
-         if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
 
-           // We found a packet
 
-           packet = bytes.subarray(startIndex, endIndex);
 
-           type = probe.ts.parseType(packet, pmt.pid);
 
-           switch (type) {
 
-             case 'pes':
 
-               pesType = probe.ts.parsePesType(packet, pmt.table);
 
-               pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
 
-               if (pesType === 'audio' && pusi) {
 
-                 parsed = probe.ts.parsePesTime(packet);
 
-                 if (parsed) {
 
-                   parsed.type = 'audio';
 
-                   result.audio.push(parsed);
 
-                   endLoop = true;
 
-                 }
 
-               }
 
-               break;
 
-           }
 
-           if (endLoop) {
 
-             break;
 
-           }
 
-           startIndex -= MP2T_PACKET_LENGTH;
 
-           endIndex -= MP2T_PACKET_LENGTH;
 
-           continue;
 
-         } // If we get here, we have somehow become de-synchronized and we need to step
 
-         // forward one byte at a time until we find a pair of sync bytes that denote
 
-         // a packet
 
-         startIndex--;
 
-         endIndex--;
 
-       }
 
-     };
 
-     /**
 
-      * walks through the segment data from the start and end to get timing information
 
-      * for the first and last video pes packets as well as timing information for the first
 
-      * key frame.
 
-      */
 
-     var parseVideoPes_ = function (bytes, pmt, result) {
 
-       var startIndex = 0,
 
-         endIndex = MP2T_PACKET_LENGTH,
 
-         packet,
 
-         type,
 
-         pesType,
 
-         pusi,
 
-         parsed,
 
-         frame,
 
-         i,
 
-         pes;
 
-       var endLoop = false;
 
-       var currentFrame = {
 
-         data: [],
 
-         size: 0
 
-       }; // Start walking from start of segment to get first video packet
 
-       while (endIndex < bytes.byteLength) {
 
-         // Look for a pair of start and end sync bytes in the data..
 
-         if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
 
-           // We found a packet
 
-           packet = bytes.subarray(startIndex, endIndex);
 
-           type = probe.ts.parseType(packet, pmt.pid);
 
-           switch (type) {
 
-             case 'pes':
 
-               pesType = probe.ts.parsePesType(packet, pmt.table);
 
-               pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
 
-               if (pesType === 'video') {
 
-                 if (pusi && !endLoop) {
 
-                   parsed = probe.ts.parsePesTime(packet);
 
-                   if (parsed) {
 
-                     parsed.type = 'video';
 
-                     result.video.push(parsed);
 
-                     endLoop = true;
 
-                   }
 
-                 }
 
-                 if (!result.firstKeyFrame) {
 
-                   if (pusi) {
 
-                     if (currentFrame.size !== 0) {
 
-                       frame = new Uint8Array(currentFrame.size);
 
-                       i = 0;
 
-                       while (currentFrame.data.length) {
 
-                         pes = currentFrame.data.shift();
 
-                         frame.set(pes, i);
 
-                         i += pes.byteLength;
 
-                       }
 
-                       if (probe.ts.videoPacketContainsKeyFrame(frame)) {
 
-                         var firstKeyFrame = probe.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
 
-                         // the keyframe seems to work fine with HLS playback
 
-                         // and definitely preferable to a crash with TypeError...
 
-                         if (firstKeyFrame) {
 
-                           result.firstKeyFrame = firstKeyFrame;
 
-                           result.firstKeyFrame.type = 'video';
 
-                         } else {
 
-                           // eslint-disable-next-line
 
-                           console.warn('Failed to extract PTS/DTS from PES at first keyframe. ' + 'This could be an unusual TS segment, or else mux.js did not ' + 'parse your TS segment correctly. If you know your TS ' + 'segments do contain PTS/DTS on keyframes please file a bug ' + 'report! You can try ffprobe to double check for yourself.');
 
-                         }
 
-                       }
 
-                       currentFrame.size = 0;
 
-                     }
 
-                   }
 
-                   currentFrame.data.push(packet);
 
-                   currentFrame.size += packet.byteLength;
 
-                 }
 
-               }
 
-               break;
 
-           }
 
-           if (endLoop && result.firstKeyFrame) {
 
-             break;
 
-           }
 
-           startIndex += MP2T_PACKET_LENGTH;
 
-           endIndex += MP2T_PACKET_LENGTH;
 
-           continue;
 
-         } // If we get here, we have somehow become de-synchronized and we need to step
 
-         // forward one byte at a time until we find a pair of sync bytes that denote
 
-         // a packet
 
-         startIndex++;
 
-         endIndex++;
 
-       } // Start walking from end of segment to get last video packet
 
-       endIndex = bytes.byteLength;
 
-       startIndex = endIndex - MP2T_PACKET_LENGTH;
 
-       endLoop = false;
 
-       while (startIndex >= 0) {
 
-         // Look for a pair of start and end sync bytes in the data..
 
-         if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
 
-           // We found a packet
 
-           packet = bytes.subarray(startIndex, endIndex);
 
-           type = probe.ts.parseType(packet, pmt.pid);
 
-           switch (type) {
 
-             case 'pes':
 
-               pesType = probe.ts.parsePesType(packet, pmt.table);
 
-               pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
 
-               if (pesType === 'video' && pusi) {
 
-                 parsed = probe.ts.parsePesTime(packet);
 
-                 if (parsed) {
 
-                   parsed.type = 'video';
 
-                   result.video.push(parsed);
 
-                   endLoop = true;
 
-                 }
 
-               }
 
-               break;
 
-           }
 
-           if (endLoop) {
 
-             break;
 
-           }
 
-           startIndex -= MP2T_PACKET_LENGTH;
 
-           endIndex -= MP2T_PACKET_LENGTH;
 
-           continue;
 
-         } // If we get here, we have somehow become de-synchronized and we need to step
 
-         // forward one byte at a time until we find a pair of sync bytes that denote
 
-         // a packet
 
-         startIndex--;
 
-         endIndex--;
 
-       }
 
-     };
 
-     /**
 
-      * Adjusts the timestamp information for the segment to account for
 
-      * rollover and convert to seconds based on pes packet timescale (90khz clock)
 
-      */
 
-     var adjustTimestamp_ = function (segmentInfo, baseTimestamp) {
 
-       if (segmentInfo.audio && segmentInfo.audio.length) {
 
-         var audioBaseTimestamp = baseTimestamp;
 
-         if (typeof audioBaseTimestamp === 'undefined' || isNaN(audioBaseTimestamp)) {
 
-           audioBaseTimestamp = segmentInfo.audio[0].dts;
 
-         }
 
-         segmentInfo.audio.forEach(function (info) {
 
-           info.dts = handleRollover(info.dts, audioBaseTimestamp);
 
-           info.pts = handleRollover(info.pts, audioBaseTimestamp); // time in seconds
 
-           info.dtsTime = info.dts / ONE_SECOND_IN_TS;
 
-           info.ptsTime = info.pts / ONE_SECOND_IN_TS;
 
-         });
 
-       }
 
-       if (segmentInfo.video && segmentInfo.video.length) {
 
-         var videoBaseTimestamp = baseTimestamp;
 
-         if (typeof videoBaseTimestamp === 'undefined' || isNaN(videoBaseTimestamp)) {
 
-           videoBaseTimestamp = segmentInfo.video[0].dts;
 
-         }
 
-         segmentInfo.video.forEach(function (info) {
 
-           info.dts = handleRollover(info.dts, videoBaseTimestamp);
 
-           info.pts = handleRollover(info.pts, videoBaseTimestamp); // time in seconds
 
-           info.dtsTime = info.dts / ONE_SECOND_IN_TS;
 
-           info.ptsTime = info.pts / ONE_SECOND_IN_TS;
 
-         });
 
-         if (segmentInfo.firstKeyFrame) {
 
-           var frame = segmentInfo.firstKeyFrame;
 
-           frame.dts = handleRollover(frame.dts, videoBaseTimestamp);
 
-           frame.pts = handleRollover(frame.pts, videoBaseTimestamp); // time in seconds
 
-           frame.dtsTime = frame.dts / ONE_SECOND_IN_TS;
 
-           frame.ptsTime = frame.pts / ONE_SECOND_IN_TS;
 
-         }
 
-       }
 
-     };
 
-     /**
 
-      * inspects the aac data stream for start and end time information
 
-      */
 
-     var inspectAac_ = function (bytes) {
 
-       var endLoop = false,
 
-         audioCount = 0,
 
-         sampleRate = null,
 
-         timestamp = null,
 
-         frameSize = 0,
 
-         byteIndex = 0,
 
-         packet;
 
-       while (bytes.length - byteIndex >= 3) {
 
-         var type = probe.aac.parseType(bytes, byteIndex);
 
-         switch (type) {
 
-           case 'timed-metadata':
 
-             // Exit early because we don't have enough to parse
 
-             // the ID3 tag header
 
-             if (bytes.length - byteIndex < 10) {
 
-               endLoop = true;
 
-               break;
 
-             }
 
-             frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
 
-             // to emit a full packet
 
-             if (frameSize > bytes.length) {
 
-               endLoop = true;
 
-               break;
 
-             }
 
-             if (timestamp === null) {
 
-               packet = bytes.subarray(byteIndex, byteIndex + frameSize);
 
-               timestamp = probe.aac.parseAacTimestamp(packet);
 
-             }
 
-             byteIndex += frameSize;
 
-             break;
 
-           case 'audio':
 
-             // Exit early because we don't have enough to parse
 
-             // the ADTS frame header
 
-             if (bytes.length - byteIndex < 7) {
 
-               endLoop = true;
 
-               break;
 
-             }
 
-             frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
 
-             // to emit a full packet
 
-             if (frameSize > bytes.length) {
 
-               endLoop = true;
 
-               break;
 
-             }
 
-             if (sampleRate === null) {
 
-               packet = bytes.subarray(byteIndex, byteIndex + frameSize);
 
-               sampleRate = probe.aac.parseSampleRate(packet);
 
-             }
 
-             audioCount++;
 
-             byteIndex += frameSize;
 
-             break;
 
-           default:
 
-             byteIndex++;
 
-             break;
 
-         }
 
-         if (endLoop) {
 
-           return null;
 
-         }
 
-       }
 
-       if (sampleRate === null || timestamp === null) {
 
-         return null;
 
-       }
 
-       var audioTimescale = ONE_SECOND_IN_TS / sampleRate;
 
-       var result = {
 
-         audio: [{
 
-           type: 'audio',
 
-           dts: timestamp,
 
-           pts: timestamp
 
-         }, {
 
-           type: 'audio',
 
-           dts: timestamp + audioCount * 1024 * audioTimescale,
 
-           pts: timestamp + audioCount * 1024 * audioTimescale
 
-         }]
 
-       };
 
-       return result;
 
-     };
 
-     /**
 
-      * inspects the transport stream segment data for start and end time information
 
-      * of the audio and video tracks (when present) as well as the first key frame's
 
-      * start time.
 
-      */
 
-     var inspectTs_ = function (bytes) {
 
-       var pmt = {
 
-         pid: null,
 
-         table: null
 
-       };
 
-       var result = {};
 
-       parsePsi_(bytes, pmt);
 
-       for (var pid in pmt.table) {
 
-         if (pmt.table.hasOwnProperty(pid)) {
 
-           var type = pmt.table[pid];
 
-           switch (type) {
 
-             case StreamTypes.H264_STREAM_TYPE:
 
-               result.video = [];
 
-               parseVideoPes_(bytes, pmt, result);
 
-               if (result.video.length === 0) {
 
-                 delete result.video;
 
-               }
 
-               break;
 
-             case StreamTypes.ADTS_STREAM_TYPE:
 
-               result.audio = [];
 
-               parseAudioPes_(bytes, pmt, result);
 
-               if (result.audio.length === 0) {
 
-                 delete result.audio;
 
-               }
 
-               break;
 
-           }
 
-         }
 
-       }
 
-       return result;
 
-     };
 
-     /**
 
-      * Inspects segment byte data and returns an object with start and end timing information
 
-      *
 
-      * @param {Uint8Array} bytes The segment byte data
 
-      * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
 
-      *  timestamps for rollover. This value must be in 90khz clock.
 
-      * @return {Object} Object containing start and end frame timing info of segment.
 
-      */
 
-     var inspect = function (bytes, baseTimestamp) {
 
-       var isAacData = probe.aac.isLikelyAacData(bytes);
 
-       var result;
 
-       if (isAacData) {
 
-         result = inspectAac_(bytes);
 
-       } else {
 
-         result = inspectTs_(bytes);
 
-       }
 
-       if (!result || !result.audio && !result.video) {
 
-         return null;
 
-       }
 
-       adjustTimestamp_(result, baseTimestamp);
 
-       return result;
 
-     };
 
-     var tsInspector = {
 
-       inspect: inspect,
 
-       parseAudioPes_: parseAudioPes_
 
-     };
 
-     /* global self */
 
-     /**
 
-      * Re-emits transmuxer events by converting them into messages to the
 
-      * world outside the worker.
 
-      *
 
-      * @param {Object} transmuxer the transmuxer to wire events on
 
-      * @private
 
-      */
 
-     const wireTransmuxerEvents = function (self, transmuxer) {
 
-       transmuxer.on('data', function (segment) {
 
-         // transfer ownership of the underlying ArrayBuffer
 
-         // instead of doing a copy to save memory
 
-         // ArrayBuffers are transferable but generic TypedArrays are not
 
-         // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
 
-         const initArray = segment.initSegment;
 
-         segment.initSegment = {
 
-           data: initArray.buffer,
 
-           byteOffset: initArray.byteOffset,
 
-           byteLength: initArray.byteLength
 
-         };
 
-         const typedArray = segment.data;
 
-         segment.data = typedArray.buffer;
 
-         self.postMessage({
 
-           action: 'data',
 
-           segment,
 
-           byteOffset: typedArray.byteOffset,
 
-           byteLength: typedArray.byteLength
 
-         }, [segment.data]);
 
-       });
 
-       transmuxer.on('done', function (data) {
 
-         self.postMessage({
 
-           action: 'done'
 
-         });
 
-       });
 
-       transmuxer.on('gopInfo', function (gopInfo) {
 
-         self.postMessage({
 
-           action: 'gopInfo',
 
-           gopInfo
 
-         });
 
-       });
 
-       transmuxer.on('videoSegmentTimingInfo', function (timingInfo) {
 
-         const videoSegmentTimingInfo = {
 
-           start: {
 
-             decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
 
-             presentation: clock$2.videoTsToSeconds(timingInfo.start.pts)
 
-           },
 
-           end: {
 
-             decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
 
-             presentation: clock$2.videoTsToSeconds(timingInfo.end.pts)
 
-           },
 
-           baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
 
-         };
 
-         if (timingInfo.prependedContentDuration) {
 
-           videoSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(timingInfo.prependedContentDuration);
 
-         }
 
-         self.postMessage({
 
-           action: 'videoSegmentTimingInfo',
 
-           videoSegmentTimingInfo
 
-         });
 
-       });
 
-       transmuxer.on('audioSegmentTimingInfo', function (timingInfo) {
 
-         // Note that all times for [audio/video]SegmentTimingInfo events are in video clock
 
-         const audioSegmentTimingInfo = {
 
-           start: {
 
-             decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
 
-             presentation: clock$2.videoTsToSeconds(timingInfo.start.pts)
 
-           },
 
-           end: {
 
-             decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
 
-             presentation: clock$2.videoTsToSeconds(timingInfo.end.pts)
 
-           },
 
-           baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
 
-         };
 
-         if (timingInfo.prependedContentDuration) {
 
-           audioSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(timingInfo.prependedContentDuration);
 
-         }
 
-         self.postMessage({
 
-           action: 'audioSegmentTimingInfo',
 
-           audioSegmentTimingInfo
 
-         });
 
-       });
 
-       transmuxer.on('id3Frame', function (id3Frame) {
 
-         self.postMessage({
 
-           action: 'id3Frame',
 
-           id3Frame
 
-         });
 
-       });
 
-       transmuxer.on('caption', function (caption) {
 
-         self.postMessage({
 
-           action: 'caption',
 
-           caption
 
-         });
 
-       });
 
-       transmuxer.on('trackinfo', function (trackInfo) {
 
-         self.postMessage({
 
-           action: 'trackinfo',
 
-           trackInfo
 
-         });
 
-       });
 
-       transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
 
-         // convert to video TS since we prioritize video time over audio
 
-         self.postMessage({
 
-           action: 'audioTimingInfo',
 
-           audioTimingInfo: {
 
-             start: clock$2.videoTsToSeconds(audioTimingInfo.start),
 
-             end: clock$2.videoTsToSeconds(audioTimingInfo.end)
 
-           }
 
-         });
 
-       });
 
-       transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
 
-         self.postMessage({
 
-           action: 'videoTimingInfo',
 
-           videoTimingInfo: {
 
-             start: clock$2.videoTsToSeconds(videoTimingInfo.start),
 
-             end: clock$2.videoTsToSeconds(videoTimingInfo.end)
 
-           }
 
-         });
 
-       });
 
-       transmuxer.on('log', function (log) {
 
-         self.postMessage({
 
-           action: 'log',
 
-           log
 
-         });
 
-       });
 
-     };
 
-     /**
 
-      * All incoming messages route through this hash. If no function exists
 
-      * to handle an incoming message, then we ignore the message.
 
-      *
 
-      * @class MessageHandlers
 
-      * @param {Object} options the options to initialize with
 
-      */
 
-     class MessageHandlers {
 
-       constructor(self, options) {
 
-         this.options = options || {};
 
-         this.self = self;
 
-         this.init();
 
-       }
 
-       /**
 
-        * initialize our web worker and wire all the events.
 
-        */
 
-       init() {
 
-         if (this.transmuxer) {
 
-           this.transmuxer.dispose();
 
-         }
 
-         this.transmuxer = new transmuxer.Transmuxer(this.options);
 
-         wireTransmuxerEvents(this.self, this.transmuxer);
 
-       }
 
-       pushMp4Captions(data) {
 
-         if (!this.captionParser) {
 
-           this.captionParser = new captionParser();
 
-           this.captionParser.init();
 
-         }
 
-         const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
 
-         const parsed = this.captionParser.parse(segment, data.trackIds, data.timescales);
 
-         this.self.postMessage({
 
-           action: 'mp4Captions',
 
-           captions: parsed && parsed.captions || [],
 
-           logs: parsed && parsed.logs || [],
 
-           data: segment.buffer
 
-         }, [segment.buffer]);
 
-       }
 
-       probeMp4StartTime({
 
-         timescales,
 
-         data
 
-       }) {
 
-         const startTime = probe$2.startTime(timescales, data);
 
-         this.self.postMessage({
 
-           action: 'probeMp4StartTime',
 
-           startTime,
 
-           data
 
-         }, [data.buffer]);
 
-       }
 
-       probeMp4Tracks({
 
-         data
 
-       }) {
 
-         const tracks = probe$2.tracks(data);
 
-         this.self.postMessage({
 
-           action: 'probeMp4Tracks',
 
-           tracks,
 
-           data
 
-         }, [data.buffer]);
 
-       }
 
-       /**
 
-        * Probe an mpeg2-ts segment to determine the start time of the segment in it's
 
-        * internal "media time," as well as whether it contains video and/or audio.
 
-        *
 
-        * @private
 
-        * @param {Uint8Array} bytes - segment bytes
 
-        * @param {number} baseStartTime
 
-        *        Relative reference timestamp used when adjusting frame timestamps for rollover.
 
-        *        This value should be in seconds, as it's converted to a 90khz clock within the
 
-        *        function body.
 
-        * @return {Object} The start time of the current segment in "media time" as well as
 
-        *                  whether it contains video and/or audio
 
-        */
 
-       probeTs({
 
-         data,
 
-         baseStartTime
 
-       }) {
 
-         const tsStartTime = typeof baseStartTime === 'number' && !isNaN(baseStartTime) ? baseStartTime * clock$2.ONE_SECOND_IN_TS : void 0;
 
-         const timeInfo = tsInspector.inspect(data, tsStartTime);
 
-         let result = null;
 
-         if (timeInfo) {
 
-           result = {
 
-             // each type's time info comes back as an array of 2 times, start and end
 
-             hasVideo: timeInfo.video && timeInfo.video.length === 2 || false,
 
-             hasAudio: timeInfo.audio && timeInfo.audio.length === 2 || false
 
-           };
 
-           if (result.hasVideo) {
 
-             result.videoStart = timeInfo.video[0].ptsTime;
 
-           }
 
-           if (result.hasAudio) {
 
-             result.audioStart = timeInfo.audio[0].ptsTime;
 
-           }
 
-         }
 
-         this.self.postMessage({
 
-           action: 'probeTs',
 
-           result,
 
-           data
 
-         }, [data.buffer]);
 
-       }
 
-       clearAllMp4Captions() {
 
-         if (this.captionParser) {
 
-           this.captionParser.clearAllCaptions();
 
-         }
 
-       }
 
-       clearParsedMp4Captions() {
 
-         if (this.captionParser) {
 
-           this.captionParser.clearParsedCaptions();
 
-         }
 
-       }
 
-       /**
 
-        * Adds data (a ts segment) to the start of the transmuxer pipeline for
 
-        * processing.
 
-        *
 
-        * @param {ArrayBuffer} data data to push into the muxer
 
-        */
 
-       push(data) {
 
-         // Cast array buffer to correct type for transmuxer
 
-         const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
 
-         this.transmuxer.push(segment);
 
-       }
 
-       /**
 
-        * Recreate the transmuxer so that the next segment added via `push`
 
-        * start with a fresh transmuxer.
 
-        */
 
-       reset() {
 
-         this.transmuxer.reset();
 
-       }
 
-       /**
 
-        * Set the value that will be used as the `baseMediaDecodeTime` time for the
 
-        * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
 
-        * set relative to the first based on the PTS values.
 
-        *
 
-        * @param {Object} data used to set the timestamp offset in the muxer
 
-        */
 
-       setTimestampOffset(data) {
 
-         const timestampOffset = data.timestampOffset || 0;
 
-         this.transmuxer.setBaseMediaDecodeTime(Math.round(clock$2.secondsToVideoTs(timestampOffset)));
 
-       }
 
-       setAudioAppendStart(data) {
 
-         this.transmuxer.setAudioAppendStart(Math.ceil(clock$2.secondsToVideoTs(data.appendStart)));
 
-       }
 
-       setRemux(data) {
 
-         this.transmuxer.setRemux(data.remux);
 
-       }
 
-       /**
 
-        * Forces the pipeline to finish processing the last segment and emit it's
 
-        * results.
 
-        *
 
-        * @param {Object} data event data, not really used
 
-        */
 
-       flush(data) {
 
-         this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed
 
-         self.postMessage({
 
-           action: 'done',
 
-           type: 'transmuxed'
 
-         });
 
-       }
 
-       endTimeline() {
 
-         this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their
 
-         // timelines
 
-         self.postMessage({
 
-           action: 'endedtimeline',
 
-           type: 'transmuxed'
 
-         });
 
-       }
 
-       alignGopsWith(data) {
 
-         this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
 
-       }
 
-     }
 
-     /**
 
-      * Our web worker interface so that things can talk to mux.js
 
-      * that will be running in a web worker. the scope is passed to this by
 
-      * webworkify.
 
-      *
 
-      * @param {Object} self the scope for the web worker
 
-      */
 
-     self.onmessage = function (event) {
 
-       if (event.data.action === 'init' && event.data.options) {
 
-         this.messageHandlers = new MessageHandlers(self, event.data.options);
 
-         return;
 
-       }
 
-       if (!this.messageHandlers) {
 
-         this.messageHandlers = new MessageHandlers(self);
 
-       }
 
-       if (event.data && event.data.action && event.data.action !== 'init') {
 
-         if (this.messageHandlers[event.data.action]) {
 
-           this.messageHandlers[event.data.action](event.data);
 
-         }
 
-       }
 
-     };
 
-   }));
 
-   var TransmuxWorker = factory(workerCode$1);
 
-   /* rollup-plugin-worker-factory end for worker!/Users/ddashkevich/projects/http-streaming/src/transmuxer-worker.js */
 
-   const handleData_ = (event, transmuxedData, callback) => {
 
-     const {
 
-       type,
 
-       initSegment,
 
-       captions,
 
-       captionStreams,
 
-       metadata,
 
-       videoFrameDtsTime,
 
-       videoFramePtsTime
 
-     } = event.data.segment;
 
-     transmuxedData.buffer.push({
 
-       captions,
 
-       captionStreams,
 
-       metadata
 
-     });
 
-     const boxes = event.data.segment.boxes || {
 
-       data: event.data.segment.data
 
-     };
 
-     const result = {
 
-       type,
 
-       // cast ArrayBuffer to TypedArray
 
-       data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength),
 
-       initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength)
 
-     };
 
-     if (typeof videoFrameDtsTime !== 'undefined') {
 
-       result.videoFrameDtsTime = videoFrameDtsTime;
 
-     }
 
-     if (typeof videoFramePtsTime !== 'undefined') {
 
-       result.videoFramePtsTime = videoFramePtsTime;
 
-     }
 
-     callback(result);
 
-   };
 
-   const handleDone_ = ({
 
-     transmuxedData,
 
-     callback
 
-   }) => {
 
-     // Previously we only returned data on data events,
 
-     // not on done events. Clear out the buffer to keep that consistent.
 
-     transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we
 
-     // have received
 
-     callback(transmuxedData);
 
-   };
 
-   const handleGopInfo_ = (event, transmuxedData) => {
 
-     transmuxedData.gopInfo = event.data.gopInfo;
 
-   };
 
-   const processTransmux = options => {
 
-     const {
 
-       transmuxer,
 
-       bytes,
 
-       audioAppendStart,
 
-       gopsToAlignWith,
 
-       remux,
 
-       onData,
 
-       onTrackInfo,
 
-       onAudioTimingInfo,
 
-       onVideoTimingInfo,
 
-       onVideoSegmentTimingInfo,
 
-       onAudioSegmentTimingInfo,
 
-       onId3,
 
-       onCaptions,
 
-       onDone,
 
-       onEndedTimeline,
 
-       onTransmuxerLog,
 
-       isEndOfTimeline
 
-     } = options;
 
-     const transmuxedData = {
 
-       buffer: []
 
-     };
 
-     let waitForEndedTimelineEvent = isEndOfTimeline;
 
-     const handleMessage = event => {
 
-       if (transmuxer.currentTransmux !== options) {
 
-         // disposed
 
-         return;
 
-       }
 
-       if (event.data.action === 'data') {
 
-         handleData_(event, transmuxedData, onData);
 
-       }
 
-       if (event.data.action === 'trackinfo') {
 
-         onTrackInfo(event.data.trackInfo);
 
-       }
 
-       if (event.data.action === 'gopInfo') {
 
-         handleGopInfo_(event, transmuxedData);
 
-       }
 
-       if (event.data.action === 'audioTimingInfo') {
 
-         onAudioTimingInfo(event.data.audioTimingInfo);
 
-       }
 
-       if (event.data.action === 'videoTimingInfo') {
 
-         onVideoTimingInfo(event.data.videoTimingInfo);
 
-       }
 
-       if (event.data.action === 'videoSegmentTimingInfo') {
 
-         onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
 
-       }
 
-       if (event.data.action === 'audioSegmentTimingInfo') {
 
-         onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo);
 
-       }
 
-       if (event.data.action === 'id3Frame') {
 
-         onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
 
-       }
 
-       if (event.data.action === 'caption') {
 
-         onCaptions(event.data.caption);
 
-       }
 
-       if (event.data.action === 'endedtimeline') {
 
-         waitForEndedTimelineEvent = false;
 
-         onEndedTimeline();
 
-       }
 
-       if (event.data.action === 'log') {
 
-         onTransmuxerLog(event.data.log);
 
-       } // wait for the transmuxed event since we may have audio and video
 
-       if (event.data.type !== 'transmuxed') {
 
-         return;
 
-       } // If the "endedtimeline" event has not yet fired, and this segment represents the end
 
-       // of a timeline, that means there may still be data events before the segment
 
-       // processing can be considerred complete. In that case, the final event should be
 
-       // an "endedtimeline" event with the type "transmuxed."
 
-       if (waitForEndedTimelineEvent) {
 
-         return;
 
-       }
 
-       transmuxer.onmessage = null;
 
-       handleDone_({
 
-         transmuxedData,
 
-         callback: onDone
 
-       });
 
-       /* eslint-disable no-use-before-define */
 
-       dequeue(transmuxer);
 
-       /* eslint-enable */
 
-     };
 
-     transmuxer.onmessage = handleMessage;
 
-     if (audioAppendStart) {
 
-       transmuxer.postMessage({
 
-         action: 'setAudioAppendStart',
 
-         appendStart: audioAppendStart
 
-       });
 
-     } // allow empty arrays to be passed to clear out GOPs
 
-     if (Array.isArray(gopsToAlignWith)) {
 
-       transmuxer.postMessage({
 
-         action: 'alignGopsWith',
 
-         gopsToAlignWith
 
-       });
 
-     }
 
-     if (typeof remux !== 'undefined') {
 
-       transmuxer.postMessage({
 
-         action: 'setRemux',
 
-         remux
 
-       });
 
-     }
 
-     if (bytes.byteLength) {
 
-       const buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
 
-       const byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
 
-       transmuxer.postMessage({
 
-         action: 'push',
 
-         // Send the typed-array of data as an ArrayBuffer so that
 
-         // it can be sent as a "Transferable" and avoid the costly
 
-         // memory copy
 
-         data: buffer,
 
-         // To recreate the original typed-array, we need information
 
-         // about what portion of the ArrayBuffer it was a view into
 
-         byteOffset,
 
-         byteLength: bytes.byteLength
 
-       }, [buffer]);
 
-     }
 
-     if (isEndOfTimeline) {
 
-       transmuxer.postMessage({
 
-         action: 'endTimeline'
 
-       });
 
-     } // even if we didn't push any bytes, we have to make sure we flush in case we reached
 
-     // the end of the segment
 
-     transmuxer.postMessage({
 
-       action: 'flush'
 
-     });
 
-   };
 
-   const dequeue = transmuxer => {
 
-     transmuxer.currentTransmux = null;
 
-     if (transmuxer.transmuxQueue.length) {
 
-       transmuxer.currentTransmux = transmuxer.transmuxQueue.shift();
 
-       if (typeof transmuxer.currentTransmux === 'function') {
 
-         transmuxer.currentTransmux();
 
-       } else {
 
-         processTransmux(transmuxer.currentTransmux);
 
-       }
 
-     }
 
-   };
 
-   const processAction = (transmuxer, action) => {
 
-     transmuxer.postMessage({
 
-       action
 
-     });
 
-     dequeue(transmuxer);
 
-   };
 
-   const enqueueAction = (action, transmuxer) => {
 
-     if (!transmuxer.currentTransmux) {
 
-       transmuxer.currentTransmux = action;
 
-       processAction(transmuxer, action);
 
-       return;
 
-     }
 
-     transmuxer.transmuxQueue.push(processAction.bind(null, transmuxer, action));
 
-   };
 
-   const reset = transmuxer => {
 
-     enqueueAction('reset', transmuxer);
 
-   };
 
-   const endTimeline = transmuxer => {
 
-     enqueueAction('endTimeline', transmuxer);
 
-   };
 
-   const transmux = options => {
 
-     if (!options.transmuxer.currentTransmux) {
 
-       options.transmuxer.currentTransmux = options;
 
-       processTransmux(options);
 
-       return;
 
-     }
 
-     options.transmuxer.transmuxQueue.push(options);
 
-   };
 
-   const createTransmuxer = options => {
 
-     const transmuxer = new TransmuxWorker();
 
-     transmuxer.currentTransmux = null;
 
-     transmuxer.transmuxQueue = [];
 
-     const term = transmuxer.terminate;
 
-     transmuxer.terminate = () => {
 
-       transmuxer.currentTransmux = null;
 
-       transmuxer.transmuxQueue.length = 0;
 
-       return term.call(transmuxer);
 
-     };
 
-     transmuxer.postMessage({
 
-       action: 'init',
 
-       options
 
-     });
 
-     return transmuxer;
 
-   };
 
-   var segmentTransmuxer = {
 
-     reset,
 
-     endTimeline,
 
-     transmux,
 
-     createTransmuxer
 
-   };
 
-   const workerCallback = function (options) {
 
-     const transmuxer = options.transmuxer;
 
-     const endAction = options.endAction || options.action;
 
-     const callback = options.callback;
 
-     const message = _extends$1({}, options, {
 
-       endAction: null,
 
-       transmuxer: null,
 
-       callback: null
 
-     });
 
-     const listenForEndEvent = event => {
 
-       if (event.data.action !== endAction) {
 
-         return;
 
-       }
 
-       transmuxer.removeEventListener('message', listenForEndEvent); // transfer ownership of bytes back to us.
 
-       if (event.data.data) {
 
-         event.data.data = new Uint8Array(event.data.data, options.byteOffset || 0, options.byteLength || event.data.data.byteLength);
 
-         if (options.data) {
 
-           options.data = event.data.data;
 
-         }
 
-       }
 
-       callback(event.data);
 
-     };
 
-     transmuxer.addEventListener('message', listenForEndEvent);
 
-     if (options.data) {
 
-       const isArrayBuffer = options.data instanceof ArrayBuffer;
 
-       message.byteOffset = isArrayBuffer ? 0 : options.data.byteOffset;
 
-       message.byteLength = options.data.byteLength;
 
-       const transfers = [isArrayBuffer ? options.data : options.data.buffer];
 
-       transmuxer.postMessage(message, transfers);
 
-     } else {
 
-       transmuxer.postMessage(message);
 
-     }
 
-   };
 
-   const REQUEST_ERRORS = {
 
-     FAILURE: 2,
 
-     TIMEOUT: -101,
 
-     ABORTED: -102
 
-   };
 
-   /**
 
-    * Abort all requests
 
-    *
 
-    * @param {Object} activeXhrs - an object that tracks all XHR requests
 
-    */
 
-   const abortAll = activeXhrs => {
 
-     activeXhrs.forEach(xhr => {
 
-       xhr.abort();
 
-     });
 
-   };
 
-   /**
 
-    * Gather important bandwidth stats once a request has completed
 
-    *
 
-    * @param {Object} request - the XHR request from which to gather stats
 
-    */
 
-   const getRequestStats = request => {
 
-     return {
 
-       bandwidth: request.bandwidth,
 
-       bytesReceived: request.bytesReceived || 0,
 
-       roundTripTime: request.roundTripTime || 0
 
-     };
 
-   };
 
-   /**
 
-    * If possible gather bandwidth stats as a request is in
 
-    * progress
 
-    *
 
-    * @param {Event} progressEvent - an event object from an XHR's progress event
 
-    */
 
-   const getProgressStats = progressEvent => {
 
-     const request = progressEvent.target;
 
-     const roundTripTime = Date.now() - request.requestTime;
 
-     const stats = {
 
-       bandwidth: Infinity,
 
-       bytesReceived: 0,
 
-       roundTripTime: roundTripTime || 0
 
-     };
 
-     stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
 
-     // because we should only use bandwidth stats on progress to determine when
 
-     // abort a request early due to insufficient bandwidth
 
-     stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
 
-     return stats;
 
-   };
 
-   /**
 
-    * Handle all error conditions in one place and return an object
 
-    * with all the information
 
-    *
 
-    * @param {Error|null} error - if non-null signals an error occured with the XHR
 
-    * @param {Object} request -  the XHR request that possibly generated the error
 
-    */
 
-   const handleErrors = (error, request) => {
 
-     if (request.timedout) {
 
-       return {
 
-         status: request.status,
 
-         message: 'HLS request timed-out at URL: ' + request.uri,
 
-         code: REQUEST_ERRORS.TIMEOUT,
 
-         xhr: request
 
-       };
 
-     }
 
-     if (request.aborted) {
 
-       return {
 
-         status: request.status,
 
-         message: 'HLS request aborted at URL: ' + request.uri,
 
-         code: REQUEST_ERRORS.ABORTED,
 
-         xhr: request
 
-       };
 
-     }
 
-     if (error) {
 
-       return {
 
-         status: request.status,
 
-         message: 'HLS request errored at URL: ' + request.uri,
 
-         code: REQUEST_ERRORS.FAILURE,
 
-         xhr: request
 
-       };
 
-     }
 
-     if (request.responseType === 'arraybuffer' && request.response.byteLength === 0) {
 
-       return {
 
-         status: request.status,
 
-         message: 'Empty HLS response at URL: ' + request.uri,
 
-         code: REQUEST_ERRORS.FAILURE,
 
-         xhr: request
 
-       };
 
-     }
 
-     return null;
 
-   };
 
-   /**
 
-    * Handle responses for key data and convert the key data to the correct format
 
-    * for the decryption step later
 
-    *
 
-    * @param {Object} segment - a simplified copy of the segmentInfo object
 
-    *                           from SegmentLoader
 
-    * @param {Array} objects - objects to add the key bytes to.
 
-    * @param {Function} finishProcessingFn - a callback to execute to continue processing
 
-    *                                        this request
 
-    */
 
-   const handleKeyResponse = (segment, objects, finishProcessingFn) => (error, request) => {
 
-     const response = request.response;
 
-     const errorObj = handleErrors(error, request);
 
-     if (errorObj) {
 
-       return finishProcessingFn(errorObj, segment);
 
-     }
 
-     if (response.byteLength !== 16) {
 
-       return finishProcessingFn({
 
-         status: request.status,
 
-         message: 'Invalid HLS key at URL: ' + request.uri,
 
-         code: REQUEST_ERRORS.FAILURE,
 
-         xhr: request
 
-       }, segment);
 
-     }
 
-     const view = new DataView(response);
 
-     const bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
 
-     for (let i = 0; i < objects.length; i++) {
 
-       objects[i].bytes = bytes;
 
-     }
 
-     return finishProcessingFn(null, segment);
 
-   };
 
-   const parseInitSegment = (segment, callback) => {
 
-     const type = detectContainerForBytes(segment.map.bytes); // TODO: We should also handle ts init segments here, but we
 
-     // only know how to parse mp4 init segments at the moment
 
-     if (type !== 'mp4') {
 
-       const uri = segment.map.resolvedUri || segment.map.uri;
 
-       return callback({
 
-         internal: true,
 
-         message: `Found unsupported ${type || 'unknown'} container for initialization segment at URL: ${uri}`,
 
-         code: REQUEST_ERRORS.FAILURE
 
-       });
 
-     }
 
-     workerCallback({
 
-       action: 'probeMp4Tracks',
 
-       data: segment.map.bytes,
 
-       transmuxer: segment.transmuxer,
 
-       callback: ({
 
-         tracks,
 
-         data
 
-       }) => {
 
-         // transfer bytes back to us
 
-         segment.map.bytes = data;
 
-         tracks.forEach(function (track) {
 
-           segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now
 
-           if (segment.map.tracks[track.type]) {
 
-             return;
 
-           }
 
-           segment.map.tracks[track.type] = track;
 
-           if (typeof track.id === 'number' && track.timescale) {
 
-             segment.map.timescales = segment.map.timescales || {};
 
-             segment.map.timescales[track.id] = track.timescale;
 
-           }
 
-         });
 
-         return callback(null);
 
-       }
 
-     });
 
-   };
 
-   /**
 
-    * Handle init-segment responses
 
-    *
 
-    * @param {Object} segment - a simplified copy of the segmentInfo object
 
-    *                           from SegmentLoader
 
-    * @param {Function} finishProcessingFn - a callback to execute to continue processing
 
-    *                                        this request
 
-    */
 
-   const handleInitSegmentResponse = ({
 
-     segment,
 
-     finishProcessingFn
 
-   }) => (error, request) => {
 
-     const errorObj = handleErrors(error, request);
 
-     if (errorObj) {
 
-       return finishProcessingFn(errorObj, segment);
 
-     }
 
-     const bytes = new Uint8Array(request.response); // init segment is encypted, we will have to wait
 
-     // until the key request is done to decrypt.
 
-     if (segment.map.key) {
 
-       segment.map.encryptedBytes = bytes;
 
-       return finishProcessingFn(null, segment);
 
-     }
 
-     segment.map.bytes = bytes;
 
-     parseInitSegment(segment, function (parseError) {
 
-       if (parseError) {
 
-         parseError.xhr = request;
 
-         parseError.status = request.status;
 
-         return finishProcessingFn(parseError, segment);
 
-       }
 
-       finishProcessingFn(null, segment);
 
-     });
 
-   };
 
-   /**
 
-    * Response handler for segment-requests being sure to set the correct
 
-    * property depending on whether the segment is encryped or not
 
-    * Also records and keeps track of stats that are used for ABR purposes
 
-    *
 
-    * @param {Object} segment - a simplified copy of the segmentInfo object
 
-    *                           from SegmentLoader
 
-    * @param {Function} finishProcessingFn - a callback to execute to continue processing
 
-    *                                        this request
 
-    */
 
-   const handleSegmentResponse = ({
 
-     segment,
 
-     finishProcessingFn,
 
-     responseType
 
-   }) => (error, request) => {
 
-     const errorObj = handleErrors(error, request);
 
-     if (errorObj) {
 
-       return finishProcessingFn(errorObj, segment);
 
-     }
 
-     const newBytes =
 
-     // although responseText "should" exist, this guard serves to prevent an error being
 
-     // thrown for two primary cases:
 
-     // 1. the mime type override stops working, or is not implemented for a specific
 
-     //    browser
 
-     // 2. when using mock XHR libraries like sinon that do not allow the override behavior
 
-     responseType === 'arraybuffer' || !request.responseText ? request.response : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0));
 
-     segment.stats = getRequestStats(request);
 
-     if (segment.key) {
 
-       segment.encryptedBytes = new Uint8Array(newBytes);
 
-     } else {
 
-       segment.bytes = new Uint8Array(newBytes);
 
-     }
 
-     return finishProcessingFn(null, segment);
 
-   };
 
-   const transmuxAndNotify = ({
 
-     segment,
 
-     bytes,
 
-     trackInfoFn,
 
-     timingInfoFn,
 
-     videoSegmentTimingInfoFn,
 
-     audioSegmentTimingInfoFn,
 
-     id3Fn,
 
-     captionsFn,
 
-     isEndOfTimeline,
 
-     endedTimelineFn,
 
-     dataFn,
 
-     doneFn,
 
-     onTransmuxerLog
 
-   }) => {
 
-     const fmp4Tracks = segment.map && segment.map.tracks || {};
 
-     const isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them.
 
-     // One reason for this is that in the case of full segments, we want to trust start
 
-     // times from the probe, rather than the transmuxer.
 
-     let audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start');
 
-     const audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end');
 
-     let videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start');
 
-     const videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end');
 
-     const finish = () => transmux({
 
-       bytes,
 
-       transmuxer: segment.transmuxer,
 
-       audioAppendStart: segment.audioAppendStart,
 
-       gopsToAlignWith: segment.gopsToAlignWith,
 
-       remux: isMuxed,
 
-       onData: result => {
 
-         result.type = result.type === 'combined' ? 'video' : result.type;
 
-         dataFn(segment, result);
 
-       },
 
-       onTrackInfo: trackInfo => {
 
-         if (trackInfoFn) {
 
-           if (isMuxed) {
 
-             trackInfo.isMuxed = true;
 
-           }
 
-           trackInfoFn(segment, trackInfo);
 
-         }
 
-       },
 
-       onAudioTimingInfo: audioTimingInfo => {
 
-         // we only want the first start value we encounter
 
-         if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') {
 
-           audioStartFn(audioTimingInfo.start);
 
-           audioStartFn = null;
 
-         } // we want to continually update the end time
 
-         if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') {
 
-           audioEndFn(audioTimingInfo.end);
 
-         }
 
-       },
 
-       onVideoTimingInfo: videoTimingInfo => {
 
-         // we only want the first start value we encounter
 
-         if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') {
 
-           videoStartFn(videoTimingInfo.start);
 
-           videoStartFn = null;
 
-         } // we want to continually update the end time
 
-         if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') {
 
-           videoEndFn(videoTimingInfo.end);
 
-         }
 
-       },
 
-       onVideoSegmentTimingInfo: videoSegmentTimingInfo => {
 
-         videoSegmentTimingInfoFn(videoSegmentTimingInfo);
 
-       },
 
-       onAudioSegmentTimingInfo: audioSegmentTimingInfo => {
 
-         audioSegmentTimingInfoFn(audioSegmentTimingInfo);
 
-       },
 
-       onId3: (id3Frames, dispatchType) => {
 
-         id3Fn(segment, id3Frames, dispatchType);
 
-       },
 
-       onCaptions: captions => {
 
-         captionsFn(segment, [captions]);
 
-       },
 
-       isEndOfTimeline,
 
-       onEndedTimeline: () => {
 
-         endedTimelineFn();
 
-       },
 
-       onTransmuxerLog,
 
-       onDone: result => {
 
-         if (!doneFn) {
 
-           return;
 
-         }
 
-         result.type = result.type === 'combined' ? 'video' : result.type;
 
-         doneFn(null, segment, result);
 
-       }
 
-     }); // In the transmuxer, we don't yet have the ability to extract a "proper" start time.
 
-     // Meaning cached frame data may corrupt our notion of where this segment
 
-     // really starts. To get around this, probe for the info needed.
 
-     workerCallback({
 
-       action: 'probeTs',
 
-       transmuxer: segment.transmuxer,
 
-       data: bytes,
 
-       baseStartTime: segment.baseStartTime,
 
-       callback: data => {
 
-         segment.bytes = bytes = data.data;
 
-         const probeResult = data.result;
 
-         if (probeResult) {
 
-           trackInfoFn(segment, {
 
-             hasAudio: probeResult.hasAudio,
 
-             hasVideo: probeResult.hasVideo,
 
-             isMuxed
 
-           });
 
-           trackInfoFn = null;
 
-           if (probeResult.hasAudio && !isMuxed) {
 
-             audioStartFn(probeResult.audioStart);
 
-           }
 
-           if (probeResult.hasVideo) {
 
-             videoStartFn(probeResult.videoStart);
 
-           }
 
-           audioStartFn = null;
 
-           videoStartFn = null;
 
-         }
 
-         finish();
 
-       }
 
-     });
 
-   };
 
-   const handleSegmentBytes = ({
 
-     segment,
 
-     bytes,
 
-     trackInfoFn,
 
-     timingInfoFn,
 
-     videoSegmentTimingInfoFn,
 
-     audioSegmentTimingInfoFn,
 
-     id3Fn,
 
-     captionsFn,
 
-     isEndOfTimeline,
 
-     endedTimelineFn,
 
-     dataFn,
 
-     doneFn,
 
-     onTransmuxerLog
 
-   }) => {
 
-     let bytesAsUint8Array = new Uint8Array(bytes); // TODO:
 
-     // We should have a handler that fetches the number of bytes required
 
-     // to check if something is fmp4. This will allow us to save bandwidth
 
-     // because we can only exclude a playlist and abort requests
 
-     // by codec after trackinfo triggers.
 
-     if (isLikelyFmp4MediaSegment(bytesAsUint8Array)) {
 
-       segment.isFmp4 = true;
 
-       const {
 
-         tracks
 
-       } = segment.map;
 
-       const trackInfo = {
 
-         isFmp4: true,
 
-         hasVideo: !!tracks.video,
 
-         hasAudio: !!tracks.audio
 
-       }; // if we have a audio track, with a codec that is not set to
 
-       // encrypted audio
 
-       if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') {
 
-         trackInfo.audioCodec = tracks.audio.codec;
 
-       } // if we have a video track, with a codec that is not set to
 
-       // encrypted video
 
-       if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') {
 
-         trackInfo.videoCodec = tracks.video.codec;
 
-       }
 
-       if (tracks.video && tracks.audio) {
 
-         trackInfo.isMuxed = true;
 
-       } // since we don't support appending fmp4 data on progress, we know we have the full
 
-       // segment here
 
-       trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start
 
-       // time. The end time can be roughly calculated by the receiver using the duration.
 
-       //
 
-       // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as
 
-       // that is the true start of the segment (where the playback engine should begin
 
-       // decoding).
 
-       const finishLoading = captions => {
 
-         // if the track still has audio at this point it is only possible
 
-         // for it to be audio only. See `tracks.video && tracks.audio` if statement
 
-         // above.
 
-         // we make sure to use segment.bytes here as that
 
-         dataFn(segment, {
 
-           data: bytesAsUint8Array,
 
-           type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video'
 
-         });
 
-         if (captions && captions.length) {
 
-           captionsFn(segment, captions);
 
-         }
 
-         doneFn(null, segment, {});
 
-       };
 
-       workerCallback({
 
-         action: 'probeMp4StartTime',
 
-         timescales: segment.map.timescales,
 
-         data: bytesAsUint8Array,
 
-         transmuxer: segment.transmuxer,
 
-         callback: ({
 
-           data,
 
-           startTime
 
-         }) => {
 
-           // transfer bytes back to us
 
-           bytes = data.buffer;
 
-           segment.bytes = bytesAsUint8Array = data;
 
-           if (trackInfo.hasAudio && !trackInfo.isMuxed) {
 
-             timingInfoFn(segment, 'audio', 'start', startTime);
 
-           }
 
-           if (trackInfo.hasVideo) {
 
-             timingInfoFn(segment, 'video', 'start', startTime);
 
-           } // Run through the CaptionParser in case there are captions.
 
-           // Initialize CaptionParser if it hasn't been yet
 
-           if (!tracks.video || !data.byteLength || !segment.transmuxer) {
 
-             finishLoading();
 
-             return;
 
-           }
 
-           workerCallback({
 
-             action: 'pushMp4Captions',
 
-             endAction: 'mp4Captions',
 
-             transmuxer: segment.transmuxer,
 
-             data: bytesAsUint8Array,
 
-             timescales: segment.map.timescales,
 
-             trackIds: [tracks.video.id],
 
-             callback: message => {
 
-               // transfer bytes back to us
 
-               bytes = message.data.buffer;
 
-               segment.bytes = bytesAsUint8Array = message.data;
 
-               message.logs.forEach(function (log) {
 
-                 onTransmuxerLog(merge(log, {
 
-                   stream: 'mp4CaptionParser'
 
-                 }));
 
-               });
 
-               finishLoading(message.captions);
 
-             }
 
-           });
 
-         }
 
-       });
 
-       return;
 
-     } // VTT or other segments that don't need processing
 
-     if (!segment.transmuxer) {
 
-       doneFn(null, segment, {});
 
-       return;
 
-     }
 
-     if (typeof segment.container === 'undefined') {
 
-       segment.container = detectContainerForBytes(bytesAsUint8Array);
 
-     }
 
-     if (segment.container !== 'ts' && segment.container !== 'aac') {
 
-       trackInfoFn(segment, {
 
-         hasAudio: false,
 
-         hasVideo: false
 
-       });
 
-       doneFn(null, segment, {});
 
-       return;
 
-     } // ts or aac
 
-     transmuxAndNotify({
 
-       segment,
 
-       bytes,
 
-       trackInfoFn,
 
-       timingInfoFn,
 
-       videoSegmentTimingInfoFn,
 
-       audioSegmentTimingInfoFn,
 
-       id3Fn,
 
-       captionsFn,
 
-       isEndOfTimeline,
 
-       endedTimelineFn,
 
-       dataFn,
 
-       doneFn,
 
-       onTransmuxerLog
 
-     });
 
-   };
 
-   const decrypt = function ({
 
-     id,
 
-     key,
 
-     encryptedBytes,
 
-     decryptionWorker
 
-   }, callback) {
 
-     const decryptionHandler = event => {
 
-       if (event.data.source === id) {
 
-         decryptionWorker.removeEventListener('message', decryptionHandler);
 
-         const decrypted = event.data.decrypted;
 
-         callback(new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength));
 
-       }
 
-     };
 
-     decryptionWorker.addEventListener('message', decryptionHandler);
 
-     let keyBytes;
 
-     if (key.bytes.slice) {
 
-       keyBytes = key.bytes.slice();
 
-     } else {
 
-       keyBytes = new Uint32Array(Array.prototype.slice.call(key.bytes));
 
-     } // incrementally decrypt the bytes
 
-     decryptionWorker.postMessage(createTransferableMessage({
 
-       source: id,
 
-       encrypted: encryptedBytes,
 
-       key: keyBytes,
 
-       iv: key.iv
 
-     }), [encryptedBytes.buffer, keyBytes.buffer]);
 
-   };
 
-   /**
 
-    * Decrypt the segment via the decryption web worker
 
-    *
 
-    * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
 
-    *                                       routines
 
-    * @param {Object} segment - a simplified copy of the segmentInfo object
 
-    *                           from SegmentLoader
 
-    * @param {Function} trackInfoFn - a callback that receives track info
 
-    * @param {Function} timingInfoFn - a callback that receives timing info
 
-    * @param {Function} videoSegmentTimingInfoFn
 
-    *                   a callback that receives video timing info based on media times and
 
-    *                   any adjustments made by the transmuxer
 
-    * @param {Function} audioSegmentTimingInfoFn
 
-    *                   a callback that receives audio timing info based on media times and
 
-    *                   any adjustments made by the transmuxer
 
-    * @param {boolean}  isEndOfTimeline
 
-    *                   true if this segment represents the last segment in a timeline
 
-    * @param {Function} endedTimelineFn
 
-    *                   a callback made when a timeline is ended, will only be called if
 
-    *                   isEndOfTimeline is true
 
-    * @param {Function} dataFn - a callback that is executed when segment bytes are available
 
-    *                            and ready to use
 
-    * @param {Function} doneFn - a callback that is executed after decryption has completed
 
-    */
 
-   const decryptSegment = ({
 
-     decryptionWorker,
 
-     segment,
 
-     trackInfoFn,
 
-     timingInfoFn,
 
-     videoSegmentTimingInfoFn,
 
-     audioSegmentTimingInfoFn,
 
-     id3Fn,
 
-     captionsFn,
 
-     isEndOfTimeline,
 
-     endedTimelineFn,
 
-     dataFn,
 
-     doneFn,
 
-     onTransmuxerLog
 
-   }) => {
 
-     decrypt({
 
-       id: segment.requestId,
 
-       key: segment.key,
 
-       encryptedBytes: segment.encryptedBytes,
 
-       decryptionWorker
 
-     }, decryptedBytes => {
 
-       segment.bytes = decryptedBytes;
 
-       handleSegmentBytes({
 
-         segment,
 
-         bytes: segment.bytes,
 
-         trackInfoFn,
 
-         timingInfoFn,
 
-         videoSegmentTimingInfoFn,
 
-         audioSegmentTimingInfoFn,
 
-         id3Fn,
 
-         captionsFn,
 
-         isEndOfTimeline,
 
-         endedTimelineFn,
 
-         dataFn,
 
-         doneFn,
 
-         onTransmuxerLog
 
-       });
 
-     });
 
-   };
 
-   /**
 
-    * This function waits for all XHRs to finish (with either success or failure)
 
-    * before continueing processing via it's callback. The function gathers errors
 
-    * from each request into a single errors array so that the error status for
 
-    * each request can be examined later.
 
-    *
 
-    * @param {Object} activeXhrs - an object that tracks all XHR requests
 
-    * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
 
-    *                                       routines
 
-    * @param {Function} trackInfoFn - a callback that receives track info
 
-    * @param {Function} timingInfoFn - a callback that receives timing info
 
-    * @param {Function} videoSegmentTimingInfoFn
 
-    *                   a callback that receives video timing info based on media times and
 
-    *                   any adjustments made by the transmuxer
 
-    * @param {Function} audioSegmentTimingInfoFn
 
-    *                   a callback that receives audio timing info based on media times and
 
-    *                   any adjustments made by the transmuxer
 
-    * @param {Function} id3Fn - a callback that receives ID3 metadata
 
-    * @param {Function} captionsFn - a callback that receives captions
 
-    * @param {boolean}  isEndOfTimeline
 
-    *                   true if this segment represents the last segment in a timeline
 
-    * @param {Function} endedTimelineFn
 
-    *                   a callback made when a timeline is ended, will only be called if
 
-    *                   isEndOfTimeline is true
 
-    * @param {Function} dataFn - a callback that is executed when segment bytes are available
 
-    *                            and ready to use
 
-    * @param {Function} doneFn - a callback that is executed after all resources have been
 
-    *                            downloaded and any decryption completed
 
-    */
 
-   const waitForCompletion = ({
 
-     activeXhrs,
 
-     decryptionWorker,
 
-     trackInfoFn,
 
-     timingInfoFn,
 
-     videoSegmentTimingInfoFn,
 
-     audioSegmentTimingInfoFn,
 
-     id3Fn,
 
-     captionsFn,
 
-     isEndOfTimeline,
 
-     endedTimelineFn,
 
-     dataFn,
 
-     doneFn,
 
-     onTransmuxerLog
 
-   }) => {
 
-     let count = 0;
 
-     let didError = false;
 
-     return (error, segment) => {
 
-       if (didError) {
 
-         return;
 
-       }
 
-       if (error) {
 
-         didError = true; // If there are errors, we have to abort any outstanding requests
 
-         abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
 
-         // handle the aborted events from those requests, there are some cases where we may
 
-         // never get an aborted event. For instance, if the network connection is lost and
 
-         // there were two requests, the first may have triggered an error immediately, while
 
-         // the second request remains unsent. In that case, the aborted algorithm will not
 
-         // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
 
-         //
 
-         // We also can't rely on the ready state of the XHR, since the request that
 
-         // triggered the connection error may also show as a ready state of 0 (unsent).
 
-         // Therefore, we have to finish this group of requests immediately after the first
 
-         // seen error.
 
-         return doneFn(error, segment);
 
-       }
 
-       count += 1;
 
-       if (count === activeXhrs.length) {
 
-         const segmentFinish = function () {
 
-           if (segment.encryptedBytes) {
 
-             return decryptSegment({
 
-               decryptionWorker,
 
-               segment,
 
-               trackInfoFn,
 
-               timingInfoFn,
 
-               videoSegmentTimingInfoFn,
 
-               audioSegmentTimingInfoFn,
 
-               id3Fn,
 
-               captionsFn,
 
-               isEndOfTimeline,
 
-               endedTimelineFn,
 
-               dataFn,
 
-               doneFn,
 
-               onTransmuxerLog
 
-             });
 
-           } // Otherwise, everything is ready just continue
 
-           handleSegmentBytes({
 
-             segment,
 
-             bytes: segment.bytes,
 
-             trackInfoFn,
 
-             timingInfoFn,
 
-             videoSegmentTimingInfoFn,
 
-             audioSegmentTimingInfoFn,
 
-             id3Fn,
 
-             captionsFn,
 
-             isEndOfTimeline,
 
-             endedTimelineFn,
 
-             dataFn,
 
-             doneFn,
 
-             onTransmuxerLog
 
-           });
 
-         }; // Keep track of when *all* of the requests have completed
 
-         segment.endOfAllRequests = Date.now();
 
-         if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) {
 
-           return decrypt({
 
-             decryptionWorker,
 
-             // add -init to the "id" to differentiate between segment
 
-             // and init segment decryption, just in case they happen
 
-             // at the same time at some point in the future.
 
-             id: segment.requestId + '-init',
 
-             encryptedBytes: segment.map.encryptedBytes,
 
-             key: segment.map.key
 
-           }, decryptedBytes => {
 
-             segment.map.bytes = decryptedBytes;
 
-             parseInitSegment(segment, parseError => {
 
-               if (parseError) {
 
-                 abortAll(activeXhrs);
 
-                 return doneFn(parseError, segment);
 
-               }
 
-               segmentFinish();
 
-             });
 
-           });
 
-         }
 
-         segmentFinish();
 
-       }
 
-     };
 
-   };
 
-   /**
 
-    * Calls the abort callback if any request within the batch was aborted. Will only call
 
-    * the callback once per batch of requests, even if multiple were aborted.
 
-    *
 
-    * @param {Object} loadendState - state to check to see if the abort function was called
 
-    * @param {Function} abortFn - callback to call for abort
 
-    */
 
-   const handleLoadEnd = ({
 
-     loadendState,
 
-     abortFn
 
-   }) => event => {
 
-     const request = event.target;
 
-     if (request.aborted && abortFn && !loadendState.calledAbortFn) {
 
-       abortFn();
 
-       loadendState.calledAbortFn = true;
 
-     }
 
-   };
 
-   /**
 
-    * Simple progress event callback handler that gathers some stats before
 
-    * executing a provided callback with the `segment` object
 
-    *
 
-    * @param {Object} segment - a simplified copy of the segmentInfo object
 
-    *                           from SegmentLoader
 
-    * @param {Function} progressFn - a callback that is executed each time a progress event
 
-    *                                is received
 
-    * @param {Function} trackInfoFn - a callback that receives track info
 
-    * @param {Function} timingInfoFn - a callback that receives timing info
 
-    * @param {Function} videoSegmentTimingInfoFn
 
-    *                   a callback that receives video timing info based on media times and
 
-    *                   any adjustments made by the transmuxer
 
-    * @param {Function} audioSegmentTimingInfoFn
 
-    *                   a callback that receives audio timing info based on media times and
 
-    *                   any adjustments made by the transmuxer
 
-    * @param {boolean}  isEndOfTimeline
 
-    *                   true if this segment represents the last segment in a timeline
 
-    * @param {Function} endedTimelineFn
 
-    *                   a callback made when a timeline is ended, will only be called if
 
-    *                   isEndOfTimeline is true
 
-    * @param {Function} dataFn - a callback that is executed when segment bytes are available
 
-    *                            and ready to use
 
-    * @param {Event} event - the progress event object from XMLHttpRequest
 
-    */
 
-   const handleProgress = ({
 
-     segment,
 
-     progressFn,
 
-     trackInfoFn,
 
-     timingInfoFn,
 
-     videoSegmentTimingInfoFn,
 
-     audioSegmentTimingInfoFn,
 
-     id3Fn,
 
-     captionsFn,
 
-     isEndOfTimeline,
 
-     endedTimelineFn,
 
-     dataFn
 
-   }) => event => {
 
-     const request = event.target;
 
-     if (request.aborted) {
 
-       return;
 
-     }
 
-     segment.stats = merge(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
 
-     if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
 
-       segment.stats.firstBytesReceivedAt = Date.now();
 
-     }
 
-     return progressFn(event, segment);
 
-   };
 
-   /**
 
-    * Load all resources and does any processing necessary for a media-segment
 
-    *
 
-    * Features:
 
-    *   decrypts the media-segment if it has a key uri and an iv
 
-    *   aborts *all* requests if *any* one request fails
 
-    *
 
-    * The segment object, at minimum, has the following format:
 
-    * {
 
-    *   resolvedUri: String,
 
-    *   [transmuxer]: Object,
 
-    *   [byterange]: {
 
-    *     offset: Number,
 
-    *     length: Number
 
-    *   },
 
-    *   [key]: {
 
-    *     resolvedUri: String
 
-    *     [byterange]: {
 
-    *       offset: Number,
 
-    *       length: Number
 
-    *     },
 
-    *     iv: {
 
-    *       bytes: Uint32Array
 
-    *     }
 
-    *   },
 
-    *   [map]: {
 
-    *     resolvedUri: String,
 
-    *     [byterange]: {
 
-    *       offset: Number,
 
-    *       length: Number
 
-    *     },
 
-    *     [bytes]: Uint8Array
 
-    *   }
 
-    * }
 
-    * ...where [name] denotes optional properties
 
-    *
 
-    * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
 
-    * @param {Object} xhrOptions - the base options to provide to all xhr requests
 
-    * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
 
-    *                                       decryption routines
 
-    * @param {Object} segment - a simplified copy of the segmentInfo object
 
-    *                           from SegmentLoader
 
-    * @param {Function} abortFn - a callback called (only once) if any piece of a request was
 
-    *                             aborted
 
-    * @param {Function} progressFn - a callback that receives progress events from the main
 
-    *                                segment's xhr request
 
-    * @param {Function} trackInfoFn - a callback that receives track info
 
-    * @param {Function} timingInfoFn - a callback that receives timing info
 
-    * @param {Function} videoSegmentTimingInfoFn
 
-    *                   a callback that receives video timing info based on media times and
 
-    *                   any adjustments made by the transmuxer
 
-    * @param {Function} audioSegmentTimingInfoFn
 
-    *                   a callback that receives audio timing info based on media times and
 
-    *                   any adjustments made by the transmuxer
 
-    * @param {Function} id3Fn - a callback that receives ID3 metadata
 
-    * @param {Function} captionsFn - a callback that receives captions
 
-    * @param {boolean}  isEndOfTimeline
 
-    *                   true if this segment represents the last segment in a timeline
 
-    * @param {Function} endedTimelineFn
 
-    *                   a callback made when a timeline is ended, will only be called if
 
-    *                   isEndOfTimeline is true
 
-    * @param {Function} dataFn - a callback that receives data from the main segment's xhr
 
-    *                            request, transmuxed if needed
 
-    * @param {Function} doneFn - a callback that is executed only once all requests have
 
-    *                            succeeded or failed
 
-    * @return {Function} a function that, when invoked, immediately aborts all
 
-    *                     outstanding requests
 
-    */
 
-   const mediaSegmentRequest = ({
 
-     xhr,
 
-     xhrOptions,
 
-     decryptionWorker,
 
-     segment,
 
-     abortFn,
 
-     progressFn,
 
-     trackInfoFn,
 
-     timingInfoFn,
 
-     videoSegmentTimingInfoFn,
 
-     audioSegmentTimingInfoFn,
 
-     id3Fn,
 
-     captionsFn,
 
-     isEndOfTimeline,
 
-     endedTimelineFn,
 
-     dataFn,
 
-     doneFn,
 
-     onTransmuxerLog
 
-   }) => {
 
-     const activeXhrs = [];
 
-     const finishProcessingFn = waitForCompletion({
 
-       activeXhrs,
 
-       decryptionWorker,
 
-       trackInfoFn,
 
-       timingInfoFn,
 
-       videoSegmentTimingInfoFn,
 
-       audioSegmentTimingInfoFn,
 
-       id3Fn,
 
-       captionsFn,
 
-       isEndOfTimeline,
 
-       endedTimelineFn,
 
-       dataFn,
 
-       doneFn,
 
-       onTransmuxerLog
 
-     }); // optionally, request the decryption key
 
-     if (segment.key && !segment.key.bytes) {
 
-       const objects = [segment.key];
 
-       if (segment.map && !segment.map.bytes && segment.map.key && segment.map.key.resolvedUri === segment.key.resolvedUri) {
 
-         objects.push(segment.map.key);
 
-       }
 
-       const keyRequestOptions = merge(xhrOptions, {
 
-         uri: segment.key.resolvedUri,
 
-         responseType: 'arraybuffer'
 
-       });
 
-       const keyRequestCallback = handleKeyResponse(segment, objects, finishProcessingFn);
 
-       const keyXhr = xhr(keyRequestOptions, keyRequestCallback);
 
-       activeXhrs.push(keyXhr);
 
-     } // optionally, request the associated media init segment
 
-     if (segment.map && !segment.map.bytes) {
 
-       const differentMapKey = segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri);
 
-       if (differentMapKey) {
 
-         const mapKeyRequestOptions = merge(xhrOptions, {
 
-           uri: segment.map.key.resolvedUri,
 
-           responseType: 'arraybuffer'
 
-         });
 
-         const mapKeyRequestCallback = handleKeyResponse(segment, [segment.map.key], finishProcessingFn);
 
-         const mapKeyXhr = xhr(mapKeyRequestOptions, mapKeyRequestCallback);
 
-         activeXhrs.push(mapKeyXhr);
 
-       }
 
-       const initSegmentOptions = merge(xhrOptions, {
 
-         uri: segment.map.resolvedUri,
 
-         responseType: 'arraybuffer',
 
-         headers: segmentXhrHeaders(segment.map)
 
-       });
 
-       const initSegmentRequestCallback = handleInitSegmentResponse({
 
-         segment,
 
-         finishProcessingFn
 
-       });
 
-       const initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
 
-       activeXhrs.push(initSegmentXhr);
 
-     }
 
-     const segmentRequestOptions = merge(xhrOptions, {
 
-       uri: segment.part && segment.part.resolvedUri || segment.resolvedUri,
 
-       responseType: 'arraybuffer',
 
-       headers: segmentXhrHeaders(segment)
 
-     });
 
-     const segmentRequestCallback = handleSegmentResponse({
 
-       segment,
 
-       finishProcessingFn,
 
-       responseType: segmentRequestOptions.responseType
 
-     });
 
-     const segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
 
-     segmentXhr.addEventListener('progress', handleProgress({
 
-       segment,
 
-       progressFn,
 
-       trackInfoFn,
 
-       timingInfoFn,
 
-       videoSegmentTimingInfoFn,
 
-       audioSegmentTimingInfoFn,
 
-       id3Fn,
 
-       captionsFn,
 
-       isEndOfTimeline,
 
-       endedTimelineFn,
 
-       dataFn
 
-     }));
 
-     activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks
 
-     // multiple times, provide a shared state object
 
-     const loadendState = {};
 
-     activeXhrs.forEach(activeXhr => {
 
-       activeXhr.addEventListener('loadend', handleLoadEnd({
 
-         loadendState,
 
-         abortFn
 
-       }));
 
-     });
 
-     return () => abortAll(activeXhrs);
 
-   };
 
-   /**
 
-    * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
 
-    * codec strings, or translating codec strings into objects that can be examined.
 
-    */
 
-   const logFn$1 = logger('CodecUtils');
 
-   /**
 
-    * Returns a set of codec strings parsed from the playlist or the default
 
-    * codec strings if no codecs were specified in the playlist
 
-    *
 
-    * @param {Playlist} media the current media playlist
 
-    * @return {Object} an object with the video and audio codecs
 
-    */
 
-   const getCodecs = function (media) {
 
-     // if the codecs were explicitly specified, use them instead of the
 
-     // defaults
 
-     const mediaAttributes = media.attributes || {};
 
-     if (mediaAttributes.CODECS) {
 
-       return parseCodecs(mediaAttributes.CODECS);
 
-     }
 
-   };
 
-   const isMaat = (main, media) => {
 
-     const mediaAttributes = media.attributes || {};
 
-     return main && main.mediaGroups && main.mediaGroups.AUDIO && mediaAttributes.AUDIO && main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
 
-   };
 
-   const isMuxed = (main, media) => {
 
-     if (!isMaat(main, media)) {
 
-       return true;
 
-     }
 
-     const mediaAttributes = media.attributes || {};
 
-     const audioGroup = main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
 
-     for (const groupId in audioGroup) {
 
-       // If an audio group has a URI (the case for HLS, as HLS will use external playlists),
 
-       // or there are listed playlists (the case for DASH, as the manifest will have already
 
-       // provided all of the details necessary to generate the audio playlist, as opposed to
 
-       // HLS' externally requested playlists), then the content is demuxed.
 
-       if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
 
-         return true;
 
-       }
 
-     }
 
-     return false;
 
-   };
 
-   const unwrapCodecList = function (codecList) {
 
-     const codecs = {};
 
-     codecList.forEach(({
 
-       mediaType,
 
-       type,
 
-       details
 
-     }) => {
 
-       codecs[mediaType] = codecs[mediaType] || [];
 
-       codecs[mediaType].push(translateLegacyCodec(`${type}${details}`));
 
-     });
 
-     Object.keys(codecs).forEach(function (mediaType) {
 
-       if (codecs[mediaType].length > 1) {
 
-         logFn$1(`multiple ${mediaType} codecs found as attributes: ${codecs[mediaType].join(', ')}. Setting playlist codecs to null so that we wait for mux.js to probe segments for real codecs.`);
 
-         codecs[mediaType] = null;
 
-         return;
 
-       }
 
-       codecs[mediaType] = codecs[mediaType][0];
 
-     });
 
-     return codecs;
 
-   };
 
-   const codecCount = function (codecObj) {
 
-     let count = 0;
 
-     if (codecObj.audio) {
 
-       count++;
 
-     }
 
-     if (codecObj.video) {
 
-       count++;
 
-     }
 
-     return count;
 
-   };
 
-   /**
 
-    * Calculates the codec strings for a working configuration of
 
-    * SourceBuffers to play variant streams in a main playlist. If
 
-    * there is no possible working configuration, an empty object will be
 
-    * returned.
 
-    *
 
-    * @param main {Object} the m3u8 object for the main playlist
 
-    * @param media {Object} the m3u8 object for the variant playlist
 
-    * @return {Object} the codec strings.
 
-    *
 
-    * @private
 
-    */
 
-   const codecsForPlaylist = function (main, media) {
 
-     const mediaAttributes = media.attributes || {};
 
-     const codecInfo = unwrapCodecList(getCodecs(media) || []); // HLS with multiple-audio tracks must always get an audio codec.
 
-     // Put another way, there is no way to have a video-only multiple-audio HLS!
 
-     if (isMaat(main, media) && !codecInfo.audio) {
 
-       if (!isMuxed(main, media)) {
 
-         // It is possible for codecs to be specified on the audio media group playlist but
 
-         // not on the rendition playlist. This is mostly the case for DASH, where audio and
 
-         // video are always separate (and separately specified).
 
-         const defaultCodecs = unwrapCodecList(codecsFromDefault(main, mediaAttributes.AUDIO) || []);
 
-         if (defaultCodecs.audio) {
 
-           codecInfo.audio = defaultCodecs.audio;
 
-         }
 
-       }
 
-     }
 
-     return codecInfo;
 
-   };
 
-   const logFn = logger('PlaylistSelector');
 
-   const representationToString = function (representation) {
 
-     if (!representation || !representation.playlist) {
 
-       return;
 
-     }
 
-     const playlist = representation.playlist;
 
-     return JSON.stringify({
 
-       id: playlist.id,
 
-       bandwidth: representation.bandwidth,
 
-       width: representation.width,
 
-       height: representation.height,
 
-       codecs: playlist.attributes && playlist.attributes.CODECS || ''
 
-     });
 
-   }; // Utilities
 
-   /**
 
-    * Returns the CSS value for the specified property on an element
 
-    * using `getComputedStyle`. Firefox has a long-standing issue where
 
-    * getComputedStyle() may return null when running in an iframe with
 
-    * `display: none`.
 
-    *
 
-    * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
 
-    * @param {HTMLElement} el the htmlelement to work on
 
-    * @param {string} the proprety to get the style for
 
-    */
 
-   const safeGetComputedStyle = function (el, property) {
 
-     if (!el) {
 
-       return '';
 
-     }
 
-     const result = window.getComputedStyle(el);
 
-     if (!result) {
 
-       return '';
 
-     }
 
-     return result[property];
 
-   };
 
-   /**
 
-    * Resuable stable sort function
 
-    *
 
-    * @param {Playlists} array
 
-    * @param {Function} sortFn Different comparators
 
-    * @function stableSort
 
-    */
 
-   const stableSort = function (array, sortFn) {
 
-     const newArray = array.slice();
 
-     array.sort(function (left, right) {
 
-       const cmp = sortFn(left, right);
 
-       if (cmp === 0) {
 
-         return newArray.indexOf(left) - newArray.indexOf(right);
 
-       }
 
-       return cmp;
 
-     });
 
-   };
 
-   /**
 
-    * A comparator function to sort two playlist object by bandwidth.
 
-    *
 
-    * @param {Object} left a media playlist object
 
-    * @param {Object} right a media playlist object
 
-    * @return {number} Greater than zero if the bandwidth attribute of
 
-    * left is greater than the corresponding attribute of right. Less
 
-    * than zero if the bandwidth of right is greater than left and
 
-    * exactly zero if the two are equal.
 
-    */
 
-   const comparePlaylistBandwidth = function (left, right) {
 
-     let leftBandwidth;
 
-     let rightBandwidth;
 
-     if (left.attributes.BANDWIDTH) {
 
-       leftBandwidth = left.attributes.BANDWIDTH;
 
-     }
 
-     leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
 
-     if (right.attributes.BANDWIDTH) {
 
-       rightBandwidth = right.attributes.BANDWIDTH;
 
-     }
 
-     rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
 
-     return leftBandwidth - rightBandwidth;
 
-   };
 
-   /**
 
-    * A comparator function to sort two playlist object by resolution (width).
 
-    *
 
-    * @param {Object} left a media playlist object
 
-    * @param {Object} right a media playlist object
 
-    * @return {number} Greater than zero if the resolution.width attribute of
 
-    * left is greater than the corresponding attribute of right. Less
 
-    * than zero if the resolution.width of right is greater than left and
 
-    * exactly zero if the two are equal.
 
-    */
 
-   const comparePlaylistResolution = function (left, right) {
 
-     let leftWidth;
 
-     let rightWidth;
 
-     if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
 
-       leftWidth = left.attributes.RESOLUTION.width;
 
-     }
 
-     leftWidth = leftWidth || window.Number.MAX_VALUE;
 
-     if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
 
-       rightWidth = right.attributes.RESOLUTION.width;
 
-     }
 
-     rightWidth = rightWidth || window.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
 
-     // have the same media dimensions/ resolution
 
-     if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
 
-       return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
 
-     }
 
-     return leftWidth - rightWidth;
 
-   };
 
-   /**
 
-    * Chooses the appropriate media playlist based on bandwidth and player size
 
-    *
 
-    * @param {Object} main
 
-    *        Object representation of the main manifest
 
-    * @param {number} playerBandwidth
 
-    *        Current calculated bandwidth of the player
 
-    * @param {number} playerWidth
 
-    *        Current width of the player element (should account for the device pixel ratio)
 
-    * @param {number} playerHeight
 
-    *        Current height of the player element (should account for the device pixel ratio)
 
-    * @param {boolean} limitRenditionByPlayerDimensions
 
-    *        True if the player width and height should be used during the selection, false otherwise
 
-    * @param {Object} playlistController
 
-    *        the current playlistController object
 
-    * @return {Playlist} the highest bitrate playlist less than the
 
-    * currently detected bandwidth, accounting for some amount of
 
-    * bandwidth variance
 
-    */
 
-   let simpleSelector = function (main, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions, playlistController) {
 
-     // If we end up getting called before `main` is available, exit early
 
-     if (!main) {
 
-       return;
 
-     }
 
-     const options = {
 
-       bandwidth: playerBandwidth,
 
-       width: playerWidth,
 
-       height: playerHeight,
 
-       limitRenditionByPlayerDimensions
 
-     };
 
-     let playlists = main.playlists; // if playlist is audio only, select between currently active audio group playlists.
 
-     if (Playlist.isAudioOnly(main)) {
 
-       playlists = playlistController.getAudioTrackPlaylists_(); // add audioOnly to options so that we log audioOnly: true
 
-       // at the buttom of this function for debugging.
 
-       options.audioOnly = true;
 
-     } // convert the playlists to an intermediary representation to make comparisons easier
 
-     let sortedPlaylistReps = playlists.map(playlist => {
 
-       let bandwidth;
 
-       const width = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
 
-       const height = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
 
-       bandwidth = playlist.attributes && playlist.attributes.BANDWIDTH;
 
-       bandwidth = bandwidth || window.Number.MAX_VALUE;
 
-       return {
 
-         bandwidth,
 
-         width,
 
-         height,
 
-         playlist
 
-       };
 
-     });
 
-     stableSort(sortedPlaylistReps, (left, right) => left.bandwidth - right.bandwidth); // filter out any playlists that have been excluded due to
 
-     // incompatible configurations
 
-     sortedPlaylistReps = sortedPlaylistReps.filter(rep => !Playlist.isIncompatible(rep.playlist)); // filter out any playlists that have been disabled manually through the representations
 
-     // api or excluded temporarily due to playback errors.
 
-     let enabledPlaylistReps = sortedPlaylistReps.filter(rep => Playlist.isEnabled(rep.playlist));
 
-     if (!enabledPlaylistReps.length) {
 
-       // if there are no enabled playlists, then they have all been excluded or disabled
 
-       // by the user through the representations api. In this case, ignore exclusion and
 
-       // fallback to what the user wants by using playlists the user has not disabled.
 
-       enabledPlaylistReps = sortedPlaylistReps.filter(rep => !Playlist.isDisabled(rep.playlist));
 
-     } // filter out any variant that has greater effective bitrate
 
-     // than the current estimated bandwidth
 
-     const bandwidthPlaylistReps = enabledPlaylistReps.filter(rep => rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth);
 
-     let highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
 
-     // and then taking the very first element
 
-     const bandwidthBestRep = bandwidthPlaylistReps.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0]; // if we're not going to limit renditions by player size, make an early decision.
 
-     if (limitRenditionByPlayerDimensions === false) {
 
-       const chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
 
-       if (chosenRep && chosenRep.playlist) {
 
-         let type = 'sortedPlaylistReps';
 
-         if (bandwidthBestRep) {
 
-           type = 'bandwidthBestRep';
 
-         }
 
-         if (enabledPlaylistReps[0]) {
 
-           type = 'enabledPlaylistReps';
 
-         }
 
-         logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
 
-         return chosenRep.playlist;
 
-       }
 
-       logFn('could not choose a playlist with options', options);
 
-       return null;
 
-     } // filter out playlists without resolution information
 
-     const haveResolution = bandwidthPlaylistReps.filter(rep => rep.width && rep.height); // sort variants by resolution
 
-     stableSort(haveResolution, (left, right) => left.width - right.width); // if we have the exact resolution as the player use it
 
-     const resolutionBestRepList = haveResolution.filter(rep => rep.width === playerWidth && rep.height === playerHeight);
 
-     highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
 
-     const resolutionBestRep = resolutionBestRepList.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0];
 
-     let resolutionPlusOneList;
 
-     let resolutionPlusOneSmallest;
 
-     let resolutionPlusOneRep; // find the smallest variant that is larger than the player
 
-     // if there is no match of exact resolution
 
-     if (!resolutionBestRep) {
 
-       resolutionPlusOneList = haveResolution.filter(rep => rep.width > playerWidth || rep.height > playerHeight); // find all the variants have the same smallest resolution
 
-       resolutionPlusOneSmallest = resolutionPlusOneList.filter(rep => rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height); // ensure that we also pick the highest bandwidth variant that
 
-       // is just-larger-than the video player
 
-       highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
 
-       resolutionPlusOneRep = resolutionPlusOneSmallest.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0];
 
-     }
 
-     let leastPixelDiffRep; // If this selector proves to be better than others,
 
-     // resolutionPlusOneRep and resolutionBestRep and all
 
-     // the code involving them should be removed.
 
-     if (playlistController.leastPixelDiffSelector) {
 
-       // find the variant that is closest to the player's pixel size
 
-       const leastPixelDiffList = haveResolution.map(rep => {
 
-         rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight);
 
-         return rep;
 
-       }); // get the highest bandwidth, closest resolution playlist
 
-       stableSort(leastPixelDiffList, (left, right) => {
 
-         // sort by highest bandwidth if pixelDiff is the same
 
-         if (left.pixelDiff === right.pixelDiff) {
 
-           return right.bandwidth - left.bandwidth;
 
-         }
 
-         return left.pixelDiff - right.pixelDiff;
 
-       });
 
-       leastPixelDiffRep = leastPixelDiffList[0];
 
-     } // fallback chain of variants
 
-     const chosenRep = leastPixelDiffRep || resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
 
-     if (chosenRep && chosenRep.playlist) {
 
-       let type = 'sortedPlaylistReps';
 
-       if (leastPixelDiffRep) {
 
-         type = 'leastPixelDiffRep';
 
-       } else if (resolutionPlusOneRep) {
 
-         type = 'resolutionPlusOneRep';
 
-       } else if (resolutionBestRep) {
 
-         type = 'resolutionBestRep';
 
-       } else if (bandwidthBestRep) {
 
-         type = 'bandwidthBestRep';
 
-       } else if (enabledPlaylistReps[0]) {
 
-         type = 'enabledPlaylistReps';
 
-       }
 
-       logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
 
-       return chosenRep.playlist;
 
-     }
 
-     logFn('could not choose a playlist with options', options);
 
-     return null;
 
-   };
 
-   /**
 
-    * Chooses the appropriate media playlist based on the most recent
 
-    * bandwidth estimate and the player size.
 
-    *
 
-    * Expects to be called within the context of an instance of VhsHandler
 
-    *
 
-    * @return {Playlist} the highest bitrate playlist less than the
 
-    * currently detected bandwidth, accounting for some amount of
 
-    * bandwidth variance
 
-    */
 
-   const lastBandwidthSelector = function () {
 
-     const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1;
 
-     return simpleSelector(this.playlists.main, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.playlistController_);
 
-   };
 
-   /**
 
-    * Chooses the appropriate media playlist based on an
 
-    * exponential-weighted moving average of the bandwidth after
 
-    * filtering for player size.
 
-    *
 
-    * Expects to be called within the context of an instance of VhsHandler
 
-    *
 
-    * @param {number} decay - a number between 0 and 1. Higher values of
 
-    * this parameter will cause previous bandwidth estimates to lose
 
-    * significance more quickly.
 
-    * @return {Function} a function which can be invoked to create a new
 
-    * playlist selector function.
 
-    * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
 
-    */
 
-   const movingAverageBandwidthSelector = function (decay) {
 
-     let average = -1;
 
-     let lastSystemBandwidth = -1;
 
-     if (decay < 0 || decay > 1) {
 
-       throw new Error('Moving average bandwidth decay must be between 0 and 1.');
 
-     }
 
-     return function () {
 
-       const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1;
 
-       if (average < 0) {
 
-         average = this.systemBandwidth;
 
-         lastSystemBandwidth = this.systemBandwidth;
 
-       } // stop the average value from decaying for every 250ms
 
-       // when the systemBandwidth is constant
 
-       // and
 
-       // stop average from setting to a very low value when the
 
-       // systemBandwidth becomes 0 in case of chunk cancellation
 
-       if (this.systemBandwidth > 0 && this.systemBandwidth !== lastSystemBandwidth) {
 
-         average = decay * this.systemBandwidth + (1 - decay) * average;
 
-         lastSystemBandwidth = this.systemBandwidth;
 
-       }
 
-       return simpleSelector(this.playlists.main, average, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.playlistController_);
 
-     };
 
-   };
 
-   /**
 
-    * Chooses the appropriate media playlist based on the potential to rebuffer
 
-    *
 
-    * @param {Object} settings
 
-    *        Object of information required to use this selector
 
-    * @param {Object} settings.main
 
-    *        Object representation of the main manifest
 
-    * @param {number} settings.currentTime
 
-    *        The current time of the player
 
-    * @param {number} settings.bandwidth
 
-    *        Current measured bandwidth
 
-    * @param {number} settings.duration
 
-    *        Duration of the media
 
-    * @param {number} settings.segmentDuration
 
-    *        Segment duration to be used in round trip time calculations
 
-    * @param {number} settings.timeUntilRebuffer
 
-    *        Time left in seconds until the player has to rebuffer
 
-    * @param {number} settings.currentTimeline
 
-    *        The current timeline segments are being loaded from
 
-    * @param {SyncController} settings.syncController
 
-    *        SyncController for determining if we have a sync point for a given playlist
 
-    * @return {Object|null}
 
-    *         {Object} return.playlist
 
-    *         The highest bandwidth playlist with the least amount of rebuffering
 
-    *         {Number} return.rebufferingImpact
 
-    *         The amount of time in seconds switching to this playlist will rebuffer. A
 
-    *         negative value means that switching will cause zero rebuffering.
 
-    */
 
-   const minRebufferMaxBandwidthSelector = function (settings) {
 
-     const {
 
-       main,
 
-       currentTime,
 
-       bandwidth,
 
-       duration,
 
-       segmentDuration,
 
-       timeUntilRebuffer,
 
-       currentTimeline,
 
-       syncController
 
-     } = settings; // filter out any playlists that have been excluded due to
 
-     // incompatible configurations
 
-     const compatiblePlaylists = main.playlists.filter(playlist => !Playlist.isIncompatible(playlist)); // filter out any playlists that have been disabled manually through the representations
 
-     // api or excluded temporarily due to playback errors.
 
-     let enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
 
-     if (!enabledPlaylists.length) {
 
-       // if there are no enabled playlists, then they have all been excluded or disabled
 
-       // by the user through the representations api. In this case, ignore exclusion and
 
-       // fallback to what the user wants by using playlists the user has not disabled.
 
-       enabledPlaylists = compatiblePlaylists.filter(playlist => !Playlist.isDisabled(playlist));
 
-     }
 
-     const bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
 
-     const rebufferingEstimates = bandwidthPlaylists.map(playlist => {
 
-       const syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
 
-       // sync request first. This will double the request time
 
-       const numRequests = syncPoint ? 1 : 2;
 
-       const requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
 
-       const rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
 
-       return {
 
-         playlist,
 
-         rebufferingImpact
 
-       };
 
-     });
 
-     const noRebufferingPlaylists = rebufferingEstimates.filter(estimate => estimate.rebufferingImpact <= 0); // Sort by bandwidth DESC
 
-     stableSort(noRebufferingPlaylists, (a, b) => comparePlaylistBandwidth(b.playlist, a.playlist));
 
-     if (noRebufferingPlaylists.length) {
 
-       return noRebufferingPlaylists[0];
 
-     }
 
-     stableSort(rebufferingEstimates, (a, b) => a.rebufferingImpact - b.rebufferingImpact);
 
-     return rebufferingEstimates[0] || null;
 
-   };
 
-   /**
 
-    * Chooses the appropriate media playlist, which in this case is the lowest bitrate
 
-    * one with video.  If no renditions with video exist, return the lowest audio rendition.
 
-    *
 
-    * Expects to be called within the context of an instance of VhsHandler
 
-    *
 
-    * @return {Object|null}
 
-    *         {Object} return.playlist
 
-    *         The lowest bitrate playlist that contains a video codec.  If no such rendition
 
-    *         exists pick the lowest audio rendition.
 
-    */
 
-   const lowestBitrateCompatibleVariantSelector = function () {
 
-     // filter out any playlists that have been excluded due to
 
-     // incompatible configurations or playback errors
 
-     const playlists = this.playlists.main.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
 
-     stableSort(playlists, (a, b) => comparePlaylistBandwidth(a, b)); // Parse and assume that playlists with no video codec have no video
 
-     // (this is not necessarily true, although it is generally true).
 
-     //
 
-     // If an entire manifest has no valid videos everything will get filtered
 
-     // out.
 
-     const playlistsWithVideo = playlists.filter(playlist => !!codecsForPlaylist(this.playlists.main, playlist).video);
 
-     return playlistsWithVideo[0] || null;
 
-   };
 
-   /**
 
-    * Combine all segments into a single Uint8Array
 
-    *
 
-    * @param {Object} segmentObj
 
-    * @return {Uint8Array} concatenated bytes
 
-    * @private
 
-    */
 
-   const concatSegments = segmentObj => {
 
-     let offset = 0;
 
-     let tempBuffer;
 
-     if (segmentObj.bytes) {
 
-       tempBuffer = new Uint8Array(segmentObj.bytes); // combine the individual segments into one large typed-array
 
-       segmentObj.segments.forEach(segment => {
 
-         tempBuffer.set(segment, offset);
 
-         offset += segment.byteLength;
 
-       });
 
-     }
 
-     return tempBuffer;
 
-   };
 
-   /**
 
-    * @file text-tracks.js
 
-    */
 
-   /**
 
-    * Create captions text tracks on video.js if they do not exist
 
-    *
 
-    * @param {Object} inbandTextTracks a reference to current inbandTextTracks
 
-    * @param {Object} tech the video.js tech
 
-    * @param {Object} captionStream the caption stream to create
 
-    * @private
 
-    */
 
-   const createCaptionsTrackIfNotExists = function (inbandTextTracks, tech, captionStream) {
 
-     if (!inbandTextTracks[captionStream]) {
 
-       tech.trigger({
 
-         type: 'usage',
 
-         name: 'vhs-608'
 
-       });
 
-       let instreamId = captionStream; // we need to translate SERVICEn for 708 to how mux.js currently labels them
 
-       if (/^cc708_/.test(captionStream)) {
 
-         instreamId = 'SERVICE' + captionStream.split('_')[1];
 
-       }
 
-       const track = tech.textTracks().getTrackById(instreamId);
 
-       if (track) {
 
-         // Resuse an existing track with a CC# id because this was
 
-         // very likely created by videojs-contrib-hls from information
 
-         // in the m3u8 for us to use
 
-         inbandTextTracks[captionStream] = track;
 
-       } else {
 
-         // This section gets called when we have caption services that aren't specified in the manifest.
 
-         // Manifest level caption services are handled in media-groups.js under CLOSED-CAPTIONS.
 
-         const captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
 
-         let label = captionStream;
 
-         let language = captionStream;
 
-         let def = false;
 
-         const captionService = captionServices[instreamId];
 
-         if (captionService) {
 
-           label = captionService.label;
 
-           language = captionService.language;
 
-           def = captionService.default;
 
-         } // Otherwise, create a track with the default `CC#` label and
 
-         // without a language
 
-         inbandTextTracks[captionStream] = tech.addRemoteTextTrack({
 
-           kind: 'captions',
 
-           id: instreamId,
 
-           // TODO: investigate why this doesn't seem to turn the caption on by default
 
-           default: def,
 
-           label,
 
-           language
 
-         }, false).track;
 
-       }
 
-     }
 
-   };
 
-   /**
 
-    * Add caption text track data to a source handler given an array of captions
 
-    *
 
-    * @param {Object}
 
-    *   @param {Object} inbandTextTracks the inband text tracks
 
-    *   @param {number} timestampOffset the timestamp offset of the source buffer
 
-    *   @param {Array} captionArray an array of caption data
 
-    * @private
 
-    */
 
-   const addCaptionData = function ({
 
-     inbandTextTracks,
 
-     captionArray,
 
-     timestampOffset
 
-   }) {
 
-     if (!captionArray) {
 
-       return;
 
-     }
 
-     const Cue = window.WebKitDataCue || window.VTTCue;
 
-     captionArray.forEach(caption => {
 
-       const track = caption.stream;
 
-       inbandTextTracks[track].addCue(new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, caption.text));
 
-     });
 
-   };
 
-   /**
 
-    * Define properties on a cue for backwards compatability,
 
-    * but warn the user that the way that they are using it
 
-    * is depricated and will be removed at a later date.
 
-    *
 
-    * @param {Cue} cue the cue to add the properties on
 
-    * @private
 
-    */
 
-   const deprecateOldCue = function (cue) {
 
-     Object.defineProperties(cue.frame, {
 
-       id: {
 
-         get() {
 
-           videojs.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
 
-           return cue.value.key;
 
-         }
 
-       },
 
-       value: {
 
-         get() {
 
-           videojs.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
 
-           return cue.value.data;
 
-         }
 
-       },
 
-       privateData: {
 
-         get() {
 
-           videojs.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
 
-           return cue.value.data;
 
-         }
 
-       }
 
-     });
 
-   };
 
-   /**
 
-    * Add metadata text track data to a source handler given an array of metadata
 
-    *
 
-    * @param {Object}
 
-    *   @param {Object} inbandTextTracks the inband text tracks
 
-    *   @param {Array} metadataArray an array of meta data
 
-    *   @param {number} timestampOffset the timestamp offset of the source buffer
 
-    *   @param {number} videoDuration the duration of the video
 
-    * @private
 
-    */
 
-   const addMetadata = ({
 
-     inbandTextTracks,
 
-     metadataArray,
 
-     timestampOffset,
 
-     videoDuration
 
-   }) => {
 
-     if (!metadataArray) {
 
-       return;
 
-     }
 
-     const Cue = window.WebKitDataCue || window.VTTCue;
 
-     const metadataTrack = inbandTextTracks.metadataTrack_;
 
-     if (!metadataTrack) {
 
-       return;
 
-     }
 
-     metadataArray.forEach(metadata => {
 
-       const time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
 
-       // ignore this bit of metadata.
 
-       // This likely occurs when you have an non-timed ID3 tag like TIT2,
 
-       // which is the "Title/Songname/Content description" frame
 
-       if (typeof time !== 'number' || window.isNaN(time) || time < 0 || !(time < Infinity)) {
 
-         return;
 
-       }
 
-       metadata.frames.forEach(frame => {
 
-         const cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
 
-         cue.frame = frame;
 
-         cue.value = frame;
 
-         deprecateOldCue(cue);
 
-         metadataTrack.addCue(cue);
 
-       });
 
-     });
 
-     if (!metadataTrack.cues || !metadataTrack.cues.length) {
 
-       return;
 
-     } // Updating the metadeta cues so that
 
-     // the endTime of each cue is the startTime of the next cue
 
-     // the endTime of last cue is the duration of the video
 
-     const cues = metadataTrack.cues;
 
-     const cuesArray = []; // Create a copy of the TextTrackCueList...
 
-     // ...disregarding cues with a falsey value
 
-     for (let i = 0; i < cues.length; i++) {
 
-       if (cues[i]) {
 
-         cuesArray.push(cues[i]);
 
-       }
 
-     } // Group cues by their startTime value
 
-     const cuesGroupedByStartTime = cuesArray.reduce((obj, cue) => {
 
-       const timeSlot = obj[cue.startTime] || [];
 
-       timeSlot.push(cue);
 
-       obj[cue.startTime] = timeSlot;
 
-       return obj;
 
-     }, {}); // Sort startTimes by ascending order
 
-     const sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort((a, b) => Number(a) - Number(b)); // Map each cue group's endTime to the next group's startTime
 
-     sortedStartTimes.forEach((startTime, idx) => {
 
-       const cueGroup = cuesGroupedByStartTime[startTime];
 
-       const nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
 
-       cueGroup.forEach(cue => {
 
-         cue.endTime = nextTime;
 
-       });
 
-     });
 
-   };
 
-   /**
 
-    * Create metadata text track on video.js if it does not exist
 
-    *
 
-    * @param {Object} inbandTextTracks a reference to current inbandTextTracks
 
-    * @param {string} dispatchType the inband metadata track dispatch type
 
-    * @param {Object} tech the video.js tech
 
-    * @private
 
-    */
 
-   const createMetadataTrackIfNotExists = (inbandTextTracks, dispatchType, tech) => {
 
-     if (inbandTextTracks.metadataTrack_) {
 
-       return;
 
-     }
 
-     inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack({
 
-       kind: 'metadata',
 
-       label: 'Timed Metadata'
 
-     }, false).track;
 
-     inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType;
 
-   };
 
-   /**
 
-    * Remove cues from a track on video.js.
 
-    *
 
-    * @param {Double} start start of where we should remove the cue
 
-    * @param {Double} end end of where the we should remove the cue
 
-    * @param {Object} track the text track to remove the cues from
 
-    * @private
 
-    */
 
-   const removeCuesFromTrack = function (start, end, track) {
 
-     let i;
 
-     let cue;
 
-     if (!track) {
 
-       return;
 
-     }
 
-     if (!track.cues) {
 
-       return;
 
-     }
 
-     i = track.cues.length;
 
-     while (i--) {
 
-       cue = track.cues[i]; // Remove any cue within the provided start and end time
 
-       if (cue.startTime >= start && cue.endTime <= end) {
 
-         track.removeCue(cue);
 
-       }
 
-     }
 
-   };
 
-   /**
 
-    * Remove duplicate cues from a track on video.js (a cue is considered a
 
-    * duplicate if it has the same time interval and text as another)
 
-    *
 
-    * @param {Object} track the text track to remove the duplicate cues from
 
-    * @private
 
-    */
 
-   const removeDuplicateCuesFromTrack = function (track) {
 
-     const cues = track.cues;
 
-     if (!cues) {
 
-       return;
 
-     }
 
-     for (let i = 0; i < cues.length; i++) {
 
-       const duplicates = [];
 
-       let occurrences = 0;
 
-       for (let j = 0; j < cues.length; j++) {
 
-         if (cues[i].startTime === cues[j].startTime && cues[i].endTime === cues[j].endTime && cues[i].text === cues[j].text) {
 
-           occurrences++;
 
-           if (occurrences > 1) {
 
-             duplicates.push(cues[j]);
 
-           }
 
-         }
 
-       }
 
-       if (duplicates.length) {
 
-         duplicates.forEach(dupe => track.removeCue(dupe));
 
-       }
 
-     }
 
-   };
 
-   /**
 
-    * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
 
-    * front of current time.
 
-    *
 
-    * @param {Array} buffer
 
-    *        The current buffer of gop information
 
-    * @param {number} currentTime
 
-    *        The current time
 
-    * @param {Double} mapping
 
-    *        Offset to map display time to stream presentation time
 
-    * @return {Array}
 
-    *         List of gops considered safe to append over
 
-    */
 
-   const gopsSafeToAlignWith = (buffer, currentTime, mapping) => {
 
-     if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
 
-       return [];
 
-     } // pts value for current time + 3 seconds to give a bit more wiggle room
 
-     const currentTimePts = Math.ceil((currentTime - mapping + 3) * clock_1);
 
-     let i;
 
-     for (i = 0; i < buffer.length; i++) {
 
-       if (buffer[i].pts > currentTimePts) {
 
-         break;
 
-       }
 
-     }
 
-     return buffer.slice(i);
 
-   };
 
-   /**
 
-    * Appends gop information (timing and byteLength) received by the transmuxer for the
 
-    * gops appended in the last call to appendBuffer
 
-    *
 
-    * @param {Array} buffer
 
-    *        The current buffer of gop information
 
-    * @param {Array} gops
 
-    *        List of new gop information
 
-    * @param {boolean} replace
 
-    *        If true, replace the buffer with the new gop information. If false, append the
 
-    *        new gop information to the buffer in the right location of time.
 
-    * @return {Array}
 
-    *         Updated list of gop information
 
-    */
 
-   const updateGopBuffer = (buffer, gops, replace) => {
 
-     if (!gops.length) {
 
-       return buffer;
 
-     }
 
-     if (replace) {
 
-       // If we are in safe append mode, then completely overwrite the gop buffer
 
-       // with the most recent appeneded data. This will make sure that when appending
 
-       // future segments, we only try to align with gops that are both ahead of current
 
-       // time and in the last segment appended.
 
-       return gops.slice();
 
-     }
 
-     const start = gops[0].pts;
 
-     let i = 0;
 
-     for (i; i < buffer.length; i++) {
 
-       if (buffer[i].pts >= start) {
 
-         break;
 
-       }
 
-     }
 
-     return buffer.slice(0, i).concat(gops);
 
-   };
 
-   /**
 
-    * Removes gop information in buffer that overlaps with provided start and end
 
-    *
 
-    * @param {Array} buffer
 
-    *        The current buffer of gop information
 
-    * @param {Double} start
 
-    *        position to start the remove at
 
-    * @param {Double} end
 
-    *        position to end the remove at
 
-    * @param {Double} mapping
 
-    *        Offset to map display time to stream presentation time
 
-    */
 
-   const removeGopBuffer = (buffer, start, end, mapping) => {
 
-     const startPts = Math.ceil((start - mapping) * clock_1);
 
-     const endPts = Math.ceil((end - mapping) * clock_1);
 
-     const updatedBuffer = buffer.slice();
 
-     let i = buffer.length;
 
-     while (i--) {
 
-       if (buffer[i].pts <= endPts) {
 
-         break;
 
-       }
 
-     }
 
-     if (i === -1) {
 
-       // no removal because end of remove range is before start of buffer
 
-       return updatedBuffer;
 
-     }
 
-     let j = i + 1;
 
-     while (j--) {
 
-       if (buffer[j].pts <= startPts) {
 
-         break;
 
-       }
 
-     } // clamp remove range start to 0 index
 
-     j = Math.max(j, 0);
 
-     updatedBuffer.splice(j, i - j + 1);
 
-     return updatedBuffer;
 
-   };
 
-   const shallowEqual = function (a, b) {
 
-     // if both are undefined
 
-     // or one or the other is undefined
 
-     // they are not equal
 
-     if (!a && !b || !a && b || a && !b) {
 
-       return false;
 
-     } // they are the same object and thus, equal
 
-     if (a === b) {
 
-       return true;
 
-     } // sort keys so we can make sure they have
 
-     // all the same keys later.
 
-     const akeys = Object.keys(a).sort();
 
-     const bkeys = Object.keys(b).sort(); // different number of keys, not equal
 
-     if (akeys.length !== bkeys.length) {
 
-       return false;
 
-     }
 
-     for (let i = 0; i < akeys.length; i++) {
 
-       const key = akeys[i]; // different sorted keys, not equal
 
-       if (key !== bkeys[i]) {
 
-         return false;
 
-       } // different values, not equal
 
-       if (a[key] !== b[key]) {
 
-         return false;
 
-       }
 
-     }
 
-     return true;
 
-   };
 
-   // https://www.w3.org/TR/WebIDL-1/#quotaexceedederror
 
-   const QUOTA_EXCEEDED_ERR = 22;
 
-   /**
 
-    * The segment loader has no recourse except to fetch a segment in the
 
-    * current playlist and use the internal timestamps in that segment to
 
-    * generate a syncPoint. This function returns a good candidate index
 
-    * for that process.
 
-    *
 
-    * @param {Array} segments - the segments array from a playlist.
 
-    * @return {number} An index of a segment from the playlist to load
 
-    */
 
-   const getSyncSegmentCandidate = function (currentTimeline, segments, targetTime) {
 
-     segments = segments || [];
 
-     const timelineSegments = [];
 
-     let time = 0;
 
-     for (let i = 0; i < segments.length; i++) {
 
-       const segment = segments[i];
 
-       if (currentTimeline === segment.timeline) {
 
-         timelineSegments.push(i);
 
-         time += segment.duration;
 
-         if (time > targetTime) {
 
-           return i;
 
-         }
 
-       }
 
-     }
 
-     if (timelineSegments.length === 0) {
 
-       return 0;
 
-     } // default to the last timeline segment
 
-     return timelineSegments[timelineSegments.length - 1];
 
-   }; // In the event of a quota exceeded error, keep at least one second of back buffer. This
 
-   // number was arbitrarily chosen and may be updated in the future, but seemed reasonable
 
-   // as a start to prevent any potential issues with removing content too close to the
 
-   // playhead.
 
-   const MIN_BACK_BUFFER = 1; // in ms
 
-   const CHECK_BUFFER_DELAY = 500;
 
-   const finite = num => typeof num === 'number' && isFinite(num); // With most content hovering around 30fps, if a segment has a duration less than a half
 
-   // frame at 30fps or one frame at 60fps, the bandwidth and throughput calculations will
 
-   // not accurately reflect the rest of the content.
 
-   const MIN_SEGMENT_DURATION_TO_SAVE_STATS = 1 / 60;
 
-   const illegalMediaSwitch = (loaderType, startingMedia, trackInfo) => {
 
-     // Although these checks should most likely cover non 'main' types, for now it narrows
 
-     // the scope of our checks.
 
-     if (loaderType !== 'main' || !startingMedia || !trackInfo) {
 
-       return null;
 
-     }
 
-     if (!trackInfo.hasAudio && !trackInfo.hasVideo) {
 
-       return 'Neither audio nor video found in segment.';
 
-     }
 
-     if (startingMedia.hasVideo && !trackInfo.hasVideo) {
 
-       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.';
 
-     }
 
-     if (!startingMedia.hasVideo && trackInfo.hasVideo) {
 
-       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.';
 
-     }
 
-     return null;
 
-   };
 
-   /**
 
-    * Calculates a time value that is safe to remove from the back buffer without interrupting
 
-    * playback.
 
-    *
 
-    * @param {TimeRange} seekable
 
-    *        The current seekable range
 
-    * @param {number} currentTime
 
-    *        The current time of the player
 
-    * @param {number} targetDuration
 
-    *        The target duration of the current playlist
 
-    * @return {number}
 
-    *         Time that is safe to remove from the back buffer without interrupting playback
 
-    */
 
-   const safeBackBufferTrimTime = (seekable, currentTime, targetDuration) => {
 
-     // 30 seconds before the playhead provides a safe default for trimming.
 
-     //
 
-     // Choosing a reasonable default is particularly important for high bitrate content and
 
-     // VOD videos/live streams with large windows, as the buffer may end up overfilled and
 
-     // throw an APPEND_BUFFER_ERR.
 
-     let trimTime = currentTime - Config.BACK_BUFFER_LENGTH;
 
-     if (seekable.length) {
 
-       // Some live playlists may have a shorter window of content than the full allowed back
 
-       // buffer. For these playlists, don't save content that's no longer within the window.
 
-       trimTime = Math.max(trimTime, seekable.start(0));
 
-     } // Don't remove within target duration of the current time to avoid the possibility of
 
-     // removing the GOP currently being played, as removing it can cause playback stalls.
 
-     const maxTrimTime = currentTime - targetDuration;
 
-     return Math.min(maxTrimTime, trimTime);
 
-   };
 
-   const segmentInfoString = segmentInfo => {
 
-     const {
 
-       startOfSegment,
 
-       duration,
 
-       segment,
 
-       part,
 
-       playlist: {
 
-         mediaSequence: seq,
 
-         id,
 
-         segments = []
 
-       },
 
-       mediaIndex: index,
 
-       partIndex,
 
-       timeline
 
-     } = segmentInfo;
 
-     const segmentLen = segments.length - 1;
 
-     let selection = 'mediaIndex/partIndex increment';
 
-     if (segmentInfo.getMediaInfoForTime) {
 
-       selection = `getMediaInfoForTime (${segmentInfo.getMediaInfoForTime})`;
 
-     } else if (segmentInfo.isSyncRequest) {
 
-       selection = 'getSyncSegmentCandidate (isSyncRequest)';
 
-     }
 
-     if (segmentInfo.independent) {
 
-       selection += ` with independent ${segmentInfo.independent}`;
 
-     }
 
-     const hasPartIndex = typeof partIndex === 'number';
 
-     const name = segmentInfo.segment.uri ? 'segment' : 'pre-segment';
 
-     const zeroBasedPartCount = hasPartIndex ? getKnownPartCount({
 
-       preloadSegment: segment
 
-     }) - 1 : 0;
 
-     return `${name} [${seq + index}/${seq + segmentLen}]` + (hasPartIndex ? ` part [${partIndex}/${zeroBasedPartCount}]` : '') + ` segment start/end [${segment.start} => ${segment.end}]` + (hasPartIndex ? ` part start/end [${part.start} => ${part.end}]` : '') + ` startOfSegment [${startOfSegment}]` + ` duration [${duration}]` + ` timeline [${timeline}]` + ` selected by [${selection}]` + ` playlist [${id}]`;
 
-   };
 
-   const timingInfoPropertyForMedia = mediaType => `${mediaType}TimingInfo`;
 
-   /**
 
-    * Returns the timestamp offset to use for the segment.
 
-    *
 
-    * @param {number} segmentTimeline
 
-    *        The timeline of the segment
 
-    * @param {number} currentTimeline
 
-    *        The timeline currently being followed by the loader
 
-    * @param {number} startOfSegment
 
-    *        The estimated segment start
 
-    * @param {TimeRange[]} buffered
 
-    *        The loader's buffer
 
-    * @param {boolean} overrideCheck
 
-    *        If true, no checks are made to see if the timestamp offset value should be set,
 
-    *        but sets it directly to a value.
 
-    *
 
-    * @return {number|null}
 
-    *         Either a number representing a new timestamp offset, or null if the segment is
 
-    *         part of the same timeline
 
-    */
 
-   const timestampOffsetForSegment = ({
 
-     segmentTimeline,
 
-     currentTimeline,
 
-     startOfSegment,
 
-     buffered,
 
-     overrideCheck
 
-   }) => {
 
-     // Check to see if we are crossing a discontinuity to see if we need to set the
 
-     // timestamp offset on the transmuxer and source buffer.
 
-     //
 
-     // Previously, we changed the timestampOffset if the start of this segment was less than
 
-     // the currently set timestampOffset, but this isn't desirable as it can produce bad
 
-     // behavior, especially around long running live streams.
 
-     if (!overrideCheck && segmentTimeline === currentTimeline) {
 
-       return null;
 
-     } // When changing renditions, it's possible to request a segment on an older timeline. For
 
-     // instance, given two renditions with the following:
 
-     //
 
-     // #EXTINF:10
 
-     // segment1
 
-     // #EXT-X-DISCONTINUITY
 
-     // #EXTINF:10
 
-     // segment2
 
-     // #EXTINF:10
 
-     // segment3
 
-     //
 
-     // And the current player state:
 
-     //
 
-     // current time: 8
 
-     // buffer: 0 => 20
 
-     //
 
-     // The next segment on the current rendition would be segment3, filling the buffer from
 
-     // 20s onwards. However, if a rendition switch happens after segment2 was requested,
 
-     // then the next segment to be requested will be segment1 from the new rendition in
 
-     // order to fill time 8 and onwards. Using the buffered end would result in repeated
 
-     // content (since it would position segment1 of the new rendition starting at 20s). This
 
-     // case can be identified when the new segment's timeline is a prior value. Instead of
 
-     // using the buffered end, the startOfSegment can be used, which, hopefully, will be
 
-     // more accurate to the actual start time of the segment.
 
-     if (segmentTimeline < currentTimeline) {
 
-       return startOfSegment;
 
-     } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that
 
-     // value uses the end of the last segment if it is available. While this value
 
-     // should often be correct, it's better to rely on the buffered end, as the new
 
-     // content post discontinuity should line up with the buffered end as if it were
 
-     // time 0 for the new content.
 
-     return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment;
 
-   };
 
-   /**
 
-    * Returns whether or not the loader should wait for a timeline change from the timeline
 
-    * change controller before processing the segment.
 
-    *
 
-    * Primary timing in VHS goes by video. This is different from most media players, as
 
-    * audio is more often used as the primary timing source. For the foreseeable future, VHS
 
-    * will continue to use video as the primary timing source, due to the current logic and
 
-    * expectations built around it.
 
-    * Since the timing follows video, in order to maintain sync, the video loader is
 
-    * responsible for setting both audio and video source buffer timestamp offsets.
 
-    *
 
-    * Setting different values for audio and video source buffers could lead to
 
-    * desyncing. The following examples demonstrate some of the situations where this
 
-    * distinction is important. Note that all of these cases involve demuxed content. When
 
-    * content is muxed, the audio and video are packaged together, therefore syncing
 
-    * separate media playlists is not an issue.
 
-    *
 
-    * CASE 1: Audio prepares to load a new timeline before video:
 
-    *
 
-    * Timeline:       0                 1
 
-    * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
 
-    * Audio Loader:                     ^
 
-    * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
 
-    * Video Loader              ^
 
-    *
 
-    * In the above example, the audio loader is preparing to load the 6th segment, the first
 
-    * after a discontinuity, while the video loader is still loading the 5th segment, before
 
-    * the discontinuity.
 
-    *
 
-    * If the audio loader goes ahead and loads and appends the 6th segment before the video
 
-    * loader crosses the discontinuity, then when appended, the 6th audio segment will use
 
-    * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition,
 
-    * the audio loader must provide the audioAppendStart value to trim the content in the
 
-    * transmuxer, and that value relies on the audio timestamp offset. Since the audio
 
-    * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the
 
-    * segment until that value is provided.
 
-    *
 
-    * CASE 2: Video prepares to load a new timeline before audio:
 
-    *
 
-    * Timeline:       0                 1
 
-    * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
 
-    * Audio Loader:             ^
 
-    * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
 
-    * Video Loader                      ^
 
-    *
 
-    * In the above example, the video loader is preparing to load the 6th segment, the first
 
-    * after a discontinuity, while the audio loader is still loading the 5th segment, before
 
-    * the discontinuity.
 
-    *
 
-    * If the video loader goes ahead and loads and appends the 6th segment, then once the
 
-    * segment is loaded and processed, both the video and audio timestamp offsets will be
 
-    * set, since video is used as the primary timing source. This is to ensure content lines
 
-    * up appropriately, as any modifications to the video timing are reflected by audio when
 
-    * the video loader sets the audio and video timestamp offsets to the same value. However,
 
-    * setting the timestamp offset for audio before audio has had a chance to change
 
-    * timelines will likely lead to desyncing, as the audio loader will append segment 5 with
 
-    * a timestamp intended to apply to segments from timeline 1 rather than timeline 0.
 
-    *
 
-    * CASE 3: When seeking, audio prepares to load a new timeline before video
 
-    *
 
-    * Timeline:       0                 1
 
-    * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
 
-    * Audio Loader:           ^
 
-    * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
 
-    * Video Loader            ^
 
-    *
 
-    * In the above example, both audio and video loaders are loading segments from timeline
 
-    * 0, but imagine that the seek originated from timeline 1.
 
-    *
 
-    * When seeking to a new timeline, the timestamp offset will be set based on the expected
 
-    * segment start of the loaded video segment. In order to maintain sync, the audio loader
 
-    * must wait for the video loader to load its segment and update both the audio and video
 
-    * timestamp offsets before it may load and append its own segment. This is the case
 
-    * whether the seek results in a mismatched segment request (e.g., the audio loader
 
-    * chooses to load segment 3 and the video loader chooses to load segment 4) or the
 
-    * loaders choose to load the same segment index from each playlist, as the segments may
 
-    * not be aligned perfectly, even for matching segment indexes.
 
-    *
 
-    * @param {Object} timelinechangeController
 
-    * @param {number} currentTimeline
 
-    *        The timeline currently being followed by the loader
 
-    * @param {number} segmentTimeline
 
-    *        The timeline of the segment being loaded
 
-    * @param {('main'|'audio')} loaderType
 
-    *        The loader type
 
-    * @param {boolean} audioDisabled
 
-    *        Whether the audio is disabled for the loader. This should only be true when the
 
-    *        loader may have muxed audio in its segment, but should not append it, e.g., for
 
-    *        the main loader when an alternate audio playlist is active.
 
-    *
 
-    * @return {boolean}
 
-    *         Whether the loader should wait for a timeline change from the timeline change
 
-    *         controller before processing the segment
 
-    */
 
-   const shouldWaitForTimelineChange = ({
 
-     timelineChangeController,
 
-     currentTimeline,
 
-     segmentTimeline,
 
-     loaderType,
 
-     audioDisabled
 
-   }) => {
 
-     if (currentTimeline === segmentTimeline) {
 
-       return false;
 
-     }
 
-     if (loaderType === 'audio') {
 
-       const lastMainTimelineChange = timelineChangeController.lastTimelineChange({
 
-         type: 'main'
 
-       }); // Audio loader should wait if:
 
-       //
 
-       // * main hasn't had a timeline change yet (thus has not loaded its first segment)
 
-       // * main hasn't yet changed to the timeline audio is looking to load
 
-       return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline;
 
-     } // The main loader only needs to wait for timeline changes if there's demuxed audio.
 
-     // Otherwise, there's nothing to wait for, since audio would be muxed into the main
 
-     // loader's segments (or the content is audio/video only and handled by the main
 
-     // loader).
 
-     if (loaderType === 'main' && audioDisabled) {
 
-       const pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
 
-         type: 'audio'
 
-       }); // Main loader should wait for the audio loader if audio is not pending a timeline
 
-       // change to the current timeline.
 
-       //
 
-       // Since the main loader is responsible for setting the timestamp offset for both
 
-       // audio and video, the main loader must wait for audio to be about to change to its
 
-       // timeline before setting the offset, otherwise, if audio is behind in loading,
 
-       // segments from the previous timeline would be adjusted by the new timestamp offset.
 
-       //
 
-       // This requirement means that video will not cross a timeline until the audio is
 
-       // about to cross to it, so that way audio and video will always cross the timeline
 
-       // together.
 
-       //
 
-       // In addition to normal timeline changes, these rules also apply to the start of a
 
-       // stream (going from a non-existent timeline, -1, to timeline 0). It's important
 
-       // that these rules apply to the first timeline change because if they did not, it's
 
-       // possible that the main loader will cross two timelines before the audio loader has
 
-       // crossed one. Logic may be implemented to handle the startup as a special case, but
 
-       // it's easier to simply treat all timeline changes the same.
 
-       if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) {
 
-         return false;
 
-       }
 
-       return true;
 
-     }
 
-     return false;
 
-   };
 
-   const mediaDuration = timingInfos => {
 
-     let maxDuration = 0;
 
-     ['video', 'audio'].forEach(function (type) {
 
-       const typeTimingInfo = timingInfos[`${type}TimingInfo`];
 
-       if (!typeTimingInfo) {
 
-         return;
 
-       }
 
-       const {
 
-         start,
 
-         end
 
-       } = typeTimingInfo;
 
-       let duration;
 
-       if (typeof start === 'bigint' || typeof end === 'bigint') {
 
-         duration = window.BigInt(end) - window.BigInt(start);
 
-       } else if (typeof start === 'number' && typeof end === 'number') {
 
-         duration = end - start;
 
-       }
 
-       if (typeof duration !== 'undefined' && duration > maxDuration) {
 
-         maxDuration = duration;
 
-       }
 
-     }); // convert back to a number if it is lower than MAX_SAFE_INTEGER
 
-     // as we only need BigInt when we are above that.
 
-     if (typeof maxDuration === 'bigint' && maxDuration < Number.MAX_SAFE_INTEGER) {
 
-       maxDuration = Number(maxDuration);
 
-     }
 
-     return maxDuration;
 
-   };
 
-   const segmentTooLong = ({
 
-     segmentDuration,
 
-     maxDuration
 
-   }) => {
 
-     // 0 duration segments are most likely due to metadata only segments or a lack of
 
-     // information.
 
-     if (!segmentDuration) {
 
-       return false;
 
-     } // For HLS:
 
-     //
 
-     // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1
 
-     // The EXTINF duration of each Media Segment in the Playlist
 
-     // file, when rounded to the nearest integer, MUST be less than or equal
 
-     // to the target duration; longer segments can trigger playback stalls
 
-     // or other errors.
 
-     //
 
-     // For DASH, the mpd-parser uses the largest reported segment duration as the target
 
-     // duration. Although that reported duration is occasionally approximate (i.e., not
 
-     // exact), a strict check may report that a segment is too long more often in DASH.
 
-     return Math.round(segmentDuration) > maxDuration + TIME_FUDGE_FACTOR;
 
-   };
 
-   const getTroublesomeSegmentDurationMessage = (segmentInfo, sourceType) => {
 
-     // Right now we aren't following DASH's timing model exactly, so only perform
 
-     // this check for HLS content.
 
-     if (sourceType !== 'hls') {
 
-       return null;
 
-     }
 
-     const segmentDuration = mediaDuration({
 
-       audioTimingInfo: segmentInfo.audioTimingInfo,
 
-       videoTimingInfo: segmentInfo.videoTimingInfo
 
-     }); // Don't report if we lack information.
 
-     //
 
-     // If the segment has a duration of 0 it is either a lack of information or a
 
-     // metadata only segment and shouldn't be reported here.
 
-     if (!segmentDuration) {
 
-       return null;
 
-     }
 
-     const targetDuration = segmentInfo.playlist.targetDuration;
 
-     const isSegmentWayTooLong = segmentTooLong({
 
-       segmentDuration,
 
-       maxDuration: targetDuration * 2
 
-     });
 
-     const isSegmentSlightlyTooLong = segmentTooLong({
 
-       segmentDuration,
 
-       maxDuration: targetDuration
 
-     });
 
-     const segmentTooLongMessage = `Segment with index ${segmentInfo.mediaIndex} ` + `from playlist ${segmentInfo.playlist.id} ` + `has a duration of ${segmentDuration} ` + `when the reported duration is ${segmentInfo.duration} ` + `and the target duration is ${targetDuration}. ` + 'For HLS content, a duration in excess of the target duration may result in ' + 'playback issues. See the HLS specification section on EXT-X-TARGETDURATION for ' + 'more details: ' + 'https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1';
 
-     if (isSegmentWayTooLong || isSegmentSlightlyTooLong) {
 
-       return {
 
-         severity: isSegmentWayTooLong ? 'warn' : 'info',
 
-         message: segmentTooLongMessage
 
-       };
 
-     }
 
-     return null;
 
-   };
 
-   /**
 
-    * An object that manages segment loading and appending.
 
-    *
 
-    * @class SegmentLoader
 
-    * @param {Object} options required and optional options
 
-    * @extends videojs.EventTarget
 
-    */
 
-   class SegmentLoader extends videojs.EventTarget {
 
-     constructor(settings, options = {}) {
 
-       super(); // check pre-conditions
 
-       if (!settings) {
 
-         throw new TypeError('Initialization settings are required');
 
-       }
 
-       if (typeof settings.currentTime !== 'function') {
 
-         throw new TypeError('No currentTime getter specified');
 
-       }
 
-       if (!settings.mediaSource) {
 
-         throw new TypeError('No MediaSource specified');
 
-       } // public properties
 
-       this.bandwidth = settings.bandwidth;
 
-       this.throughput = {
 
-         rate: 0,
 
-         count: 0
 
-       };
 
-       this.roundTrip = NaN;
 
-       this.resetStats_();
 
-       this.mediaIndex = null;
 
-       this.partIndex = null; // private settings
 
-       this.hasPlayed_ = settings.hasPlayed;
 
-       this.currentTime_ = settings.currentTime;
 
-       this.seekable_ = settings.seekable;
 
-       this.seeking_ = settings.seeking;
 
-       this.duration_ = settings.duration;
 
-       this.mediaSource_ = settings.mediaSource;
 
-       this.vhs_ = settings.vhs;
 
-       this.loaderType_ = settings.loaderType;
 
-       this.currentMediaInfo_ = void 0;
 
-       this.startingMediaInfo_ = void 0;
 
-       this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
 
-       this.goalBufferLength_ = settings.goalBufferLength;
 
-       this.sourceType_ = settings.sourceType;
 
-       this.sourceUpdater_ = settings.sourceUpdater;
 
-       this.inbandTextTracks_ = settings.inbandTextTracks;
 
-       this.state_ = 'INIT';
 
-       this.timelineChangeController_ = settings.timelineChangeController;
 
-       this.shouldSaveSegmentTimingInfo_ = true;
 
-       this.parse708captions_ = settings.parse708captions;
 
-       this.useDtsForTimestampOffset_ = settings.useDtsForTimestampOffset;
 
-       this.captionServices_ = settings.captionServices;
 
-       this.exactManifestTimings = settings.exactManifestTimings; // private instance variables
 
-       this.checkBufferTimeout_ = null;
 
-       this.error_ = void 0;
 
-       this.currentTimeline_ = -1;
 
-       this.pendingSegment_ = null;
 
-       this.xhrOptions_ = null;
 
-       this.pendingSegments_ = [];
 
-       this.audioDisabled_ = false;
 
-       this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller
 
-       this.gopBuffer_ = [];
 
-       this.timeMapping_ = 0;
 
-       this.safeAppend_ = videojs.browser.IE_VERSION >= 11;
 
-       this.appendInitSegment_ = {
 
-         audio: true,
 
-         video: true
 
-       };
 
-       this.playlistOfLastInitSegment_ = {
 
-         audio: null,
 
-         video: null
 
-       };
 
-       this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough
 
-       // information yet to start the loading process (e.g., if the audio loader wants to
 
-       // load a segment from the next timeline but the main loader hasn't yet crossed that
 
-       // timeline), then the load call will be added to the queue until it is ready to be
 
-       // processed.
 
-       this.loadQueue_ = [];
 
-       this.metadataQueue_ = {
 
-         id3: [],
 
-         caption: []
 
-       };
 
-       this.waitingOnRemove_ = false;
 
-       this.quotaExceededErrorRetryTimeout_ = null; // Fragmented mp4 playback
 
-       this.activeInitSegmentId_ = null;
 
-       this.initSegments_ = {}; // HLSe playback
 
-       this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
 
-       this.keyCache_ = {};
 
-       this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
 
-       // between a time in the display time and a segment index within
 
-       // a playlist
 
-       this.syncController_ = settings.syncController;
 
-       this.syncPoint_ = {
 
-         segmentIndex: 0,
 
-         time: 0
 
-       };
 
-       this.transmuxer_ = this.createTransmuxer_();
 
-       this.triggerSyncInfoUpdate_ = () => this.trigger('syncinfoupdate');
 
-       this.syncController_.on('syncinfoupdate', this.triggerSyncInfoUpdate_);
 
-       this.mediaSource_.addEventListener('sourceopen', () => {
 
-         if (!this.isEndOfStream_()) {
 
-           this.ended_ = false;
 
-         }
 
-       }); // ...for determining the fetch location
 
-       this.fetchAtBuffer_ = false;
 
-       this.logger_ = logger(`SegmentLoader[${this.loaderType_}]`);
 
-       Object.defineProperty(this, 'state', {
 
-         get() {
 
-           return this.state_;
 
-         },
 
-         set(newState) {
 
-           if (newState !== this.state_) {
 
-             this.logger_(`${this.state_} -> ${newState}`);
 
-             this.state_ = newState;
 
-             this.trigger('statechange');
 
-           }
 
-         }
 
-       });
 
-       this.sourceUpdater_.on('ready', () => {
 
-         if (this.hasEnoughInfoToAppend_()) {
 
-           this.processCallQueue_();
 
-         }
 
-       }); // Only the main loader needs to listen for pending timeline changes, as the main
 
-       // loader should wait for audio to be ready to change its timeline so that both main
 
-       // and audio timelines change together. For more details, see the
 
-       // shouldWaitForTimelineChange function.
 
-       if (this.loaderType_ === 'main') {
 
-         this.timelineChangeController_.on('pendingtimelinechange', () => {
 
-           if (this.hasEnoughInfoToAppend_()) {
 
-             this.processCallQueue_();
 
-           }
 
-         });
 
-       } // The main loader only listens on pending timeline changes, but the audio loader,
 
-       // since its loads follow main, needs to listen on timeline changes. For more details,
 
-       // see the shouldWaitForTimelineChange function.
 
-       if (this.loaderType_ === 'audio') {
 
-         this.timelineChangeController_.on('timelinechange', () => {
 
-           if (this.hasEnoughInfoToLoad_()) {
 
-             this.processLoadQueue_();
 
-           }
 
-           if (this.hasEnoughInfoToAppend_()) {
 
-             this.processCallQueue_();
 
-           }
 
-         });
 
-       }
 
-     }
 
-     createTransmuxer_() {
 
-       return segmentTransmuxer.createTransmuxer({
 
-         remux: false,
 
-         alignGopsAtEnd: this.safeAppend_,
 
-         keepOriginalTimestamps: true,
 
-         parse708captions: this.parse708captions_,
 
-         captionServices: this.captionServices_
 
-       });
 
-     }
 
-     /**
 
-      * reset all of our media stats
 
-      *
 
-      * @private
 
-      */
 
-     resetStats_() {
 
-       this.mediaBytesTransferred = 0;
 
-       this.mediaRequests = 0;
 
-       this.mediaRequestsAborted = 0;
 
-       this.mediaRequestsTimedout = 0;
 
-       this.mediaRequestsErrored = 0;
 
-       this.mediaTransferDuration = 0;
 
-       this.mediaSecondsLoaded = 0;
 
-       this.mediaAppends = 0;
 
-     }
 
-     /**
 
-      * dispose of the SegmentLoader and reset to the default state
 
-      */
 
-     dispose() {
 
-       this.trigger('dispose');
 
-       this.state = 'DISPOSED';
 
-       this.pause();
 
-       this.abort_();
 
-       if (this.transmuxer_) {
 
-         this.transmuxer_.terminate();
 
-       }
 
-       this.resetStats_();
 
-       if (this.checkBufferTimeout_) {
 
-         window.clearTimeout(this.checkBufferTimeout_);
 
-       }
 
-       if (this.syncController_ && this.triggerSyncInfoUpdate_) {
 
-         this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_);
 
-       }
 
-       this.off();
 
-     }
 
-     setAudio(enable) {
 
-       this.audioDisabled_ = !enable;
 
-       if (enable) {
 
-         this.appendInitSegment_.audio = true;
 
-       } else {
 
-         // remove current track audio if it gets disabled
 
-         this.sourceUpdater_.removeAudio(0, this.duration_());
 
-       }
 
-     }
 
-     /**
 
-      * abort anything that is currently doing on with the SegmentLoader
 
-      * and reset to a default state
 
-      */
 
-     abort() {
 
-       if (this.state !== 'WAITING') {
 
-         if (this.pendingSegment_) {
 
-           this.pendingSegment_ = null;
 
-         }
 
-         return;
 
-       }
 
-       this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
 
-       // since we are no longer "waiting" on any requests. XHR callback is not always run
 
-       // when the request is aborted. This will prevent the loader from being stuck in the
 
-       // WAITING state indefinitely.
 
-       this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
 
-       // next segment
 
-       if (!this.paused()) {
 
-         this.monitorBuffer_();
 
-       }
 
-     }
 
-     /**
 
-      * abort all pending xhr requests and null any pending segements
 
-      *
 
-      * @private
 
-      */
 
-     abort_() {
 
-       if (this.pendingSegment_ && this.pendingSegment_.abortRequests) {
 
-         this.pendingSegment_.abortRequests();
 
-       } // clear out the segment being processed
 
-       this.pendingSegment_ = null;
 
-       this.callQueue_ = [];
 
-       this.loadQueue_ = [];
 
-       this.metadataQueue_.id3 = [];
 
-       this.metadataQueue_.caption = [];
 
-       this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_);
 
-       this.waitingOnRemove_ = false;
 
-       window.clearTimeout(this.quotaExceededErrorRetryTimeout_);
 
-       this.quotaExceededErrorRetryTimeout_ = null;
 
-     }
 
-     checkForAbort_(requestId) {
 
-       // If the state is APPENDING, then aborts will not modify the state, meaning the first
 
-       // callback that happens should reset the state to READY so that loading can continue.
 
-       if (this.state === 'APPENDING' && !this.pendingSegment_) {
 
-         this.state = 'READY';
 
-         return true;
 
-       }
 
-       if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) {
 
-         return true;
 
-       }
 
-       return false;
 
-     }
 
-     /**
 
-      * set an error on the segment loader and null out any pending segements
 
-      *
 
-      * @param {Error} error the error to set on the SegmentLoader
 
-      * @return {Error} the error that was set or that is currently set
 
-      */
 
-     error(error) {
 
-       if (typeof error !== 'undefined') {
 
-         this.logger_('error occurred:', error);
 
-         this.error_ = error;
 
-       }
 
-       this.pendingSegment_ = null;
 
-       return this.error_;
 
-     }
 
-     endOfStream() {
 
-       this.ended_ = true;
 
-       if (this.transmuxer_) {
 
-         // need to clear out any cached data to prepare for the new segment
 
-         segmentTransmuxer.reset(this.transmuxer_);
 
-       }
 
-       this.gopBuffer_.length = 0;
 
-       this.pause();
 
-       this.trigger('ended');
 
-     }
 
-     /**
 
-      * Indicates which time ranges are buffered
 
-      *
 
-      * @return {TimeRange}
 
-      *         TimeRange object representing the current buffered ranges
 
-      */
 
-     buffered_() {
 
-       const trackInfo = this.getMediaInfo_();
 
-       if (!this.sourceUpdater_ || !trackInfo) {
 
-         return createTimeRanges();
 
-       }
 
-       if (this.loaderType_ === 'main') {
 
-         const {
 
-           hasAudio,
 
-           hasVideo,
 
-           isMuxed
 
-         } = trackInfo;
 
-         if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) {
 
-           return this.sourceUpdater_.buffered();
 
-         }
 
-         if (hasVideo) {
 
-           return this.sourceUpdater_.videoBuffered();
 
-         }
 
-       } // One case that can be ignored for now is audio only with alt audio,
 
-       // as we don't yet have proper support for that.
 
-       return this.sourceUpdater_.audioBuffered();
 
-     }
 
-     /**
 
-      * Gets and sets init segment for the provided map
 
-      *
 
-      * @param {Object} map
 
-      *        The map object representing the init segment to get or set
 
-      * @param {boolean=} set
 
-      *        If true, the init segment for the provided map should be saved
 
-      * @return {Object}
 
-      *         map object for desired init segment
 
-      */
 
-     initSegmentForMap(map, set = false) {
 
-       if (!map) {
 
-         return null;
 
-       }
 
-       const id = initSegmentId(map);
 
-       let storedMap = this.initSegments_[id];
 
-       if (set && !storedMap && map.bytes) {
 
-         this.initSegments_[id] = storedMap = {
 
-           resolvedUri: map.resolvedUri,
 
-           byterange: map.byterange,
 
-           bytes: map.bytes,
 
-           tracks: map.tracks,
 
-           timescales: map.timescales
 
-         };
 
-       }
 
-       return storedMap || map;
 
-     }
 
-     /**
 
-      * Gets and sets key for the provided key
 
-      *
 
-      * @param {Object} key
 
-      *        The key object representing the key to get or set
 
-      * @param {boolean=} set
 
-      *        If true, the key for the provided key should be saved
 
-      * @return {Object}
 
-      *         Key object for desired key
 
-      */
 
-     segmentKey(key, set = false) {
 
-       if (!key) {
 
-         return null;
 
-       }
 
-       const id = segmentKeyId(key);
 
-       let storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
 
-       // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
 
-       if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) {
 
-         this.keyCache_[id] = storedKey = {
 
-           resolvedUri: key.resolvedUri,
 
-           bytes: key.bytes
 
-         };
 
-       }
 
-       const result = {
 
-         resolvedUri: (storedKey || key).resolvedUri
 
-       };
 
-       if (storedKey) {
 
-         result.bytes = storedKey.bytes;
 
-       }
 
-       return result;
 
-     }
 
-     /**
 
-      * Returns true if all configuration required for loading is present, otherwise false.
 
-      *
 
-      * @return {boolean} True if the all configuration is ready for loading
 
-      * @private
 
-      */
 
-     couldBeginLoading_() {
 
-       return this.playlist_ && !this.paused();
 
-     }
 
-     /**
 
-      * load a playlist and start to fill the buffer
 
-      */
 
-     load() {
 
-       // un-pause
 
-       this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
 
-       // specified
 
-       if (!this.playlist_) {
 
-         return;
 
-       } // if all the configuration is ready, initialize and begin loading
 
-       if (this.state === 'INIT' && this.couldBeginLoading_()) {
 
-         return this.init_();
 
-       } // if we're in the middle of processing a segment already, don't
 
-       // kick off an additional segment request
 
-       if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
 
-         return;
 
-       }
 
-       this.state = 'READY';
 
-     }
 
-     /**
 
-      * Once all the starting parameters have been specified, begin
 
-      * operation. This method should only be invoked from the INIT
 
-      * state.
 
-      *
 
-      * @private
 
-      */
 
-     init_() {
 
-       this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old
 
-       // audio data from the muxed content should be removed
 
-       this.resetEverything();
 
-       return this.monitorBuffer_();
 
-     }
 
-     /**
 
-      * set a playlist on the segment loader
 
-      *
 
-      * @param {PlaylistLoader} media the playlist to set on the segment loader
 
-      */
 
-     playlist(newPlaylist, options = {}) {
 
-       if (!newPlaylist) {
 
-         return;
 
-       }
 
-       const oldPlaylist = this.playlist_;
 
-       const segmentInfo = this.pendingSegment_;
 
-       this.playlist_ = newPlaylist;
 
-       this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
 
-       // is always our zero-time so force a sync update each time the playlist
 
-       // is refreshed from the server
 
-       //
 
-       // Use the INIT state to determine if playback has started, as the playlist sync info
 
-       // should be fixed once requests begin (as sync points are generated based on sync
 
-       // info), but not before then.
 
-       if (this.state === 'INIT') {
 
-         newPlaylist.syncInfo = {
 
-           mediaSequence: newPlaylist.mediaSequence,
 
-           time: 0
 
-         }; // Setting the date time mapping means mapping the program date time (if available)
 
-         // to time 0 on the player's timeline. The playlist's syncInfo serves a similar
 
-         // purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can
 
-         // be updated as the playlist is refreshed before the loader starts loading, the
 
-         // program date time mapping needs to be updated as well.
 
-         //
 
-         // This mapping is only done for the main loader because a program date time should
 
-         // map equivalently between playlists.
 
-         if (this.loaderType_ === 'main') {
 
-           this.syncController_.setDateTimeMappingForStart(newPlaylist);
 
-         }
 
-       }
 
-       let oldId = null;
 
-       if (oldPlaylist) {
 
-         if (oldPlaylist.id) {
 
-           oldId = oldPlaylist.id;
 
-         } else if (oldPlaylist.uri) {
 
-           oldId = oldPlaylist.uri;
 
-         }
 
-       }
 
-       this.logger_(`playlist update [${oldId} => ${newPlaylist.id || newPlaylist.uri}]`); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
 
-       // in LIVE, we always want to update with new playlists (including refreshes)
 
-       this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
 
-       // buffering now
 
-       if (this.state === 'INIT' && this.couldBeginLoading_()) {
 
-         return this.init_();
 
-       }
 
-       if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
 
-         if (this.mediaIndex !== null) {
 
-           // we must reset/resync the segment loader when we switch renditions and
 
-           // the segment loader is already synced to the previous rendition
 
-           // on playlist changes we want it to be possible to fetch
 
-           // at the buffer for vod but not for live. So we use resetLoader
 
-           // for live and resyncLoader for vod. We want this because
 
-           // if a playlist uses independent and non-independent segments/parts the
 
-           // buffer may not accurately reflect the next segment that we should try
 
-           // downloading.
 
-           if (!newPlaylist.endList) {
 
-             this.resetLoader();
 
-           } else {
 
-             this.resyncLoader();
 
-           }
 
-         }
 
-         this.currentMediaInfo_ = void 0;
 
-         this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined
 
-         return;
 
-       } // we reloaded the same playlist so we are in a live scenario
 
-       // and we will likely need to adjust the mediaIndex
 
-       const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
 
-       this.logger_(`live window shift [${mediaSequenceDiff}]`); // update the mediaIndex on the SegmentLoader
 
-       // this is important because we can abort a request and this value must be
 
-       // equal to the last appended mediaIndex
 
-       if (this.mediaIndex !== null) {
 
-         this.mediaIndex -= mediaSequenceDiff; // this can happen if we are going to load the first segment, but get a playlist
 
-         // update during that. mediaIndex would go from 0 to -1 if mediaSequence in the
 
-         // new playlist was incremented by 1.
 
-         if (this.mediaIndex < 0) {
 
-           this.mediaIndex = null;
 
-           this.partIndex = null;
 
-         } else {
 
-           const segment = this.playlist_.segments[this.mediaIndex]; // partIndex should remain the same for the same segment
 
-           // unless parts fell off of the playlist for this segment.
 
-           // In that case we need to reset partIndex and resync
 
-           if (this.partIndex && (!segment.parts || !segment.parts.length || !segment.parts[this.partIndex])) {
 
-             const mediaIndex = this.mediaIndex;
 
-             this.logger_(`currently processing part (index ${this.partIndex}) no longer exists.`);
 
-             this.resetLoader(); // We want to throw away the partIndex and the data associated with it,
 
-             // as the part was dropped from our current playlists segment.
 
-             // The mediaIndex will still be valid so keep that around.
 
-             this.mediaIndex = mediaIndex;
 
-           }
 
-         }
 
-       } // update the mediaIndex on the SegmentInfo object
 
-       // this is important because we will update this.mediaIndex with this value
 
-       // in `handleAppendsDone_` after the segment has been successfully appended
 
-       if (segmentInfo) {
 
-         segmentInfo.mediaIndex -= mediaSequenceDiff;
 
-         if (segmentInfo.mediaIndex < 0) {
 
-           segmentInfo.mediaIndex = null;
 
-           segmentInfo.partIndex = null;
 
-         } else {
 
-           // we need to update the referenced segment so that timing information is
 
-           // saved for the new playlist's segment, however, if the segment fell off the
 
-           // playlist, we can leave the old reference and just lose the timing info
 
-           if (segmentInfo.mediaIndex >= 0) {
 
-             segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
 
-           }
 
-           if (segmentInfo.partIndex >= 0 && segmentInfo.segment.parts) {
 
-             segmentInfo.part = segmentInfo.segment.parts[segmentInfo.partIndex];
 
-           }
 
-         }
 
-       }
 
-       this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
 
-     }
 
-     /**
 
-      * Prevent the loader from fetching additional segments. If there
 
-      * is a segment request outstanding, it will finish processing
 
-      * before the loader halts. A segment loader can be unpaused by
 
-      * calling load().
 
-      */
 
-     pause() {
 
-       if (this.checkBufferTimeout_) {
 
-         window.clearTimeout(this.checkBufferTimeout_);
 
-         this.checkBufferTimeout_ = null;
 
-       }
 
-     }
 
-     /**
 
-      * Returns whether the segment loader is fetching additional
 
-      * segments when given the opportunity. This property can be
 
-      * modified through calls to pause() and load().
 
-      */
 
-     paused() {
 
-       return this.checkBufferTimeout_ === null;
 
-     }
 
-     /**
 
-      * Delete all the buffered data and reset the SegmentLoader
 
-      *
 
-      * @param {Function} [done] an optional callback to be executed when the remove
 
-      * operation is complete
 
-      */
 
-     resetEverything(done) {
 
-       this.ended_ = false;
 
-       this.activeInitSegmentId_ = null;
 
-       this.appendInitSegment_ = {
 
-         audio: true,
 
-         video: true
 
-       };
 
-       this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything.
 
-       // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader,
 
-       // we then clamp the value to duration if necessary.
 
-       this.remove(0, Infinity, done); // clears fmp4 captions
 
-       if (this.transmuxer_) {
 
-         this.transmuxer_.postMessage({
 
-           action: 'clearAllMp4Captions'
 
-         }); // reset the cache in the transmuxer
 
-         this.transmuxer_.postMessage({
 
-           action: 'reset'
 
-         });
 
-       }
 
-     }
 
-     /**
 
-      * Force the SegmentLoader to resync and start loading around the currentTime instead
 
-      * of starting at the end of the buffer
 
-      *
 
-      * Useful for fast quality changes
 
-      */
 
-     resetLoader() {
 
-       this.fetchAtBuffer_ = false;
 
-       this.resyncLoader();
 
-     }
 
-     /**
 
-      * Force the SegmentLoader to restart synchronization and make a conservative guess
 
-      * before returning to the simple walk-forward method
 
-      */
 
-     resyncLoader() {
 
-       if (this.transmuxer_) {
 
-         // need to clear out any cached data to prepare for the new segment
 
-         segmentTransmuxer.reset(this.transmuxer_);
 
-       }
 
-       this.mediaIndex = null;
 
-       this.partIndex = null;
 
-       this.syncPoint_ = null;
 
-       this.isPendingTimestampOffset_ = false;
 
-       this.callQueue_ = [];
 
-       this.loadQueue_ = [];
 
-       this.metadataQueue_.id3 = [];
 
-       this.metadataQueue_.caption = [];
 
-       this.abort();
 
-       if (this.transmuxer_) {
 
-         this.transmuxer_.postMessage({
 
-           action: 'clearParsedMp4Captions'
 
-         });
 
-       }
 
-     }
 
-     /**
 
-      * Remove any data in the source buffer between start and end times
 
-      *
 
-      * @param {number} start - the start time of the region to remove from the buffer
 
-      * @param {number} end - the end time of the region to remove from the buffer
 
-      * @param {Function} [done] - an optional callback to be executed when the remove
 
-      * @param {boolean} force - force all remove operations to happen
 
-      * operation is complete
 
-      */
 
-     remove(start, end, done = () => {}, force = false) {
 
-       // clamp end to duration if we need to remove everything.
 
-       // This is due to a browser bug that causes issues if we remove to Infinity.
 
-       // videojs/videojs-contrib-hls#1225
 
-       if (end === Infinity) {
 
-         end = this.duration_();
 
-       } // skip removes that would throw an error
 
-       // commonly happens during a rendition switch at the start of a video
 
-       // from start 0 to end 0
 
-       if (end <= start) {
 
-         this.logger_('skipping remove because end ${end} is <= start ${start}');
 
-         return;
 
-       }
 
-       if (!this.sourceUpdater_ || !this.getMediaInfo_()) {
 
-         this.logger_('skipping remove because no source updater or starting media info'); // nothing to remove if we haven't processed any media
 
-         return;
 
-       } // set it to one to complete this function's removes
 
-       let removesRemaining = 1;
 
-       const removeFinished = () => {
 
-         removesRemaining--;
 
-         if (removesRemaining === 0) {
 
-           done();
 
-         }
 
-       };
 
-       if (force || !this.audioDisabled_) {
 
-         removesRemaining++;
 
-         this.sourceUpdater_.removeAudio(start, end, removeFinished);
 
-       } // While it would be better to only remove video if the main loader has video, this
 
-       // should be safe with audio only as removeVideo will call back even if there's no
 
-       // video buffer.
 
-       //
 
-       // In theory we can check to see if there's video before calling the remove, but in
 
-       // the event that we're switching between renditions and from video to audio only
 
-       // (when we add support for that), we may need to clear the video contents despite
 
-       // what the new media will contain.
 
-       if (force || this.loaderType_ === 'main') {
 
-         this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
 
-         removesRemaining++;
 
-         this.sourceUpdater_.removeVideo(start, end, removeFinished);
 
-       } // remove any captions and ID3 tags
 
-       for (const track in this.inbandTextTracks_) {
 
-         removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
 
-       }
 
-       removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes
 
-       removeFinished();
 
-     }
 
-     /**
 
-      * (re-)schedule monitorBufferTick_ to run as soon as possible
 
-      *
 
-      * @private
 
-      */
 
-     monitorBuffer_() {
 
-       if (this.checkBufferTimeout_) {
 
-         window.clearTimeout(this.checkBufferTimeout_);
 
-       }
 
-       this.checkBufferTimeout_ = window.setTimeout(this.monitorBufferTick_.bind(this), 1);
 
-     }
 
-     /**
 
-      * As long as the SegmentLoader is in the READY state, periodically
 
-      * invoke fillBuffer_().
 
-      *
 
-      * @private
 
-      */
 
-     monitorBufferTick_() {
 
-       if (this.state === 'READY') {
 
-         this.fillBuffer_();
 
-       }
 
-       if (this.checkBufferTimeout_) {
 
-         window.clearTimeout(this.checkBufferTimeout_);
 
-       }
 
-       this.checkBufferTimeout_ = window.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
 
-     }
 
-     /**
 
-      * fill the buffer with segements unless the sourceBuffers are
 
-      * currently updating
 
-      *
 
-      * Note: this function should only ever be called by monitorBuffer_
 
-      * and never directly
 
-      *
 
-      * @private
 
-      */
 
-     fillBuffer_() {
 
-       // TODO since the source buffer maintains a queue, and we shouldn't call this function
 
-       // except when we're ready for the next segment, this check can most likely be removed
 
-       if (this.sourceUpdater_.updating()) {
 
-         return;
 
-       } // see if we need to begin loading immediately
 
-       const segmentInfo = this.chooseNextRequest_();
 
-       if (!segmentInfo) {
 
-         return;
 
-       }
 
-       if (typeof segmentInfo.timestampOffset === 'number') {
 
-         this.isPendingTimestampOffset_ = false;
 
-         this.timelineChangeController_.pendingTimelineChange({
 
-           type: this.loaderType_,
 
-           from: this.currentTimeline_,
 
-           to: segmentInfo.timeline
 
-         });
 
-       }
 
-       this.loadSegment_(segmentInfo);
 
-     }
 
-     /**
 
-      * Determines if we should call endOfStream on the media source based
 
-      * on the state of the buffer or if appened segment was the final
 
-      * segment in the playlist.
 
-      *
 
-      * @param {number} [mediaIndex] the media index of segment we last appended
 
-      * @param {Object} [playlist] a media playlist object
 
-      * @return {boolean} do we need to call endOfStream on the MediaSource
 
-      */
 
-     isEndOfStream_(mediaIndex = this.mediaIndex, playlist = this.playlist_, partIndex = this.partIndex) {
 
-       if (!playlist || !this.mediaSource_) {
 
-         return false;
 
-       }
 
-       const segment = typeof mediaIndex === 'number' && playlist.segments[mediaIndex]; // mediaIndex is zero based but length is 1 based
 
-       const appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // true if there are no parts, or this is the last part.
 
-       const appendedLastPart = !segment || !segment.parts || partIndex + 1 === segment.parts.length; // if we've buffered to the end of the video, we need to call endOfStream
 
-       // so that MediaSources can trigger the `ended` event when it runs out of
 
-       // buffered data instead of waiting for me
 
-       return playlist.endList && this.mediaSource_.readyState === 'open' && appendedLastSegment && appendedLastPart;
 
-     }
 
-     /**
 
-      * Determines what request should be made given current segment loader state.
 
-      *
 
-      * @return {Object} a request object that describes the segment/part to load
 
-      */
 
-     chooseNextRequest_() {
 
-       const buffered = this.buffered_();
 
-       const bufferedEnd = lastBufferedEnd(buffered) || 0;
 
-       const bufferedTime = timeAheadOf(buffered, this.currentTime_());
 
-       const preloaded = !this.hasPlayed_() && bufferedTime >= 1;
 
-       const haveEnoughBuffer = bufferedTime >= this.goalBufferLength_();
 
-       const segments = this.playlist_.segments; // return no segment if:
 
-       // 1. we don't have segments
 
-       // 2. The video has not yet played and we already downloaded a segment
 
-       // 3. we already have enough buffered time
 
-       if (!segments.length || preloaded || haveEnoughBuffer) {
 
-         return null;
 
-       }
 
-       this.syncPoint_ = this.syncPoint_ || this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
 
-       const next = {
 
-         partIndex: null,
 
-         mediaIndex: null,
 
-         startOfSegment: null,
 
-         playlist: this.playlist_,
 
-         isSyncRequest: Boolean(!this.syncPoint_)
 
-       };
 
-       if (next.isSyncRequest) {
 
-         next.mediaIndex = getSyncSegmentCandidate(this.currentTimeline_, segments, bufferedEnd);
 
-       } else if (this.mediaIndex !== null) {
 
-         const segment = segments[this.mediaIndex];
 
-         const partIndex = typeof this.partIndex === 'number' ? this.partIndex : -1;
 
-         next.startOfSegment = segment.end ? segment.end : bufferedEnd;
 
-         if (segment.parts && segment.parts[partIndex + 1]) {
 
-           next.mediaIndex = this.mediaIndex;
 
-           next.partIndex = partIndex + 1;
 
-         } else {
 
-           next.mediaIndex = this.mediaIndex + 1;
 
-         }
 
-       } else {
 
-         // Find the segment containing the end of the buffer or current time.
 
-         const {
 
-           segmentIndex,
 
-           startTime,
 
-           partIndex
 
-         } = Playlist.getMediaInfoForTime({
 
-           exactManifestTimings: this.exactManifestTimings,
 
-           playlist: this.playlist_,
 
-           currentTime: this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(),
 
-           startingPartIndex: this.syncPoint_.partIndex,
 
-           startingSegmentIndex: this.syncPoint_.segmentIndex,
 
-           startTime: this.syncPoint_.time
 
-         });
 
-         next.getMediaInfoForTime = this.fetchAtBuffer_ ? `bufferedEnd ${bufferedEnd}` : `currentTime ${this.currentTime_()}`;
 
-         next.mediaIndex = segmentIndex;
 
-         next.startOfSegment = startTime;
 
-         next.partIndex = partIndex;
 
-       }
 
-       const nextSegment = segments[next.mediaIndex];
 
-       let nextPart = nextSegment && typeof next.partIndex === 'number' && nextSegment.parts && nextSegment.parts[next.partIndex]; // if the next segment index is invalid or
 
-       // the next partIndex is invalid do not choose a next segment.
 
-       if (!nextSegment || typeof next.partIndex === 'number' && !nextPart) {
 
-         return null;
 
-       } // if the next segment has parts, and we don't have a partIndex.
 
-       // Set partIndex to 0
 
-       if (typeof next.partIndex !== 'number' && nextSegment.parts) {
 
-         next.partIndex = 0;
 
-         nextPart = nextSegment.parts[0];
 
-       } // if we have no buffered data then we need to make sure
 
-       // that the next part we append is "independent" if possible.
 
-       // So we check if the previous part is independent, and request
 
-       // it if it is.
 
-       if (!bufferedTime && nextPart && !nextPart.independent) {
 
-         if (next.partIndex === 0) {
 
-           const lastSegment = segments[next.mediaIndex - 1];
 
-           const lastSegmentLastPart = lastSegment.parts && lastSegment.parts.length && lastSegment.parts[lastSegment.parts.length - 1];
 
-           if (lastSegmentLastPart && lastSegmentLastPart.independent) {
 
-             next.mediaIndex -= 1;
 
-             next.partIndex = lastSegment.parts.length - 1;
 
-             next.independent = 'previous segment';
 
-           }
 
-         } else if (nextSegment.parts[next.partIndex - 1].independent) {
 
-           next.partIndex -= 1;
 
-           next.independent = 'previous part';
 
-         }
 
-       }
 
-       const ended = this.mediaSource_ && this.mediaSource_.readyState === 'ended'; // do not choose a next segment if all of the following:
 
-       // 1. this is the last segment in the playlist
 
-       // 2. end of stream has been called on the media source already
 
-       // 3. the player is not seeking
 
-       if (next.mediaIndex >= segments.length - 1 && ended && !this.seeking_()) {
 
-         return null;
 
-       }
 
-       return this.generateSegmentInfo_(next);
 
-     }
 
-     generateSegmentInfo_(options) {
 
-       const {
 
-         independent,
 
-         playlist,
 
-         mediaIndex,
 
-         startOfSegment,
 
-         isSyncRequest,
 
-         partIndex,
 
-         forceTimestampOffset,
 
-         getMediaInfoForTime
 
-       } = options;
 
-       const segment = playlist.segments[mediaIndex];
 
-       const part = typeof partIndex === 'number' && segment.parts[partIndex];
 
-       const segmentInfo = {
 
-         requestId: 'segment-loader-' + Math.random(),
 
-         // resolve the segment URL relative to the playlist
 
-         uri: part && part.resolvedUri || segment.resolvedUri,
 
-         // the segment's mediaIndex at the time it was requested
 
-         mediaIndex,
 
-         partIndex: part ? partIndex : null,
 
-         // whether or not to update the SegmentLoader's state with this
 
-         // segment's mediaIndex
 
-         isSyncRequest,
 
-         startOfSegment,
 
-         // the segment's playlist
 
-         playlist,
 
-         // unencrypted bytes of the segment
 
-         bytes: null,
 
-         // when a key is defined for this segment, the encrypted bytes
 
-         encryptedBytes: null,
 
-         // The target timestampOffset for this segment when we append it
 
-         // to the source buffer
 
-         timestampOffset: null,
 
-         // The timeline that the segment is in
 
-         timeline: segment.timeline,
 
-         // The expected duration of the segment in seconds
 
-         duration: part && part.duration || segment.duration,
 
-         // retain the segment in case the playlist updates while doing an async process
 
-         segment,
 
-         part,
 
-         byteLength: 0,
 
-         transmuxer: this.transmuxer_,
 
-         // type of getMediaInfoForTime that was used to get this segment
 
-         getMediaInfoForTime,
 
-         independent
 
-       };
 
-       const overrideCheck = typeof forceTimestampOffset !== 'undefined' ? forceTimestampOffset : this.isPendingTimestampOffset_;
 
-       segmentInfo.timestampOffset = this.timestampOffsetForSegment_({
 
-         segmentTimeline: segment.timeline,
 
-         currentTimeline: this.currentTimeline_,
 
-         startOfSegment,
 
-         buffered: this.buffered_(),
 
-         overrideCheck
 
-       });
 
-       const audioBufferedEnd = lastBufferedEnd(this.sourceUpdater_.audioBuffered());
 
-       if (typeof audioBufferedEnd === 'number') {
 
-         // since the transmuxer is using the actual timing values, but the buffer is
 
-         // adjusted by the timestamp offset, we must adjust the value here
 
-         segmentInfo.audioAppendStart = audioBufferedEnd - this.sourceUpdater_.audioTimestampOffset();
 
-       }
 
-       if (this.sourceUpdater_.videoBuffered().length) {
 
-         segmentInfo.gopsToAlignWith = gopsSafeToAlignWith(this.gopBuffer_,
 
-         // since the transmuxer is using the actual timing values, but the time is
 
-         // adjusted by the timestmap offset, we must adjust the value here
 
-         this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(), this.timeMapping_);
 
-       }
 
-       return segmentInfo;
 
-     } // get the timestampoffset for a segment,
 
-     // added so that vtt segment loader can override and prevent
 
-     // adding timestamp offsets.
 
-     timestampOffsetForSegment_(options) {
 
-       return timestampOffsetForSegment(options);
 
-     }
 
-     /**
 
-      * Determines if the network has enough bandwidth to complete the current segment
 
-      * request in a timely manner. If not, the request will be aborted early and bandwidth
 
-      * updated to trigger a playlist switch.
 
-      *
 
-      * @param {Object} stats
 
-      *        Object containing stats about the request timing and size
 
-      * @private
 
-      */
 
-     earlyAbortWhenNeeded_(stats) {
 
-       if (this.vhs_.tech_.paused() ||
 
-       // Don't abort if the current playlist is on the lowestEnabledRendition
 
-       // TODO: Replace using timeout with a boolean indicating whether this playlist is
 
-       //       the lowestEnabledRendition.
 
-       !this.xhrOptions_.timeout ||
 
-       // Don't abort if we have no bandwidth information to estimate segment sizes
 
-       !this.playlist_.attributes.BANDWIDTH) {
 
-         return;
 
-       } // Wait at least 1 second since the first byte of data has been received before
 
-       // using the calculated bandwidth from the progress event to allow the bitrate
 
-       // to stabilize
 
-       if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
 
-         return;
 
-       }
 
-       const currentTime = this.currentTime_();
 
-       const measuredBandwidth = stats.bandwidth;
 
-       const segmentDuration = this.pendingSegment_.duration;
 
-       const requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
 
-       // if we are only left with less than 1 second when the request completes.
 
-       // A negative timeUntilRebuffering indicates we are already rebuffering
 
-       const timeUntilRebuffer$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
 
-       // is larger than the estimated time until the player runs out of forward buffer
 
-       if (requestTimeRemaining <= timeUntilRebuffer$1) {
 
-         return;
 
-       }
 
-       const switchCandidate = minRebufferMaxBandwidthSelector({
 
-         main: this.vhs_.playlists.main,
 
-         currentTime,
 
-         bandwidth: measuredBandwidth,
 
-         duration: this.duration_(),
 
-         segmentDuration,
 
-         timeUntilRebuffer: timeUntilRebuffer$1,
 
-         currentTimeline: this.currentTimeline_,
 
-         syncController: this.syncController_
 
-       });
 
-       if (!switchCandidate) {
 
-         return;
 
-       }
 
-       const rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1;
 
-       const timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
 
-       let minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
 
-       // potential round trip time of the new request so that we are not too aggressive
 
-       // with switching to a playlist that might save us a fraction of a second.
 
-       if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) {
 
-         minimumTimeSaving = 1;
 
-       }
 
-       if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
 
-         return;
 
-       } // set the bandwidth to that of the desired playlist being sure to scale by
 
-       // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
 
-       // don't trigger a bandwidthupdate as the bandwidth is artifial
 
-       this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
 
-       this.trigger('earlyabort');
 
-     }
 
-     handleAbort_(segmentInfo) {
 
-       this.logger_(`Aborting ${segmentInfoString(segmentInfo)}`);
 
-       this.mediaRequestsAborted += 1;
 
-     }
 
-     /**
 
-      * XHR `progress` event handler
 
-      *
 
-      * @param {Event}
 
-      *        The XHR `progress` event
 
-      * @param {Object} simpleSegment
 
-      *        A simplified segment object copy
 
-      * @private
 
-      */
 
-     handleProgress_(event, simpleSegment) {
 
-       this.earlyAbortWhenNeeded_(simpleSegment.stats);
 
-       if (this.checkForAbort_(simpleSegment.requestId)) {
 
-         return;
 
-       }
 
-       this.trigger('progress');
 
-     }
 
-     handleTrackInfo_(simpleSegment, trackInfo) {
 
-       this.earlyAbortWhenNeeded_(simpleSegment.stats);
 
-       if (this.checkForAbort_(simpleSegment.requestId)) {
 
-         return;
 
-       }
 
-       if (this.checkForIllegalMediaSwitch(trackInfo)) {
 
-         return;
 
-       }
 
-       trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with.
 
-       // Guard against cases where we're not getting track info at all until we are
 
-       // certain that all streams will provide it.
 
-       if (!shallowEqual(this.currentMediaInfo_, trackInfo)) {
 
-         this.appendInitSegment_ = {
 
-           audio: true,
 
-           video: true
 
-         };
 
-         this.startingMediaInfo_ = trackInfo;
 
-         this.currentMediaInfo_ = trackInfo;
 
-         this.logger_('trackinfo update', trackInfo);
 
-         this.trigger('trackinfo');
 
-       } // trackinfo may cause an abort if the trackinfo
 
-       // causes a codec change to an unsupported codec.
 
-       if (this.checkForAbort_(simpleSegment.requestId)) {
 
-         return;
 
-       } // set trackinfo on the pending segment so that
 
-       // it can append.
 
-       this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info
 
-       if (this.hasEnoughInfoToAppend_()) {
 
-         this.processCallQueue_();
 
-       }
 
-     }
 
-     handleTimingInfo_(simpleSegment, mediaType, timeType, time) {
 
-       this.earlyAbortWhenNeeded_(simpleSegment.stats);
 
-       if (this.checkForAbort_(simpleSegment.requestId)) {
 
-         return;
 
-       }
 
-       const segmentInfo = this.pendingSegment_;
 
-       const timingInfoProperty = timingInfoPropertyForMedia(mediaType);
 
-       segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {};
 
-       segmentInfo[timingInfoProperty][timeType] = time;
 
-       this.logger_(`timinginfo: ${mediaType} - ${timeType} - ${time}`); // check if any calls were waiting on the timing info
 
-       if (this.hasEnoughInfoToAppend_()) {
 
-         this.processCallQueue_();
 
-       }
 
-     }
 
-     handleCaptions_(simpleSegment, captionData) {
 
-       this.earlyAbortWhenNeeded_(simpleSegment.stats);
 
-       if (this.checkForAbort_(simpleSegment.requestId)) {
 
-         return;
 
-       } // This could only happen with fmp4 segments, but
 
-       // should still not happen in general
 
-       if (captionData.length === 0) {
 
-         this.logger_('SegmentLoader received no captions from a caption event');
 
-         return;
 
-       }
 
-       const segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing
 
-       // can be adjusted by the timestamp offset
 
-       if (!segmentInfo.hasAppendedData_) {
 
-         this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData));
 
-         return;
 
-       }
 
-       const timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset();
 
-       const captionTracks = {}; // get total start/end and captions for each track/stream
 
-       captionData.forEach(caption => {
 
-         // caption.stream is actually a track name...
 
-         // set to the existing values in tracks or default values
 
-         captionTracks[caption.stream] = captionTracks[caption.stream] || {
 
-           // Infinity, as any other value will be less than this
 
-           startTime: Infinity,
 
-           captions: [],
 
-           // 0 as an other value will be more than this
 
-           endTime: 0
 
-         };
 
-         const captionTrack = captionTracks[caption.stream];
 
-         captionTrack.startTime = Math.min(captionTrack.startTime, caption.startTime + timestampOffset);
 
-         captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset);
 
-         captionTrack.captions.push(caption);
 
-       });
 
-       Object.keys(captionTracks).forEach(trackName => {
 
-         const {
 
-           startTime,
 
-           endTime,
 
-           captions
 
-         } = captionTracks[trackName];
 
-         const inbandTextTracks = this.inbandTextTracks_;
 
-         this.logger_(`adding cues from ${startTime} -> ${endTime} for ${trackName}`);
 
-         createCaptionsTrackIfNotExists(inbandTextTracks, this.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track.
 
-         // We do this because a rendition change that also changes the timescale for captions
 
-         // will result in captions being re-parsed for certain segments. If we add them again
 
-         // without clearing we will have two of the same captions visible.
 
-         removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]);
 
-         addCaptionData({
 
-           captionArray: captions,
 
-           inbandTextTracks,
 
-           timestampOffset
 
-         });
 
-       }); // Reset stored captions since we added parsed
 
-       // captions to a text track at this point
 
-       if (this.transmuxer_) {
 
-         this.transmuxer_.postMessage({
 
-           action: 'clearParsedMp4Captions'
 
-         });
 
-       }
 
-     }
 
-     handleId3_(simpleSegment, id3Frames, dispatchType) {
 
-       this.earlyAbortWhenNeeded_(simpleSegment.stats);
 
-       if (this.checkForAbort_(simpleSegment.requestId)) {
 
-         return;
 
-       }
 
-       const segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set
 
-       if (!segmentInfo.hasAppendedData_) {
 
-         this.metadataQueue_.id3.push(this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType));
 
-         return;
 
-       }
 
-       const timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset(); // There's potentially an issue where we could double add metadata if there's a muxed
 
-       // audio/video source with a metadata track, and an alt audio with a metadata track.
 
-       // However, this probably won't happen, and if it does it can be handled then.
 
-       createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.vhs_.tech_);
 
-       addMetadata({
 
-         inbandTextTracks: this.inbandTextTracks_,
 
-         metadataArray: id3Frames,
 
-         timestampOffset,
 
-         videoDuration: this.duration_()
 
-       });
 
-     }
 
-     processMetadataQueue_() {
 
-       this.metadataQueue_.id3.forEach(fn => fn());
 
-       this.metadataQueue_.caption.forEach(fn => fn());
 
-       this.metadataQueue_.id3 = [];
 
-       this.metadataQueue_.caption = [];
 
-     }
 
-     processCallQueue_() {
 
-       const callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the
 
-       // functions may check the length of the load queue and default to pushing themselves
 
-       // back onto the queue.
 
-       this.callQueue_ = [];
 
-       callQueue.forEach(fun => fun());
 
-     }
 
-     processLoadQueue_() {
 
-       const loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the
 
-       // functions may check the length of the load queue and default to pushing themselves
 
-       // back onto the queue.
 
-       this.loadQueue_ = [];
 
-       loadQueue.forEach(fun => fun());
 
-     }
 
-     /**
 
-      * Determines whether the loader has enough info to load the next segment.
 
-      *
 
-      * @return {boolean}
 
-      *         Whether or not the loader has enough info to load the next segment
 
-      */
 
-     hasEnoughInfoToLoad_() {
 
-       // Since primary timing goes by video, only the audio loader potentially needs to wait
 
-       // to load.
 
-       if (this.loaderType_ !== 'audio') {
 
-         return true;
 
-       }
 
-       const segmentInfo = this.pendingSegment_; // A fill buffer must have already run to establish a pending segment before there's
 
-       // enough info to load.
 
-       if (!segmentInfo) {
 
-         return false;
 
-       } // The first segment can and should be loaded immediately so that source buffers are
 
-       // created together (before appending). Source buffer creation uses the presence of
 
-       // audio and video data to determine whether to create audio/video source buffers, and
 
-       // uses processed (transmuxed or parsed) media to determine the types required.
 
-       if (!this.getCurrentMediaInfo_()) {
 
-         return true;
 
-       }
 
-       if (
 
-       // Technically, instead of waiting to load a segment on timeline changes, a segment
 
-       // can be requested and downloaded and only wait before it is transmuxed or parsed.
 
-       // But in practice, there are a few reasons why it is better to wait until a loader
 
-       // is ready to append that segment before requesting and downloading:
 
-       //
 
-       // 1. Because audio and main loaders cross discontinuities together, if this loader
 
-       //    is waiting for the other to catch up, then instead of requesting another
 
-       //    segment and using up more bandwidth, by not yet loading, more bandwidth is
 
-       //    allotted to the loader currently behind.
 
-       // 2. media-segment-request doesn't have to have logic to consider whether a segment
 
-       // is ready to be processed or not, isolating the queueing behavior to the loader.
 
-       // 3. The audio loader bases some of its segment properties on timing information
 
-       //    provided by the main loader, meaning that, if the logic for waiting on
 
-       //    processing was in media-segment-request, then it would also need to know how
 
-       //    to re-generate the segment information after the main loader caught up.
 
-       shouldWaitForTimelineChange({
 
-         timelineChangeController: this.timelineChangeController_,
 
-         currentTimeline: this.currentTimeline_,
 
-         segmentTimeline: segmentInfo.timeline,
 
-         loaderType: this.loaderType_,
 
-         audioDisabled: this.audioDisabled_
 
-       })) {
 
-         return false;
 
-       }
 
-       return true;
 
-     }
 
-     getCurrentMediaInfo_(segmentInfo = this.pendingSegment_) {
 
-       return segmentInfo && segmentInfo.trackInfo || this.currentMediaInfo_;
 
-     }
 
-     getMediaInfo_(segmentInfo = this.pendingSegment_) {
 
-       return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_;
 
-     }
 
-     getPendingSegmentPlaylist() {
 
-       return this.pendingSegment_ ? this.pendingSegment_.playlist : null;
 
-     }
 
-     hasEnoughInfoToAppend_() {
 
-       if (!this.sourceUpdater_.ready()) {
 
-         return false;
 
-       } // If content needs to be removed or the loader is waiting on an append reattempt,
 
-       // then no additional content should be appended until the prior append is resolved.
 
-       if (this.waitingOnRemove_ || this.quotaExceededErrorRetryTimeout_) {
 
-         return false;
 
-       }
 
-       const segmentInfo = this.pendingSegment_;
 
-       const trackInfo = this.getCurrentMediaInfo_(); // no segment to append any data for or
 
-       // we do not have information on this specific
 
-       // segment yet
 
-       if (!segmentInfo || !trackInfo) {
 
-         return false;
 
-       }
 
-       const {
 
-         hasAudio,
 
-         hasVideo,
 
-         isMuxed
 
-       } = trackInfo;
 
-       if (hasVideo && !segmentInfo.videoTimingInfo) {
 
-         return false;
 
-       } // muxed content only relies on video timing information for now.
 
-       if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) {
 
-         return false;
 
-       }
 
-       if (shouldWaitForTimelineChange({
 
-         timelineChangeController: this.timelineChangeController_,
 
-         currentTimeline: this.currentTimeline_,
 
-         segmentTimeline: segmentInfo.timeline,
 
-         loaderType: this.loaderType_,
 
-         audioDisabled: this.audioDisabled_
 
-       })) {
 
-         return false;
 
-       }
 
-       return true;
 
-     }
 
-     handleData_(simpleSegment, result) {
 
-       this.earlyAbortWhenNeeded_(simpleSegment.stats);
 
-       if (this.checkForAbort_(simpleSegment.requestId)) {
 
-         return;
 
-       } // If there's anything in the call queue, then this data came later and should be
 
-       // executed after the calls currently queued.
 
-       if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) {
 
-         this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result));
 
-         return;
 
-       }
 
-       const segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time
 
-       this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats
 
-       this.updateMediaSecondsLoaded_(segmentInfo.part || segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort
 
-       // logic may change behavior depending on the state, and changing state too early may
 
-       // inflate our estimates of bandwidth. In the future this should be re-examined to
 
-       // note more granular states.
 
-       // don't process and append data if the mediaSource is closed
 
-       if (this.mediaSource_.readyState === 'closed') {
 
-         return;
 
-       } // if this request included an initialization segment, save that data
 
-       // to the initSegment cache
 
-       if (simpleSegment.map) {
 
-         simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request
 
-         segmentInfo.segment.map = simpleSegment.map;
 
-       } // if this request included a segment key, save that data in the cache
 
-       if (simpleSegment.key) {
 
-         this.segmentKey(simpleSegment.key, true);
 
-       }
 
-       segmentInfo.isFmp4 = simpleSegment.isFmp4;
 
-       segmentInfo.timingInfo = segmentInfo.timingInfo || {};
 
-       if (segmentInfo.isFmp4) {
 
-         this.trigger('fmp4');
 
-         segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start;
 
-       } else {
 
-         const trackInfo = this.getCurrentMediaInfo_();
 
-         const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
 
-         let firstVideoFrameTimeForData;
 
-         if (useVideoTimingInfo) {
 
-           firstVideoFrameTimeForData = segmentInfo.videoTimingInfo.start;
 
-         } // Segment loader knows more about segment timing than the transmuxer (in certain
 
-         // aspects), so make any changes required for a more accurate start time.
 
-         // Don't set the end time yet, as the segment may not be finished processing.
 
-         segmentInfo.timingInfo.start = this.trueSegmentStart_({
 
-           currentStart: segmentInfo.timingInfo.start,
 
-           playlist: segmentInfo.playlist,
 
-           mediaIndex: segmentInfo.mediaIndex,
 
-           currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(),
 
-           useVideoTimingInfo,
 
-           firstVideoFrameTimeForData,
 
-           videoTimingInfo: segmentInfo.videoTimingInfo,
 
-           audioTimingInfo: segmentInfo.audioTimingInfo
 
-         });
 
-       } // Init segments for audio and video only need to be appended in certain cases. Now
 
-       // that data is about to be appended, we can check the final cases to determine
 
-       // whether we should append an init segment.
 
-       this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info,
 
-       // as we use the start of the segment to offset the best guess (playlist provided)
 
-       // timestamp offset.
 
-       this.updateSourceBufferTimestampOffset_(segmentInfo); // if this is a sync request we need to determine whether it should
 
-       // be appended or not.
 
-       if (segmentInfo.isSyncRequest) {
 
-         // first save/update our timing info for this segment.
 
-         // this is what allows us to choose an accurate segment
 
-         // and the main reason we make a sync request.
 
-         this.updateTimingInfoEnd_(segmentInfo);
 
-         this.syncController_.saveSegmentTimingInfo({
 
-           segmentInfo,
 
-           shouldSaveTimelineMapping: this.loaderType_ === 'main'
 
-         });
 
-         const next = this.chooseNextRequest_(); // If the sync request isn't the segment that would be requested next
 
-         // after taking into account its timing info, do not append it.
 
-         if (next.mediaIndex !== segmentInfo.mediaIndex || next.partIndex !== segmentInfo.partIndex) {
 
-           this.logger_('sync segment was incorrect, not appending');
 
-           return;
 
-         } // otherwise append it like any other segment as our guess was correct.
 
-         this.logger_('sync segment was correct, appending');
 
-       } // Save some state so that in the future anything waiting on first append (and/or
 
-       // timestamp offset(s)) can process immediately. While the extra state isn't optimal,
 
-       // we need some notion of whether the timestamp offset or other relevant information
 
-       // has had a chance to be set.
 
-       segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags.
 
-       this.processMetadataQueue_();
 
-       this.appendData_(segmentInfo, result);
 
-     }
 
-     updateAppendInitSegmentStatus(segmentInfo, type) {
 
-       // alt audio doesn't manage timestamp offset
 
-       if (this.loaderType_ === 'main' && typeof segmentInfo.timestampOffset === 'number' &&
 
-       // in the case that we're handling partial data, we don't want to append an init
 
-       // segment for each chunk
 
-       !segmentInfo.changedTimestampOffset) {
 
-         // if the timestamp offset changed, the timeline may have changed, so we have to re-
 
-         // append init segments
 
-         this.appendInitSegment_ = {
 
-           audio: true,
 
-           video: true
 
-         };
 
-       }
 
-       if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) {
 
-         // make sure we append init segment on playlist changes, in case the media config
 
-         // changed
 
-         this.appendInitSegment_[type] = true;
 
-       }
 
-     }
 
-     getInitSegmentAndUpdateState_({
 
-       type,
 
-       initSegment,
 
-       map,
 
-       playlist
 
-     }) {
 
-       // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section
 
-       // (Section 3) required to parse the applicable Media Segments.  It applies to every
 
-       // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag
 
-       // or until the end of the playlist."
 
-       // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5
 
-       if (map) {
 
-         const id = initSegmentId(map);
 
-         if (this.activeInitSegmentId_ === id) {
 
-           // don't need to re-append the init segment if the ID matches
 
-           return null;
 
-         } // a map-specified init segment takes priority over any transmuxed (or otherwise
 
-         // obtained) init segment
 
-         //
 
-         // this also caches the init segment for later use
 
-         initSegment = this.initSegmentForMap(map, true).bytes;
 
-         this.activeInitSegmentId_ = id;
 
-       } // We used to always prepend init segments for video, however, that shouldn't be
 
-       // necessary. Instead, we should only append on changes, similar to what we've always
 
-       // done for audio. This is more important (though may not be that important) for
 
-       // frame-by-frame appending for LHLS, simply because of the increased quantity of
 
-       // appends.
 
-       if (initSegment && this.appendInitSegment_[type]) {
 
-         // Make sure we track the playlist that we last used for the init segment, so that
 
-         // we can re-append the init segment in the event that we get data from a new
 
-         // playlist. Discontinuities and track changes are handled in other sections.
 
-         this.playlistOfLastInitSegment_[type] = playlist; // Disable future init segment appends for this type. Until a change is necessary.
 
-         this.appendInitSegment_[type] = false; // we need to clear out the fmp4 active init segment id, since
 
-         // we are appending the muxer init segment
 
-         this.activeInitSegmentId_ = null;
 
-         return initSegment;
 
-       }
 
-       return null;
 
-     }
 
-     handleQuotaExceededError_({
 
-       segmentInfo,
 
-       type,
 
-       bytes
 
-     }, error) {
 
-       const audioBuffered = this.sourceUpdater_.audioBuffered();
 
-       const videoBuffered = this.sourceUpdater_.videoBuffered(); // For now we're ignoring any notion of gaps in the buffer, but they, in theory,
 
-       // should be cleared out during the buffer removals. However, log in case it helps
 
-       // debug.
 
-       if (audioBuffered.length > 1) {
 
-         this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the audio buffer: ' + timeRangesToArray(audioBuffered).join(', '));
 
-       }
 
-       if (videoBuffered.length > 1) {
 
-         this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the video buffer: ' + timeRangesToArray(videoBuffered).join(', '));
 
-       }
 
-       const audioBufferStart = audioBuffered.length ? audioBuffered.start(0) : 0;
 
-       const audioBufferEnd = audioBuffered.length ? audioBuffered.end(audioBuffered.length - 1) : 0;
 
-       const videoBufferStart = videoBuffered.length ? videoBuffered.start(0) : 0;
 
-       const videoBufferEnd = videoBuffered.length ? videoBuffered.end(videoBuffered.length - 1) : 0;
 
-       if (audioBufferEnd - audioBufferStart <= MIN_BACK_BUFFER && videoBufferEnd - videoBufferStart <= MIN_BACK_BUFFER) {
 
-         // Can't remove enough buffer to make room for new segment (or the browser doesn't
 
-         // allow for appends of segments this size). In the future, it may be possible to
 
-         // split up the segment and append in pieces, but for now, error out this playlist
 
-         // in an attempt to switch to a more manageable rendition.
 
-         this.logger_('On QUOTA_EXCEEDED_ERR, single segment too large to append to ' + 'buffer, triggering an error. ' + `Appended byte length: ${bytes.byteLength}, ` + `audio buffer: ${timeRangesToArray(audioBuffered).join(', ')}, ` + `video buffer: ${timeRangesToArray(videoBuffered).join(', ')}, `);
 
-         this.error({
 
-           message: 'Quota exceeded error with append of a single segment of content',
 
-           excludeUntil: Infinity
 
-         });
 
-         this.trigger('error');
 
-         return;
 
-       } // To try to resolve the quota exceeded error, clear back buffer and retry. This means
 
-       // that the segment-loader should block on future events until this one is handled, so
 
-       // that it doesn't keep moving onto further segments. Adding the call to the call
 
-       // queue will prevent further appends until waitingOnRemove_ and
 
-       // quotaExceededErrorRetryTimeout_ are cleared.
 
-       //
 
-       // Note that this will only block the current loader. In the case of demuxed content,
 
-       // the other load may keep filling as fast as possible. In practice, this should be
 
-       // OK, as it is a rare case when either audio has a high enough bitrate to fill up a
 
-       // source buffer, or video fills without enough room for audio to append (and without
 
-       // the availability of clearing out seconds of back buffer to make room for audio).
 
-       // But it might still be good to handle this case in the future as a TODO.
 
-       this.waitingOnRemove_ = true;
 
-       this.callQueue_.push(this.appendToSourceBuffer_.bind(this, {
 
-         segmentInfo,
 
-         type,
 
-         bytes
 
-       }));
 
-       const currentTime = this.currentTime_(); // Try to remove as much audio and video as possible to make room for new content
 
-       // before retrying.
 
-       const timeToRemoveUntil = currentTime - MIN_BACK_BUFFER;
 
-       this.logger_(`On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to ${timeToRemoveUntil}`);
 
-       this.remove(0, timeToRemoveUntil, () => {
 
-         this.logger_(`On QUOTA_EXCEEDED_ERR, retrying append in ${MIN_BACK_BUFFER}s`);
 
-         this.waitingOnRemove_ = false; // wait the length of time alotted in the back buffer to prevent wasted
 
-         // attempts (since we can't clear less than the minimum)
 
-         this.quotaExceededErrorRetryTimeout_ = window.setTimeout(() => {
 
-           this.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue');
 
-           this.quotaExceededErrorRetryTimeout_ = null;
 
-           this.processCallQueue_();
 
-         }, MIN_BACK_BUFFER * 1000);
 
-       }, true);
 
-     }
 
-     handleAppendError_({
 
-       segmentInfo,
 
-       type,
 
-       bytes
 
-     }, error) {
 
-       // if there's no error, nothing to do
 
-       if (!error) {
 
-         return;
 
-       }
 
-       if (error.code === QUOTA_EXCEEDED_ERR) {
 
-         this.handleQuotaExceededError_({
 
-           segmentInfo,
 
-           type,
 
-           bytes
 
-         }); // A quota exceeded error should be recoverable with a future re-append, so no need
 
-         // to trigger an append error.
 
-         return;
 
-       }
 
-       this.logger_('Received non QUOTA_EXCEEDED_ERR on append', error);
 
-       this.error(`${type} append of ${bytes.length}b failed for segment ` + `#${segmentInfo.mediaIndex} in playlist ${segmentInfo.playlist.id}`); // If an append errors, we often can't recover.
 
-       // (see https://w3c.github.io/media-source/#sourcebuffer-append-error).
 
-       //
 
-       // Trigger a special error so that it can be handled separately from normal,
 
-       // recoverable errors.
 
-       this.trigger('appenderror');
 
-     }
 
-     appendToSourceBuffer_({
 
-       segmentInfo,
 
-       type,
 
-       initSegment,
 
-       data,
 
-       bytes
 
-     }) {
 
-       // If this is a re-append, bytes were already created and don't need to be recreated
 
-       if (!bytes) {
 
-         const segments = [data];
 
-         let byteLength = data.byteLength;
 
-         if (initSegment) {
 
-           // if the media initialization segment is changing, append it before the content
 
-           // segment
 
-           segments.unshift(initSegment);
 
-           byteLength += initSegment.byteLength;
 
-         } // Technically we should be OK appending the init segment separately, however, we
 
-         // haven't yet tested that, and prepending is how we have always done things.
 
-         bytes = concatSegments({
 
-           bytes: byteLength,
 
-           segments
 
-         });
 
-       }
 
-       this.sourceUpdater_.appendBuffer({
 
-         segmentInfo,
 
-         type,
 
-         bytes
 
-       }, this.handleAppendError_.bind(this, {
 
-         segmentInfo,
 
-         type,
 
-         bytes
 
-       }));
 
-     }
 
-     handleSegmentTimingInfo_(type, requestId, segmentTimingInfo) {
 
-       if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
 
-         return;
 
-       }
 
-       const segment = this.pendingSegment_.segment;
 
-       const timingInfoProperty = `${type}TimingInfo`;
 
-       if (!segment[timingInfoProperty]) {
 
-         segment[timingInfoProperty] = {};
 
-       }
 
-       segment[timingInfoProperty].transmuxerPrependedSeconds = segmentTimingInfo.prependedContentDuration || 0;
 
-       segment[timingInfoProperty].transmuxedPresentationStart = segmentTimingInfo.start.presentation;
 
-       segment[timingInfoProperty].transmuxedDecodeStart = segmentTimingInfo.start.decode;
 
-       segment[timingInfoProperty].transmuxedPresentationEnd = segmentTimingInfo.end.presentation;
 
-       segment[timingInfoProperty].transmuxedDecodeEnd = segmentTimingInfo.end.decode; // mainly used as a reference for debugging
 
-       segment[timingInfoProperty].baseMediaDecodeTime = segmentTimingInfo.baseMediaDecodeTime;
 
-     }
 
-     appendData_(segmentInfo, result) {
 
-       const {
 
-         type,
 
-         data
 
-       } = result;
 
-       if (!data || !data.byteLength) {
 
-         return;
 
-       }
 
-       if (type === 'audio' && this.audioDisabled_) {
 
-         return;
 
-       }
 
-       const initSegment = this.getInitSegmentAndUpdateState_({
 
-         type,
 
-         initSegment: result.initSegment,
 
-         playlist: segmentInfo.playlist,
 
-         map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null
 
-       });
 
-       this.appendToSourceBuffer_({
 
-         segmentInfo,
 
-         type,
 
-         initSegment,
 
-         data
 
-       });
 
-     }
 
-     /**
 
-      * load a specific segment from a request into the buffer
 
-      *
 
-      * @private
 
-      */
 
-     loadSegment_(segmentInfo) {
 
-       this.state = 'WAITING';
 
-       this.pendingSegment_ = segmentInfo;
 
-       this.trimBackBuffer_(segmentInfo);
 
-       if (typeof segmentInfo.timestampOffset === 'number') {
 
-         if (this.transmuxer_) {
 
-           this.transmuxer_.postMessage({
 
-             action: 'clearAllMp4Captions'
 
-           });
 
-         }
 
-       }
 
-       if (!this.hasEnoughInfoToLoad_()) {
 
-         this.loadQueue_.push(() => {
 
-           // regenerate the audioAppendStart, timestampOffset, etc as they
 
-           // may have changed since this function was added to the queue.
 
-           const options = _extends$1({}, segmentInfo, {
 
-             forceTimestampOffset: true
 
-           });
 
-           _extends$1(segmentInfo, this.generateSegmentInfo_(options));
 
-           this.isPendingTimestampOffset_ = false;
 
-           this.updateTransmuxerAndRequestSegment_(segmentInfo);
 
-         });
 
-         return;
 
-       }
 
-       this.updateTransmuxerAndRequestSegment_(segmentInfo);
 
-     }
 
-     updateTransmuxerAndRequestSegment_(segmentInfo) {
 
-       // We'll update the source buffer's timestamp offset once we have transmuxed data, but
 
-       // the transmuxer still needs to be updated before then.
 
-       //
 
-       // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp
 
-       // offset must be passed to the transmuxer for stream correcting adjustments.
 
-       if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) {
 
-         this.gopBuffer_.length = 0; // gopsToAlignWith was set before the GOP buffer was cleared
 
-         segmentInfo.gopsToAlignWith = [];
 
-         this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh
 
-         this.transmuxer_.postMessage({
 
-           action: 'reset'
 
-         });
 
-         this.transmuxer_.postMessage({
 
-           action: 'setTimestampOffset',
 
-           timestampOffset: segmentInfo.timestampOffset
 
-         });
 
-       }
 
-       const simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
 
-       const isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex);
 
-       const isWalkingForward = this.mediaIndex !== null;
 
-       const isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ &&
 
-       // currentTimeline starts at -1, so we shouldn't end the timeline switching to 0,
 
-       // the first timeline
 
-       segmentInfo.timeline > 0;
 
-       const isEndOfTimeline = isEndOfStream || isWalkingForward && isDiscontinuity;
 
-       this.logger_(`Requesting ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated with this segment, but it is not cached (identified by a lack of bytes),
 
-       // then this init segment has never been seen before and should be appended.
 
-       //
 
-       // At this point the content type (audio/video or both) is not yet known, but it should be safe to set
 
-       // both to true and leave the decision of whether to append the init segment to append time.
 
-       if (simpleSegment.map && !simpleSegment.map.bytes) {
 
-         this.logger_('going to request init segment.');
 
-         this.appendInitSegment_ = {
 
-           video: true,
 
-           audio: true
 
-         };
 
-       }
 
-       segmentInfo.abortRequests = mediaSegmentRequest({
 
-         xhr: this.vhs_.xhr,
 
-         xhrOptions: this.xhrOptions_,
 
-         decryptionWorker: this.decrypter_,
 
-         segment: simpleSegment,
 
-         abortFn: this.handleAbort_.bind(this, segmentInfo),
 
-         progressFn: this.handleProgress_.bind(this),
 
-         trackInfoFn: this.handleTrackInfo_.bind(this),
 
-         timingInfoFn: this.handleTimingInfo_.bind(this),
 
-         videoSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'video', segmentInfo.requestId),
 
-         audioSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'audio', segmentInfo.requestId),
 
-         captionsFn: this.handleCaptions_.bind(this),
 
-         isEndOfTimeline,
 
-         endedTimelineFn: () => {
 
-           this.logger_('received endedtimeline callback');
 
-         },
 
-         id3Fn: this.handleId3_.bind(this),
 
-         dataFn: this.handleData_.bind(this),
 
-         doneFn: this.segmentRequestFinished_.bind(this),
 
-         onTransmuxerLog: ({
 
-           message,
 
-           level,
 
-           stream
 
-         }) => {
 
-           this.logger_(`${segmentInfoString(segmentInfo)} logged from transmuxer stream ${stream} as a ${level}: ${message}`);
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * trim the back buffer so that we don't have too much data
 
-      * in the source buffer
 
-      *
 
-      * @private
 
-      *
 
-      * @param {Object} segmentInfo - the current segment
 
-      */
 
-     trimBackBuffer_(segmentInfo) {
 
-       const removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
 
-       // buffer and a very conservative "garbage collector"
 
-       // We manually clear out the old buffer to ensure
 
-       // we don't trigger the QuotaExceeded error
 
-       // on the source buffer during subsequent appends
 
-       if (removeToTime > 0) {
 
-         this.remove(0, removeToTime);
 
-       }
 
-     }
 
-     /**
 
-      * created a simplified copy of the segment object with just the
 
-      * information necessary to perform the XHR and decryption
 
-      *
 
-      * @private
 
-      *
 
-      * @param {Object} segmentInfo - the current segment
 
-      * @return {Object} a simplified segment object copy
 
-      */
 
-     createSimplifiedSegmentObj_(segmentInfo) {
 
-       const segment = segmentInfo.segment;
 
-       const part = segmentInfo.part;
 
-       const simpleSegment = {
 
-         resolvedUri: part ? part.resolvedUri : segment.resolvedUri,
 
-         byterange: part ? part.byterange : segment.byterange,
 
-         requestId: segmentInfo.requestId,
 
-         transmuxer: segmentInfo.transmuxer,
 
-         audioAppendStart: segmentInfo.audioAppendStart,
 
-         gopsToAlignWith: segmentInfo.gopsToAlignWith,
 
-         part: segmentInfo.part
 
-       };
 
-       const previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex - 1];
 
-       if (previousSegment && previousSegment.timeline === segment.timeline) {
 
-         // The baseStartTime of a segment is used to handle rollover when probing the TS
 
-         // segment to retrieve timing information. Since the probe only looks at the media's
 
-         // times (e.g., PTS and DTS values of the segment), and doesn't consider the
 
-         // player's time (e.g., player.currentTime()), baseStartTime should reflect the
 
-         // media time as well. transmuxedDecodeEnd represents the end time of a segment, in
 
-         // seconds of media time, so should be used here. The previous segment is used since
 
-         // the end of the previous segment should represent the beginning of the current
 
-         // segment, so long as they are on the same timeline.
 
-         if (previousSegment.videoTimingInfo) {
 
-           simpleSegment.baseStartTime = previousSegment.videoTimingInfo.transmuxedDecodeEnd;
 
-         } else if (previousSegment.audioTimingInfo) {
 
-           simpleSegment.baseStartTime = previousSegment.audioTimingInfo.transmuxedDecodeEnd;
 
-         }
 
-       }
 
-       if (segment.key) {
 
-         // if the media sequence is greater than 2^32, the IV will be incorrect
 
-         // assuming 10s segments, that would be about 1300 years
 
-         const iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
 
-         simpleSegment.key = this.segmentKey(segment.key);
 
-         simpleSegment.key.iv = iv;
 
-       }
 
-       if (segment.map) {
 
-         simpleSegment.map = this.initSegmentForMap(segment.map);
 
-       }
 
-       return simpleSegment;
 
-     }
 
-     saveTransferStats_(stats) {
 
-       // every request counts as a media request even if it has been aborted
 
-       // or canceled due to a timeout
 
-       this.mediaRequests += 1;
 
-       if (stats) {
 
-         this.mediaBytesTransferred += stats.bytesReceived;
 
-         this.mediaTransferDuration += stats.roundTripTime;
 
-       }
 
-     }
 
-     saveBandwidthRelatedStats_(duration, stats) {
 
-       // byteLength will be used for throughput, and should be based on bytes receieved,
 
-       // which we only know at the end of the request and should reflect total bytes
 
-       // downloaded rather than just bytes processed from components of the segment
 
-       this.pendingSegment_.byteLength = stats.bytesReceived;
 
-       if (duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
 
-         this.logger_(`Ignoring segment's bandwidth because its duration of ${duration}` + ` is less than the min to record ${MIN_SEGMENT_DURATION_TO_SAVE_STATS}`);
 
-         return;
 
-       }
 
-       this.bandwidth = stats.bandwidth;
 
-       this.roundTrip = stats.roundTripTime;
 
-     }
 
-     handleTimeout_() {
 
-       // although the VTT segment loader bandwidth isn't really used, it's good to
 
-       // maintain functinality between segment loaders
 
-       this.mediaRequestsTimedout += 1;
 
-       this.bandwidth = 1;
 
-       this.roundTrip = NaN;
 
-       this.trigger('bandwidthupdate');
 
-       this.trigger('timeout');
 
-     }
 
-     /**
 
-      * Handle the callback from the segmentRequest function and set the
 
-      * associated SegmentLoader state and errors if necessary
 
-      *
 
-      * @private
 
-      */
 
-     segmentRequestFinished_(error, simpleSegment, result) {
 
-       // TODO handle special cases, e.g., muxed audio/video but only audio in the segment
 
-       // check the call queue directly since this function doesn't need to deal with any
 
-       // data, and can continue even if the source buffers are not set up and we didn't get
 
-       // any data from the segment
 
-       if (this.callQueue_.length) {
 
-         this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result));
 
-         return;
 
-       }
 
-       this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset
 
-       if (!this.pendingSegment_) {
 
-         return;
 
-       } // the request was aborted and the SegmentLoader has already started
 
-       // another request. this can happen when the timeout for an aborted
 
-       // request triggers due to a limitation in the XHR library
 
-       // do not count this as any sort of request or we risk double-counting
 
-       if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
 
-         return;
 
-       } // an error occurred from the active pendingSegment_ so reset everything
 
-       if (error) {
 
-         this.pendingSegment_ = null;
 
-         this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done
 
-         if (error.code === REQUEST_ERRORS.ABORTED) {
 
-           return;
 
-         }
 
-         this.pause(); // the error is really just that at least one of the requests timed-out
 
-         // set the bandwidth to a very low value and trigger an ABR switch to
 
-         // take emergency action
 
-         if (error.code === REQUEST_ERRORS.TIMEOUT) {
 
-           this.handleTimeout_();
 
-           return;
 
-         } // if control-flow has arrived here, then the error is real
 
-         // emit an error event to exclude the current playlist
 
-         this.mediaRequestsErrored += 1;
 
-         this.error(error);
 
-         this.trigger('error');
 
-         return;
 
-       }
 
-       const segmentInfo = this.pendingSegment_; // the response was a success so set any bandwidth stats the request
 
-       // generated for ABR purposes
 
-       this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
 
-       segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
 
-       if (result.gopInfo) {
 
-         this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_);
 
-       } // Although we may have already started appending on progress, we shouldn't switch the
 
-       // state away from loading until we are officially done loading the segment data.
 
-       this.state = 'APPENDING'; // used for testing
 
-       this.trigger('appending');
 
-       this.waitForAppendsToComplete_(segmentInfo);
 
-     }
 
-     setTimeMapping_(timeline) {
 
-       const timelineMapping = this.syncController_.mappingForTimeline(timeline);
 
-       if (timelineMapping !== null) {
 
-         this.timeMapping_ = timelineMapping;
 
-       }
 
-     }
 
-     updateMediaSecondsLoaded_(segment) {
 
-       if (typeof segment.start === 'number' && typeof segment.end === 'number') {
 
-         this.mediaSecondsLoaded += segment.end - segment.start;
 
-       } else {
 
-         this.mediaSecondsLoaded += segment.duration;
 
-       }
 
-     }
 
-     shouldUpdateTransmuxerTimestampOffset_(timestampOffset) {
 
-       if (timestampOffset === null) {
 
-         return false;
 
-       } // note that we're potentially using the same timestamp offset for both video and
 
-       // audio
 
-       if (this.loaderType_ === 'main' && timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
 
-         return true;
 
-       }
 
-       if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
 
-         return true;
 
-       }
 
-       return false;
 
-     }
 
-     trueSegmentStart_({
 
-       currentStart,
 
-       playlist,
 
-       mediaIndex,
 
-       firstVideoFrameTimeForData,
 
-       currentVideoTimestampOffset,
 
-       useVideoTimingInfo,
 
-       videoTimingInfo,
 
-       audioTimingInfo
 
-     }) {
 
-       if (typeof currentStart !== 'undefined') {
 
-         // if start was set once, keep using it
 
-         return currentStart;
 
-       }
 
-       if (!useVideoTimingInfo) {
 
-         return audioTimingInfo.start;
 
-       }
 
-       const previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame contained
 
-       // within that segment. Since the transmuxer maintains a cache of incomplete data
 
-       // from and/or the last frame seen, the start time may reflect a frame that starts
 
-       // in the previous segment. Check for that case and ensure the start time is
 
-       // accurate for the segment.
 
-       if (mediaIndex === 0 || !previousSegment || typeof previousSegment.start === 'undefined' || previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset) {
 
-         return firstVideoFrameTimeForData;
 
-       }
 
-       return videoTimingInfo.start;
 
-     }
 
-     waitForAppendsToComplete_(segmentInfo) {
 
-       const trackInfo = this.getCurrentMediaInfo_(segmentInfo);
 
-       if (!trackInfo) {
 
-         this.error({
 
-           message: 'No starting media returned, likely due to an unsupported media format.',
 
-           playlistExclusionDuration: Infinity
 
-         });
 
-         this.trigger('error');
 
-         return;
 
-       } // Although transmuxing is done, appends may not yet be finished. Throw a marker
 
-       // on each queue this loader is responsible for to ensure that the appends are
 
-       // complete.
 
-       const {
 
-         hasAudio,
 
-         hasVideo,
 
-         isMuxed
 
-       } = trackInfo;
 
-       const waitForVideo = this.loaderType_ === 'main' && hasVideo;
 
-       const waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed;
 
-       segmentInfo.waitingOnAppends = 0; // segments with no data
 
-       if (!segmentInfo.hasAppendedData_) {
 
-         if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') {
 
-           // When there's no audio or video data in the segment, there's no audio or video
 
-           // timing information.
 
-           //
 
-           // If there's no audio or video timing information, then the timestamp offset
 
-           // can't be adjusted to the appropriate value for the transmuxer and source
 
-           // buffers.
 
-           //
 
-           // Therefore, the next segment should be used to set the timestamp offset.
 
-           this.isPendingTimestampOffset_ = true;
 
-         } // override settings for metadata only segments
 
-         segmentInfo.timingInfo = {
 
-           start: 0
 
-         };
 
-         segmentInfo.waitingOnAppends++;
 
-         if (!this.isPendingTimestampOffset_) {
 
-           // update the timestampoffset
 
-           this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have
 
-           // no video/audio data.
 
-           this.processMetadataQueue_();
 
-         } // append is "done" instantly with no data.
 
-         this.checkAppendsDone_(segmentInfo);
 
-         return;
 
-       } // Since source updater could call back synchronously, do the increments first.
 
-       if (waitForVideo) {
 
-         segmentInfo.waitingOnAppends++;
 
-       }
 
-       if (waitForAudio) {
 
-         segmentInfo.waitingOnAppends++;
 
-       }
 
-       if (waitForVideo) {
 
-         this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
 
-       }
 
-       if (waitForAudio) {
 
-         this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
 
-       }
 
-     }
 
-     checkAppendsDone_(segmentInfo) {
 
-       if (this.checkForAbort_(segmentInfo.requestId)) {
 
-         return;
 
-       }
 
-       segmentInfo.waitingOnAppends--;
 
-       if (segmentInfo.waitingOnAppends === 0) {
 
-         this.handleAppendsDone_();
 
-       }
 
-     }
 
-     checkForIllegalMediaSwitch(trackInfo) {
 
-       const illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.getCurrentMediaInfo_(), trackInfo);
 
-       if (illegalMediaSwitchError) {
 
-         this.error({
 
-           message: illegalMediaSwitchError,
 
-           playlistExclusionDuration: Infinity
 
-         });
 
-         this.trigger('error');
 
-         return true;
 
-       }
 
-       return false;
 
-     }
 
-     updateSourceBufferTimestampOffset_(segmentInfo) {
 
-       if (segmentInfo.timestampOffset === null ||
 
-       // we don't yet have the start for whatever media type (video or audio) has
 
-       // priority, timing-wise, so we must wait
 
-       typeof segmentInfo.timingInfo.start !== 'number' ||
 
-       // already updated the timestamp offset for this segment
 
-       segmentInfo.changedTimestampOffset ||
 
-       // the alt audio loader should not be responsible for setting the timestamp offset
 
-       this.loaderType_ !== 'main') {
 
-         return;
 
-       }
 
-       let didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that
 
-       // the timing info here comes from video. In the event that the audio is longer than
 
-       // the video, this will trim the start of the audio.
 
-       // This also trims any offset from 0 at the beginning of the media
 
-       segmentInfo.timestampOffset -= this.getSegmentStartTimeForTimestampOffsetCalculation_({
 
-         videoTimingInfo: segmentInfo.segment.videoTimingInfo,
 
-         audioTimingInfo: segmentInfo.segment.audioTimingInfo,
 
-         timingInfo: segmentInfo.timingInfo
 
-       }); // In the event that there are part segment downloads, each will try to update the
 
-       // timestamp offset. Retaining this bit of state prevents us from updating in the
 
-       // future (within the same segment), however, there may be a better way to handle it.
 
-       segmentInfo.changedTimestampOffset = true;
 
-       if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
 
-         this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset);
 
-         didChange = true;
 
-       }
 
-       if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
 
-         this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset);
 
-         didChange = true;
 
-       }
 
-       if (didChange) {
 
-         this.trigger('timestampoffset');
 
-       }
 
-     }
 
-     getSegmentStartTimeForTimestampOffsetCalculation_({
 
-       videoTimingInfo,
 
-       audioTimingInfo,
 
-       timingInfo
 
-     }) {
 
-       if (!this.useDtsForTimestampOffset_) {
 
-         return timingInfo.start;
 
-       }
 
-       if (videoTimingInfo && typeof videoTimingInfo.transmuxedDecodeStart === 'number') {
 
-         return videoTimingInfo.transmuxedDecodeStart;
 
-       } // handle audio only
 
-       if (audioTimingInfo && typeof audioTimingInfo.transmuxedDecodeStart === 'number') {
 
-         return audioTimingInfo.transmuxedDecodeStart;
 
-       } // handle content not transmuxed (e.g., MP4)
 
-       return timingInfo.start;
 
-     }
 
-     updateTimingInfoEnd_(segmentInfo) {
 
-       segmentInfo.timingInfo = segmentInfo.timingInfo || {};
 
-       const trackInfo = this.getMediaInfo_();
 
-       const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
 
-       const prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo;
 
-       if (!prioritizedTimingInfo) {
 
-         return;
 
-       }
 
-       segmentInfo.timingInfo.end = typeof prioritizedTimingInfo.end === 'number' ?
 
-       // End time may not exist in a case where we aren't parsing the full segment (one
 
-       // current example is the case of fmp4), so use the rough duration to calculate an
 
-       // end time.
 
-       prioritizedTimingInfo.end : prioritizedTimingInfo.start + segmentInfo.duration;
 
-     }
 
-     /**
 
-      * callback to run when appendBuffer is finished. detects if we are
 
-      * in a good state to do things with the data we got, or if we need
 
-      * to wait for more
 
-      *
 
-      * @private
 
-      */
 
-     handleAppendsDone_() {
 
-       // appendsdone can cause an abort
 
-       if (this.pendingSegment_) {
 
-         this.trigger('appendsdone');
 
-       }
 
-       if (!this.pendingSegment_) {
 
-         this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in
 
-         // all appending cases?
 
-         if (!this.paused()) {
 
-           this.monitorBuffer_();
 
-         }
 
-         return;
 
-       }
 
-       const segmentInfo = this.pendingSegment_; // Now that the end of the segment has been reached, we can set the end time. It's
 
-       // best to wait until all appends are done so we're sure that the primary media is
 
-       // finished (and we have its end time).
 
-       this.updateTimingInfoEnd_(segmentInfo);
 
-       if (this.shouldSaveSegmentTimingInfo_) {
 
-         // Timeline mappings should only be saved for the main loader. This is for multiple
 
-         // reasons:
 
-         //
 
-         // 1) Only one mapping is saved per timeline, meaning that if both the audio loader
 
-         //    and the main loader try to save the timeline mapping, whichever comes later
 
-         //    will overwrite the first. In theory this is OK, as the mappings should be the
 
-         //    same, however, it breaks for (2)
 
-         // 2) In the event of a live stream, the initial live point will make for a somewhat
 
-         //    arbitrary mapping. If audio and video streams are not perfectly in-sync, then
 
-         //    the mapping will be off for one of the streams, dependent on which one was
 
-         //    first saved (see (1)).
 
-         // 3) Primary timing goes by video in VHS, so the mapping should be video.
 
-         //
 
-         // Since the audio loader will wait for the main loader to load the first segment,
 
-         // the main loader will save the first timeline mapping, and ensure that there won't
 
-         // be a case where audio loads two segments without saving a mapping (thus leading
 
-         // to missing segment timing info).
 
-         this.syncController_.saveSegmentTimingInfo({
 
-           segmentInfo,
 
-           shouldSaveTimelineMapping: this.loaderType_ === 'main'
 
-         });
 
-       }
 
-       const segmentDurationMessage = getTroublesomeSegmentDurationMessage(segmentInfo, this.sourceType_);
 
-       if (segmentDurationMessage) {
 
-         if (segmentDurationMessage.severity === 'warn') {
 
-           videojs.log.warn(segmentDurationMessage.message);
 
-         } else {
 
-           this.logger_(segmentDurationMessage.message);
 
-         }
 
-       }
 
-       this.recordThroughput_(segmentInfo);
 
-       this.pendingSegment_ = null;
 
-       this.state = 'READY';
 
-       if (segmentInfo.isSyncRequest) {
 
-         this.trigger('syncinfoupdate'); // if the sync request was not appended
 
-         // then it was not the correct segment.
 
-         // throw it away and use the data it gave us
 
-         // to get the correct one.
 
-         if (!segmentInfo.hasAppendedData_) {
 
-           this.logger_(`Throwing away un-appended sync request ${segmentInfoString(segmentInfo)}`);
 
-           return;
 
-         }
 
-       }
 
-       this.logger_(`Appended ${segmentInfoString(segmentInfo)}`);
 
-       this.addSegmentMetadataCue_(segmentInfo);
 
-       this.fetchAtBuffer_ = true;
 
-       if (this.currentTimeline_ !== segmentInfo.timeline) {
 
-         this.timelineChangeController_.lastTimelineChange({
 
-           type: this.loaderType_,
 
-           from: this.currentTimeline_,
 
-           to: segmentInfo.timeline
 
-         }); // If audio is not disabled, the main segment loader is responsible for updating
 
-         // the audio timeline as well. If the content is video only, this won't have any
 
-         // impact.
 
-         if (this.loaderType_ === 'main' && !this.audioDisabled_) {
 
-           this.timelineChangeController_.lastTimelineChange({
 
-             type: 'audio',
 
-             from: this.currentTimeline_,
 
-             to: segmentInfo.timeline
 
-           });
 
-         }
 
-       }
 
-       this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
 
-       // the following conditional otherwise it may consider this a bad "guess"
 
-       // and attempt to resync when the post-update seekable window and live
 
-       // point would mean that this was the perfect segment to fetch
 
-       this.trigger('syncinfoupdate');
 
-       const segment = segmentInfo.segment;
 
-       const part = segmentInfo.part;
 
-       const badSegmentGuess = segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3;
 
-       const badPartGuess = part && part.end && this.currentTime_() - part.end > segmentInfo.playlist.partTargetDuration * 3; // If we previously appended a segment/part that ends more than 3 part/targetDurations before
 
-       // the currentTime_ that means that our conservative guess was too conservative.
 
-       // In that case, reset the loader state so that we try to use any information gained
 
-       // from the previous request to create a new, more accurate, sync-point.
 
-       if (badSegmentGuess || badPartGuess) {
 
-         this.logger_(`bad ${badSegmentGuess ? 'segment' : 'part'} ${segmentInfoString(segmentInfo)}`);
 
-         this.resetEverything();
 
-         return;
 
-       }
 
-       const isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment
 
-       // and conservatively guess
 
-       if (isWalkingForward) {
 
-         this.trigger('bandwidthupdate');
 
-       }
 
-       this.trigger('progress');
 
-       this.mediaIndex = segmentInfo.mediaIndex;
 
-       this.partIndex = segmentInfo.partIndex; // any time an update finishes and the last segment is in the
 
-       // buffer, end the stream. this ensures the "ended" event will
 
-       // fire if playback reaches that point.
 
-       if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex)) {
 
-         this.endOfStream();
 
-       } // used for testing
 
-       this.trigger('appended');
 
-       if (segmentInfo.hasAppendedData_) {
 
-         this.mediaAppends++;
 
-       }
 
-       if (!this.paused()) {
 
-         this.monitorBuffer_();
 
-       }
 
-     }
 
-     /**
 
-      * Records the current throughput of the decrypt, transmux, and append
 
-      * portion of the semgment pipeline. `throughput.rate` is a the cumulative
 
-      * moving average of the throughput. `throughput.count` is the number of
 
-      * data points in the average.
 
-      *
 
-      * @private
 
-      * @param {Object} segmentInfo the object returned by loadSegment
 
-      */
 
-     recordThroughput_(segmentInfo) {
 
-       if (segmentInfo.duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
 
-         this.logger_(`Ignoring segment's throughput because its duration of ${segmentInfo.duration}` + ` is less than the min to record ${MIN_SEGMENT_DURATION_TO_SAVE_STATS}`);
 
-         return;
 
-       }
 
-       const rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
 
-       // by zero in the case where the throughput is ridiculously high
 
-       const segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
 
-       const segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
 
-       //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
 
-       this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
 
-     }
 
-     /**
 
-      * Adds a cue to the segment-metadata track with some metadata information about the
 
-      * segment
 
-      *
 
-      * @private
 
-      * @param {Object} segmentInfo
 
-      *        the object returned by loadSegment
 
-      * @method addSegmentMetadataCue_
 
-      */
 
-     addSegmentMetadataCue_(segmentInfo) {
 
-       if (!this.segmentMetadataTrack_) {
 
-         return;
 
-       }
 
-       const segment = segmentInfo.segment;
 
-       const start = segment.start;
 
-       const end = segment.end; // Do not try adding the cue if the start and end times are invalid.
 
-       if (!finite(start) || !finite(end)) {
 
-         return;
 
-       }
 
-       removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
 
-       const Cue = window.WebKitDataCue || window.VTTCue;
 
-       const value = {
 
-         custom: segment.custom,
 
-         dateTimeObject: segment.dateTimeObject,
 
-         dateTimeString: segment.dateTimeString,
 
-         bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
 
-         resolution: segmentInfo.playlist.attributes.RESOLUTION,
 
-         codecs: segmentInfo.playlist.attributes.CODECS,
 
-         byteLength: segmentInfo.byteLength,
 
-         uri: segmentInfo.uri,
 
-         timeline: segmentInfo.timeline,
 
-         playlist: segmentInfo.playlist.id,
 
-         start,
 
-         end
 
-       };
 
-       const data = JSON.stringify(value);
 
-       const cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
 
-       // the differences of WebKitDataCue in safari and VTTCue in other browsers
 
-       cue.value = value;
 
-       this.segmentMetadataTrack_.addCue(cue);
 
-     }
 
-   }
 
-   function noop() {}
 
-   const toTitleCase = function (string) {
 
-     if (typeof string !== 'string') {
 
-       return string;
 
-     }
 
-     return string.replace(/./, w => w.toUpperCase());
 
-   };
 
-   /**
 
-    * @file source-updater.js
 
-    */
 
-   const bufferTypes = ['video', 'audio'];
 
-   const updating = (type, sourceUpdater) => {
 
-     const sourceBuffer = sourceUpdater[`${type}Buffer`];
 
-     return sourceBuffer && sourceBuffer.updating || sourceUpdater.queuePending[type];
 
-   };
 
-   const nextQueueIndexOfType = (type, queue) => {
 
-     for (let i = 0; i < queue.length; i++) {
 
-       const queueEntry = queue[i];
 
-       if (queueEntry.type === 'mediaSource') {
 
-         // If the next entry is a media source entry (uses multiple source buffers), block
 
-         // processing to allow it to go through first.
 
-         return null;
 
-       }
 
-       if (queueEntry.type === type) {
 
-         return i;
 
-       }
 
-     }
 
-     return null;
 
-   };
 
-   const shiftQueue = (type, sourceUpdater) => {
 
-     if (sourceUpdater.queue.length === 0) {
 
-       return;
 
-     }
 
-     let queueIndex = 0;
 
-     let queueEntry = sourceUpdater.queue[queueIndex];
 
-     if (queueEntry.type === 'mediaSource') {
 
-       if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') {
 
-         sourceUpdater.queue.shift();
 
-         queueEntry.action(sourceUpdater);
 
-         if (queueEntry.doneFn) {
 
-           queueEntry.doneFn();
 
-         } // Only specific source buffer actions must wait for async updateend events. Media
 
-         // Source actions process synchronously. Therefore, both audio and video source
 
-         // buffers are now clear to process the next queue entries.
 
-         shiftQueue('audio', sourceUpdater);
 
-         shiftQueue('video', sourceUpdater);
 
-       } // Media Source actions require both source buffers, so if the media source action
 
-       // couldn't process yet (because one or both source buffers are busy), block other
 
-       // queue actions until both are available and the media source action can process.
 
-       return;
 
-     }
 
-     if (type === 'mediaSource') {
 
-       // If the queue was shifted by a media source action (this happens when pushing a
 
-       // media source action onto the queue), then it wasn't from an updateend event from an
 
-       // audio or video source buffer, so there's no change from previous state, and no
 
-       // processing should be done.
 
-       return;
 
-     } // Media source queue entries don't need to consider whether the source updater is
 
-     // started (i.e., source buffers are created) as they don't need the source buffers, but
 
-     // source buffer queue entries do.
 
-     if (!sourceUpdater.ready() || sourceUpdater.mediaSource.readyState === 'closed' || updating(type, sourceUpdater)) {
 
-       return;
 
-     }
 
-     if (queueEntry.type !== type) {
 
-       queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue);
 
-       if (queueIndex === null) {
 
-         // Either there's no queue entry that uses this source buffer type in the queue, or
 
-         // there's a media source queue entry before the next entry of this type, in which
 
-         // case wait for that action to process first.
 
-         return;
 
-       }
 
-       queueEntry = sourceUpdater.queue[queueIndex];
 
-     }
 
-     sourceUpdater.queue.splice(queueIndex, 1); // Keep a record that this source buffer type is in use.
 
-     //
 
-     // The queue pending operation must be set before the action is performed in the event
 
-     // that the action results in a synchronous event that is acted upon. For instance, if
 
-     // an exception is thrown that can be handled, it's possible that new actions will be
 
-     // appended to an empty queue and immediately executed, but would not have the correct
 
-     // pending information if this property was set after the action was performed.
 
-     sourceUpdater.queuePending[type] = queueEntry;
 
-     queueEntry.action(type, sourceUpdater);
 
-     if (!queueEntry.doneFn) {
 
-       // synchronous operation, process next entry
 
-       sourceUpdater.queuePending[type] = null;
 
-       shiftQueue(type, sourceUpdater);
 
-       return;
 
-     }
 
-   };
 
-   const cleanupBuffer = (type, sourceUpdater) => {
 
-     const buffer = sourceUpdater[`${type}Buffer`];
 
-     const titleType = toTitleCase(type);
 
-     if (!buffer) {
 
-       return;
 
-     }
 
-     buffer.removeEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
 
-     buffer.removeEventListener('error', sourceUpdater[`on${titleType}Error_`]);
 
-     sourceUpdater.codecs[type] = null;
 
-     sourceUpdater[`${type}Buffer`] = null;
 
-   };
 
-   const inSourceBuffers = (mediaSource, sourceBuffer) => mediaSource && sourceBuffer && Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1;
 
-   const actions = {
 
-     appendBuffer: (bytes, segmentInfo, onError) => (type, sourceUpdater) => {
 
-       const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
 
-       // or the media source does not contain this source buffer.
 
-       if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
 
-         return;
 
-       }
 
-       sourceUpdater.logger_(`Appending segment ${segmentInfo.mediaIndex}'s ${bytes.length} bytes to ${type}Buffer`);
 
-       try {
 
-         sourceBuffer.appendBuffer(bytes);
 
-       } catch (e) {
 
-         sourceUpdater.logger_(`Error with code ${e.code} ` + (e.code === QUOTA_EXCEEDED_ERR ? '(QUOTA_EXCEEDED_ERR) ' : '') + `when appending segment ${segmentInfo.mediaIndex} to ${type}Buffer`);
 
-         sourceUpdater.queuePending[type] = null;
 
-         onError(e);
 
-       }
 
-     },
 
-     remove: (start, end) => (type, sourceUpdater) => {
 
-       const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
 
-       // or the media source does not contain this source buffer.
 
-       if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
 
-         return;
 
-       }
 
-       sourceUpdater.logger_(`Removing ${start} to ${end} from ${type}Buffer`);
 
-       try {
 
-         sourceBuffer.remove(start, end);
 
-       } catch (e) {
 
-         sourceUpdater.logger_(`Remove ${start} to ${end} from ${type}Buffer failed`);
 
-       }
 
-     },
 
-     timestampOffset: offset => (type, sourceUpdater) => {
 
-       const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
 
-       // or the media source does not contain this source buffer.
 
-       if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
 
-         return;
 
-       }
 
-       sourceUpdater.logger_(`Setting ${type}timestampOffset to ${offset}`);
 
-       sourceBuffer.timestampOffset = offset;
 
-     },
 
-     callback: callback => (type, sourceUpdater) => {
 
-       callback();
 
-     },
 
-     endOfStream: error => sourceUpdater => {
 
-       if (sourceUpdater.mediaSource.readyState !== 'open') {
 
-         return;
 
-       }
 
-       sourceUpdater.logger_(`Calling mediaSource endOfStream(${error || ''})`);
 
-       try {
 
-         sourceUpdater.mediaSource.endOfStream(error);
 
-       } catch (e) {
 
-         videojs.log.warn('Failed to call media source endOfStream', e);
 
-       }
 
-     },
 
-     duration: duration => sourceUpdater => {
 
-       sourceUpdater.logger_(`Setting mediaSource duration to ${duration}`);
 
-       try {
 
-         sourceUpdater.mediaSource.duration = duration;
 
-       } catch (e) {
 
-         videojs.log.warn('Failed to set media source duration', e);
 
-       }
 
-     },
 
-     abort: () => (type, sourceUpdater) => {
 
-       if (sourceUpdater.mediaSource.readyState !== 'open') {
 
-         return;
 
-       }
 
-       const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
 
-       // or the media source does not contain this source buffer.
 
-       if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
 
-         return;
 
-       }
 
-       sourceUpdater.logger_(`calling abort on ${type}Buffer`);
 
-       try {
 
-         sourceBuffer.abort();
 
-       } catch (e) {
 
-         videojs.log.warn(`Failed to abort on ${type}Buffer`, e);
 
-       }
 
-     },
 
-     addSourceBuffer: (type, codec) => sourceUpdater => {
 
-       const titleType = toTitleCase(type);
 
-       const mime = getMimeForCodec(codec);
 
-       sourceUpdater.logger_(`Adding ${type}Buffer with codec ${codec} to mediaSource`);
 
-       const sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime);
 
-       sourceBuffer.addEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
 
-       sourceBuffer.addEventListener('error', sourceUpdater[`on${titleType}Error_`]);
 
-       sourceUpdater.codecs[type] = codec;
 
-       sourceUpdater[`${type}Buffer`] = sourceBuffer;
 
-     },
 
-     removeSourceBuffer: type => sourceUpdater => {
 
-       const sourceBuffer = sourceUpdater[`${type}Buffer`];
 
-       cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null
 
-       // or the media source does not contain this source buffer.
 
-       if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
 
-         return;
 
-       }
 
-       sourceUpdater.logger_(`Removing ${type}Buffer with codec ${sourceUpdater.codecs[type]} from mediaSource`);
 
-       try {
 
-         sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer);
 
-       } catch (e) {
 
-         videojs.log.warn(`Failed to removeSourceBuffer ${type}Buffer`, e);
 
-       }
 
-     },
 
-     changeType: codec => (type, sourceUpdater) => {
 
-       const sourceBuffer = sourceUpdater[`${type}Buffer`];
 
-       const mime = getMimeForCodec(codec); // can't do anything if the media source / source buffer is null
 
-       // or the media source does not contain this source buffer.
 
-       if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
 
-         return;
 
-       } // do not update codec if we don't need to.
 
-       if (sourceUpdater.codecs[type] === codec) {
 
-         return;
 
-       }
 
-       sourceUpdater.logger_(`changing ${type}Buffer codec from ${sourceUpdater.codecs[type]} to ${codec}`);
 
-       sourceBuffer.changeType(mime);
 
-       sourceUpdater.codecs[type] = codec;
 
-     }
 
-   };
 
-   const pushQueue = ({
 
-     type,
 
-     sourceUpdater,
 
-     action,
 
-     doneFn,
 
-     name
 
-   }) => {
 
-     sourceUpdater.queue.push({
 
-       type,
 
-       action,
 
-       doneFn,
 
-       name
 
-     });
 
-     shiftQueue(type, sourceUpdater);
 
-   };
 
-   const onUpdateend = (type, sourceUpdater) => e => {
 
-     // Although there should, in theory, be a pending action for any updateend receieved,
 
-     // there are some actions that may trigger updateend events without set definitions in
 
-     // the w3c spec. For instance, setting the duration on the media source may trigger
 
-     // updateend events on source buffers. This does not appear to be in the spec. As such,
 
-     // if we encounter an updateend without a corresponding pending action from our queue
 
-     // for that source buffer type, process the next action.
 
-     if (sourceUpdater.queuePending[type]) {
 
-       const doneFn = sourceUpdater.queuePending[type].doneFn;
 
-       sourceUpdater.queuePending[type] = null;
 
-       if (doneFn) {
 
-         // if there's an error, report it
 
-         doneFn(sourceUpdater[`${type}Error_`]);
 
-       }
 
-     }
 
-     shiftQueue(type, sourceUpdater);
 
-   };
 
-   /**
 
-    * A queue of callbacks to be serialized and applied when a
 
-    * MediaSource and its associated SourceBuffers are not in the
 
-    * updating state. It is used by the segment loader to update the
 
-    * underlying SourceBuffers when new data is loaded, for instance.
 
-    *
 
-    * @class SourceUpdater
 
-    * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from
 
-    * @param {string} mimeType the desired MIME type of the underlying SourceBuffer
 
-    */
 
-   class SourceUpdater extends videojs.EventTarget {
 
-     constructor(mediaSource) {
 
-       super();
 
-       this.mediaSource = mediaSource;
 
-       this.sourceopenListener_ = () => shiftQueue('mediaSource', this);
 
-       this.mediaSource.addEventListener('sourceopen', this.sourceopenListener_);
 
-       this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0
 
-       this.audioTimestampOffset_ = 0;
 
-       this.videoTimestampOffset_ = 0;
 
-       this.queue = [];
 
-       this.queuePending = {
 
-         audio: null,
 
-         video: null
 
-       };
 
-       this.delayedAudioAppendQueue_ = [];
 
-       this.videoAppendQueued_ = false;
 
-       this.codecs = {};
 
-       this.onVideoUpdateEnd_ = onUpdateend('video', this);
 
-       this.onAudioUpdateEnd_ = onUpdateend('audio', this);
 
-       this.onVideoError_ = e => {
 
-         // used for debugging
 
-         this.videoError_ = e;
 
-       };
 
-       this.onAudioError_ = e => {
 
-         // used for debugging
 
-         this.audioError_ = e;
 
-       };
 
-       this.createdSourceBuffers_ = false;
 
-       this.initializedEme_ = false;
 
-       this.triggeredReady_ = false;
 
-     }
 
-     initializedEme() {
 
-       this.initializedEme_ = true;
 
-       this.triggerReady();
 
-     }
 
-     hasCreatedSourceBuffers() {
 
-       // if false, likely waiting on one of the segment loaders to get enough data to create
 
-       // source buffers
 
-       return this.createdSourceBuffers_;
 
-     }
 
-     hasInitializedAnyEme() {
 
-       return this.initializedEme_;
 
-     }
 
-     ready() {
 
-       return this.hasCreatedSourceBuffers() && this.hasInitializedAnyEme();
 
-     }
 
-     createSourceBuffers(codecs) {
 
-       if (this.hasCreatedSourceBuffers()) {
 
-         // already created them before
 
-         return;
 
-       } // the intial addOrChangeSourceBuffers will always be
 
-       // two add buffers.
 
-       this.addOrChangeSourceBuffers(codecs);
 
-       this.createdSourceBuffers_ = true;
 
-       this.trigger('createdsourcebuffers');
 
-       this.triggerReady();
 
-     }
 
-     triggerReady() {
 
-       // only allow ready to be triggered once, this prevents the case
 
-       // where:
 
-       // 1. we trigger createdsourcebuffers
 
-       // 2. ie 11 synchronously initializates eme
 
-       // 3. the synchronous initialization causes us to trigger ready
 
-       // 4. We go back to the ready check in createSourceBuffers and ready is triggered again.
 
-       if (this.ready() && !this.triggeredReady_) {
 
-         this.triggeredReady_ = true;
 
-         this.trigger('ready');
 
-       }
 
-     }
 
-     /**
 
-      * Add a type of source buffer to the media source.
 
-      *
 
-      * @param {string} type
 
-      *        The type of source buffer to add.
 
-      *
 
-      * @param {string} codec
 
-      *        The codec to add the source buffer with.
 
-      */
 
-     addSourceBuffer(type, codec) {
 
-       pushQueue({
 
-         type: 'mediaSource',
 
-         sourceUpdater: this,
 
-         action: actions.addSourceBuffer(type, codec),
 
-         name: 'addSourceBuffer'
 
-       });
 
-     }
 
-     /**
 
-      * call abort on a source buffer.
 
-      *
 
-      * @param {string} type
 
-      *        The type of source buffer to call abort on.
 
-      */
 
-     abort(type) {
 
-       pushQueue({
 
-         type,
 
-         sourceUpdater: this,
 
-         action: actions.abort(type),
 
-         name: 'abort'
 
-       });
 
-     }
 
-     /**
 
-      * Call removeSourceBuffer and remove a specific type
 
-      * of source buffer on the mediaSource.
 
-      *
 
-      * @param {string} type
 
-      *        The type of source buffer to remove.
 
-      */
 
-     removeSourceBuffer(type) {
 
-       if (!this.canRemoveSourceBuffer()) {
 
-         videojs.log.error('removeSourceBuffer is not supported!');
 
-         return;
 
-       }
 
-       pushQueue({
 
-         type: 'mediaSource',
 
-         sourceUpdater: this,
 
-         action: actions.removeSourceBuffer(type),
 
-         name: 'removeSourceBuffer'
 
-       });
 
-     }
 
-     /**
 
-      * Whether or not the removeSourceBuffer function is supported
 
-      * on the mediaSource.
 
-      *
 
-      * @return {boolean}
 
-      *          if removeSourceBuffer can be called.
 
-      */
 
-     canRemoveSourceBuffer() {
 
-       // IE reports that it supports removeSourceBuffer, but often throws
 
-       // errors when attempting to use the function. So we report that it
 
-       // does not support removeSourceBuffer. As of Firefox 83 removeSourceBuffer
 
-       // throws errors, so we report that it does not support this as well.
 
-       return !videojs.browser.IE_VERSION && !videojs.browser.IS_FIREFOX && window.MediaSource && window.MediaSource.prototype && typeof window.MediaSource.prototype.removeSourceBuffer === 'function';
 
-     }
 
-     /**
 
-      * Whether or not the changeType function is supported
 
-      * on our SourceBuffers.
 
-      *
 
-      * @return {boolean}
 
-      *         if changeType can be called.
 
-      */
 
-     static canChangeType() {
 
-       return window.SourceBuffer && window.SourceBuffer.prototype && typeof window.SourceBuffer.prototype.changeType === 'function';
 
-     }
 
-     /**
 
-      * Whether or not the changeType function is supported
 
-      * on our SourceBuffers.
 
-      *
 
-      * @return {boolean}
 
-      *         if changeType can be called.
 
-      */
 
-     canChangeType() {
 
-       return this.constructor.canChangeType();
 
-     }
 
-     /**
 
-      * Call the changeType function on a source buffer, given the code and type.
 
-      *
 
-      * @param {string} type
 
-      *        The type of source buffer to call changeType on.
 
-      *
 
-      * @param {string} codec
 
-      *        The codec string to change type with on the source buffer.
 
-      */
 
-     changeType(type, codec) {
 
-       if (!this.canChangeType()) {
 
-         videojs.log.error('changeType is not supported!');
 
-         return;
 
-       }
 
-       pushQueue({
 
-         type,
 
-         sourceUpdater: this,
 
-         action: actions.changeType(codec),
 
-         name: 'changeType'
 
-       });
 
-     }
 
-     /**
 
-      * Add source buffers with a codec or, if they are already created,
 
-      * call changeType on source buffers using changeType.
 
-      *
 
-      * @param {Object} codecs
 
-      *        Codecs to switch to
 
-      */
 
-     addOrChangeSourceBuffers(codecs) {
 
-       if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) {
 
-         throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs');
 
-       }
 
-       Object.keys(codecs).forEach(type => {
 
-         const codec = codecs[type];
 
-         if (!this.hasCreatedSourceBuffers()) {
 
-           return this.addSourceBuffer(type, codec);
 
-         }
 
-         if (this.canChangeType()) {
 
-           this.changeType(type, codec);
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * Queue an update to append an ArrayBuffer.
 
-      *
 
-      * @param {MediaObject} object containing audioBytes and/or videoBytes
 
-      * @param {Function} done the function to call when done
 
-      * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
 
-      */
 
-     appendBuffer(options, doneFn) {
 
-       const {
 
-         segmentInfo,
 
-         type,
 
-         bytes
 
-       } = options;
 
-       this.processedAppend_ = true;
 
-       if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) {
 
-         this.delayedAudioAppendQueue_.push([options, doneFn]);
 
-         this.logger_(`delayed audio append of ${bytes.length} until video append`);
 
-         return;
 
-       } // In the case of certain errors, for instance, QUOTA_EXCEEDED_ERR, updateend will
 
-       // not be fired. This means that the queue will be blocked until the next action
 
-       // taken by the segment-loader. Provide a mechanism for segment-loader to handle
 
-       // these errors by calling the doneFn with the specific error.
 
-       const onError = doneFn;
 
-       pushQueue({
 
-         type,
 
-         sourceUpdater: this,
 
-         action: actions.appendBuffer(bytes, segmentInfo || {
 
-           mediaIndex: -1
 
-         }, onError),
 
-         doneFn,
 
-         name: 'appendBuffer'
 
-       });
 
-       if (type === 'video') {
 
-         this.videoAppendQueued_ = true;
 
-         if (!this.delayedAudioAppendQueue_.length) {
 
-           return;
 
-         }
 
-         const queue = this.delayedAudioAppendQueue_.slice();
 
-         this.logger_(`queuing delayed audio ${queue.length} appendBuffers`);
 
-         this.delayedAudioAppendQueue_.length = 0;
 
-         queue.forEach(que => {
 
-           this.appendBuffer.apply(this, que);
 
-         });
 
-       }
 
-     }
 
-     /**
 
-      * Get the audio buffer's buffered timerange.
 
-      *
 
-      * @return {TimeRange}
 
-      *         The audio buffer's buffered time range
 
-      */
 
-     audioBuffered() {
 
-       // no media source/source buffer or it isn't in the media sources
 
-       // source buffer list
 
-       if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) {
 
-         return createTimeRanges();
 
-       }
 
-       return this.audioBuffer.buffered ? this.audioBuffer.buffered : createTimeRanges();
 
-     }
 
-     /**
 
-      * Get the video buffer's buffered timerange.
 
-      *
 
-      * @return {TimeRange}
 
-      *         The video buffer's buffered time range
 
-      */
 
-     videoBuffered() {
 
-       // no media source/source buffer or it isn't in the media sources
 
-       // source buffer list
 
-       if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) {
 
-         return createTimeRanges();
 
-       }
 
-       return this.videoBuffer.buffered ? this.videoBuffer.buffered : createTimeRanges();
 
-     }
 
-     /**
 
-      * Get a combined video/audio buffer's buffered timerange.
 
-      *
 
-      * @return {TimeRange}
 
-      *         the combined time range
 
-      */
 
-     buffered() {
 
-       const video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null;
 
-       const audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null;
 
-       if (audio && !video) {
 
-         return this.audioBuffered();
 
-       }
 
-       if (video && !audio) {
 
-         return this.videoBuffered();
 
-       }
 
-       return bufferIntersection(this.audioBuffered(), this.videoBuffered());
 
-     }
 
-     /**
 
-      * Add a callback to the queue that will set duration on the mediaSource.
 
-      *
 
-      * @param {number} duration
 
-      *        The duration to set
 
-      *
 
-      * @param {Function} [doneFn]
 
-      *        function to run after duration has been set.
 
-      */
 
-     setDuration(duration, doneFn = noop) {
 
-       // In order to set the duration on the media source, it's necessary to wait for all
 
-       // source buffers to no longer be updating. "If the updating attribute equals true on
 
-       // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
 
-       // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
 
-       pushQueue({
 
-         type: 'mediaSource',
 
-         sourceUpdater: this,
 
-         action: actions.duration(duration),
 
-         name: 'duration',
 
-         doneFn
 
-       });
 
-     }
 
-     /**
 
-      * Add a mediaSource endOfStream call to the queue
 
-      *
 
-      * @param {Error} [error]
 
-      *        Call endOfStream with an error
 
-      *
 
-      * @param {Function} [doneFn]
 
-      *        A function that should be called when the
 
-      *        endOfStream call has finished.
 
-      */
 
-     endOfStream(error = null, doneFn = noop) {
 
-       if (typeof error !== 'string') {
 
-         error = undefined;
 
-       } // In order to set the duration on the media source, it's necessary to wait for all
 
-       // source buffers to no longer be updating. "If the updating attribute equals true on
 
-       // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
 
-       // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
 
-       pushQueue({
 
-         type: 'mediaSource',
 
-         sourceUpdater: this,
 
-         action: actions.endOfStream(error),
 
-         name: 'endOfStream',
 
-         doneFn
 
-       });
 
-     }
 
-     /**
 
-      * Queue an update to remove a time range from the buffer.
 
-      *
 
-      * @param {number} start where to start the removal
 
-      * @param {number} end where to end the removal
 
-      * @param {Function} [done=noop] optional callback to be executed when the remove
 
-      * operation is complete
 
-      * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
 
-      */
 
-     removeAudio(start, end, done = noop) {
 
-       if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) {
 
-         done();
 
-         return;
 
-       }
 
-       pushQueue({
 
-         type: 'audio',
 
-         sourceUpdater: this,
 
-         action: actions.remove(start, end),
 
-         doneFn: done,
 
-         name: 'remove'
 
-       });
 
-     }
 
-     /**
 
-      * Queue an update to remove a time range from the buffer.
 
-      *
 
-      * @param {number} start where to start the removal
 
-      * @param {number} end where to end the removal
 
-      * @param {Function} [done=noop] optional callback to be executed when the remove
 
-      * operation is complete
 
-      * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
 
-      */
 
-     removeVideo(start, end, done = noop) {
 
-       if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) {
 
-         done();
 
-         return;
 
-       }
 
-       pushQueue({
 
-         type: 'video',
 
-         sourceUpdater: this,
 
-         action: actions.remove(start, end),
 
-         doneFn: done,
 
-         name: 'remove'
 
-       });
 
-     }
 
-     /**
 
-      * Whether the underlying sourceBuffer is updating or not
 
-      *
 
-      * @return {boolean} the updating status of the SourceBuffer
 
-      */
 
-     updating() {
 
-       // the audio/video source buffer is updating
 
-       if (updating('audio', this) || updating('video', this)) {
 
-         return true;
 
-       }
 
-       return false;
 
-     }
 
-     /**
 
-      * Set/get the timestampoffset on the audio SourceBuffer
 
-      *
 
-      * @return {number} the timestamp offset
 
-      */
 
-     audioTimestampOffset(offset) {
 
-       if (typeof offset !== 'undefined' && this.audioBuffer &&
 
-       // no point in updating if it's the same
 
-       this.audioTimestampOffset_ !== offset) {
 
-         pushQueue({
 
-           type: 'audio',
 
-           sourceUpdater: this,
 
-           action: actions.timestampOffset(offset),
 
-           name: 'timestampOffset'
 
-         });
 
-         this.audioTimestampOffset_ = offset;
 
-       }
 
-       return this.audioTimestampOffset_;
 
-     }
 
-     /**
 
-      * Set/get the timestampoffset on the video SourceBuffer
 
-      *
 
-      * @return {number} the timestamp offset
 
-      */
 
-     videoTimestampOffset(offset) {
 
-       if (typeof offset !== 'undefined' && this.videoBuffer &&
 
-       // no point in updating if it's the same
 
-       this.videoTimestampOffset !== offset) {
 
-         pushQueue({
 
-           type: 'video',
 
-           sourceUpdater: this,
 
-           action: actions.timestampOffset(offset),
 
-           name: 'timestampOffset'
 
-         });
 
-         this.videoTimestampOffset_ = offset;
 
-       }
 
-       return this.videoTimestampOffset_;
 
-     }
 
-     /**
 
-      * Add a function to the queue that will be called
 
-      * when it is its turn to run in the audio queue.
 
-      *
 
-      * @param {Function} callback
 
-      *        The callback to queue.
 
-      */
 
-     audioQueueCallback(callback) {
 
-       if (!this.audioBuffer) {
 
-         return;
 
-       }
 
-       pushQueue({
 
-         type: 'audio',
 
-         sourceUpdater: this,
 
-         action: actions.callback(callback),
 
-         name: 'callback'
 
-       });
 
-     }
 
-     /**
 
-      * Add a function to the queue that will be called
 
-      * when it is its turn to run in the video queue.
 
-      *
 
-      * @param {Function} callback
 
-      *        The callback to queue.
 
-      */
 
-     videoQueueCallback(callback) {
 
-       if (!this.videoBuffer) {
 
-         return;
 
-       }
 
-       pushQueue({
 
-         type: 'video',
 
-         sourceUpdater: this,
 
-         action: actions.callback(callback),
 
-         name: 'callback'
 
-       });
 
-     }
 
-     /**
 
-      * dispose of the source updater and the underlying sourceBuffer
 
-      */
 
-     dispose() {
 
-       this.trigger('dispose');
 
-       bufferTypes.forEach(type => {
 
-         this.abort(type);
 
-         if (this.canRemoveSourceBuffer()) {
 
-           this.removeSourceBuffer(type);
 
-         } else {
 
-           this[`${type}QueueCallback`](() => cleanupBuffer(type, this));
 
-         }
 
-       });
 
-       this.videoAppendQueued_ = false;
 
-       this.delayedAudioAppendQueue_.length = 0;
 
-       if (this.sourceopenListener_) {
 
-         this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_);
 
-       }
 
-       this.off();
 
-     }
 
-   }
 
-   const uint8ToUtf8 = uintArray => decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
 
-   /**
 
-    * @file vtt-segment-loader.js
 
-    */
 
-   const VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(char => char.charCodeAt(0)));
 
-   class NoVttJsError extends Error {
 
-     constructor() {
 
-       super('Trying to parse received VTT cues, but there is no WebVTT. Make sure vtt.js is loaded.');
 
-     }
 
-   }
 
-   /**
 
-    * An object that manages segment loading and appending.
 
-    *
 
-    * @class VTTSegmentLoader
 
-    * @param {Object} options required and optional options
 
-    * @extends videojs.EventTarget
 
-    */
 
-   class VTTSegmentLoader extends SegmentLoader {
 
-     constructor(settings, options = {}) {
 
-       super(settings, options); // SegmentLoader requires a MediaSource be specified or it will throw an error;
 
-       // however, VTTSegmentLoader has no need of a media source, so delete the reference
 
-       this.mediaSource_ = null;
 
-       this.subtitlesTrack_ = null;
 
-       this.loaderType_ = 'subtitle';
 
-       this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks;
 
-       this.loadVttJs = settings.loadVttJs; // The VTT segment will have its own time mappings. Saving VTT segment timing info in
 
-       // the sync controller leads to improper behavior.
 
-       this.shouldSaveSegmentTimingInfo_ = false;
 
-     }
 
-     createTransmuxer_() {
 
-       // don't need to transmux any subtitles
 
-       return null;
 
-     }
 
-     /**
 
-      * Indicates which time ranges are buffered
 
-      *
 
-      * @return {TimeRange}
 
-      *         TimeRange object representing the current buffered ranges
 
-      */
 
-     buffered_() {
 
-       if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) {
 
-         return createTimeRanges();
 
-       }
 
-       const cues = this.subtitlesTrack_.cues;
 
-       const start = cues[0].startTime;
 
-       const end = cues[cues.length - 1].startTime;
 
-       return createTimeRanges([[start, end]]);
 
-     }
 
-     /**
 
-      * Gets and sets init segment for the provided map
 
-      *
 
-      * @param {Object} map
 
-      *        The map object representing the init segment to get or set
 
-      * @param {boolean=} set
 
-      *        If true, the init segment for the provided map should be saved
 
-      * @return {Object}
 
-      *         map object for desired init segment
 
-      */
 
-     initSegmentForMap(map, set = false) {
 
-       if (!map) {
 
-         return null;
 
-       }
 
-       const id = initSegmentId(map);
 
-       let storedMap = this.initSegments_[id];
 
-       if (set && !storedMap && map.bytes) {
 
-         // append WebVTT line terminators to the media initialization segment if it exists
 
-         // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
 
-         // requires two or more WebVTT line terminators between the WebVTT header and the
 
-         // rest of the file
 
-         const combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
 
-         const combinedSegment = new Uint8Array(combinedByteLength);
 
-         combinedSegment.set(map.bytes);
 
-         combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
 
-         this.initSegments_[id] = storedMap = {
 
-           resolvedUri: map.resolvedUri,
 
-           byterange: map.byterange,
 
-           bytes: combinedSegment
 
-         };
 
-       }
 
-       return storedMap || map;
 
-     }
 
-     /**
 
-      * Returns true if all configuration required for loading is present, otherwise false.
 
-      *
 
-      * @return {boolean} True if the all configuration is ready for loading
 
-      * @private
 
-      */
 
-     couldBeginLoading_() {
 
-       return this.playlist_ && this.subtitlesTrack_ && !this.paused();
 
-     }
 
-     /**
 
-      * Once all the starting parameters have been specified, begin
 
-      * operation. This method should only be invoked from the INIT
 
-      * state.
 
-      *
 
-      * @private
 
-      */
 
-     init_() {
 
-       this.state = 'READY';
 
-       this.resetEverything();
 
-       return this.monitorBuffer_();
 
-     }
 
-     /**
 
-      * Set a subtitle track on the segment loader to add subtitles to
 
-      *
 
-      * @param {TextTrack=} track
 
-      *        The text track to add loaded subtitles to
 
-      * @return {TextTrack}
 
-      *        Returns the subtitles track
 
-      */
 
-     track(track) {
 
-       if (typeof track === 'undefined') {
 
-         return this.subtitlesTrack_;
 
-       }
 
-       this.subtitlesTrack_ = track; // if we were unpaused but waiting for a sourceUpdater, start
 
-       // buffering now
 
-       if (this.state === 'INIT' && this.couldBeginLoading_()) {
 
-         this.init_();
 
-       }
 
-       return this.subtitlesTrack_;
 
-     }
 
-     /**
 
-      * Remove any data in the source buffer between start and end times
 
-      *
 
-      * @param {number} start - the start time of the region to remove from the buffer
 
-      * @param {number} end - the end time of the region to remove from the buffer
 
-      */
 
-     remove(start, end) {
 
-       removeCuesFromTrack(start, end, this.subtitlesTrack_);
 
-     }
 
-     /**
 
-      * fill the buffer with segements unless the sourceBuffers are
 
-      * currently updating
 
-      *
 
-      * Note: this function should only ever be called by monitorBuffer_
 
-      * and never directly
 
-      *
 
-      * @private
 
-      */
 
-     fillBuffer_() {
 
-       // see if we need to begin loading immediately
 
-       const segmentInfo = this.chooseNextRequest_();
 
-       if (!segmentInfo) {
 
-         return;
 
-       }
 
-       if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
 
-         // We don't have the timestamp offset that we need to sync subtitles.
 
-         // Rerun on a timestamp offset or user interaction.
 
-         const checkTimestampOffset = () => {
 
-           this.state = 'READY';
 
-           if (!this.paused()) {
 
-             // if not paused, queue a buffer check as soon as possible
 
-             this.monitorBuffer_();
 
-           }
 
-         };
 
-         this.syncController_.one('timestampoffset', checkTimestampOffset);
 
-         this.state = 'WAITING_ON_TIMELINE';
 
-         return;
 
-       }
 
-       this.loadSegment_(segmentInfo);
 
-     } // never set a timestamp offset for vtt segments.
 
-     timestampOffsetForSegment_() {
 
-       return null;
 
-     }
 
-     chooseNextRequest_() {
 
-       return this.skipEmptySegments_(super.chooseNextRequest_());
 
-     }
 
-     /**
 
-      * Prevents the segment loader from requesting segments we know contain no subtitles
 
-      * by walking forward until we find the next segment that we don't know whether it is
 
-      * empty or not.
 
-      *
 
-      * @param {Object} segmentInfo
 
-      *        a segment info object that describes the current segment
 
-      * @return {Object}
 
-      *         a segment info object that describes the current segment
 
-      */
 
-     skipEmptySegments_(segmentInfo) {
 
-       while (segmentInfo && segmentInfo.segment.empty) {
 
-         // stop at the last possible segmentInfo
 
-         if (segmentInfo.mediaIndex + 1 >= segmentInfo.playlist.segments.length) {
 
-           segmentInfo = null;
 
-           break;
 
-         }
 
-         segmentInfo = this.generateSegmentInfo_({
 
-           playlist: segmentInfo.playlist,
 
-           mediaIndex: segmentInfo.mediaIndex + 1,
 
-           startOfSegment: segmentInfo.startOfSegment + segmentInfo.duration,
 
-           isSyncRequest: segmentInfo.isSyncRequest
 
-         });
 
-       }
 
-       return segmentInfo;
 
-     }
 
-     stopForError(error) {
 
-       this.error(error);
 
-       this.state = 'READY';
 
-       this.pause();
 
-       this.trigger('error');
 
-     }
 
-     /**
 
-      * append a decrypted segement to the SourceBuffer through a SourceUpdater
 
-      *
 
-      * @private
 
-      */
 
-     segmentRequestFinished_(error, simpleSegment, result) {
 
-       if (!this.subtitlesTrack_) {
 
-         this.state = 'READY';
 
-         return;
 
-       }
 
-       this.saveTransferStats_(simpleSegment.stats); // the request was aborted
 
-       if (!this.pendingSegment_) {
 
-         this.state = 'READY';
 
-         this.mediaRequestsAborted += 1;
 
-         return;
 
-       }
 
-       if (error) {
 
-         if (error.code === REQUEST_ERRORS.TIMEOUT) {
 
-           this.handleTimeout_();
 
-         }
 
-         if (error.code === REQUEST_ERRORS.ABORTED) {
 
-           this.mediaRequestsAborted += 1;
 
-         } else {
 
-           this.mediaRequestsErrored += 1;
 
-         }
 
-         this.stopForError(error);
 
-         return;
 
-       }
 
-       const segmentInfo = this.pendingSegment_; // although the VTT segment loader bandwidth isn't really used, it's good to
 
-       // maintain functionality between segment loaders
 
-       this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats); // if this request included a segment key, save that data in the cache
 
-       if (simpleSegment.key) {
 
-         this.segmentKey(simpleSegment.key, true);
 
-       }
 
-       this.state = 'APPENDING'; // used for tests
 
-       this.trigger('appending');
 
-       const segment = segmentInfo.segment;
 
-       if (segment.map) {
 
-         segment.map.bytes = simpleSegment.map.bytes;
 
-       }
 
-       segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, load it and wait till it finished loading
 
-       if (typeof window.WebVTT !== 'function' && typeof this.loadVttJs === 'function') {
 
-         this.state = 'WAITING_ON_VTTJS'; // should be fine to call multiple times
 
-         // script will be loaded once but multiple listeners will be added to the queue, which is expected.
 
-         this.loadVttJs().then(() => this.segmentRequestFinished_(error, simpleSegment, result), () => this.stopForError({
 
-           message: 'Error loading vtt.js'
 
-         }));
 
-         return;
 
-       }
 
-       segment.requested = true;
 
-       try {
 
-         this.parseVTTCues_(segmentInfo);
 
-       } catch (e) {
 
-         this.stopForError({
 
-           message: e.message
 
-         });
 
-         return;
 
-       }
 
-       this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
 
-       if (segmentInfo.cues.length) {
 
-         segmentInfo.timingInfo = {
 
-           start: segmentInfo.cues[0].startTime,
 
-           end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime
 
-         };
 
-       } else {
 
-         segmentInfo.timingInfo = {
 
-           start: segmentInfo.startOfSegment,
 
-           end: segmentInfo.startOfSegment + segmentInfo.duration
 
-         };
 
-       }
 
-       if (segmentInfo.isSyncRequest) {
 
-         this.trigger('syncinfoupdate');
 
-         this.pendingSegment_ = null;
 
-         this.state = 'READY';
 
-         return;
 
-       }
 
-       segmentInfo.byteLength = segmentInfo.bytes.byteLength;
 
-       this.mediaSecondsLoaded += segment.duration; // Create VTTCue instances for each cue in the new segment and add them to
 
-       // the subtitle track
 
-       segmentInfo.cues.forEach(cue => {
 
-         this.subtitlesTrack_.addCue(this.featuresNativeTextTracks_ ? new window.VTTCue(cue.startTime, cue.endTime, cue.text) : cue);
 
-       }); // Remove any duplicate cues from the subtitle track. The WebVTT spec allows
 
-       // cues to have identical time-intervals, but if the text is also identical
 
-       // we can safely assume it is a duplicate that can be removed (ex. when a cue
 
-       // "overlaps" VTT segments)
 
-       removeDuplicateCuesFromTrack(this.subtitlesTrack_);
 
-       this.handleAppendsDone_();
 
-     }
 
-     handleData_() {// noop as we shouldn't be getting video/audio data captions
 
-       // that we do not support here.
 
-     }
 
-     updateTimingInfoEnd_() {// noop
 
-     }
 
-     /**
 
-      * Uses the WebVTT parser to parse the segment response
 
-      *
 
-      * @throws NoVttJsError
 
-      *
 
-      * @param {Object} segmentInfo
 
-      *        a segment info object that describes the current segment
 
-      * @private
 
-      */
 
-     parseVTTCues_(segmentInfo) {
 
-       let decoder;
 
-       let decodeBytesToString = false;
 
-       if (typeof window.WebVTT !== 'function') {
 
-         // caller is responsible for exception handling.
 
-         throw new NoVttJsError();
 
-       }
 
-       if (typeof window.TextDecoder === 'function') {
 
-         decoder = new window.TextDecoder('utf8');
 
-       } else {
 
-         decoder = window.WebVTT.StringDecoder();
 
-         decodeBytesToString = true;
 
-       }
 
-       const parser = new window.WebVTT.Parser(window, window.vttjs, decoder);
 
-       segmentInfo.cues = [];
 
-       segmentInfo.timestampmap = {
 
-         MPEGTS: 0,
 
-         LOCAL: 0
 
-       };
 
-       parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
 
-       parser.ontimestampmap = map => {
 
-         segmentInfo.timestampmap = map;
 
-       };
 
-       parser.onparsingerror = error => {
 
-         videojs.log.warn('Error encountered when parsing cues: ' + error.message);
 
-       };
 
-       if (segmentInfo.segment.map) {
 
-         let mapData = segmentInfo.segment.map.bytes;
 
-         if (decodeBytesToString) {
 
-           mapData = uint8ToUtf8(mapData);
 
-         }
 
-         parser.parse(mapData);
 
-       }
 
-       let segmentData = segmentInfo.bytes;
 
-       if (decodeBytesToString) {
 
-         segmentData = uint8ToUtf8(segmentData);
 
-       }
 
-       parser.parse(segmentData);
 
-       parser.flush();
 
-     }
 
-     /**
 
-      * Updates the start and end times of any cues parsed by the WebVTT parser using
 
-      * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
 
-      * from the SyncController
 
-      *
 
-      * @param {Object} segmentInfo
 
-      *        a segment info object that describes the current segment
 
-      * @param {Object} mappingObj
 
-      *        object containing a mapping from TS to media time
 
-      * @param {Object} playlist
 
-      *        the playlist object containing the segment
 
-      * @private
 
-      */
 
-     updateTimeMapping_(segmentInfo, mappingObj, playlist) {
 
-       const segment = segmentInfo.segment;
 
-       if (!mappingObj) {
 
-         // If the sync controller does not have a mapping of TS to Media Time for the
 
-         // timeline, then we don't have enough information to update the cue
 
-         // start/end times
 
-         return;
 
-       }
 
-       if (!segmentInfo.cues.length) {
 
-         // If there are no cues, we also do not have enough information to figure out
 
-         // segment timing. Mark that the segment contains no cues so we don't re-request
 
-         // an empty segment.
 
-         segment.empty = true;
 
-         return;
 
-       }
 
-       const timestampmap = segmentInfo.timestampmap;
 
-       const diff = timestampmap.MPEGTS / clock_1 - timestampmap.LOCAL + mappingObj.mapping;
 
-       segmentInfo.cues.forEach(cue => {
 
-         // First convert cue time to TS time using the timestamp-map provided within the vtt
 
-         cue.startTime += diff;
 
-         cue.endTime += diff;
 
-       });
 
-       if (!playlist.syncInfo) {
 
-         const firstStart = segmentInfo.cues[0].startTime;
 
-         const lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
 
-         playlist.syncInfo = {
 
-           mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
 
-           time: Math.min(firstStart, lastStart - segment.duration)
 
-         };
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * @file ad-cue-tags.js
 
-    */
 
-   /**
 
-    * Searches for an ad cue that overlaps with the given mediaTime
 
-    *
 
-    * @param {Object} track
 
-    *        the track to find the cue for
 
-    *
 
-    * @param {number} mediaTime
 
-    *        the time to find the cue at
 
-    *
 
-    * @return {Object|null}
 
-    *         the found cue or null
 
-    */
 
-   const findAdCue = function (track, mediaTime) {
 
-     const cues = track.cues;
 
-     for (let i = 0; i < cues.length; i++) {
 
-       const cue = cues[i];
 
-       if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
 
-         return cue;
 
-       }
 
-     }
 
-     return null;
 
-   };
 
-   const updateAdCues = function (media, track, offset = 0) {
 
-     if (!media.segments) {
 
-       return;
 
-     }
 
-     let mediaTime = offset;
 
-     let cue;
 
-     for (let i = 0; i < media.segments.length; i++) {
 
-       const segment = media.segments[i];
 
-       if (!cue) {
 
-         // Since the cues will span for at least the segment duration, adding a fudge
 
-         // factor of half segment duration will prevent duplicate cues from being
 
-         // created when timing info is not exact (e.g. cue start time initialized
 
-         // at 10.006677, but next call mediaTime is 10.003332 )
 
-         cue = findAdCue(track, mediaTime + segment.duration / 2);
 
-       }
 
-       if (cue) {
 
-         if ('cueIn' in segment) {
 
-           // Found a CUE-IN so end the cue
 
-           cue.endTime = mediaTime;
 
-           cue.adEndTime = mediaTime;
 
-           mediaTime += segment.duration;
 
-           cue = null;
 
-           continue;
 
-         }
 
-         if (mediaTime < cue.endTime) {
 
-           // Already processed this mediaTime for this cue
 
-           mediaTime += segment.duration;
 
-           continue;
 
-         } // otherwise extend cue until a CUE-IN is found
 
-         cue.endTime += segment.duration;
 
-       } else {
 
-         if ('cueOut' in segment) {
 
-           cue = new window.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
 
-           cue.adStartTime = mediaTime; // Assumes tag format to be
 
-           // #EXT-X-CUE-OUT:30
 
-           cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
 
-           track.addCue(cue);
 
-         }
 
-         if ('cueOutCont' in segment) {
 
-           // Entered into the middle of an ad cue
 
-           // Assumes tag formate to be
 
-           // #EXT-X-CUE-OUT-CONT:10/30
 
-           const [adOffset, adTotal] = segment.cueOutCont.split('/').map(parseFloat);
 
-           cue = new window.VTTCue(mediaTime, mediaTime + segment.duration, '');
 
-           cue.adStartTime = mediaTime - adOffset;
 
-           cue.adEndTime = cue.adStartTime + adTotal;
 
-           track.addCue(cue);
 
-         }
 
-       }
 
-       mediaTime += segment.duration;
 
-     }
 
-   };
 
-   /**
 
-    * @file sync-controller.js
 
-    */
 
-   // synchronize expired playlist segments.
 
-   // the max media sequence diff is 48 hours of live stream
 
-   // content with two second segments. Anything larger than that
 
-   // will likely be invalid.
 
-   const MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC = 86400;
 
-   const syncPointStrategies = [
 
-   // Stategy "VOD": Handle the VOD-case where the sync-point is *always*
 
-   //                the equivalence display-time 0 === segment-index 0
 
-   {
 
-     name: 'VOD',
 
-     run: (syncController, playlist, duration, currentTimeline, currentTime) => {
 
-       if (duration !== Infinity) {
 
-         const syncPoint = {
 
-           time: 0,
 
-           segmentIndex: 0,
 
-           partIndex: null
 
-         };
 
-         return syncPoint;
 
-       }
 
-       return null;
 
-     }
 
-   },
 
-   // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
 
-   {
 
-     name: 'ProgramDateTime',
 
-     run: (syncController, playlist, duration, currentTimeline, currentTime) => {
 
-       if (!Object.keys(syncController.timelineToDatetimeMappings).length) {
 
-         return null;
 
-       }
 
-       let syncPoint = null;
 
-       let lastDistance = null;
 
-       const partsAndSegments = getPartsAndSegments(playlist);
 
-       currentTime = currentTime || 0;
 
-       for (let i = 0; i < partsAndSegments.length; i++) {
 
-         // start from the end and loop backwards for live
 
-         // or start from the front and loop forwards for non-live
 
-         const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
 
-         const partAndSegment = partsAndSegments[index];
 
-         const segment = partAndSegment.segment;
 
-         const datetimeMapping = syncController.timelineToDatetimeMappings[segment.timeline];
 
-         if (!datetimeMapping || !segment.dateTimeObject) {
 
-           continue;
 
-         }
 
-         const segmentTime = segment.dateTimeObject.getTime() / 1000;
 
-         let start = segmentTime + datetimeMapping; // take part duration into account.
 
-         if (segment.parts && typeof partAndSegment.partIndex === 'number') {
 
-           for (let z = 0; z < partAndSegment.partIndex; z++) {
 
-             start += segment.parts[z].duration;
 
-           }
 
-         }
 
-         const distance = Math.abs(currentTime - start); // Once the distance begins to increase, or if distance is 0, we have passed
 
-         // currentTime and can stop looking for better candidates
 
-         if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
 
-           break;
 
-         }
 
-         lastDistance = distance;
 
-         syncPoint = {
 
-           time: start,
 
-           segmentIndex: partAndSegment.segmentIndex,
 
-           partIndex: partAndSegment.partIndex
 
-         };
 
-       }
 
-       return syncPoint;
 
-     }
 
-   },
 
-   // Stategy "Segment": We have a known time mapping for a timeline and a
 
-   //                    segment in the current timeline with timing data
 
-   {
 
-     name: 'Segment',
 
-     run: (syncController, playlist, duration, currentTimeline, currentTime) => {
 
-       let syncPoint = null;
 
-       let lastDistance = null;
 
-       currentTime = currentTime || 0;
 
-       const partsAndSegments = getPartsAndSegments(playlist);
 
-       for (let i = 0; i < partsAndSegments.length; i++) {
 
-         // start from the end and loop backwards for live
 
-         // or start from the front and loop forwards for non-live
 
-         const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
 
-         const partAndSegment = partsAndSegments[index];
 
-         const segment = partAndSegment.segment;
 
-         const start = partAndSegment.part && partAndSegment.part.start || segment && segment.start;
 
-         if (segment.timeline === currentTimeline && typeof start !== 'undefined') {
 
-           const distance = Math.abs(currentTime - start); // Once the distance begins to increase, we have passed
 
-           // currentTime and can stop looking for better candidates
 
-           if (lastDistance !== null && lastDistance < distance) {
 
-             break;
 
-           }
 
-           if (!syncPoint || lastDistance === null || lastDistance >= distance) {
 
-             lastDistance = distance;
 
-             syncPoint = {
 
-               time: start,
 
-               segmentIndex: partAndSegment.segmentIndex,
 
-               partIndex: partAndSegment.partIndex
 
-             };
 
-           }
 
-         }
 
-       }
 
-       return syncPoint;
 
-     }
 
-   },
 
-   // Stategy "Discontinuity": We have a discontinuity with a known
 
-   //                          display-time
 
-   {
 
-     name: 'Discontinuity',
 
-     run: (syncController, playlist, duration, currentTimeline, currentTime) => {
 
-       let syncPoint = null;
 
-       currentTime = currentTime || 0;
 
-       if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
 
-         let lastDistance = null;
 
-         for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
 
-           const segmentIndex = playlist.discontinuityStarts[i];
 
-           const discontinuity = playlist.discontinuitySequence + i + 1;
 
-           const discontinuitySync = syncController.discontinuities[discontinuity];
 
-           if (discontinuitySync) {
 
-             const distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
 
-             // currentTime and can stop looking for better candidates
 
-             if (lastDistance !== null && lastDistance < distance) {
 
-               break;
 
-             }
 
-             if (!syncPoint || lastDistance === null || lastDistance >= distance) {
 
-               lastDistance = distance;
 
-               syncPoint = {
 
-                 time: discontinuitySync.time,
 
-                 segmentIndex,
 
-                 partIndex: null
 
-               };
 
-             }
 
-           }
 
-         }
 
-       }
 
-       return syncPoint;
 
-     }
 
-   },
 
-   // Stategy "Playlist": We have a playlist with a known mapping of
 
-   //                     segment index to display time
 
-   {
 
-     name: 'Playlist',
 
-     run: (syncController, playlist, duration, currentTimeline, currentTime) => {
 
-       if (playlist.syncInfo) {
 
-         const syncPoint = {
 
-           time: playlist.syncInfo.time,
 
-           segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence,
 
-           partIndex: null
 
-         };
 
-         return syncPoint;
 
-       }
 
-       return null;
 
-     }
 
-   }];
 
-   class SyncController extends videojs.EventTarget {
 
-     constructor(options = {}) {
 
-       super(); // ...for synching across variants
 
-       this.timelines = [];
 
-       this.discontinuities = [];
 
-       this.timelineToDatetimeMappings = {};
 
-       this.logger_ = logger('SyncController');
 
-     }
 
-     /**
 
-      * Find a sync-point for the playlist specified
 
-      *
 
-      * A sync-point is defined as a known mapping from display-time to
 
-      * a segment-index in the current playlist.
 
-      *
 
-      * @param {Playlist} playlist
 
-      *        The playlist that needs a sync-point
 
-      * @param {number} duration
 
-      *        Duration of the MediaSource (Infinite if playing a live source)
 
-      * @param {number} currentTimeline
 
-      *        The last timeline from which a segment was loaded
 
-      * @return {Object}
 
-      *          A sync-point object
 
-      */
 
-     getSyncPoint(playlist, duration, currentTimeline, currentTime) {
 
-       const syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime);
 
-       if (!syncPoints.length) {
 
-         // Signal that we need to attempt to get a sync-point manually
 
-         // by fetching a segment in the playlist and constructing
 
-         // a sync-point from that information
 
-         return null;
 
-       } // Now find the sync-point that is closest to the currentTime because
 
-       // that should result in the most accurate guess about which segment
 
-       // to fetch
 
-       return this.selectSyncPoint_(syncPoints, {
 
-         key: 'time',
 
-         value: currentTime
 
-       });
 
-     }
 
-     /**
 
-      * Calculate the amount of time that has expired off the playlist during playback
 
-      *
 
-      * @param {Playlist} playlist
 
-      *        Playlist object to calculate expired from
 
-      * @param {number} duration
 
-      *        Duration of the MediaSource (Infinity if playling a live source)
 
-      * @return {number|null}
 
-      *          The amount of time that has expired off the playlist during playback. Null
 
-      *          if no sync-points for the playlist can be found.
 
-      */
 
-     getExpiredTime(playlist, duration) {
 
-       if (!playlist || !playlist.segments) {
 
-         return null;
 
-       }
 
-       const syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
 
-       if (!syncPoints.length) {
 
-         return null;
 
-       }
 
-       const syncPoint = this.selectSyncPoint_(syncPoints, {
 
-         key: 'segmentIndex',
 
-         value: 0
 
-       }); // If the sync-point is beyond the start of the playlist, we want to subtract the
 
-       // duration from index 0 to syncPoint.segmentIndex instead of adding.
 
-       if (syncPoint.segmentIndex > 0) {
 
-         syncPoint.time *= -1;
 
-       }
 
-       return Math.abs(syncPoint.time + sumDurations({
 
-         defaultDuration: playlist.targetDuration,
 
-         durationList: playlist.segments,
 
-         startIndex: syncPoint.segmentIndex,
 
-         endIndex: 0
 
-       }));
 
-     }
 
-     /**
 
-      * Runs each sync-point strategy and returns a list of sync-points returned by the
 
-      * strategies
 
-      *
 
-      * @private
 
-      * @param {Playlist} playlist
 
-      *        The playlist that needs a sync-point
 
-      * @param {number} duration
 
-      *        Duration of the MediaSource (Infinity if playing a live source)
 
-      * @param {number} currentTimeline
 
-      *        The last timeline from which a segment was loaded
 
-      * @return {Array}
 
-      *          A list of sync-point objects
 
-      */
 
-     runStrategies_(playlist, duration, currentTimeline, currentTime) {
 
-       const syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
 
-       for (let i = 0; i < syncPointStrategies.length; i++) {
 
-         const strategy = syncPointStrategies[i];
 
-         const syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime);
 
-         if (syncPoint) {
 
-           syncPoint.strategy = strategy.name;
 
-           syncPoints.push({
 
-             strategy: strategy.name,
 
-             syncPoint
 
-           });
 
-         }
 
-       }
 
-       return syncPoints;
 
-     }
 
-     /**
 
-      * Selects the sync-point nearest the specified target
 
-      *
 
-      * @private
 
-      * @param {Array} syncPoints
 
-      *        List of sync-points to select from
 
-      * @param {Object} target
 
-      *        Object specifying the property and value we are targeting
 
-      * @param {string} target.key
 
-      *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
 
-      * @param {number} target.value
 
-      *        The value to target for the specified key.
 
-      * @return {Object}
 
-      *          The sync-point nearest the target
 
-      */
 
-     selectSyncPoint_(syncPoints, target) {
 
-       let bestSyncPoint = syncPoints[0].syncPoint;
 
-       let bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
 
-       let bestStrategy = syncPoints[0].strategy;
 
-       for (let i = 1; i < syncPoints.length; i++) {
 
-         const newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
 
-         if (newDistance < bestDistance) {
 
-           bestDistance = newDistance;
 
-           bestSyncPoint = syncPoints[i].syncPoint;
 
-           bestStrategy = syncPoints[i].strategy;
 
-         }
 
-       }
 
-       this.logger_(`syncPoint for [${target.key}: ${target.value}] chosen with strategy` + ` [${bestStrategy}]: [time:${bestSyncPoint.time},` + ` segmentIndex:${bestSyncPoint.segmentIndex}` + (typeof bestSyncPoint.partIndex === 'number' ? `,partIndex:${bestSyncPoint.partIndex}` : '') + ']');
 
-       return bestSyncPoint;
 
-     }
 
-     /**
 
-      * Save any meta-data present on the segments when segments leave
 
-      * the live window to the playlist to allow for synchronization at the
 
-      * playlist level later.
 
-      *
 
-      * @param {Playlist} oldPlaylist - The previous active playlist
 
-      * @param {Playlist} newPlaylist - The updated and most current playlist
 
-      */
 
-     saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
 
-       const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // Ignore large media sequence gaps
 
-       if (mediaSequenceDiff > MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC) {
 
-         videojs.log.warn(`Not saving expired segment info. Media sequence gap ${mediaSequenceDiff} is too large.`);
 
-         return;
 
-       } // When a segment expires from the playlist and it has a start time
 
-       // save that information as a possible sync-point reference in future
 
-       for (let i = mediaSequenceDiff - 1; i >= 0; i--) {
 
-         const lastRemovedSegment = oldPlaylist.segments[i];
 
-         if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
 
-           newPlaylist.syncInfo = {
 
-             mediaSequence: oldPlaylist.mediaSequence + i,
 
-             time: lastRemovedSegment.start
 
-           };
 
-           this.logger_(`playlist refresh sync: [time:${newPlaylist.syncInfo.time},` + ` mediaSequence: ${newPlaylist.syncInfo.mediaSequence}]`);
 
-           this.trigger('syncinfoupdate');
 
-           break;
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Save the mapping from playlist's ProgramDateTime to display. This should only happen
 
-      * before segments start to load.
 
-      *
 
-      * @param {Playlist} playlist - The currently active playlist
 
-      */
 
-     setDateTimeMappingForStart(playlist) {
 
-       // It's possible for the playlist to be updated before playback starts, meaning time
 
-       // zero is not yet set. If, during these playlist refreshes, a discontinuity is
 
-       // crossed, then the old time zero mapping (for the prior timeline) would be retained
 
-       // unless the mappings are cleared.
 
-       this.timelineToDatetimeMappings = {};
 
-       if (playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
 
-         const firstSegment = playlist.segments[0];
 
-         const playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000;
 
-         this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp;
 
-       }
 
-     }
 
-     /**
 
-      * Calculates and saves timeline mappings, playlist sync info, and segment timing values
 
-      * based on the latest timing information.
 
-      *
 
-      * @param {Object} options
 
-      *        Options object
 
-      * @param {SegmentInfo} options.segmentInfo
 
-      *        The current active request information
 
-      * @param {boolean} options.shouldSaveTimelineMapping
 
-      *        If there's a timeline change, determines if the timeline mapping should be
 
-      *        saved for timeline mapping and program date time mappings.
 
-      */
 
-     saveSegmentTimingInfo({
 
-       segmentInfo,
 
-       shouldSaveTimelineMapping
 
-     }) {
 
-       const didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(segmentInfo, segmentInfo.timingInfo, shouldSaveTimelineMapping);
 
-       const segment = segmentInfo.segment;
 
-       if (didCalculateSegmentTimeMapping) {
 
-         this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
 
-         // now with segment timing information
 
-         if (!segmentInfo.playlist.syncInfo) {
 
-           segmentInfo.playlist.syncInfo = {
 
-             mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
 
-             time: segment.start
 
-           };
 
-         }
 
-       }
 
-       const dateTime = segment.dateTimeObject;
 
-       if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) {
 
-         this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000);
 
-       }
 
-     }
 
-     timestampOffsetForTimeline(timeline) {
 
-       if (typeof this.timelines[timeline] === 'undefined') {
 
-         return null;
 
-       }
 
-       return this.timelines[timeline].time;
 
-     }
 
-     mappingForTimeline(timeline) {
 
-       if (typeof this.timelines[timeline] === 'undefined') {
 
-         return null;
 
-       }
 
-       return this.timelines[timeline].mapping;
 
-     }
 
-     /**
 
-      * Use the "media time" for a segment to generate a mapping to "display time" and
 
-      * save that display time to the segment.
 
-      *
 
-      * @private
 
-      * @param {SegmentInfo} segmentInfo
 
-      *        The current active request information
 
-      * @param {Object} timingInfo
 
-      *        The start and end time of the current segment in "media time"
 
-      * @param {boolean} shouldSaveTimelineMapping
 
-      *        If there's a timeline change, determines if the timeline mapping should be
 
-      *        saved in timelines.
 
-      * @return {boolean}
 
-      *          Returns false if segment time mapping could not be calculated
 
-      */
 
-     calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) {
 
-       // TODO: remove side effects
 
-       const segment = segmentInfo.segment;
 
-       const part = segmentInfo.part;
 
-       let mappingObj = this.timelines[segmentInfo.timeline];
 
-       let start;
 
-       let end;
 
-       if (typeof segmentInfo.timestampOffset === 'number') {
 
-         mappingObj = {
 
-           time: segmentInfo.startOfSegment,
 
-           mapping: segmentInfo.startOfSegment - timingInfo.start
 
-         };
 
-         if (shouldSaveTimelineMapping) {
 
-           this.timelines[segmentInfo.timeline] = mappingObj;
 
-           this.trigger('timestampoffset');
 
-           this.logger_(`time mapping for timeline ${segmentInfo.timeline}: ` + `[time: ${mappingObj.time}] [mapping: ${mappingObj.mapping}]`);
 
-         }
 
-         start = segmentInfo.startOfSegment;
 
-         end = timingInfo.end + mappingObj.mapping;
 
-       } else if (mappingObj) {
 
-         start = timingInfo.start + mappingObj.mapping;
 
-         end = timingInfo.end + mappingObj.mapping;
 
-       } else {
 
-         return false;
 
-       }
 
-       if (part) {
 
-         part.start = start;
 
-         part.end = end;
 
-       } // If we don't have a segment start yet or the start value we got
 
-       // is less than our current segment.start value, save a new start value.
 
-       // We have to do this because parts will have segment timing info saved
 
-       // multiple times and we want segment start to be the earliest part start
 
-       // value for that segment.
 
-       if (!segment.start || start < segment.start) {
 
-         segment.start = start;
 
-       }
 
-       segment.end = end;
 
-       return true;
 
-     }
 
-     /**
 
-      * Each time we have discontinuity in the playlist, attempt to calculate the location
 
-      * in display of the start of the discontinuity and save that. We also save an accuracy
 
-      * value so that we save values with the most accuracy (closest to 0.)
 
-      *
 
-      * @private
 
-      * @param {SegmentInfo} segmentInfo - The current active request information
 
-      */
 
-     saveDiscontinuitySyncInfo_(segmentInfo) {
 
-       const playlist = segmentInfo.playlist;
 
-       const segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
 
-       // the start of the range and it's accuracy is 0 (greater accuracy values
 
-       // mean more approximation)
 
-       if (segment.discontinuity) {
 
-         this.discontinuities[segment.timeline] = {
 
-           time: segment.start,
 
-           accuracy: 0
 
-         };
 
-       } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
 
-         // Search for future discontinuities that we can provide better timing
 
-         // information for and save that information for sync purposes
 
-         for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
 
-           const segmentIndex = playlist.discontinuityStarts[i];
 
-           const discontinuity = playlist.discontinuitySequence + i + 1;
 
-           const mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
 
-           const accuracy = Math.abs(mediaIndexDiff);
 
-           if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
 
-             let time;
 
-             if (mediaIndexDiff < 0) {
 
-               time = segment.start - sumDurations({
 
-                 defaultDuration: playlist.targetDuration,
 
-                 durationList: playlist.segments,
 
-                 startIndex: segmentInfo.mediaIndex,
 
-                 endIndex: segmentIndex
 
-               });
 
-             } else {
 
-               time = segment.end + sumDurations({
 
-                 defaultDuration: playlist.targetDuration,
 
-                 durationList: playlist.segments,
 
-                 startIndex: segmentInfo.mediaIndex + 1,
 
-                 endIndex: segmentIndex
 
-               });
 
-             }
 
-             this.discontinuities[discontinuity] = {
 
-               time,
 
-               accuracy
 
-             };
 
-           }
 
-         }
 
-       }
 
-     }
 
-     dispose() {
 
-       this.trigger('dispose');
 
-       this.off();
 
-     }
 
-   }
 
-   /**
 
-    * The TimelineChangeController acts as a source for segment loaders to listen for and
 
-    * keep track of latest and pending timeline changes. This is useful to ensure proper
 
-    * sync, as each loader may need to make a consideration for what timeline the other
 
-    * loader is on before making changes which could impact the other loader's media.
 
-    *
 
-    * @class TimelineChangeController
 
-    * @extends videojs.EventTarget
 
-    */
 
-   class TimelineChangeController extends videojs.EventTarget {
 
-     constructor() {
 
-       super();
 
-       this.pendingTimelineChanges_ = {};
 
-       this.lastTimelineChanges_ = {};
 
-     }
 
-     clearPendingTimelineChange(type) {
 
-       this.pendingTimelineChanges_[type] = null;
 
-       this.trigger('pendingtimelinechange');
 
-     }
 
-     pendingTimelineChange({
 
-       type,
 
-       from,
 
-       to
 
-     }) {
 
-       if (typeof from === 'number' && typeof to === 'number') {
 
-         this.pendingTimelineChanges_[type] = {
 
-           type,
 
-           from,
 
-           to
 
-         };
 
-         this.trigger('pendingtimelinechange');
 
-       }
 
-       return this.pendingTimelineChanges_[type];
 
-     }
 
-     lastTimelineChange({
 
-       type,
 
-       from,
 
-       to
 
-     }) {
 
-       if (typeof from === 'number' && typeof to === 'number') {
 
-         this.lastTimelineChanges_[type] = {
 
-           type,
 
-           from,
 
-           to
 
-         };
 
-         delete this.pendingTimelineChanges_[type];
 
-         this.trigger('timelinechange');
 
-       }
 
-       return this.lastTimelineChanges_[type];
 
-     }
 
-     dispose() {
 
-       this.trigger('dispose');
 
-       this.pendingTimelineChanges_ = {};
 
-       this.lastTimelineChanges_ = {};
 
-       this.off();
 
-     }
 
-   }
 
-   /* rollup-plugin-worker-factory start for worker!/Users/ddashkevich/projects/http-streaming/src/decrypter-worker.js */
 
-   const workerCode = transform(getWorkerString(function () {
 
-     /**
 
-      * @file stream.js
 
-      */
 
-     /**
 
-      * A lightweight readable stream implemention that handles event dispatching.
 
-      *
 
-      * @class Stream
 
-      */
 
-     var Stream = /*#__PURE__*/function () {
 
-       function Stream() {
 
-         this.listeners = {};
 
-       }
 
-       /**
 
-        * Add a listener for a specified event type.
 
-        *
 
-        * @param {string} type the event name
 
-        * @param {Function} listener the callback to be invoked when an event of
 
-        * the specified type occurs
 
-        */
 
-       var _proto = Stream.prototype;
 
-       _proto.on = function on(type, listener) {
 
-         if (!this.listeners[type]) {
 
-           this.listeners[type] = [];
 
-         }
 
-         this.listeners[type].push(listener);
 
-       }
 
-       /**
 
-        * Remove a listener for a specified event type.
 
-        *
 
-        * @param {string} type the event name
 
-        * @param {Function} listener  a function previously registered for this
 
-        * type of event through `on`
 
-        * @return {boolean} if we could turn it off or not
 
-        */;
 
-       _proto.off = function off(type, listener) {
 
-         if (!this.listeners[type]) {
 
-           return false;
 
-         }
 
-         var index = this.listeners[type].indexOf(listener); // TODO: which is better?
 
-         // In Video.js we slice listener functions
 
-         // on trigger so that it does not mess up the order
 
-         // while we loop through.
 
-         //
 
-         // Here we slice on off so that the loop in trigger
 
-         // can continue using it's old reference to loop without
 
-         // messing up the order.
 
-         this.listeners[type] = this.listeners[type].slice(0);
 
-         this.listeners[type].splice(index, 1);
 
-         return index > -1;
 
-       }
 
-       /**
 
-        * Trigger an event of the specified type on this stream. Any additional
 
-        * arguments to this function are passed as parameters to event listeners.
 
-        *
 
-        * @param {string} type the event name
 
-        */;
 
-       _proto.trigger = function trigger(type) {
 
-         var callbacks = this.listeners[type];
 
-         if (!callbacks) {
 
-           return;
 
-         } // Slicing the arguments on every invocation of this method
 
-         // can add a significant amount of overhead. Avoid the
 
-         // intermediate object creation for the common case of a
 
-         // single callback argument
 
-         if (arguments.length === 2) {
 
-           var length = callbacks.length;
 
-           for (var i = 0; i < length; ++i) {
 
-             callbacks[i].call(this, arguments[1]);
 
-           }
 
-         } else {
 
-           var args = Array.prototype.slice.call(arguments, 1);
 
-           var _length = callbacks.length;
 
-           for (var _i = 0; _i < _length; ++_i) {
 
-             callbacks[_i].apply(this, args);
 
-           }
 
-         }
 
-       }
 
-       /**
 
-        * Destroys the stream and cleans up.
 
-        */;
 
-       _proto.dispose = function dispose() {
 
-         this.listeners = {};
 
-       }
 
-       /**
 
-        * Forwards all `data` events on this stream to the destination stream. The
 
-        * destination stream should provide a method `push` to receive the data
 
-        * events as they arrive.
 
-        *
 
-        * @param {Stream} destination the stream that will receive all `data` events
 
-        * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
 
-        */;
 
-       _proto.pipe = function pipe(destination) {
 
-         this.on('data', function (data) {
 
-           destination.push(data);
 
-         });
 
-       };
 
-       return Stream;
 
-     }();
 
-     /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
 
-     /**
 
-      * Returns the subarray of a Uint8Array without PKCS#7 padding.
 
-      *
 
-      * @param padded {Uint8Array} unencrypted bytes that have been padded
 
-      * @return {Uint8Array} the unpadded bytes
 
-      * @see http://tools.ietf.org/html/rfc5652
 
-      */
 
-     function unpad(padded) {
 
-       return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
 
-     }
 
-     /*! @name aes-decrypter @version 4.0.1 @license Apache-2.0 */
 
-     /**
 
-      * @file aes.js
 
-      *
 
-      * This file contains an adaptation of the AES decryption algorithm
 
-      * from the Standford Javascript Cryptography Library. That work is
 
-      * covered by the following copyright and permissions notice:
 
-      *
 
-      * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
 
-      * All rights reserved.
 
-      *
 
-      * Redistribution and use in source and binary forms, with or without
 
-      * modification, are permitted provided that the following conditions are
 
-      * met:
 
-      *
 
-      * 1. Redistributions of source code must retain the above copyright
 
-      *    notice, this list of conditions and the following disclaimer.
 
-      *
 
-      * 2. Redistributions in binary form must reproduce the above
 
-      *    copyright notice, this list of conditions and the following
 
-      *    disclaimer in the documentation and/or other materials provided
 
-      *    with the distribution.
 
-      *
 
-      * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
 
-      * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 
-      * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 
-      * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
 
-      * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 
-      * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 
-      * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 
-      * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 
-      * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 
-      * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 
-      * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-      *
 
-      * The views and conclusions contained in the software and documentation
 
-      * are those of the authors and should not be interpreted as representing
 
-      * official policies, either expressed or implied, of the authors.
 
-      */
 
-     /**
 
-      * Expand the S-box tables.
 
-      *
 
-      * @private
 
-      */
 
-     const precompute = function () {
 
-       const tables = [[[], [], [], [], []], [[], [], [], [], []]];
 
-       const encTable = tables[0];
 
-       const decTable = tables[1];
 
-       const sbox = encTable[4];
 
-       const sboxInv = decTable[4];
 
-       let i;
 
-       let x;
 
-       let xInv;
 
-       const d = [];
 
-       const th = [];
 
-       let x2;
 
-       let x4;
 
-       let x8;
 
-       let s;
 
-       let tEnc;
 
-       let tDec; // Compute double and third tables
 
-       for (i = 0; i < 256; i++) {
 
-         th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
 
-       }
 
-       for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
 
-         // Compute sbox
 
-         s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
 
-         s = s >> 8 ^ s & 255 ^ 99;
 
-         sbox[x] = s;
 
-         sboxInv[s] = x; // Compute MixColumns
 
-         x8 = d[x4 = d[x2 = d[x]]];
 
-         tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
 
-         tEnc = d[s] * 0x101 ^ s * 0x1010100;
 
-         for (i = 0; i < 4; i++) {
 
-           encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
 
-           decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
 
-         }
 
-       } // Compactify. Considerable speedup on Firefox.
 
-       for (i = 0; i < 5; i++) {
 
-         encTable[i] = encTable[i].slice(0);
 
-         decTable[i] = decTable[i].slice(0);
 
-       }
 
-       return tables;
 
-     };
 
-     let aesTables = null;
 
-     /**
 
-      * Schedule out an AES key for both encryption and decryption. This
 
-      * is a low-level class. Use a cipher mode to do bulk encryption.
 
-      *
 
-      * @class AES
 
-      * @param key {Array} The key as an array of 4, 6 or 8 words.
 
-      */
 
-     class AES {
 
-       constructor(key) {
 
-         /**
 
-         * The expanded S-box and inverse S-box tables. These will be computed
 
-         * on the client so that we don't have to send them down the wire.
 
-         *
 
-         * There are two tables, _tables[0] is for encryption and
 
-         * _tables[1] is for decryption.
 
-         *
 
-         * The first 4 sub-tables are the expanded S-box with MixColumns. The
 
-         * last (_tables[01][4]) is the S-box itself.
 
-         *
 
-         * @private
 
-         */
 
-         // if we have yet to precompute the S-box tables
 
-         // do so now
 
-         if (!aesTables) {
 
-           aesTables = precompute();
 
-         } // then make a copy of that object for use
 
-         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()]];
 
-         let i;
 
-         let j;
 
-         let tmp;
 
-         const sbox = this._tables[0][4];
 
-         const decTable = this._tables[1];
 
-         const keyLen = key.length;
 
-         let rcon = 1;
 
-         if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
 
-           throw new Error('Invalid aes key size');
 
-         }
 
-         const encKey = key.slice(0);
 
-         const decKey = [];
 
-         this._key = [encKey, decKey]; // schedule encryption keys
 
-         for (i = keyLen; i < 4 * keyLen + 28; i++) {
 
-           tmp = encKey[i - 1]; // apply sbox
 
-           if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
 
-             tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
 
-             if (i % keyLen === 0) {
 
-               tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
 
-               rcon = rcon << 1 ^ (rcon >> 7) * 283;
 
-             }
 
-           }
 
-           encKey[i] = encKey[i - keyLen] ^ tmp;
 
-         } // schedule decryption keys
 
-         for (j = 0; i; j++, i--) {
 
-           tmp = encKey[j & 3 ? i : i - 4];
 
-           if (i <= 4 || j < 4) {
 
-             decKey[j] = tmp;
 
-           } else {
 
-             decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
 
-           }
 
-         }
 
-       }
 
-       /**
 
-        * Decrypt 16 bytes, specified as four 32-bit words.
 
-        *
 
-        * @param {number} encrypted0 the first word to decrypt
 
-        * @param {number} encrypted1 the second word to decrypt
 
-        * @param {number} encrypted2 the third word to decrypt
 
-        * @param {number} encrypted3 the fourth word to decrypt
 
-        * @param {Int32Array} out the array to write the decrypted words
 
-        * into
 
-        * @param {number} offset the offset into the output array to start
 
-        * writing results
 
-        * @return {Array} The plaintext.
 
-        */
 
-       decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
 
-         const key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
 
-         let a = encrypted0 ^ key[0];
 
-         let b = encrypted3 ^ key[1];
 
-         let c = encrypted2 ^ key[2];
 
-         let d = encrypted1 ^ key[3];
 
-         let a2;
 
-         let b2;
 
-         let c2; // key.length === 2 ?
 
-         const nInnerRounds = key.length / 4 - 2;
 
-         let i;
 
-         let kIndex = 4;
 
-         const table = this._tables[1]; // load up the tables
 
-         const table0 = table[0];
 
-         const table1 = table[1];
 
-         const table2 = table[2];
 
-         const table3 = table[3];
 
-         const sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
 
-         for (i = 0; i < nInnerRounds; i++) {
 
-           a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
 
-           b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
 
-           c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
 
-           d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
 
-           kIndex += 4;
 
-           a = a2;
 
-           b = b2;
 
-           c = c2;
 
-         } // Last round.
 
-         for (i = 0; i < 4; i++) {
 
-           out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
 
-           a2 = a;
 
-           a = b;
 
-           b = c;
 
-           c = d;
 
-           d = a2;
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * @file async-stream.js
 
-      */
 
-     /**
 
-      * A wrapper around the Stream class to use setTimeout
 
-      * and run stream "jobs" Asynchronously
 
-      *
 
-      * @class AsyncStream
 
-      * @extends Stream
 
-      */
 
-     class AsyncStream extends Stream {
 
-       constructor() {
 
-         super(Stream);
 
-         this.jobs = [];
 
-         this.delay = 1;
 
-         this.timeout_ = null;
 
-       }
 
-       /**
 
-        * process an async job
 
-        *
 
-        * @private
 
-        */
 
-       processJob_() {
 
-         this.jobs.shift()();
 
-         if (this.jobs.length) {
 
-           this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
 
-         } else {
 
-           this.timeout_ = null;
 
-         }
 
-       }
 
-       /**
 
-        * push a job into the stream
 
-        *
 
-        * @param {Function} job the job to push into the stream
 
-        */
 
-       push(job) {
 
-         this.jobs.push(job);
 
-         if (!this.timeout_) {
 
-           this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * @file decrypter.js
 
-      *
 
-      * An asynchronous implementation of AES-128 CBC decryption with
 
-      * PKCS#7 padding.
 
-      */
 
-     /**
 
-      * Convert network-order (big-endian) bytes into their little-endian
 
-      * representation.
 
-      */
 
-     const ntoh = function (word) {
 
-       return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
 
-     };
 
-     /**
 
-      * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
 
-      *
 
-      * @param {Uint8Array} encrypted the encrypted bytes
 
-      * @param {Uint32Array} key the bytes of the decryption key
 
-      * @param {Uint32Array} initVector the initialization vector (IV) to
 
-      * use for the first round of CBC.
 
-      * @return {Uint8Array} the decrypted bytes
 
-      *
 
-      * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
 
-      * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
 
-      * @see https://tools.ietf.org/html/rfc2315
 
-      */
 
-     const decrypt = function (encrypted, key, initVector) {
 
-       // word-level access to the encrypted bytes
 
-       const encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
 
-       const decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
 
-       const decrypted = new Uint8Array(encrypted.byteLength);
 
-       const decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
 
-       // decrypted data
 
-       let init0;
 
-       let init1;
 
-       let init2;
 
-       let init3;
 
-       let encrypted0;
 
-       let encrypted1;
 
-       let encrypted2;
 
-       let encrypted3; // iteration variable
 
-       let wordIx; // pull out the words of the IV to ensure we don't modify the
 
-       // passed-in reference and easier access
 
-       init0 = initVector[0];
 
-       init1 = initVector[1];
 
-       init2 = initVector[2];
 
-       init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
 
-       // to each decrypted block
 
-       for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
 
-         // convert big-endian (network order) words into little-endian
 
-         // (javascript order)
 
-         encrypted0 = ntoh(encrypted32[wordIx]);
 
-         encrypted1 = ntoh(encrypted32[wordIx + 1]);
 
-         encrypted2 = ntoh(encrypted32[wordIx + 2]);
 
-         encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
 
-         decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
 
-         // plaintext
 
-         decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
 
-         decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
 
-         decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
 
-         decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
 
-         init0 = encrypted0;
 
-         init1 = encrypted1;
 
-         init2 = encrypted2;
 
-         init3 = encrypted3;
 
-       }
 
-       return decrypted;
 
-     };
 
-     /**
 
-      * The `Decrypter` class that manages decryption of AES
 
-      * data through `AsyncStream` objects and the `decrypt`
 
-      * function
 
-      *
 
-      * @param {Uint8Array} encrypted the encrypted bytes
 
-      * @param {Uint32Array} key the bytes of the decryption key
 
-      * @param {Uint32Array} initVector the initialization vector (IV) to
 
-      * @param {Function} done the function to run when done
 
-      * @class Decrypter
 
-      */
 
-     class Decrypter {
 
-       constructor(encrypted, key, initVector, done) {
 
-         const step = Decrypter.STEP;
 
-         const encrypted32 = new Int32Array(encrypted.buffer);
 
-         const decrypted = new Uint8Array(encrypted.byteLength);
 
-         let i = 0;
 
-         this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
 
-         this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
 
-         for (i = step; i < encrypted32.length; i += step) {
 
-           initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
 
-           this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
 
-         } // invoke the done() callback when everything is finished
 
-         this.asyncStream_.push(function () {
 
-           // remove pkcs#7 padding from the decrypted bytes
 
-           done(null, unpad(decrypted));
 
-         });
 
-       }
 
-       /**
 
-        * a getter for step the maximum number of bytes to process at one time
 
-        *
 
-        * @return {number} the value of step 32000
 
-        */
 
-       static get STEP() {
 
-         // 4 * 8000;
 
-         return 32000;
 
-       }
 
-       /**
 
-        * @private
 
-        */
 
-       decryptChunk_(encrypted, key, initVector, decrypted) {
 
-         return function () {
 
-           const bytes = decrypt(encrypted, key, initVector);
 
-           decrypted.set(bytes, encrypted.byteOffset);
 
-         };
 
-       }
 
-     }
 
-     var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
 
-     var win;
 
-     if (typeof window !== "undefined") {
 
-       win = window;
 
-     } else if (typeof commonjsGlobal !== "undefined") {
 
-       win = commonjsGlobal;
 
-     } else if (typeof self !== "undefined") {
 
-       win = self;
 
-     } else {
 
-       win = {};
 
-     }
 
-     var window_1 = win;
 
-     var isArrayBufferView = function isArrayBufferView(obj) {
 
-       if (ArrayBuffer.isView === 'function') {
 
-         return ArrayBuffer.isView(obj);
 
-       }
 
-       return obj && obj.buffer instanceof ArrayBuffer;
 
-     };
 
-     var BigInt = window_1.BigInt || Number;
 
-     [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
 
-     (function () {
 
-       var a = new Uint16Array([0xFFCC]);
 
-       var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
 
-       if (b[0] === 0xFF) {
 
-         return 'big';
 
-       }
 
-       if (b[0] === 0xCC) {
 
-         return 'little';
 
-       }
 
-       return 'unknown';
 
-     })();
 
-     /**
 
-      * Creates an object for sending to a web worker modifying properties that are TypedArrays
 
-      * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
 
-      *
 
-      * @param {Object} message
 
-      *        Object of properties and values to send to the web worker
 
-      * @return {Object}
 
-      *         Modified message with TypedArray values expanded
 
-      * @function createTransferableMessage
 
-      */
 
-     const createTransferableMessage = function (message) {
 
-       const transferable = {};
 
-       Object.keys(message).forEach(key => {
 
-         const value = message[key];
 
-         if (isArrayBufferView(value)) {
 
-           transferable[key] = {
 
-             bytes: value.buffer,
 
-             byteOffset: value.byteOffset,
 
-             byteLength: value.byteLength
 
-           };
 
-         } else {
 
-           transferable[key] = value;
 
-         }
 
-       });
 
-       return transferable;
 
-     };
 
-     /* global self */
 
-     /**
 
-      * Our web worker interface so that things can talk to aes-decrypter
 
-      * that will be running in a web worker. the scope is passed to this by
 
-      * webworkify.
 
-      */
 
-     self.onmessage = function (event) {
 
-       const data = event.data;
 
-       const encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
 
-       const key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
 
-       const iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
 
-       /* eslint-disable no-new, handle-callback-err */
 
-       new Decrypter(encrypted, key, iv, function (err, bytes) {
 
-         self.postMessage(createTransferableMessage({
 
-           source: data.source,
 
-           decrypted: bytes
 
-         }), [bytes.buffer]);
 
-       });
 
-       /* eslint-enable */
 
-     };
 
-   }));
 
-   var Decrypter = factory(workerCode);
 
-   /* rollup-plugin-worker-factory end for worker!/Users/ddashkevich/projects/http-streaming/src/decrypter-worker.js */
 
-   /**
 
-    * Convert the properties of an HLS track into an audioTrackKind.
 
-    *
 
-    * @private
 
-    */
 
-   const audioTrackKind_ = properties => {
 
-     let kind = properties.default ? 'main' : 'alternative';
 
-     if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
 
-       kind = 'main-desc';
 
-     }
 
-     return kind;
 
-   };
 
-   /**
 
-    * Pause provided segment loader and playlist loader if active
 
-    *
 
-    * @param {SegmentLoader} segmentLoader
 
-    *        SegmentLoader to pause
 
-    * @param {Object} mediaType
 
-    *        Active media type
 
-    * @function stopLoaders
 
-    */
 
-   const stopLoaders = (segmentLoader, mediaType) => {
 
-     segmentLoader.abort();
 
-     segmentLoader.pause();
 
-     if (mediaType && mediaType.activePlaylistLoader) {
 
-       mediaType.activePlaylistLoader.pause();
 
-       mediaType.activePlaylistLoader = null;
 
-     }
 
-   };
 
-   /**
 
-    * Start loading provided segment loader and playlist loader
 
-    *
 
-    * @param {PlaylistLoader} playlistLoader
 
-    *        PlaylistLoader to start loading
 
-    * @param {Object} mediaType
 
-    *        Active media type
 
-    * @function startLoaders
 
-    */
 
-   const startLoaders = (playlistLoader, mediaType) => {
 
-     // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
 
-     // playlist loader
 
-     mediaType.activePlaylistLoader = playlistLoader;
 
-     playlistLoader.load();
 
-   };
 
-   /**
 
-    * Returns a function to be called when the media group changes. It performs a
 
-    * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
 
-    * change of group is merely a rendition switch of the same content at another encoding,
 
-    * rather than a change of content, such as switching audio from English to Spanish.
 
-    *
 
-    * @param {string} type
 
-    *        MediaGroup type
 
-    * @param {Object} settings
 
-    *        Object containing required information for media groups
 
-    * @return {Function}
 
-    *         Handler for a non-destructive resync of SegmentLoader when the active media
 
-    *         group changes.
 
-    * @function onGroupChanged
 
-    */
 
-   const onGroupChanged = (type, settings) => () => {
 
-     const {
 
-       segmentLoaders: {
 
-         [type]: segmentLoader,
 
-         main: mainSegmentLoader
 
-       },
 
-       mediaTypes: {
 
-         [type]: mediaType
 
-       }
 
-     } = settings;
 
-     const activeTrack = mediaType.activeTrack();
 
-     const activeGroup = mediaType.getActiveGroup();
 
-     const previousActiveLoader = mediaType.activePlaylistLoader;
 
-     const lastGroup = mediaType.lastGroup_; // the group did not change do nothing
 
-     if (activeGroup && lastGroup && activeGroup.id === lastGroup.id) {
 
-       return;
 
-     }
 
-     mediaType.lastGroup_ = activeGroup;
 
-     mediaType.lastTrack_ = activeTrack;
 
-     stopLoaders(segmentLoader, mediaType);
 
-     if (!activeGroup || activeGroup.isMainPlaylist) {
 
-       // there is no group active or active group is a main playlist and won't change
 
-       return;
 
-     }
 
-     if (!activeGroup.playlistLoader) {
 
-       if (previousActiveLoader) {
 
-         // The previous group had a playlist loader but the new active group does not
 
-         // this means we are switching from demuxed to muxed audio. In this case we want to
 
-         // do a destructive reset of the main segment loader and not restart the audio
 
-         // loaders.
 
-         mainSegmentLoader.resetEverything();
 
-       }
 
-       return;
 
-     } // Non-destructive resync
 
-     segmentLoader.resyncLoader();
 
-     startLoaders(activeGroup.playlistLoader, mediaType);
 
-   };
 
-   const onGroupChanging = (type, settings) => () => {
 
-     const {
 
-       segmentLoaders: {
 
-         [type]: segmentLoader
 
-       },
 
-       mediaTypes: {
 
-         [type]: mediaType
 
-       }
 
-     } = settings;
 
-     mediaType.lastGroup_ = null;
 
-     segmentLoader.abort();
 
-     segmentLoader.pause();
 
-   };
 
-   /**
 
-    * Returns a function to be called when the media track changes. It performs a
 
-    * destructive reset of the SegmentLoader to ensure we start loading as close to
 
-    * currentTime as possible.
 
-    *
 
-    * @param {string} type
 
-    *        MediaGroup type
 
-    * @param {Object} settings
 
-    *        Object containing required information for media groups
 
-    * @return {Function}
 
-    *         Handler for a destructive reset of SegmentLoader when the active media
 
-    *         track changes.
 
-    * @function onTrackChanged
 
-    */
 
-   const onTrackChanged = (type, settings) => () => {
 
-     const {
 
-       mainPlaylistLoader,
 
-       segmentLoaders: {
 
-         [type]: segmentLoader,
 
-         main: mainSegmentLoader
 
-       },
 
-       mediaTypes: {
 
-         [type]: mediaType
 
-       }
 
-     } = settings;
 
-     const activeTrack = mediaType.activeTrack();
 
-     const activeGroup = mediaType.getActiveGroup();
 
-     const previousActiveLoader = mediaType.activePlaylistLoader;
 
-     const lastTrack = mediaType.lastTrack_; // track did not change, do nothing
 
-     if (lastTrack && activeTrack && lastTrack.id === activeTrack.id) {
 
-       return;
 
-     }
 
-     mediaType.lastGroup_ = activeGroup;
 
-     mediaType.lastTrack_ = activeTrack;
 
-     stopLoaders(segmentLoader, mediaType);
 
-     if (!activeGroup) {
 
-       // there is no group active so we do not want to restart loaders
 
-       return;
 
-     }
 
-     if (activeGroup.isMainPlaylist) {
 
-       // track did not change, do nothing
 
-       if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) {
 
-         return;
 
-       }
 
-       const pc = settings.vhs.playlistController_;
 
-       const newPlaylist = pc.selectPlaylist(); // media will not change do nothing
 
-       if (pc.media() === newPlaylist) {
 
-         return;
 
-       }
 
-       mediaType.logger_(`track change. Switching main audio from ${lastTrack.id} to ${activeTrack.id}`);
 
-       mainPlaylistLoader.pause();
 
-       mainSegmentLoader.resetEverything();
 
-       pc.fastQualityChange_(newPlaylist);
 
-       return;
 
-     }
 
-     if (type === 'AUDIO') {
 
-       if (!activeGroup.playlistLoader) {
 
-         // when switching from demuxed audio/video to muxed audio/video (noted by no
 
-         // playlist loader for the audio group), we want to do a destructive reset of the
 
-         // main segment loader and not restart the audio loaders
 
-         mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since
 
-         // it should be stopped
 
-         mainSegmentLoader.resetEverything();
 
-         return;
 
-       } // although the segment loader is an audio segment loader, call the setAudio
 
-       // function to ensure it is prepared to re-append the init segment (or handle other
 
-       // config changes)
 
-       segmentLoader.setAudio(true);
 
-       mainSegmentLoader.setAudio(false);
 
-     }
 
-     if (previousActiveLoader === activeGroup.playlistLoader) {
 
-       // Nothing has actually changed. This can happen because track change events can fire
 
-       // multiple times for a "single" change. One for enabling the new active track, and
 
-       // one for disabling the track that was active
 
-       startLoaders(activeGroup.playlistLoader, mediaType);
 
-       return;
 
-     }
 
-     if (segmentLoader.track) {
 
-       // For WebVTT, set the new text track in the segmentloader
 
-       segmentLoader.track(activeTrack);
 
-     } // destructive reset
 
-     segmentLoader.resetEverything();
 
-     startLoaders(activeGroup.playlistLoader, mediaType);
 
-   };
 
-   const onError = {
 
-     /**
 
-      * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
 
-      * an error.
 
-      *
 
-      * @param {string} type
 
-      *        MediaGroup type
 
-      * @param {Object} settings
 
-      *        Object containing required information for media groups
 
-      * @return {Function}
 
-      *         Error handler. Logs warning (or error if the playlist is excluded) to
 
-      *         console and switches back to default audio track.
 
-      * @function onError.AUDIO
 
-      */
 
-     AUDIO: (type, settings) => () => {
 
-       const {
 
-         segmentLoaders: {
 
-           [type]: segmentLoader
 
-         },
 
-         mediaTypes: {
 
-           [type]: mediaType
 
-         },
 
-         excludePlaylist
 
-       } = settings;
 
-       stopLoaders(segmentLoader, mediaType); // switch back to default audio track
 
-       const activeTrack = mediaType.activeTrack();
 
-       const activeGroup = mediaType.activeGroup();
 
-       const id = (activeGroup.filter(group => group.default)[0] || activeGroup[0]).id;
 
-       const defaultTrack = mediaType.tracks[id];
 
-       if (activeTrack === defaultTrack) {
 
-         // Default track encountered an error. All we can do now is exclude the current
 
-         // rendition and hope another will switch audio groups
 
-         excludePlaylist({
 
-           error: {
 
-             message: 'Problem encountered loading the default audio track.'
 
-           }
 
-         });
 
-         return;
 
-       }
 
-       videojs.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
 
-       for (const trackId in mediaType.tracks) {
 
-         mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
 
-       }
 
-       mediaType.onTrackChanged();
 
-     },
 
-     /**
 
-      * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
 
-      * an error.
 
-      *
 
-      * @param {string} type
 
-      *        MediaGroup type
 
-      * @param {Object} settings
 
-      *        Object containing required information for media groups
 
-      * @return {Function}
 
-      *         Error handler. Logs warning to console and disables the active subtitle track
 
-      * @function onError.SUBTITLES
 
-      */
 
-     SUBTITLES: (type, settings) => () => {
 
-       const {
 
-         segmentLoaders: {
 
-           [type]: segmentLoader
 
-         },
 
-         mediaTypes: {
 
-           [type]: mediaType
 
-         }
 
-       } = settings;
 
-       videojs.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
 
-       stopLoaders(segmentLoader, mediaType);
 
-       const track = mediaType.activeTrack();
 
-       if (track) {
 
-         track.mode = 'disabled';
 
-       }
 
-       mediaType.onTrackChanged();
 
-     }
 
-   };
 
-   const setupListeners = {
 
-     /**
 
-      * Setup event listeners for audio playlist loader
 
-      *
 
-      * @param {string} type
 
-      *        MediaGroup type
 
-      * @param {PlaylistLoader|null} playlistLoader
 
-      *        PlaylistLoader to register listeners on
 
-      * @param {Object} settings
 
-      *        Object containing required information for media groups
 
-      * @function setupListeners.AUDIO
 
-      */
 
-     AUDIO: (type, playlistLoader, settings) => {
 
-       if (!playlistLoader) {
 
-         // no playlist loader means audio will be muxed with the video
 
-         return;
 
-       }
 
-       const {
 
-         tech,
 
-         requestOptions,
 
-         segmentLoaders: {
 
-           [type]: segmentLoader
 
-         }
 
-       } = settings;
 
-       playlistLoader.on('loadedmetadata', () => {
 
-         const media = playlistLoader.media();
 
-         segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
 
-         // permits, start downloading segments
 
-         if (!tech.paused() || media.endList && tech.preload() !== 'none') {
 
-           segmentLoader.load();
 
-         }
 
-       });
 
-       playlistLoader.on('loadedplaylist', () => {
 
-         segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
 
-         if (!tech.paused()) {
 
-           segmentLoader.load();
 
-         }
 
-       });
 
-       playlistLoader.on('error', onError[type](type, settings));
 
-     },
 
-     /**
 
-      * Setup event listeners for subtitle playlist loader
 
-      *
 
-      * @param {string} type
 
-      *        MediaGroup type
 
-      * @param {PlaylistLoader|null} playlistLoader
 
-      *        PlaylistLoader to register listeners on
 
-      * @param {Object} settings
 
-      *        Object containing required information for media groups
 
-      * @function setupListeners.SUBTITLES
 
-      */
 
-     SUBTITLES: (type, playlistLoader, settings) => {
 
-       const {
 
-         tech,
 
-         requestOptions,
 
-         segmentLoaders: {
 
-           [type]: segmentLoader
 
-         },
 
-         mediaTypes: {
 
-           [type]: mediaType
 
-         }
 
-       } = settings;
 
-       playlistLoader.on('loadedmetadata', () => {
 
-         const media = playlistLoader.media();
 
-         segmentLoader.playlist(media, requestOptions);
 
-         segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
 
-         // permits, start downloading segments
 
-         if (!tech.paused() || media.endList && tech.preload() !== 'none') {
 
-           segmentLoader.load();
 
-         }
 
-       });
 
-       playlistLoader.on('loadedplaylist', () => {
 
-         segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
 
-         if (!tech.paused()) {
 
-           segmentLoader.load();
 
-         }
 
-       });
 
-       playlistLoader.on('error', onError[type](type, settings));
 
-     }
 
-   };
 
-   const initialize = {
 
-     /**
 
-      * Setup PlaylistLoaders and AudioTracks for the audio groups
 
-      *
 
-      * @param {string} type
 
-      *        MediaGroup type
 
-      * @param {Object} settings
 
-      *        Object containing required information for media groups
 
-      * @function initialize.AUDIO
 
-      */
 
-     'AUDIO': (type, settings) => {
 
-       const {
 
-         vhs,
 
-         sourceType,
 
-         segmentLoaders: {
 
-           [type]: segmentLoader
 
-         },
 
-         requestOptions,
 
-         main: {
 
-           mediaGroups
 
-         },
 
-         mediaTypes: {
 
-           [type]: {
 
-             groups,
 
-             tracks,
 
-             logger_
 
-           }
 
-         },
 
-         mainPlaylistLoader
 
-       } = settings;
 
-       const audioOnlyMain = isAudioOnly(mainPlaylistLoader.main); // force a default if we have none
 
-       if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
 
-         mediaGroups[type] = {
 
-           main: {
 
-             default: {
 
-               default: true
 
-             }
 
-           }
 
-         };
 
-         if (audioOnlyMain) {
 
-           mediaGroups[type].main.default.playlists = mainPlaylistLoader.main.playlists;
 
-         }
 
-       }
 
-       for (const groupId in mediaGroups[type]) {
 
-         if (!groups[groupId]) {
 
-           groups[groupId] = [];
 
-         }
 
-         for (const variantLabel in mediaGroups[type][groupId]) {
 
-           let properties = mediaGroups[type][groupId][variantLabel];
 
-           let playlistLoader;
 
-           if (audioOnlyMain) {
 
-             logger_(`AUDIO group '${groupId}' label '${variantLabel}' is a main playlist`);
 
-             properties.isMainPlaylist = true;
 
-             playlistLoader = null; // if vhs-json was provided as the source, and the media playlist was resolved,
 
-             // use the resolved media playlist object
 
-           } else if (sourceType === 'vhs-json' && properties.playlists) {
 
-             playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions);
 
-           } else if (properties.resolvedUri) {
 
-             playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); // TODO: dash isn't the only type with properties.playlists
 
-             // should we even have properties.playlists in this check.
 
-           } else if (properties.playlists && sourceType === 'dash') {
 
-             playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, mainPlaylistLoader);
 
-           } else {
 
-             // no resolvedUri means the audio is muxed with the video when using this
 
-             // audio track
 
-             playlistLoader = null;
 
-           }
 
-           properties = merge({
 
-             id: variantLabel,
 
-             playlistLoader
 
-           }, properties);
 
-           setupListeners[type](type, properties.playlistLoader, settings);
 
-           groups[groupId].push(properties);
 
-           if (typeof tracks[variantLabel] === 'undefined') {
 
-             const track = new videojs.AudioTrack({
 
-               id: variantLabel,
 
-               kind: audioTrackKind_(properties),
 
-               enabled: false,
 
-               language: properties.language,
 
-               default: properties.default,
 
-               label: variantLabel
 
-             });
 
-             tracks[variantLabel] = track;
 
-           }
 
-         }
 
-       } // setup single error event handler for the segment loader
 
-       segmentLoader.on('error', onError[type](type, settings));
 
-     },
 
-     /**
 
-      * Setup PlaylistLoaders and TextTracks for the subtitle groups
 
-      *
 
-      * @param {string} type
 
-      *        MediaGroup type
 
-      * @param {Object} settings
 
-      *        Object containing required information for media groups
 
-      * @function initialize.SUBTITLES
 
-      */
 
-     'SUBTITLES': (type, settings) => {
 
-       const {
 
-         tech,
 
-         vhs,
 
-         sourceType,
 
-         segmentLoaders: {
 
-           [type]: segmentLoader
 
-         },
 
-         requestOptions,
 
-         main: {
 
-           mediaGroups
 
-         },
 
-         mediaTypes: {
 
-           [type]: {
 
-             groups,
 
-             tracks
 
-           }
 
-         },
 
-         mainPlaylistLoader
 
-       } = settings;
 
-       for (const groupId in mediaGroups[type]) {
 
-         if (!groups[groupId]) {
 
-           groups[groupId] = [];
 
-         }
 
-         for (const variantLabel in mediaGroups[type][groupId]) {
 
-           if (mediaGroups[type][groupId][variantLabel].forced) {
 
-             // Subtitle playlists with the forced attribute are not selectable in Safari.
 
-             // According to Apple's HLS Authoring Specification:
 
-             //   If content has forced subtitles and regular subtitles in a given language,
 
-             //   the regular subtitles track in that language MUST contain both the forced
 
-             //   subtitles and the regular subtitles for that language.
 
-             // Because of this requirement and that Safari does not add forced subtitles,
 
-             // forced subtitles are skipped here to maintain consistent experience across
 
-             // all platforms
 
-             continue;
 
-           }
 
-           let properties = mediaGroups[type][groupId][variantLabel];
 
-           let playlistLoader;
 
-           if (sourceType === 'hls') {
 
-             playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions);
 
-           } else if (sourceType === 'dash') {
 
-             const playlists = properties.playlists.filter(p => p.excludeUntil !== Infinity);
 
-             if (!playlists.length) {
 
-               return;
 
-             }
 
-             playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, mainPlaylistLoader);
 
-           } else if (sourceType === 'vhs-json') {
 
-             playlistLoader = new PlaylistLoader(
 
-             // if the vhs-json object included the media playlist, use the media playlist
 
-             // as provided, otherwise use the resolved URI to load the playlist
 
-             properties.playlists ? properties.playlists[0] : properties.resolvedUri, vhs, requestOptions);
 
-           }
 
-           properties = merge({
 
-             id: variantLabel,
 
-             playlistLoader
 
-           }, properties);
 
-           setupListeners[type](type, properties.playlistLoader, settings);
 
-           groups[groupId].push(properties);
 
-           if (typeof tracks[variantLabel] === 'undefined') {
 
-             const track = tech.addRemoteTextTrack({
 
-               id: variantLabel,
 
-               kind: 'subtitles',
 
-               default: properties.default && properties.autoselect,
 
-               language: properties.language,
 
-               label: variantLabel
 
-             }, false).track;
 
-             tracks[variantLabel] = track;
 
-           }
 
-         }
 
-       } // setup single error event handler for the segment loader
 
-       segmentLoader.on('error', onError[type](type, settings));
 
-     },
 
-     /**
 
-      * Setup TextTracks for the closed-caption groups
 
-      *
 
-      * @param {String} type
 
-      *        MediaGroup type
 
-      * @param {Object} settings
 
-      *        Object containing required information for media groups
 
-      * @function initialize['CLOSED-CAPTIONS']
 
-      */
 
-     'CLOSED-CAPTIONS': (type, settings) => {
 
-       const {
 
-         tech,
 
-         main: {
 
-           mediaGroups
 
-         },
 
-         mediaTypes: {
 
-           [type]: {
 
-             groups,
 
-             tracks
 
-           }
 
-         }
 
-       } = settings;
 
-       for (const groupId in mediaGroups[type]) {
 
-         if (!groups[groupId]) {
 
-           groups[groupId] = [];
 
-         }
 
-         for (const variantLabel in mediaGroups[type][groupId]) {
 
-           const properties = mediaGroups[type][groupId][variantLabel]; // Look for either 608 (CCn) or 708 (SERVICEn) caption services
 
-           if (!/^(?:CC|SERVICE)/.test(properties.instreamId)) {
 
-             continue;
 
-           }
 
-           const captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
 
-           let newProps = {
 
-             label: variantLabel,
 
-             language: properties.language,
 
-             instreamId: properties.instreamId,
 
-             default: properties.default && properties.autoselect
 
-           };
 
-           if (captionServices[newProps.instreamId]) {
 
-             newProps = merge(newProps, captionServices[newProps.instreamId]);
 
-           }
 
-           if (newProps.default === undefined) {
 
-             delete newProps.default;
 
-           } // No PlaylistLoader is required for Closed-Captions because the captions are
 
-           // embedded within the video stream
 
-           groups[groupId].push(merge({
 
-             id: variantLabel
 
-           }, properties));
 
-           if (typeof tracks[variantLabel] === 'undefined') {
 
-             const track = tech.addRemoteTextTrack({
 
-               id: newProps.instreamId,
 
-               kind: 'captions',
 
-               default: newProps.default,
 
-               language: newProps.language,
 
-               label: newProps.label
 
-             }, false).track;
 
-             tracks[variantLabel] = track;
 
-           }
 
-         }
 
-       }
 
-     }
 
-   };
 
-   const groupMatch = (list, media) => {
 
-     for (let i = 0; i < list.length; i++) {
 
-       if (playlistMatch(media, list[i])) {
 
-         return true;
 
-       }
 
-       if (list[i].playlists && groupMatch(list[i].playlists, media)) {
 
-         return true;
 
-       }
 
-     }
 
-     return false;
 
-   };
 
-   /**
 
-    * Returns a function used to get the active group of the provided type
 
-    *
 
-    * @param {string} type
 
-    *        MediaGroup type
 
-    * @param {Object} settings
 
-    *        Object containing required information for media groups
 
-    * @return {Function}
 
-    *         Function that returns the active media group for the provided type. Takes an
 
-    *         optional parameter {TextTrack} track. If no track is provided, a list of all
 
-    *         variants in the group, otherwise the variant corresponding to the provided
 
-    *         track is returned.
 
-    * @function activeGroup
 
-    */
 
-   const activeGroup = (type, settings) => track => {
 
-     const {
 
-       mainPlaylistLoader,
 
-       mediaTypes: {
 
-         [type]: {
 
-           groups
 
-         }
 
-       }
 
-     } = settings;
 
-     const media = mainPlaylistLoader.media();
 
-     if (!media) {
 
-       return null;
 
-     }
 
-     let variants = null; // set to variants to main media active group
 
-     if (media.attributes[type]) {
 
-       variants = groups[media.attributes[type]];
 
-     }
 
-     const groupKeys = Object.keys(groups);
 
-     if (!variants) {
 
-       // find the mainPlaylistLoader media
 
-       // that is in a media group if we are dealing
 
-       // with audio only
 
-       if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.main)) {
 
-         for (let i = 0; i < groupKeys.length; i++) {
 
-           const groupPropertyList = groups[groupKeys[i]];
 
-           if (groupMatch(groupPropertyList, media)) {
 
-             variants = groupPropertyList;
 
-             break;
 
-           }
 
-         } // use the main group if it exists
 
-       } else if (groups.main) {
 
-         variants = groups.main; // only one group, use that one
 
-       } else if (groupKeys.length === 1) {
 
-         variants = groups[groupKeys[0]];
 
-       }
 
-     }
 
-     if (typeof track === 'undefined') {
 
-       return variants;
 
-     }
 
-     if (track === null || !variants) {
 
-       // An active track was specified so a corresponding group is expected. track === null
 
-       // means no track is currently active so there is no corresponding group
 
-       return null;
 
-     }
 
-     return variants.filter(props => props.id === track.id)[0] || null;
 
-   };
 
-   const activeTrack = {
 
-     /**
 
-      * Returns a function used to get the active track of type provided
 
-      *
 
-      * @param {string} type
 
-      *        MediaGroup type
 
-      * @param {Object} settings
 
-      *        Object containing required information for media groups
 
-      * @return {Function}
 
-      *         Function that returns the active media track for the provided type. Returns
 
-      *         null if no track is active
 
-      * @function activeTrack.AUDIO
 
-      */
 
-     AUDIO: (type, settings) => () => {
 
-       const {
 
-         mediaTypes: {
 
-           [type]: {
 
-             tracks
 
-           }
 
-         }
 
-       } = settings;
 
-       for (const id in tracks) {
 
-         if (tracks[id].enabled) {
 
-           return tracks[id];
 
-         }
 
-       }
 
-       return null;
 
-     },
 
-     /**
 
-      * Returns a function used to get the active track of type provided
 
-      *
 
-      * @param {string} type
 
-      *        MediaGroup type
 
-      * @param {Object} settings
 
-      *        Object containing required information for media groups
 
-      * @return {Function}
 
-      *         Function that returns the active media track for the provided type. Returns
 
-      *         null if no track is active
 
-      * @function activeTrack.SUBTITLES
 
-      */
 
-     SUBTITLES: (type, settings) => () => {
 
-       const {
 
-         mediaTypes: {
 
-           [type]: {
 
-             tracks
 
-           }
 
-         }
 
-       } = settings;
 
-       for (const id in tracks) {
 
-         if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') {
 
-           return tracks[id];
 
-         }
 
-       }
 
-       return null;
 
-     }
 
-   };
 
-   const getActiveGroup = (type, {
 
-     mediaTypes
 
-   }) => () => {
 
-     const activeTrack_ = mediaTypes[type].activeTrack();
 
-     if (!activeTrack_) {
 
-       return null;
 
-     }
 
-     return mediaTypes[type].activeGroup(activeTrack_);
 
-   };
 
-   /**
 
-    * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
 
-    * Closed-Captions) specified in the main manifest.
 
-    *
 
-    * @param {Object} settings
 
-    *        Object containing required information for setting up the media groups
 
-    * @param {Tech} settings.tech
 
-    *        The tech of the player
 
-    * @param {Object} settings.requestOptions
 
-    *        XHR request options used by the segment loaders
 
-    * @param {PlaylistLoader} settings.mainPlaylistLoader
 
-    *        PlaylistLoader for the main source
 
-    * @param {VhsHandler} settings.vhs
 
-    *        VHS SourceHandler
 
-    * @param {Object} settings.main
 
-    *        The parsed main manifest
 
-    * @param {Object} settings.mediaTypes
 
-    *        Object to store the loaders, tracks, and utility methods for each media type
 
-    * @param {Function} settings.excludePlaylist
 
-    *        Excludes the current rendition and forces a rendition switch.
 
-    * @function setupMediaGroups
 
-    */
 
-   const setupMediaGroups = settings => {
 
-     ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
 
-       initialize[type](type, settings);
 
-     });
 
-     const {
 
-       mediaTypes,
 
-       mainPlaylistLoader,
 
-       tech,
 
-       vhs,
 
-       segmentLoaders: {
 
-         ['AUDIO']: audioSegmentLoader,
 
-         main: mainSegmentLoader
 
-       }
 
-     } = settings; // setup active group and track getters and change event handlers
 
-     ['AUDIO', 'SUBTITLES'].forEach(type => {
 
-       mediaTypes[type].activeGroup = activeGroup(type, settings);
 
-       mediaTypes[type].activeTrack = activeTrack[type](type, settings);
 
-       mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
 
-       mediaTypes[type].onGroupChanging = onGroupChanging(type, settings);
 
-       mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
 
-       mediaTypes[type].getActiveGroup = getActiveGroup(type, settings);
 
-     }); // DO NOT enable the default subtitle or caption track.
 
-     // DO enable the default audio track
 
-     const audioGroup = mediaTypes.AUDIO.activeGroup();
 
-     if (audioGroup) {
 
-       const groupId = (audioGroup.filter(group => group.default)[0] || audioGroup[0]).id;
 
-       mediaTypes.AUDIO.tracks[groupId].enabled = true;
 
-       mediaTypes.AUDIO.onGroupChanged();
 
-       mediaTypes.AUDIO.onTrackChanged();
 
-       const activeAudioGroup = mediaTypes.AUDIO.getActiveGroup(); // a similar check for handling setAudio on each loader is run again each time the
 
-       // track is changed, but needs to be handled here since the track may not be considered
 
-       // changed on the first call to onTrackChanged
 
-       if (!activeAudioGroup.playlistLoader) {
 
-         // either audio is muxed with video or the stream is audio only
 
-         mainSegmentLoader.setAudio(true);
 
-       } else {
 
-         // audio is demuxed
 
-         mainSegmentLoader.setAudio(false);
 
-         audioSegmentLoader.setAudio(true);
 
-       }
 
-     }
 
-     mainPlaylistLoader.on('mediachange', () => {
 
-       ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanged());
 
-     });
 
-     mainPlaylistLoader.on('mediachanging', () => {
 
-       ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanging());
 
-     }); // custom audio track change event handler for usage event
 
-     const onAudioTrackChanged = () => {
 
-       mediaTypes.AUDIO.onTrackChanged();
 
-       tech.trigger({
 
-         type: 'usage',
 
-         name: 'vhs-audio-change'
 
-       });
 
-     };
 
-     tech.audioTracks().addEventListener('change', onAudioTrackChanged);
 
-     tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
 
-     vhs.on('dispose', () => {
 
-       tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
 
-       tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
 
-     }); // clear existing audio tracks and add the ones we just created
 
-     tech.clearTracks('audio');
 
-     for (const id in mediaTypes.AUDIO.tracks) {
 
-       tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
 
-     }
 
-   };
 
-   /**
 
-    * Creates skeleton object used to store the loaders, tracks, and utility methods for each
 
-    * media type
 
-    *
 
-    * @return {Object}
 
-    *         Object to store the loaders, tracks, and utility methods for each media type
 
-    * @function createMediaTypes
 
-    */
 
-   const createMediaTypes = () => {
 
-     const mediaTypes = {};
 
-     ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
 
-       mediaTypes[type] = {
 
-         groups: {},
 
-         tracks: {},
 
-         activePlaylistLoader: null,
 
-         activeGroup: noop,
 
-         activeTrack: noop,
 
-         getActiveGroup: noop,
 
-         onGroupChanged: noop,
 
-         onTrackChanged: noop,
 
-         lastTrack_: null,
 
-         logger_: logger(`MediaGroups[${type}]`)
 
-       };
 
-     });
 
-     return mediaTypes;
 
-   };
 
-   /**
 
-    * @file playlist-controller.js
 
-    */
 
-   const ABORT_EARLY_EXCLUSION_SECONDS = 60 * 2;
 
-   let Vhs$1; // SegmentLoader stats that need to have each loader's
 
-   // values summed to calculate the final value
 
-   const loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred', 'mediaAppends'];
 
-   const sumLoaderStat = function (stat) {
 
-     return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
 
-   };
 
-   const shouldSwitchToMedia = function ({
 
-     currentPlaylist,
 
-     buffered,
 
-     currentTime,
 
-     nextPlaylist,
 
-     bufferLowWaterLine,
 
-     bufferHighWaterLine,
 
-     duration,
 
-     bufferBasedABR,
 
-     log
 
-   }) {
 
-     // we have no other playlist to switch to
 
-     if (!nextPlaylist) {
 
-       videojs.log.warn('We received no playlist to switch to. Please check your stream.');
 
-       return false;
 
-     }
 
-     const sharedLogLine = `allowing switch ${currentPlaylist && currentPlaylist.id || 'null'} -> ${nextPlaylist.id}`;
 
-     if (!currentPlaylist) {
 
-       log(`${sharedLogLine} as current playlist is not set`);
 
-       return true;
 
-     } // no need to switch if playlist is the same
 
-     if (nextPlaylist.id === currentPlaylist.id) {
 
-       return false;
 
-     } // determine if current time is in a buffered range.
 
-     const isBuffered = Boolean(findRange(buffered, currentTime).length); // If the playlist is live, then we want to not take low water line into account.
 
-     // This is because in LIVE, the player plays 3 segments from the end of the
 
-     // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
 
-     // in those segments, a viewer will never experience a rendition upswitch.
 
-     if (!currentPlaylist.endList) {
 
-       // For LLHLS live streams, don't switch renditions before playback has started, as it almost
 
-       // doubles the time to first playback.
 
-       if (!isBuffered && typeof currentPlaylist.partTargetDuration === 'number') {
 
-         log(`not ${sharedLogLine} as current playlist is live llhls, but currentTime isn't in buffered.`);
 
-         return false;
 
-       }
 
-       log(`${sharedLogLine} as current playlist is live`);
 
-       return true;
 
-     }
 
-     const forwardBuffer = timeAheadOf(buffered, currentTime);
 
-     const maxBufferLowWaterLine = bufferBasedABR ? Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE : Config.MAX_BUFFER_LOW_WATER_LINE; // For the same reason as LIVE, we ignore the low water line when the VOD
 
-     // duration is below the max potential low water line
 
-     if (duration < maxBufferLowWaterLine) {
 
-       log(`${sharedLogLine} as duration < max low water line (${duration} < ${maxBufferLowWaterLine})`);
 
-       return true;
 
-     }
 
-     const nextBandwidth = nextPlaylist.attributes.BANDWIDTH;
 
-     const currBandwidth = currentPlaylist.attributes.BANDWIDTH; // when switching down, if our buffer is lower than the high water line,
 
-     // we can switch down
 
-     if (nextBandwidth < currBandwidth && (!bufferBasedABR || forwardBuffer < bufferHighWaterLine)) {
 
-       let logLine = `${sharedLogLine} as next bandwidth < current bandwidth (${nextBandwidth} < ${currBandwidth})`;
 
-       if (bufferBasedABR) {
 
-         logLine += ` and forwardBuffer < bufferHighWaterLine (${forwardBuffer} < ${bufferHighWaterLine})`;
 
-       }
 
-       log(logLine);
 
-       return true;
 
-     } // and if our buffer is higher than the low water line,
 
-     // we can switch up
 
-     if ((!bufferBasedABR || nextBandwidth > currBandwidth) && forwardBuffer >= bufferLowWaterLine) {
 
-       let logLine = `${sharedLogLine} as forwardBuffer >= bufferLowWaterLine (${forwardBuffer} >= ${bufferLowWaterLine})`;
 
-       if (bufferBasedABR) {
 
-         logLine += ` and next bandwidth > current bandwidth (${nextBandwidth} > ${currBandwidth})`;
 
-       }
 
-       log(logLine);
 
-       return true;
 
-     }
 
-     log(`not ${sharedLogLine} as no switching criteria met`);
 
-     return false;
 
-   };
 
-   /**
 
-    * the main playlist controller controller all interactons
 
-    * between playlists and segmentloaders. At this time this mainly
 
-    * involves a main playlist and a series of audio playlists
 
-    * if they are available
 
-    *
 
-    * @class PlaylistController
 
-    * @extends videojs.EventTarget
 
-    */
 
-   class PlaylistController extends videojs.EventTarget {
 
-     constructor(options) {
 
-       super();
 
-       const {
 
-         src,
 
-         withCredentials,
 
-         tech,
 
-         bandwidth,
 
-         externVhs,
 
-         useCueTags,
 
-         playlistExclusionDuration,
 
-         enableLowInitialPlaylist,
 
-         sourceType,
 
-         cacheEncryptionKeys,
 
-         bufferBasedABR,
 
-         leastPixelDiffSelector,
 
-         captionServices
 
-       } = options;
 
-       if (!src) {
 
-         throw new Error('A non-empty playlist URL or JSON manifest string is required');
 
-       }
 
-       let {
 
-         maxPlaylistRetries
 
-       } = options;
 
-       if (maxPlaylistRetries === null || typeof maxPlaylistRetries === 'undefined') {
 
-         maxPlaylistRetries = Infinity;
 
-       }
 
-       Vhs$1 = externVhs;
 
-       this.bufferBasedABR = Boolean(bufferBasedABR);
 
-       this.leastPixelDiffSelector = Boolean(leastPixelDiffSelector);
 
-       this.withCredentials = withCredentials;
 
-       this.tech_ = tech;
 
-       this.vhs_ = tech.vhs;
 
-       this.sourceType_ = sourceType;
 
-       this.useCueTags_ = useCueTags;
 
-       this.playlistExclusionDuration = playlistExclusionDuration;
 
-       this.maxPlaylistRetries = maxPlaylistRetries;
 
-       this.enableLowInitialPlaylist = enableLowInitialPlaylist;
 
-       if (this.useCueTags_) {
 
-         this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'ad-cues');
 
-         this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
 
-       }
 
-       this.requestOptions_ = {
 
-         withCredentials,
 
-         maxPlaylistRetries,
 
-         timeout: null
 
-       };
 
-       this.on('error', this.pauseLoading);
 
-       this.mediaTypes_ = createMediaTypes();
 
-       this.mediaSource = new window.MediaSource();
 
-       this.handleDurationChange_ = this.handleDurationChange_.bind(this);
 
-       this.handleSourceOpen_ = this.handleSourceOpen_.bind(this);
 
-       this.handleSourceEnded_ = this.handleSourceEnded_.bind(this);
 
-       this.mediaSource.addEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
 
-       this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_);
 
-       this.mediaSource.addEventListener('sourceended', this.handleSourceEnded_); // we don't have to handle sourceclose since dispose will handle termination of
 
-       // everything, and the MediaSource should not be detached without a proper disposal
 
-       this.seekable_ = createTimeRanges();
 
-       this.hasPlayed_ = false;
 
-       this.syncController_ = new SyncController(options);
 
-       this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
 
-         kind: 'metadata',
 
-         label: 'segment-metadata'
 
-       }, false).track;
 
-       this.decrypter_ = new Decrypter();
 
-       this.sourceUpdater_ = new SourceUpdater(this.mediaSource);
 
-       this.inbandTextTracks_ = {};
 
-       this.timelineChangeController_ = new TimelineChangeController();
 
-       const segmentLoaderSettings = {
 
-         vhs: this.vhs_,
 
-         parse708captions: options.parse708captions,
 
-         useDtsForTimestampOffset: options.useDtsForTimestampOffset,
 
-         captionServices,
 
-         mediaSource: this.mediaSource,
 
-         currentTime: this.tech_.currentTime.bind(this.tech_),
 
-         seekable: () => this.seekable(),
 
-         seeking: () => this.tech_.seeking(),
 
-         duration: () => this.duration(),
 
-         hasPlayed: () => this.hasPlayed_,
 
-         goalBufferLength: () => this.goalBufferLength(),
 
-         bandwidth,
 
-         syncController: this.syncController_,
 
-         decrypter: this.decrypter_,
 
-         sourceType: this.sourceType_,
 
-         inbandTextTracks: this.inbandTextTracks_,
 
-         cacheEncryptionKeys,
 
-         sourceUpdater: this.sourceUpdater_,
 
-         timelineChangeController: this.timelineChangeController_,
 
-         exactManifestTimings: options.exactManifestTimings
 
-       }; // The source type check not only determines whether a special DASH playlist loader
 
-       // should be used, but also covers the case where the provided src is a vhs-json
 
-       // manifest object (instead of a URL). In the case of vhs-json, the default
 
-       // PlaylistLoader should be used.
 
-       this.mainPlaylistLoader_ = this.sourceType_ === 'dash' ? new DashPlaylistLoader(src, this.vhs_, this.requestOptions_) : new PlaylistLoader(src, this.vhs_, this.requestOptions_);
 
-       this.setupMainPlaylistLoaderListeners_(); // setup segment loaders
 
-       // combined audio/video or just video when alternate audio track is selected
 
-       this.mainSegmentLoader_ = new SegmentLoader(merge(segmentLoaderSettings, {
 
-         segmentMetadataTrack: this.segmentMetadataTrack_,
 
-         loaderType: 'main'
 
-       }), options); // alternate audio track
 
-       this.audioSegmentLoader_ = new SegmentLoader(merge(segmentLoaderSettings, {
 
-         loaderType: 'audio'
 
-       }), options);
 
-       this.subtitleSegmentLoader_ = new VTTSegmentLoader(merge(segmentLoaderSettings, {
 
-         loaderType: 'vtt',
 
-         featuresNativeTextTracks: this.tech_.featuresNativeTextTracks,
 
-         loadVttJs: () => new Promise((resolve, reject) => {
 
-           function onLoad() {
 
-             tech.off('vttjserror', onError);
 
-             resolve();
 
-           }
 
-           function onError() {
 
-             tech.off('vttjsloaded', onLoad);
 
-             reject();
 
-           }
 
-           tech.one('vttjsloaded', onLoad);
 
-           tech.one('vttjserror', onError); // safe to call multiple times, script will be loaded only once:
 
-           tech.addWebVttScript_();
 
-         })
 
-       }), options);
 
-       this.setupSegmentLoaderListeners_();
 
-       if (this.bufferBasedABR) {
 
-         this.mainPlaylistLoader_.one('loadedplaylist', () => this.startABRTimer_());
 
-         this.tech_.on('pause', () => this.stopABRTimer_());
 
-         this.tech_.on('play', () => this.startABRTimer_());
 
-       } // Create SegmentLoader stat-getters
 
-       // mediaRequests_
 
-       // mediaRequestsAborted_
 
-       // mediaRequestsTimedout_
 
-       // mediaRequestsErrored_
 
-       // mediaTransferDuration_
 
-       // mediaBytesTransferred_
 
-       // mediaAppends_
 
-       loaderStats.forEach(stat => {
 
-         this[stat + '_'] = sumLoaderStat.bind(this, stat);
 
-       });
 
-       this.logger_ = logger('pc');
 
-       this.triggeredFmp4Usage = false;
 
-       if (this.tech_.preload() === 'none') {
 
-         this.loadOnPlay_ = () => {
 
-           this.loadOnPlay_ = null;
 
-           this.mainPlaylistLoader_.load();
 
-         };
 
-         this.tech_.one('play', this.loadOnPlay_);
 
-       } else {
 
-         this.mainPlaylistLoader_.load();
 
-       }
 
-       this.timeToLoadedData__ = -1;
 
-       this.mainAppendsToLoadedData__ = -1;
 
-       this.audioAppendsToLoadedData__ = -1;
 
-       const event = this.tech_.preload() === 'none' ? 'play' : 'loadstart'; // start the first frame timer on loadstart or play (for preload none)
 
-       this.tech_.one(event, () => {
 
-         const timeToLoadedDataStart = Date.now();
 
-         this.tech_.one('loadeddata', () => {
 
-           this.timeToLoadedData__ = Date.now() - timeToLoadedDataStart;
 
-           this.mainAppendsToLoadedData__ = this.mainSegmentLoader_.mediaAppends;
 
-           this.audioAppendsToLoadedData__ = this.audioSegmentLoader_.mediaAppends;
 
-         });
 
-       });
 
-     }
 
-     mainAppendsToLoadedData_() {
 
-       return this.mainAppendsToLoadedData__;
 
-     }
 
-     audioAppendsToLoadedData_() {
 
-       return this.audioAppendsToLoadedData__;
 
-     }
 
-     appendsToLoadedData_() {
 
-       const main = this.mainAppendsToLoadedData_();
 
-       const audio = this.audioAppendsToLoadedData_();
 
-       if (main === -1 || audio === -1) {
 
-         return -1;
 
-       }
 
-       return main + audio;
 
-     }
 
-     timeToLoadedData_() {
 
-       return this.timeToLoadedData__;
 
-     }
 
-     /**
 
-      * Run selectPlaylist and switch to the new playlist if we should
 
-      *
 
-      * @param {string} [reason=abr] a reason for why the ABR check is made
 
-      * @private
 
-      */
 
-     checkABR_(reason = 'abr') {
 
-       const nextPlaylist = this.selectPlaylist();
 
-       if (nextPlaylist && this.shouldSwitchToMedia_(nextPlaylist)) {
 
-         this.switchMedia_(nextPlaylist, reason);
 
-       }
 
-     }
 
-     switchMedia_(playlist, cause, delay) {
 
-       const oldMedia = this.media();
 
-       const oldId = oldMedia && (oldMedia.id || oldMedia.uri);
 
-       const newId = playlist.id || playlist.uri;
 
-       if (oldId && oldId !== newId) {
 
-         this.logger_(`switch media ${oldId} -> ${newId} from ${cause}`);
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: `vhs-rendition-change-${cause}`
 
-         });
 
-       }
 
-       this.mainPlaylistLoader_.media(playlist, delay);
 
-     }
 
-     /**
 
-      * Start a timer that periodically calls checkABR_
 
-      *
 
-      * @private
 
-      */
 
-     startABRTimer_() {
 
-       this.stopABRTimer_();
 
-       this.abrTimer_ = window.setInterval(() => this.checkABR_(), 250);
 
-     }
 
-     /**
 
-      * Stop the timer that periodically calls checkABR_
 
-      *
 
-      * @private
 
-      */
 
-     stopABRTimer_() {
 
-       // if we're scrubbing, we don't need to pause.
 
-       // This getter will be added to Video.js in version 7.11.
 
-       if (this.tech_.scrubbing && this.tech_.scrubbing()) {
 
-         return;
 
-       }
 
-       window.clearInterval(this.abrTimer_);
 
-       this.abrTimer_ = null;
 
-     }
 
-     /**
 
-      * Get a list of playlists for the currently selected audio playlist
 
-      *
 
-      * @return {Array} the array of audio playlists
 
-      */
 
-     getAudioTrackPlaylists_() {
 
-       const main = this.main();
 
-       const defaultPlaylists = main && main.playlists || []; // if we don't have any audio groups then we can only
 
-       // assume that the audio tracks are contained in main
 
-       // playlist array, use that or an empty array.
 
-       if (!main || !main.mediaGroups || !main.mediaGroups.AUDIO) {
 
-         return defaultPlaylists;
 
-       }
 
-       const AUDIO = main.mediaGroups.AUDIO;
 
-       const groupKeys = Object.keys(AUDIO);
 
-       let track; // get the current active track
 
-       if (Object.keys(this.mediaTypes_.AUDIO.groups).length) {
 
-         track = this.mediaTypes_.AUDIO.activeTrack(); // or get the default track from main if mediaTypes_ isn't setup yet
 
-       } else {
 
-         // default group is `main` or just the first group.
 
-         const defaultGroup = AUDIO.main || groupKeys.length && AUDIO[groupKeys[0]];
 
-         for (const label in defaultGroup) {
 
-           if (defaultGroup[label].default) {
 
-             track = {
 
-               label
 
-             };
 
-             break;
 
-           }
 
-         }
 
-       } // no active track no playlists.
 
-       if (!track) {
 
-         return defaultPlaylists;
 
-       }
 
-       const playlists = []; // get all of the playlists that are possible for the
 
-       // active track.
 
-       for (const group in AUDIO) {
 
-         if (AUDIO[group][track.label]) {
 
-           const properties = AUDIO[group][track.label];
 
-           if (properties.playlists && properties.playlists.length) {
 
-             playlists.push.apply(playlists, properties.playlists);
 
-           } else if (properties.uri) {
 
-             playlists.push(properties);
 
-           } else if (main.playlists.length) {
 
-             // if an audio group does not have a uri
 
-             // see if we have main playlists that use it as a group.
 
-             // if we do then add those to the playlists list.
 
-             for (let i = 0; i < main.playlists.length; i++) {
 
-               const playlist = main.playlists[i];
 
-               if (playlist.attributes && playlist.attributes.AUDIO && playlist.attributes.AUDIO === group) {
 
-                 playlists.push(playlist);
 
-               }
 
-             }
 
-           }
 
-         }
 
-       }
 
-       if (!playlists.length) {
 
-         return defaultPlaylists;
 
-       }
 
-       return playlists;
 
-     }
 
-     /**
 
-      * Register event handlers on the main playlist loader. A helper
 
-      * function for construction time.
 
-      *
 
-      * @private
 
-      */
 
-     setupMainPlaylistLoaderListeners_() {
 
-       this.mainPlaylistLoader_.on('loadedmetadata', () => {
 
-         const media = this.mainPlaylistLoader_.media();
 
-         const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
 
-         // timeout the request.
 
-         if (isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())) {
 
-           this.requestOptions_.timeout = 0;
 
-         } else {
 
-           this.requestOptions_.timeout = requestTimeout;
 
-         } // if this isn't a live video and preload permits, start
 
-         // downloading segments
 
-         if (media.endList && this.tech_.preload() !== 'none') {
 
-           this.mainSegmentLoader_.playlist(media, this.requestOptions_);
 
-           this.mainSegmentLoader_.load();
 
-         }
 
-         setupMediaGroups({
 
-           sourceType: this.sourceType_,
 
-           segmentLoaders: {
 
-             AUDIO: this.audioSegmentLoader_,
 
-             SUBTITLES: this.subtitleSegmentLoader_,
 
-             main: this.mainSegmentLoader_
 
-           },
 
-           tech: this.tech_,
 
-           requestOptions: this.requestOptions_,
 
-           mainPlaylistLoader: this.mainPlaylistLoader_,
 
-           vhs: this.vhs_,
 
-           main: this.main(),
 
-           mediaTypes: this.mediaTypes_,
 
-           excludePlaylist: this.excludePlaylist.bind(this)
 
-         });
 
-         this.triggerPresenceUsage_(this.main(), media);
 
-         this.setupFirstPlay();
 
-         if (!this.mediaTypes_.AUDIO.activePlaylistLoader || this.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
 
-           this.trigger('selectedinitialmedia');
 
-         } else {
 
-           // We must wait for the active audio playlist loader to
 
-           // finish setting up before triggering this event so the
 
-           // representations API and EME setup is correct
 
-           this.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', () => {
 
-             this.trigger('selectedinitialmedia');
 
-           });
 
-         }
 
-       });
 
-       this.mainPlaylistLoader_.on('loadedplaylist', () => {
 
-         if (this.loadOnPlay_) {
 
-           this.tech_.off('play', this.loadOnPlay_);
 
-         }
 
-         let updatedPlaylist = this.mainPlaylistLoader_.media();
 
-         if (!updatedPlaylist) {
 
-           // exclude any variants that are not supported by the browser before selecting
 
-           // an initial media as the playlist selectors do not consider browser support
 
-           this.excludeUnsupportedVariants_();
 
-           let selectedMedia;
 
-           if (this.enableLowInitialPlaylist) {
 
-             selectedMedia = this.selectInitialPlaylist();
 
-           }
 
-           if (!selectedMedia) {
 
-             selectedMedia = this.selectPlaylist();
 
-           }
 
-           if (!selectedMedia || !this.shouldSwitchToMedia_(selectedMedia)) {
 
-             return;
 
-           }
 
-           this.initialMedia_ = selectedMedia;
 
-           this.switchMedia_(this.initialMedia_, 'initial'); // Under the standard case where a source URL is provided, loadedplaylist will
 
-           // fire again since the playlist will be requested. In the case of vhs-json
 
-           // (where the manifest object is provided as the source), when the media
 
-           // playlist's `segments` list is already available, a media playlist won't be
 
-           // requested, and loadedplaylist won't fire again, so the playlist handler must be
 
-           // called on its own here.
 
-           const haveJsonSource = this.sourceType_ === 'vhs-json' && this.initialMedia_.segments;
 
-           if (!haveJsonSource) {
 
-             return;
 
-           }
 
-           updatedPlaylist = this.initialMedia_;
 
-         }
 
-         this.handleUpdatedMediaPlaylist(updatedPlaylist);
 
-       });
 
-       this.mainPlaylistLoader_.on('error', () => {
 
-         const error = this.mainPlaylistLoader_.error;
 
-         this.excludePlaylist({
 
-           playlistToExclude: error.playlist,
 
-           error
 
-         });
 
-       });
 
-       this.mainPlaylistLoader_.on('mediachanging', () => {
 
-         this.mainSegmentLoader_.abort();
 
-         this.mainSegmentLoader_.pause();
 
-       });
 
-       this.mainPlaylistLoader_.on('mediachange', () => {
 
-         const media = this.mainPlaylistLoader_.media();
 
-         const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
 
-         // timeout the request.
 
-         if (isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())) {
 
-           this.requestOptions_.timeout = 0;
 
-         } else {
 
-           this.requestOptions_.timeout = requestTimeout;
 
-         }
 
-         this.mainPlaylistLoader_.load(); // TODO: Create a new event on the PlaylistLoader that signals
 
-         // that the segments have changed in some way and use that to
 
-         // update the SegmentLoader instead of doing it twice here and
 
-         // on `loadedplaylist`
 
-         this.mainSegmentLoader_.playlist(media, this.requestOptions_);
 
-         this.mainSegmentLoader_.load();
 
-         this.tech_.trigger({
 
-           type: 'mediachange',
 
-           bubbles: true
 
-         });
 
-       });
 
-       this.mainPlaylistLoader_.on('playlistunchanged', () => {
 
-         const updatedPlaylist = this.mainPlaylistLoader_.media(); // ignore unchanged playlists that have already been
 
-         // excluded for not-changing. We likely just have a really slowly updating
 
-         // playlist.
 
-         if (updatedPlaylist.lastExcludeReason_ === 'playlist-unchanged') {
 
-           return;
 
-         }
 
-         const playlistOutdated = this.stuckAtPlaylistEnd_(updatedPlaylist);
 
-         if (playlistOutdated) {
 
-           // Playlist has stopped updating and we're stuck at its end. Try to
 
-           // exclude it and switch to another playlist in the hope that that
 
-           // one is updating (and give the player a chance to re-adjust to the
 
-           // safe live point).
 
-           this.excludePlaylist({
 
-             error: {
 
-               message: 'Playlist no longer updating.',
 
-               reason: 'playlist-unchanged'
 
-             }
 
-           }); // useful for monitoring QoS
 
-           this.tech_.trigger('playliststuck');
 
-         }
 
-       });
 
-       this.mainPlaylistLoader_.on('renditiondisabled', () => {
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-rendition-disabled'
 
-         });
 
-       });
 
-       this.mainPlaylistLoader_.on('renditionenabled', () => {
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-rendition-enabled'
 
-         });
 
-       });
 
-     }
 
-     /**
 
-      * Given an updated media playlist (whether it was loaded for the first time, or
 
-      * refreshed for live playlists), update any relevant properties and state to reflect
 
-      * changes in the media that should be accounted for (e.g., cues and duration).
 
-      *
 
-      * @param {Object} updatedPlaylist the updated media playlist object
 
-      *
 
-      * @private
 
-      */
 
-     handleUpdatedMediaPlaylist(updatedPlaylist) {
 
-       if (this.useCueTags_) {
 
-         this.updateAdCues_(updatedPlaylist);
 
-       } // TODO: Create a new event on the PlaylistLoader that signals
 
-       // that the segments have changed in some way and use that to
 
-       // update the SegmentLoader instead of doing it twice here and
 
-       // on `mediachange`
 
-       this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_);
 
-       this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running,
 
-       // as it is possible that it was temporarily stopped while waiting for
 
-       // a playlist (e.g., in case the playlist errored and we re-requested it).
 
-       if (!this.tech_.paused()) {
 
-         this.mainSegmentLoader_.load();
 
-         if (this.audioSegmentLoader_) {
 
-           this.audioSegmentLoader_.load();
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * A helper function for triggerring presence usage events once per source
 
-      *
 
-      * @private
 
-      */
 
-     triggerPresenceUsage_(main, media) {
 
-       const mediaGroups = main.mediaGroups || {};
 
-       let defaultDemuxed = true;
 
-       const audioGroupKeys = Object.keys(mediaGroups.AUDIO);
 
-       for (const mediaGroup in mediaGroups.AUDIO) {
 
-         for (const label in mediaGroups.AUDIO[mediaGroup]) {
 
-           const properties = mediaGroups.AUDIO[mediaGroup][label];
 
-           if (!properties.uri) {
 
-             defaultDemuxed = false;
 
-           }
 
-         }
 
-       }
 
-       if (defaultDemuxed) {
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-demuxed'
 
-         });
 
-       }
 
-       if (Object.keys(mediaGroups.SUBTITLES).length) {
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-webvtt'
 
-         });
 
-       }
 
-       if (Vhs$1.Playlist.isAes(media)) {
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-aes'
 
-         });
 
-       }
 
-       if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-alternate-audio'
 
-         });
 
-       }
 
-       if (this.useCueTags_) {
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-playlist-cue-tags'
 
-         });
 
-       }
 
-     }
 
-     shouldSwitchToMedia_(nextPlaylist) {
 
-       const currentPlaylist = this.mainPlaylistLoader_.media() || this.mainPlaylistLoader_.pendingMedia_;
 
-       const currentTime = this.tech_.currentTime();
 
-       const bufferLowWaterLine = this.bufferLowWaterLine();
 
-       const bufferHighWaterLine = this.bufferHighWaterLine();
 
-       const buffered = this.tech_.buffered();
 
-       return shouldSwitchToMedia({
 
-         buffered,
 
-         currentTime,
 
-         currentPlaylist,
 
-         nextPlaylist,
 
-         bufferLowWaterLine,
 
-         bufferHighWaterLine,
 
-         duration: this.duration(),
 
-         bufferBasedABR: this.bufferBasedABR,
 
-         log: this.logger_
 
-       });
 
-     }
 
-     /**
 
-      * Register event handlers on the segment loaders. A helper function
 
-      * for construction time.
 
-      *
 
-      * @private
 
-      */
 
-     setupSegmentLoaderListeners_() {
 
-       this.mainSegmentLoader_.on('bandwidthupdate', () => {
 
-         // Whether or not buffer based ABR or another ABR is used, on a bandwidth change it's
 
-         // useful to check to see if a rendition switch should be made.
 
-         this.checkABR_('bandwidthupdate');
 
-         this.tech_.trigger('bandwidthupdate');
 
-       });
 
-       this.mainSegmentLoader_.on('timeout', () => {
 
-         if (this.bufferBasedABR) {
 
-           // If a rendition change is needed, then it would've be done on `bandwidthupdate`.
 
-           // Here the only consideration is that for buffer based ABR there's no guarantee
 
-           // of an immediate switch (since the bandwidth is averaged with a timeout
 
-           // bandwidth value of 1), so force a load on the segment loader to keep it going.
 
-           this.mainSegmentLoader_.load();
 
-         }
 
-       }); // `progress` events are not reliable enough of a bandwidth measure to trigger buffer
 
-       // based ABR.
 
-       if (!this.bufferBasedABR) {
 
-         this.mainSegmentLoader_.on('progress', () => {
 
-           this.trigger('progress');
 
-         });
 
-       }
 
-       this.mainSegmentLoader_.on('error', () => {
 
-         const error = this.mainSegmentLoader_.error();
 
-         this.excludePlaylist({
 
-           playlistToExclude: error.playlist,
 
-           error
 
-         });
 
-       });
 
-       this.mainSegmentLoader_.on('appenderror', () => {
 
-         this.error = this.mainSegmentLoader_.error_;
 
-         this.trigger('error');
 
-       });
 
-       this.mainSegmentLoader_.on('syncinfoupdate', () => {
 
-         this.onSyncInfoUpdate_();
 
-       });
 
-       this.mainSegmentLoader_.on('timestampoffset', () => {
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-timestamp-offset'
 
-         });
 
-       });
 
-       this.audioSegmentLoader_.on('syncinfoupdate', () => {
 
-         this.onSyncInfoUpdate_();
 
-       });
 
-       this.audioSegmentLoader_.on('appenderror', () => {
 
-         this.error = this.audioSegmentLoader_.error_;
 
-         this.trigger('error');
 
-       });
 
-       this.mainSegmentLoader_.on('ended', () => {
 
-         this.logger_('main segment loader ended');
 
-         this.onEndOfStream();
 
-       });
 
-       this.mainSegmentLoader_.on('earlyabort', event => {
 
-         // never try to early abort with the new ABR algorithm
 
-         if (this.bufferBasedABR) {
 
-           return;
 
-         }
 
-         this.delegateLoaders_('all', ['abort']);
 
-         this.excludePlaylist({
 
-           error: {
 
-             message: 'Aborted early because there isn\'t enough bandwidth to complete ' + 'the request without rebuffering.'
 
-           },
 
-           playlistExclusionDuration: ABORT_EARLY_EXCLUSION_SECONDS
 
-         });
 
-       });
 
-       const updateCodecs = () => {
 
-         if (!this.sourceUpdater_.hasCreatedSourceBuffers()) {
 
-           return this.tryToCreateSourceBuffers_();
 
-         }
 
-         const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
 
-         if (!codecs) {
 
-           return;
 
-         }
 
-         this.sourceUpdater_.addOrChangeSourceBuffers(codecs);
 
-       };
 
-       this.mainSegmentLoader_.on('trackinfo', updateCodecs);
 
-       this.audioSegmentLoader_.on('trackinfo', updateCodecs);
 
-       this.mainSegmentLoader_.on('fmp4', () => {
 
-         if (!this.triggeredFmp4Usage) {
 
-           this.tech_.trigger({
 
-             type: 'usage',
 
-             name: 'vhs-fmp4'
 
-           });
 
-           this.triggeredFmp4Usage = true;
 
-         }
 
-       });
 
-       this.audioSegmentLoader_.on('fmp4', () => {
 
-         if (!this.triggeredFmp4Usage) {
 
-           this.tech_.trigger({
 
-             type: 'usage',
 
-             name: 'vhs-fmp4'
 
-           });
 
-           this.triggeredFmp4Usage = true;
 
-         }
 
-       });
 
-       this.audioSegmentLoader_.on('ended', () => {
 
-         this.logger_('audioSegmentLoader ended');
 
-         this.onEndOfStream();
 
-       });
 
-     }
 
-     mediaSecondsLoaded_() {
 
-       return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
 
-     }
 
-     /**
 
-      * Call load on our SegmentLoaders
 
-      */
 
-     load() {
 
-       this.mainSegmentLoader_.load();
 
-       if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
 
-         this.audioSegmentLoader_.load();
 
-       }
 
-       if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
 
-         this.subtitleSegmentLoader_.load();
 
-       }
 
-     }
 
-     /**
 
-      * Re-tune playback quality level for the current player
 
-      * conditions. This method will perform destructive actions like removing
 
-      * already buffered content in order to readjust the currently active
 
-      * playlist quickly. This is good for manual quality changes
 
-      *
 
-      * @private
 
-      */
 
-     fastQualityChange_(media = this.selectPlaylist()) {
 
-       if (media === this.mainPlaylistLoader_.media()) {
 
-         this.logger_('skipping fastQualityChange because new media is same as old');
 
-         return;
 
-       }
 
-       this.switchMedia_(media, 'fast-quality'); // Delete all buffered data to allow an immediate quality switch, then seek to give
 
-       // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
 
-       // ahead is roughly the minimum that will accomplish this across a variety of content
 
-       // in IE and Edge, but seeking in place is sufficient on all other browsers)
 
-       // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
 
-       // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
 
-       this.mainSegmentLoader_.resetEverything(() => {
 
-         // Since this is not a typical seek, we avoid the seekTo method which can cause segments
 
-         // from the previously enabled rendition to load before the new playlist has finished loading
 
-         if (videojs.browser.IE_VERSION || videojs.browser.IS_EDGE) {
 
-           this.tech_.setCurrentTime(this.tech_.currentTime() + 0.04);
 
-         } else {
 
-           this.tech_.setCurrentTime(this.tech_.currentTime());
 
-         }
 
-       }); // don't need to reset audio as it is reset when media changes
 
-     }
 
-     /**
 
-      * Begin playback.
 
-      */
 
-     play() {
 
-       if (this.setupFirstPlay()) {
 
-         return;
 
-       }
 
-       if (this.tech_.ended()) {
 
-         this.tech_.setCurrentTime(0);
 
-       }
 
-       if (this.hasPlayed_) {
 
-         this.load();
 
-       }
 
-       const seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
 
-       // seek forward to the live point
 
-       if (this.tech_.duration() === Infinity) {
 
-         if (this.tech_.currentTime() < seekable.start(0)) {
 
-           return this.tech_.setCurrentTime(seekable.end(seekable.length - 1));
 
-         }
 
-       }
 
-     }
 
-     /**
 
-      * Seek to the latest media position if this is a live video and the
 
-      * player and video are loaded and initialized.
 
-      */
 
-     setupFirstPlay() {
 
-       const media = this.mainPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
 
-       //  If 1) there is no active media
 
-       //     2) the player is paused
 
-       //     3) the first play has already been setup
 
-       // then exit early
 
-       if (!media || this.tech_.paused() || this.hasPlayed_) {
 
-         return false;
 
-       } // when the video is a live stream
 
-       if (!media.endList) {
 
-         const seekable = this.seekable();
 
-         if (!seekable.length) {
 
-           // without a seekable range, the player cannot seek to begin buffering at the live
 
-           // point
 
-           return false;
 
-         }
 
-         if (videojs.browser.IE_VERSION && this.tech_.readyState() === 0) {
 
-           // IE11 throws an InvalidStateError if you try to set currentTime while the
 
-           // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
 
-           this.tech_.one('loadedmetadata', () => {
 
-             this.trigger('firstplay');
 
-             this.tech_.setCurrentTime(seekable.end(0));
 
-             this.hasPlayed_ = true;
 
-           });
 
-           return false;
 
-         } // trigger firstplay to inform the source handler to ignore the next seek event
 
-         this.trigger('firstplay'); // seek to the live point
 
-         this.tech_.setCurrentTime(seekable.end(0));
 
-       }
 
-       this.hasPlayed_ = true; // we can begin loading now that everything is ready
 
-       this.load();
 
-       return true;
 
-     }
 
-     /**
 
-      * handle the sourceopen event on the MediaSource
 
-      *
 
-      * @private
 
-      */
 
-     handleSourceOpen_() {
 
-       // Only attempt to create the source buffer if none already exist.
 
-       // handleSourceOpen is also called when we are "re-opening" a source buffer
 
-       // after `endOfStream` has been called (in response to a seek for instance)
 
-       this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of
 
-       // code in video.js but is required because play() must be invoked
 
-       // *after* the media source has opened.
 
-       if (this.tech_.autoplay()) {
 
-         const playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
 
-         // on browsers which return a promise
 
-         if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
 
-           playPromise.then(null, e => {});
 
-         }
 
-       }
 
-       this.trigger('sourceopen');
 
-     }
 
-     /**
 
-      * handle the sourceended event on the MediaSource
 
-      *
 
-      * @private
 
-      */
 
-     handleSourceEnded_() {
 
-       if (!this.inbandTextTracks_.metadataTrack_) {
 
-         return;
 
-       }
 
-       const cues = this.inbandTextTracks_.metadataTrack_.cues;
 
-       if (!cues || !cues.length) {
 
-         return;
 
-       }
 
-       const duration = this.duration();
 
-       cues[cues.length - 1].endTime = isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration;
 
-     }
 
-     /**
 
-      * handle the durationchange event on the MediaSource
 
-      *
 
-      * @private
 
-      */
 
-     handleDurationChange_() {
 
-       this.tech_.trigger('durationchange');
 
-     }
 
-     /**
 
-      * Calls endOfStream on the media source when all active stream types have called
 
-      * endOfStream
 
-      *
 
-      * @param {string} streamType
 
-      *        Stream type of the segment loader that called endOfStream
 
-      * @private
 
-      */
 
-     onEndOfStream() {
 
-       let isEndOfStream = this.mainSegmentLoader_.ended_;
 
-       if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
 
-         const mainMediaInfo = this.mainSegmentLoader_.getCurrentMediaInfo_(); // if the audio playlist loader exists, then alternate audio is active
 
-         if (!mainMediaInfo || mainMediaInfo.hasVideo) {
 
-           // if we do not know if the main segment loader contains video yet or if we
 
-           // definitively know the main segment loader contains video, then we need to wait
 
-           // for both main and audio segment loaders to call endOfStream
 
-           isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
 
-         } else {
 
-           // otherwise just rely on the audio loader
 
-           isEndOfStream = this.audioSegmentLoader_.ended_;
 
-         }
 
-       }
 
-       if (!isEndOfStream) {
 
-         return;
 
-       }
 
-       this.stopABRTimer_();
 
-       this.sourceUpdater_.endOfStream();
 
-     }
 
-     /**
 
-      * Check if a playlist has stopped being updated
 
-      *
 
-      * @param {Object} playlist the media playlist object
 
-      * @return {boolean} whether the playlist has stopped being updated or not
 
-      */
 
-     stuckAtPlaylistEnd_(playlist) {
 
-       const seekable = this.seekable();
 
-       if (!seekable.length) {
 
-         // playlist doesn't have enough information to determine whether we are stuck
 
-         return false;
 
-       }
 
-       const expired = this.syncController_.getExpiredTime(playlist, this.duration());
 
-       if (expired === null) {
 
-         return false;
 
-       } // does not use the safe live end to calculate playlist end, since we
 
-       // don't want to say we are stuck while there is still content
 
-       const absolutePlaylistEnd = Vhs$1.Playlist.playlistEnd(playlist, expired);
 
-       const currentTime = this.tech_.currentTime();
 
-       const buffered = this.tech_.buffered();
 
-       if (!buffered.length) {
 
-         // return true if the playhead reached the absolute end of the playlist
 
-         return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
 
-       }
 
-       const bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
 
-       // end of playlist
 
-       return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
 
-     }
 
-     /**
 
-      * Exclude a playlist for a set amount of time, making it unavailable for selection by
 
-      * the rendition selection algorithm, then force a new playlist (rendition) selection.
 
-      *
 
-      * @param {Object=} playlistToExclude
 
-      *                  the playlist to exclude, defaults to the currently selected playlist
 
-      * @param {Object=} error
 
-      *                  an optional error
 
-      * @param {number=} playlistExclusionDuration
 
-      *                  an optional number of seconds to exclude the playlist
 
-      */
 
-     excludePlaylist({
 
-       playlistToExclude = this.mainPlaylistLoader_.media(),
 
-       error = {},
 
-       playlistExclusionDuration
 
-     }) {
 
-       // If the `error` was generated by the playlist loader, it will contain
 
-       // the playlist we were trying to load (but failed) and that should be
 
-       // excluded instead of the currently selected playlist which is likely
 
-       // out-of-date in this scenario
 
-       playlistToExclude = playlistToExclude || this.mainPlaylistLoader_.media();
 
-       playlistExclusionDuration = playlistExclusionDuration || error.playlistExclusionDuration || this.playlistExclusionDuration; // If there is no current playlist, then an error occurred while we were
 
-       // trying to load the main OR while we were disposing of the tech
 
-       if (!playlistToExclude) {
 
-         this.error = error;
 
-         if (this.mediaSource.readyState !== 'open') {
 
-           this.trigger('error');
 
-         } else {
 
-           this.sourceUpdater_.endOfStream('network');
 
-         }
 
-         return;
 
-       }
 
-       playlistToExclude.playlistErrors_++;
 
-       const playlists = this.mainPlaylistLoader_.main.playlists;
 
-       const enabledPlaylists = playlists.filter(isEnabled);
 
-       const isFinalRendition = enabledPlaylists.length === 1 && enabledPlaylists[0] === playlistToExclude; // Don't exclude the only playlist unless it was excluded
 
-       // forever
 
-       if (playlists.length === 1 && playlistExclusionDuration !== Infinity) {
 
-         videojs.log.warn(`Problem encountered with playlist ${playlistToExclude.id}. ` + 'Trying again since it is the only playlist.');
 
-         this.tech_.trigger('retryplaylist'); // if this is a final rendition, we should delay
 
-         return this.mainPlaylistLoader_.load(isFinalRendition);
 
-       }
 
-       if (isFinalRendition) {
 
-         // Since we're on the final non-excluded playlist, and we're about to exclude
 
-         // it, instead of erring the player or retrying this playlist, clear out the current
 
-         // exclusion list. This allows other playlists to be attempted in case any have been
 
-         // fixed.
 
-         let reincluded = false;
 
-         playlists.forEach(playlist => {
 
-           // skip current playlist which is about to be excluded
 
-           if (playlist === playlistToExclude) {
 
-             return;
 
-           }
 
-           const excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with.
 
-           if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) {
 
-             reincluded = true;
 
-             delete playlist.excludeUntil;
 
-           }
 
-         });
 
-         if (reincluded) {
 
-           videojs.log.warn('Removing other playlists from the exclusion list because the last ' + 'rendition is about to be excluded.'); // Technically we are retrying a playlist, in that we are simply retrying a previous
 
-           // playlist. This is needed for users relying on the retryplaylist event to catch a
 
-           // case where the player might be stuck and looping through "dead" playlists.
 
-           this.tech_.trigger('retryplaylist');
 
-         }
 
-       } // Exclude this playlist
 
-       let excludeUntil;
 
-       if (playlistToExclude.playlistErrors_ > this.maxPlaylistRetries) {
 
-         excludeUntil = Infinity;
 
-       } else {
 
-         excludeUntil = Date.now() + playlistExclusionDuration * 1000;
 
-       }
 
-       playlistToExclude.excludeUntil = excludeUntil;
 
-       if (error.reason) {
 
-         playlistToExclude.lastExcludeReason_ = error.reason;
 
-       }
 
-       this.tech_.trigger('excludeplaylist');
 
-       this.tech_.trigger({
 
-         type: 'usage',
 
-         name: 'vhs-rendition-excluded'
 
-       }); // TODO: only load a new playlist if we're excluding the current playlist
 
-       // If this function was called with a playlist that's not the current active playlist
 
-       // (e.g., media().id !== playlistToExclude.id),
 
-       // then a new playlist should not be selected and loaded, as there's nothing wrong with the current playlist.
 
-       const nextPlaylist = this.selectPlaylist();
 
-       if (!nextPlaylist) {
 
-         this.error = 'Playback cannot continue. No available working or supported playlists.';
 
-         this.trigger('error');
 
-         return;
 
-       }
 
-       const logFn = error.internal ? this.logger_ : videojs.log.warn;
 
-       const errorMessage = error.message ? ' ' + error.message : '';
 
-       logFn(`${error.internal ? 'Internal problem' : 'Problem'} encountered with playlist ${playlistToExclude.id}.` + `${errorMessage} Switching to playlist ${nextPlaylist.id}.`); // if audio group changed reset audio loaders
 
-       if (nextPlaylist.attributes.AUDIO !== playlistToExclude.attributes.AUDIO) {
 
-         this.delegateLoaders_('audio', ['abort', 'pause']);
 
-       } // if subtitle group changed reset subtitle loaders
 
-       if (nextPlaylist.attributes.SUBTITLES !== playlistToExclude.attributes.SUBTITLES) {
 
-         this.delegateLoaders_('subtitle', ['abort', 'pause']);
 
-       }
 
-       this.delegateLoaders_('main', ['abort', 'pause']);
 
-       const delayDuration = nextPlaylist.targetDuration / 2 * 1000 || 5 * 1000;
 
-       const shouldDelay = typeof nextPlaylist.lastRequest === 'number' && Date.now() - nextPlaylist.lastRequest <= delayDuration; // delay if it's a final rendition or if the last refresh is sooner than half targetDuration
 
-       return this.switchMedia_(nextPlaylist, 'exclude', isFinalRendition || shouldDelay);
 
-     }
 
-     /**
 
-      * Pause all segment/playlist loaders
 
-      */
 
-     pauseLoading() {
 
-       this.delegateLoaders_('all', ['abort', 'pause']);
 
-       this.stopABRTimer_();
 
-     }
 
-     /**
 
-      * Call a set of functions in order on playlist loaders, segment loaders,
 
-      * or both types of loaders.
 
-      *
 
-      * @param {string} filter
 
-      *        Filter loaders that should call fnNames using a string. Can be:
 
-      *        * all - run on all loaders
 
-      *        * audio - run on all audio loaders
 
-      *        * subtitle - run on all subtitle loaders
 
-      *        * main - run on the main loaders
 
-      *
 
-      * @param {Array|string} fnNames
 
-      *        A string or array of function names to call.
 
-      */
 
-     delegateLoaders_(filter, fnNames) {
 
-       const loaders = [];
 
-       const dontFilterPlaylist = filter === 'all';
 
-       if (dontFilterPlaylist || filter === 'main') {
 
-         loaders.push(this.mainPlaylistLoader_);
 
-       }
 
-       const mediaTypes = [];
 
-       if (dontFilterPlaylist || filter === 'audio') {
 
-         mediaTypes.push('AUDIO');
 
-       }
 
-       if (dontFilterPlaylist || filter === 'subtitle') {
 
-         mediaTypes.push('CLOSED-CAPTIONS');
 
-         mediaTypes.push('SUBTITLES');
 
-       }
 
-       mediaTypes.forEach(mediaType => {
 
-         const loader = this.mediaTypes_[mediaType] && this.mediaTypes_[mediaType].activePlaylistLoader;
 
-         if (loader) {
 
-           loaders.push(loader);
 
-         }
 
-       });
 
-       ['main', 'audio', 'subtitle'].forEach(name => {
 
-         const loader = this[`${name}SegmentLoader_`];
 
-         if (loader && (filter === name || filter === 'all')) {
 
-           loaders.push(loader);
 
-         }
 
-       });
 
-       loaders.forEach(loader => fnNames.forEach(fnName => {
 
-         if (typeof loader[fnName] === 'function') {
 
-           loader[fnName]();
 
-         }
 
-       }));
 
-     }
 
-     /**
 
-      * set the current time on all segment loaders
 
-      *
 
-      * @param {TimeRange} currentTime the current time to set
 
-      * @return {TimeRange} the current time
 
-      */
 
-     setCurrentTime(currentTime) {
 
-       const buffered = findRange(this.tech_.buffered(), currentTime);
 
-       if (!(this.mainPlaylistLoader_ && this.mainPlaylistLoader_.media())) {
 
-         // return immediately if the metadata is not ready yet
 
-         return 0;
 
-       } // it's clearly an edge-case but don't thrown an error if asked to
 
-       // seek within an empty playlist
 
-       if (!this.mainPlaylistLoader_.media().segments) {
 
-         return 0;
 
-       } // if the seek location is already buffered, continue buffering as usual
 
-       if (buffered && buffered.length) {
 
-         return currentTime;
 
-       } // cancel outstanding requests so we begin buffering at the new
 
-       // location
 
-       this.mainSegmentLoader_.resetEverything();
 
-       this.mainSegmentLoader_.abort();
 
-       if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
 
-         this.audioSegmentLoader_.resetEverything();
 
-         this.audioSegmentLoader_.abort();
 
-       }
 
-       if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
 
-         this.subtitleSegmentLoader_.resetEverything();
 
-         this.subtitleSegmentLoader_.abort();
 
-       } // start segment loader loading in case they are paused
 
-       this.load();
 
-     }
 
-     /**
 
-      * get the current duration
 
-      *
 
-      * @return {TimeRange} the duration
 
-      */
 
-     duration() {
 
-       if (!this.mainPlaylistLoader_) {
 
-         return 0;
 
-       }
 
-       const media = this.mainPlaylistLoader_.media();
 
-       if (!media) {
 
-         // no playlists loaded yet, so can't determine a duration
 
-         return 0;
 
-       } // Don't rely on the media source for duration in the case of a live playlist since
 
-       // setting the native MediaSource's duration to infinity ends up with consequences to
 
-       // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
 
-       //
 
-       // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
 
-       // however, few browsers have support for setLiveSeekableRange()
 
-       // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
 
-       //
 
-       // Until a time when the duration of the media source can be set to infinity, and a
 
-       // seekable range specified across browsers, just return Infinity.
 
-       if (!media.endList) {
 
-         return Infinity;
 
-       } // Since this is a VOD video, it is safe to rely on the media source's duration (if
 
-       // available). If it's not available, fall back to a playlist-calculated estimate.
 
-       if (this.mediaSource) {
 
-         return this.mediaSource.duration;
 
-       }
 
-       return Vhs$1.Playlist.duration(media);
 
-     }
 
-     /**
 
-      * check the seekable range
 
-      *
 
-      * @return {TimeRange} the seekable range
 
-      */
 
-     seekable() {
 
-       return this.seekable_;
 
-     }
 
-     onSyncInfoUpdate_() {
 
-       let audioSeekable; // TODO check for creation of both source buffers before updating seekable
 
-       //
 
-       // A fix was made to this function where a check for
 
-       // this.sourceUpdater_.hasCreatedSourceBuffers
 
-       // was added to ensure that both source buffers were created before seekable was
 
-       // updated. However, it originally had a bug where it was checking for a true and
 
-       // returning early instead of checking for false. Setting it to check for false to
 
-       // return early though created other issues. A call to play() would check for seekable
 
-       // end without verifying that a seekable range was present. In addition, even checking
 
-       // for that didn't solve some issues, as handleFirstPlay is sometimes worked around
 
-       // due to a media update calling load on the segment loaders, skipping a seek to live,
 
-       // thereby starting live streams at the beginning of the stream rather than at the end.
 
-       //
 
-       // This conditional should be fixed to wait for the creation of two source buffers at
 
-       // the same time as the other sections of code are fixed to properly seek to live and
 
-       // not throw an error due to checking for a seekable end when no seekable range exists.
 
-       //
 
-       // For now, fall back to the older behavior, with the understanding that the seekable
 
-       // range may not be completely correct, leading to a suboptimal initial live point.
 
-       if (!this.mainPlaylistLoader_) {
 
-         return;
 
-       }
 
-       let media = this.mainPlaylistLoader_.media();
 
-       if (!media) {
 
-         return;
 
-       }
 
-       let expired = this.syncController_.getExpiredTime(media, this.duration());
 
-       if (expired === null) {
 
-         // not enough information to update seekable
 
-         return;
 
-       }
 
-       const main = this.mainPlaylistLoader_.main;
 
-       const mainSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(main, media));
 
-       if (mainSeekable.length === 0) {
 
-         return;
 
-       }
 
-       if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
 
-         media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
 
-         expired = this.syncController_.getExpiredTime(media, this.duration());
 
-         if (expired === null) {
 
-           return;
 
-         }
 
-         audioSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(main, media));
 
-         if (audioSeekable.length === 0) {
 
-           return;
 
-         }
 
-       }
 
-       let oldEnd;
 
-       let oldStart;
 
-       if (this.seekable_ && this.seekable_.length) {
 
-         oldEnd = this.seekable_.end(0);
 
-         oldStart = this.seekable_.start(0);
 
-       }
 
-       if (!audioSeekable) {
 
-         // seekable has been calculated based on buffering video data so it
 
-         // can be returned directly
 
-         this.seekable_ = mainSeekable;
 
-       } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
 
-         // seekables are pretty far off, rely on main
 
-         this.seekable_ = mainSeekable;
 
-       } else {
 
-         this.seekable_ = 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)]]);
 
-       } // seekable is the same as last time
 
-       if (this.seekable_ && this.seekable_.length) {
 
-         if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
 
-           return;
 
-         }
 
-       }
 
-       this.logger_(`seekable updated [${printableRange(this.seekable_)}]`);
 
-       this.tech_.trigger('seekablechanged');
 
-     }
 
-     /**
 
-      * Update the player duration
 
-      */
 
-     updateDuration(isLive) {
 
-       if (this.updateDuration_) {
 
-         this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
 
-         this.updateDuration_ = null;
 
-       }
 
-       if (this.mediaSource.readyState !== 'open') {
 
-         this.updateDuration_ = this.updateDuration.bind(this, isLive);
 
-         this.mediaSource.addEventListener('sourceopen', this.updateDuration_);
 
-         return;
 
-       }
 
-       if (isLive) {
 
-         const seekable = this.seekable();
 
-         if (!seekable.length) {
 
-           return;
 
-         } // Even in the case of a live playlist, the native MediaSource's duration should not
 
-         // be set to Infinity (even though this would be expected for a live playlist), since
 
-         // setting the native MediaSource's duration to infinity ends up with consequences to
 
-         // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
 
-         //
 
-         // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
 
-         // however, few browsers have support for setLiveSeekableRange()
 
-         // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
 
-         //
 
-         // Until a time when the duration of the media source can be set to infinity, and a
 
-         // seekable range specified across browsers, the duration should be greater than or
 
-         // equal to the last possible seekable value.
 
-         // MediaSource duration starts as NaN
 
-         // It is possible (and probable) that this case will never be reached for many
 
-         // sources, since the MediaSource reports duration as the highest value without
 
-         // accounting for timestamp offset. For example, if the timestamp offset is -100 and
 
-         // we buffered times 0 to 100 with real times of 100 to 200, even though current
 
-         // time will be between 0 and 100, the native media source may report the duration
 
-         // as 200. However, since we report duration separate from the media source (as
 
-         // Infinity), and as long as the native media source duration value is greater than
 
-         // our reported seekable range, seeks will work as expected. The large number as
 
-         // duration for live is actually a strategy used by some players to work around the
 
-         // issue of live seekable ranges cited above.
 
-         if (isNaN(this.mediaSource.duration) || this.mediaSource.duration < seekable.end(seekable.length - 1)) {
 
-           this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1));
 
-         }
 
-         return;
 
-       }
 
-       const buffered = this.tech_.buffered();
 
-       let duration = Vhs$1.Playlist.duration(this.mainPlaylistLoader_.media());
 
-       if (buffered.length > 0) {
 
-         duration = Math.max(duration, buffered.end(buffered.length - 1));
 
-       }
 
-       if (this.mediaSource.duration !== duration) {
 
-         this.sourceUpdater_.setDuration(duration);
 
-       }
 
-     }
 
-     /**
 
-      * dispose of the PlaylistController and everything
 
-      * that it controls
 
-      */
 
-     dispose() {
 
-       this.trigger('dispose');
 
-       this.decrypter_.terminate();
 
-       this.mainPlaylistLoader_.dispose();
 
-       this.mainSegmentLoader_.dispose();
 
-       if (this.loadOnPlay_) {
 
-         this.tech_.off('play', this.loadOnPlay_);
 
-       }
 
-       ['AUDIO', 'SUBTITLES'].forEach(type => {
 
-         const groups = this.mediaTypes_[type].groups;
 
-         for (const id in groups) {
 
-           groups[id].forEach(group => {
 
-             if (group.playlistLoader) {
 
-               group.playlistLoader.dispose();
 
-             }
 
-           });
 
-         }
 
-       });
 
-       this.audioSegmentLoader_.dispose();
 
-       this.subtitleSegmentLoader_.dispose();
 
-       this.sourceUpdater_.dispose();
 
-       this.timelineChangeController_.dispose();
 
-       this.stopABRTimer_();
 
-       if (this.updateDuration_) {
 
-         this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
 
-       }
 
-       this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
 
-       this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_);
 
-       this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_);
 
-       this.off();
 
-     }
 
-     /**
 
-      * return the main playlist object if we have one
 
-      *
 
-      * @return {Object} the main playlist object that we parsed
 
-      */
 
-     main() {
 
-       return this.mainPlaylistLoader_.main;
 
-     }
 
-     /**
 
-      * return the currently selected playlist
 
-      *
 
-      * @return {Object} the currently selected playlist object that we parsed
 
-      */
 
-     media() {
 
-       // playlist loader will not return media if it has not been fully loaded
 
-       return this.mainPlaylistLoader_.media() || this.initialMedia_;
 
-     }
 
-     areMediaTypesKnown_() {
 
-       const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
 
-       const hasMainMediaInfo = !!this.mainSegmentLoader_.getCurrentMediaInfo_(); // if we are not using an audio loader, then we have audio media info
 
-       // otherwise check on the segment loader.
 
-       const hasAudioMediaInfo = !usingAudioLoader ? true : !!this.audioSegmentLoader_.getCurrentMediaInfo_(); // one or both loaders has not loaded sufficently to get codecs
 
-       if (!hasMainMediaInfo || !hasAudioMediaInfo) {
 
-         return false;
 
-       }
 
-       return true;
 
-     }
 
-     getCodecsOrExclude_() {
 
-       const media = {
 
-         main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {},
 
-         audio: this.audioSegmentLoader_.getCurrentMediaInfo_() || {}
 
-       };
 
-       const playlist = this.mainSegmentLoader_.getPendingSegmentPlaylist() || this.media(); // set "main" media equal to video
 
-       media.video = media.main;
 
-       const playlistCodecs = codecsForPlaylist(this.main(), playlist);
 
-       const codecs = {};
 
-       const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
 
-       if (media.main.hasVideo) {
 
-         codecs.video = playlistCodecs.video || media.main.videoCodec || DEFAULT_VIDEO_CODEC;
 
-       }
 
-       if (media.main.isMuxed) {
 
-         codecs.video += `,${playlistCodecs.audio || media.main.audioCodec || DEFAULT_AUDIO_CODEC}`;
 
-       }
 
-       if (media.main.hasAudio && !media.main.isMuxed || media.audio.hasAudio || usingAudioLoader) {
 
-         codecs.audio = playlistCodecs.audio || media.main.audioCodec || media.audio.audioCodec || DEFAULT_AUDIO_CODEC; // set audio isFmp4 so we use the correct "supports" function below
 
-         media.audio.isFmp4 = media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4;
 
-       } // no codecs, no playback.
 
-       if (!codecs.audio && !codecs.video) {
 
-         this.excludePlaylist({
 
-           playlistToExclude: playlist,
 
-           error: {
 
-             message: 'Could not determine codecs for playlist.'
 
-           },
 
-           playlistExclusionDuration: Infinity
 
-         });
 
-         return;
 
-       } // fmp4 relies on browser support, while ts relies on muxer support
 
-       const supportFunction = (isFmp4, codec) => isFmp4 ? browserSupportsCodec(codec) : muxerSupportsCodec(codec);
 
-       const unsupportedCodecs = {};
 
-       let unsupportedAudio;
 
-       ['video', 'audio'].forEach(function (type) {
 
-         if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) {
 
-           const supporter = media[type].isFmp4 ? 'browser' : 'muxer';
 
-           unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || [];
 
-           unsupportedCodecs[supporter].push(codecs[type]);
 
-           if (type === 'audio') {
 
-             unsupportedAudio = supporter;
 
-           }
 
-         }
 
-       });
 
-       if (usingAudioLoader && unsupportedAudio && playlist.attributes.AUDIO) {
 
-         const audioGroup = playlist.attributes.AUDIO;
 
-         this.main().playlists.forEach(variant => {
 
-           const variantAudioGroup = variant.attributes && variant.attributes.AUDIO;
 
-           if (variantAudioGroup === audioGroup && variant !== playlist) {
 
-             variant.excludeUntil = Infinity;
 
-           }
 
-         });
 
-         this.logger_(`excluding audio group ${audioGroup} as ${unsupportedAudio} does not support codec(s): "${codecs.audio}"`);
 
-       } // if we have any unsupported codecs exclude this playlist.
 
-       if (Object.keys(unsupportedCodecs).length) {
 
-         const message = Object.keys(unsupportedCodecs).reduce((acc, supporter) => {
 
-           if (acc) {
 
-             acc += ', ';
 
-           }
 
-           acc += `${supporter} does not support codec(s): "${unsupportedCodecs[supporter].join(',')}"`;
 
-           return acc;
 
-         }, '') + '.';
 
-         this.excludePlaylist({
 
-           playlistToExclude: playlist,
 
-           error: {
 
-             internal: true,
 
-             message
 
-           },
 
-           playlistExclusionDuration: Infinity
 
-         });
 
-         return;
 
-       } // check if codec switching is happening
 
-       if (this.sourceUpdater_.hasCreatedSourceBuffers() && !this.sourceUpdater_.canChangeType()) {
 
-         const switchMessages = [];
 
-         ['video', 'audio'].forEach(type => {
 
-           const newCodec = (parseCodecs(this.sourceUpdater_.codecs[type] || '')[0] || {}).type;
 
-           const oldCodec = (parseCodecs(codecs[type] || '')[0] || {}).type;
 
-           if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) {
 
-             switchMessages.push(`"${this.sourceUpdater_.codecs[type]}" -> "${codecs[type]}"`);
 
-           }
 
-         });
 
-         if (switchMessages.length) {
 
-           this.excludePlaylist({
 
-             playlistToExclude: playlist,
 
-             error: {
 
-               message: `Codec switching not supported: ${switchMessages.join(', ')}.`,
 
-               internal: true
 
-             },
 
-             playlistExclusionDuration: Infinity
 
-           });
 
-           return;
 
-         }
 
-       } // TODO: when using the muxer shouldn't we just return
 
-       // the codecs that the muxer outputs?
 
-       return codecs;
 
-     }
 
-     /**
 
-      * Create source buffers and exlude any incompatible renditions.
 
-      *
 
-      * @private
 
-      */
 
-     tryToCreateSourceBuffers_() {
 
-       // media source is not ready yet or sourceBuffers are already
 
-       // created.
 
-       if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.hasCreatedSourceBuffers()) {
 
-         return;
 
-       }
 
-       if (!this.areMediaTypesKnown_()) {
 
-         return;
 
-       }
 
-       const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
 
-       if (!codecs) {
 
-         return;
 
-       }
 
-       this.sourceUpdater_.createSourceBuffers(codecs);
 
-       const codecString = [codecs.video, codecs.audio].filter(Boolean).join(',');
 
-       this.excludeIncompatibleVariants_(codecString);
 
-     }
 
-     /**
 
-      * Excludes playlists with codecs that are unsupported by the muxer and browser.
 
-      */
 
-     excludeUnsupportedVariants_() {
 
-       const playlists = this.main().playlists;
 
-       const ids = []; // TODO: why don't we have a property to loop through all
 
-       // playlist? Why did we ever mix indexes and keys?
 
-       Object.keys(playlists).forEach(key => {
 
-         const variant = playlists[key]; // check if we already processed this playlist.
 
-         if (ids.indexOf(variant.id) !== -1) {
 
-           return;
 
-         }
 
-         ids.push(variant.id);
 
-         const codecs = codecsForPlaylist(this.main, variant);
 
-         const unsupported = [];
 
-         if (codecs.audio && !muxerSupportsCodec(codecs.audio) && !browserSupportsCodec(codecs.audio)) {
 
-           unsupported.push(`audio codec ${codecs.audio}`);
 
-         }
 
-         if (codecs.video && !muxerSupportsCodec(codecs.video) && !browserSupportsCodec(codecs.video)) {
 
-           unsupported.push(`video codec ${codecs.video}`);
 
-         }
 
-         if (codecs.text && codecs.text === 'stpp.ttml.im1t') {
 
-           unsupported.push(`text codec ${codecs.text}`);
 
-         }
 
-         if (unsupported.length) {
 
-           variant.excludeUntil = Infinity;
 
-           this.logger_(`excluding ${variant.id} for unsupported: ${unsupported.join(', ')}`);
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * Exclude playlists that are known to be codec or
 
-      * stream-incompatible with the SourceBuffer configuration. For
 
-      * instance, Media Source Extensions would cause the video element to
 
-      * stall waiting for video data if you switched from a variant with
 
-      * video and audio to an audio-only one.
 
-      *
 
-      * @param {Object} media a media playlist compatible with the current
 
-      * set of SourceBuffers. Variants in the current main playlist that
 
-      * do not appear to have compatible codec or stream configurations
 
-      * will be excluded from the default playlist selection algorithm
 
-      * indefinitely.
 
-      * @private
 
-      */
 
-     excludeIncompatibleVariants_(codecString) {
 
-       const ids = [];
 
-       const playlists = this.main().playlists;
 
-       const codecs = unwrapCodecList(parseCodecs(codecString));
 
-       const codecCount_ = codecCount(codecs);
 
-       const videoDetails = codecs.video && parseCodecs(codecs.video)[0] || null;
 
-       const audioDetails = codecs.audio && parseCodecs(codecs.audio)[0] || null;
 
-       Object.keys(playlists).forEach(key => {
 
-         const variant = playlists[key]; // check if we already processed this playlist.
 
-         // or it if it is already excluded forever.
 
-         if (ids.indexOf(variant.id) !== -1 || variant.excludeUntil === Infinity) {
 
-           return;
 
-         }
 
-         ids.push(variant.id);
 
-         const exclusionReasons = []; // get codecs from the playlist for this variant
 
-         const variantCodecs = codecsForPlaylist(this.mainPlaylistLoader_.main, variant);
 
-         const variantCodecCount = codecCount(variantCodecs); // if no codecs are listed, we cannot determine that this
 
-         // variant is incompatible. Wait for mux.js to probe
 
-         if (!variantCodecs.audio && !variantCodecs.video) {
 
-           return;
 
-         } // TODO: we can support this by removing the
 
-         // old media source and creating a new one, but it will take some work.
 
-         // The number of streams cannot change
 
-         if (variantCodecCount !== codecCount_) {
 
-           exclusionReasons.push(`codec count "${variantCodecCount}" !== "${codecCount_}"`);
 
-         } // only exclude playlists by codec change, if codecs cannot switch
 
-         // during playback.
 
-         if (!this.sourceUpdater_.canChangeType()) {
 
-           const variantVideoDetails = variantCodecs.video && parseCodecs(variantCodecs.video)[0] || null;
 
-           const variantAudioDetails = variantCodecs.audio && parseCodecs(variantCodecs.audio)[0] || null; // the video codec cannot change
 
-           if (variantVideoDetails && videoDetails && variantVideoDetails.type.toLowerCase() !== videoDetails.type.toLowerCase()) {
 
-             exclusionReasons.push(`video codec "${variantVideoDetails.type}" !== "${videoDetails.type}"`);
 
-           } // the audio codec cannot change
 
-           if (variantAudioDetails && audioDetails && variantAudioDetails.type.toLowerCase() !== audioDetails.type.toLowerCase()) {
 
-             exclusionReasons.push(`audio codec "${variantAudioDetails.type}" !== "${audioDetails.type}"`);
 
-           }
 
-         }
 
-         if (exclusionReasons.length) {
 
-           variant.excludeUntil = Infinity;
 
-           this.logger_(`excluding ${variant.id}: ${exclusionReasons.join(' && ')}`);
 
-         }
 
-       });
 
-     }
 
-     updateAdCues_(media) {
 
-       let offset = 0;
 
-       const seekable = this.seekable();
 
-       if (seekable.length) {
 
-         offset = seekable.start(0);
 
-       }
 
-       updateAdCues(media, this.cueTagsTrack_, offset);
 
-     }
 
-     /**
 
-      * Calculates the desired forward buffer length based on current time
 
-      *
 
-      * @return {number} Desired forward buffer length in seconds
 
-      */
 
-     goalBufferLength() {
 
-       const currentTime = this.tech_.currentTime();
 
-       const initial = Config.GOAL_BUFFER_LENGTH;
 
-       const rate = Config.GOAL_BUFFER_LENGTH_RATE;
 
-       const max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
 
-       return Math.min(initial + currentTime * rate, max);
 
-     }
 
-     /**
 
-      * Calculates the desired buffer low water line based on current time
 
-      *
 
-      * @return {number} Desired buffer low water line in seconds
 
-      */
 
-     bufferLowWaterLine() {
 
-       const currentTime = this.tech_.currentTime();
 
-       const initial = Config.BUFFER_LOW_WATER_LINE;
 
-       const rate = Config.BUFFER_LOW_WATER_LINE_RATE;
 
-       const max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
 
-       const newMax = Math.max(initial, Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE);
 
-       return Math.min(initial + currentTime * rate, this.bufferBasedABR ? newMax : max);
 
-     }
 
-     bufferHighWaterLine() {
 
-       return Config.BUFFER_HIGH_WATER_LINE;
 
-     }
 
-   }
 
-   /**
 
-    * Returns a function that acts as the Enable/disable playlist function.
 
-    *
 
-    * @param {PlaylistLoader} loader - The main playlist loader
 
-    * @param {string} playlistID - id of the playlist
 
-    * @param {Function} changePlaylistFn - A function to be called after a
 
-    * playlist's enabled-state has been changed. Will NOT be called if a
 
-    * playlist's enabled-state is unchanged
 
-    * @param {boolean=} enable - Value to set the playlist enabled-state to
 
-    * or if undefined returns the current enabled-state for the playlist
 
-    * @return {Function} Function for setting/getting enabled
 
-    */
 
-   const enableFunction = (loader, playlistID, changePlaylistFn) => enable => {
 
-     const playlist = loader.main.playlists[playlistID];
 
-     const incompatible = isIncompatible(playlist);
 
-     const currentlyEnabled = isEnabled(playlist);
 
-     if (typeof enable === 'undefined') {
 
-       return currentlyEnabled;
 
-     }
 
-     if (enable) {
 
-       delete playlist.disabled;
 
-     } else {
 
-       playlist.disabled = true;
 
-     }
 
-     if (enable !== currentlyEnabled && !incompatible) {
 
-       // Ensure the outside world knows about our changes
 
-       changePlaylistFn();
 
-       if (enable) {
 
-         loader.trigger('renditionenabled');
 
-       } else {
 
-         loader.trigger('renditiondisabled');
 
-       }
 
-     }
 
-     return enable;
 
-   };
 
-   /**
 
-    * The representation object encapsulates the publicly visible information
 
-    * in a media playlist along with a setter/getter-type function (enabled)
 
-    * for changing the enabled-state of a particular playlist entry
 
-    *
 
-    * @class Representation
 
-    */
 
-   class Representation {
 
-     constructor(vhsHandler, playlist, id) {
 
-       const {
 
-         playlistController_: pc
 
-       } = vhsHandler;
 
-       const qualityChangeFunction = pc.fastQualityChange_.bind(pc); // some playlist attributes are optional
 
-       if (playlist.attributes) {
 
-         const resolution = playlist.attributes.RESOLUTION;
 
-         this.width = resolution && resolution.width;
 
-         this.height = resolution && resolution.height;
 
-         this.bandwidth = playlist.attributes.BANDWIDTH;
 
-         this.frameRate = playlist.attributes['FRAME-RATE'];
 
-       }
 
-       this.codecs = codecsForPlaylist(pc.main(), playlist);
 
-       this.playlist = playlist; // The id is simply the ordinality of the media playlist
 
-       // within the main playlist
 
-       this.id = id; // Partially-apply the enableFunction to create a playlist-
 
-       // specific variant
 
-       this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction);
 
-     }
 
-   }
 
-   /**
 
-    * A mixin function that adds the `representations` api to an instance
 
-    * of the VhsHandler class
 
-    *
 
-    * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the
 
-    * representation API into
 
-    */
 
-   const renditionSelectionMixin = function (vhsHandler) {
 
-     // Add a single API-specific function to the VhsHandler instance
 
-     vhsHandler.representations = () => {
 
-       const main = vhsHandler.playlistController_.main();
 
-       const playlists = isAudioOnly(main) ? vhsHandler.playlistController_.getAudioTrackPlaylists_() : main.playlists;
 
-       if (!playlists) {
 
-         return [];
 
-       }
 
-       return playlists.filter(media => !isIncompatible(media)).map((e, i) => new Representation(vhsHandler, e, e.id));
 
-     };
 
-   };
 
-   /**
 
-    * @file playback-watcher.js
 
-    *
 
-    * Playback starts, and now my watch begins. It shall not end until my death. I shall
 
-    * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
 
-    * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
 
-    * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
 
-    * my life and honor to the Playback Watch, for this Player and all the Players to come.
 
-    */
 
-   const timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
 
-   /**
 
-    * @class PlaybackWatcher
 
-    */
 
-   class PlaybackWatcher {
 
-     /**
 
-      * Represents an PlaybackWatcher object.
 
-      *
 
-      * @class
 
-      * @param {Object} options an object that includes the tech and settings
 
-      */
 
-     constructor(options) {
 
-       this.playlistController_ = options.playlistController;
 
-       this.tech_ = options.tech;
 
-       this.seekable = options.seekable;
 
-       this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
 
-       this.liveRangeSafeTimeDelta = options.liveRangeSafeTimeDelta;
 
-       this.media = options.media;
 
-       this.consecutiveUpdates = 0;
 
-       this.lastRecordedTime = null;
 
-       this.checkCurrentTimeTimeout_ = null;
 
-       this.logger_ = logger('PlaybackWatcher');
 
-       this.logger_('initialize');
 
-       const playHandler = () => this.monitorCurrentTime_();
 
-       const canPlayHandler = () => this.monitorCurrentTime_();
 
-       const waitingHandler = () => this.techWaiting_();
 
-       const cancelTimerHandler = () => this.resetTimeUpdate_();
 
-       const pc = this.playlistController_;
 
-       const loaderTypes = ['main', 'subtitle', 'audio'];
 
-       const loaderChecks = {};
 
-       loaderTypes.forEach(type => {
 
-         loaderChecks[type] = {
 
-           reset: () => this.resetSegmentDownloads_(type),
 
-           updateend: () => this.checkSegmentDownloads_(type)
 
-         };
 
-         pc[`${type}SegmentLoader_`].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer
 
-         // isn't changing we want to reset. We cannot assume that the new rendition
 
-         // will also be stalled, until after new appends.
 
-         pc[`${type}SegmentLoader_`].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking.
 
-         // This prevents one segment playlists (single vtt or single segment content)
 
-         // from being detected as stalling. As the buffer will not change in those cases, since
 
-         // the buffer is the entire video duration.
 
-         this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset);
 
-       });
 
-       /**
 
-        * We check if a seek was into a gap through the following steps:
 
-        * 1. We get a seeking event and we do not get a seeked event. This means that
 
-        *    a seek was attempted but not completed.
 
-        * 2. We run `fixesBadSeeks_` on segment loader appends. This means that we already
 
-        *    removed everything from our buffer and appended a segment, and should be ready
 
-        *    to check for gaps.
 
-        */
 
-       const setSeekingHandlers = fn => {
 
-         ['main', 'audio'].forEach(type => {
 
-           pc[`${type}SegmentLoader_`][fn]('appended', this.seekingAppendCheck_);
 
-         });
 
-       };
 
-       this.seekingAppendCheck_ = () => {
 
-         if (this.fixesBadSeeks_()) {
 
-           this.consecutiveUpdates = 0;
 
-           this.lastRecordedTime = this.tech_.currentTime();
 
-           setSeekingHandlers('off');
 
-         }
 
-       };
 
-       this.clearSeekingAppendCheck_ = () => setSeekingHandlers('off');
 
-       this.watchForBadSeeking_ = () => {
 
-         this.clearSeekingAppendCheck_();
 
-         setSeekingHandlers('on');
 
-       };
 
-       this.tech_.on('seeked', this.clearSeekingAppendCheck_);
 
-       this.tech_.on('seeking', this.watchForBadSeeking_);
 
-       this.tech_.on('waiting', waitingHandler);
 
-       this.tech_.on(timerCancelEvents, cancelTimerHandler);
 
-       this.tech_.on('canplay', canPlayHandler);
 
-       /*
 
-         An edge case exists that results in gaps not being skipped when they exist at the beginning of a stream. This case
 
-         is surfaced in one of two ways:
 
-          1)  The `waiting` event is fired before the player has buffered content, making it impossible
 
-             to find or skip the gap. The `waiting` event is followed by a `play` event. On first play
 
-             we can check if playback is stalled due to a gap, and skip the gap if necessary.
 
-         2)  A source with a gap at the beginning of the stream is loaded programatically while the player
 
-             is in a playing state. To catch this case, it's important that our one-time play listener is setup
 
-             even if the player is in a playing state
 
-       */
 
-       this.tech_.one('play', playHandler); // Define the dispose function to clean up our events
 
-       this.dispose = () => {
 
-         this.clearSeekingAppendCheck_();
 
-         this.logger_('dispose');
 
-         this.tech_.off('waiting', waitingHandler);
 
-         this.tech_.off(timerCancelEvents, cancelTimerHandler);
 
-         this.tech_.off('canplay', canPlayHandler);
 
-         this.tech_.off('play', playHandler);
 
-         this.tech_.off('seeking', this.watchForBadSeeking_);
 
-         this.tech_.off('seeked', this.clearSeekingAppendCheck_);
 
-         loaderTypes.forEach(type => {
 
-           pc[`${type}SegmentLoader_`].off('appendsdone', loaderChecks[type].updateend);
 
-           pc[`${type}SegmentLoader_`].off('playlistupdate', loaderChecks[type].reset);
 
-           this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset);
 
-         });
 
-         if (this.checkCurrentTimeTimeout_) {
 
-           window.clearTimeout(this.checkCurrentTimeTimeout_);
 
-         }
 
-         this.resetTimeUpdate_();
 
-       };
 
-     }
 
-     /**
 
-      * Periodically check current time to see if playback stopped
 
-      *
 
-      * @private
 
-      */
 
-     monitorCurrentTime_() {
 
-       this.checkCurrentTime_();
 
-       if (this.checkCurrentTimeTimeout_) {
 
-         window.clearTimeout(this.checkCurrentTimeTimeout_);
 
-       } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
 
-       this.checkCurrentTimeTimeout_ = window.setTimeout(this.monitorCurrentTime_.bind(this), 250);
 
-     }
 
-     /**
 
-      * Reset stalled download stats for a specific type of loader
 
-      *
 
-      * @param {string} type
 
-      *        The segment loader type to check.
 
-      *
 
-      * @listens SegmentLoader#playlistupdate
 
-      * @listens Tech#seeking
 
-      * @listens Tech#seeked
 
-      */
 
-     resetSegmentDownloads_(type) {
 
-       const loader = this.playlistController_[`${type}SegmentLoader_`];
 
-       if (this[`${type}StalledDownloads_`] > 0) {
 
-         this.logger_(`resetting possible stalled download count for ${type} loader`);
 
-       }
 
-       this[`${type}StalledDownloads_`] = 0;
 
-       this[`${type}Buffered_`] = loader.buffered_();
 
-     }
 
-     /**
 
-      * Checks on every segment `appendsdone` to see
 
-      * if segment appends are making progress. If they are not
 
-      * and we are still downloading bytes. We exclude the playlist.
 
-      *
 
-      * @param {string} type
 
-      *        The segment loader type to check.
 
-      *
 
-      * @listens SegmentLoader#appendsdone
 
-      */
 
-     checkSegmentDownloads_(type) {
 
-       const pc = this.playlistController_;
 
-       const loader = pc[`${type}SegmentLoader_`];
 
-       const buffered = loader.buffered_();
 
-       const isBufferedDifferent = isRangeDifferent(this[`${type}Buffered_`], buffered);
 
-       this[`${type}Buffered_`] = buffered; // if another watcher is going to fix the issue or
 
-       // the buffered value for this loader changed
 
-       // appends are working
 
-       if (isBufferedDifferent) {
 
-         this.resetSegmentDownloads_(type);
 
-         return;
 
-       }
 
-       this[`${type}StalledDownloads_`]++;
 
-       this.logger_(`found #${this[`${type}StalledDownloads_`]} ${type} appends that did not increase buffer (possible stalled download)`, {
 
-         playlistId: loader.playlist_ && loader.playlist_.id,
 
-         buffered: timeRangesToArray(buffered)
 
-       }); // after 10 possibly stalled appends with no reset, exclude
 
-       if (this[`${type}StalledDownloads_`] < 10) {
 
-         return;
 
-       }
 
-       this.logger_(`${type} loader stalled download exclusion`);
 
-       this.resetSegmentDownloads_(type);
 
-       this.tech_.trigger({
 
-         type: 'usage',
 
-         name: `vhs-${type}-download-exclusion`
 
-       });
 
-       if (type === 'subtitle') {
 
-         return;
 
-       } // TODO: should we exclude audio tracks rather than main tracks
 
-       // when type is audio?
 
-       pc.excludePlaylist({
 
-         error: {
 
-           message: `Excessive ${type} segment downloading detected.`
 
-         },
 
-         playlistExclusionDuration: Infinity
 
-       });
 
-     }
 
-     /**
 
-      * The purpose of this function is to emulate the "waiting" event on
 
-      * browsers that do not emit it when they are waiting for more
 
-      * data to continue playback
 
-      *
 
-      * @private
 
-      */
 
-     checkCurrentTime_() {
 
-       if (this.tech_.paused() || this.tech_.seeking()) {
 
-         return;
 
-       }
 
-       const currentTime = this.tech_.currentTime();
 
-       const buffered = this.tech_.buffered();
 
-       if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
 
-         // If current time is at the end of the final buffered region, then any playback
 
-         // stall is most likely caused by buffering in a low bandwidth environment. The tech
 
-         // should fire a `waiting` event in this scenario, but due to browser and tech
 
-         // inconsistencies. Calling `techWaiting_` here allows us to simulate
 
-         // responding to a native `waiting` event when the tech fails to emit one.
 
-         return this.techWaiting_();
 
-       }
 
-       if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
 
-         this.consecutiveUpdates++;
 
-         this.waiting_();
 
-       } else if (currentTime === this.lastRecordedTime) {
 
-         this.consecutiveUpdates++;
 
-       } else {
 
-         this.consecutiveUpdates = 0;
 
-         this.lastRecordedTime = currentTime;
 
-       }
 
-     }
 
-     /**
 
-      * Resets the 'timeupdate' mechanism designed to detect that we are stalled
 
-      *
 
-      * @private
 
-      */
 
-     resetTimeUpdate_() {
 
-       this.consecutiveUpdates = 0;
 
-     }
 
-     /**
 
-      * Fixes situations where there's a bad seek
 
-      *
 
-      * @return {boolean} whether an action was taken to fix the seek
 
-      * @private
 
-      */
 
-     fixesBadSeeks_() {
 
-       const seeking = this.tech_.seeking();
 
-       if (!seeking) {
 
-         return false;
 
-       } // TODO: It's possible that these seekable checks should be moved out of this function
 
-       // and into a function that runs on seekablechange. It's also possible that we only need
 
-       // afterSeekableWindow as the buffered check at the bottom is good enough to handle before
 
-       // seekable range.
 
-       const seekable = this.seekable();
 
-       const currentTime = this.tech_.currentTime();
 
-       const isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
 
-       let seekTo;
 
-       if (isAfterSeekableRange) {
 
-         const seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
 
-         seekTo = seekableEnd;
 
-       }
 
-       if (this.beforeSeekableWindow_(seekable, currentTime)) {
 
-         const seekableStart = seekable.start(0); // sync to the beginning of the live window
 
-         // provide a buffer of .1 seconds to handle rounding/imprecise numbers
 
-         seekTo = seekableStart + (
 
-         // if the playlist is too short and the seekable range is an exact time (can
 
-         // happen in live with a 3 segment playlist), then don't use a time delta
 
-         seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA);
 
-       }
 
-       if (typeof seekTo !== 'undefined') {
 
-         this.logger_(`Trying to seek outside of seekable at time ${currentTime} with ` + `seekable range ${printableRange(seekable)}. Seeking to ` + `${seekTo}.`);
 
-         this.tech_.setCurrentTime(seekTo);
 
-         return true;
 
-       }
 
-       const sourceUpdater = this.playlistController_.sourceUpdater_;
 
-       const buffered = this.tech_.buffered();
 
-       const audioBuffered = sourceUpdater.audioBuffer ? sourceUpdater.audioBuffered() : null;
 
-       const videoBuffered = sourceUpdater.videoBuffer ? sourceUpdater.videoBuffered() : null;
 
-       const media = this.media(); // verify that at least two segment durations or one part duration have been
 
-       // appended before checking for a gap.
 
-       const minAppendedDuration = media.partTargetDuration ? media.partTargetDuration : (media.targetDuration - TIME_FUDGE_FACTOR) * 2; // verify that at least two segment durations have been
 
-       // appended before checking for a gap.
 
-       const bufferedToCheck = [audioBuffered, videoBuffered];
 
-       for (let i = 0; i < bufferedToCheck.length; i++) {
 
-         // skip null buffered
 
-         if (!bufferedToCheck[i]) {
 
-           continue;
 
-         }
 
-         const timeAhead = timeAheadOf(bufferedToCheck[i], currentTime); // if we are less than two video/audio segment durations or one part
 
-         // duration behind we haven't appended enough to call this a bad seek.
 
-         if (timeAhead < minAppendedDuration) {
 
-           return false;
 
-         }
 
-       }
 
-       const nextRange = findNextRange(buffered, currentTime); // we have appended enough content, but we don't have anything buffered
 
-       // to seek over the gap
 
-       if (nextRange.length === 0) {
 
-         return false;
 
-       }
 
-       seekTo = nextRange.start(0) + SAFE_TIME_DELTA;
 
-       this.logger_(`Buffered region starts (${nextRange.start(0)}) ` + ` just beyond seek point (${currentTime}). Seeking to ${seekTo}.`);
 
-       this.tech_.setCurrentTime(seekTo);
 
-       return true;
 
-     }
 
-     /**
 
-      * Handler for situations when we determine the player is waiting.
 
-      *
 
-      * @private
 
-      */
 
-     waiting_() {
 
-       if (this.techWaiting_()) {
 
-         return;
 
-       } // All tech waiting checks failed. Use last resort correction
 
-       const currentTime = this.tech_.currentTime();
 
-       const buffered = this.tech_.buffered();
 
-       const currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
 
-       // region with no indication that anything is amiss (seen in Firefox). Seeking to
 
-       // currentTime is usually enough to kickstart the player. This checks that the player
 
-       // is currently within a buffered region before attempting a corrective seek.
 
-       // Chrome does not appear to continue `timeupdate` events after a `waiting` event
 
-       // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
 
-       // make sure there is ~3 seconds of forward buffer before taking any corrective action
 
-       // to avoid triggering an `unknownwaiting` event when the network is slow.
 
-       if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
 
-         this.resetTimeUpdate_();
 
-         this.tech_.setCurrentTime(currentTime);
 
-         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
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-unknown-waiting'
 
-         });
 
-         return;
 
-       }
 
-     }
 
-     /**
 
-      * Handler for situations when the tech fires a `waiting` event
 
-      *
 
-      * @return {boolean}
 
-      *         True if an action (or none) was needed to correct the waiting. False if no
 
-      *         checks passed
 
-      * @private
 
-      */
 
-     techWaiting_() {
 
-       const seekable = this.seekable();
 
-       const currentTime = this.tech_.currentTime();
 
-       if (this.tech_.seeking()) {
 
-         // Tech is seeking or already waiting on another action, no action needed
 
-         return true;
 
-       }
 
-       if (this.beforeSeekableWindow_(seekable, currentTime)) {
 
-         const livePoint = seekable.end(seekable.length - 1);
 
-         this.logger_(`Fell out of live window at time ${currentTime}. Seeking to ` + `live point (seekable end) ${livePoint}`);
 
-         this.resetTimeUpdate_();
 
-         this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-live-resync'
 
-         });
 
-         return true;
 
-       }
 
-       const sourceUpdater = this.tech_.vhs.playlistController_.sourceUpdater_;
 
-       const buffered = this.tech_.buffered();
 
-       const videoUnderflow = this.videoUnderflow_({
 
-         audioBuffered: sourceUpdater.audioBuffered(),
 
-         videoBuffered: sourceUpdater.videoBuffered(),
 
-         currentTime
 
-       });
 
-       if (videoUnderflow) {
 
-         // Even though the video underflowed and was stuck in a gap, the audio overplayed
 
-         // the gap, leading currentTime into a buffered range. Seeking to currentTime
 
-         // allows the video to catch up to the audio position without losing any audio
 
-         // (only suffering ~3 seconds of frozen video and a pause in audio playback).
 
-         this.resetTimeUpdate_();
 
-         this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS
 
-         this.tech_.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-video-underflow'
 
-         });
 
-         return true;
 
-       }
 
-       const nextRange = findNextRange(buffered, currentTime); // check for gap
 
-       if (nextRange.length > 0) {
 
-         this.logger_(`Stopped at ${currentTime} and seeking to ${nextRange.start(0)}`);
 
-         this.resetTimeUpdate_();
 
-         this.skipTheGap_(currentTime);
 
-         return true;
 
-       } // All checks failed. Returning false to indicate failure to correct waiting
 
-       return false;
 
-     }
 
-     afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow = false) {
 
-       if (!seekable.length) {
 
-         // we can't make a solid case if there's no seekable, default to false
 
-         return false;
 
-       }
 
-       let allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
 
-       const isLive = !playlist.endList;
 
-       if (isLive && allowSeeksWithinUnsafeLiveWindow) {
 
-         allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
 
-       }
 
-       if (currentTime > allowedEnd) {
 
-         return true;
 
-       }
 
-       return false;
 
-     }
 
-     beforeSeekableWindow_(seekable, currentTime) {
 
-       if (seekable.length &&
 
-       // can't fall before 0 and 0 seekable start identifies VOD stream
 
-       seekable.start(0) > 0 && currentTime < seekable.start(0) - this.liveRangeSafeTimeDelta) {
 
-         return true;
 
-       }
 
-       return false;
 
-     }
 
-     videoUnderflow_({
 
-       videoBuffered,
 
-       audioBuffered,
 
-       currentTime
 
-     }) {
 
-       // audio only content will not have video underflow :)
 
-       if (!videoBuffered) {
 
-         return;
 
-       }
 
-       let gap; // find a gap in demuxed content.
 
-       if (videoBuffered.length && audioBuffered.length) {
 
-         // in Chrome audio will continue to play for ~3s when we run out of video
 
-         // so we have to check that the video buffer did have some buffer in the
 
-         // past.
 
-         const lastVideoRange = findRange(videoBuffered, currentTime - 3);
 
-         const videoRange = findRange(videoBuffered, currentTime);
 
-         const audioRange = findRange(audioBuffered, currentTime);
 
-         if (audioRange.length && !videoRange.length && lastVideoRange.length) {
 
-           gap = {
 
-             start: lastVideoRange.end(0),
 
-             end: audioRange.end(0)
 
-           };
 
-         } // find a gap in muxed content.
 
-       } else {
 
-         const nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are
 
-         // stuck in a gap due to video underflow.
 
-         if (!nextRange.length) {
 
-           gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime);
 
-         }
 
-       }
 
-       if (gap) {
 
-         this.logger_(`Encountered a gap in video from ${gap.start} to ${gap.end}. ` + `Seeking to current time ${currentTime}`);
 
-         return true;
 
-       }
 
-       return false;
 
-     }
 
-     /**
 
-      * Timer callback. If playback still has not proceeded, then we seek
 
-      * to the start of the next buffered region.
 
-      *
 
-      * @private
 
-      */
 
-     skipTheGap_(scheduledCurrentTime) {
 
-       const buffered = this.tech_.buffered();
 
-       const currentTime = this.tech_.currentTime();
 
-       const nextRange = findNextRange(buffered, currentTime);
 
-       this.resetTimeUpdate_();
 
-       if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
 
-         return;
 
-       }
 
-       this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
 
-       this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
 
-       this.tech_.trigger({
 
-         type: 'usage',
 
-         name: 'vhs-gap-skip'
 
-       });
 
-     }
 
-     gapFromVideoUnderflow_(buffered, currentTime) {
 
-       // At least in Chrome, if there is a gap in the video buffer, the audio will continue
 
-       // playing for ~3 seconds after the video gap starts. This is done to account for
 
-       // video buffer underflow/underrun (note that this is not done when there is audio
 
-       // buffer underflow/underrun -- in that case the video will stop as soon as it
 
-       // encounters the gap, as audio stalls are more noticeable/jarring to a user than
 
-       // video stalls). The player's time will reflect the playthrough of audio, so the
 
-       // time will appear as if we are in a buffered region, even if we are stuck in a
 
-       // "gap."
 
-       //
 
-       // Example:
 
-       // video buffer:   0 => 10.1, 10.2 => 20
 
-       // audio buffer:   0 => 20
 
-       // overall buffer: 0 => 10.1, 10.2 => 20
 
-       // current time: 13
 
-       //
 
-       // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
 
-       // however, the audio continued playing until it reached ~3 seconds past the gap
 
-       // (13 seconds), at which point it stops as well. Since current time is past the
 
-       // gap, findNextRange will return no ranges.
 
-       //
 
-       // To check for this issue, we see if there is a gap that starts somewhere within
 
-       // a 3 second range (3 seconds +/- 1 second) back from our current time.
 
-       const gaps = findGaps(buffered);
 
-       for (let i = 0; i < gaps.length; i++) {
 
-         const start = gaps.start(i);
 
-         const end = gaps.end(i); // gap is starts no more than 4 seconds back
 
-         if (currentTime - start < 4 && currentTime - start > 2) {
 
-           return {
 
-             start,
 
-             end
 
-           };
 
-         }
 
-       }
 
-       return null;
 
-     }
 
-   }
 
-   const defaultOptions = {
 
-     errorInterval: 30,
 
-     getSource(next) {
 
-       const tech = this.tech({
 
-         IWillNotUseThisInPlugins: true
 
-       });
 
-       const sourceObj = tech.currentSource_ || this.currentSource();
 
-       return next(sourceObj);
 
-     }
 
-   };
 
-   /**
 
-    * Main entry point for the plugin
 
-    *
 
-    * @param {Player} player a reference to a videojs Player instance
 
-    * @param {Object} [options] an object with plugin options
 
-    * @private
 
-    */
 
-   const initPlugin = function (player, options) {
 
-     let lastCalled = 0;
 
-     let seekTo = 0;
 
-     const localOptions = merge(defaultOptions, options);
 
-     player.ready(() => {
 
-       player.trigger({
 
-         type: 'usage',
 
-         name: 'vhs-error-reload-initialized'
 
-       });
 
-     });
 
-     /**
 
-      * Player modifications to perform that must wait until `loadedmetadata`
 
-      * has been triggered
 
-      *
 
-      * @private
 
-      */
 
-     const loadedMetadataHandler = function () {
 
-       if (seekTo) {
 
-         player.currentTime(seekTo);
 
-       }
 
-     };
 
-     /**
 
-      * Set the source on the player element, play, and seek if necessary
 
-      *
 
-      * @param {Object} sourceObj An object specifying the source url and mime-type to play
 
-      * @private
 
-      */
 
-     const setSource = function (sourceObj) {
 
-       if (sourceObj === null || sourceObj === undefined) {
 
-         return;
 
-       }
 
-       seekTo = player.duration() !== Infinity && player.currentTime() || 0;
 
-       player.one('loadedmetadata', loadedMetadataHandler);
 
-       player.src(sourceObj);
 
-       player.trigger({
 
-         type: 'usage',
 
-         name: 'vhs-error-reload'
 
-       });
 
-       player.play();
 
-     };
 
-     /**
 
-      * Attempt to get a source from either the built-in getSource function
 
-      * or a custom function provided via the options
 
-      *
 
-      * @private
 
-      */
 
-     const errorHandler = function () {
 
-       // Do not attempt to reload the source if a source-reload occurred before
 
-       // 'errorInterval' time has elapsed since the last source-reload
 
-       if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
 
-         player.trigger({
 
-           type: 'usage',
 
-           name: 'vhs-error-reload-canceled'
 
-         });
 
-         return;
 
-       }
 
-       if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
 
-         videojs.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
 
-         return;
 
-       }
 
-       lastCalled = Date.now();
 
-       return localOptions.getSource.call(player, setSource);
 
-     };
 
-     /**
 
-      * Unbind any event handlers that were bound by the plugin
 
-      *
 
-      * @private
 
-      */
 
-     const cleanupEvents = function () {
 
-       player.off('loadedmetadata', loadedMetadataHandler);
 
-       player.off('error', errorHandler);
 
-       player.off('dispose', cleanupEvents);
 
-     };
 
-     /**
 
-      * Cleanup before re-initializing the plugin
 
-      *
 
-      * @param {Object} [newOptions] an object with plugin options
 
-      * @private
 
-      */
 
-     const reinitPlugin = function (newOptions) {
 
-       cleanupEvents();
 
-       initPlugin(player, newOptions);
 
-     };
 
-     player.on('error', errorHandler);
 
-     player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
 
-     // initializing the plugin
 
-     player.reloadSourceOnError = reinitPlugin;
 
-   };
 
-   /**
 
-    * Reload the source when an error is detected as long as there
 
-    * wasn't an error previously within the last 30 seconds
 
-    *
 
-    * @param {Object} [options] an object with plugin options
 
-    */
 
-   const reloadSourceOnError = function (options) {
 
-     initPlugin(this, options);
 
-   };
 
-   var version$4 = "3.0.2";
 
-   var version$3 = "6.3.0";
 
-   var version$2 = "1.0.1";
 
-   var version$1 = "6.0.0";
 
-   var version = "4.0.1";
 
-   /**
 
-    * @file videojs-http-streaming.js
 
-    *
 
-    * The main file for the VHS project.
 
-    * License: https://github.com/videojs/videojs-http-streaming/blob/main/LICENSE
 
-    */
 
-   const Vhs = {
 
-     PlaylistLoader,
 
-     Playlist,
 
-     utils,
 
-     STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
 
-     INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
 
-     lastBandwidthSelector,
 
-     movingAverageBandwidthSelector,
 
-     comparePlaylistBandwidth,
 
-     comparePlaylistResolution,
 
-     xhr: xhrFactory()
 
-   }; // Define getter/setters for config properties
 
-   Object.keys(Config).forEach(prop => {
 
-     Object.defineProperty(Vhs, prop, {
 
-       get() {
 
-         videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
 
-         return Config[prop];
 
-       },
 
-       set(value) {
 
-         videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
 
-         if (typeof value !== 'number' || value < 0) {
 
-           videojs.log.warn(`value of Vhs.${prop} must be greater than or equal to 0`);
 
-           return;
 
-         }
 
-         Config[prop] = value;
 
-       }
 
-     });
 
-   });
 
-   const LOCAL_STORAGE_KEY = 'videojs-vhs';
 
-   /**
 
-    * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs.
 
-    *
 
-    * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
 
-    * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
 
-    * @function handleVhsMediaChange
 
-    */
 
-   const handleVhsMediaChange = function (qualityLevels, playlistLoader) {
 
-     const newPlaylist = playlistLoader.media();
 
-     let selectedIndex = -1;
 
-     for (let i = 0; i < qualityLevels.length; i++) {
 
-       if (qualityLevels[i].id === newPlaylist.id) {
 
-         selectedIndex = i;
 
-         break;
 
-       }
 
-     }
 
-     qualityLevels.selectedIndex_ = selectedIndex;
 
-     qualityLevels.trigger({
 
-       selectedIndex,
 
-       type: 'change'
 
-     });
 
-   };
 
-   /**
 
-    * Adds quality levels to list once playlist metadata is available
 
-    *
 
-    * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
 
-    * @param {Object} vhs Vhs object to listen to for media events.
 
-    * @function handleVhsLoadedMetadata
 
-    */
 
-   const handleVhsLoadedMetadata = function (qualityLevels, vhs) {
 
-     vhs.representations().forEach(rep => {
 
-       qualityLevels.addQualityLevel(rep);
 
-     });
 
-     handleVhsMediaChange(qualityLevels, vhs.playlists);
 
-   }; // VHS is a source handler, not a tech. Make sure attempts to use it
 
-   // as one do not cause exceptions.
 
-   Vhs.canPlaySource = function () {
 
-     return videojs.log.warn('VHS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
 
-   };
 
-   const emeKeySystems = (keySystemOptions, mainPlaylist, audioPlaylist) => {
 
-     if (!keySystemOptions) {
 
-       return keySystemOptions;
 
-     }
 
-     let codecs = {};
 
-     if (mainPlaylist && mainPlaylist.attributes && mainPlaylist.attributes.CODECS) {
 
-       codecs = unwrapCodecList(parseCodecs(mainPlaylist.attributes.CODECS));
 
-     }
 
-     if (audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS) {
 
-       codecs.audio = audioPlaylist.attributes.CODECS;
 
-     }
 
-     const videoContentType = getMimeForCodec(codecs.video);
 
-     const audioContentType = getMimeForCodec(codecs.audio); // upsert the content types based on the selected playlist
 
-     const keySystemContentTypes = {};
 
-     for (const keySystem in keySystemOptions) {
 
-       keySystemContentTypes[keySystem] = {};
 
-       if (audioContentType) {
 
-         keySystemContentTypes[keySystem].audioContentType = audioContentType;
 
-       }
 
-       if (videoContentType) {
 
-         keySystemContentTypes[keySystem].videoContentType = videoContentType;
 
-       } // Default to using the video playlist's PSSH even though they may be different, as
 
-       // videojs-contrib-eme will only accept one in the options.
 
-       //
 
-       // This shouldn't be an issue for most cases as early intialization will handle all
 
-       // unique PSSH values, and if they aren't, then encrypted events should have the
 
-       // specific information needed for the unique license.
 
-       if (mainPlaylist.contentProtection && mainPlaylist.contentProtection[keySystem] && mainPlaylist.contentProtection[keySystem].pssh) {
 
-         keySystemContentTypes[keySystem].pssh = mainPlaylist.contentProtection[keySystem].pssh;
 
-       } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
 
-       // so we need to prevent overwriting the URL entirely
 
-       if (typeof keySystemOptions[keySystem] === 'string') {
 
-         keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
 
-       }
 
-     }
 
-     return merge(keySystemOptions, keySystemContentTypes);
 
-   };
 
-   /**
 
-    * @typedef {Object} KeySystems
 
-    *
 
-    * keySystems configuration for https://github.com/videojs/videojs-contrib-eme
 
-    * Note: not all options are listed here.
 
-    *
 
-    * @property {Uint8Array} [pssh]
 
-    *           Protection System Specific Header
 
-    */
 
-   /**
 
-    * Goes through all the playlists and collects an array of KeySystems options objects
 
-    * containing each playlist's keySystems and their pssh values, if available.
 
-    *
 
-    * @param {Object[]} playlists
 
-    *        The playlists to look through
 
-    * @param {string[]} keySystems
 
-    *        The keySystems to collect pssh values for
 
-    *
 
-    * @return {KeySystems[]}
 
-    *         An array of KeySystems objects containing available key systems and their
 
-    *         pssh values
 
-    */
 
-   const getAllPsshKeySystemsOptions = (playlists, keySystems) => {
 
-     return playlists.reduce((keySystemsArr, playlist) => {
 
-       if (!playlist.contentProtection) {
 
-         return keySystemsArr;
 
-       }
 
-       const keySystemsOptions = keySystems.reduce((keySystemsObj, keySystem) => {
 
-         const keySystemOptions = playlist.contentProtection[keySystem];
 
-         if (keySystemOptions && keySystemOptions.pssh) {
 
-           keySystemsObj[keySystem] = {
 
-             pssh: keySystemOptions.pssh
 
-           };
 
-         }
 
-         return keySystemsObj;
 
-       }, {});
 
-       if (Object.keys(keySystemsOptions).length) {
 
-         keySystemsArr.push(keySystemsOptions);
 
-       }
 
-       return keySystemsArr;
 
-     }, []);
 
-   };
 
-   /**
 
-    * Returns a promise that waits for the
 
-    * [eme plugin](https://github.com/videojs/videojs-contrib-eme) to create a key session.
 
-    *
 
-    * Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 in non-IE11
 
-    * browsers.
 
-    *
 
-    * As per the above ticket, this is particularly important for Chrome, where, if
 
-    * unencrypted content is appended before encrypted content and the key session has not
 
-    * been created, a MEDIA_ERR_DECODE will be thrown once the encrypted content is reached
 
-    * during playback.
 
-    *
 
-    * @param {Object} player
 
-    *        The player instance
 
-    * @param {Object[]} sourceKeySystems
 
-    *        The key systems options from the player source
 
-    * @param {Object} [audioMedia]
 
-    *        The active audio media playlist (optional)
 
-    * @param {Object[]} mainPlaylists
 
-    *        The playlists found on the main playlist object
 
-    *
 
-    * @return {Object}
 
-    *         Promise that resolves when the key session has been created
 
-    */
 
-   const waitForKeySessionCreation = ({
 
-     player,
 
-     sourceKeySystems,
 
-     audioMedia,
 
-     mainPlaylists
 
-   }) => {
 
-     if (!player.eme.initializeMediaKeys) {
 
-       return Promise.resolve();
 
-     } // TODO should all audio PSSH values be initialized for DRM?
 
-     //
 
-     // All unique video rendition pssh values are initialized for DRM, but here only
 
-     // the initial audio playlist license is initialized. In theory, an encrypted
 
-     // event should be fired if the user switches to an alternative audio playlist
 
-     // where a license is required, but this case hasn't yet been tested. In addition, there
 
-     // may be many alternate audio playlists unlikely to be used (e.g., multiple different
 
-     // languages).
 
-     const playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists;
 
-     const keySystemsOptionsArr = getAllPsshKeySystemsOptions(playlists, Object.keys(sourceKeySystems));
 
-     const initializationFinishedPromises = [];
 
-     const keySessionCreatedPromises = []; // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The
 
-     // only place where it should not be deduped is for ms-prefixed APIs, but the early
 
-     // return for IE11 above, and the existence of modern EME APIs in addition to
 
-     // ms-prefixed APIs on Edge should prevent this from being a concern.
 
-     // initializeMediaKeys also won't use the webkit-prefixed APIs.
 
-     keySystemsOptionsArr.forEach(keySystemsOptions => {
 
-       keySessionCreatedPromises.push(new Promise((resolve, reject) => {
 
-         player.tech_.one('keysessioncreated', resolve);
 
-       }));
 
-       initializationFinishedPromises.push(new Promise((resolve, reject) => {
 
-         player.eme.initializeMediaKeys({
 
-           keySystems: keySystemsOptions
 
-         }, err => {
 
-           if (err) {
 
-             reject(err);
 
-             return;
 
-           }
 
-           resolve();
 
-         });
 
-       }));
 
-     }); // The reasons Promise.race is chosen over Promise.any:
 
-     //
 
-     // * Promise.any is only available in Safari 14+.
 
-     // * None of these promises are expected to reject. If they do reject, it might be
 
-     //   better here for the race to surface the rejection, rather than mask it by using
 
-     //   Promise.any.
 
-     return Promise.race([
 
-     // If a session was previously created, these will all finish resolving without
 
-     // creating a new session, otherwise it will take until the end of all license
 
-     // requests, which is why the key session check is used (to make setup much faster).
 
-     Promise.all(initializationFinishedPromises),
 
-     // Once a single session is created, the browser knows DRM will be used.
 
-     Promise.race(keySessionCreatedPromises)]);
 
-   };
 
-   /**
 
-    * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and
 
-    * there are keySystems on the source, sets up source options to prepare the source for
 
-    * eme.
 
-    *
 
-    * @param {Object} player
 
-    *        The player instance
 
-    * @param {Object[]} sourceKeySystems
 
-    *        The key systems options from the player source
 
-    * @param {Object} media
 
-    *        The active media playlist
 
-    * @param {Object} [audioMedia]
 
-    *        The active audio media playlist (optional)
 
-    *
 
-    * @return {boolean}
 
-    *         Whether or not options were configured and EME is available
 
-    */
 
-   const setupEmeOptions = ({
 
-     player,
 
-     sourceKeySystems,
 
-     media,
 
-     audioMedia
 
-   }) => {
 
-     const sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia);
 
-     if (!sourceOptions) {
 
-       return false;
 
-     }
 
-     player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing
 
-     // do nothing.
 
-     if (sourceOptions && !player.eme) {
 
-       videojs.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin');
 
-       return false;
 
-     }
 
-     return true;
 
-   };
 
-   const getVhsLocalStorage = () => {
 
-     if (!window.localStorage) {
 
-       return null;
 
-     }
 
-     const storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY);
 
-     if (!storedObject) {
 
-       return null;
 
-     }
 
-     try {
 
-       return JSON.parse(storedObject);
 
-     } catch (e) {
 
-       // someone may have tampered with the value
 
-       return null;
 
-     }
 
-   };
 
-   const updateVhsLocalStorage = options => {
 
-     if (!window.localStorage) {
 
-       return false;
 
-     }
 
-     let objectToStore = getVhsLocalStorage();
 
-     objectToStore = objectToStore ? merge(objectToStore, options) : options;
 
-     try {
 
-       window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(objectToStore));
 
-     } catch (e) {
 
-       // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
 
-       // storage is set to 0).
 
-       // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
 
-       // No need to perform any operation.
 
-       return false;
 
-     }
 
-     return objectToStore;
 
-   };
 
-   /**
 
-    * Parses VHS-supported media types from data URIs. See
 
-    * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
 
-    * for information on data URIs.
 
-    *
 
-    * @param {string} dataUri
 
-    *        The data URI
 
-    *
 
-    * @return {string|Object}
 
-    *         The parsed object/string, or the original string if no supported media type
 
-    *         was found
 
-    */
 
-   const expandDataUri = dataUri => {
 
-     if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) {
 
-       return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1));
 
-     } // no known case for this data URI, return the string as-is
 
-     return dataUri;
 
-   };
 
-   /**
 
-    * Whether the browser has built-in HLS support.
 
-    */
 
-   Vhs.supportsNativeHls = function () {
 
-     if (!document || !document.createElement) {
 
-       return false;
 
-     }
 
-     const video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
 
-     if (!videojs.getTech('Html5').isSupported()) {
 
-       return false;
 
-     } // HLS manifests can go by many mime-types
 
-     const canPlay = [
 
-     // Apple santioned
 
-     'application/vnd.apple.mpegurl',
 
-     // Apple sanctioned for backwards compatibility
 
-     'audio/mpegurl',
 
-     // Very common
 
-     'audio/x-mpegurl',
 
-     // Very common
 
-     'application/x-mpegurl',
 
-     // Included for completeness
 
-     'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
 
-     return canPlay.some(function (canItPlay) {
 
-       return /maybe|probably/i.test(video.canPlayType(canItPlay));
 
-     });
 
-   }();
 
-   Vhs.supportsNativeDash = function () {
 
-     if (!document || !document.createElement || !videojs.getTech('Html5').isSupported()) {
 
-       return false;
 
-     }
 
-     return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
 
-   }();
 
-   Vhs.supportsTypeNatively = type => {
 
-     if (type === 'hls') {
 
-       return Vhs.supportsNativeHls;
 
-     }
 
-     if (type === 'dash') {
 
-       return Vhs.supportsNativeDash;
 
-     }
 
-     return false;
 
-   };
 
-   /**
 
-    * VHS is a source handler, not a tech. Make sure attempts to use it
 
-    * as one do not cause exceptions.
 
-    */
 
-   Vhs.isSupported = function () {
 
-     return videojs.log.warn('VHS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
 
-   };
 
-   const Component = videojs.getComponent('Component');
 
-   /**
 
-    * The Vhs Handler object, where we orchestrate all of the parts
 
-    * of VHS to interact with video.js
 
-    *
 
-    * @class VhsHandler
 
-    * @extends videojs.Component
 
-    * @param {Object} source the soruce object
 
-    * @param {Tech} tech the parent tech object
 
-    * @param {Object} options optional and required options
 
-    */
 
-   class VhsHandler extends Component {
 
-     constructor(source, tech, options) {
 
-       super(tech, options.vhs); // if a tech level `initialBandwidth` option was passed
 
-       // use that over the VHS level `bandwidth` option
 
-       if (typeof options.initialBandwidth === 'number') {
 
-         this.options_.bandwidth = options.initialBandwidth;
 
-       }
 
-       this.logger_ = logger('VhsHandler'); // we need access to the player in some cases,
 
-       // so, get it from Video.js via the `playerId`
 
-       if (tech.options_ && tech.options_.playerId) {
 
-         const _player = videojs.getPlayer(tech.options_.playerId);
 
-         this.player_ = _player;
 
-       }
 
-       this.tech_ = tech;
 
-       this.source_ = source;
 
-       this.stats = {};
 
-       this.ignoreNextSeekingEvent_ = false;
 
-       this.setOptions_();
 
-       if (this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
 
-         tech.overrideNativeAudioTracks(true);
 
-         tech.overrideNativeVideoTracks(true);
 
-       } else if (this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
 
-         // overriding native VHS only works if audio tracks have been emulated
 
-         // error early if we're misconfigured
 
-         throw new Error('Overriding native VHS requires emulated tracks. ' + 'See https://git.io/vMpjB');
 
-       } // listen for fullscreenchange events for this player so that we
 
-       // can adjust our quality selection quickly
 
-       this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], event => {
 
-         const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
 
-         if (fullscreenElement && fullscreenElement.contains(this.tech_.el())) {
 
-           this.playlistController_.fastQualityChange_();
 
-         } else {
 
-           // When leaving fullscreen, since the in page pixel dimensions should be smaller
 
-           // than full screen, see if there should be a rendition switch down to preserve
 
-           // bandwidth.
 
-           this.playlistController_.checkABR_();
 
-         }
 
-       });
 
-       this.on(this.tech_, 'seeking', function () {
 
-         if (this.ignoreNextSeekingEvent_) {
 
-           this.ignoreNextSeekingEvent_ = false;
 
-           return;
 
-         }
 
-         this.setCurrentTime(this.tech_.currentTime());
 
-       });
 
-       this.on(this.tech_, 'error', function () {
 
-         // verify that the error was real and we are loaded
 
-         // enough to have pc loaded.
 
-         if (this.tech_.error() && this.playlistController_) {
 
-           this.playlistController_.pauseLoading();
 
-         }
 
-       });
 
-       this.on(this.tech_, 'play', this.play);
 
-     }
 
-     setOptions_() {
 
-       // defaults
 
-       this.options_.withCredentials = this.options_.withCredentials || false;
 
-       this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
 
-       this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
 
-       this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
 
-       this.options_.useNetworkInformationApi = this.options_.useNetworkInformationApi || false;
 
-       this.options_.useDtsForTimestampOffset = this.options_.useDtsForTimestampOffset || false;
 
-       this.options_.customTagParsers = this.options_.customTagParsers || [];
 
-       this.options_.customTagMappers = this.options_.customTagMappers || [];
 
-       this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
 
-       this.options_.llhls = this.options_.llhls === false ? false : true;
 
-       this.options_.bufferBasedABR = this.options_.bufferBasedABR || false;
 
-       if (typeof this.options_.playlistExclusionDuration !== 'number') {
 
-         this.options_.playlistExclusionDuration = 5 * 60;
 
-       }
 
-       if (typeof this.options_.bandwidth !== 'number') {
 
-         if (this.options_.useBandwidthFromLocalStorage) {
 
-           const storedObject = getVhsLocalStorage();
 
-           if (storedObject && storedObject.bandwidth) {
 
-             this.options_.bandwidth = storedObject.bandwidth;
 
-             this.tech_.trigger({
 
-               type: 'usage',
 
-               name: 'vhs-bandwidth-from-local-storage'
 
-             });
 
-           }
 
-           if (storedObject && storedObject.throughput) {
 
-             this.options_.throughput = storedObject.throughput;
 
-             this.tech_.trigger({
 
-               type: 'usage',
 
-               name: 'vhs-throughput-from-local-storage'
 
-             });
 
-           }
 
-         }
 
-       } // if bandwidth was not set by options or pulled from local storage, start playlist
 
-       // selection at a reasonable bandwidth
 
-       if (typeof this.options_.bandwidth !== 'number') {
 
-         this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
 
-       } // If the bandwidth number is unchanged from the initial setting
 
-       // then this takes precedence over the enableLowInitialPlaylist option
 
-       this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
 
-       ['withCredentials', 'useDevicePixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'customTagParsers', 'customTagMappers', 'cacheEncryptionKeys', 'playlistSelector', 'initialPlaylistSelector', 'bufferBasedABR', 'liveRangeSafeTimeDelta', 'llhls', 'useNetworkInformationApi', 'useDtsForTimestampOffset', 'exactManifestTimings', 'leastPixelDiffSelector'].forEach(option => {
 
-         if (typeof this.source_[option] !== 'undefined') {
 
-           this.options_[option] = this.source_[option];
 
-         }
 
-       });
 
-       this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
 
-       this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
 
-     }
 
-     /**
 
-      * called when player.src gets called, handle a new source
 
-      *
 
-      * @param {Object} src the source object to handle
 
-      */
 
-     src(src, type) {
 
-       // do nothing if the src is falsey
 
-       if (!src) {
 
-         return;
 
-       }
 
-       this.setOptions_(); // add main playlist controller options
 
-       this.options_.src = expandDataUri(this.source_.src);
 
-       this.options_.tech = this.tech_;
 
-       this.options_.externVhs = Vhs;
 
-       this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update the tech
 
-       this.options_.seekTo = time => {
 
-         this.tech_.setCurrentTime(time);
 
-       };
 
-       this.playlistController_ = new PlaylistController(this.options_);
 
-       const playbackWatcherOptions = merge({
 
-         liveRangeSafeTimeDelta: SAFE_TIME_DELTA
 
-       }, this.options_, {
 
-         seekable: () => this.seekable(),
 
-         media: () => this.playlistController_.media(),
 
-         playlistController: this.playlistController_
 
-       });
 
-       this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions);
 
-       this.playlistController_.on('error', () => {
 
-         const player = videojs.players[this.tech_.options_.playerId];
 
-         let error = this.playlistController_.error;
 
-         if (typeof error === 'object' && !error.code) {
 
-           error.code = 3;
 
-         } else if (typeof error === 'string') {
 
-           error = {
 
-             message: error,
 
-             code: 3
 
-           };
 
-         }
 
-         player.error(error);
 
-       });
 
-       const defaultSelector = this.options_.bufferBasedABR ? Vhs.movingAverageBandwidthSelector(0.55) : Vhs.STANDARD_PLAYLIST_SELECTOR; // `this` in selectPlaylist should be the VhsHandler for backwards
 
-       // compatibility with < v2
 
-       this.playlistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : defaultSelector.bind(this);
 
-       this.playlistController_.selectInitialPlaylist = Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
 
-       this.playlists = this.playlistController_.mainPlaylistLoader_;
 
-       this.mediaSource = this.playlistController_.mediaSource; // Proxy assignment of some properties to the main playlist
 
-       // controller. Using a custom property for backwards compatibility
 
-       // with < v2
 
-       Object.defineProperties(this, {
 
-         selectPlaylist: {
 
-           get() {
 
-             return this.playlistController_.selectPlaylist;
 
-           },
 
-           set(selectPlaylist) {
 
-             this.playlistController_.selectPlaylist = selectPlaylist.bind(this);
 
-           }
 
-         },
 
-         throughput: {
 
-           get() {
 
-             return this.playlistController_.mainSegmentLoader_.throughput.rate;
 
-           },
 
-           set(throughput) {
 
-             this.playlistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
 
-             // for the cumulative average
 
-             this.playlistController_.mainSegmentLoader_.throughput.count = 1;
 
-           }
 
-         },
 
-         bandwidth: {
 
-           get() {
 
-             let playerBandwidthEst = this.playlistController_.mainSegmentLoader_.bandwidth;
 
-             const networkInformation = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection;
 
-             const tenMbpsAsBitsPerSecond = 10e6;
 
-             if (this.options_.useNetworkInformationApi && networkInformation) {
 
-               // downlink returns Mbps
 
-               // https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
 
-               const networkInfoBandwidthEstBitsPerSec = networkInformation.downlink * 1000 * 1000; // downlink maxes out at 10 Mbps. In the event that both networkInformationApi and the player
 
-               // estimate a bandwidth greater than 10 Mbps, use the larger of the two estimates to ensure that
 
-               // high quality streams are not filtered out.
 
-               if (networkInfoBandwidthEstBitsPerSec >= tenMbpsAsBitsPerSecond && playerBandwidthEst >= tenMbpsAsBitsPerSecond) {
 
-                 playerBandwidthEst = Math.max(playerBandwidthEst, networkInfoBandwidthEstBitsPerSec);
 
-               } else {
 
-                 playerBandwidthEst = networkInfoBandwidthEstBitsPerSec;
 
-               }
 
-             }
 
-             return playerBandwidthEst;
 
-           },
 
-           set(bandwidth) {
 
-             this.playlistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
 
-             // `count` is set to zero that current value of `rate` isn't included
 
-             // in the cumulative average
 
-             this.playlistController_.mainSegmentLoader_.throughput = {
 
-               rate: 0,
 
-               count: 0
 
-             };
 
-           }
 
-         },
 
-         /**
 
-          * `systemBandwidth` is a combination of two serial processes bit-rates. The first
 
-          * is the network bitrate provided by `bandwidth` and the second is the bitrate of
 
-          * the entire process after that - decryption, transmuxing, and appending - provided
 
-          * by `throughput`.
 
-          *
 
-          * Since the two process are serial, the overall system bandwidth is given by:
 
-          *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
 
-          */
 
-         systemBandwidth: {
 
-           get() {
 
-             const invBandwidth = 1 / (this.bandwidth || 1);
 
-             let invThroughput;
 
-             if (this.throughput > 0) {
 
-               invThroughput = 1 / this.throughput;
 
-             } else {
 
-               invThroughput = 0;
 
-             }
 
-             const systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
 
-             return systemBitrate;
 
-           },
 
-           set() {
 
-             videojs.log.error('The "systemBandwidth" property is read-only');
 
-           }
 
-         }
 
-       });
 
-       if (this.options_.bandwidth) {
 
-         this.bandwidth = this.options_.bandwidth;
 
-       }
 
-       if (this.options_.throughput) {
 
-         this.throughput = this.options_.throughput;
 
-       }
 
-       Object.defineProperties(this.stats, {
 
-         bandwidth: {
 
-           get: () => this.bandwidth || 0,
 
-           enumerable: true
 
-         },
 
-         mediaRequests: {
 
-           get: () => this.playlistController_.mediaRequests_() || 0,
 
-           enumerable: true
 
-         },
 
-         mediaRequestsAborted: {
 
-           get: () => this.playlistController_.mediaRequestsAborted_() || 0,
 
-           enumerable: true
 
-         },
 
-         mediaRequestsTimedout: {
 
-           get: () => this.playlistController_.mediaRequestsTimedout_() || 0,
 
-           enumerable: true
 
-         },
 
-         mediaRequestsErrored: {
 
-           get: () => this.playlistController_.mediaRequestsErrored_() || 0,
 
-           enumerable: true
 
-         },
 
-         mediaTransferDuration: {
 
-           get: () => this.playlistController_.mediaTransferDuration_() || 0,
 
-           enumerable: true
 
-         },
 
-         mediaBytesTransferred: {
 
-           get: () => this.playlistController_.mediaBytesTransferred_() || 0,
 
-           enumerable: true
 
-         },
 
-         mediaSecondsLoaded: {
 
-           get: () => this.playlistController_.mediaSecondsLoaded_() || 0,
 
-           enumerable: true
 
-         },
 
-         mediaAppends: {
 
-           get: () => this.playlistController_.mediaAppends_() || 0,
 
-           enumerable: true
 
-         },
 
-         mainAppendsToLoadedData: {
 
-           get: () => this.playlistController_.mainAppendsToLoadedData_() || 0,
 
-           enumerable: true
 
-         },
 
-         audioAppendsToLoadedData: {
 
-           get: () => this.playlistController_.audioAppendsToLoadedData_() || 0,
 
-           enumerable: true
 
-         },
 
-         appendsToLoadedData: {
 
-           get: () => this.playlistController_.appendsToLoadedData_() || 0,
 
-           enumerable: true
 
-         },
 
-         timeToLoadedData: {
 
-           get: () => this.playlistController_.timeToLoadedData_() || 0,
 
-           enumerable: true
 
-         },
 
-         buffered: {
 
-           get: () => timeRangesToArray(this.tech_.buffered()),
 
-           enumerable: true
 
-         },
 
-         currentTime: {
 
-           get: () => this.tech_.currentTime(),
 
-           enumerable: true
 
-         },
 
-         currentSource: {
 
-           get: () => this.tech_.currentSource_,
 
-           enumerable: true
 
-         },
 
-         currentTech: {
 
-           get: () => this.tech_.name_,
 
-           enumerable: true
 
-         },
 
-         duration: {
 
-           get: () => this.tech_.duration(),
 
-           enumerable: true
 
-         },
 
-         main: {
 
-           get: () => this.playlists.main,
 
-           enumerable: true
 
-         },
 
-         playerDimensions: {
 
-           get: () => this.tech_.currentDimensions(),
 
-           enumerable: true
 
-         },
 
-         seekable: {
 
-           get: () => timeRangesToArray(this.tech_.seekable()),
 
-           enumerable: true
 
-         },
 
-         timestamp: {
 
-           get: () => Date.now(),
 
-           enumerable: true
 
-         },
 
-         videoPlaybackQuality: {
 
-           get: () => this.tech_.getVideoPlaybackQuality(),
 
-           enumerable: true
 
-         }
 
-       });
 
-       this.tech_.one('canplay', this.playlistController_.setupFirstPlay.bind(this.playlistController_));
 
-       this.tech_.on('bandwidthupdate', () => {
 
-         if (this.options_.useBandwidthFromLocalStorage) {
 
-           updateVhsLocalStorage({
 
-             bandwidth: this.bandwidth,
 
-             throughput: Math.round(this.throughput)
 
-           });
 
-         }
 
-       });
 
-       this.playlistController_.on('selectedinitialmedia', () => {
 
-         // Add the manual rendition mix-in to VhsHandler
 
-         renditionSelectionMixin(this);
 
-       });
 
-       this.playlistController_.sourceUpdater_.on('createdsourcebuffers', () => {
 
-         this.setupEme_();
 
-       }); // the bandwidth of the primary segment loader is our best
 
-       // estimate of overall bandwidth
 
-       this.on(this.playlistController_, 'progress', function () {
 
-         this.tech_.trigger('progress');
 
-       }); // In the live case, we need to ignore the very first `seeking` event since
 
-       // that will be the result of the seek-to-live behavior
 
-       this.on(this.playlistController_, 'firstplay', function () {
 
-         this.ignoreNextSeekingEvent_ = true;
 
-       });
 
-       this.setupQualityLevels_(); // do nothing if the tech has been disposed already
 
-       // this can occur if someone sets the src in player.ready(), for instance
 
-       if (!this.tech_.el()) {
 
-         return;
 
-       }
 
-       this.mediaSourceUrl_ = window.URL.createObjectURL(this.playlistController_.mediaSource);
 
-       this.tech_.src(this.mediaSourceUrl_);
 
-     }
 
-     createKeySessions_() {
 
-       const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
 
-       this.logger_('waiting for EME key session creation');
 
-       waitForKeySessionCreation({
 
-         player: this.player_,
 
-         sourceKeySystems: this.source_.keySystems,
 
-         audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
 
-         mainPlaylists: this.playlists.main.playlists
 
-       }).then(() => {
 
-         this.logger_('created EME key session');
 
-         this.playlistController_.sourceUpdater_.initializedEme();
 
-       }).catch(err => {
 
-         this.logger_('error while creating EME key session', err);
 
-         this.player_.error({
 
-           message: 'Failed to initialize media keys for EME',
 
-           code: 3
 
-         });
 
-       });
 
-     }
 
-     handleWaitingForKey_() {
 
-       // If waitingforkey is fired, it's possible that the data that's necessary to retrieve
 
-       // the key is in the manifest. While this should've happened on initial source load, it
 
-       // may happen again in live streams where the keys change, and the manifest info
 
-       // reflects the update.
 
-       //
 
-       // Because videojs-contrib-eme compares the PSSH data we send to that of PSSH data it's
 
-       // already requested keys for, we don't have to worry about this generating extraneous
 
-       // requests.
 
-       this.logger_('waitingforkey fired, attempting to create any new key sessions');
 
-       this.createKeySessions_();
 
-     }
 
-     /**
 
-      * If necessary and EME is available, sets up EME options and waits for key session
 
-      * creation.
 
-      *
 
-      * This function also updates the source updater so taht it can be used, as for some
 
-      * browsers, EME must be configured before content is appended (if appending unencrypted
 
-      * content before encrypted content).
 
-      */
 
-     setupEme_() {
 
-       const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
 
-       const didSetupEmeOptions = setupEmeOptions({
 
-         player: this.player_,
 
-         sourceKeySystems: this.source_.keySystems,
 
-         media: this.playlists.media(),
 
-         audioMedia: audioPlaylistLoader && audioPlaylistLoader.media()
 
-       });
 
-       this.player_.tech_.on('keystatuschange', e => {
 
-         if (e.status !== 'output-restricted') {
 
-           return;
 
-         }
 
-         const mainPlaylist = this.playlistController_.main();
 
-         if (!mainPlaylist || !mainPlaylist.playlists) {
 
-           return;
 
-         }
 
-         const excludedHDPlaylists = []; // Assume all HD streams are unplayable and exclude them from ABR selection
 
-         mainPlaylist.playlists.forEach(playlist => {
 
-           if (playlist && playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height >= 720) {
 
-             if (!playlist.excludeUntil || playlist.excludeUntil < Infinity) {
 
-               playlist.excludeUntil = Infinity;
 
-               excludedHDPlaylists.push(playlist);
 
-             }
 
-           }
 
-         });
 
-         if (excludedHDPlaylists.length) {
 
-           videojs.log.warn('DRM keystatus changed to "output-restricted." Removing the following HD playlists ' + 'that will most likely fail to play and clearing the buffer. ' + 'This may be due to HDCP restrictions on the stream and the capabilities of the current device.', ...excludedHDPlaylists); // Clear the buffer before switching playlists, since it may already contain unplayable segments
 
-           this.playlistController_.fastQualityChange_();
 
-         }
 
-       });
 
-       this.handleWaitingForKey_ = this.handleWaitingForKey_.bind(this);
 
-       this.player_.tech_.on('waitingforkey', this.handleWaitingForKey_); // In IE11 this is too early to initialize media keys, and IE11 does not support
 
-       // promises.
 
-       if (videojs.browser.IE_VERSION === 11 || !didSetupEmeOptions) {
 
-         // If EME options were not set up, we've done all we could to initialize EME.
 
-         this.playlistController_.sourceUpdater_.initializedEme();
 
-         return;
 
-       }
 
-       this.createKeySessions_();
 
-     }
 
-     /**
 
-      * Initializes the quality levels and sets listeners to update them.
 
-      *
 
-      * @method setupQualityLevels_
 
-      * @private
 
-      */
 
-     setupQualityLevels_() {
 
-       const player = videojs.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin
 
-       // or qualityLevels_ listeners have already been setup, do nothing.
 
-       if (!player || !player.qualityLevels || this.qualityLevels_) {
 
-         return;
 
-       }
 
-       this.qualityLevels_ = player.qualityLevels();
 
-       this.playlistController_.on('selectedinitialmedia', () => {
 
-         handleVhsLoadedMetadata(this.qualityLevels_, this);
 
-       });
 
-       this.playlists.on('mediachange', () => {
 
-         handleVhsMediaChange(this.qualityLevels_, this.playlists);
 
-       });
 
-     }
 
-     /**
 
-      * return the version
 
-      */
 
-     static version() {
 
-       return {
 
-         '@videojs/http-streaming': version$4,
 
-         'mux.js': version$3,
 
-         'mpd-parser': version$2,
 
-         'm3u8-parser': version$1,
 
-         'aes-decrypter': version
 
-       };
 
-     }
 
-     /**
 
-      * return the version
 
-      */
 
-     version() {
 
-       return this.constructor.version();
 
-     }
 
-     canChangeType() {
 
-       return SourceUpdater.canChangeType();
 
-     }
 
-     /**
 
-      * Begin playing the video.
 
-      */
 
-     play() {
 
-       this.playlistController_.play();
 
-     }
 
-     /**
 
-      * a wrapper around the function in PlaylistController
 
-      */
 
-     setCurrentTime(currentTime) {
 
-       this.playlistController_.setCurrentTime(currentTime);
 
-     }
 
-     /**
 
-      * a wrapper around the function in PlaylistController
 
-      */
 
-     duration() {
 
-       return this.playlistController_.duration();
 
-     }
 
-     /**
 
-      * a wrapper around the function in PlaylistController
 
-      */
 
-     seekable() {
 
-       return this.playlistController_.seekable();
 
-     }
 
-     /**
 
-      * Abort all outstanding work and cleanup.
 
-      */
 
-     dispose() {
 
-       if (this.playbackWatcher_) {
 
-         this.playbackWatcher_.dispose();
 
-       }
 
-       if (this.playlistController_) {
 
-         this.playlistController_.dispose();
 
-       }
 
-       if (this.qualityLevels_) {
 
-         this.qualityLevels_.dispose();
 
-       }
 
-       if (this.tech_ && this.tech_.vhs) {
 
-         delete this.tech_.vhs;
 
-       }
 
-       if (this.mediaSourceUrl_ && window.URL.revokeObjectURL) {
 
-         window.URL.revokeObjectURL(this.mediaSourceUrl_);
 
-         this.mediaSourceUrl_ = null;
 
-       }
 
-       if (this.tech_) {
 
-         this.tech_.off('waitingforkey', this.handleWaitingForKey_);
 
-       }
 
-       super.dispose();
 
-     }
 
-     convertToProgramTime(time, callback) {
 
-       return getProgramTime({
 
-         playlist: this.playlistController_.media(),
 
-         time,
 
-         callback
 
-       });
 
-     } // the player must be playing before calling this
 
-     seekToProgramTime(programTime, callback, pauseAfterSeek = true, retryCount = 2) {
 
-       return seekToProgramTime({
 
-         programTime,
 
-         playlist: this.playlistController_.media(),
 
-         retryCount,
 
-         pauseAfterSeek,
 
-         seekTo: this.options_.seekTo,
 
-         tech: this.options_.tech,
 
-         callback
 
-       });
 
-     }
 
-   }
 
-   /**
 
-    * The Source Handler object, which informs video.js what additional
 
-    * MIME types are supported and sets up playback. It is registered
 
-    * automatically to the appropriate tech based on the capabilities of
 
-    * the browser it is running in. It is not necessary to use or modify
 
-    * this object in normal usage.
 
-    */
 
-   const VhsSourceHandler = {
 
-     name: 'videojs-http-streaming',
 
-     VERSION: version$4,
 
-     canHandleSource(srcObj, options = {}) {
 
-       const localOptions = merge(videojs.options, options);
 
-       return VhsSourceHandler.canPlayType(srcObj.type, localOptions);
 
-     },
 
-     handleSource(source, tech, options = {}) {
 
-       const localOptions = merge(videojs.options, options);
 
-       tech.vhs = new VhsHandler(source, tech, localOptions);
 
-       tech.vhs.xhr = xhrFactory();
 
-       tech.vhs.src(source.src, source.type);
 
-       return tech.vhs;
 
-     },
 
-     canPlayType(type, options) {
 
-       const simpleType = simpleTypeFromSourceType(type);
 
-       if (!simpleType) {
 
-         return '';
 
-       }
 
-       const overrideNative = VhsSourceHandler.getOverrideNative(options);
 
-       const supportsTypeNatively = Vhs.supportsTypeNatively(simpleType);
 
-       const canUseMsePlayback = !supportsTypeNatively || overrideNative;
 
-       return canUseMsePlayback ? 'maybe' : '';
 
-     },
 
-     getOverrideNative(options = {}) {
 
-       const {
 
-         vhs = {}
 
-       } = options;
 
-       const defaultOverrideNative = !(videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS);
 
-       const {
 
-         overrideNative = defaultOverrideNative
 
-       } = vhs;
 
-       return overrideNative;
 
-     }
 
-   };
 
-   /**
 
-    * Check to see if the native MediaSource object exists and supports
 
-    * an MP4 container with both H.264 video and AAC-LC audio.
 
-    *
 
-    * @return {boolean} if  native media sources are supported
 
-    */
 
-   const supportsNativeMediaSources = () => {
 
-     return browserSupportsCodec('avc1.4d400d,mp4a.40.2');
 
-   }; // register source handlers with the appropriate techs
 
-   if (supportsNativeMediaSources()) {
 
-     videojs.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0);
 
-   }
 
-   videojs.VhsHandler = VhsHandler;
 
-   videojs.VhsSourceHandler = VhsSourceHandler;
 
-   videojs.Vhs = Vhs;
 
-   if (!videojs.use) {
 
-     videojs.registerComponent('Vhs', Vhs);
 
-   }
 
-   videojs.options.vhs = videojs.options.vhs || {};
 
-   if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
 
-     videojs.registerPlugin('reloadSourceOnError', reloadSourceOnError);
 
-   }
 
-   return videojs;
 
- }));
 
 
  |